[6320] | 1 | # -*- coding: utf-8 -*- |
---|
| 2 | """ |
---|
| 3 | ------------------------------------------------------------------------------ |
---|
| 4 | Mango 802.11 Reference Design Experiments Framework - Log Entry Types |
---|
| 5 | ------------------------------------------------------------------------------ |
---|
| 6 | License: Copyright 2019 Mango Communications, Inc. All rights reserved. |
---|
| 7 | Use and distribution subject to terms in LICENSE.txt |
---|
| 8 | ------------------------------------------------------------------------------ |
---|
| 9 | |
---|
| 10 | This module defines each type of log entry that may exist in the event log of |
---|
| 11 | an 802.11 Reference Design Node. |
---|
| 12 | |
---|
| 13 | The log entry definitions in this file must match the corresponding |
---|
| 14 | definitions in the wlan_mac_entries.h header file in the C code |
---|
| 15 | running on the node. |
---|
| 16 | |
---|
| 17 | This module maintains a dictionary which contains a reference to each |
---|
| 18 | known log entry type. This dictionary is stored in the variable |
---|
| 19 | ``wlan_exp_log_entry_types``. The :class:`WlanExpLogEntryType` constructor |
---|
| 20 | automatically adds each log entry type definition to this dictionary. Users |
---|
| 21 | may access the dictionary to view currently defined log entry types. But |
---|
| 22 | user code should not modify the dictionary contents directly. |
---|
| 23 | |
---|
| 24 | |
---|
| 25 | Custom Log Entry Types |
---|
| 26 | ---------------------- |
---|
| 27 | The :mod:`log_entries` module includes definitions for the log entry types |
---|
| 28 | implemented in the current 802.11 Reference Design C code. |
---|
| 29 | |
---|
| 30 | Log entry types defined here must match the corresponding entry definitions in |
---|
| 31 | the node C code. Custom entries can be defined and added to the global |
---|
| 32 | dictionary by user scripts. |
---|
| 33 | |
---|
| 34 | Log entry type definitions are instances of the :class:`WlanExpLogEntryType` |
---|
| 35 | class. The :class:`WlanExpLogEntryType` constructor requires two arguments: |
---|
| 36 | ``name`` and ``entry_type_id``. Both the name and entry type ID **must** be |
---|
| 37 | unique relative to the existing entry types defined in :mod:`log_entries`. |
---|
| 38 | |
---|
| 39 | To define a custom log entry type:: |
---|
| 40 | |
---|
| 41 | import wlan_exp.log.entry_types as entry_types |
---|
| 42 | |
---|
| 43 | #name and entry_type_id must not collide with existing log entry type definitions |
---|
| 44 | my_entry_type = entry_types.WlanExpLogEntryType(name='MY_ENTRY', entry_type_id=999) |
---|
| 45 | my_entry_type.append_field_defs([ |
---|
| 46 | ('timestamp', 'Q', 'uint64'), |
---|
| 47 | ('field_A', 'H', 'uint16'), |
---|
| 48 | ('field_B', 'H', 'uint16')]) |
---|
| 49 | |
---|
| 50 | """ |
---|
| 51 | import sys, os |
---|
| 52 | from struct import pack, unpack, calcsize, error |
---|
| 53 | import wlan_exp.util as util |
---|
| 54 | |
---|
| 55 | # Fix to support Python 2.x and 3.x |
---|
| 56 | if sys.version[0]=="3": long=None |
---|
| 57 | |
---|
| 58 | |
---|
| 59 | # Event Log Constants |
---|
| 60 | # Must match corresponding C definitions in wlan_mac_event_log.h |
---|
| 61 | WLAN_EXP_LOG_DELIM = 0xACED |
---|
| 62 | |
---|
| 63 | |
---|
| 64 | # Log Entry Type Constants |
---|
| 65 | # Must match corresponding C definitions in wlan_mac_entries.h |
---|
| 66 | ENTRY_TYPE_NULL = 0 |
---|
| 67 | ENTRY_TYPE_NODE_INFO = 1 |
---|
| 68 | ENTRY_TYPE_EXP_INFO = 2 |
---|
| 69 | |
---|
| 70 | ENTRY_TYPE_NODE_TEMPERATURE = 4 |
---|
| 71 | |
---|
| 72 | ENTRY_TYPE_TIME_INFO = 6 |
---|
| 73 | |
---|
| 74 | ENTRY_TYPE_RX_OFDM = 10 |
---|
| 75 | ENTRY_TYPE_RX_OFDM_LTG = 11 |
---|
| 76 | |
---|
| 77 | ENTRY_TYPE_RX_DSSS = 15 |
---|
| 78 | |
---|
| 79 | ENTRY_TYPE_TX_HIGH = 20 |
---|
| 80 | ENTRY_TYPE_TX_HIGH_LTG = 21 |
---|
| 81 | |
---|
| 82 | ENTRY_TYPE_TX_LOW = 25 |
---|
| 83 | ENTRY_TYPE_TX_LOW_LTG = 26 |
---|
| 84 | |
---|
| 85 | # ----------------------------------------------------------------------------- |
---|
| 86 | # Log Entry Type Container |
---|
| 87 | # ----------------------------------------------------------------------------- |
---|
| 88 | |
---|
| 89 | log_entry_types = dict() |
---|
| 90 | |
---|
| 91 | # ----------------------------------------------------------------------------- |
---|
| 92 | # Log Entry Type Base Class |
---|
| 93 | # ----------------------------------------------------------------------------- |
---|
| 94 | |
---|
| 95 | # Function whose docstring is updated every time a WlanExpLogEntryType is created |
---|
| 96 | # Sphinx autodoc will read this function's docstring via the .__doc__ attribute |
---|
| 97 | # The .__doc__ attribute of a Type (like WlanExpLogEntryType) cannot be modified at |
---|
| 98 | # runtime, but the .__doc__ of a function can. |
---|
| 99 | def _log_entry_types_doc_container(): |
---|
| 100 | """Default docstring""" |
---|
| 101 | pass |
---|
| 102 | |
---|
| 103 | |
---|
| 104 | class WlanExpLogEntryType(object): |
---|
| 105 | """Class to define a log entry type.""" |
---|
| 106 | # _fields is a list of 3-tuples: |
---|
| 107 | # (field_name, field_fmt_struct, field_fmt_np) |
---|
| 108 | _fields = None |
---|
| 109 | |
---|
| 110 | entry_type_id = None # Unique integer ID for entry type |
---|
| 111 | name = None # Unique string name for entry type |
---|
| 112 | description = '' # Description of log entry type, used for generating documentation |
---|
| 113 | |
---|
| 114 | fields_np_dt = None # numpy dtype object describing format |
---|
| 115 | fields_fmt_struct = None # List of field formats, in struct module format |
---|
| 116 | _field_offsets = None |
---|
| 117 | |
---|
| 118 | gen_numpy_callbacks = None |
---|
| 119 | |
---|
| 120 | consts = None # Container for user-defined, entry-specific constants |
---|
| 121 | |
---|
| 122 | def __init__(self, name=None, entry_type_id=None): |
---|
| 123 | |
---|
| 124 | self.name = name |
---|
| 125 | self.entry_type_id = entry_type_id |
---|
| 126 | |
---|
| 127 | # Check if this new WlanExpLogEntryType instannce should be added to the global log_entry_types dictionary |
---|
| 128 | # The log_entry_types dictionary is used to map entry type IDs (integers) and names (strings) by the log |
---|
| 129 | # processing utilities. The code below prints warnings if any IDs or names are duplicated. Only entry types |
---|
| 130 | # with not-None type IDs and names are added to the dictionary. Types with null IDs or names can still be used |
---|
| 131 | # as containers for common field sets shared by multiple entry types (i.e. tx_common for TX_HIGH and TX_HIGH_LTG) |
---|
| 132 | if (name is not None) and (entry_type_id is not None): |
---|
| 133 | if type(entry_type_id) is not int: |
---|
| 134 | raise Exception("ERROR: Invalid entry_type_id {0} - WlanExpLogEntryType entry_type_id must be int".format(entry_type_id)) |
---|
| 135 | |
---|
| 136 | if(name in log_entry_types.keys()): |
---|
| 137 | print("WARNING: replacing exisitng WlanExpLogEntryType with name {0}".format(name)) |
---|
| 138 | |
---|
| 139 | if(entry_type_id in log_entry_types.keys()): |
---|
| 140 | print("WARNING: replacing exisitng WlanExpLogEntryType with ID {0}".format(entry_type_id)) |
---|
| 141 | |
---|
| 142 | log_entry_types[str(name)] = self |
---|
| 143 | log_entry_types[entry_type_id] = self |
---|
| 144 | |
---|
| 145 | # Initialize fields to empty lists |
---|
| 146 | self._fields = [] |
---|
| 147 | |
---|
| 148 | # Initialize unpack variables |
---|
| 149 | self.fields_fmt_struct = '' |
---|
| 150 | |
---|
| 151 | # Initialize callbacks |
---|
| 152 | self.gen_numpy_callbacks = [] |
---|
| 153 | |
---|
| 154 | # Initialize dictionary to contain constants specific to each entry type |
---|
| 155 | self.consts = util.consts_dict() |
---|
| 156 | |
---|
| 157 | # Initialize variable that contains field names and byte offsets |
---|
| 158 | self._field_offsets = {} |
---|
| 159 | |
---|
| 160 | |
---|
| 161 | # ------------------------------------------------------------------------- |
---|
| 162 | # Accessor methods for the WlanExpLogEntryType |
---|
| 163 | # ------------------------------------------------------------------------- |
---|
| 164 | def get_field_names(self): |
---|
| 165 | """Get the field names of the log entry. |
---|
| 166 | |
---|
| 167 | Returns: |
---|
| 168 | names (list of str): List of string field names for the entry |
---|
| 169 | """ |
---|
| 170 | return [f[0] for f in self._fields] |
---|
| 171 | |
---|
| 172 | def get_field_struct_formats(self): |
---|
| 173 | """Get the Python struct format of the log entry fields. |
---|
| 174 | |
---|
| 175 | Returns: |
---|
| 176 | formats (list of str): List of Python struct formats for the fields in the log entry |
---|
| 177 | """ |
---|
| 178 | return [f[1] for f in self._fields] |
---|
| 179 | |
---|
| 180 | def get_field_defs(self): return self._fields |
---|
| 181 | |
---|
| 182 | def get_field_offsets(self): return self._field_offsets |
---|
| 183 | |
---|
| 184 | def get_entry_type_id(self): |
---|
| 185 | """Get the ID for the entry type. |
---|
| 186 | |
---|
| 187 | Returns: |
---|
| 188 | ID (int): Integer ID for the log entry |
---|
| 189 | """ |
---|
| 190 | return self.entry_type_id |
---|
| 191 | |
---|
| 192 | def append_field_defs(self, field_info): |
---|
| 193 | """Adds fields to the definition of the log entry type. |
---|
| 194 | |
---|
| 195 | Args: |
---|
| 196 | field_info (list of tuple): Each must be of the form |
---|
| 197 | ``(field_name, field_type_struct, field_type_numpy)`` where: |
---|
| 198 | |
---|
| 199 | * ``field_name``: Name of field as string |
---|
| 200 | * ``field_type_struct``: Field type as string, using formats specified by ``struct`` module |
---|
| 201 | * ``field_type_numpy``: Field type as string, using formats specified by numpy ``dtype`` |
---|
| 202 | * ``field_desc``: String describing the field's meaning, used to generate wiki docs |
---|
| 203 | |
---|
| 204 | """ |
---|
| 205 | if type(field_info) is list: |
---|
| 206 | self._fields.extend(field_info) |
---|
| 207 | else: |
---|
| 208 | self._fields.append(field_info) |
---|
| 209 | self._update_field_defs() |
---|
| 210 | |
---|
| 211 | |
---|
| 212 | def modify_field_def(self, name, struct_type, numpy_type, doc_str=None): |
---|
| 213 | """Modifies fields of the definition of the log entry type. |
---|
| 214 | |
---|
| 215 | Args: |
---|
| 216 | name (str): Name of field to modify |
---|
| 217 | struct_type (str): New Python struct type for the field |
---|
| 218 | numpy_type (str): New numpy type for the field |
---|
| 219 | doc_str (str, optional): New documentation string |
---|
| 220 | """ |
---|
| 221 | index = None |
---|
| 222 | |
---|
| 223 | for idx, f in enumerate(self._fields): |
---|
| 224 | if (f[0] == name): |
---|
| 225 | index = idx |
---|
| 226 | |
---|
| 227 | if index is None: |
---|
| 228 | print("WARNING: Field {0} not found in {1}.".format(name, self.name)) |
---|
| 229 | return |
---|
| 230 | |
---|
| 231 | if doc_str is None: |
---|
| 232 | field = (name, struct_type, numpy_type, self._fields[index][3]) |
---|
| 233 | else: |
---|
| 234 | field = (name, struct_type, numpy_type, doc_str) |
---|
| 235 | |
---|
| 236 | self._fields[index] = field |
---|
| 237 | |
---|
| 238 | |
---|
| 239 | def add_gen_numpy_array_callback(self, callback): |
---|
| 240 | """Add callback that is run after the numpy array is generated from |
---|
| 241 | the entry type. |
---|
| 242 | |
---|
| 243 | The callbacks will be executed in the order the are added to the log |
---|
| 244 | entry |
---|
| 245 | |
---|
| 246 | Args: |
---|
| 247 | callback (function): Function to run after the numpy array is generated |
---|
| 248 | """ |
---|
| 249 | if callable(callback): |
---|
| 250 | self.gen_numpy_callbacks.append(callback) |
---|
| 251 | else: |
---|
| 252 | print("ERROR: Callback must be callable function.") |
---|
| 253 | |
---|
| 254 | |
---|
| 255 | # ------------------------------------------------------------------------- |
---|
| 256 | # Utility methods for the WlanExpLogEntryType |
---|
| 257 | # ------------------------------------------------------------------------- |
---|
| 258 | def generate_numpy_array(self, log_data, byte_offsets): |
---|
| 259 | """Generate a NumPy array from the log_bytes of the given WlanExpLogEntryType instance |
---|
| 260 | at the given byte_offsets. |
---|
| 261 | """ |
---|
| 262 | import numpy as np |
---|
| 263 | |
---|
| 264 | index_iter = [log_data[o : o + self.fields_np_dt.itemsize] for o in byte_offsets] |
---|
| 265 | # fromiter began throwing an error in numpy ~1.14.5: |
---|
| 266 | # ValueError: invalid literal for long() with base 10: `xxx` |
---|
| 267 | # where xxx are the first 3 bytes of the first entry of index_iter |
---|
| 268 | # I can't find the underlying change that triggered this, so |
---|
| 269 | # the explicit iteration below is the slower-but-functional workaround |
---|
| 270 | #np_arr = np.fromiter(index_iter[2:], self.fields_np_dt, len(byte_offsets)) |
---|
| 271 | |
---|
| 272 | # Workaround: explicitly create the numpy array then populate it one |
---|
| 273 | # entry at a time using fromstring(), which is happy with the index_iter |
---|
| 274 | # list that somehow breaks fromiter() |
---|
| 275 | np_arr = np.empty(dtype=self.fields_np_dt, shape=(len(byte_offsets),)) |
---|
| 276 | for ii in range(len(index_iter)): |
---|
| 277 | np_arr[ii] = np.fromstring(index_iter[ii], self.fields_np_dt) |
---|
| 278 | |
---|
| 279 | if self.gen_numpy_callbacks: |
---|
| 280 | for callback in self.gen_numpy_callbacks: |
---|
| 281 | np_arr = callback(np_arr) |
---|
| 282 | |
---|
| 283 | return np_arr |
---|
| 284 | |
---|
| 285 | |
---|
| 286 | def generate_entry_doc(self, fmt='wiki'): |
---|
| 287 | """Generate wiki documentation.""" |
---|
| 288 | import textwrap |
---|
| 289 | import wlan_exp.log.util as log_util |
---|
| 290 | |
---|
| 291 | field_descs = list() |
---|
| 292 | for f in self._fields: |
---|
| 293 | # Field tuple is (name, struct_type, np_type, (optional)desc) |
---|
| 294 | # Construct new tuple of (name, np_type, desc ('' if not defined)) |
---|
| 295 | try: |
---|
| 296 | field_descs.append( (f[0], f[2], f[3])) |
---|
| 297 | except IndexError: |
---|
| 298 | # Field missing description; use empty string |
---|
| 299 | field_descs.append( (f[0], f[2], '')) |
---|
| 300 | |
---|
| 301 | def doc_fields_table(field_list, fmt='wiki'): |
---|
| 302 | """field_list must be iterable of 3-tuples: |
---|
| 303 | 0: Field name (string) |
---|
| 304 | 1: Field data type (string, preferably the numpy datatype string) |
---|
| 305 | 2: Field description (string) |
---|
| 306 | """ |
---|
| 307 | |
---|
| 308 | doc_str = '' |
---|
| 309 | |
---|
| 310 | if fmt == 'wiki': |
---|
| 311 | doc_str += '{{{#!th align=center\nField Name\n}}}\n' |
---|
| 312 | doc_str += '{{{#!th align=center\nData Type\n}}}\n' |
---|
| 313 | doc_str += '{{{#!th align=center\nDescription\n}}}\n' |
---|
| 314 | doc_str += '|----------------\n' |
---|
| 315 | |
---|
| 316 | for fd in field_list: |
---|
| 317 | import re |
---|
| 318 | # Wiki-ify some string formats: |
---|
| 319 | # Line breaks in descriptions must be explicit "[[BR]]" |
---|
| 320 | # Braces with numeric contents need escape (![) to disable Trac interpretting as changeset number |
---|
| 321 | fd_desc = fd[2] |
---|
| 322 | fd_desc = re.sub(r'(\[[\d:,]+\])', '!\\1', fd_desc) # do this first, so other wiki tags inserted below aren't escaped |
---|
| 323 | #fd_desc = fd_desc.replace('\n', '[[BR]]') |
---|
| 324 | |
---|
| 325 | try: |
---|
| 326 | consts = self.consts[fd[0]] |
---|
| 327 | fd_desc += '\n\nConstants defined for this field:\n' |
---|
| 328 | fd_desc += '||= Name =||= Value =||\n' |
---|
| 329 | for c in consts.keys(): |
---|
| 330 | #fd_desc += ' * {{{{{{\'{0}\': 0x{1:X}}}}}}}\n'.format(c, consts[c]) |
---|
| 331 | fd_desc += '|| {{{{{{{0}}}}}}} || {{{{{{0x{1:X}}}}}}} ||\n'.format(c, consts[c]) |
---|
| 332 | except: |
---|
| 333 | # Field didn't have constants defined |
---|
| 334 | pass |
---|
| 335 | |
---|
| 336 | doc_str += '{{{{{{#!td align=left\n{0}\n}}}}}}\n'.format(fd[0]) |
---|
| 337 | doc_str += '{{{{{{#!td align=center\n{0}\n}}}}}}\n'.format(fd[1]) |
---|
| 338 | doc_str += '{{{{{{#!td align=left\n{0}\n}}}}}}\n'.format(fd_desc) |
---|
| 339 | doc_str += '|----------------\n' |
---|
| 340 | |
---|
| 341 | elif fmt == 'txt': |
---|
| 342 | doc_str += 'Field Name\t\t\t| Data Type\t| Description\n' |
---|
| 343 | doc_str += '---------------------------------------------------------------------------------------------------------------------\n' |
---|
| 344 | |
---|
| 345 | for fd in field_descs: |
---|
| 346 | doc_str += '{0:30}\t| {1:10}\t| {2}\n'.format(fd[0], fd[1], fd[2]) |
---|
| 347 | |
---|
| 348 | doc_str += '---------------------------------------------------------------------------------------------------------------------\n' |
---|
| 349 | |
---|
| 350 | elif fmt == 'rst': |
---|
| 351 | import re |
---|
| 352 | |
---|
| 353 | rst_table_row = lambda c0, c1, c2: ' * - {0}\n - {1}\n - {2}\n'.format(c0, c1, c2) |
---|
| 354 | |
---|
| 355 | doc_str += '\n.. list-table::\n :widths: 25 10 65\n :header-rows: 1\n\n' |
---|
| 356 | doc_str += rst_table_row('Field', 'Data Type', 'Description') |
---|
| 357 | |
---|
| 358 | for fd in field_list: |
---|
| 359 | fd_desc = fd[2] |
---|
| 360 | |
---|
| 361 | # Apply some transformations for rst formatting |
---|
| 362 | # Prefix any explicit line breaks with spaces to align the rst block quote |
---|
| 363 | fd_desc = re.sub('\n +', '\n\n ', fd_desc) # do this first, so other wiki tags inserted below aren't escaped |
---|
| 364 | |
---|
| 365 | try: |
---|
| 366 | consts = self.consts[fd[0]] |
---|
| 367 | fd_desc += '\n\n Constants defined for this field:\n\n' |
---|
| 368 | for c in consts.keys(): |
---|
| 369 | fd_desc += ' * ``{0}``: ``0x{1:X}``\n'.format(c, consts[c]) |
---|
| 370 | except: |
---|
| 371 | # Field didn't have constants defined |
---|
| 372 | pass |
---|
| 373 | |
---|
| 374 | doc_str += rst_table_row(fd[0], fd[1], fd_desc) |
---|
| 375 | |
---|
| 376 | return doc_str |
---|
| 377 | |
---|
| 378 | if fmt == 'wiki': |
---|
| 379 | # Construct the Trac-wiki-style documentation string for this entry type |
---|
| 380 | doc_str = '\n----\n\n' |
---|
| 381 | doc_str += '=== Entry Type {0} ===\n'.format(self.name) |
---|
| 382 | doc_str += self.description + '\n\n' |
---|
| 383 | doc_str += 'Entry type ID: {0}\n\n'.format(self.entry_type_id) |
---|
| 384 | |
---|
| 385 | doc_str += doc_fields_table(field_descs, fmt='wiki') |
---|
| 386 | |
---|
| 387 | elif fmt == 'txt': |
---|
| 388 | # Construct plain text version of documentation string for this entry type |
---|
| 389 | doc_str = '---------------------------------------------------------------------------------------------------------------------\n' |
---|
| 390 | doc_str += 'Entry Type {0}\n'.format(self.name) |
---|
| 391 | doc_str += 'Entry type ID: {0}\n\n'.format(self.entry_type_id) |
---|
| 392 | doc_str += textwrap.fill(self.description) + '\n\n' |
---|
| 393 | |
---|
| 394 | doc_str += doc_fields_table(field_descs, fmt='txt') |
---|
| 395 | |
---|
| 396 | elif fmt == 'rst': |
---|
| 397 | # Construct the Rst documentation string for this entry type |
---|
| 398 | doc_str = '\n\n' |
---|
| 399 | doc_str += '``{0}``\n'.format(self.name) |
---|
| 400 | doc_str += '{0}\n\n'.format('*'*(11+len(self.name))) |
---|
| 401 | |
---|
| 402 | doc_str += self.description + '\n\n' |
---|
| 403 | doc_str += 'Entry type ID: {0}\n\n'.format(self.entry_type_id) |
---|
| 404 | |
---|
| 405 | doc_str += doc_fields_table(field_descs, fmt='rst') |
---|
| 406 | |
---|
| 407 | # Check each post-numpy array generation callback to any documentation to include |
---|
| 408 | # The callbacks can define "virtual" fields - convenience fields appended to the numpy |
---|
| 409 | # array that are calculated from the "real" fields |
---|
| 410 | for cb in self.gen_numpy_callbacks: |
---|
| 411 | field_descs = [] |
---|
| 412 | try: |
---|
| 413 | (cb_doc_str, cb_doc_fields) = cb(np_arr_orig=None, docs_only=True) |
---|
| 414 | |
---|
| 415 | for f in cb_doc_fields: |
---|
| 416 | field_descs.append((f[0], f[1], f[2])) |
---|
| 417 | |
---|
| 418 | doc_str += '\n\n' |
---|
| 419 | doc_str += cb_doc_str + '\n\n' |
---|
| 420 | doc_str += doc_fields_table(field_descs, fmt=fmt) |
---|
| 421 | |
---|
| 422 | except (TypeError, IndexError): |
---|
| 423 | # Callback didn't implement suitable 'docs_only' output; fail quietly and return |
---|
| 424 | # print('Error generating callback field docs for {0}\n'.format(cb)) |
---|
| 425 | pass |
---|
| 426 | |
---|
| 427 | return doc_str |
---|
| 428 | |
---|
| 429 | |
---|
| 430 | def _entry_as_string(self, buf): |
---|
| 431 | """Generate a string representation of the entry from a buffer. |
---|
| 432 | |
---|
| 433 | This method should only be used for debugging log data parsing and log |
---|
| 434 | index generation, not for general creation of text log files. |
---|
| 435 | |
---|
| 436 | This method does not work correctly on RX_OFDM entries due to the way |
---|
| 437 | the channel estimates are defined. The channel_est, mac_payload_len, |
---|
| 438 | and mac_payload will all be zeros. |
---|
| 439 | """ |
---|
| 440 | entry_size = calcsize(self.fields_fmt_struct) |
---|
| 441 | entry = self.deserialize(buf[0:entry_size])[0] |
---|
| 442 | |
---|
| 443 | str_out = self.name + ': ' |
---|
| 444 | |
---|
| 445 | for k in entry.keys(): |
---|
| 446 | s = entry[k] |
---|
| 447 | if((type(s) is int) or (type(s) is long)): |
---|
| 448 | str_out += "\n {0:30s} = {1:20d} (0x{1:16x})".format(k, s) |
---|
| 449 | elif(type(s) is str): |
---|
| 450 | s = map(ord, list(entry[k])) |
---|
| 451 | str_out += "\n {0:30s} = [".format(k) |
---|
| 452 | for x in s: |
---|
| 453 | str_out += "{0:d}, ".format(x) |
---|
| 454 | str_out += "\b\b]" |
---|
| 455 | |
---|
| 456 | str_out += "\n" |
---|
| 457 | |
---|
| 458 | return str_out |
---|
| 459 | |
---|
| 460 | |
---|
| 461 | def _entry_as_byte_array_string(self, buf): |
---|
| 462 | """Generate a string representation of the entry from a buffer as an array of bytes. This |
---|
| 463 | method should only be used for debugging log data parsing and log index generation, not |
---|
| 464 | for general creation of text log files. |
---|
| 465 | """ |
---|
| 466 | entry_size = calcsize(self.fields_fmt_struct) |
---|
| 467 | entry = self.deserialize(buf[0:entry_size])[0] |
---|
| 468 | |
---|
| 469 | str_out = self.name + ': ' |
---|
| 470 | |
---|
| 471 | line_num = 0 |
---|
| 472 | |
---|
| 473 | for byte in range(entry_size): |
---|
| 474 | if (byte % 16) == 0: |
---|
| 475 | str_out += "\n{0:>8d}: ".format(line_num) |
---|
| 476 | line_num += 16 |
---|
| 477 | str_out += "0x{0:02x} ".format(ord(buf[byte])) |
---|
| 478 | |
---|
| 479 | str_out += "\n" |
---|
| 480 | |
---|
| 481 | print(entry) |
---|
| 482 | |
---|
| 483 | return str_out |
---|
| 484 | |
---|
| 485 | |
---|
| 486 | def deserialize(self, buf): |
---|
| 487 | """Unpacks one or more raw log entries of the same type into a list of dictionaries |
---|
| 488 | |
---|
| 489 | Args: |
---|
| 490 | buf (bytearray): Array of raw log data containing 1 or more log entries of the same type. |
---|
| 491 | |
---|
| 492 | Returns: |
---|
| 493 | entries (List of dict): |
---|
| 494 | Each dictionary in the list has one value per field in the log entry definition using |
---|
| 495 | the field names as keys. |
---|
| 496 | """ |
---|
| 497 | from collections import OrderedDict |
---|
| 498 | |
---|
| 499 | ret_val = [] |
---|
| 500 | buf_size = len(buf) |
---|
| 501 | entry_size = calcsize(self.fields_fmt_struct) |
---|
| 502 | index = 0 |
---|
| 503 | |
---|
| 504 | while (index < buf_size): |
---|
| 505 | try: |
---|
| 506 | dataTuple = unpack(self.fields_fmt_struct, buf[index:index+entry_size]) |
---|
| 507 | all_names = self.get_field_names() |
---|
| 508 | all_fmts = self.get_field_struct_formats() |
---|
| 509 | |
---|
| 510 | # Filter out names for fields ignored during unpacking |
---|
| 511 | names = [n for (n,f) in zip(all_names, all_fmts) if 'x' not in f] |
---|
| 512 | |
---|
| 513 | # Use OrderedDict to preserve user-specified field order |
---|
| 514 | ret_val.append(OrderedDict(zip(names, dataTuple))) |
---|
| 515 | |
---|
| 516 | except error as err: |
---|
| 517 | print("Error unpacking {0} buffer with len {1}: {2}".format(self.name, len(buf), err)) |
---|
| 518 | |
---|
| 519 | index += entry_size |
---|
| 520 | |
---|
| 521 | return ret_val |
---|
| 522 | |
---|
| 523 | |
---|
| 524 | def serialize(self, entries): |
---|
| 525 | """Packs one or more list of dictionaries into a buffer of log entries of the same type |
---|
| 526 | |
---|
| 527 | Args: |
---|
| 528 | entry_list (dictionary): Array of dictionaries for 1 or more log entries of the same type |
---|
| 529 | |
---|
| 530 | Returns: |
---|
| 531 | data (bytearray): Bytearray of packed binary data |
---|
| 532 | """ |
---|
| 533 | length = 1 |
---|
| 534 | ret_val = "" |
---|
| 535 | entry_size = calcsize(self.fields_fmt_struct) |
---|
| 536 | |
---|
| 537 | # Convert entries to a list if it is not already one |
---|
| 538 | if type(entries) is not list: |
---|
| 539 | entries = [entries] |
---|
| 540 | |
---|
| 541 | # Pack each of the entries into a single data buffer |
---|
| 542 | for entry in entries: |
---|
| 543 | fields = [] |
---|
| 544 | tmp_values = [] |
---|
| 545 | used_field = [] |
---|
| 546 | |
---|
| 547 | for field in self._fields: |
---|
| 548 | if 'x' not in field[1]: |
---|
| 549 | fields.append(field[0]) |
---|
| 550 | try: |
---|
| 551 | tmp_values.append(entry[field[0]]) |
---|
| 552 | used_field.append(True) |
---|
| 553 | except KeyError: |
---|
| 554 | tmp_values.append(0) |
---|
| 555 | used_field.append(False) |
---|
| 556 | |
---|
| 557 | if (False): |
---|
| 558 | print("Serialize Entry:") |
---|
| 559 | print(fields) |
---|
| 560 | print(used_field) |
---|
| 561 | print(tmp_values) |
---|
| 562 | |
---|
| 563 | ret_val += pack(self.fields_fmt_struct, *tmp_values) |
---|
| 564 | |
---|
| 565 | if (entry_size * length) != len(ret_val): |
---|
| 566 | msg = "WARNING: Sizes do not match.\n" |
---|
| 567 | msg += " Expected {0} bytes".format(entry_size * length) |
---|
| 568 | msg += " Buffer is {0} bytes".format(len(ret_val)) |
---|
| 569 | print(msg) |
---|
| 570 | |
---|
| 571 | return ret_val |
---|
| 572 | |
---|
| 573 | |
---|
| 574 | # ------------------------------------------------------------------------- |
---|
| 575 | # Internal methods for the WlanExpLogEntryType |
---|
| 576 | # ------------------------------------------------------------------------- |
---|
| 577 | def _update_field_defs(self): |
---|
| 578 | """Internal method to update fields.""" |
---|
| 579 | import numpy as np |
---|
| 580 | |
---|
| 581 | # Update the fields format used by struct unpack/calcsize |
---|
| 582 | self.fields_fmt_struct = ' '.join(self.get_field_struct_formats()) |
---|
| 583 | |
---|
| 584 | # Update the numpy dtype definition |
---|
| 585 | # fields_np_dt is a numpy dtype, built using a dictionary of names/formats/offsets: |
---|
| 586 | # {'names':[field_names], 'formats':[field_formats], 'offsets':[field_offsets]} |
---|
| 587 | # We specify each field's byte offset explicitly. Byte offsets for fields in _fields |
---|
| 588 | # are inferred, assuming tight C-struct-type packing (same assumption as struct.unpack) |
---|
| 589 | |
---|
| 590 | get_np_size = (lambda f: np.dtype(f).itemsize) |
---|
| 591 | |
---|
| 592 | # Compute the offset of each real field, inferred by the sizes of all previous fields |
---|
| 593 | # This loop must look at all real fields, even ignored/padding fields |
---|
| 594 | sizes = list(map(get_np_size, [f[2] for f in self._fields])) |
---|
| 595 | offsets = [sum(sizes[0:i]) for i in range(len(sizes))] |
---|
| 596 | |
---|
| 597 | np_fields = self._fields |
---|
| 598 | |
---|
| 599 | # numpy processing ignores the same fields ignored by struct.unpack |
---|
| 600 | # !!!BAD!!! Doing this filtering breaks HDF5 export |
---|
| 601 | # offsets = [o for (o,f) in zip(offsets_all, self._fields) if 'x' not in f[1]] |
---|
| 602 | # np_fields = [f for f in self._fields if 'x' not in f[1]] |
---|
| 603 | |
---|
| 604 | names = [f[0] for f in np_fields] |
---|
| 605 | formats = [f[2] for f in np_fields] |
---|
| 606 | |
---|
| 607 | self.fields_np_dt = np.dtype({'names':names, 'formats':formats, 'offsets':offsets}) |
---|
| 608 | |
---|
| 609 | # Update the field offsets |
---|
| 610 | self._field_offsets = dict(zip(names, offsets)) |
---|
| 611 | |
---|
| 612 | # Check our definitions of struct vs numpy are in sync |
---|
| 613 | struct_size = calcsize(self.fields_fmt_struct) |
---|
| 614 | np_size = self.fields_np_dt.itemsize |
---|
| 615 | |
---|
| 616 | if (struct_size != np_size): |
---|
| 617 | msg = "WARNING: Definitions of struct {0} do not match.\n".format(self.name) |
---|
| 618 | msg += " Struct size = {0} Numpy size = {1}".format(struct_size, np_size) |
---|
| 619 | print(msg) |
---|
| 620 | |
---|
| 621 | def __eq__(self, other): |
---|
| 622 | """WlanExpLogEntryType are equal if their names are equal.""" |
---|
| 623 | if type(other) is str: |
---|
| 624 | return self.name == other |
---|
| 625 | else: |
---|
| 626 | return (isinstance(other, self.__class__) and (self.name == other.name)) |
---|
| 627 | |
---|
| 628 | def __lt__(self, other): |
---|
| 629 | """WlanExpLogEntryType are less than if their names are less than.""" |
---|
| 630 | if type(other) is str: |
---|
| 631 | return self.name < other |
---|
| 632 | else: |
---|
| 633 | return (isinstance(other, self.__class__) and (self.name < other.name)) |
---|
| 634 | |
---|
| 635 | def __gt__(self, other): |
---|
| 636 | """WlanExpLogEntryType are greater than if their names are greater than.""" |
---|
| 637 | if type(other) is str: |
---|
| 638 | return self.name > other |
---|
| 639 | else: |
---|
| 640 | return (isinstance(other, self.__class__) and (self.name > other.name)) |
---|
| 641 | |
---|
| 642 | def __ne__(self, other): |
---|
| 643 | return not self.__eq__(other) |
---|
| 644 | |
---|
| 645 | def __hash__(self): |
---|
| 646 | return hash(self.name) |
---|
| 647 | |
---|
| 648 | def __repr__(self): |
---|
| 649 | return self.name |
---|
| 650 | |
---|
| 651 | # End class |
---|
| 652 | |
---|
| 653 | |
---|
| 654 | # ----------------------------------------------------------------------------- |
---|
| 655 | # Log Entry Type Functions |
---|
| 656 | # ----------------------------------------------------------------------------- |
---|
| 657 | |
---|
| 658 | def np_array_add_txrx_ltg_fields(np_arr_orig, docs_only=False): |
---|
| 659 | """Add 'virtual' fields to TX/RX LTG packets.""" |
---|
| 660 | return np_array_add_fields(np_arr_orig, mac_addr=True, ltg=True, docs_only=docs_only) |
---|
| 661 | |
---|
| 662 | # End def |
---|
| 663 | |
---|
| 664 | |
---|
| 665 | def np_array_add_txrx_fields(np_arr_orig, docs_only=False): |
---|
| 666 | """Add 'virtual' fields to TX/RX packets.""" |
---|
| 667 | return np_array_add_fields(np_arr_orig, mac_addr=True, ltg=False, docs_only=docs_only) |
---|
| 668 | |
---|
| 669 | # End def |
---|
| 670 | |
---|
| 671 | |
---|
| 672 | def np_array_add_fields(np_arr_orig, mac_addr=False, ltg=False, docs_only=False): |
---|
| 673 | """Add 'virtual' fields to the numpy array. |
---|
| 674 | |
---|
| 675 | This is an example of a gen_numpy_array_callback |
---|
| 676 | |
---|
| 677 | Args: |
---|
| 678 | np_arr_orig (Numpy Array): Numpy array to extend |
---|
| 679 | mac_addr (bool, optional): Add MAC address information to the numpy array? |
---|
| 680 | ltg (bool, optional): Add LTG information ot the numpy array? |
---|
| 681 | docs_only (bool): Only generate documentation strings for virtual fields? |
---|
| 682 | If this is True, then only documentation strings are returned. |
---|
| 683 | |
---|
| 684 | Returns: |
---|
| 685 | np_array (Numpy Array): Updated numpy array |
---|
| 686 | |
---|
| 687 | Extend the default np_arr with convenience fields for: |
---|
| 688 | * MAC header addresses |
---|
| 689 | * LTG packet information |
---|
| 690 | |
---|
| 691 | IMPORTANT: np_arr uses the original bytearray as its underlying data |
---|
| 692 | We must operate on a copy to avoid clobbering log entries adjacent to the |
---|
| 693 | Tx or Rx entries being extended. |
---|
| 694 | """ |
---|
| 695 | import numpy as np |
---|
| 696 | |
---|
| 697 | names = () |
---|
| 698 | formats = () |
---|
| 699 | descs = () |
---|
| 700 | |
---|
| 701 | # Add the MAC address fields |
---|
| 702 | if mac_addr: |
---|
| 703 | names += ('addr1', 'addr2', 'addr3', 'mac_seq') |
---|
| 704 | formats += ('uint64', 'uint64', 'uint64', 'uint16') |
---|
| 705 | descs += ('MAC Header Address 1', 'MAC Header Address 2', 'MAC Header Address 3', 'MAC Header Sequence Number') |
---|
| 706 | |
---|
| 707 | # Add the LTG fields |
---|
| 708 | if ltg: |
---|
| 709 | names += ('ltg_uniq_seq', 'ltg_flow_id') |
---|
| 710 | formats += ('uint64', 'uint64') |
---|
| 711 | descs += ('Unique sequence number for LTG packet', 'LTG Flow ID, calculated as:\n 16LSB: LTG instance ID\n 48MSB: Destination MAC address') |
---|
| 712 | |
---|
| 713 | # If there are no fields to add, just return the original array |
---|
| 714 | if not names: |
---|
| 715 | return np_arr_orig |
---|
| 716 | |
---|
| 717 | if docs_only: |
---|
| 718 | ret_str = 'The following fields are populated when the log entry is part of a numpy array generated via the {{{generate_numpy_array}}} method. ' |
---|
| 719 | ret_str += 'These fields are calculated from the underlying bytes in the raw log entries and are stored in more convenient formats tha the raw ' |
---|
| 720 | ret_str += 'log fields. For example, these MAC address fields are 48-bit values stored in 64-bit integers. These integer addresses are much easier ' |
---|
| 721 | ret_str += 'to use when filtering Tx/Rx log entries using numpy and pandas.' |
---|
| 722 | |
---|
| 723 | ret_list = zip(names, formats, descs) |
---|
| 724 | |
---|
| 725 | return (ret_str, ret_list) |
---|
| 726 | |
---|
| 727 | # Return if array is None to not raise an Attribute Exception |
---|
| 728 | if np_arr_orig is None: |
---|
| 729 | return np_arr_orig |
---|
| 730 | |
---|
| 731 | # Create a new numpy dtype with additional fields |
---|
| 732 | dt_new = extend_np_dt(np_arr_orig.dtype, {'names': names, 'formats': formats}) |
---|
| 733 | |
---|
| 734 | # Initialize the output array (same shape, new dtype) |
---|
| 735 | np_arr_out = np.zeros(np_arr_orig.shape, dtype=dt_new) |
---|
| 736 | |
---|
| 737 | # Copy data from the base numpy array into the output array |
---|
| 738 | for f in np_arr_orig.dtype.names: |
---|
| 739 | # TODO: maybe don't copy fields that are ignored in the struct format? |
---|
| 740 | # problem is non-TxRx entries would still have these fields in their numpy versions |
---|
| 741 | np_arr_out[f] = np_arr_orig[f] |
---|
| 742 | |
---|
| 743 | # Extract the MAC payload (the payload is at a minimum a 24-entry uint8 array) |
---|
| 744 | mac_hdrs = np_arr_orig['mac_payload'] |
---|
| 745 | |
---|
| 746 | # Populate the MAC Address fields |
---|
| 747 | if mac_addr: |
---|
| 748 | # Helper array of powers of 2 |
---|
| 749 | # this array arranges bytes such that they match other u64 representations of MAC addresses |
---|
| 750 | # elsewhere in the framework |
---|
| 751 | addr_conv_arr = np.uint64(2)**np.array(range(40, -1, -8), dtype='uint64') |
---|
| 752 | |
---|
| 753 | # Compute values for address-as-int fields using numpy's dot-product routine |
---|
| 754 | # MAC header offsets here select the 3 6-byte address fields and 1 3-byte sequence number |
---|
| 755 | # Each field is conditioned on the MAC payload length exceeding the bytes needed to compute the field |
---|
| 756 | # This check handles the case of logging code being changed to record short/no payloads or |
---|
| 757 | # for short packet Tx/Rx, such as ACKs, which do not contain all 3 address and sequence number fields |
---|
| 758 | |
---|
| 759 | arr_filt = np_arr_out['mac_payload_len'] >= 10 |
---|
| 760 | np_arr_out['addr1'][arr_filt] = np.dot(addr_conv_arr, np.transpose(mac_hdrs[arr_filt, 4:10])) |
---|
| 761 | |
---|
| 762 | arr_filt = np_arr_out['mac_payload_len'] >= 16 |
---|
| 763 | np_arr_out['addr2'][arr_filt] = np.dot(addr_conv_arr, np.transpose(mac_hdrs[arr_filt, 10:16])) |
---|
| 764 | |
---|
| 765 | arr_filt = np_arr_out['mac_payload_len'] >= 22 |
---|
| 766 | np_arr_out['addr3'][arr_filt] = np.dot(addr_conv_arr, np.transpose(mac_hdrs[arr_filt, 16:22])) |
---|
| 767 | |
---|
| 768 | arr_filt = np_arr_out['mac_payload_len'] >= 24 |
---|
| 769 | np_arr_out['mac_seq'][arr_filt] = np.dot(mac_hdrs[arr_filt, 22:24], [1, 256]) // 16 |
---|
| 770 | |
---|
| 771 | # Populate the LTG fields |
---|
| 772 | if ltg: |
---|
| 773 | # Helper array of powers of 2 |
---|
| 774 | # this array arranges bytes such that they match other u64 representations of MAC addresses |
---|
| 775 | # elsewhere in the framework |
---|
| 776 | uniq_seq_conv_arr = np.uint64(2)**np.array(range(0, 64, 8), dtype='uint64') |
---|
| 777 | flow_id_conv_arr = np.uint64(2)**np.array(range(0, 32, 8), dtype='uint64') |
---|
| 778 | |
---|
| 779 | # Compute the LTG unique sequence number from the bytes in the LTG mac payload |
---|
| 780 | np_arr_out['ltg_uniq_seq'] = np.dot(uniq_seq_conv_arr, np.transpose(mac_hdrs[:, 32:40])) |
---|
| 781 | |
---|
| 782 | # Compute the LTG flow ID from the bytes in the LTG mac payload and the transmitting address (ie 'addr2') if present. |
---|
| 783 | # flow_id[63:16] = Transmitting address |
---|
| 784 | # flow_id[15: 0] = LTG ID from LTG mac payload |
---|
| 785 | try: |
---|
| 786 | np_arr_out['ltg_flow_id'] = (np_arr_out['addr2'] << 16) + (np.dot(flow_id_conv_arr, np.transpose(mac_hdrs[:, 40:44])) & 0xFFFF) |
---|
| 787 | except KeyError: |
---|
| 788 | np_arr_out['ltg_flow_id'] = np.dot(flow_id_conv_arr, np.transpose(mac_hdrs[:, 40:44])) |
---|
| 789 | |
---|
| 790 | return np_arr_out |
---|
| 791 | |
---|
| 792 | # End def |
---|
| 793 | |
---|
| 794 | |
---|
| 795 | def extend_np_dt(dt_orig, new_fields=None): |
---|
| 796 | """Extends a numpy dtype object with additional fields. |
---|
| 797 | |
---|
| 798 | Args: |
---|
| 799 | dt_orig (Numpy DataType): Original Numpy data type |
---|
| 800 | new_fields (dict): Dictionary with keys 'names' and 'formats', same as when specifying |
---|
| 801 | new dtype objects. The return dtype will *not* contain byte offset values for existing or |
---|
| 802 | new fields, even if exisiting fields had specified offsets. Thus the original dtype should |
---|
| 803 | be used to interpret raw data buffers before the extended dtype is used to add new fields. |
---|
| 804 | """ |
---|
| 805 | import numpy as np |
---|
| 806 | from collections import OrderedDict |
---|
| 807 | |
---|
| 808 | if(type(dt_orig) is not np.dtype): |
---|
| 809 | raise Exception("ERROR: extend_np_dt requires valid numpy dtype as input") |
---|
| 810 | else: |
---|
| 811 | # Use ordered dictionary to preserve original field order (not required, just convenient) |
---|
| 812 | dt_ext = OrderedDict() |
---|
| 813 | |
---|
| 814 | # Extract the names/formats/offsets dictionary for the base dtype |
---|
| 815 | # dt.fields returns dictionary with field names as keys and |
---|
| 816 | # values of (dtype, offset). The dtype objects in the first tuple field |
---|
| 817 | # can be used as values in the 'formats' entry when creating a new dtype |
---|
| 818 | # This approach will preserve the types and dimensions of scalar and non-scalar fields |
---|
| 819 | dt_ext['names'] = list(dt_orig.names) |
---|
| 820 | dt_ext['formats'] = [dt_orig.fields[f][0] for f in dt_orig.names] |
---|
| 821 | |
---|
| 822 | if(type(new_fields) is dict): |
---|
| 823 | # Add new fields to the extended dtype |
---|
| 824 | dt_ext['names'].extend(new_fields['names']) |
---|
| 825 | dt_ext['formats'].extend(new_fields['formats']) |
---|
| 826 | elif type(new_fields) is not None: |
---|
| 827 | raise Exception("ERROR: new_fields argument must be dictionary with keys 'names' and 'formats'") |
---|
| 828 | |
---|
| 829 | # Construct and return the new numpy dtype object |
---|
| 830 | dt_new = np.dtype(dt_ext) |
---|
| 831 | |
---|
| 832 | return dt_new |
---|
| 833 | |
---|
| 834 | # End def |
---|
| 835 | |
---|
| 836 | |
---|
| 837 | # ----------------------------------------------------------------------------- |
---|
| 838 | # Log Entry Type Definitions |
---|
| 839 | # ----------------------------------------------------------------------------- |
---|
| 840 | |
---|
| 841 | # The code below runs when this module is imported to define each log entry type implemented |
---|
| 842 | # in the reference design. However the append_field_defs method depends on numpy, which is |
---|
| 843 | # not avaiable on the warpproject.org server, so this code will break autodoc as-is |
---|
| 844 | # The server sets an environment variable BUILDING_DOCS_ON_SERVER when it runs post-svn-commit |
---|
| 845 | # Skip all code below if this variable is set |
---|
| 846 | if not os.environ.get('BUILDING_DOCS_ON_SERVER', False): |
---|
| 847 | |
---|
| 848 | # ----------------------------------------------------------------------------- |
---|
| 849 | # Common Log Entry Contants |
---|
| 850 | # ----------------------------------------------------------------------------- |
---|
| 851 | |
---|
| 852 | # Packet Type/Sub-type values |
---|
| 853 | # Matches definition in wlan_mac_802_11_defs.h (802.11-2012 Table 8-1) |
---|
| 854 | common_pkt_types = util.consts_dict({ |
---|
| 855 | # Management sub-types |
---|
| 856 | 'ASSOC_REQ' : 0x00, |
---|
| 857 | 'ASSOC_RESP' : 0x10, |
---|
| 858 | 'REASSOC_REQ' : 0x20, |
---|
| 859 | 'REASSOC_RESP' : 0x30, |
---|
| 860 | 'PROBE_REQ' : 0x40, |
---|
| 861 | 'PROBE_RESP' : 0x50, |
---|
| 862 | 'BEACON' : 0x80, |
---|
| 863 | 'DISASSOC' : 0xA0, |
---|
| 864 | 'AUTH' : 0xB0, |
---|
| 865 | 'DEAUTH' : 0xC0, |
---|
| 866 | 'ACTION' : 0xD0, |
---|
| 867 | |
---|
| 868 | # Control sub-types |
---|
| 869 | 'BLOCK_ACK_REQ' : 0x84, |
---|
| 870 | 'BLOCK_ACK' : 0x94, |
---|
| 871 | 'RTS' : 0xB4, |
---|
| 872 | 'CTS' : 0xC4, |
---|
| 873 | 'ACK' : 0xD4, |
---|
| 874 | |
---|
| 875 | # Data sub-types |
---|
| 876 | 'DATA' : 0x08, |
---|
| 877 | 'NULLDATA' : 0x48, |
---|
| 878 | 'QOSDATA' : 0x88, |
---|
| 879 | }) |
---|
| 880 | |
---|
| 881 | |
---|
| 882 | # ----------------------------------------------------------------------------- |
---|
| 883 | # NULL Log Entry Instance |
---|
| 884 | # ----------------------------------------------------------------------------- |
---|
| 885 | |
---|
| 886 | # The NULL entry is used to "remove" an existing entry within the log. |
---|
| 887 | # By replacing the current entry type with the NULL entry type and zeroing |
---|
| 888 | # out all data following the header, this can effectively remove an entry |
---|
| 889 | # without changing the memory footprint of the log. NULL entries will |
---|
| 890 | # be filtered and never show up in the raw_log_index. |
---|
| 891 | |
---|
| 892 | entry_null = WlanExpLogEntryType(name='NULL', entry_type_id=ENTRY_TYPE_NULL) |
---|
| 893 | |
---|
| 894 | |
---|
| 895 | # ----------------------------------------------------------------------------- |
---|
| 896 | # Virtual Log Entry Instances |
---|
| 897 | # ----------------------------------------------------------------------------- |
---|
| 898 | |
---|
| 899 | ########################################################################### |
---|
| 900 | # Rx Common |
---|
| 901 | # |
---|
| 902 | entry_rx_common = WlanExpLogEntryType(name='RX_COMMON_FIELDS', entry_type_id=None) |
---|
| 903 | |
---|
| 904 | entry_rx_common.description = 'These log entries will only be created for packets that are passed to the high-level MAC code in CPU High. If ' |
---|
| 905 | entry_rx_common.description += 'the low-level MAC filter drops the packet, it will not be logged. For full "monitor mode" ensure the low-level ' |
---|
| 906 | entry_rx_common.description += 'MAC filter is configured to pass all receptions up to CPU High.' |
---|
| 907 | |
---|
| 908 | entry_rx_common.append_field_defs([ |
---|
| 909 | ('timestamp', 'Q', 'uint64', 'Value of MAC Time in microseconds at PHY RX_START'), |
---|
| 910 | ('timestamp_frac', 'B', 'uint8', 'Fractional part of timestamp (units of 6.25ns)'), |
---|
| 911 | ('phy_samp_rate', 'B', 'uint8', 'PHY sampling rate in MSps'), |
---|
| 912 | ('length', 'H', 'uint16', 'Length of payload in bytes'), |
---|
| 913 | ('cfo_est', 'i', 'int32', 'Time-domain CFO estimate from Rx PHY; Fix32_31 value, CFO as fraction of sampling frequency'), |
---|
| 914 | ('mcs', 'B', 'uint8', 'MCS index, in [0:7]'), |
---|
| 915 | ('phy_mode', 'B', 'uint8', 'PHY mode index, in [0:2]'), |
---|
| 916 | ('ant_mode', 'B', 'uint8', 'Antenna mode: [1,2,3,4] for SISO Rx on RF [A,B,C,D]'), |
---|
| 917 | ('power', 'b', 'int8', 'Rx power in dBm'), |
---|
| 918 | ('padding0', 'x', 'uint8', ''), |
---|
| 919 | ('pkt_type', 'B', 'uint8', 'Packet type, first frame control byte of 802.11 header'), |
---|
| 920 | ('channel', 'B', 'uint8', 'Channel (center frequency) index'), |
---|
| 921 | ('padding1', 'x', 'uint8', ''), |
---|
| 922 | ('rx_gain_index', 'B', 'uint8', 'Radio Rx gain index; larger values mean larger Rx gains, mapping to absolute dB is radio-dependent'), |
---|
| 923 | ('padding2', 'B', 'uint8', ''), |
---|
| 924 | ('flags', 'H', 'uint16', '1-bit flags')]) |
---|
| 925 | |
---|
| 926 | entry_rx_common.consts = util.consts_dict({ |
---|
| 927 | 'ant_mode' : util.consts_dict({ |
---|
| 928 | 'RF_A': 0x01, |
---|
| 929 | 'RF_B': 0x02, |
---|
| 930 | 'RF_C': 0x03, |
---|
| 931 | 'RF_D': 0x04, |
---|
| 932 | }), |
---|
| 933 | 'pkt_type' : common_pkt_types, |
---|
| 934 | 'phy_mode' : util.phy_modes, |
---|
| 935 | 'flags' : util.consts_dict({ |
---|
| 936 | 'FCS_GOOD' : 0x0001, |
---|
| 937 | 'DUPLICATE' : 0x0002, |
---|
| 938 | 'UNEXPECTED_RESPONSE' : 0x0004, |
---|
| 939 | 'LTG_PYLD' : 0x0040, |
---|
| 940 | 'LTG' : 0x0080 |
---|
| 941 | }) |
---|
| 942 | }) |
---|
| 943 | |
---|
| 944 | |
---|
| 945 | ########################################################################### |
---|
| 946 | # Tx CPU High Common |
---|
| 947 | # |
---|
| 948 | entry_tx_common = WlanExpLogEntryType(name='TX_HIGH_COMMON_FIELDS', entry_type_id=None) |
---|
| 949 | |
---|
| 950 | entry_tx_common.description = 'Tx events in CPU High, logged for each data and management frame created and enqueued in CPU High. See TX_LOW for log entries of ' |
---|
| 951 | entry_tx_common.description += 'actual Tx events, including re-transmissions. The time values in this log entry can be used to determine time in queue ' |
---|
| 952 | entry_tx_common.description += '(time_to_accept), time taken by CPU Low for all Tx attempts (time_to_done) and total time from creation to completion ' |
---|
| 953 | entry_tx_common.description += '(time_to_accept+time_to_done).' |
---|
| 954 | |
---|
| 955 | entry_tx_common.append_field_defs([ |
---|
| 956 | ('timestamp', 'Q', 'uint64', 'Value of MAC Time in microseconds when packet was created, immediately before it was enqueued'), |
---|
| 957 | ('time_to_accept', 'I', 'uint32', 'Time duration in microseconds between packet creation and acceptance by frame_transmit() in CPU Low'), |
---|
| 958 | ('time_to_done', 'I', 'uint32', 'Time duration in microseconds between packet acceptance by CPU Low and completion of all transmissions by CPU Low'), |
---|
| 959 | ('uniq_seq', 'Q', 'uint64', 'Unique sequence number for Tx packet; 12 LSB of this used for 802.11 MAC header sequence number'), |
---|
| 960 | ('padding0', 'I', 'uint32', ''), |
---|
| 961 | ('num_tx', 'H', 'uint16', 'Number of Tx attempts that were made for this packet'), |
---|
| 962 | ('length', 'H', 'uint16', 'Length in bytes of MPDU; includes MAC header, payload and FCS'), |
---|
| 963 | ('padding1', 'x', 'uint8', ''), |
---|
| 964 | ('pkt_type', 'B', 'uint8', 'Packet type, first frame control byte of 802.11 header'), |
---|
| 965 | ('queue_id', 'H', 'uint16', 'Tx queue ID from which the packet was retrieved'), |
---|
| 966 | ('queue_occupancy', 'H', 'uint16', 'Occupancy of the Tx queue immediately after this packet was enqueued'), |
---|
| 967 | ('flags', 'H', 'uint16', '1-bit flags')]) |
---|
| 968 | |
---|
| 969 | entry_tx_common.consts = util.consts_dict({ |
---|
| 970 | 'pkt_type' : common_pkt_types, |
---|
| 971 | 'flags' : util.consts_dict({ |
---|
| 972 | 'SUCCESSFUL' : 0x0001, |
---|
| 973 | 'LTG_PYLD' : 0x0040, |
---|
| 974 | 'LTG' : 0x0080 |
---|
| 975 | }) |
---|
| 976 | }) |
---|
| 977 | |
---|
| 978 | |
---|
| 979 | ########################################################################### |
---|
| 980 | # Tx CPU Low Common |
---|
| 981 | # |
---|
| 982 | entry_tx_low_common = WlanExpLogEntryType(name='TX_LOW_COMMON_FIELDS', entry_type_id=None) |
---|
| 983 | |
---|
| 984 | entry_tx_low_common.description = 'Record of actual PHY transmission. At least one TX_LOW will be logged for every TX_HIGH entry. Multiple TX_LOW entries ' |
---|
| 985 | entry_tx_low_common.description += 'may be created for the same TX_HIGH entry if the low-level MAC re-transmitted the frame. Use the uniq_seq fields to match ' |
---|
| 986 | entry_tx_low_common.description += 'TX_HIGH and TX_LOW entries to find records common to the same MPDU.' |
---|
| 987 | |
---|
| 988 | entry_tx_low_common.append_field_defs([ |
---|
| 989 | ('timestamp', 'Q', 'uint64', 'Value of MAC Time in microseconds when packet transmission actually started (PHY TX_START time)'), |
---|
| 990 | ('uniq_seq', 'Q', 'uint64', 'Unique sequence number of original MPDU'), |
---|
| 991 | ('mcs', 'B', 'uint8', 'MCS index in [0:7]'), |
---|
| 992 | ('phy_mode', 'B', 'uint8', 'PHY mode index, in [1:2]'), |
---|
| 993 | ('ant_mode', 'B', 'uint8', 'PHY antenna mode in [0x10, 0x20, 0x30, 0x40]'), |
---|
| 994 | ('tx_power', 'b', 'int8', 'Tx power in dBm'), |
---|
| 995 | ('reserved0', 'B', 'uint8', ''), |
---|
| 996 | ('channel', 'B', 'uint8', 'Channel (center frequency) index'), |
---|
| 997 | ('length', 'H', 'uint16', 'Length in bytes of MPDU; includes MAC header, payload and FCS'), |
---|
| 998 | ('num_slots', 'h', 'int16', 'Number of backoff slots allotted prior to this transmission; may not have been used for initial Tx (attempt_number==1); A value of -1 in this field means no backoff occured'), |
---|
| 999 | ('cw', 'H', 'uint16', 'Contention window value at time of this Tx'), |
---|
| 1000 | ('pkt_type', 'B', 'uint8', 'Packet type, (first frame control byte of 802.11 header)'), |
---|
| 1001 | ('flags', 'B', 'uint8', '1-bit Flags'), |
---|
| 1002 | ('timestamp_frac', 'B', 'uint8', 'Fractional part of Tx timestamp (units of 6.25ns)'), |
---|
| 1003 | ('phy_samp_rate', 'B', 'uint8', 'PHY Sampling Rate Mode'), |
---|
| 1004 | ('attempt_number', 'H', 'uint16', 'Transmission index for this attempt, starting at 1 (1 = first Tx)'), |
---|
| 1005 | ('reserved1', 'H', 'uint16', '')]) |
---|
| 1006 | |
---|
| 1007 | entry_tx_low_common.consts = util.consts_dict({ |
---|
| 1008 | 'ant_mode' : util.consts_dict({ |
---|
| 1009 | 'RF_A': 0x10, |
---|
| 1010 | 'RF_B': 0x20, |
---|
| 1011 | 'RF_C': 0x30, |
---|
| 1012 | 'RF_D': 0x40, |
---|
| 1013 | }), |
---|
| 1014 | 'phy_mode' : util.phy_modes, |
---|
| 1015 | 'pkt_type' : common_pkt_types, |
---|
| 1016 | 'flags' : util.consts_dict({ |
---|
| 1017 | 'RECEIVED_RESPONSE' : 0x01, |
---|
| 1018 | 'LTG' : 0x40, |
---|
| 1019 | 'LTG_PYLD' : 0x80 |
---|
| 1020 | }) |
---|
| 1021 | }) |
---|
| 1022 | |
---|
| 1023 | |
---|
| 1024 | # ----------------------------------------------------------------------------- |
---|
| 1025 | # Log Entry Type Instances |
---|
| 1026 | # ----------------------------------------------------------------------------- |
---|
| 1027 | |
---|
| 1028 | ########################################################################### |
---|
| 1029 | # Node Info |
---|
| 1030 | # |
---|
| 1031 | entry_node_info = WlanExpLogEntryType(name='NODE_INFO', entry_type_id=ENTRY_TYPE_NODE_INFO) |
---|
| 1032 | |
---|
| 1033 | entry_node_info.description = 'Details about the node hardware and its configuration. Node info values are static after boot.' |
---|
| 1034 | |
---|
| 1035 | entry_node_info.append_field_defs([ |
---|
| 1036 | ('timestamp', 'Q', 'uint64', 'Value of MAC Time in microseconds when log entry created'), |
---|
| 1037 | ('wlan_mac_addr', 'Q', 'uint64', 'MAC address of station'), |
---|
| 1038 | ('high_sw_id', 'B', 'uint8', 'ID of CPU_HIGH project'), |
---|
| 1039 | ('low_sw_id', 'B', 'uint8', 'ID of CPU_LOW project'), |
---|
| 1040 | ('padding', 'H', 'uint16', 'padding for alignment'), |
---|
| 1041 | ('high_sw_config', 'I', 'uint32', 'Configuration of CPU_HIGH'), |
---|
| 1042 | ('low_sw_config', 'I', 'uint32', 'Configuration of CPU_LOW'), |
---|
| 1043 | ('node_id', 'I', 'uint32', 'Node ID, as set during wlan_exp init'), |
---|
| 1044 | ('platform_id', 'I', 'uint32', 'Platform ID'), |
---|
| 1045 | ('serial_num', 'I', 'uint32', 'Node serial number'), |
---|
| 1046 | ('wlan_exp_version', 'I', 'uint32', 'Version of wlan_exp'), |
---|
| 1047 | ('max_tx_power_dbm', 'h', 'int16', 'Maximum transmit power'), |
---|
| 1048 | ('min_tx_power_dbm', 'h', 'int16', 'Minimum transmit power'), |
---|
| 1049 | ('cpu_high_compilation_date', '12s', '12S', 'CPU High Compilation Date string'), |
---|
| 1050 | ('cpu_high_compilation_time', '12s', '12S', 'CPU High Compilation Time string'), |
---|
| 1051 | ('cpu_low_compilation_date', '12s', '12S', 'CPU Low Compilation Date string'), |
---|
| 1052 | ('cpu_low_compilation_time', '12s', '12S', 'CPU Low Compilation Time string')]) |
---|
| 1053 | |
---|
| 1054 | entry_node_info.consts = util.consts_dict({ |
---|
| 1055 | 'high_sw_id': util.consts_dict({ |
---|
| 1056 | 'AP': 0x01, |
---|
| 1057 | 'STA': 0x02, |
---|
| 1058 | 'IBSS': 0x03, |
---|
| 1059 | }), |
---|
| 1060 | 'low_sw_id': util.consts_dict({ |
---|
| 1061 | 'DCF': 0x01, |
---|
| 1062 | 'NOMAC': 0x02, |
---|
| 1063 | }) |
---|
| 1064 | }) |
---|
| 1065 | |
---|
| 1066 | ########################################################################### |
---|
| 1067 | # Experiment Info header - actual exp_info contains a "message" field that |
---|
| 1068 | # follows this header. Since the message is variable length it is not described |
---|
| 1069 | # in the fields list below. Full exp_info entries (header + message) must be extracted |
---|
| 1070 | # directly by a user script. |
---|
| 1071 | # |
---|
| 1072 | entry_exp_info_hdr = WlanExpLogEntryType(name='EXP_INFO', entry_type_id=ENTRY_TYPE_EXP_INFO) |
---|
| 1073 | |
---|
| 1074 | entry_exp_info_hdr.description = 'Header for generic experiment info entries created by the user application. ' |
---|
| 1075 | entry_exp_info_hdr.description += 'The payload of the EXP_INFO entry is not described by the Python entry type. User ' |
---|
| 1076 | entry_exp_info_hdr.description += 'code must access the payload in the binary log data directly.' |
---|
| 1077 | |
---|
| 1078 | entry_exp_info_hdr.append_field_defs([ |
---|
| 1079 | ('timestamp', 'Q', 'uint64', 'Value of MAC Time in microseconds when log entry created'), |
---|
| 1080 | ('info_type', 'I', 'uint32', 'Exp info type - arbitrary value supplied by application'), |
---|
| 1081 | ('msg_len', 'I', 'uint32', 'Message length in bytes'),]) |
---|
| 1082 | |
---|
| 1083 | |
---|
| 1084 | ########################################################################### |
---|
| 1085 | # Time Info |
---|
| 1086 | # |
---|
| 1087 | entry_time_info = WlanExpLogEntryType(name='TIME_INFO', entry_type_id=ENTRY_TYPE_TIME_INFO) |
---|
| 1088 | |
---|
| 1089 | entry_time_info.description = 'Record of a time base event at the node. This log entry is used to enable parsing of log data ' |
---|
| 1090 | entry_time_info.description += 'recorded before and after changes to the node\'s microsecond MAC timer. This entry also allows a wlan_exp controler to ' |
---|
| 1091 | entry_time_info.description += 'write the current host time to the node log without affecting the node\'s MAC timer value. This enables adjustment ' |
---|
| 1092 | entry_time_info.description += 'of log entry timestamps to real timestamps in post-proessing.' |
---|
| 1093 | |
---|
| 1094 | entry_time_info.append_field_defs([ |
---|
| 1095 | ('timestamp', 'Q', 'uint64', 'Value of MAC Time in microseconds when log entry created (before any time change is applied)'), |
---|
| 1096 | ('time_id', 'I', 'uint32', 'Random ID value included in wlan_exp TIME_INFO command; used to find common entries across nodes'), |
---|
| 1097 | ('reason', 'I', 'uint32', 'Reason code for TIME_INFO log entry creation'), |
---|
| 1098 | ('mac_timestamp', 'Q', 'uint64', 'New value of MAC Time in microseconds '), |
---|
| 1099 | ('system_timestamp', 'Q', 'uint64', 'Value of System Time in microseconds'), |
---|
| 1100 | ('host_timestamp', 'Q', 'uint64', 'Host time in microseconds-since-epoch; 0xFFFFFFFFFFFFFFFF if unknown')]) |
---|
| 1101 | |
---|
| 1102 | entry_time_info.consts = util.consts_dict({ |
---|
| 1103 | 'reason' : util.consts_dict({ |
---|
| 1104 | 'SYSTEM' : 0x00000000, |
---|
| 1105 | 'WLAN_EXP_SET_TIME' : 0x00000001, |
---|
| 1106 | 'WLAN_EXP_ADD_LOG' : 0x00000002 |
---|
| 1107 | }) |
---|
| 1108 | }) |
---|
| 1109 | |
---|
| 1110 | |
---|
| 1111 | ########################################################################### |
---|
| 1112 | # Temperature |
---|
| 1113 | # |
---|
| 1114 | entry_node_temperature = WlanExpLogEntryType(name='NODE_TEMPERATURE', entry_type_id=ENTRY_TYPE_NODE_TEMPERATURE) |
---|
| 1115 | |
---|
| 1116 | entry_node_temperature.description = 'Record of the FPGA system monitor die temperature. This entry is only created when directed by a wlan_exp ' |
---|
| 1117 | entry_node_temperature.description += 'command. Temperature values are stored as 32-bit unsigned integers. To convert to degrees Celcius, apply ' |
---|
| 1118 | entry_node_temperature.description += '(((float)temp_u32)/(65536.0*0.00198421639)) - 273.15' |
---|
| 1119 | |
---|
| 1120 | entry_node_temperature.append_field_defs([ |
---|
| 1121 | ('timestamp', 'Q', 'uint64', 'Value of MAC Time in microseconds when log entry created'), |
---|
| 1122 | ('temp_current', 'I', 'uint32', 'Current FPGA die temperature (deg C)'), |
---|
| 1123 | ('temp_min', 'I', 'uint32', 'Minimum FPGA die temperature (deg C) since FPGA configuration or sysmon reset'), |
---|
| 1124 | ('temp_max', 'I', 'uint32', 'Maximum FPGA die temperature (deg C) since FPGA configuration or sysmon reset')]) |
---|
| 1125 | |
---|
| 1126 | |
---|
| 1127 | ########################################################################### |
---|
| 1128 | # Receive OFDM |
---|
| 1129 | # |
---|
| 1130 | entry_rx_ofdm = WlanExpLogEntryType(name='RX_OFDM', entry_type_id=ENTRY_TYPE_RX_OFDM) |
---|
| 1131 | |
---|
| 1132 | entry_rx_ofdm.description = 'Rx events from OFDM PHY. ' + entry_rx_common.description |
---|
| 1133 | |
---|
| 1134 | entry_rx_ofdm.append_field_defs(entry_rx_common.get_field_defs()) |
---|
| 1135 | entry_rx_ofdm.append_field_defs([ |
---|
| 1136 | ('chan_est', '256B', '(64,2)i2', 'OFDM Rx channel estimates, packed as [(uint16)I (uint16)Q] values, one per subcarrier'), |
---|
| 1137 | ('mac_payload_len', 'I', 'uint32', 'Length in bytes of MAC payload recorded in log for this packet'), |
---|
| 1138 | ('mac_payload', '24s', '24uint8', 'First 24 bytes of MAC payload, typically the 802.11 MAC header')]) |
---|
| 1139 | |
---|
| 1140 | entry_rx_ofdm.add_gen_numpy_array_callback(np_array_add_txrx_fields) |
---|
| 1141 | |
---|
| 1142 | entry_rx_ofdm.consts = entry_rx_common.consts.copy() |
---|
| 1143 | |
---|
| 1144 | |
---|
| 1145 | ########################################################################### |
---|
| 1146 | # Receive OFDM LTG packet |
---|
| 1147 | # |
---|
| 1148 | entry_rx_ofdm_ltg = WlanExpLogEntryType(name='RX_OFDM_LTG', entry_type_id=ENTRY_TYPE_RX_OFDM_LTG) |
---|
| 1149 | |
---|
| 1150 | entry_rx_ofdm_ltg.description = 'LTG ' + entry_rx_ofdm.description |
---|
| 1151 | |
---|
| 1152 | entry_rx_ofdm_ltg.append_field_defs(entry_rx_common.get_field_defs()) |
---|
| 1153 | entry_rx_ofdm_ltg.append_field_defs([ |
---|
| 1154 | ('chan_est', '256B', '(64,2)i2', 'OFDM Rx channel estimates, packed as [(uint16)I (uint16)Q] values, one per subcarrier'), |
---|
| 1155 | ('mac_payload_len', 'I', 'uint32', 'Length in bytes of MAC payload recorded in log for this packet'), |
---|
| 1156 | ('mac_payload', '44s', '44uint8', 'First 44 bytes of MAC payload: the 802.11 MAC header, LLC header, Packet ID, LTG ID')]) |
---|
| 1157 | |
---|
| 1158 | entry_rx_ofdm_ltg.add_gen_numpy_array_callback(np_array_add_txrx_ltg_fields) |
---|
| 1159 | |
---|
| 1160 | entry_rx_ofdm_ltg.consts = entry_rx_common.consts.copy() |
---|
| 1161 | |
---|
| 1162 | |
---|
| 1163 | ########################################################################### |
---|
| 1164 | # Receive DSSS |
---|
| 1165 | # |
---|
| 1166 | entry_rx_dsss = WlanExpLogEntryType(name='RX_DSSS', entry_type_id=ENTRY_TYPE_RX_DSSS) |
---|
| 1167 | |
---|
| 1168 | entry_rx_dsss.description = 'Rx events from DSSS PHY. ' + entry_rx_common.description |
---|
| 1169 | |
---|
| 1170 | entry_rx_dsss.append_field_defs(entry_rx_common.get_field_defs()) |
---|
| 1171 | entry_rx_dsss.append_field_defs([ |
---|
| 1172 | ('mac_payload_len', 'I', 'uint32', 'Length in bytes of MAC payload recorded in log for this packet'), |
---|
| 1173 | ('mac_payload', '24s', '24uint8', 'First 24 bytes of MAC payload, typically the 802.11 MAC header')]) |
---|
| 1174 | |
---|
| 1175 | entry_rx_dsss.add_gen_numpy_array_callback(np_array_add_txrx_fields) |
---|
| 1176 | |
---|
| 1177 | entry_rx_dsss.consts = entry_rx_common.consts.copy() |
---|
| 1178 | |
---|
| 1179 | |
---|
| 1180 | ########################################################################### |
---|
| 1181 | # Transmit from CPU High |
---|
| 1182 | # |
---|
| 1183 | entry_tx_high = WlanExpLogEntryType(name='TX_HIGH', entry_type_id=ENTRY_TYPE_TX_HIGH) |
---|
| 1184 | |
---|
| 1185 | entry_tx_high.description = entry_tx_common.description |
---|
| 1186 | |
---|
| 1187 | entry_tx_high.append_field_defs(entry_tx_common.get_field_defs()) |
---|
| 1188 | entry_tx_high.append_field_defs([ |
---|
| 1189 | ('mac_payload_len', 'I', 'uint32', 'Length in bytes of MAC payload recorded in log for this packet'), |
---|
| 1190 | ('mac_payload', '24s', '24uint8', 'First 24 bytes of MAC payload, typically the 802.11 MAC header')]) |
---|
| 1191 | |
---|
| 1192 | entry_tx_high.add_gen_numpy_array_callback(np_array_add_txrx_fields) |
---|
| 1193 | |
---|
| 1194 | entry_tx_high.consts = entry_tx_common.consts.copy() |
---|
| 1195 | |
---|
| 1196 | |
---|
| 1197 | ########################################################################### |
---|
| 1198 | # Transmit from CPU High LTG packet |
---|
| 1199 | # |
---|
| 1200 | entry_tx_high_ltg = WlanExpLogEntryType(name='TX_HIGH_LTG', entry_type_id=ENTRY_TYPE_TX_HIGH_LTG) |
---|
| 1201 | |
---|
| 1202 | entry_tx_high_ltg.description = entry_tx_common.description |
---|
| 1203 | |
---|
| 1204 | entry_tx_high_ltg.append_field_defs(entry_tx_common.get_field_defs()) |
---|
| 1205 | entry_tx_high_ltg.append_field_defs([ |
---|
| 1206 | ('mac_payload_len', 'I', 'uint32', 'Length in bytes of MAC payload recorded in log for this packet'), |
---|
| 1207 | ('mac_payload', '44s', '44uint8', 'First 44 bytes of MAC payload: the 802.11 MAC header, LLC header, Packet ID, LTG ID')]) |
---|
| 1208 | |
---|
| 1209 | entry_tx_high_ltg.add_gen_numpy_array_callback(np_array_add_txrx_ltg_fields) |
---|
| 1210 | |
---|
| 1211 | entry_tx_high_ltg.consts = entry_tx_common.consts.copy() |
---|
| 1212 | |
---|
| 1213 | |
---|
| 1214 | ########################################################################### |
---|
| 1215 | # Transmit from CPU Low |
---|
| 1216 | # |
---|
| 1217 | entry_tx_low = WlanExpLogEntryType(name='TX_LOW', entry_type_id=ENTRY_TYPE_TX_LOW) |
---|
| 1218 | |
---|
| 1219 | entry_tx_low.description = entry_tx_low_common.description |
---|
| 1220 | |
---|
| 1221 | entry_tx_low.append_field_defs(entry_tx_low_common.get_field_defs()) |
---|
| 1222 | entry_tx_low.append_field_defs([ |
---|
| 1223 | ('mac_payload_len', 'I', 'uint32', 'Length in bytes of MAC payload recorded in log for this packet'), |
---|
| 1224 | ('mac_payload', '24s', '24uint8', 'First 24 bytes of MAC payload, typically the 802.11 MAC header')]) |
---|
| 1225 | |
---|
| 1226 | entry_tx_low.add_gen_numpy_array_callback(np_array_add_txrx_fields) |
---|
| 1227 | |
---|
| 1228 | entry_tx_low.consts = entry_tx_low_common.consts.copy() |
---|
| 1229 | |
---|
| 1230 | |
---|
| 1231 | ########################################################################### |
---|
| 1232 | # Transmit from CPU Low LTG packet |
---|
| 1233 | # |
---|
| 1234 | entry_tx_low_ltg = WlanExpLogEntryType(name='TX_LOW_LTG', entry_type_id=ENTRY_TYPE_TX_LOW_LTG) |
---|
| 1235 | |
---|
| 1236 | entry_tx_low_ltg.description = entry_tx_low_common.description |
---|
| 1237 | |
---|
| 1238 | entry_tx_low_ltg.append_field_defs(entry_tx_low_common.get_field_defs()) |
---|
| 1239 | entry_tx_low_ltg.append_field_defs([ |
---|
| 1240 | ('mac_payload_len', 'I', 'uint32', 'Length in bytes of MAC payload recorded in log for this packet'), |
---|
| 1241 | ('mac_payload', '44s', '44uint8', 'First 44 bytes of MAC payload: the 802.11 MAC header, LLC header, Packet ID, LTG ID')]) |
---|
| 1242 | |
---|
| 1243 | entry_tx_low_ltg.add_gen_numpy_array_callback(np_array_add_txrx_ltg_fields) |
---|
| 1244 | |
---|
| 1245 | entry_tx_low_ltg.consts = entry_tx_low_common.consts.copy() |
---|
| 1246 | |
---|