# -*- coding: utf-8 -*-
"""
------------------------------------------------------------------------------
Mango 802.11 Reference Design Experiments Framework
- Local Traffic Generation (LTG)
------------------------------------------------------------------------------
License: Copyright 2019 Mango Communications, Inc. All rights reserved.
Use and distribution subject to terms in LICENSE.txt
------------------------------------------------------------------------------
This module provides definitions for Local Traffic Generation (LTG) on
wlan_exp nodes.
"""
from wlan_exp.info import InfoStruct
import wlan_exp.util as util
__all__ = ['Schedule', 'SchedulePeriodic', 'ScheduleRandom',
'Payload', 'PayloadMSDU', 'PayloadRandLengthMSDU', 'PayloadRawMPDU',]
# LTG Schedule IDs - must match ltg_sched_type_t enum in wlan_mac_ltg.h
LTG_SCHED_TYPE_PERIODIC = 1
LTG_SCHED_TYPE_UNIFORM_RAND = 2
# LTG Payload IDs - must match ltg_pyld_type_t enum in wlan_mac_ltg.h
LTG_PYLD_TYPE_MSDU_FIXED_LEN = 1
LTG_PYLD_TYPE_MSDU_RAND_LEN = 2
LTG_PYLD_TYPE_MPDU_RAW = 3
# LTG IDs are u32 in C; define a special value that means "all LTG IDs" for more
# efficient implementation of start_all/stop_all/remove_all commands
LTG_ID_ALL = 0xFFFFFFFF
class LTGConfig(InfoStruct):
"""LTG configuration common to all LTG types. This class is created by the LTGConfigure
command when constructing the over-the-wire command message. User code should not created
instances of this class diractly."""
flags = {'LTG_START_ON_CREATE': 0x00000001}
# Must match _ltg_config_otw_t typedef
struct_fields = [
('duration_usec', 'I', 'uint32', 'LTG duration in microseconds'),
('start_delay_usec', 'I', 'uint32', 'LTG start delay in microseconds'),
('ltg_config_flags', 'I', 'uint32', 'Flags defining LTG behavior'),
]
def __init__(self, start, start_delay, duration):
super(LTGConfig, self).__init__(field_sets=None, field_defs=self.struct_fields)
self['ltg_config_flags'] = 0
self['duration_usec'] = duration
self['start_delay_usec'] = start_delay
if(start):
self['ltg_config_flags'] |= self.flags['LTG_START_ON_CREATE']
#-----------------------------------------------------------------------------
# LTG Schedules
#-----------------------------------------------------------------------------
class Schedule(InfoStruct):
"""Base class for LTG Schedules."""
def __init__(self, field_defs):
super(Schedule, self).__init__(field_sets=None, field_defs=field_defs)
[docs]class SchedulePeriodic(Schedule):
"""Generates a new packet every `interval` seconds.
Args:
interval (float): Interval between packet creation in float seconds; actual packet
creation interval will be quantized to the LTG polling interval in CPU
High (typically 64 usec).
"""
# Must match _ltg_sched_periodic_otw typedef
struct_fields = [
('sched_type', 'I', 'uint32', 'LTG Schedule spec type'),
('interval', 'I', 'uint32', 'Interval between packet creation, in microseconds'),
]
def __init__(self, interval):
super(SchedulePeriodic, self).__init__(field_defs=self.struct_fields)
self['sched_type'] = LTG_SCHED_TYPE_PERIODIC
# Convert argument from float seconds to integer microseconds
intvl = int(float(interval) * 10**6)
if(intvl >= 2**32):
raise Exception('ERROR: interval {0} is larger than maximum {1}'.format(interval, 2**32/10**6))
self['interval'] = int(float(interval) * 10**6)
[docs]class ScheduleRandom(Schedule):
"""Generates a packet at a uniformaly-distributed random interval.
Args:
min_interval (numeric): Minimum interval between packets in seconds
max_interval (numeric): Maximum interval between packets in seconds
"""
# Must match _ltg_sched_uniform_rand_otw typedef
struct_fields = [
('sched_type', 'I', 'uint32', 'LTG Schedule spec type'),
('min_interval', 'I', 'uint32', 'Minimum interval between packet creation, in microseconds'),
('max_interval', 'I', 'uint32', 'Maximum interval between packet creation, in microseconds'),
]
def __init__(self, min_interval, max_interval):
super(ScheduleRandom, self).__init__(field_defs=self.struct_fields)
self['sched_type'] = LTG_SCHED_TYPE_UNIFORM_RAND
min_intvl_usec = int(float(min_interval) * 10**6)
max_intvl_usec = int(float(max_interval) * 10**6)
if(min_intvl_usec >= 2**32):
raise Exception('ERROR: interval {0} is larger than maximum {1}'.format(min_interval, 2**32/10**6))
if(max_intvl_usec >= 2**32):
raise Exception('ERROR: interval {0} is larger than maximum {1}'.format(max_interval, 2**32/10**6))
# Convert intervals from float seconds to integer usec
self['min_interval'] = min_intvl_usec
self['max_interval'] = max_intvl_usec
#-----------------------------------------------------------------------------
# LTG Payloads
#-----------------------------------------------------------------------------
class Payload(InfoStruct):
"""Base class for LTG Payloads. Should not be instantiated directly."""
def __init__(self, field_defs):
# InfoStruct constructor accepts either pre-defined fields in Info.py via 'field_sets'
# or caller-specified fields via 'field_defs'
super(Payload, self).__init__(field_sets=None, field_defs=field_defs)
[docs]class PayloadMSDU(Payload):
"""
LTG payload of a fixed-length MSDU with the sepcified destination address. On each execution
this LTG payload will create a single pseudo-Ethernet packet and inject the packet into the
high MAC framework at the same point as real Ethernet receptions.
The pseudo-Ethernet packet has contents:
* ``header.da = dest_addr``
* ``header.sa =`` MAC address of the transmittig node
* ``header.ethertype = 0x9090``
* ``payload = <LTG ID, uniq_seq, arbitrary bytes>``
The pseudo-Ethernet packet will be passed to the high MAC framework's Ethernet encapsulation
functions. The behavior of the encapsulation flow depends on the MAC application.
* AP: if the ``dest_addr`` matches the MAC address of an associated node, the new packet will be enqueued
for transmission to that node. If the ``dest_addr`` is a multicast address the new packet will be
enqueued in the dedicated multicast queue. Any other ``dest_addr`` will cause the packet to be discarded.
* STA: the new packet will be enqueued for transmission to the associated AP, like all other data packets
* IBSS: the new packet will be enqueued for transmission to the specified address
The payload of the LTG MSDU contains the packet's ``uniq_seq`` (64-bit unique sequence number) and the
integer ID of the LTG. The remainder of the payload (up to ``length`` bytes) is random.
Args:
dest_addr (u64 int): MSDU Destination MAC address
length (int): MSDU payload length in bytes
"""
# Must match _ltg_pyld_msdu_fixed_otw typedef
struct_fields = [
('pyld_type', 'I', 'uint32', 'LTG Payload spec type'),
('dest_addr', '6s', '6uint8', 'MSDU Destination MAC address'),
('length', 'H', 'uint16', 'Length of payload in bytes for LTG packets'),
]
def __init__(self, dest_addr, length):
super(PayloadMSDU, self).__init__(field_defs=self.struct_fields)
self['pyld_type'] = LTG_PYLD_TYPE_MSDU_FIXED_LEN
self['dest_addr'] = util.mac_addr_to_byte_str(dest_addr)
# FIXME: how to validate MSDU payload lengths
self['length'] = length
[docs]class PayloadRandLengthMSDU(Payload):
"""LTG payload of an MSDU with the sepcified destination address and uniform
random payload length. This LTG payload has the same behavior as ``PayloadMSDU``
but with random payload length.
Args:
dest_addr (u64 int): MSDU Destination MAC address
min_length (int): Minimum length of the MSDU payload in bytes
max_length (int): Maximum length of the MSDU payload in bytes
"""
# Must match _ltg_pyld_msdu_randlen_otw typedef
struct_fields = [
('pyld_type', 'I', 'uint32', 'LTG Payload spec type'),
('dest_addr', '6s', '6uint8', 'MSDU Destination MAC address'),
('min_length', 'H', 'uint16', 'Minimum length of the MSDU payload in bytes'),
('max_length', 'H', 'uint16', 'Maximum length of the MSDU payload in bytes'),
]
def __init__(self, dest_addr, min_length, max_length):
super(PayloadRandLengthMSDU, self).__init__(field_defs=self.struct_fields)
self['pyld_type'] = LTG_PYLD_TYPE_MSDU_RAND_LEN
self['dest_addr'] = util.mac_addr_to_byte_str(dest_addr)
# FIXME: how to validate MSDU payload lengths
self['min_length'] = min_length
self['max_length'] = max_length
[docs]class PayloadRawMPDU(Payload):
"""LTG payload of an arbitrary MPDU. The MPDU payload can be any byte sequence.
The Raw MPDU LTG flow creates packets with arbitrary contents and enqueues the packets for transmission without
any further proecssing. Raw MPDUs bypass all high MAC framework functions (i.e. matching destination
addresses to known stations (AP), Tx/Rx counts, source MAC address substitution (STA/IBSS), etc.).
Packets created by a Raw MPDU LTG also bypass robustness functions in the lower MAC (no RTS/CTS, no retranmissions).
The Raw MPDU LTG is useful for creating arbitrary transmissions of 802.11 control packets. It can
also be used to transmit packets as 802.11 waveforms which contain invalid 802.11 header/payloads.
Args:
payload (byte array): MPDU payload, must be iterable of numeric values; each value is cast to a u8.
The length of this argument defines the MPDU length. The actual MPDU will be
4 bytes longer than this argument due to the FCS appended before transmission.
"""
# Must match _ltg_pyld_mpdu_raw_otw typedef
struct_fields = [
('pyld_type', 'I', 'uint32', 'LTG Payload spec type'),
('pyld_flags', 'H', 'uint16', 'Flags for packet construction options'),
('length', 'H', 'uint16', 'Length of payload in bytes'),
('mcs', 'B', 'uint8', 'MCS for MPDU waveform'),
('phy_mode', 'B', 'uint8', 'PHY mode for MPDU waveform'),
('payload', '1400s', '1400uint8', 'MPDU payload'),
]
flags = {'OVERRIDE_DEFAULT_MCS': 0x00000001}
def __init__(self, payload, mcs=None, phy_mode=None):
super(PayloadRawMPDU, self).__init__(field_defs=self.struct_fields)
self['pyld_type'] = LTG_PYLD_TYPE_MPDU_RAW
self['pyld_flags'] = 0
# Check arguments
if mcs is None and phy_mode is not None:
raise Exception('ERROR: must specify phy_mode when setting mcs')
if mcs is not None and phy_mode is None:
raise Exception('ERROR: must specify mcs when setting phy_mode')
# Check if user set an explicit MCS
if mcs is not None:
self['mcs'] = int(mcs) #FIXME: validate?
self['phy_mode'] = int(phy_mode) #FIXME: validate?
self['pyld_flags'] |= self.flags['OVERRIDE_DEFAULT_MCS']
# FIXME: how to validate MPDU payload lengths
pyld_len = len(payload)
self['length'] = pyld_len
# Initialize payload to all zeros, then fill in user-supplied payload bytes
# bytearray() call will throw exception if any element of payload is invalid byte value
pyld = 1400*[0]
pyld[0:pyld_len] = bytearray(payload)
self['payload'] = pyld
# Method that mimics constructor for old FlowConfigCBR class used in old wlan_exp code
# This was the most (only?) used LTG FlowConfig
# Leave this method for a few releases to assist users in updating scripts to use the new
# ltg_create(payload,schedule) syntax for creating LTGs
def FlowConfigCBR(dest_addr=None, payload_length=None, interval=None, duration=None):
msg = "FlowConfigCBR has been deprecated. LTG payload and schedule params are now specified "
msg += "as explicit arguments to ltg_create(). A CBR flow can be created using the PayloadMSDU and "
msg += "SchedulePeriodic classes as arguments to ltg_create()."
raise Exception(msg)