source: ReferenceDesigns/w3_802.11/python/wlan_exp/info.py

Last change on this file was 6320, checked in by chunter, 5 years ago

1.8.0 release wlan-exp

File size: 40.4 KB
Line 
1# -*- coding: utf-8 -*-
2"""
3------------------------------------------------------------------------------
4Mango 802.11 Reference Design Experiments Framework
5    - Information Struct classes
6------------------------------------------------------------------------------
7License:   Copyright 2019 Mango Communications, Inc. All rights reserved.
8           Use and distribution subject to terms in LICENSE.txt
9------------------------------------------------------------------------------
10
11This module provides class definitions for information classes.
12
13Classes (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"""
20import struct
21import sys
22
23import wlan_exp.util as util
24
25__all__ = ['NodeInfo', 'StationInfo', 'NetworkInfo', 'TxRxCounts']
26
27# Fix to support Python 2.x and 3.x
28if 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
58station_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
79info_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
156info_struct_len_reqs = {
157    'TXRX_COUNTS': 128,
158    'BSS_CONFIG_UPDATE': 52
159}
160
161info_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
216class 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
497class 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
541class 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
597class 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# -----------------------------------------------------------------------------
657class 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# -----------------------------------------------------------------------------
706class 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
724class 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# -----------------------------------------------------------------------------
900def 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
Note: See TracBrowser for help on using the repository browser.