1 | # -*- coding: utf-8 -*- |
---|
2 | """ |
---|
3 | ------------------------------------------------------------------------------ |
---|
4 | Mango 802.11 Reference Design Experiments Framework - Client (STA) Node |
---|
5 | ------------------------------------------------------------------------------ |
---|
6 | License: Copyright 2019 Mango Communications, Inc. All rights reserved. |
---|
7 | Use and distribution subject to terms in LICENSE.txt |
---|
8 | ------------------------------------------------------------------------------ |
---|
9 | |
---|
10 | """ |
---|
11 | |
---|
12 | import wlan_exp.node as node |
---|
13 | import wlan_exp.cmds as cmds |
---|
14 | |
---|
15 | |
---|
16 | __all__ = ['WlanExpNodeSta'] |
---|
17 | |
---|
18 | |
---|
19 | class WlanExpNodeSta(node.WlanExpNode): |
---|
20 | """wlan_exp Node class for the 802.11 Reference Design STA MAC project |
---|
21 | |
---|
22 | Args: |
---|
23 | network_config (transport.NetworkConfiguration) : Network configuration of the node |
---|
24 | """ |
---|
25 | |
---|
26 | #------------------------------------------------------------------------- |
---|
27 | # Node Commands |
---|
28 | #------------------------------------------------------------------------- |
---|
29 | def configure_bss(self, bssid=False, ssid=None, channel=None, beacon_interval=False, ht_capable=None): |
---|
30 | """Configure the BSS information of the node |
---|
31 | |
---|
32 | Each node is either a member of no BSS (colloquially "unassociated") |
---|
33 | or a member of one BSS. A node requires a minimum valid set of BSS |
---|
34 | information to be a member of a BSS. The minimum valid set of BSS |
---|
35 | information for an STA is: |
---|
36 | |
---|
37 | #. BSSID: 48-bit MAC address |
---|
38 | #. Channel: Logical channel for Tx/Rx by BSS members |
---|
39 | #. SSID: Variable length string (ie the name of the network) |
---|
40 | |
---|
41 | If a node is not a member of a BSS (i.e. ``n.get_network_info()`` returns |
---|
42 | ``None``), then the node requires all parameters of a minimum valid |
---|
43 | set of BSS information be specified (i.e. BSSID, Channel, and SSID). |
---|
44 | |
---|
45 | See https://warpproject.org/trac/wiki/802.11/wlan_exp/bss |
---|
46 | for more documentation on BSS information / configuration. |
---|
47 | |
---|
48 | |
---|
49 | Args: |
---|
50 | bssid (int, str): 48-bit ID of the BSS either as a integer or |
---|
51 | colon delimited string of the form ``'01:23:45:67:89:ab'`` |
---|
52 | ssid (str): SSID string (Must be 32 characters or less) |
---|
53 | channel (int): Channel number on which the BSS operates |
---|
54 | beacon_interval (int): Integer number of beacon Time Units in [10, 65534] |
---|
55 | (http://en.wikipedia.org/wiki/TU_(Time_Unit); a TU is 1024 microseconds); |
---|
56 | A value of None will disable beacons; A value of False will not |
---|
57 | update the current beacon interval. |
---|
58 | ht_capable (bool): Is the PHY mode HTMF (True) or NONHT (False)? |
---|
59 | """ |
---|
60 | if (self.is_scanning() is True) and (bssid is None): |
---|
61 | print('Warning: network scan is still running when BSS was set to None. Use stop_active_scan if needed.') |
---|
62 | |
---|
63 | resp_args = self.send_cmd(cmds.NodeConfigBSS(bssid=bssid, ssid=ssid, channel=channel, |
---|
64 | beacon_interval=beacon_interval, ht_capable=ht_capable)) |
---|
65 | |
---|
66 | # Process response arguments |
---|
67 | if (resp_args is not False): |
---|
68 | status = resp_args[0] |
---|
69 | msg = "ERROR:\n" |
---|
70 | ret_val = True |
---|
71 | |
---|
72 | # Check status |
---|
73 | if (status & cmds.ERROR_CONFIG_BSS_BSSID_INVALID): |
---|
74 | if type(bssid) in [int, long]: |
---|
75 | import wlan_exp.util as util |
---|
76 | self.bssid = util.mac_addr_to_str(bssid) |
---|
77 | msg += " BSSID {0} was invalid.\n".format(self.bssid) |
---|
78 | ret_val = False |
---|
79 | |
---|
80 | if (status & cmds.ERROR_CONFIG_BSS_BSSID_INSUFFICIENT_ARGUMENTS): |
---|
81 | msg += " Insufficient arguments to create BSS. Must provide:\n" |
---|
82 | if (bssid is False): |
---|
83 | msg += " BSSID\n" |
---|
84 | if (ssid is None): |
---|
85 | msg += " SSID\n" |
---|
86 | if (channel is None): |
---|
87 | msg += " CHANNEL\n" |
---|
88 | ret_val = False |
---|
89 | |
---|
90 | if (status & cmds.ERROR_CONFIG_BSS_CHANNEL_INVALID): |
---|
91 | msg += " Channel {0} was invalid.\n".format(channel) |
---|
92 | ret_val = False |
---|
93 | |
---|
94 | if (status & cmds.ERROR_CONFIG_BSS_BEACON_INTERVAL_INVALID): |
---|
95 | msg += " Beacon interval {0} was invalid.\n".format(beacon_interval) |
---|
96 | ret_val = False |
---|
97 | |
---|
98 | if (status & cmds.ERROR_CONFIG_BSS_HT_CAPABLE_INVALID): |
---|
99 | msg += " HT capable {0} was invalid.\n".format(ht_capable) |
---|
100 | ret_val = False |
---|
101 | |
---|
102 | if not ret_val: |
---|
103 | print(msg) |
---|
104 | |
---|
105 | |
---|
106 | def disassociate(self): |
---|
107 | """Causes the STA node to transmit a de-authenticate packet and to reset |
---|
108 | its BSS status to null. After this method the STA will be disassociated from |
---|
109 | its former BSS. |
---|
110 | |
---|
111 | """ |
---|
112 | self.send_cmd(cmds.NodeDisassociate()) |
---|
113 | |
---|
114 | |
---|
115 | def is_associated(self, ap): |
---|
116 | """Is the AP in the STA's association table? |
---|
117 | |
---|
118 | Args: |
---|
119 | ap (WlanDevice): WLAN Device |
---|
120 | |
---|
121 | Returns: |
---|
122 | associated (bool): Boolean describing whether the STA is associated with the AP |
---|
123 | |
---|
124 | """ |
---|
125 | ret_val = False |
---|
126 | |
---|
127 | if ap is not None: |
---|
128 | my_station_info = self.get_station_info_list(ap) |
---|
129 | |
---|
130 | if my_station_info is not None: |
---|
131 | ret_val = True |
---|
132 | |
---|
133 | return ret_val |
---|
134 | |
---|
135 | |
---|
136 | |
---|
137 | #------------------------------------------------------------------------- |
---|
138 | # STA specific Commands |
---|
139 | #------------------------------------------------------------------------- |
---|
140 | def _check_allowed_rate(self, mcs, phy_mode, verbose=False): |
---|
141 | """Check that rate parameters are allowed |
---|
142 | |
---|
143 | Args: |
---|
144 | mcs (int): Modulation and coding scheme (MCS) index |
---|
145 | phy_mode (str, int): PHY mode (from util.phy_modes) |
---|
146 | |
---|
147 | Returns: |
---|
148 | valid (bool): Are all parameters valid? |
---|
149 | """ |
---|
150 | # TODO: implement STA-specific rate checking here |
---|
151 | # Allow all supported rates for now |
---|
152 | |
---|
153 | return self._check_supported_rate(mcs, phy_mode, verbose) |
---|
154 | |
---|
155 | |
---|
156 | |
---|
157 | def set_aid(self, aid): |
---|
158 | """Set the Association ID (AID) of the STA. |
---|
159 | |
---|
160 | Normally the AID is assigned by the AP during the probe/authentication/association |
---|
161 | handshake. However if the BSS configuration of the STA is set via the |
---|
162 | configure_bss() method there is no AP-assigned AID. Thankfully this is only a |
---|
163 | cosmetic problem. The reference code does not use the AID for any MAC processing. |
---|
164 | By default the AID is only used to set the hex display at the STA node. This |
---|
165 | method will have the same affect. |
---|
166 | |
---|
167 | Args: |
---|
168 | aid (int): Association ID (must be in [1, 255]) |
---|
169 | """ |
---|
170 | self.send_cmd(cmds.NodeSTASetAID(aid)) |
---|
171 | |
---|
172 | |
---|
173 | def join_network(self, ssid, bssid=None, channel=None, timeout=5.0): |
---|
174 | """Join the specified network (BSS). |
---|
175 | |
---|
176 | By specifying the SSID, the STA will try to join the given network. If |
---|
177 | the bssid and channel of the network are also known, they can be |
---|
178 | provided. Otherwise, the STA will scan for the given SSID to find the |
---|
179 | corresponding bssid and channel. If the SSID is None, then any |
---|
180 | on-going join process will be halted. |
---|
181 | |
---|
182 | If the node is currently associated with the given BSS, then the node |
---|
183 | state is not changed and this method will return "success" immediately. |
---|
184 | |
---|
185 | By default, the join process has a timeout that will automatically |
---|
186 | halt the join process once the timeout is exceeded. Depending on the |
---|
187 | scan parameters, this timeout value may need to be adjusted to allow |
---|
188 | the STA to scan all channels before the timeout period is exceeded. |
---|
189 | If the timeout is None, then the method will return immediately and |
---|
190 | the methods is_joining() and get_network_info() can be used to determine |
---|
191 | if the STA has joined the BSS. |
---|
192 | |
---|
193 | If this method starts an active scan, the scan will use the parameters |
---|
194 | previously configured with node.set_scan_parameters(). |
---|
195 | |
---|
196 | Args: |
---|
197 | ssid (str): SSID string (Must be 32 characters or less); a value |
---|
198 | of None will stop any current join process. |
---|
199 | bssid (int, str): 48-bit ID of the BSS either as a integer or |
---|
200 | colon delimited string of the form: XX:XX:XX:XX:XX:XX |
---|
201 | channel (int): Channel number on which the BSS operates |
---|
202 | timeout (float): Time to complete the join process; a value of |
---|
203 | None will cause the method to return immediately and allow |
---|
204 | the join process to continue forever until the node has joined |
---|
205 | the network or the join has failed. |
---|
206 | |
---|
207 | """ |
---|
208 | status = True |
---|
209 | |
---|
210 | # Check if node is currently associated with ssid / bssid |
---|
211 | # - _check_associated_node() returns True if ssid is None |
---|
212 | if self._check_associated_node(ssid, bssid): |
---|
213 | # Still need to send the Join command to stop join process |
---|
214 | if ssid is None: |
---|
215 | self.send_cmd(cmds.NodeSTAJoin(ssid=None)) |
---|
216 | else: |
---|
217 | # Perform the join process |
---|
218 | |
---|
219 | # Send the join command |
---|
220 | status = self.send_cmd(cmds.NodeSTAJoin(ssid=ssid, bssid=bssid, channel=channel)) |
---|
221 | |
---|
222 | if timeout is not None: |
---|
223 | import time |
---|
224 | |
---|
225 | # Get the start time so that the timeout can be enforced |
---|
226 | start_time = time.time() |
---|
227 | |
---|
228 | # Check when join process completes that STA has joined BSS |
---|
229 | while ((time.time() - start_time) < timeout): |
---|
230 | if self.is_joining(): |
---|
231 | # Sleep for 0.1 seconds to not flood the node |
---|
232 | time.sleep(0.1) |
---|
233 | else: |
---|
234 | status = self._check_associated_node(ssid, bssid) |
---|
235 | break |
---|
236 | |
---|
237 | return status |
---|
238 | |
---|
239 | |
---|
240 | def is_joining(self): |
---|
241 | """Queries if the STA is currently attempting to join a network. The join |
---|
242 | state machine runs until the target network is successfully joined or |
---|
243 | the joing process is terminated by the user. This method tests whether |
---|
244 | the join process is still running. To check success or failure of the |
---|
245 | join process, use node.get_network_info(). |
---|
246 | |
---|
247 | Returns: |
---|
248 | status (bool): |
---|
249 | |
---|
250 | * True -- Inidcates node is currently in the join process |
---|
251 | * False -- Indicates node is not currently in the join process |
---|
252 | """ |
---|
253 | return self.send_cmd(cmds.NodeSTAJoinStatus()) |
---|
254 | |
---|
255 | |
---|
256 | |
---|
257 | #------------------------------------------------------------------------- |
---|
258 | # Internal STA methods |
---|
259 | #------------------------------------------------------------------------- |
---|
260 | def _check_associated_node(self, ssid, bssid): |
---|
261 | """Check if node is currently associated to the given BSS |
---|
262 | |
---|
263 | This method only checks that the bss_info of the node matches the |
---|
264 | provided ssid / bssid and is not a substitute for is_associated(). |
---|
265 | |
---|
266 | This method will only check the bssid after it matches the SSID. If |
---|
267 | either argument is None, then it will not be checked. If SSID is |
---|
268 | None, then the method will return True. |
---|
269 | |
---|
270 | Args: |
---|
271 | ssid (str): SSID of the BSS |
---|
272 | bssid (str): BSSID of the BSS |
---|
273 | |
---|
274 | Returns: |
---|
275 | associated (bool): Do the SSID / BSSID matche the BSS of the node? |
---|
276 | """ |
---|
277 | if ssid is not None: |
---|
278 | network_info = self.get_network_info() |
---|
279 | |
---|
280 | if network_info is None: |
---|
281 | return False |
---|
282 | |
---|
283 | if (network_info['ssid'] != ssid): |
---|
284 | return False |
---|
285 | |
---|
286 | if bssid is not None: |
---|
287 | if (network_info['bssid'] != bssid): |
---|
288 | return False |
---|
289 | |
---|
290 | return True |
---|
291 | |
---|
292 | |
---|
293 | # End class |
---|