1 | # -*- coding: utf-8 -*- |
---|
2 | """ |
---|
3 | ------------------------------------------------------------------------------ |
---|
4 | Mango 802.11 Reference Design Experiments Framework |
---|
5 | - Information Struct classes |
---|
6 | ------------------------------------------------------------------------------ |
---|
7 | License: Copyright 2019 Mango Communications, Inc. All rights reserved. |
---|
8 | Use and distribution subject to terms in LICENSE.txt |
---|
9 | ------------------------------------------------------------------------------ |
---|
10 | |
---|
11 | This module provides class definitions for information classes. |
---|
12 | |
---|
13 | Classes (see below for more information): |
---|
14 | Info() -- Base class for information classes |
---|
15 | StationInfo() -- Base class for station information |
---|
16 | NetworkInfo() -- Base class for network information |
---|
17 | CountsInfo() -- Base class for information on node counts |
---|
18 | |
---|
19 | """ |
---|
20 | import struct |
---|
21 | import sys |
---|
22 | |
---|
23 | import wlan_exp.util as util |
---|
24 | |
---|
25 | __all__ = ['NodeInfo', 'StationInfo', 'NetworkInfo', 'TxRxCounts'] |
---|
26 | |
---|
27 | # Fix to support Python 2.x and 3.x |
---|
28 | if sys.version[0]=="3": long=None |
---|
29 | |
---|
30 | |
---|
31 | #------------------------------------------------------------------------- |
---|
32 | # Field definitions for Information structures |
---|
33 | # |
---|
34 | # The info_field_defs is a dictionary of field defintions whose key is the |
---|
35 | # field name and whose value is an ordered list of tuples of the form: |
---|
36 | # (field name, struct format, numpy format, description) |
---|
37 | # |
---|
38 | # where: |
---|
39 | # field name - Text name of the field |
---|
40 | # struct format - Python struct format identifier |
---|
41 | # numpy format - Numpy format identifier |
---|
42 | # description - Text description of the field |
---|
43 | # |
---|
44 | # These tuples can be extended by sub-classes, but the first four fields are |
---|
45 | # required in order to efficiently serialize / deserialize information objects |
---|
46 | # for communication over the transport. |
---|
47 | # |
---|
48 | # These definitions match the corresponding definitions in the 802.11 |
---|
49 | # Reference Design Experiments Framework in C. |
---|
50 | # |
---|
51 | #------------------------------------------------------------------------- |
---|
52 | |
---|
53 | # The Tx/Rx counts fields are used in the STATION_INFO and TXRX_COUNTS structs |
---|
54 | # The C code always returns STATION_INFO structs with embedded counts |
---|
55 | # The Python node API still supports "retrieving" counts indepdent of the |
---|
56 | # station_info list. The TXRX_COUNTS struct below is used to create the |
---|
57 | # standalone counts structs returned by the node method |
---|
58 | station_info_counts_fields = [ |
---|
59 | ('data_num_rx_bytes', 'Q', 'uint64', 'Number of non-duplicate bytes received in DATA packets from remote node'), |
---|
60 | ('data_num_rx_bytes_total', 'Q', 'uint64', 'Total number of bytes received in DATA packets from remote node'), |
---|
61 | ('data_num_tx_bytes_success', 'Q', 'uint64', 'Total number of bytes successfully transmitted in DATA packets to remote node'), |
---|
62 | ('data_num_tx_bytes_total', 'Q', 'uint64', 'Total number of bytes transmitted (successfully or not) in DATA packets to remote node'), |
---|
63 | ('data_num_rx_packets', 'I', 'uint32', 'Number of non-duplicate DATA packets received from remote node'), |
---|
64 | ('data_num_rx_packets_total', 'I', 'uint32', 'Total number of DATA packets received from remote node'), |
---|
65 | ('data_num_tx_packets_success', 'I', 'uint32', 'Total number of DATA packets successfully transmitted to remote node'), |
---|
66 | ('data_num_tx_packets_total', 'I', 'uint32', 'Total number of DATA packets transmitted (successfully or not) to remote node'), |
---|
67 | ('data_num_tx_attempts', 'Q', 'uint64', 'Total number of attempts of DATA packets to remote node (includes re-transmissions)'), |
---|
68 | ('mgmt_num_rx_bytes', 'Q', 'uint64', 'Number of bytes non-duplicate received in management packets from remote node'), |
---|
69 | ('mgmt_num_rx_bytes_total', 'Q', 'uint64', 'Total number of bytes received in management packets from remote node'), |
---|
70 | ('mgmt_num_tx_bytes_success', 'Q', 'uint64', 'Total number of bytes successfully transmitted in management packets to remote node'), |
---|
71 | ('mgmt_num_tx_bytes_total', 'Q', 'uint64', 'Total number of bytes transmitted (successfully or not) in management packets to remote node'), |
---|
72 | ('mgmt_num_rx_packets', 'I', 'uint32', 'Number of non-duplicate management packets received from remote node'), |
---|
73 | ('mgmt_num_rx_packets_total', 'I', 'uint32', 'Total number of management packets received from remote node'), |
---|
74 | ('mgmt_num_tx_packets_success', 'I', 'uint32', 'Total number of management packets successfully transmitted to remote node'), |
---|
75 | ('mgmt_num_tx_packets_total', 'I', 'uint32', 'Total number of management packets transmitted (successfully or not) to remote node'), |
---|
76 | ('mgmt_num_tx_attempts', 'Q', 'uint64', 'Total number of attempts management packets to remote node (includes re-transmissions)') |
---|
77 | ] |
---|
78 | |
---|
79 | info_field_defs = { |
---|
80 | |
---|
81 | # Framework NODE_INFO carries fields shared by all platforms |
---|
82 | 'NODE_INFO': [ |
---|
83 | ('platform_id', 'I', 'uint32', 'Platform ID, unique integer assigned to each supported hardware platform'), |
---|
84 | ('platform_config', 'I', 'uint32', 'Platform config flags, values are platform-specific'), |
---|
85 | ('serial_number', 'I', 'uint32', 'Numeric part of node serial number, full serial number format is platform-specific'), |
---|
86 | ('wlan_exp_version', 'I', 'uint32', 'Version of wlan_exp code running on node'), |
---|
87 | ('node_id', 'I', 'uint32', 'Node ID for wlan_exp transport messages, typically assigned during wlan_exp init handshake'), |
---|
88 | ('wlan_mac_addr', '6s', '6uint8', 'MAC address of node\'s wireless interface'), |
---|
89 | ('high_sw_id', 'B', 'uint8', 'ID for MAC software application in CPU High'), |
---|
90 | ('low_sw_id', 'B', 'uint8', 'ID for MAC software application in CPU Low'), |
---|
91 | ('high_sw_config', 'I', 'uint32', 'Config flags for high MAC aplication/framework'), |
---|
92 | ('low_sw_config', 'I', 'uint32', 'Config flags for low MAC aplication/framework'), |
---|
93 | ('wlan_exp_eth_mtu', 'H', 'uint16', 'MTU in bytes of wlan_exp Ethernet interface'), |
---|
94 | ('scheduler_interval', 'H', 'uint16', 'Minimum interval for scheduled events, in usec'), |
---|
95 | ('high_compilation_date', '12s', '12uint8', 'Compilation date string for high application'), |
---|
96 | ('high_compilation_time', '12s', '12uint8', 'Compilation time string for high application'), |
---|
97 | ('low_compilation_date', '12s', '12uint8', 'Compilation date string for low application'), |
---|
98 | ('low_compilation_time', '12s', '12uint8', 'Compilation time string for low application'), |
---|
99 | ], |
---|
100 | |
---|
101 | 'STATION_INFO' : [ |
---|
102 | ('mac_addr', '6s', '6uint8', 'MAC address of station'), |
---|
103 | ('id', 'H', 'uint16', 'Identification Index for this station'), |
---|
104 | ('host_name', '20s', '20uint8', 'String hostname (19 chars max), taken from DHCP DISCOVER packets'), |
---|
105 | ('flags', 'B', 'uint8', 'Station flags'), |
---|
106 | ('ps_state', 'B', 'uint8', 'Power save state'), |
---|
107 | ('capabiltiies', 'H', 'uint16', 'Capabilities'), |
---|
108 | ('latest_rx_timestamp', 'Q', 'uint64', 'Value of System Time in microseconds of last successful Rx from device'), |
---|
109 | ('latest_txrx_timestamp', 'Q', 'uint64', 'Value of System Time in microseconds of last successful Rx from device or Tx to device'), |
---|
110 | ('latest_rx_seq', 'H', 'uint16', 'Sequence number of last packet received from device'), |
---|
111 | ('qid', 'H', 'uint16', 'Queue ID used by this station'), |
---|
112 | ('num_tx_queued', 'I', 'uint32', 'Number of queued transmissions'), |
---|
113 | ('tx_phy_mcs_data', 'B', 'uint8', 'Current PHY MCS index in [0:7] for new transmissions to device'), |
---|
114 | ('tx_phy_mode_data', 'B', 'uint8', 'Current PHY mode for new transmissions to deivce'), |
---|
115 | ('tx_phy_antenna_mode_data', 'B', 'uint8', 'Current PHY antenna mode in [1:4] for new transmissions to device'), |
---|
116 | ('tx_phy_power_data', 'b', 'int8', 'Current Tx power in dBm for new transmissions to device'), |
---|
117 | ('tx_mac_flags_data', 'B', 'uint8', 'Flags for Tx MAC config for new transmissions to device'), |
---|
118 | ('padding1', '3x', '3uint8', ''), |
---|
119 | ('tx_phy_mcs_mgmt', 'B', 'uint8', 'Current PHY MCS index in [0:7] for new transmissions to device'), |
---|
120 | ('tx_phy_mode_mgmt', 'B', 'uint8', 'Current PHY mode for new transmissions to deivce'), |
---|
121 | ('tx_phy_antenna_mode_mgmt', 'B', 'uint8', 'Current PHY antenna mode in [1:4] for new transmissions to device'), |
---|
122 | ('tx_phy_power_mgmt', 'b', 'int8', 'Current Tx power in dBm for new transmissions to device'), |
---|
123 | ('tx_mac_flags_mgmt', 'B', 'uint8', 'Flags for Tx MAC config for new transmissions to device'), |
---|
124 | ('padding2', '3x', '3uint8', ''), |
---|
125 | ] + station_info_counts_fields, |
---|
126 | |
---|
127 | 'BSS_CONFIG_COMMON' : [ |
---|
128 | ('bssid', '6s', '6uint8', 'BSS ID'), |
---|
129 | ('channel', 'B', 'uint8', 'Primary channel'), |
---|
130 | ('channel_type', 'B', 'uint8', 'Channel Type'), |
---|
131 | ('ssid', '33s', '33uint8', 'SSID (32 chars max)'), |
---|
132 | ('ht_capable', 'B', 'uint8', 'Support for HTMF Tx/Rx'), |
---|
133 | ('beacon_interval', 'H', 'uint16', 'Beacon interval - In time units of 1024 us'), |
---|
134 | ('dtim_period', 'B', 'uint8', 'DTIM Period - In units of beacon intervals'), |
---|
135 | ('reserved', '3x', '3uint8', '')], |
---|
136 | |
---|
137 | 'NETWORK_INFO' : [ |
---|
138 | # BSS_CONFIG_COMMON Fields to be inserted here! |
---|
139 | ('flags', 'I', 'uint32', 'Bit Flags'), |
---|
140 | ('capabilities', 'I', 'uint32', 'Supported capabilities of the BSS'), |
---|
141 | ('latest_beacon_rx_time', 'Q', 'uint64', 'Value of System Time in microseconds of last beacon Rx'), |
---|
142 | ('latest_beacon_rx_power', 'b', 'int8', 'Last observed beacon Rx Power (dBm)'), |
---|
143 | ('padding1', '3x', '3uint8', '')], |
---|
144 | |
---|
145 | 'BSS_CONFIG_UPDATE' : [ |
---|
146 | ('update_mask', 'I', 'uint32', 'Bit mask indicating which fields were updated')], |
---|
147 | # BSS_CONFIG_COMMON Fields to be inserted here! |
---|
148 | |
---|
149 | 'TXRX_COUNTS' : [ |
---|
150 | ('retrieval_timestamp', 'Q', 'uint64', 'Value of System Time in microseconds when structure retrieved from the node'), |
---|
151 | ('mac_addr', '6s', '6uint8', 'MAC address of remote node whose counts are recorded here'), |
---|
152 | ] + station_info_counts_fields, |
---|
153 | } |
---|
154 | |
---|
155 | |
---|
156 | info_struct_len_reqs = { |
---|
157 | 'TXRX_COUNTS': 128, |
---|
158 | 'BSS_CONFIG_UPDATE': 52 |
---|
159 | } |
---|
160 | |
---|
161 | info_consts_defs = { |
---|
162 | 'STATION_INFO' : util.consts_dict({ |
---|
163 | 'flags' : util.consts_dict({ |
---|
164 | 'KEEP' : 0x01, |
---|
165 | 'DISABLE_ASSOC_CHECK' : 0x02 |
---|
166 | }), |
---|
167 | 'ps_state' : util.consts_dict({ |
---|
168 | 'DOZE' : 0x01 |
---|
169 | }), |
---|
170 | 'capabilities' : util.consts_dict({ |
---|
171 | 'HT_CAPABLE' : 0x0001 |
---|
172 | }), |
---|
173 | 'tx_phy_mode' : util.phy_modes, |
---|
174 | 'tx_mac_flags' : util.consts_dict() |
---|
175 | }), |
---|
176 | |
---|
177 | 'NETWORK_INFO' : util.consts_dict({ |
---|
178 | 'flags' : util.consts_dict({ |
---|
179 | 'KEEP' : 0x00000001, |
---|
180 | }), |
---|
181 | 'channel_type' : util.consts_dict({ |
---|
182 | 'BW20' : 0x0000, |
---|
183 | 'BW40_SEC_BELOW' : 0x0001, |
---|
184 | 'BW40_SEC_ABOVE' : 0x0002, |
---|
185 | }), |
---|
186 | 'capabilities' : util.consts_dict({ |
---|
187 | 'ESS' : 0x0001, |
---|
188 | 'IBSS' : 0x0002, |
---|
189 | 'PRIVACY' : 0x0010, |
---|
190 | }) |
---|
191 | }), |
---|
192 | |
---|
193 | 'BSS_CONFIG_UPDATE' : util.consts_dict({ |
---|
194 | 'update_mask' : util.consts_dict({ |
---|
195 | 'BSSID' : 0x00000001, |
---|
196 | 'CHANNEL' : 0x00000002, |
---|
197 | 'SSID' : 0x00000004, |
---|
198 | 'BEACON_INTERVAL' : 0x00000008, |
---|
199 | 'HT_CAPABLE' : 0x00000010, |
---|
200 | 'DTIM_PERIOD' : 0x00000020, |
---|
201 | }), |
---|
202 | 'channel_type' : util.consts_dict({ |
---|
203 | 'BW20' : 0x0000, |
---|
204 | 'BW40_SEC_BELOW' : 0x0001, |
---|
205 | 'BW40_SEC_ABOVE' : 0x0002, |
---|
206 | }) |
---|
207 | }), |
---|
208 | } |
---|
209 | |
---|
210 | |
---|
211 | |
---|
212 | # ----------------------------------------------------------------------------- |
---|
213 | # Information Structure Base Class |
---|
214 | # ----------------------------------------------------------------------------- |
---|
215 | |
---|
216 | class InfoStruct(dict): |
---|
217 | """Base class for structured information classes |
---|
218 | |
---|
219 | This class provides the basic methods for setting / accessing information |
---|
220 | within the info struct object. It also provides the base methods for |
---|
221 | serializing / deserializing the object for communication by the transport. |
---|
222 | |
---|
223 | An InfoStruct object represents structured data that is passed to/from the |
---|
224 | MAC C code. In the C code these objects are represented by struct defintions. |
---|
225 | The fields defined for an InfoStruct object must match the fields in the |
---|
226 | corresponding C struct. Conceptually these InfoStruct objects are very similar |
---|
227 | to log entries; they even share the log entry syntax for defining fields. By |
---|
228 | defining InfoStruct objects here, however, wlan_exp scripts can control and |
---|
229 | retrieve parameters encoded in non-log structs on the node without relying |
---|
230 | on any of wlan_exp's logging framework. The primary benefit of this separation |
---|
231 | is removing the numpy dependency when dealing with the non-log version of |
---|
232 | the info structures described here. |
---|
233 | """ |
---|
234 | _field_name = None # Internal name for the info_field_defs entry |
---|
235 | _fields_struct_fmt = None # Internal string of field formats, in struct module format |
---|
236 | _consts = None # Internal container for user-defined, type-specific constants |
---|
237 | |
---|
238 | def __init__(self, field_sets=None, field_defs=None): |
---|
239 | super(InfoStruct, self).__init__() |
---|
240 | |
---|
241 | # field_sets: scalar or list of strings matching keys in the global info_field_defs |
---|
242 | # field_defs: list of field definitions, for structs whose fields are not in the global info_field_defs |
---|
243 | # Code below supports both arguments being not-None, in case caller wants to create InfoStruct |
---|
244 | # based on global defs with extra fields appeneded |
---|
245 | |
---|
246 | if field_sets is not None: |
---|
247 | if(type(field_sets) is str): |
---|
248 | field_sets = [field_sets] |
---|
249 | |
---|
250 | # Initialize variables |
---|
251 | self._consts = util.consts_dict() |
---|
252 | self._field_defs = [] |
---|
253 | for fs in field_sets: |
---|
254 | if(fs not in info_field_defs.keys()): |
---|
255 | msg = "Field set name {0} does not exist in info_field_defs.".format(fs) |
---|
256 | raise AttributeError(msg) |
---|
257 | |
---|
258 | self.append_field_defs(info_field_defs[fs]) |
---|
259 | |
---|
260 | if(fs in info_consts_defs.keys()): |
---|
261 | self.append_const_defs(info_consts_defs[fs]) |
---|
262 | |
---|
263 | # Update the struct format string, used by pack/unpack/calcsize below |
---|
264 | self._fields_struct_fmt = ' '.join(self.get_field_struct_formats()) |
---|
265 | |
---|
266 | # Check the final struct size using the name of the last field set |
---|
267 | last_field_set = field_sets[-1] |
---|
268 | if(last_field_set in info_struct_len_reqs.keys()): |
---|
269 | # A struct length value was provided - confirm the field defs match this length |
---|
270 | def_size = self.sizeof() |
---|
271 | req_size = info_struct_len_reqs[last_field_set] |
---|
272 | |
---|
273 | if(def_size != req_size): |
---|
274 | msg = 'Struct size definition mismatch - {0} field defs have size {1} vs required size {2}'.format(last_field_set, def_size, req_size) |
---|
275 | raise AttributeError(msg) |
---|
276 | |
---|
277 | if field_defs is not None: |
---|
278 | # Caller provided field definitions directly - adopt these as-is |
---|
279 | self.append_field_defs(field_defs) |
---|
280 | |
---|
281 | # Update the struct format string, used by pack/unpack/calcsize below |
---|
282 | #self._fields_struct_fmt = ' '.join(self.get_field_struct_formats()) |
---|
283 | # The '=' qualifier mimics the __packed__ attribute in C |
---|
284 | self._fields_struct_fmt = '=' + ' '.join(self.get_field_struct_formats()) |
---|
285 | |
---|
286 | # Add and initialize all the fields in the info_field_defs |
---|
287 | for field in self._field_defs: |
---|
288 | if 'x' not in field[1]: |
---|
289 | self[field[0]] = None |
---|
290 | |
---|
291 | |
---|
292 | |
---|
293 | # ------------------------------------------------------------------------- |
---|
294 | # Helper methods for the Info Type |
---|
295 | # ------------------------------------------------------------------------- |
---|
296 | def append_field_defs(self, new_field_defs): |
---|
297 | try: |
---|
298 | # Existing field defs - concatenate lists |
---|
299 | self._field_defs = self._field_defs + new_field_defs |
---|
300 | except TypeError: |
---|
301 | # No existing field defs |
---|
302 | self._field_defs = new_field_defs |
---|
303 | |
---|
304 | def append_const_defs(self, new_const_defs): |
---|
305 | for k in new_const_defs.keys(): |
---|
306 | self._consts[k] = new_const_defs[k] |
---|
307 | |
---|
308 | def get_field_names(self): |
---|
309 | """Get the field names. |
---|
310 | |
---|
311 | Returns: |
---|
312 | names (list of str): List of string field names for the entry |
---|
313 | """ |
---|
314 | return [f[0] for f in self.get_field_defs()] |
---|
315 | |
---|
316 | |
---|
317 | def get_field_struct_formats(self): |
---|
318 | """Get the Python struct format of the fields. |
---|
319 | |
---|
320 | Returns: |
---|
321 | formats (list of str): List of Python struct formats for the fields |
---|
322 | """ |
---|
323 | return [f[1] for f in self.get_field_defs()] |
---|
324 | |
---|
325 | |
---|
326 | def get_field_defs(self): |
---|
327 | """Get the field definitions. |
---|
328 | |
---|
329 | Returns: |
---|
330 | fields (list of tuple): List of tuples that describe each field |
---|
331 | """ |
---|
332 | return self._field_defs |
---|
333 | |
---|
334 | def get_consts(self): |
---|
335 | """Get all constants defined in the info struct object as a dictionary |
---|
336 | |
---|
337 | Returns: |
---|
338 | values (dict): All constant values in the object |
---|
339 | """ |
---|
340 | return self._consts.copy() |
---|
341 | |
---|
342 | |
---|
343 | def get_const(self, name): |
---|
344 | """Get a constant defined in the info struct object |
---|
345 | |
---|
346 | Returns: |
---|
347 | value (int or str): Value associated with the constant |
---|
348 | """ |
---|
349 | if (name in self._consts.keys()): |
---|
350 | return self._consts[name] |
---|
351 | else: |
---|
352 | msg = "Constant {0} does not exist ".format(name) |
---|
353 | msg += "in {0}".format(self.__class__.__name__) |
---|
354 | raise AttributeError(msg) |
---|
355 | |
---|
356 | |
---|
357 | def sizeof(self): |
---|
358 | """Return the size of the object when being transmitted / received by |
---|
359 | the transport |
---|
360 | """ |
---|
361 | return struct.calcsize(self._fields_struct_fmt) |
---|
362 | |
---|
363 | |
---|
364 | # ------------------------------------------------------------------------- |
---|
365 | # Utility methods for the InfoStruct object |
---|
366 | # ------------------------------------------------------------------------- |
---|
367 | def serialize(self): |
---|
368 | """Packs object into a data buffer |
---|
369 | |
---|
370 | Returns: |
---|
371 | data (bytearray): Bytearray of packed binary data |
---|
372 | """ |
---|
373 | # Pack the object into a single data buffer |
---|
374 | ret_val = b'' |
---|
375 | fields = [] |
---|
376 | field_type = [] |
---|
377 | tmp_values = [] |
---|
378 | used_field = [] |
---|
379 | |
---|
380 | for field in self._field_defs: |
---|
381 | if 'x' not in field[1]: |
---|
382 | fields.append(field[0]) |
---|
383 | try: |
---|
384 | tmp_values.append(self[field[0]]) |
---|
385 | field_type.append(type(self[field[0]])) |
---|
386 | used_field.append(True) |
---|
387 | except KeyError: |
---|
388 | tmp_values.append(0) |
---|
389 | field_type.append(None) |
---|
390 | used_field.append(False) |
---|
391 | |
---|
392 | try: |
---|
393 | ret_val += struct.pack(self._fields_struct_fmt, *tmp_values) |
---|
394 | except struct.error as err: |
---|
395 | print("Error serializing structure:\n\t{0}".format(err)) |
---|
396 | print("Serialize Structure:") |
---|
397 | print(fields) |
---|
398 | print(field_type) |
---|
399 | print(used_field) |
---|
400 | print(tmp_values) |
---|
401 | print(self._fields_struct_fmt) |
---|
402 | raise RuntimeError("See above print statements to debug error.") |
---|
403 | |
---|
404 | |
---|
405 | if (self.sizeof()) != len(ret_val): |
---|
406 | msg = "WARNING: Sizes do not match.\n" |
---|
407 | msg += " Expected {0} bytes".format(self.sizeof()) |
---|
408 | msg += " Buffer is {0} bytes".format(len(ret_val)) |
---|
409 | print(msg) |
---|
410 | |
---|
411 | return ret_val |
---|
412 | |
---|
413 | |
---|
414 | def deserialize(self, buf): |
---|
415 | """Unpacks a buffer of data into the object |
---|
416 | |
---|
417 | Args: |
---|
418 | buf (bytearray): Array of bytes containing the values of an information object |
---|
419 | |
---|
420 | Returns: |
---|
421 | (Info Object): Each Info object in the list has been filled in with the corresponding |
---|
422 | data from the buffer. |
---|
423 | """ |
---|
424 | all_names = self.get_field_names() |
---|
425 | all_fmts = self.get_field_struct_formats() |
---|
426 | object_size = self.sizeof() |
---|
427 | |
---|
428 | try: |
---|
429 | data_tuple = struct.unpack(self._fields_struct_fmt, buf[0:object_size]) |
---|
430 | |
---|
431 | # Filter out names for fields ignored during unpacking |
---|
432 | names = [n for (n,f) in zip(all_names, all_fmts) if 'x' not in f] |
---|
433 | |
---|
434 | # Populate object with data |
---|
435 | for i, name in enumerate(names): |
---|
436 | self[name] = data_tuple[i] |
---|
437 | |
---|
438 | except struct.error as err: |
---|
439 | print("Error unpacking buffer with len {0}: {1}".format(len(buf), err)) |
---|
440 | |
---|
441 | |
---|
442 | |
---|
443 | # ------------------------------------------------------------------------- |
---|
444 | # Internal methods for the InfoStruct object |
---|
445 | # ------------------------------------------------------------------------- |
---|
446 | def _update_field_defs(self): |
---|
447 | """Internal method to update meta-data about the fields.""" |
---|
448 | # Update the fields format used by struct unpack/calcsize |
---|
449 | self._fields_struct_fmt = ' '.join(self.get_field_struct_formats()) |
---|
450 | |
---|
451 | |
---|
452 | def __str__(self): |
---|
453 | """Pretty print info struct object""" |
---|
454 | msg = "{0}\n".format(self.__class__.__name__) |
---|
455 | |
---|
456 | for field in self._field_defs: |
---|
457 | if 'x' not in field[1]: |
---|
458 | msg += " {0:30s} = {1}\n".format(field[0], self[field[0]]) |
---|
459 | |
---|
460 | return msg |
---|
461 | |
---|
462 | # Allow attribute (ie ".") notation to access contents of dictionary |
---|
463 | def __getattr__(self, name): |
---|
464 | if name in self: |
---|
465 | return self[name] |
---|
466 | |
---|
467 | def __setattr__(self, name, value): |
---|
468 | if name in self: |
---|
469 | self[name] = value |
---|
470 | else: |
---|
471 | super(InfoStruct, self).__setattr__(name, value) |
---|
472 | |
---|
473 | def __add__(self, x): |
---|
474 | """Define an addition operator for Infostruct to build a new InfoStruct |
---|
475 | from two other InfoStructs. Neither input struct is modified. The return |
---|
476 | is a new InfoStruct instance with all the fields and values of the two inputs.""" |
---|
477 | |
---|
478 | # Verify no overlapping field names |
---|
479 | for f1_name in self.get_field_names(): |
---|
480 | if f1_name in x.get_field_names(): |
---|
481 | raise Exception('ERROR: cannot combine InfoStructs with duplicate field name {0}'.format(f1_name)) |
---|
482 | |
---|
483 | # Define the new sturct type, with |
---|
484 | r = InfoStruct(field_defs=self._field_defs + x._field_defs) |
---|
485 | |
---|
486 | # Copy values from the input structs to the new combined struct |
---|
487 | for f1_name in self.get_field_names(): |
---|
488 | r[f1_name] = self[f1_name] |
---|
489 | |
---|
490 | for f2_name in x.get_field_names(): |
---|
491 | r[f2_name] = x[f2_name] |
---|
492 | |
---|
493 | return r |
---|
494 | |
---|
495 | # End Class |
---|
496 | |
---|
497 | class NodeInfo(InfoStruct): |
---|
498 | """Class for framework NODE_INFO struct retrieved during init.""" |
---|
499 | |
---|
500 | def __init__(self): |
---|
501 | super(NodeInfo, self).__init__(field_sets='NODE_INFO') |
---|
502 | |
---|
503 | def serialize(self): |
---|
504 | print("Error: serialize() is not supported for NodeInfo.") |
---|
505 | raise NotImplementedError |
---|
506 | |
---|
507 | def deserialize(self, buf): |
---|
508 | import ctypes |
---|
509 | |
---|
510 | # Use superclass for raw deserialization using the struct field |
---|
511 | # formats specified for NODE_INFO above |
---|
512 | super(NodeInfo, self).deserialize(buf) |
---|
513 | |
---|
514 | # Then convert some fields to more useable Python types |
---|
515 | |
---|
516 | # compilation_date/time fields are null-terminated ASCII strings |
---|
517 | # Check last character is 0, then pass to util to convert to string |
---|
518 | # Return empty string if last character is not null (i.e. C screwed up) |
---|
519 | for f in ['high_compilation_date', 'high_compilation_time', |
---|
520 | 'low_compilation_date', 'low_compilation_time',]: |
---|
521 | |
---|
522 | if self[f][-1] == 0: |
---|
523 | # Valid null termination - decode ASCII bytes to Python string |
---|
524 | # C pads the strings with extra null characters to align |
---|
525 | # struct fields. The rstrip() call removes all trailing nulls |
---|
526 | self[f] = self[f].rstrip(b'\00').decode('utf-8') |
---|
527 | else: |
---|
528 | # Invalid termination - return empty string |
---|
529 | print('WARNING: NodeInfo deserialize ignored invalid string for {0}: {1}'.format(f, self[f])) |
---|
530 | self[f] = u'' |
---|
531 | |
---|
532 | # Convert the MAC address byte array to u64 |
---|
533 | self['wlan_mac_addr'] = util.byte_str_to_mac_addr(self['wlan_mac_addr']) |
---|
534 | |
---|
535 | # End Class |
---|
536 | |
---|
537 | # ----------------------------------------------------------------------------- |
---|
538 | # TX/RX Counts Class |
---|
539 | # ----------------------------------------------------------------------------- |
---|
540 | |
---|
541 | class TxRxCounts(InfoStruct): |
---|
542 | """Class for TX/RX counts.""" |
---|
543 | |
---|
544 | def __init__(self): |
---|
545 | super(TxRxCounts, self).__init__(field_sets='TXRX_COUNTS') |
---|
546 | |
---|
547 | # To populate the TxRxCounts with information, use the |
---|
548 | # deserialize() function on a proper buffer of data |
---|
549 | |
---|
550 | |
---|
551 | def serialize(self): |
---|
552 | # serialize() is currently not supported for TxRxCounts. This |
---|
553 | # is due to the fact that TxRxCounts information should only come |
---|
554 | # directly from the node and should not be set to the node. |
---|
555 | # |
---|
556 | print("Error: serialize() is not supported for TxRxCounts.") |
---|
557 | raise NotImplementedError |
---|
558 | |
---|
559 | # If serialize() needs to be supported in future version for |
---|
560 | # TxRxCounts, below is the code to use: |
---|
561 | # |
---|
562 | # # Convert MAC address to byte string for transmit |
---|
563 | # mac_addr_tmp = self['mac_addr'] |
---|
564 | # self['mac_addr'] = util.str_to_mac_addr(self['mac_addr']) |
---|
565 | # self['mac_addr'] = util.mac_addr_to_byte_str(self['mac_addr']) |
---|
566 | # |
---|
567 | # ret_val = super(TxRxCounts, self).serialize() |
---|
568 | # |
---|
569 | # # Revert MAC address to a colon delimited string |
---|
570 | # self['mac_addr'] = mac_addr_tmp |
---|
571 | # |
---|
572 | # return ret_val |
---|
573 | |
---|
574 | |
---|
575 | def deserialize(self, buf): |
---|
576 | super(TxRxCounts, self).deserialize(buf) |
---|
577 | |
---|
578 | if (False): |
---|
579 | msg = "TX/RX Counts Data Buffer:\n" |
---|
580 | msg += util.buffer_to_str(buf) |
---|
581 | print(msg) |
---|
582 | |
---|
583 | # Clean up the values |
---|
584 | # - Convert the MAC Address to a colon delimited string |
---|
585 | self['mac_addr'] = util.byte_str_to_mac_addr(self['mac_addr']) |
---|
586 | #self['mac_addr'] = util.mac_addr_to_str(self['mac_addr']) |
---|
587 | |
---|
588 | |
---|
589 | # End Class |
---|
590 | |
---|
591 | |
---|
592 | |
---|
593 | # ----------------------------------------------------------------------------- |
---|
594 | # Station Info Class |
---|
595 | # ----------------------------------------------------------------------------- |
---|
596 | |
---|
597 | class StationInfo(InfoStruct): |
---|
598 | """Class for Station Information.""" |
---|
599 | |
---|
600 | def __init__(self): |
---|
601 | super(StationInfo, self).__init__(field_sets='STATION_INFO') |
---|
602 | |
---|
603 | # To populate the TxRxCounts with information, use the |
---|
604 | # deserialize() function on a proper buffer of data |
---|
605 | |
---|
606 | |
---|
607 | def serialize(self): |
---|
608 | # serialize() is currently not supported for StationInfo. This |
---|
609 | # is due to the fact that StationInfo information should only come |
---|
610 | # directly from the node and should not be set to the node. |
---|
611 | # |
---|
612 | print("Error: serialize() is not supported for StationInfo.") |
---|
613 | raise NotImplementedError |
---|
614 | |
---|
615 | # If serialize() needs to be supported in future version for |
---|
616 | # StationInfo, below is the code to use: |
---|
617 | # |
---|
618 | # # Convert MAC address to byte string for transmit |
---|
619 | # mac_addr_tmp = self['mac_addr'] |
---|
620 | # self['mac_addr'] = util.str_to_mac_addr(self['mac_addr']) |
---|
621 | # self['mac_addr'] = util.mac_addr_to_byte_str(self['mac_addr']) |
---|
622 | # |
---|
623 | # ret_val = super(StationInfo, self).serialize() |
---|
624 | # |
---|
625 | # # Revert MAC address to a colon delimited string |
---|
626 | # self['mac_addr'] = mac_addr_tmp |
---|
627 | # |
---|
628 | # return ret_val |
---|
629 | |
---|
630 | |
---|
631 | def deserialize(self, buf): |
---|
632 | super(StationInfo, self).deserialize(buf) |
---|
633 | |
---|
634 | if (False): |
---|
635 | msg = "Station Info Data buffer:\n" |
---|
636 | msg += util.buffer_to_str(buf) |
---|
637 | print(msg) |
---|
638 | |
---|
639 | # Clean up the values |
---|
640 | # - Remove extra characters in the SSID |
---|
641 | # - Convert the MAC Address to a colon delimited string |
---|
642 | if (self['host_name'][0] == '\x00'): |
---|
643 | self['host_name'] = '\x00' |
---|
644 | else: |
---|
645 | import ctypes |
---|
646 | self['host_name'] = ctypes.c_char_p(self['host_name']).value |
---|
647 | |
---|
648 | self['mac_addr'] = util.byte_str_to_mac_addr(self['mac_addr']) |
---|
649 | #self['mac_addr'] = util.mac_addr_to_str(self['mac_addr']) |
---|
650 | |
---|
651 | |
---|
652 | # End Class |
---|
653 | |
---|
654 | # ----------------------------------------------------------------------------- |
---|
655 | # Network Info Class |
---|
656 | # ----------------------------------------------------------------------------- |
---|
657 | class NetworkInfo(InfoStruct): |
---|
658 | """Class for Network Information, represents information about wireless network |
---|
659 | observed by hardware nodes. |
---|
660 | """ |
---|
661 | def __init__(self): |
---|
662 | # Constructor has no arguments since NetworkInfo objects are only |
---|
663 | # created by deserializing bytes from the hardware node |
---|
664 | |
---|
665 | # Initialize the field definitions with BSS_CONFIG fields first |
---|
666 | super(NetworkInfo, self).__init__(field_sets=['BSS_CONFIG_COMMON', 'NETWORK_INFO']) |
---|
667 | |
---|
668 | def serialize(self): |
---|
669 | print("Error: serialize() is not supported for NetworkInfo") |
---|
670 | raise NotImplementedError |
---|
671 | |
---|
672 | |
---|
673 | def deserialize(self, buf): |
---|
674 | super(NetworkInfo, self).deserialize(buf) |
---|
675 | |
---|
676 | if (False): |
---|
677 | msg = "Network Info Data buffer:\n" |
---|
678 | msg += util.buffer_to_str(buf) |
---|
679 | print(msg) |
---|
680 | |
---|
681 | # Clean up the values |
---|
682 | # - Remove extra characters in the SSID |
---|
683 | # - Convert the BSS ID to a colon delimited string for storage |
---|
684 | # |
---|
685 | # A BSSID is a 48-bit integer and can be treated like a MAC |
---|
686 | # address in the wlan_exp framework (ie all the MAC address |
---|
687 | # utility functions can be used on it.) |
---|
688 | # |
---|
689 | import ctypes |
---|
690 | self['ssid'] = ctypes.c_char_p(self['ssid']).value |
---|
691 | # If the SSID is not a string already (which happens in Python3) |
---|
692 | # then decode the bytes class assuming a UTF-8 encoding |
---|
693 | if type(self['ssid']) is not str: |
---|
694 | self['ssid'] = self['ssid'].decode('utf-8') |
---|
695 | |
---|
696 | self['bssid'] = util.byte_str_to_mac_addr(self['bssid']) |
---|
697 | self['bssid'] = util.mac_addr_to_str(self['bssid']) |
---|
698 | |
---|
699 | |
---|
700 | # End Class |
---|
701 | |
---|
702 | |
---|
703 | # ----------------------------------------------------------------------------- |
---|
704 | # BSS Config Class |
---|
705 | # ----------------------------------------------------------------------------- |
---|
706 | class BSSConfig(InfoStruct): |
---|
707 | """Represents the BSS Config struct in hardware. This struct is created only |
---|
708 | in Python. The NetworkInfo and BSSConfigUpdate structs should be used for |
---|
709 | communicating BSS details with hardware nodes" |
---|
710 | """ |
---|
711 | def __init__(self): |
---|
712 | super(BSSConfig, self).__init__(field_sets='BSS_CONFIG_COMMON') |
---|
713 | |
---|
714 | def serialize(self): |
---|
715 | raise NotImplementedError('serialize() is not supported for BSSConfig') |
---|
716 | |
---|
717 | def deserialize(self): |
---|
718 | raise NotImplementedError('deserialize() is not supported for BSSConfig') |
---|
719 | |
---|
720 | # ----------------------------------------------------------------------------- |
---|
721 | # BSS Config Update Class |
---|
722 | # ----------------------------------------------------------------------------- |
---|
723 | |
---|
724 | class BSSConfigUpdate(InfoStruct): |
---|
725 | """Class for updating Basic Service Set (BSS) Configuration Information |
---|
726 | |
---|
727 | Attributes: |
---|
728 | bssid (int): 48-bit ID of the BSS either as a integer; A value of |
---|
729 | None will remove current BSS on the node (similar to |
---|
730 | node.reset(bss=True)); A value of False will not update the current |
---|
731 | bssid |
---|
732 | ssid (str): SSID string (Must be 32 characters or less); A value of |
---|
733 | None will not update the current SSID |
---|
734 | channel (int): Channel on which the BSS operates; A value of None will |
---|
735 | not update the current channel |
---|
736 | beacon_interval (int): Integer number of beacon Time Units in [10, 65534] |
---|
737 | (http://en.wikipedia.org/wiki/TU_(Time_Unit); a TU is 1024 microseconds); |
---|
738 | A value of None will disable beacons; A value of False will not |
---|
739 | update the current beacon interval |
---|
740 | dtim_period (int): Number of beacon intervals between DTIMs |
---|
741 | ht_capable (bool): Does the node support HTMF Tx/Rx. A value of None |
---|
742 | will not update the current value of HT capable. |
---|
743 | |
---|
744 | |
---|
745 | """ |
---|
746 | def __init__(self, bssid=False, ssid=None, channel=None, beacon_interval=False, dtim_period=None, ht_capable=None): |
---|
747 | # Initialize the field definitions with BSS_CONFIG fields first |
---|
748 | super(BSSConfigUpdate, self).__init__(field_sets=['BSS_CONFIG_UPDATE', 'BSS_CONFIG_COMMON']) |
---|
749 | |
---|
750 | # Default values used if value not provided: |
---|
751 | # bssid - 00:00:00:00:00:00 |
---|
752 | # ssid - "" |
---|
753 | # channel - 0 |
---|
754 | # beacon_interval - 0xFFFF |
---|
755 | # ht_capable - 0xFF |
---|
756 | |
---|
757 | # Initialize update mask |
---|
758 | self['update_mask'] = 0 |
---|
759 | |
---|
760 | # Set the BSSID field |
---|
761 | if bssid is not False: |
---|
762 | if bssid is not None: |
---|
763 | self['bssid'] = bssid |
---|
764 | |
---|
765 | # Convert BSSID to colon delimited string for internal storage |
---|
766 | if type(bssid) in [int, long]: |
---|
767 | self['bssid'] = util.mac_addr_to_str(self['bssid']) |
---|
768 | |
---|
769 | else: |
---|
770 | self['bssid'] = "00:00:00:00:00:00" |
---|
771 | |
---|
772 | # Set update mask |
---|
773 | self['update_mask'] |= self._consts.update_mask.BSSID |
---|
774 | else: |
---|
775 | # Remove current BSS on the node |
---|
776 | self['bssid'] = "00:00:00:00:00:00" |
---|
777 | |
---|
778 | # Set SSID field |
---|
779 | if ssid is not None: |
---|
780 | # Check SSID |
---|
781 | |
---|
782 | if type(ssid) is not str: |
---|
783 | raise ValueError("The SSID type was {0}".format(type(ssid))) |
---|
784 | |
---|
785 | if len(ssid) > 32: |
---|
786 | print("WARNING: Requested SSID {0} too long - truncating to {1}".format(ssid, ssid[:32])) |
---|
787 | ssid = ssid[:32] |
---|
788 | |
---|
789 | try: |
---|
790 | self['ssid'] = bytes(ssid, "UTF8") |
---|
791 | except: |
---|
792 | self['ssid'] = ssid |
---|
793 | |
---|
794 | # Set update mask |
---|
795 | self['update_mask'] |= self._consts.update_mask.SSID |
---|
796 | else: |
---|
797 | self['ssid'] = bytes() |
---|
798 | |
---|
799 | # Set Channel field |
---|
800 | if channel is not None: |
---|
801 | # Check Channel |
---|
802 | # - Make sure it is a valid channel; only store channel |
---|
803 | if channel not in util.wlan_channels: |
---|
804 | msg = "The channel must be a valid channel number. See util.py wlan_channels." |
---|
805 | raise ValueError(msg) |
---|
806 | |
---|
807 | self['channel'] = channel |
---|
808 | |
---|
809 | # Set update mask |
---|
810 | self['update_mask'] |= self._consts.update_mask.CHANNEL |
---|
811 | else: |
---|
812 | self['channel'] = 0 |
---|
813 | |
---|
814 | self['channel_type'] = self._consts.channel_type.BW20 |
---|
815 | |
---|
816 | # Set the beacon interval field |
---|
817 | if beacon_interval is not False: |
---|
818 | if beacon_interval is not None: |
---|
819 | # Check beacon interval |
---|
820 | b = int(beacon_interval) |
---|
821 | if b != beacon_interval: |
---|
822 | print("WARNING: Beacon interval {0} rounded to integer {1}".format(beacon_interval, b)) |
---|
823 | beacon_interval = b |
---|
824 | |
---|
825 | if not ((beacon_interval > 9) and (beacon_interval < (2**16 - 1))): |
---|
826 | msg = "The beacon interval must be in [10, 65534] (ie 16-bit positive integer)." |
---|
827 | raise ValueError(msg) |
---|
828 | |
---|
829 | self['beacon_interval'] = beacon_interval |
---|
830 | else: |
---|
831 | # Disable beacons |
---|
832 | self['beacon_interval'] = 0 |
---|
833 | |
---|
834 | # Set update mask |
---|
835 | self['update_mask'] |= self._consts.update_mask.BEACON_INTERVAL |
---|
836 | else: |
---|
837 | self['beacon_interval'] = 0xFFFF |
---|
838 | |
---|
839 | |
---|
840 | # Set the DTIM period |
---|
841 | if dtim_period is not None: |
---|
842 | |
---|
843 | # Check DTIM period |
---|
844 | d = int(dtim_period) |
---|
845 | if d != dtim_period: |
---|
846 | print("WARNING: DTIM period {0} rounded to nearest integer {1}".format(dtim_period, d)) |
---|
847 | dtim_period = d |
---|
848 | |
---|
849 | if not ((dtim_period > 0) and (dtim_period < 255)): |
---|
850 | msg = "The DTIM period must be in [1, 255] (ie 8-bit positive integer)." |
---|
851 | raise ValueError(msg) |
---|
852 | |
---|
853 | self['dtim_period'] = dtim_period |
---|
854 | |
---|
855 | # Set update mask |
---|
856 | self['update_mask'] |= self._consts.update_mask.DTIM_PERIOD |
---|
857 | else: |
---|
858 | self['dtim_period'] = 0xFF |
---|
859 | |
---|
860 | # Set the HT capable field |
---|
861 | if ht_capable is not None: |
---|
862 | # Check HT capable |
---|
863 | if type(ht_capable) is not bool: |
---|
864 | msg = "ht_capable must be a boolean." |
---|
865 | raise ValueError(msg) |
---|
866 | |
---|
867 | self['ht_capable'] = ht_capable |
---|
868 | |
---|
869 | # Set update mask |
---|
870 | self['update_mask'] |= self._consts.update_mask.HT_CAPABLE |
---|
871 | else: |
---|
872 | self['ht_capable'] = 0xFF |
---|
873 | |
---|
874 | |
---|
875 | def serialize(self): |
---|
876 | # Convert bssid to byte string for transmit |
---|
877 | bssid_tmp = self['bssid'] |
---|
878 | self['bssid'] = util.str_to_mac_addr(self['bssid']) |
---|
879 | self['bssid'] = util.mac_addr_to_byte_str(self['bssid']) |
---|
880 | |
---|
881 | ret_val = super(BSSConfigUpdate, self).serialize() |
---|
882 | |
---|
883 | # Revert bssid to colon delimited string |
---|
884 | self['bssid'] = bssid_tmp |
---|
885 | |
---|
886 | return ret_val |
---|
887 | |
---|
888 | |
---|
889 | def deserialize(self, buf): |
---|
890 | raise NotImplementedError("") |
---|
891 | |
---|
892 | |
---|
893 | # End Class |
---|
894 | |
---|
895 | |
---|
896 | |
---|
897 | # ----------------------------------------------------------------------------- |
---|
898 | # Misc Utilities |
---|
899 | # ----------------------------------------------------------------------------- |
---|
900 | def deserialize_info_buffer(buffer, buffer_class): |
---|
901 | """Unpacks a buffer of data into a list of objects |
---|
902 | |
---|
903 | Args: |
---|
904 | buf (bytearray): Array of bytes containing 1 or more objects of the same type |
---|
905 | |
---|
906 | Returns: |
---|
907 | information objects (List of Info Objects): |
---|
908 | Each Info object in the list has been filled in with the corresponding |
---|
909 | data from the buffer. |
---|
910 | """ |
---|
911 | ret_val = [] |
---|
912 | buffer_size = len(buffer) |
---|
913 | index = 0 |
---|
914 | object_size = 1 |
---|
915 | |
---|
916 | try: |
---|
917 | tmp_obj = eval(buffer_class, globals(), locals()) |
---|
918 | object_size = tmp_obj.sizeof() |
---|
919 | except: |
---|
920 | print("ERROR: Cannot create information object of class: {0}".format(buffer_class)) |
---|
921 | |
---|
922 | while (index < buffer_size): |
---|
923 | # try: |
---|
924 | tmp_obj = eval(buffer_class, globals(), locals()) |
---|
925 | tmp_obj.deserialize(buffer[index:index+object_size]) |
---|
926 | # except: |
---|
927 | # tmp_obj = None |
---|
928 | |
---|
929 | ret_val.append(tmp_obj) |
---|
930 | |
---|
931 | index += object_size |
---|
932 | |
---|
933 | return ret_val |
---|
934 | |
---|
935 | # End def |
---|
936 | |
---|