[6320] | 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 |
---|