From 225d2513d392f525078c395273e2fb821646926a Mon Sep 17 00:00:00 2001 From: Henri Chataing Date: Thu, 29 Feb 2024 14:09:14 -0800 Subject: Refactor APP config management - Move enum definitions to uci_packets.pdl - Move AppConfig declaration to separate module for readability - Sync APP config definitions with UCI 2.0 - Fix mis-declaration of Device Role (responder and initiator reversed) --- Cargo.lock | 2 +- py/pica/pica/packets/uci.py | 482 +++++++++++++++++++++++++-- src/app_config.rs | 497 ++++++++++++++++++++++++++++ src/lib.rs | 12 +- src/mac_address.rs | 15 + src/session.rs | 784 +++++--------------------------------------- src/uci_packets.pdl | 235 +++++++++++-- tests/data_transfer.py | 54 ++- tests/ranging.py | 54 ++- 9 files changed, 1352 insertions(+), 783 deletions(-) create mode 100644 src/app_config.rs diff --git a/Cargo.lock b/Cargo.lock index a59c5c0..67143ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -648,7 +648,7 @@ dependencies = [ [[package]] name = "pica" -version = "0.1.7" +version = "0.1.8" dependencies = [ "anyhow", "bytes", diff --git a/py/pica/pica/packets/uci.py b/py/pica/pica/packets/uci.py index 32ffbdc..0043c45 100644 --- a/py/pica/pica/packets/uci.py +++ b/py/pica/pica/packets/uci.py @@ -351,7 +351,7 @@ class AppConfigTlvType(enum.IntEnum): STS_CONFIG = 0x2 MULTI_NODE_MODE = 0x3 CHANNEL_NUMBER = 0x4 - NO_OF_CONTROLEE = 0x5 + NUMBER_OF_CONTROLEES = 0x5 DEVICE_MAC_ADDRESS = 0x6 DST_MAC_ADDRESS = 0x7 SLOT_DURATION = 0x8 @@ -360,9 +360,9 @@ class AppConfigTlvType(enum.IntEnum): MAC_FCS_TYPE = 0xb RANGING_ROUND_CONTROL = 0xc AOA_RESULT_REQ = 0xd - RNG_DATA_NTF = 0xe - RNG_DATA_NTF_PROXIMITY_NEAR = 0xf - RNG_DATA_NTF_PROXIMITY_FAR = 0x10 + SESSION_INFO_NTF_CONFIG = 0xe + NEAR_PROXIMITY_CONFIG = 0xf + FAR_PROXIMITY_CONFIG = 0x10 DEVICE_ROLE = 0x11 RFRAME_CONFIG = 0x12 RSSI_REPORTING = 0x13 @@ -374,13 +374,11 @@ class AppConfigTlvType(enum.IntEnum): DATA_REPETITION_COUNT = 0x19 RANGING_TIME_STRUCT = 0x1a SLOTS_PER_RR = 0x1b - TX_ADAPTIVE_PAYLOAD_POWER = 0x1c - RNG_DATA_NTF_AOA_BOUND = 0x1d - RESPONDER_SLOT_INDEX = 0x1e + AOA_BOUND_CONFIG = 0x1d PRF_MODE = 0x1f CAP_SIZE_RANGE = 0x20 TX_JITTER_WINDOW_SIZE = 0x21 - SCHEDULED_MODE = 0x22 + SCHEDULE_MODE = 0x22 KEY_ROTATION = 0x23 KEY_ROTATION_RATE = 0x24 SESSION_PRIORITY = 0x25 @@ -397,13 +395,7 @@ class AppConfigTlvType(enum.IntEnum): SUB_SESSION_ID = 0x30 BPRF_PHR_DATA_RATE = 0x31 MAX_NUMBER_OF_MEASUREMENTS = 0x32 - UL_TDOA_TX_INTERVAL = 0x33 - UL_TDOA_RANDOM_WINDOW = 0x34 STS_LENGTH = 0x35 - SUSPEND_RANGING_ROUNDS = 0x36 - UL_TDOA_NTF_REPORT_CONFIG = 0x37 - UL_TDOA_DEVICE_ID = 0x38 - UL_TDOA_TX_TIMESTAMP = 0x39 MIN_FRAMES_PER_RR = 0x3a MTU_SIZE = 0x3b INTER_FRAME_INTERVAL = 0x3c @@ -413,10 +405,10 @@ class AppConfigTlvType(enum.IntEnum): DL_TDOA_ANCHOR_CFO = 0x40 DL_TDOA_ANCHOR_LOCATION = 0x41 DL_TDOA_TX_ACTIVE_RANGING_ROUNDS = 0x42 - DL_TDOA_BLOCK_STRIDING = 0x43 + DL_TDOA_BLOCK_SKIPPING = 0x43 DL_TDOA_TIME_REFERENCE_ANCHOR = 0x44 SESSION_KEY = 0x45 - SUBSESSION_KEY = 0x46 + SUB_SESSION_KEY = 0x46 SESSION_DATA_TRANSFER_STATUS_NTF_CONFIG = 0x47 SESSION_TIME_BASE = 0x48 DL_TDOA_RESPONDER_TOF = 0x49 @@ -433,15 +425,434 @@ class AppConfigTlvType(enum.IntEnum): return v -class FrameReportTlvType(enum.IntEnum): - RSSI = 0x0 - AOA = 0x1 - CIR = 0x2 +class DeviceType(enum.IntEnum): + CONTROLEE = 0x0 + CONTROLLER = 0x1 @staticmethod - def from_int(v: int) -> Union[int, 'FrameReportTlvType']: + def from_int(v: int) -> Union[int, 'DeviceType']: try: - return FrameReportTlvType(v) + return DeviceType(v) + except ValueError as exn: + raise exn + + +class RangingRoundUsage(enum.IntEnum): + SS_TWR_DEFERRED_MODE = 0x1 + DS_TWR_DEFERRED_MODE = 0x2 + SS_TWR_NON_DEFERRED_MODE = 0x3 + DS_TWR_NON_DEFERRED_MODE = 0x4 + ON_WAY_RANGING_DL_TDOA = 0x5 + OWR_AOA_MEASUREMENT = 0x6 + ESS_TWR_NON_DEFERRED = 0x7 + ADS_TWR_NON_DEFERRED = 0x8 + + @staticmethod + def from_int(v: int) -> Union[int, 'RangingRoundUsage']: + try: + return RangingRoundUsage(v) + except ValueError as exn: + raise exn + + +class StsConfig(enum.IntEnum): + STATIC = 0x0 + DYNAMIC = 0x1 + DYNAMIC_FOR_RESPONDER_SUB_SESSION_KEY = 0x2 + PROVISIONED = 0x3 + PROVISIONED_FOR_RESPONDER_SUB_SESSION_KEY = 0x4 + + @staticmethod + def from_int(v: int) -> Union[int, 'StsConfig']: + try: + return StsConfig(v) + except ValueError as exn: + raise exn + + +class MultiNodeMode(enum.IntEnum): + ONE_TO_ONE = 0x0 + ONE_TO_MANY = 0x1 + + @staticmethod + def from_int(v: int) -> Union[int, 'MultiNodeMode']: + try: + return MultiNodeMode(v) + except ValueError as exn: + raise exn + + +class ChannelNumber(enum.IntEnum): + CHANNEL_NUMBER_5 = 0x5 + CHANNEL_NUMBER_6 = 0x6 + CHANNEL_NUMBER_8 = 0x8 + CHANNEL_NUMBER_9 = 0x9 + CHANNEL_NUMBER_10 = 0xa + CHANNEL_NUMBER_12 = 0xc + CHANNEL_NUMBER_13 = 0xd + CHANNEL_NUMBER_14 = 0xe + + @staticmethod + def from_int(v: int) -> Union[int, 'ChannelNumber']: + try: + return ChannelNumber(v) + except ValueError as exn: + raise exn + + +class MacFcsType(enum.IntEnum): + CRC_16 = 0x0 + CRC_32 = 0x1 + + @staticmethod + def from_int(v: int) -> Union[int, 'MacFcsType']: + try: + return MacFcsType(v) + except ValueError as exn: + raise exn + + +@dataclass +class RangingRoundControl(Packet): + rrrm: int = field(kw_only=True, default=0) + rcp: int = field(kw_only=True, default=0) + mrp: int = field(kw_only=True, default=0) + mrm: int = field(kw_only=True, default=0) + + def __post_init__(self): + pass + + @staticmethod + def parse(span: bytes) -> Tuple['RangingRoundControl', bytes]: + fields = {'payload': None} + if len(span) < 1: + raise Exception('Invalid packet size') + fields['rrrm'] = (span[0] >> 0) & 0x1 + if (span[0] >> 1) & 0x1 != 0x1: + raise Exception('Unexpected fixed field value') + fields['rcp'] = (span[0] >> 2) & 0x1 + fields['mrp'] = (span[0] >> 6) & 0x1 + fields['mrm'] = (span[0] >> 7) & 0x1 + span = span[1:] + return RangingRoundControl(**fields), span + + def serialize(self, payload: bytes = None) -> bytes: + _span = bytearray() + if self.rrrm > 1: + print(f"Invalid value for field RangingRoundControl::rrrm: {self.rrrm} > 1; the value will be truncated") + self.rrrm &= 1 + if self.rcp > 1: + print(f"Invalid value for field RangingRoundControl::rcp: {self.rcp} > 1; the value will be truncated") + self.rcp &= 1 + if self.mrp > 1: + print(f"Invalid value for field RangingRoundControl::mrp: {self.mrp} > 1; the value will be truncated") + self.mrp &= 1 + if self.mrm > 1: + print(f"Invalid value for field RangingRoundControl::mrm: {self.mrm} > 1; the value will be truncated") + self.mrm &= 1 + _value = ( + (self.rrrm << 0) | + (1 << 1) | + (self.rcp << 2) | + (self.mrp << 6) | + (self.mrm << 7) + ) + _span.append(_value) + return bytes(_span) + + @property + def size(self) -> int: + return 1 + +class AoaResultReq(enum.IntEnum): + AOA_DISABLED = 0x0 + AOA_ENABLED = 0x1 + AOA_ENABLED_AZIMUTH_ONLY = 0x2 + AOA_ENABLED_ELEVATION_ONLY = 0x3 + + @staticmethod + def from_int(v: int) -> Union[int, 'AoaResultReq']: + try: + return AoaResultReq(v) + except ValueError as exn: + raise exn + + +class SessionInfoNtfConfig(enum.IntEnum): + DISABLE = 0x0 + ENABLE = 0x1 + ENABLE_PROXIMITY_TRIGGER = 0x2 + ENABLE_AOA_TRIGGER = 0x3 + ENABLE_PROXIMITY_AOA_TRIGGER = 0x4 + ENABLE_PROXIMITY_EDGE_TRIGGER = 0x5 + ENABLE_AOA_EDGE_TRIGGER = 0x6 + ENABLE_PROXIMITY_AOA_EDGE_TRIGGER = 0x7 + + @staticmethod + def from_int(v: int) -> Union[int, 'SessionInfoNtfConfig']: + try: + return SessionInfoNtfConfig(v) + except ValueError as exn: + raise exn + + +class DeviceRole(enum.IntEnum): + RESPONDER = 0x0 + INITIATOR = 0x1 + ADVERTISER = 0x5 + OBSERVER = 0x6 + DT_ANCHOR = 0x7 + DT_TAG = 0x8 + + @staticmethod + def from_int(v: int) -> Union[int, 'DeviceRole']: + try: + return DeviceRole(v) + except ValueError as exn: + raise exn + + +class RframeConfig(enum.IntEnum): + SP0 = 0x0 + SP1 = 0x1 + SP3 = 0x3 + + @staticmethod + def from_int(v: int) -> Union[int, 'RframeConfig']: + try: + return RframeConfig(v) + except ValueError as exn: + raise exn + + +class RssiReporting(enum.IntEnum): + DISABLE = 0x0 + ENABLE = 0x1 + + @staticmethod + def from_int(v: int) -> Union[int, 'RssiReporting']: + try: + return RssiReporting(v) + except ValueError as exn: + raise exn + + +class PsduDataRate(enum.IntEnum): + DATA_RATE_6M81 = 0x0 + DATA_RATE_7M80 = 0x1 + DATA_RATE_27M2 = 0x2 + DATA_RATE_31M2 = 0x3 + + @staticmethod + def from_int(v: int) -> Union[int, 'PsduDataRate']: + try: + return PsduDataRate(v) + except ValueError as exn: + raise exn + + +class PreambleDuration(enum.IntEnum): + DURATION_32_SYMBOLS = 0x0 + DURATION_64_SYMBOLS = 0x1 + + @staticmethod + def from_int(v: int) -> Union[int, 'PreambleDuration']: + try: + return PreambleDuration(v) + except ValueError as exn: + raise exn + + +class LinkLayerMode(enum.IntEnum): + BYPASS_MODE = 0x0 + + @staticmethod + def from_int(v: int) -> Union[int, 'LinkLayerMode']: + try: + return LinkLayerMode(v) + except ValueError as exn: + raise exn + + +class RangingTimeStruct(enum.IntEnum): + BLOCK_BASED_SCHEDULING = 0x1 + + @staticmethod + def from_int(v: int) -> Union[int, 'RangingTimeStruct']: + try: + return RangingTimeStruct(v) + except ValueError as exn: + raise exn + + +class PrfMode(enum.IntEnum): + BPRF_MODE = 0x0 + HPRF_MODE_124M8 = 0x1 + HPRF_MODE_249M6 = 0x2 + + @staticmethod + def from_int(v: int) -> Union[int, 'PrfMode']: + try: + return PrfMode(v) + except ValueError as exn: + raise exn + + +class ScheduleMode(enum.IntEnum): + CONTENTION_BASED = 0x0 + TIME_SCHEDULED = 0x1 + + @staticmethod + def from_int(v: int) -> Union[int, 'ScheduleMode']: + try: + return ScheduleMode(v) + except ValueError as exn: + raise exn + + +class KeyRotation(enum.IntEnum): + DISABLE = 0x0 + ENABLE = 0x1 + + @staticmethod + def from_int(v: int) -> Union[int, 'KeyRotation']: + try: + return KeyRotation(v) + except ValueError as exn: + raise exn + + +class MacAddressMode(enum.IntEnum): + MODE_0 = 0x0 + MODE_1 = 0x1 + MODE_2 = 0x2 + + @staticmethod + def from_int(v: int) -> Union[int, 'MacAddressMode']: + try: + return MacAddressMode(v) + except ValueError as exn: + raise exn + + +class HoppingMode(enum.IntEnum): + DISABLE = 0x0 + ENABLE = 0x1 + + @staticmethod + def from_int(v: int) -> Union[int, 'HoppingMode']: + try: + return HoppingMode(v) + except ValueError as exn: + raise exn + + +@dataclass +class ResultReportConfig(Packet): + tof: int = field(kw_only=True, default=0) + aoa_azimuth: int = field(kw_only=True, default=0) + aoa_elevation: int = field(kw_only=True, default=0) + aoa_fom: int = field(kw_only=True, default=0) + + def __post_init__(self): + pass + + @staticmethod + def parse(span: bytes) -> Tuple['ResultReportConfig', bytes]: + fields = {'payload': None} + if len(span) < 1: + raise Exception('Invalid packet size') + fields['tof'] = (span[0] >> 0) & 0x1 + fields['aoa_azimuth'] = (span[0] >> 1) & 0x1 + fields['aoa_elevation'] = (span[0] >> 2) & 0x1 + fields['aoa_fom'] = (span[0] >> 3) & 0x1 + span = span[1:] + return ResultReportConfig(**fields), span + + def serialize(self, payload: bytes = None) -> bytes: + _span = bytearray() + if self.tof > 1: + print(f"Invalid value for field ResultReportConfig::tof: {self.tof} > 1; the value will be truncated") + self.tof &= 1 + if self.aoa_azimuth > 1: + print(f"Invalid value for field ResultReportConfig::aoa_azimuth: {self.aoa_azimuth} > 1; the value will be truncated") + self.aoa_azimuth &= 1 + if self.aoa_elevation > 1: + print(f"Invalid value for field ResultReportConfig::aoa_elevation: {self.aoa_elevation} > 1; the value will be truncated") + self.aoa_elevation &= 1 + if self.aoa_fom > 1: + print(f"Invalid value for field ResultReportConfig::aoa_fom: {self.aoa_fom} > 1; the value will be truncated") + self.aoa_fom &= 1 + _value = ( + (self.tof << 0) | + (self.aoa_azimuth << 1) | + (self.aoa_elevation << 2) | + (self.aoa_fom << 3) + ) + _span.append(_value) + return bytes(_span) + + @property + def size(self) -> int: + return 1 + +class BprfPhrDataRate(enum.IntEnum): + DATA_RATE_850K = 0x0 + DATA_RATE_6M81 = 0x1 + + @staticmethod + def from_int(v: int) -> Union[int, 'BprfPhrDataRate']: + try: + return BprfPhrDataRate(v) + except ValueError as exn: + raise exn + + +class StsLength(enum.IntEnum): + LENGTH_32_SYMBOLS = 0x0 + LENGTH_64_SYMBOLS = 0x1 + LENGTH_128_SYMBOLS = 0x2 + + @staticmethod + def from_int(v: int) -> Union[int, 'StsLength']: + try: + return StsLength(v) + except ValueError as exn: + raise exn + + +class DlTdoaRangingMethod(enum.IntEnum): + SS_TWR = 0x0 + DS_TWR = 0x1 + + @staticmethod + def from_int(v: int) -> Union[int, 'DlTdoaRangingMethod']: + try: + return DlTdoaRangingMethod(v) + except ValueError as exn: + raise exn + + +class DlTdoaAnchorCfo(enum.IntEnum): + ANCHOR_CFO_NOT_INCLUDED = 0x0 + ANCHOR_CFO_INCLUDED = 0x1 + + @staticmethod + def from_int(v: int) -> Union[int, 'DlTdoaAnchorCfo']: + try: + return DlTdoaAnchorCfo(v) + except ValueError as exn: + raise exn + + +class SessionDataTransferStatusNtfConfig(enum.IntEnum): + DISABLE = 0x0 + ENABLE = 0x1 + + @staticmethod + def from_int(v: int) -> Union[int, 'SessionDataTransferStatusNtfConfig']: + try: + return SessionDataTransferStatusNtfConfig(v) except ValueError as exn: raise exn @@ -2875,7 +3286,7 @@ class SessionSetAppConfigRsp(SessionConfigResponse): @dataclass class SessionGetAppConfigCmd(SessionConfigCommand): session_token: int = field(kw_only=True, default=0) - app_cfg: bytearray = field(kw_only=True, default_factory=bytearray) + app_cfg: List[AppConfigTlvType] = field(kw_only=True, default_factory=list) def __post_init__(self): self.opcode = 4 @@ -2894,7 +3305,10 @@ class SessionGetAppConfigCmd(SessionConfigCommand): span = span[5:] if len(span) < app_cfg_count: raise Exception('Invalid packet size') - fields['app_cfg'] = list(span[:app_cfg_count]) + app_cfg = [] + for n in range(app_cfg_count): + app_cfg.append(AppConfigTlvType(int.from_bytes(span[n:n + 1], byteorder='little'))) + fields['app_cfg'] = app_cfg span = span[app_cfg_count:] return SessionGetAppConfigCmd(**fields), span @@ -2908,12 +3322,13 @@ class SessionGetAppConfigCmd(SessionConfigCommand): print(f"Invalid length for field SessionGetAppConfigCmd::app_cfg: {len(self.app_cfg)} > 255; the array will be truncated") del self.app_cfg[255:] _span.append((len(self.app_cfg) << 0)) - _span.extend(self.app_cfg) + for _elt in self.app_cfg: + _span.append(_elt) return SessionConfigCommand.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.app_cfg) * 1 + 5 + return len(self.app_cfg) * 8 + 5 @dataclass class SessionGetAppConfigRsp(SessionConfigResponse): @@ -3279,7 +3694,7 @@ class UpdateMulticastListAction(enum.IntEnum): ADD_CONTROLEE = 0x0 REMOVE_CONTROLEE = 0x1 ADD_CONTROLEE_WITH_SHORT_SUB_SESSION_KEY = 0x2 - ADD_CONTROLEE_WITH_LONG_SUB_SESSION_KEY = 0x3 + ADD_CONTROLEE_WITH_EXTENDED_SUB_SESSION_KEY = 0x3 @staticmethod def from_int(v: int) -> Union[int, 'UpdateMulticastListAction']: @@ -4908,6 +5323,19 @@ class AndroidSetCountryCodeRsp(AndroidResponse): def size(self) -> int: return 1 +class FrameReportTlvType(enum.IntEnum): + RSSI = 0x0 + AOA = 0x1 + CIR = 0x2 + + @staticmethod + def from_int(v: int) -> Union[int, 'FrameReportTlvType']: + try: + return FrameReportTlvType(v) + except ValueError as exn: + raise exn + + @dataclass class FrameReportTlv(Packet): t: FrameReportTlvType = field(kw_only=True, default=FrameReportTlvType.RSSI) diff --git a/src/app_config.rs b/src/app_config.rs new file mode 100644 index 0000000..82cb738 --- /dev/null +++ b/src/app_config.rs @@ -0,0 +1,497 @@ +use crate::packets::uci; +use crate::MacAddress; + +/// [UCI] 8.3 Application Configuration Parameters. +/// The configuration is initially filled with default values from the +/// specification. +/// See [UCI] Table 45: APP Configuration Parameters IDs +/// for the format of each parameter and the default value. +/// Mandatory APP configuration parameters are declared as optional, +/// and must be set before moving the session from SESSION_STATE_INIT to +/// SESSION_STATE_IDLE. +#[derive(Clone, PartialEq, Eq)] +pub struct AppConfig { + pub device_type: Option, + pub ranging_round_usage: Option, + pub sts_config: uci::StsConfig, + pub multi_node_mode: Option, + channel_number: uci::ChannelNumber, + /// Number of Controlees(N) 1<=N<=8 (Default is 1) + pub number_of_controlees: u8, + /// MAC Address of the UWBS itself participating in UWB session. + /// The short address (2 bytes) or extended MAC address (8 bytes) + /// shall be indicated via MAC_ADDRESS_MODE config. + pub device_mac_address: Option, + /// MAC Address list(N) for NUMBER_OF_CONTROLEES + /// devices participating in UWB Session. + /// + /// The size of this list shall be: + /// - equal to 1 when MULTI_NODE_MODE is set 0x00 (O2O). + /// - ranging from 1 to 8 when MULTI_NODE_MODE is set to 0x01 (O2M). + pub dst_mac_address: Vec, + slot_duration: u16, + pub ranging_duration: u32, + sts_index: u32, + mac_fcs_type: uci::MacFcsType, + ranging_round_control: u8, + aoa_result_req: uci::AoaResultReq, + pub session_info_ntf_config: uci::SessionInfoNtfConfig, + near_proximity_config: u16, + far_proximity_config: u16, + pub device_role: Option, + rframe_config: uci::RframeConfig, + rssi_reporting: uci::RssiReporting, + preamble_code_index: u8, + sfd_id: u8, + psdu_data_rate: uci::PsduDataRate, + preamble_duration: uci::PreambleDuration, + link_layer_mode: uci::LinkLayerMode, + data_repetition_count: u8, + ranging_time_struct: uci::RangingTimeStruct, + slots_per_rr: u8, + aoa_bound_config: [u16; 4], + prf_mode: uci::PrfMode, + cap_size_range: [u8; 2], + tx_jitter_window_size: u8, + pub schedule_mode: Option, + key_rotation: uci::KeyRotation, + key_rotation_rate: u8, + session_priority: u8, + pub mac_address_mode: uci::MacAddressMode, + vendor_id: u16, + static_sts_iv: [u8; 6], + number_of_sts_segments: u8, + max_rr_retry: u16, + uwb_initiation_time: u64, + hopping_mode: uci::HoppingMode, + block_stride_length: u8, + result_report_config: u8, + pub in_band_termination_attempt_count: u8, + sub_session_id: u32, + bprf_phr_data_rate: uci::BprfPhrDataRate, + max_number_of_measurements: u16, + sts_length: uci::StsLength, + min_frames_per_rr: u8, + mtu_size: u16, + inter_frame_interval: u8, + session_key: Vec, + sub_session_key: Vec, + pub session_data_transfer_status_ntf_config: uci::SessionDataTransferStatusNtfConfig, + session_time_base: [u8; 9], + application_data_endpoint: u8, +} + +impl Default for AppConfig { + fn default() -> Self { + AppConfig { + device_type: None, + ranging_round_usage: None, + sts_config: uci::StsConfig::Static, + multi_node_mode: None, + channel_number: uci::ChannelNumber::ChannelNumber9, + number_of_controlees: 1, + device_mac_address: None, + dst_mac_address: vec![], + slot_duration: 2400, + ranging_duration: 200, + sts_index: 0, + mac_fcs_type: uci::MacFcsType::Crc16, + // The default is 0x03 when Time Scheduled Ranging is used, + // 0x06 when Contention-based Ranging is used. + ranging_round_control: 0x06, + aoa_result_req: uci::AoaResultReq::AoaEnabled, + session_info_ntf_config: uci::SessionInfoNtfConfig::Enable, + near_proximity_config: 0, + far_proximity_config: 20000, + device_role: None, + rframe_config: uci::RframeConfig::Sp3, + rssi_reporting: uci::RssiReporting::Disable, + preamble_code_index: 10, + sfd_id: 2, + psdu_data_rate: uci::PsduDataRate::DataRate6m81, + preamble_duration: uci::PreambleDuration::Duration64Symbols, + link_layer_mode: uci::LinkLayerMode::BypassMode, + data_repetition_count: 0, + ranging_time_struct: uci::RangingTimeStruct::BlockBasedScheduling, + slots_per_rr: 25, + aoa_bound_config: [0; 4], + prf_mode: uci::PrfMode::BprfMode, + // Default for Octet[0] is SLOTS_PER_RR - 1 + cap_size_range: [24, 5], + tx_jitter_window_size: 0, + schedule_mode: None, + key_rotation: uci::KeyRotation::Disable, + key_rotation_rate: 0, + session_priority: 50, + mac_address_mode: uci::MacAddressMode::Mode0, + vendor_id: 0, + static_sts_iv: [0; 6], + number_of_sts_segments: 1, + max_rr_retry: 0, + uwb_initiation_time: 0, + hopping_mode: uci::HoppingMode::Disable, + block_stride_length: 0, + result_report_config: 0x01, + in_band_termination_attempt_count: 1, + sub_session_id: 0, // XX + bprf_phr_data_rate: uci::BprfPhrDataRate::DataRate850k, + max_number_of_measurements: 0, + sts_length: uci::StsLength::Length64Symbols, + min_frames_per_rr: 4, + mtu_size: 0, // XX + inter_frame_interval: 1, + session_key: vec![], + sub_session_key: vec![], + session_data_transfer_status_ntf_config: + uci::SessionDataTransferStatusNtfConfig::Disable, + session_time_base: [0; 9], + application_data_endpoint: 0, + } + } +} + +impl AppConfig { + /// Set the APP configuration value with the selected identifier + /// and value. Returns `Ok` if the identifier is known and the value + /// well formatted, `Err` otherwise. + pub fn set(&mut self, id: uci::AppConfigTlvType, value: &[u8]) -> anyhow::Result<()> { + fn try_parse>(value: &[u8]) -> anyhow::Result { + T::try_from(u8::from_le_bytes(value.try_into()?)).map_err(anyhow::Error::msg) + } + + fn try_parse_u8(value: &[u8]) -> anyhow::Result { + Ok(u8::from_le_bytes(value.try_into()?)) + } + + fn try_parse_u16(value: &[u8]) -> anyhow::Result { + Ok(u16::from_le_bytes(value.try_into()?)) + } + + fn try_parse_u32(value: &[u8]) -> anyhow::Result { + Ok(u32::from_le_bytes(value.try_into()?)) + } + + fn try_parse_u64(value: &[u8]) -> anyhow::Result { + Ok(u64::from_le_bytes(value.try_into()?)) + } + + match id { + uci::AppConfigTlvType::DeviceType => self.device_type = Some(try_parse(value)?), + uci::AppConfigTlvType::RangingRoundUsage => { + self.ranging_round_usage = Some(try_parse(value)?) + } + uci::AppConfigTlvType::StsConfig => self.sts_config = try_parse(value)?, + uci::AppConfigTlvType::MultiNodeMode => self.multi_node_mode = Some(try_parse(value)?), + uci::AppConfigTlvType::ChannelNumber => self.channel_number = try_parse(value)?, + uci::AppConfigTlvType::NumberOfControlees => { + self.number_of_controlees = try_parse_u8(value)? + } + uci::AppConfigTlvType::DeviceMacAddress => { + self.device_mac_address = Some(match self.mac_address_mode { + uci::MacAddressMode::Mode0 => MacAddress::Short(value.try_into()?), + uci::MacAddressMode::Mode1 => unimplemented!(), + uci::MacAddressMode::Mode2 => MacAddress::Extended(value.try_into()?), + }) + } + uci::AppConfigTlvType::DstMacAddress => { + let mac_address_size = match self.mac_address_mode { + uci::MacAddressMode::Mode0 => 2, + uci::MacAddressMode::Mode1 => unimplemented!(), + uci::MacAddressMode::Mode2 => 8, + }; + if value.len() != self.number_of_controlees as usize * mac_address_size { + log::error!( + "invalid dst_mac_address len: expected {}x{}, got {}", + self.number_of_controlees, + mac_address_size, + value.len() + ); + anyhow::bail!("invalid dst_mac_address len") + } + self.dst_mac_address = value + .chunks(mac_address_size) + .map(|value| match self.mac_address_mode { + uci::MacAddressMode::Mode0 => MacAddress::Short(value.try_into().unwrap()), + uci::MacAddressMode::Mode1 => unimplemented!(), + uci::MacAddressMode::Mode2 => { + MacAddress::Extended(value.try_into().unwrap()) + } + }) + .collect(); + } + uci::AppConfigTlvType::SlotDuration => self.slot_duration = try_parse_u16(value)?, + uci::AppConfigTlvType::RangingDuration => self.ranging_duration = try_parse_u32(value)?, + uci::AppConfigTlvType::StsIndex => self.sts_index = try_parse_u32(value)?, + uci::AppConfigTlvType::MacFcsType => self.mac_fcs_type = try_parse(value)?, + uci::AppConfigTlvType::RangingRoundControl => { + self.ranging_round_control = try_parse_u8(value)? + } + uci::AppConfigTlvType::AoaResultReq => self.aoa_result_req = try_parse(value)?, + uci::AppConfigTlvType::SessionInfoNtfConfig => { + self.session_info_ntf_config = try_parse(value)? + } + uci::AppConfigTlvType::NearProximityConfig => { + self.near_proximity_config = try_parse_u16(value)? + } + uci::AppConfigTlvType::FarProximityConfig => { + self.far_proximity_config = try_parse_u16(value)? + } + uci::AppConfigTlvType::DeviceRole => self.device_role = Some(try_parse(value)?), + uci::AppConfigTlvType::RframeConfig => self.rframe_config = try_parse(value)?, + uci::AppConfigTlvType::RssiReporting => self.rssi_reporting = try_parse(value)?, + uci::AppConfigTlvType::PreambleCodeIndex => { + self.preamble_code_index = try_parse_u8(value)? + } + uci::AppConfigTlvType::SfdId => self.sfd_id = try_parse_u8(value)?, + uci::AppConfigTlvType::PsduDataRate => self.psdu_data_rate = try_parse(value)?, + uci::AppConfigTlvType::PreambleDuration => self.preamble_duration = try_parse(value)?, + uci::AppConfigTlvType::LinkLayerMode => self.link_layer_mode = try_parse(value)?, + uci::AppConfigTlvType::DataRepetitionCount => { + self.data_repetition_count = try_parse_u8(value)? + } + uci::AppConfigTlvType::RangingTimeStruct => { + self.ranging_time_struct = try_parse(value)? + } + uci::AppConfigTlvType::SlotsPerRr => self.slots_per_rr = try_parse_u8(value)?, + uci::AppConfigTlvType::AoaBoundConfig => { + if value.len() != 8 { + log::error!( + "invalid aoa_bound_config len: expected 8, got {}", + value.len() + ); + anyhow::bail!("invalid aoa_bound_config len") + } + self.aoa_bound_config = [ + u16::from_le_bytes([value[0], value[1]]), + u16::from_le_bytes([value[2], value[3]]), + u16::from_le_bytes([value[4], value[5]]), + u16::from_le_bytes([value[6], value[7]]), + ] + } + uci::AppConfigTlvType::PrfMode => self.prf_mode = try_parse(value)?, + uci::AppConfigTlvType::CapSizeRange => self.cap_size_range = value.try_into()?, + uci::AppConfigTlvType::TxJitterWindowSize => { + self.tx_jitter_window_size = try_parse_u8(value)? + } + uci::AppConfigTlvType::ScheduleMode => self.schedule_mode = Some(try_parse(value)?), + uci::AppConfigTlvType::KeyRotation => self.key_rotation = try_parse(value)?, + uci::AppConfigTlvType::KeyRotationRate => self.key_rotation_rate = try_parse_u8(value)?, + uci::AppConfigTlvType::SessionPriority => self.session_priority = try_parse_u8(value)?, + uci::AppConfigTlvType::MacAddressMode => self.mac_address_mode = try_parse(value)?, + uci::AppConfigTlvType::VendorId => self.vendor_id = try_parse_u16(value)?, + uci::AppConfigTlvType::StaticStsIv => self.static_sts_iv = value.try_into()?, + uci::AppConfigTlvType::NumberOfStsSegments => { + self.number_of_sts_segments = try_parse_u8(value)? + } + uci::AppConfigTlvType::MaxRrRetry => self.max_rr_retry = try_parse_u16(value)?, + uci::AppConfigTlvType::UwbInitiationTime => { + // Implement backward compatiblity for UCI 1.0 + // where the value is 4 bytes instead of 8. + self.uwb_initiation_time = match value.len() { + 4 => try_parse_u32(value)? as u64, + _ => try_parse_u64(value)?, + } + } + uci::AppConfigTlvType::HoppingMode => self.hopping_mode = try_parse(value)?, + uci::AppConfigTlvType::BlockStrideLength => { + self.block_stride_length = try_parse_u8(value)? + } + uci::AppConfigTlvType::ResultReportConfig => { + self.result_report_config = try_parse_u8(value)? + } + uci::AppConfigTlvType::InBandTerminationAttemptCount => { + self.in_band_termination_attempt_count = try_parse_u8(value)? + } + uci::AppConfigTlvType::SubSessionId => self.sub_session_id = try_parse_u32(value)?, + uci::AppConfigTlvType::BprfPhrDataRate => self.bprf_phr_data_rate = try_parse(value)?, + uci::AppConfigTlvType::MaxNumberOfMeasurements => { + self.max_number_of_measurements = try_parse_u16(value)? + } + uci::AppConfigTlvType::StsLength => self.sts_length = try_parse(value)?, + uci::AppConfigTlvType::MinFramesPerRr => self.min_frames_per_rr = try_parse_u8(value)?, + uci::AppConfigTlvType::MtuSize => self.mtu_size = try_parse_u16(value)?, + uci::AppConfigTlvType::InterFrameInterval => { + self.inter_frame_interval = try_parse_u8(value)? + } + uci::AppConfigTlvType::SessionKey => self.session_key = value.to_vec(), + uci::AppConfigTlvType::SubSessionKey => self.sub_session_key = value.to_vec(), + uci::AppConfigTlvType::SessionDataTransferStatusNtfConfig => { + self.session_data_transfer_status_ntf_config = try_parse(value)? + } + uci::AppConfigTlvType::SessionTimeBase => self.session_time_base = value.try_into()?, + uci::AppConfigTlvType::ApplicationDataEndpoint => { + self.application_data_endpoint = try_parse_u8(value)? + } + + uci::AppConfigTlvType::CccHopModeKey + | uci::AppConfigTlvType::CccUwbTime0 + | uci::AppConfigTlvType::CccRangingProtocolVer + | uci::AppConfigTlvType::CccUwbConfigId + | uci::AppConfigTlvType::CccPulseshapeCombo + | uci::AppConfigTlvType::CccUrskTtl + | uci::AppConfigTlvType::CccLastIndexUsed + | uci::AppConfigTlvType::NbOfRangeMeasurements + | uci::AppConfigTlvType::NbOfAzimuthMeasurements + | uci::AppConfigTlvType::NbOfElevationMeasurements + | uci::AppConfigTlvType::EnableDiagnostics + | uci::AppConfigTlvType::DiagramsFrameReportsFields => { + log::error!("unsupported vendor config type {:?}", id); + anyhow::bail!("unsupported vendor config type {:?}", id) + } + _ => { + log::error!("unsupported app config type {:?}", id); + anyhow::bail!("unsupported app config type {:?}", id) + } + } + Ok(()) + } + + /// Retrieve the APP configuration value with the selected identifier + /// Returns `Ok` if the identifier is known, `Err` otherwise. + pub fn get(&self, id: uci::AppConfigTlvType) -> anyhow::Result> { + match id { + uci::AppConfigTlvType::DeviceType => Ok(vec![self + .device_type + .ok_or(anyhow::anyhow!("optional app config not set"))? + .into()]), + uci::AppConfigTlvType::RangingRoundUsage => Ok(vec![self + .ranging_round_usage + .ok_or(anyhow::anyhow!("optional app config not set"))? + .into()]), + uci::AppConfigTlvType::StsConfig => Ok(vec![self.sts_config.into()]), + uci::AppConfigTlvType::MultiNodeMode => Ok(vec![self + .multi_node_mode + .ok_or(anyhow::anyhow!("optional app config not set"))? + .into()]), + uci::AppConfigTlvType::ChannelNumber => Ok(vec![self.channel_number.into()]), + uci::AppConfigTlvType::NumberOfControlees => Ok(vec![self.number_of_controlees]), + uci::AppConfigTlvType::DeviceMacAddress => Ok(self + .device_mac_address + .ok_or(anyhow::anyhow!("optional app config not set"))? + .into()), + uci::AppConfigTlvType::DstMacAddress => Ok(self + .dst_mac_address + .iter() + .flat_map(Vec::::from) + .collect()), + uci::AppConfigTlvType::SlotDuration => Ok(self.slot_duration.to_le_bytes().to_vec()), + uci::AppConfigTlvType::RangingDuration => { + Ok(self.ranging_duration.to_le_bytes().to_vec()) + } + uci::AppConfigTlvType::StsIndex => Ok(self.sts_index.to_le_bytes().to_vec()), + uci::AppConfigTlvType::MacFcsType => Ok(vec![self.mac_fcs_type.into()]), + uci::AppConfigTlvType::RangingRoundControl => Ok(vec![self.ranging_round_control]), + uci::AppConfigTlvType::AoaResultReq => Ok(vec![self.aoa_result_req.into()]), + uci::AppConfigTlvType::SessionInfoNtfConfig => { + Ok(vec![self.session_info_ntf_config.into()]) + } + uci::AppConfigTlvType::NearProximityConfig => { + Ok(self.near_proximity_config.to_le_bytes().to_vec()) + } + uci::AppConfigTlvType::FarProximityConfig => { + Ok(self.far_proximity_config.to_le_bytes().to_vec()) + } + uci::AppConfigTlvType::DeviceRole => Ok(vec![self + .device_role + .ok_or(anyhow::anyhow!("optional app config not set"))? + .into()]), + uci::AppConfigTlvType::RframeConfig => Ok(vec![self.rframe_config.into()]), + uci::AppConfigTlvType::RssiReporting => Ok(vec![self.rssi_reporting.into()]), + uci::AppConfigTlvType::PreambleCodeIndex => Ok(vec![self.preamble_code_index]), + uci::AppConfigTlvType::SfdId => Ok(vec![self.sfd_id]), + uci::AppConfigTlvType::PsduDataRate => Ok(vec![self.psdu_data_rate.into()]), + uci::AppConfigTlvType::PreambleDuration => Ok(vec![self.preamble_duration.into()]), + uci::AppConfigTlvType::LinkLayerMode => Ok(vec![self.link_layer_mode.into()]), + uci::AppConfigTlvType::DataRepetitionCount => Ok(vec![self.data_repetition_count]), + uci::AppConfigTlvType::RangingTimeStruct => Ok(vec![self.ranging_time_struct.into()]), + uci::AppConfigTlvType::SlotsPerRr => Ok(vec![self.slots_per_rr]), + uci::AppConfigTlvType::AoaBoundConfig => Ok(self + .aoa_bound_config + .iter() + .copied() + .flat_map(u16::to_le_bytes) + .collect()), + uci::AppConfigTlvType::PrfMode => Ok(vec![self.prf_mode.into()]), + uci::AppConfigTlvType::CapSizeRange => Ok(self.cap_size_range.to_vec()), + uci::AppConfigTlvType::TxJitterWindowSize => Ok(vec![self.tx_jitter_window_size]), + uci::AppConfigTlvType::ScheduleMode => Ok(vec![self + .schedule_mode + .ok_or(anyhow::anyhow!("optional app config not set"))? + .into()]), + uci::AppConfigTlvType::KeyRotation => Ok(vec![self.key_rotation.into()]), + uci::AppConfigTlvType::KeyRotationRate => Ok(vec![self.key_rotation_rate]), + uci::AppConfigTlvType::SessionPriority => Ok(vec![self.session_priority]), + uci::AppConfigTlvType::MacAddressMode => Ok(vec![self.mac_address_mode.into()]), + uci::AppConfigTlvType::VendorId => Ok(self.vendor_id.to_le_bytes().to_vec()), + uci::AppConfigTlvType::StaticStsIv => Ok(self.static_sts_iv.to_vec()), + uci::AppConfigTlvType::NumberOfStsSegments => Ok(vec![self.number_of_sts_segments]), + uci::AppConfigTlvType::MaxRrRetry => Ok(self.max_rr_retry.to_le_bytes().to_vec()), + uci::AppConfigTlvType::UwbInitiationTime => { + Ok(self.uwb_initiation_time.to_le_bytes().to_vec()) + } + uci::AppConfigTlvType::HoppingMode => Ok(vec![self.hopping_mode.into()]), + uci::AppConfigTlvType::BlockStrideLength => Ok(vec![self.block_stride_length]), + uci::AppConfigTlvType::ResultReportConfig => Ok(vec![self.result_report_config]), + uci::AppConfigTlvType::InBandTerminationAttemptCount => { + Ok(vec![self.in_band_termination_attempt_count]) + } + uci::AppConfigTlvType::SubSessionId => Ok(self.sub_session_id.to_le_bytes().to_vec()), + uci::AppConfigTlvType::BprfPhrDataRate => Ok(vec![self.bprf_phr_data_rate.into()]), + uci::AppConfigTlvType::MaxNumberOfMeasurements => { + Ok(self.max_number_of_measurements.to_le_bytes().to_vec()) + } + uci::AppConfigTlvType::StsLength => Ok(vec![self.sts_length.into()]), + uci::AppConfigTlvType::MinFramesPerRr => Ok(vec![self.min_frames_per_rr]), + uci::AppConfigTlvType::MtuSize => Ok(self.mtu_size.to_le_bytes().to_vec()), + uci::AppConfigTlvType::InterFrameInterval => Ok(vec![self.inter_frame_interval]), + uci::AppConfigTlvType::SessionKey => Ok(self.session_key.clone()), + uci::AppConfigTlvType::SubSessionKey => Ok(self.sub_session_key.clone()), + uci::AppConfigTlvType::SessionDataTransferStatusNtfConfig => { + Ok(vec![self.session_data_transfer_status_ntf_config.into()]) + } + uci::AppConfigTlvType::SessionTimeBase => Ok(self.session_time_base.to_vec()), + uci::AppConfigTlvType::ApplicationDataEndpoint => { + Ok(vec![self.application_data_endpoint]) + } + + uci::AppConfigTlvType::CccHopModeKey + | uci::AppConfigTlvType::CccUwbTime0 + | uci::AppConfigTlvType::CccRangingProtocolVer + | uci::AppConfigTlvType::CccUwbConfigId + | uci::AppConfigTlvType::CccPulseshapeCombo + | uci::AppConfigTlvType::CccUrskTtl + | uci::AppConfigTlvType::CccLastIndexUsed + | uci::AppConfigTlvType::NbOfRangeMeasurements + | uci::AppConfigTlvType::NbOfAzimuthMeasurements + | uci::AppConfigTlvType::NbOfElevationMeasurements + | uci::AppConfigTlvType::EnableDiagnostics + | uci::AppConfigTlvType::DiagramsFrameReportsFields => { + log::error!("unsupported vendor config type {:?}", id); + anyhow::bail!("unsupported vendor config type {:?}", id) + } + _ => { + log::error!("unsupported app config type {:?}", id); + anyhow::bail!("unsupported app config type {:?}", id) + } + } + } + + pub fn is_compatible_for_ranging(&self, peer_config: &Self) -> bool { + self.device_role != peer_config.device_role + && self.device_type != peer_config.device_type + && peer_config + .dst_mac_address + .contains(&self.device_mac_address.unwrap()) + && self + .dst_mac_address + .contains(&peer_config.device_mac_address.unwrap()) + } + + pub fn can_start_data_transfer(&self) -> bool { + self.device_role == Some(uci::DeviceRole::Initiator) + } + + pub fn can_receive_data_transfer(&self) -> bool { + self.device_role == Some(uci::DeviceRole::Responder) + } +} diff --git a/src/lib.rs b/src/lib.rs index bd154f7..25a09d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,7 +36,8 @@ use session::MAX_SESSION; mod mac_address; pub use mac_address::MacAddress; -use crate::session::RangeDataNtfConfig; +mod app_config; +pub use app_config::AppConfig; pub type UciPacket = Vec; pub type UciStream = Pin> + Send>>; @@ -412,7 +413,7 @@ impl Pica { let mut measurements = Vec::new(); // Look for compatible anchors. - for mac_address in session.get_dst_mac_addresses() { + for mac_address in session.get_dst_mac_address() { if let Some(other) = self.anchors.get(mac_address) { let local = self .ranging_estimator @@ -437,7 +438,8 @@ impl Pica { .session(session_id) .unwrap() .app_config - .device_mac_address; + .device_mac_address + .unwrap(); let local = self .ranging_estimator .estimate(&device.handle, &peer_device.handle) @@ -475,7 +477,7 @@ impl Pica { ) .unwrap(); } - if session.is_ranging_data_ntf_enabled() != RangeDataNtfConfig::Disable { + if session.is_session_info_ntf_enabled() { device .tx .send( @@ -554,7 +556,7 @@ impl Pica { continue; }; - if &session.app_config.device_mac_address != mac_address { + if session.app_config.device_mac_address != Some(*mac_address) { continue; } diff --git a/src/mac_address.rs b/src/mac_address.rs index 79cfe94..6b67e7e 100644 --- a/src/mac_address.rs +++ b/src/mac_address.rs @@ -52,6 +52,21 @@ impl From for u64 { } } +impl From<&MacAddress> for Vec { + fn from(mac_address: &MacAddress) -> Self { + match mac_address { + MacAddress::Short(addr) => addr.to_vec(), + MacAddress::Extended(addr) => addr.to_vec(), + } + } +} + +impl From for Vec { + fn from(mac_address: MacAddress) -> Self { + Vec::::from(&mac_address) + } +} + impl TryFrom for MacAddress { type Error = Error; fn try_from(mac_address: String) -> std::result::Result { diff --git a/src/session.rs b/src/session.rs index bbd23bb..dc0ed30 100644 --- a/src/session.rs +++ b/src/session.rs @@ -17,674 +17,25 @@ //! - [UCI] FiRa Consortium UWB Command Interface Generic Technical specification use crate::packets::uci::{self, *}; -use crate::{MacAddress, PicaCommand}; +use crate::{AppConfig, MacAddress, PicaCommand}; use bytes::BytesMut; -use std::collections::HashMap; use std::time::Duration; use tokio::sync::mpsc; use tokio::task::JoinHandle; use tokio::time; -use num_derive::{FromPrimitive, ToPrimitive}; -use num_traits::FromPrimitive; - use super::UciPacket; pub const MAX_SESSION: usize = 255; -pub const DEFAULT_RANGING_INTERVAL: Duration = time::Duration::from_millis(200); -pub const DEFAULT_SLOT_DURATION: u16 = 2400; // RTSU unit /// cf. [UCI] 8.3 Table 29 pub const MAX_NUMBER_OF_CONTROLEES: usize = 8; -pub const FIRA_1_1_INITIATION_TIME_SIZE: usize = 4; -pub const FIRA_2_0_INITIATION_TIME_SIZE: usize = 8; - -#[derive(Copy, Clone, FromPrimitive, PartialEq, Eq)] -pub enum DeviceType { - /// [MAC] 5.1.2 Device utilizing the ranging features set through Control Messages - Controlee = 0x00, - /// [MAC] 5.1.1 Device controlling the ranging features through Control Messages - Controller = 0x01, -} - -#[derive(Copy, Clone, FromPrimitive, PartialEq, Eq)] -pub enum DeviceRole { - /// [MAC] 5.1.3 Device initiating a ranging exchange with a ranging initiation message - Initiator, - /// [MAC] 5.1.4 Device responding to ranging initiation messages - Responder, -} - -/// cf. [UCI] 8.4 Table 29 -#[derive(Copy, Clone, FromPrimitive, ToPrimitive, PartialEq, Eq)] -#[repr(u8)] -pub enum MacAddressMode { - /// MAC address is 2 bytes and 2 bytes to be used in MAC header - AddressMode0 = 0x00, - /// Not Supported: MAC address is 8 bytes and 2 bytes to be used in MAC header - AddressMode1 = 0x01, - /// MAC address is 8 bytes and 8 bytes to be used in MAC header - AddressMode2 = 0x02, -} - -/// cf. [UCI] 8.3 Table 29 -#[derive(Copy, Clone, FromPrimitive, ToPrimitive, PartialEq, Eq)] -#[repr(u8)] -pub enum ChannelNumber { - ChannelNumber5 = 0x05, - ChannelNumber6 = 0x06, - ChannelNumber8 = 0x08, - ChannelNumber9 = 0x09, - ChannelNumber10 = 0x0a, - ChannelNumber12 = 0x0c, - ChannelNumber13 = 0x0d, - ChannelNumber14 = 0x0e, -} - -const DEFAULT_CHANNEL_NUMBER: ChannelNumber = ChannelNumber::ChannelNumber9; - -/// cf. [UCI] 8.3 Table 29 -#[derive(Copy, Clone, FromPrimitive, ToPrimitive, PartialEq)] -#[repr(u8)] -enum MultiNodeMode { - /// Single device to single device - Unicast = 0x00, - OneToMany = 0x01, - ManyToMany = 0x02, -} - -/// cf. [UCI] 7.7 -#[derive(Copy, Clone, FromPrimitive, ToPrimitive, PartialEq)] -#[repr(u8)] -enum UpdateMulticastListAction { - Add = 0x00, - Delete = 0x01, - AddWithShortSubSessionKey = 0x02, - AddwithExtendedSubSessionKey = 0x03, -} - -#[derive(Copy, Clone, FromPrimitive, ToPrimitive, PartialEq)] -#[repr(u8)] -enum RangingRoundUsage { - UlTdoa = 0x00, - SsTwrDeferredMode = 0x01, - DsTwrDeferredMode = 0x02, - SsTwrNonDeferredMode = 0x03, - DsTwrNonDeferredMode = 0x04, - DlTdoa = 0x05, - OwrAoaMeasurement = 0x06, - DataTransferMode = 0x09, -} - -#[derive(Copy, Clone, FromPrimitive, ToPrimitive, PartialEq)] -#[repr(u8)] -enum StsConfig { - Static = 0x00, - Dynamic = 0x01, - DynamicForControleeIndividualKey = 0x02, - Provisioned = 0x03, - ProvisionedForControleeIndividualKey = 0x04, -} - -#[derive(Copy, Clone, FromPrimitive, ToPrimitive, PartialEq)] -#[repr(u8)] -enum MacFcsType { - MacFcsTypeCrc16 = 0x00, - MacFcsTypeCrc32 = 0x01, -} - -#[derive(Copy, Clone, FromPrimitive, ToPrimitive, PartialEq)] -#[repr(u8)] -enum AoaResultReq { - NoAoaResult = 0x00, - ReqAoaResults = 0x01, - ReqAoaResultsAzimuthOnly = 0x02, - ReqAoaResultsElevationOnly = 0x03, - ReqAoaResultsInterleaved = 0x04, -} - -#[derive(Copy, Clone, FromPrimitive, ToPrimitive, PartialEq)] -#[repr(u8)] -enum RframeConfig { - Sp0 = 0x00, - Sp1 = 0x01, - Sp3 = 0x03, -} - -#[derive(Copy, Clone, FromPrimitive, ToPrimitive, PartialEq)] -#[repr(u8)] -enum PsduDataRate { - Rate6M81 = 0x00, - Rate7M80 = 0x01, - Rate27M2 = 0x02, - Rate31M2 = 0x03, -} - -#[derive(Copy, Clone, FromPrimitive, ToPrimitive, PartialEq)] -#[repr(u8)] -enum PreambleDuration { - PreambleDurationT32Symbols = 0x00, - PreambleDurationT64Symbols = 0x01, -} - -#[derive(Copy, Clone, FromPrimitive, ToPrimitive, PartialEq)] -#[repr(u8)] -enum RangingTimeStruct { - IntervalBasedScheduling = 0x00, - BlockBasedScheduling = 0x01, -} - -#[derive(Copy, Clone, FromPrimitive, ToPrimitive, PartialEq)] -#[repr(u8)] -enum PrfMode { - PrfModeBprf = 0x00, - PrfModeHprf = 0x01, -} - -#[derive(Copy, Clone, FromPrimitive, ToPrimitive, PartialEq)] -#[repr(u8)] -enum SchedulingMode { - ContentionBased = 0x00, - TimeScheduled = 0x01, - HybridScheduled = 0x02, -} - -#[derive(Copy, Clone, FromPrimitive, ToPrimitive, PartialEq)] -#[repr(u8)] -enum HoppingMode { - Disable = 0x00, - FiraEnable = 0x01, -} - -#[derive(Copy, Clone, FromPrimitive, ToPrimitive, PartialEq)] -#[repr(u8)] -enum StsLength { - StsLength32 = 0x00, - StsLength64 = 0x01, - StsLength128 = 0x02, -} - -#[derive(Copy, Clone, FromPrimitive, ToPrimitive, PartialEq)] -#[repr(u8)] -enum BprfPhrDataRate { - BprfPhrDataRate850K = 0x00, - BprfPhrDataRate6M81 = 0x01, -} - -#[derive(Copy, Clone, FromPrimitive, ToPrimitive, PartialEq)] -#[repr(u8)] -enum SfdIdValue { - SfdIdValue0 = 0x00, - SfdIdValue1 = 0x01, - SfdIdValue2 = 0x02, - SfdIdValue3 = 0x03, - SfdIdValue4 = 0x04, -} - -#[derive(Copy, Clone, FromPrimitive, ToPrimitive, PartialEq)] -#[repr(u8)] -enum StsSegmentCountValue { - StsSegmentCountValue0 = 0x00, - StsSegmentCountValue1 = 0x01, - StsSegmentCountValue2 = 0x02, -} - -#[derive(Copy, Clone, FromPrimitive, ToPrimitive, PartialEq)] -#[repr(u8)] -pub enum RangeDataNtfConfig { - Disable = 0x00, - Enable = 0x01, - EnableProximityLevelTrig = 0x02, - EnableAoaLevelTrig = 0x03, - EnableProximityAoaLevelTrig = 0x04, - EnableProximityEdgeTrig = 0x05, - EnableAoaEdgeTrig = 0x06, - EnableProximityAoaEdgeTrig = 0x07, -} - -#[derive(Copy, Clone, FromPrimitive, ToPrimitive, PartialEq)] -#[repr(u8)] -pub enum LinkLayerMode { - Bypass = 0x00, - Assigned = 0x01, -} - -#[derive(Copy, Clone, FromPrimitive, ToPrimitive, PartialEq)] -#[repr(u8)] -pub enum DataRepetitionCount { - NoRepetition = 0x00, - Infinite = 0xFF, -} - -#[derive(Copy, Clone, FromPrimitive, ToPrimitive, PartialEq)] -#[repr(u8)] -pub enum SessionDataTransferStatusNtfConfig { - Disable = 0x00, - Enable = 0x01, -} -/// cf. [UCI] 8.3 Table 29 -#[derive(Clone)] -pub struct AppConfig { - /// Copy of the valid App Configuration parameters provided by host - raw: HashMap>, - - device_type: DeviceType, - device_role: DeviceRole, - mac_address_mode: MacAddressMode, - pub device_mac_address: MacAddress, - number_of_controlees: usize, - dst_mac_addresses: Vec, - ranging_interval: time::Duration, - slot_duration: u16, - channel_number: ChannelNumber, - multi_node_mode: MultiNodeMode, - ranging_round_usage: RangingRoundUsage, - sts_config: StsConfig, - mac_fcs_type: MacFcsType, - ranging_round_control: u8, - aoa_result_req: AoaResultReq, - rng_data_ntf: RangeDataNtfConfig, - rng_data_ntf_proximity_near: u16, - rng_data_ntf_proximity_far: u16, - r_frame_config: RframeConfig, - rssi_reporting: bool, - preamble_code_index: u8, - sfd_id: SfdIdValue, - psdu_data_rate: PsduDataRate, - preamble_duration: PreambleDuration, - ranging_time_struct: RangingTimeStruct, - slots_per_rr: u8, - tx_adaptive_payload_power: bool, - prf_mode: PrfMode, - schedule_mode: SchedulingMode, - key_rotation: bool, - key_rotation_rate: u8, - session_priority: u8, - number_of_sts_segments: StsSegmentCountValue, - max_rr_retry: u16, - hopping_mode: HoppingMode, - block_stride_length: u8, - result_report_config: bool, - in_band_termination_attempt_count: u8, - bprf_phr_data_rate: BprfPhrDataRate, - max_number_of_measurements: u8, - sts_length: StsLength, - uwb_initiation_time: u64, - vendor_id: Option>, - static_sts_iv: Option>, - session_key: Option>, - sub_session_key: Option>, - sub_session_id: u32, - link_layer_mode: LinkLayerMode, - data_repetition_count: DataRepetitionCount, - session_data_transfer_status_ntf_config: SessionDataTransferStatusNtfConfig, - application_data_endpoint: u8, -} - -impl Default for AppConfig { - fn default() -> Self { - AppConfig { - raw: HashMap::new(), - mac_address_mode: MacAddressMode::AddressMode0, - device_role: DeviceRole::Responder, - device_type: DeviceType::Controlee, - ranging_interval: DEFAULT_RANGING_INTERVAL, - slot_duration: DEFAULT_SLOT_DURATION, - channel_number: DEFAULT_CHANNEL_NUMBER, - device_mac_address: MacAddress::Short([0x00, 0x00]), - number_of_controlees: 0, - dst_mac_addresses: Vec::new(), - multi_node_mode: MultiNodeMode::Unicast, - ranging_round_usage: RangingRoundUsage::DsTwrDeferredMode, - sts_config: StsConfig::Static, - mac_fcs_type: MacFcsType::MacFcsTypeCrc16, - ranging_round_control: 6_u8, - aoa_result_req: AoaResultReq::ReqAoaResults, - rng_data_ntf: RangeDataNtfConfig::Enable, - rng_data_ntf_proximity_near: 0, - rng_data_ntf_proximity_far: 0, - r_frame_config: RframeConfig::Sp3, - rssi_reporting: false, - preamble_code_index: 10, - sfd_id: SfdIdValue::SfdIdValue2, - psdu_data_rate: PsduDataRate::Rate6M81, - preamble_duration: PreambleDuration::PreambleDurationT64Symbols, - ranging_time_struct: RangingTimeStruct::IntervalBasedScheduling, - slots_per_rr: 25, - tx_adaptive_payload_power: false, - prf_mode: PrfMode::PrfModeBprf, - schedule_mode: SchedulingMode::TimeScheduled, - key_rotation: false, - key_rotation_rate: 0, - session_priority: 50, - number_of_sts_segments: StsSegmentCountValue::StsSegmentCountValue1, - max_rr_retry: 0, - hopping_mode: HoppingMode::Disable, - block_stride_length: 0, - result_report_config: true, - in_band_termination_attempt_count: 1, - bprf_phr_data_rate: BprfPhrDataRate::BprfPhrDataRate850K, - max_number_of_measurements: 0, - sts_length: StsLength::StsLength64, - uwb_initiation_time: 0, - vendor_id: None, - static_sts_iv: None, - session_key: None, - sub_session_key: None, - sub_session_id: 0, - link_layer_mode: LinkLayerMode::Bypass, - data_repetition_count: DataRepetitionCount::NoRepetition, - session_data_transfer_status_ntf_config: SessionDataTransferStatusNtfConfig::Disable, - application_data_endpoint: 0, - } - } -} - -impl PartialEq for AppConfig { - fn eq(&self, other: &Self) -> bool { - self.mac_address_mode == other.mac_address_mode - && self.ranging_interval == other.ranging_interval - && self.slot_duration == other.slot_duration - && self.channel_number == other.channel_number - && self.multi_node_mode == other.multi_node_mode - && self.ranging_round_usage == other.ranging_round_usage - && self.sts_config == other.sts_config - && self.mac_fcs_type == other.mac_fcs_type - && self.ranging_round_control == other.ranging_round_control - && self.aoa_result_req == other.aoa_result_req - && self.rng_data_ntf == other.rng_data_ntf - && self.rng_data_ntf_proximity_near == other.rng_data_ntf_proximity_near - && self.rng_data_ntf_proximity_far == other.rng_data_ntf_proximity_far - && self.r_frame_config == other.r_frame_config - && self.rssi_reporting == other.rssi_reporting - && self.preamble_code_index == other.preamble_code_index - && self.sfd_id == other.sfd_id - && self.psdu_data_rate == other.psdu_data_rate - && self.preamble_duration == other.preamble_duration - && self.ranging_time_struct == other.ranging_time_struct - && self.slots_per_rr == other.slots_per_rr - && self.tx_adaptive_payload_power == other.tx_adaptive_payload_power - && self.prf_mode == other.prf_mode - && self.schedule_mode == other.schedule_mode - && self.key_rotation == other.key_rotation - && self.key_rotation_rate == other.key_rotation_rate - && self.session_priority == other.session_priority - && self.number_of_sts_segments == other.number_of_sts_segments - && self.max_rr_retry == other.max_rr_retry - && self.result_report_config == other.result_report_config - && self.bprf_phr_data_rate == other.bprf_phr_data_rate - && self.max_number_of_measurements == other.max_number_of_measurements - && self.sts_length == other.sts_length - && self.uwb_initiation_time == other.uwb_initiation_time - && self.vendor_id == other.vendor_id - && self.static_sts_iv == other.static_sts_iv - } -} - -fn app_config_has_mandatory_parameters(configs: &[AppConfigTlv]) -> bool { - const MANDATORY_PARAMETERS: [AppConfigTlvType; 6] = [ - AppConfigTlvType::DeviceRole, - AppConfigTlvType::MultiNodeMode, - AppConfigTlvType::NoOfControlee, - AppConfigTlvType::DeviceMacAddress, - AppConfigTlvType::DstMacAddress, - AppConfigTlvType::DeviceType, - ]; - - MANDATORY_PARAMETERS - .iter() - .all(|&mparam| configs.iter().any(|param| mparam == param.cfg_id)) -} - -impl AppConfig { - fn set_config( - &mut self, - id: AppConfigTlvType, - value: &[u8], - ) -> std::result::Result<(), StatusCode> { - match id { - AppConfigTlvType::MacAddressMode => { - let mode = MacAddressMode::from_u8(value[0]).unwrap(); - if mode == MacAddressMode::AddressMode1 { - return Err(StatusCode::UciStatusInvalidParam); - } - self.mac_address_mode = mode; - } - AppConfigTlvType::RangingDuration => { - let interval = u32::from_le_bytes(value[..].try_into().unwrap()); - self.ranging_interval = time::Duration::from_millis(interval as u64) - } - AppConfigTlvType::SlotDuration => { - self.slot_duration = u16::from_le_bytes(value[..].try_into().unwrap()) - } - AppConfigTlvType::ChannelNumber => { - self.channel_number = ChannelNumber::from_u8(value[0]).unwrap() - } - AppConfigTlvType::DeviceMacAddress => { - self.device_mac_address = match self.mac_address_mode { - MacAddressMode::AddressMode0 => { - MacAddress::Short(value[..].try_into().unwrap()) - } - MacAddressMode::AddressMode2 => { - MacAddress::Extended(value[..].try_into().unwrap()) - } - _ => panic!("Unexpected MAC Address Mode"), - }; - } - AppConfigTlvType::NoOfControlee => { - assert!(value[0] as usize <= MAX_NUMBER_OF_CONTROLEES); - self.number_of_controlees = value[0] as usize; - } - AppConfigTlvType::DstMacAddress => { - let mac_address_size = match self.mac_address_mode { - MacAddressMode::AddressMode0 => 2, - MacAddressMode::AddressMode2 => 8, - _ => panic!("Unexpected MAC Address Mode"), - }; - if value.len() % mac_address_size != 0 - || (value.len() / mac_address_size) != self.number_of_controlees - { - return Err(StatusCode::UciStatusInvalidParam); - } - self.dst_mac_addresses = value - .chunks(mac_address_size) - .map(|c| match self.mac_address_mode { - MacAddressMode::AddressMode0 => MacAddress::Short(c.try_into().unwrap()), - MacAddressMode::AddressMode2 => MacAddress::Extended(c.try_into().unwrap()), - _ => panic!("Unexpected MAC Address Mode"), - }) - .collect(); - assert_eq!(self.dst_mac_addresses.len(), self.number_of_controlees); - } - AppConfigTlvType::MultiNodeMode => { - self.multi_node_mode = MultiNodeMode::from_u8(value[0]).unwrap() - } - AppConfigTlvType::DeviceType => { - self.device_type = DeviceType::from_u8(value[0]).unwrap(); - } - AppConfigTlvType::RangingRoundUsage => { - self.ranging_round_usage = RangingRoundUsage::from_u8(value[0]).unwrap() - } - AppConfigTlvType::StsConfig => self.sts_config = StsConfig::from_u8(value[0]).unwrap(), - AppConfigTlvType::MacFcsType => { - self.mac_fcs_type = MacFcsType::from_u8(value[0]).unwrap() - } - AppConfigTlvType::RangingRoundControl => self.ranging_round_control = value[0], - AppConfigTlvType::AoaResultReq => { - self.aoa_result_req = AoaResultReq::from_u8(value[0]).unwrap() - } - AppConfigTlvType::RngDataNtf => { - self.rng_data_ntf = RangeDataNtfConfig::from_u8(value[0]).unwrap() - } - AppConfigTlvType::RngDataNtfProximityNear => { - self.rng_data_ntf_proximity_near = u16::from_le_bytes(value[..].try_into().unwrap()) - } - AppConfigTlvType::RngDataNtfProximityFar => { - self.rng_data_ntf_proximity_far = u16::from_le_bytes(value[..].try_into().unwrap()) - } - AppConfigTlvType::DeviceRole => { - self.device_role = DeviceRole::from_u8(value[0]).unwrap(); - } - AppConfigTlvType::RframeConfig => { - self.r_frame_config = RframeConfig::from_u8(value[0]).unwrap() - } - AppConfigTlvType::RssiReporting => { - self.rssi_reporting = match value[0] { - 0 => false, - 1 => true, - _ => panic!("Invalid rssi reporting value!"), - } - } - AppConfigTlvType::PreambleCodeIndex => self.preamble_code_index = value[0], - AppConfigTlvType::SfdId => self.sfd_id = SfdIdValue::from_u8(value[0]).unwrap(), - AppConfigTlvType::PsduDataRate => { - self.psdu_data_rate = PsduDataRate::from_u8(value[0]).unwrap() - } - AppConfigTlvType::PreambleDuration => { - self.preamble_duration = PreambleDuration::from_u8(value[0]).unwrap() - } - AppConfigTlvType::RangingTimeStruct => { - self.ranging_time_struct = RangingTimeStruct::from_u8(value[0]).unwrap() - } - AppConfigTlvType::SlotsPerRr => self.slots_per_rr = value[0], - AppConfigTlvType::TxAdaptivePayloadPower => { - self.tx_adaptive_payload_power = match value[0] { - 0 => false, - 1 => true, - _ => panic!("Invalid tx adaptive payload power value!"), - } - } - AppConfigTlvType::PrfMode => self.prf_mode = PrfMode::from_u8(value[0]).unwrap(), - AppConfigTlvType::ScheduledMode => { - self.schedule_mode = SchedulingMode::from_u8(value[0]).unwrap() - } - AppConfigTlvType::KeyRotation => { - self.key_rotation = match value[0] { - 0 => false, - 1 => true, - _ => panic!("Invalid key rotation value!"), - } - } - AppConfigTlvType::KeyRotationRate => self.key_rotation_rate = value[0], - AppConfigTlvType::SessionPriority => self.session_priority = value[0], - AppConfigTlvType::VendorId => { - self.vendor_id = Some(value.to_vec()); - } - AppConfigTlvType::StaticStsIv => { - self.static_sts_iv = Some(value.to_vec()); - } - AppConfigTlvType::NumberOfStsSegments => { - self.number_of_sts_segments = StsSegmentCountValue::from_u8(value[0]).unwrap() - } - AppConfigTlvType::MaxRrRetry => { - self.max_rr_retry = u16::from_le_bytes(value[..].try_into().unwrap()) - } - AppConfigTlvType::UwbInitiationTime => { - self.uwb_initiation_time = match value.len() { - // Backward compatible with Fira 1.1 Version UCI host. - FIRA_1_1_INITIATION_TIME_SIZE => { - u32::from_le_bytes(value[..].try_into().unwrap()) as u64 - } - FIRA_2_0_INITIATION_TIME_SIZE => { - u64::from_le_bytes(value[..].try_into().unwrap()) - } - _ => panic!("Invalid initiation time!"), - } - } - AppConfigTlvType::HoppingMode => { - self.hopping_mode = HoppingMode::from_u8(value[0]).unwrap() - } - AppConfigTlvType::BlockStrideLength => self.block_stride_length = value[0], - AppConfigTlvType::ResultReportConfig => { - self.result_report_config = match value[0] { - 0 => false, - 1 => true, - _ => panic!("Invalid result report config value!"), - } - } - AppConfigTlvType::BprfPhrDataRate => { - self.bprf_phr_data_rate = BprfPhrDataRate::from_u8(value[0]).unwrap() - } - AppConfigTlvType::MaxNumberOfMeasurements => self.max_number_of_measurements = value[0], - AppConfigTlvType::StsLength => self.sts_length = StsLength::from_u8(value[0]).unwrap(), - AppConfigTlvType::InBandTerminationAttemptCount => { - self.in_band_termination_attempt_count = value[0] - } - AppConfigTlvType::SessionKey => self.session_key = Some(value.to_vec()), - AppConfigTlvType::SubSessionId => { - self.sub_session_id = u32::from_le_bytes(value[..].try_into().unwrap()) - } - AppConfigTlvType::SubsessionKey => self.sub_session_key = Some(value.to_vec()), - AppConfigTlvType::LinkLayerMode => { - self.link_layer_mode = LinkLayerMode::from_u8(value[0]).unwrap() - } - AppConfigTlvType::DataRepetitionCount => { - self.data_repetition_count = DataRepetitionCount::from_u8(value[0]).unwrap() - } - AppConfigTlvType::SessionDataTransferStatusNtfConfig => { - self.session_data_transfer_status_ntf_config = - SessionDataTransferStatusNtfConfig::from_u8(value[0]).unwrap() - } - AppConfigTlvType::ApplicationDataEndpoint => self.application_data_endpoint = value[0], - id => { - log::error!("Ignored AppConfig parameter {:?}", id); - return Err(StatusCode::UciStatusInvalidParam); - } - }; - - self.raw.insert(id, value.to_vec()); - - Ok(()) - } - - fn get_config(&self, id: AppConfigTlvType) -> Option> { - self.raw.get(&id).cloned() - } - - pub fn is_compatible_for_ranging(&self, peer_config: &Self) -> bool { - self == peer_config - && self.device_role != peer_config.device_role - && self.device_type != peer_config.device_type - && peer_config - .dst_mac_addresses - .contains(&self.device_mac_address) - && self - .dst_mac_addresses - .contains(&peer_config.device_mac_address) - } - - pub fn can_start_data_transfer(&self) -> bool { - self.device_role == DeviceRole::Initiator - } - - pub fn can_receive_data_transfer(&self) -> bool { - self.device_role == DeviceRole::Responder - } - - fn extend(&mut self, configs: &[AppConfigTlv]) -> Vec { - if !app_config_has_mandatory_parameters(configs) { - // TODO: What shall we do in this situation? - } - - configs - .iter() - .fold(Vec::new(), |mut invalid_parameters, config| { - match self.set_config(config.cfg_id, &config.v) { - Ok(_) => (), - Err(status) => invalid_parameters.push(AppConfigStatus { - cfg_id: config.cfg_id, - status, - }), - }; - invalid_parameters - }) - } -} enum SubSessionKey { None, Short([u8; 16]), Extended([u8; 32]), } + struct Controlee { short_address: MacAddress, sub_session_id: u32, @@ -785,12 +136,18 @@ impl Session { }); } - pub fn get_dst_mac_addresses(&self) -> &Vec { - &self.app_config.dst_mac_addresses + pub fn get_dst_mac_address(&self) -> &[MacAddress] { + &self.app_config.dst_mac_address + } + + pub fn is_session_info_ntf_enabled(&self) -> bool { + self.app_config.session_info_ntf_config != uci::SessionInfoNtfConfig::Disable } - pub fn is_ranging_data_ntf_enabled(&self) -> RangeDataNtfConfig { - self.app_config.rng_data_ntf + #[allow(unused)] + pub fn is_session_data_transfer_status_ntf_enabled(&self) -> bool { + self.app_config.session_data_transfer_status_ntf_config + != uci::SessionDataTransferStatusNtfConfig::Disable } pub fn data(&self) -> &BytesMut { @@ -852,7 +209,47 @@ impl Session { (StatusCode::UciStatusRejected, Vec::new()) } else { let mut app_config = self.app_config.clone(); - let invalid_parameters = app_config.extend(cmd.get_tlvs()); + let mut invalid_parameters = vec![]; + for cfg in cmd.get_tlvs() { + match app_config.set(cfg.cfg_id, &cfg.v) { + Ok(_) => (), + Err(_) => invalid_parameters.push(AppConfigStatus { + cfg_id: cfg.cfg_id, + status: uci::StatusCode::UciStatusInvalidParam, + }), + } + } + + // [UCI] 7.5.1 Configuration of a Session + // This section defines the mandatory APP Configuration Parameters to be applied + // by the Host for FiRa defined UWB Session types. The Host shall apply these + // mandatory configurations to move the Session State from SESSION_STATE_INIT + // to SESSION_STATE_IDLE. + // + // - DEVICE_ROLE + // - MULTI_NODE_MODE + // - RANGING_ROUND_USAGE + // - DEVICE_MAC_ADDRESS + // - DEVICE_TYPE (see Note1) + // - SCHEDULE_MODE + if app_config.device_role.is_none() + || app_config.multi_node_mode.is_none() + || app_config.ranging_round_usage.is_none() + || app_config.device_mac_address.is_none() + || app_config.schedule_mode.is_none() + { + log::error!( + "[{}:0x{:x}] missing mandatory APP config parameters", + self.device_handle, + self.id + ); + return SessionSetAppConfigRspBuilder { + status: uci::StatusCode::UciStatusRejected, + cfg_status: vec![], + } + .build(); + } + if invalid_parameters.is_empty() { self.app_config = app_config; if self.state == SessionState::SessionStateInit { @@ -883,25 +280,21 @@ impl Session { assert_eq!(self.id, cmd.get_session_token()); let (status, valid_parameters) = { - let (valid_parameters, invalid_parameters) = cmd.get_app_cfg().iter().fold( - (Vec::new(), Vec::new()), - |(mut valid_parameters, mut invalid_parameters), config_id| { - match AppConfigTlvType::try_from(*config_id) { - Ok(id) => match self.app_config.get_config(id) { - Some(value) => valid_parameters.push(AppConfigTlv { - cfg_id: id, - v: value, - }), - None => invalid_parameters.push(AppConfigTlv { - cfg_id: id, - v: Vec::new(), - }), - }, - Err(_) => log::error!("Failed to parse AppConfigTlv: {:?}", *config_id), - } - (valid_parameters, invalid_parameters) - }, - ); + let mut valid_parameters = vec![]; + let mut invalid_parameters = vec![]; + for id in cmd.get_app_cfg() { + match self.app_config.get(*id) { + Ok(value) => valid_parameters.push(AppConfigTlv { + cfg_id: *id, + v: value, + }), + Err(_) => invalid_parameters.push(AppConfigTlv { + cfg_id: *id, + v: vec![], + }), + } + } + if invalid_parameters.is_empty() { (StatusCode::UciStatusOk, valid_parameters) } else { @@ -937,19 +330,19 @@ impl Session { assert_eq!(self.id, cmd.get_session_token()); if (self.state != SessionState::SessionStateActive && self.state != SessionState::SessionStateIdle) - || self.app_config.device_type != DeviceType::Controller - || (self.app_config.multi_node_mode != MultiNodeMode::OneToMany - && self.app_config.multi_node_mode != MultiNodeMode::ManyToMany) + || self.app_config.device_type != Some(DeviceType::Controller) + || self.app_config.multi_node_mode != Some(MultiNodeMode::OneToMany) { return SessionUpdateControllerMulticastListRspBuilder { status: StatusCode::UciStatusRejected, } .build(); } - let action = UpdateMulticastListAction::from_u8(cmd.get_action().into()).unwrap(); - let mut dst_addresses = self.app_config.dst_mac_addresses.clone(); + let action = cmd.get_action(); + let mut dst_addresses = self.app_config.dst_mac_address.clone(); let new_controlees: Vec = match action { - UpdateMulticastListAction::Add | UpdateMulticastListAction::Delete => { + UpdateMulticastListAction::AddControlee + | UpdateMulticastListAction::RemoveControlee => { if let Ok(packet) = SessionUpdateControllerMulticastListCmdPayload::parse(cmd.get_payload()) { @@ -965,7 +358,7 @@ impl Session { .build(); } } - UpdateMulticastListAction::AddWithShortSubSessionKey => { + UpdateMulticastListAction::AddControleeWithShortSubSessionKey => { if let Ok(packet) = SessionUpdateControllerMulticastListCmd_2_0_16_Byte_Payload::parse( cmd.get_payload(), @@ -983,7 +376,7 @@ impl Session { .build(); } } - UpdateMulticastListAction::AddwithExtendedSubSessionKey => { + UpdateMulticastListAction::AddControleeWithExtendedSubSessionKey => { if let Ok(packet) = SessionUpdateControllerMulticastListCmd_2_0_32_Byte_Payload::parse( cmd.get_payload(), @@ -1008,19 +401,21 @@ impl Session { let mut status = StatusCode::UciStatusOk; match action { - UpdateMulticastListAction::Add - | UpdateMulticastListAction::AddWithShortSubSessionKey - | UpdateMulticastListAction::AddwithExtendedSubSessionKey => { + UpdateMulticastListAction::AddControlee + | UpdateMulticastListAction::AddControleeWithShortSubSessionKey + | UpdateMulticastListAction::AddControleeWithExtendedSubSessionKey => { new_controlees.iter().for_each(|controlee| { let mut update_status = MulticastUpdateStatusCode::StatusOkMulticastListUpdate; if !dst_addresses.contains(&controlee.short_address) { if dst_addresses.len() == MAX_NUMBER_OF_CONTROLEES { status = StatusCode::UciStatusMulticastListFull; update_status = MulticastUpdateStatusCode::StatusErrorMulticastListFull; - } else if (action == UpdateMulticastListAction::AddWithShortSubSessionKey - || action == UpdateMulticastListAction::AddwithExtendedSubSessionKey) + } else if (action + == UpdateMulticastListAction::AddControleeWithShortSubSessionKey + || action + == UpdateMulticastListAction::AddControleeWithExtendedSubSessionKey) && self.app_config.sts_config - != StsConfig::ProvisionedForControleeIndividualKey + != uci::StsConfig::ProvisionedForResponderSubSessionKey { // If Action is 0x02 or 0x03 for STS_CONFIG values other than // 0x04, the UWBS shall return SESSION_UPDATE_CONTROLLER_MULTICAST_LIST_NTF @@ -1043,7 +438,7 @@ impl Session { }); }); } - UpdateMulticastListAction::Delete => { + UpdateMulticastListAction::RemoveControlee => { new_controlees.iter().for_each(|controlee: &Controlee| { let pica_tx = self.pica_tx.clone(); let address = controlee.short_address; @@ -1080,12 +475,12 @@ impl Session { }); } } - self.app_config.number_of_controlees = dst_addresses.len(); - self.app_config.dst_mac_addresses = dst_addresses.clone(); + self.app_config.number_of_controlees = dst_addresses.len() as u8; + self.app_config.dst_mac_address = dst_addresses.clone(); // If the multicast list becomes empty, the UWBS shall move the session to // SESSION_STATE_IDLE by sending the SESSION_STATUS_NTF with Reason Code // set to ERROR_INVALID_NUM_OF_CONTROLEES. - if self.app_config.dst_mac_addresses.is_empty() { + if self.app_config.dst_mac_address.is_empty() { self.set_state( SessionState::SessionStateIdle, ReasonCode::ErrorInvalidNumOfControlees, @@ -1118,7 +513,8 @@ impl Session { assert_eq!(self.state, SessionState::SessionStateIdle); let session_id = self.id; - let ranging_interval = self.app_config.ranging_interval; + let ranging_interval = + time::Duration::from_millis(self.app_config.ranging_duration as u64); let device_handle = self.device_handle; let tx = self.pica_tx.clone(); self.ranging_task = Some(tokio::spawn(async move { diff --git a/src/uci_packets.pdl b/src/uci_packets.pdl index 2213e02..ebb8db2 100644 --- a/src/uci_packets.pdl +++ b/src/uci_packets.pdl @@ -199,13 +199,14 @@ enum DeviceConfigId : 8 { LOW_POWER_MODE = 0x01, } +// [UCI] Table 45: APP Configuration Parameters IDs enum AppConfigTlvType : 8 { DEVICE_TYPE = 0x00, RANGING_ROUND_USAGE = 0x01, STS_CONFIG = 0x02, MULTI_NODE_MODE = 0x03, CHANNEL_NUMBER = 0x04, - NO_OF_CONTROLEE = 0x05, + NUMBER_OF_CONTROLEES = 0x05, DEVICE_MAC_ADDRESS = 0x06, DST_MAC_ADDRESS = 0x07, SLOT_DURATION = 0x08, @@ -214,9 +215,9 @@ enum AppConfigTlvType : 8 { MAC_FCS_TYPE = 0x0B, RANGING_ROUND_CONTROL = 0x0C, AOA_RESULT_REQ = 0x0D, - RNG_DATA_NTF = 0x0E, - RNG_DATA_NTF_PROXIMITY_NEAR = 0x0F, - RNG_DATA_NTF_PROXIMITY_FAR = 0x10, + SESSION_INFO_NTF_CONFIG = 0x0E, + NEAR_PROXIMITY_CONFIG = 0x0F, + FAR_PROXIMITY_CONFIG = 0x10, DEVICE_ROLE = 0x11, RFRAME_CONFIG = 0x12, RSSI_REPORTING = 0x13, @@ -228,14 +229,11 @@ enum AppConfigTlvType : 8 { DATA_REPETITION_COUNT = 0x19, RANGING_TIME_STRUCT = 0x1A, SLOTS_PER_RR = 0x1B, - TX_ADAPTIVE_PAYLOAD_POWER = 0x1C, - // TODO: Ensure this value is correct in the final 2.0 specification. - RNG_DATA_NTF_AOA_BOUND = 0x1D, - RESPONDER_SLOT_INDEX = 0x1E, + AOA_BOUND_CONFIG = 0x1D, PRF_MODE = 0x1F, CAP_SIZE_RANGE = 0x20, TX_JITTER_WINDOW_SIZE = 0x21, - SCHEDULED_MODE = 0x22, + SCHEDULE_MODE = 0x22, KEY_ROTATION = 0x23, KEY_ROTATION_RATE = 0x24, SESSION_PRIORITY = 0x25, @@ -252,13 +250,7 @@ enum AppConfigTlvType : 8 { SUB_SESSION_ID = 0x30, BPRF_PHR_DATA_RATE = 0x31, MAX_NUMBER_OF_MEASUREMENTS = 0x32, - UL_TDOA_TX_INTERVAL = 0x33, - UL_TDOA_RANDOM_WINDOW = 0x34, STS_LENGTH = 0x35, - SUSPEND_RANGING_ROUNDS = 0x36, - UL_TDOA_NTF_REPORT_CONFIG = 0x37, - UL_TDOA_DEVICE_ID = 0x38, - UL_TDOA_TX_TIMESTAMP = 0x39, MIN_FRAMES_PER_RR = 0x3A, MTU_SIZE = 0x3B, INTER_FRAME_INTERVAL = 0x3C, @@ -268,10 +260,10 @@ enum AppConfigTlvType : 8 { DL_TDOA_ANCHOR_CFO = 0x40, DL_TDOA_ANCHOR_LOCATION = 0x41, DL_TDOA_TX_ACTIVE_RANGING_ROUNDS = 0x42, - DL_TDOA_BLOCK_STRIDING = 0x43, + DL_TDOA_BLOCK_SKIPPING = 0x43, DL_TDOA_TIME_REFERENCE_ANCHOR = 0x44, SESSION_KEY = 0x45, - SUBSESSION_KEY = 0x46, + SUB_SESSION_KEY = 0x46, SESSION_DATA_TRANSFER_STATUS_NTF_CONFIG = 0x47, SESSION_TIME_BASE = 0x48, DL_TDOA_RESPONDER_TOF = 0x49, @@ -279,9 +271,9 @@ enum AppConfigTlvType : 8 { SECURE_RANGING_CSW_LENGTH = 0x4B, APPLICATION_DATA_ENDPOINT = 0x4C, OWR_AOA_MEASUREMENT_NTF_PERIOD = 0x4D, - RFU_APP_CFG_TLV_TYPE_RANGE = 0x4E..0x9F, + RFU = 0x4E..0x9F, - VENDOR_SPECIFIC_APP_CFG_TLV_TYPE_RANGE_1 = 0xA0..0xDF { + VENDOR_SPECIFIC_1 = 0xA0..0xDF { // CCC specific CCC_HOP_MODE_KEY = 0xA0, CCC_UWB_TIME0 = 0xA1, @@ -293,9 +285,10 @@ enum AppConfigTlvType : 8 { }, // Reserved for extension IDs. - RFU_APP_CFG_TLV_TYPE_RANGE_3 = 0xE0..0xE2, + // ID is 2 Octets in length. Refer section 8.1 for details + ID_EXTENSION = 0xE0..0xE2, - VENDOR_SPECIFIC_APP_CFG_TLV_TYPE_RANGE_2 = 0xE3..0xFF { + VENDOR_SPECIFIC_2 = 0xE3..0xFF { // Interleaving ratio if AOA_RESULT_REQ is set to 0xF0. NB_OF_RANGE_MEASUREMENTS = 0xE3, NB_OF_AZIMUTH_MEASUREMENTS = 0xE4, @@ -305,10 +298,189 @@ enum AppConfigTlvType : 8 { }, } -enum FrameReportTlvType : 8 { - RSSI = 0x0, - AOA = 0x1, - CIR = 0x2, +enum DeviceType : 8 { + // [MAC] 5.1.2 + // Device utilizing the ranging features set through Control Messages + CONTROLEE = 0x00, + // [MAC] 5.1.1 + // Device controlling the ranging features through Control Messages + CONTROLLER = 0x01, +} + +enum RangingRoundUsage : 8 { + SS_TWR_DEFERRED_MODE = 0x01, + DS_TWR_DEFERRED_MODE = 0x02, + SS_TWR_NON_DEFERRED_MODE = 0x03, + DS_TWR_NON_DEFERRED_MODE = 0x04, + ON_WAY_RANGING_DL_TDOA = 0x05, + OWR_AOA_MEASUREMENT = 0x06, + ESS_TWR_NON_DEFERRED = 0x07, + ADS_TWR_NON_DEFERRED = 0x08, +} + +enum StsConfig : 8 { + STATIC = 0x00, // Default + DYNAMIC = 0x01, + DYNAMIC_FOR_RESPONDER_SUB_SESSION_KEY = 0x02, + PROVISIONED = 0x03, + PROVISIONED_FOR_RESPONDER_SUB_SESSION_KEY = 0x04, +} + +enum MultiNodeMode : 8 { + ONE_TO_ONE = 0x00, + ONE_TO_MANY = 0x01, +} + +enum ChannelNumber : 8 { + CHANNEL_NUMBER_5 = 0x05, + CHANNEL_NUMBER_6 = 0x06, + CHANNEL_NUMBER_8 = 0x08, + CHANNEL_NUMBER_9 = 0x09, // Default + CHANNEL_NUMBER_10 = 0x0a, + CHANNEL_NUMBER_12 = 0x0c, + CHANNEL_NUMBER_13 = 0x0d, + CHANNEL_NUMBER_14 = 0x0e, +} + +enum MacFcsType : 8 { + CRC_16 = 0x00, // Default + CRC_32 = 0x01, +} + +struct RangingRoundControl { + rrrm : 1, // Default 1 + _fixed_ = 1 : 1, + rcp : 1, // Default 0 + _reserved_ : 3, + mrp : 1, // Default 0 + mrm : 1, // Default 0 +} + +enum AoaResultReq : 8 { + AOA_DISABLED = 0x00, + AOA_ENABLED = 0x01, // Default + AOA_ENABLED_AZIMUTH_ONLY = 0x02, + AOA_ENABLED_ELEVATION_ONLY = 0x03, +} + +enum SessionInfoNtfConfig : 8 { + DISABLE = 0x00, + ENABLE = 0x01, // Default + ENABLE_PROXIMITY_TRIGGER = 0x02, + ENABLE_AOA_TRIGGER = 0x03, + ENABLE_PROXIMITY_AOA_TRIGGER = 0x04, + ENABLE_PROXIMITY_EDGE_TRIGGER = 0x05, + ENABLE_AOA_EDGE_TRIGGER = 0x06, + ENABLE_PROXIMITY_AOA_EDGE_TRIGGER = 0x07, +} + +enum DeviceRole : 8 { + // [MAC] 5.1.3 + // Device initiating a ranging exchange with a ranging initiation message + RESPONDER = 0x00, + // [MAC] 5.1.4 + // Device responding to ranging initiation messages + INITIATOR = 0x01, + ADVERTISER = 0x05, + OBSERVER = 0x06, + DT_ANCHOR = 0x07, + DT_TAG = 0x08, +} + +enum RframeConfig : 8 { + SP0 = 0x00, + SP1 = 0x01, + SP3 = 0x03, // Default +} + +enum RssiReporting : 8 { + DISABLE = 0x00, // Default + ENABLE = 0x01, +} + +enum PsduDataRate : 8 { + DATA_RATE_6M81 = 0x00, + DATA_RATE_7M80 = 0x01, + DATA_RATE_27M2 = 0x02, + DATA_RATE_31M2 = 0x03, +} + +enum PreambleDuration : 8 { + DURATION_32_SYMBOLS = 0x00, + DURATION_64_SYMBOLS = 0x01, // Default +} + +enum LinkLayerMode : 8 { + BYPASS_MODE = 0x00, // Default +} + +enum RangingTimeStruct : 8 { + BLOCK_BASED_SCHEDULING = 0x01, // Default +} + +enum PrfMode : 8 { + BPRF_MODE = 0x00, // Default + HPRF_MODE_124M8 = 0x01, + HPRF_MODE_249M6 = 0x02, +} + +enum ScheduleMode : 8 { + CONTENTION_BASED = 0x00, + TIME_SCHEDULED = 0x01, +} + +enum KeyRotation : 8 { + DISABLE = 0x00, // Default + ENABLE = 0x01, +} + +enum MacAddressMode : 8 { + // MAC address is 2 bytes and 2 bytes to be used in MAC header + MODE_0 = 0x00, // Default + // MAC address is 8 bytes and 2 bytes to be used in MAC header + // (Not supported). + MODE_1 = 0x01, + // MAC address is 8 bytes and 8 bytes to be used in MAC header + MODE_2 = 0x02, +} + +enum HoppingMode : 8 { + DISABLE = 0x00, // Default + ENABLE = 0x01, +} + +struct ResultReportConfig { + tof : 1, // Default 1 + aoa_azimuth : 1, + aoa_elevation : 1, + aoa_fom : 1, + _reserved_ : 4, +} + +enum BprfPhrDataRate : 8 { + DATA_RATE_850K = 0x00, // Default + DATA_RATE_6M81 = 0x01 +} + +enum StsLength : 8 { + LENGTH_32_SYMBOLS = 0x00, // Default + LENGTH_64_SYMBOLS = 0x01, + LENGTH_128_SYMBOLS = 0x02, +} + +enum DlTdoaRangingMethod : 8 { + SS_TWR = 0x00, + DS_TWR = 0x01, // Default +} + +enum DlTdoaAnchorCfo : 8 { + ANCHOR_CFO_NOT_INCLUDED = 0x00, + ANCHOR_CFO_INCLUDED = 0x01, // Default +} + +enum SessionDataTransferStatusNtfConfig : 8 { + DISABLE = 0x00, // Default + ENABLE = 0x01, } enum CapTlvType : 8 { @@ -706,7 +878,7 @@ test SetConfigRsp { packet GetConfigCmd : CoreCommand (opcode = 0x5) { //CORE_GET_CONFIG _count_(cfg_id): 8, - cfg_id: 8[], // DeviceConfigId (Infra does not allow array of enums) + cfg_id: 8[], // DeviceConfigId } test GetConfigCmd { @@ -839,7 +1011,7 @@ test SessionSetAppConfigRsp { packet SessionGetAppConfigCmd : SessionConfigCommand (opcode = 0x4) { //SESSION_GET_APP_CONFIG session_token: 32, // Session ID or Session Handle (based on UWBS version) _count_(app_cfg): 8, - app_cfg: 8[], // AppConfigTlvType (Infra does not allow array of enums) + app_cfg: AppConfigTlvType[], } test SessionGetAppConfigCmd { @@ -926,11 +1098,12 @@ struct Controlee_V2_0_32_Byte_Version { subsession_key: 8[32], } +/// cf. [UCI] 7.7 enum UpdateMulticastListAction: 8 { ADD_CONTROLEE = 0x00, REMOVE_CONTROLEE = 0x01, ADD_CONTROLEE_WITH_SHORT_SUB_SESSION_KEY = 0x02, - ADD_CONTROLEE_WITH_LONG_SUB_SESSION_KEY = 0x03, + ADD_CONTROLEE_WITH_EXTENDED_SUB_SESSION_KEY = 0x03, } packet SessionUpdateControllerMulticastListCmd : SessionConfigCommand (opcode = 0x7) { //SESSION_UPDATE_CONTROLLER_MULTICAST_LIST @@ -1262,6 +1435,12 @@ test AndroidSetCountryCodeRsp { "\x4c\x01\x00\x01\x00\x00\x00\x00", } +enum FrameReportTlvType : 8 { + RSSI = 0x0, + AOA = 0x1, + CIR = 0x2, +} + struct FrameReportTlv { t: FrameReportTlvType, _size_(v): 16, diff --git a/tests/data_transfer.py b/tests/data_transfer.py index 241a6b9..7449a35 100755 --- a/tests/data_transfer.py +++ b/tests/data_transfer.py @@ -45,32 +45,45 @@ async def controller(host: Host, peer: Host, file: Path): ) ) - mac_address_mode = 0x0 + ranging_round_usage = 0x06 ranging_duration = int(1000).to_bytes(4, byteorder="little") - device_role_initiator = bytes([0]) - device_type_controller = bytes([1]) + host.send_control( uci.SessionSetAppConfigCmd( session_token=0, tlvs=[ uci.AppConfigTlv( - cfg_id=uci.AppConfigTlvType.DEVICE_ROLE, v=device_role_initiator + cfg_id=uci.AppConfigTlvType.DEVICE_ROLE, + v=bytes([uci.DeviceRole.INITIATOR]), ), uci.AppConfigTlv( - cfg_id=uci.AppConfigTlvType.DEVICE_TYPE, v=device_type_controller + cfg_id=uci.AppConfigTlvType.DEVICE_TYPE, + v=bytes([uci.DeviceType.CONTROLLER]), ), uci.AppConfigTlv( cfg_id=uci.AppConfigTlvType.DEVICE_MAC_ADDRESS, v=host.mac_address ), uci.AppConfigTlv( cfg_id=uci.AppConfigTlvType.MAC_ADDRESS_MODE, - v=bytes([mac_address_mode]), + v=bytes([uci.MacAddressMode.MODE_0]), + ), + uci.AppConfigTlv( + cfg_id=uci.AppConfigTlvType.MULTI_NODE_MODE, + v=bytes([uci.MultiNodeMode.ONE_TO_ONE]), + ), + uci.AppConfigTlv( + cfg_id=uci.AppConfigTlvType.SCHEDULE_MODE, + v=bytes([uci.ScheduleMode.CONTENTION_BASED]), + ), + uci.AppConfigTlv( + cfg_id=uci.AppConfigTlvType.RANGING_ROUND_USAGE, + v=bytes([ranging_round_usage]), ), uci.AppConfigTlv( cfg_id=uci.AppConfigTlvType.RANGING_DURATION, v=ranging_duration ), uci.AppConfigTlv( - cfg_id=uci.AppConfigTlvType.NO_OF_CONTROLEE, v=bytes([1]) + cfg_id=uci.AppConfigTlvType.NUMBER_OF_CONTROLEES, v=bytes([1]) ), uci.AppConfigTlv( cfg_id=uci.AppConfigTlvType.DST_MAC_ADDRESS, v=peer.mac_address @@ -159,32 +172,45 @@ async def controlee(host: Host, peer: Host, file: Path): ) ) - mac_address_mode = 0x0 + ranging_round_usage = 0x06 ranging_duration = int(1000).to_bytes(4, byteorder="little") - device_role_responder = bytes([1]) - device_type_controllee = bytes([0]) + host.send_control( uci.SessionSetAppConfigCmd( session_token=0, tlvs=[ uci.AppConfigTlv( - cfg_id=uci.AppConfigTlvType.DEVICE_ROLE, v=device_role_responder + cfg_id=uci.AppConfigTlvType.DEVICE_ROLE, + v=bytes([uci.DeviceRole.RESPONDER]), ), uci.AppConfigTlv( - cfg_id=uci.AppConfigTlvType.DEVICE_TYPE, v=device_type_controllee + cfg_id=uci.AppConfigTlvType.DEVICE_TYPE, + v=bytes([uci.DeviceType.CONTROLEE]), ), uci.AppConfigTlv( cfg_id=uci.AppConfigTlvType.DEVICE_MAC_ADDRESS, v=host.mac_address ), uci.AppConfigTlv( cfg_id=uci.AppConfigTlvType.MAC_ADDRESS_MODE, - v=bytes([mac_address_mode]), + v=bytes([uci.MacAddressMode.MODE_0]), + ), + uci.AppConfigTlv( + cfg_id=uci.AppConfigTlvType.MULTI_NODE_MODE, + v=bytes([uci.MultiNodeMode.ONE_TO_ONE]), + ), + uci.AppConfigTlv( + cfg_id=uci.AppConfigTlvType.SCHEDULE_MODE, + v=bytes([uci.ScheduleMode.CONTENTION_BASED]), + ), + uci.AppConfigTlv( + cfg_id=uci.AppConfigTlvType.RANGING_ROUND_USAGE, + v=bytes([ranging_round_usage]), ), uci.AppConfigTlv( cfg_id=uci.AppConfigTlvType.RANGING_DURATION, v=ranging_duration ), uci.AppConfigTlv( - cfg_id=uci.AppConfigTlvType.NO_OF_CONTROLEE, v=bytes([1]) + cfg_id=uci.AppConfigTlvType.NUMBER_OF_CONTROLEES, v=bytes([1]) ), uci.AppConfigTlv( cfg_id=uci.AppConfigTlvType.DST_MAC_ADDRESS, v=peer.mac_address diff --git a/tests/ranging.py b/tests/ranging.py index 7af8224..9af4376 100755 --- a/tests/ranging.py +++ b/tests/ranging.py @@ -42,32 +42,45 @@ async def controller(host: Host, peer: Host): ) ) - mac_address_mode = 0x0 + ranging_round_usage = 0x06 ranging_duration = int(1000).to_bytes(4, byteorder="little") - device_role_initiator = bytes([0]) - device_type_controller = bytes([1]) + host.send_control( uci.SessionSetAppConfigCmd( session_token=0, tlvs=[ uci.AppConfigTlv( - cfg_id=uci.AppConfigTlvType.DEVICE_ROLE, v=device_role_initiator + cfg_id=uci.AppConfigTlvType.DEVICE_ROLE, + v=bytes([uci.DeviceRole.INITIATOR]), ), uci.AppConfigTlv( - cfg_id=uci.AppConfigTlvType.DEVICE_TYPE, v=device_type_controller + cfg_id=uci.AppConfigTlvType.DEVICE_TYPE, + v=bytes([uci.DeviceType.CONTROLLER]), ), uci.AppConfigTlv( cfg_id=uci.AppConfigTlvType.DEVICE_MAC_ADDRESS, v=host.mac_address ), uci.AppConfigTlv( cfg_id=uci.AppConfigTlvType.MAC_ADDRESS_MODE, - v=bytes([mac_address_mode]), + v=bytes([uci.MacAddressMode.MODE_0]), + ), + uci.AppConfigTlv( + cfg_id=uci.AppConfigTlvType.MULTI_NODE_MODE, + v=bytes([uci.MultiNodeMode.ONE_TO_ONE]), + ), + uci.AppConfigTlv( + cfg_id=uci.AppConfigTlvType.SCHEDULE_MODE, + v=bytes([uci.ScheduleMode.CONTENTION_BASED]), + ), + uci.AppConfigTlv( + cfg_id=uci.AppConfigTlvType.RANGING_ROUND_USAGE, + v=bytes([ranging_round_usage]), ), uci.AppConfigTlv( cfg_id=uci.AppConfigTlvType.RANGING_DURATION, v=ranging_duration ), uci.AppConfigTlv( - cfg_id=uci.AppConfigTlvType.NO_OF_CONTROLEE, v=bytes([1]) + cfg_id=uci.AppConfigTlvType.NUMBER_OF_CONTROLEES, v=bytes([1]) ), uci.AppConfigTlv( cfg_id=uci.AppConfigTlvType.DST_MAC_ADDRESS, v=peer.mac_address @@ -148,32 +161,45 @@ async def controlee(host: Host, peer: Host): ) ) - mac_address_mode = 0x0 + ranging_round_usage = 0x06 ranging_duration = int(1000).to_bytes(4, byteorder="little") - device_role_responder = bytes([1]) - device_type_controlee = bytes([0]) + host.send_control( uci.SessionSetAppConfigCmd( session_token=0, tlvs=[ uci.AppConfigTlv( - cfg_id=uci.AppConfigTlvType.DEVICE_ROLE, v=device_role_responder + cfg_id=uci.AppConfigTlvType.DEVICE_ROLE, + v=bytes([uci.DeviceRole.RESPONDER]), ), uci.AppConfigTlv( - cfg_id=uci.AppConfigTlvType.DEVICE_TYPE, v=device_type_controlee + cfg_id=uci.AppConfigTlvType.DEVICE_TYPE, + v=bytes([uci.DeviceType.CONTROLEE]), ), uci.AppConfigTlv( cfg_id=uci.AppConfigTlvType.DEVICE_MAC_ADDRESS, v=host.mac_address ), uci.AppConfigTlv( cfg_id=uci.AppConfigTlvType.MAC_ADDRESS_MODE, - v=bytes([mac_address_mode]), + v=bytes([uci.MacAddressMode.MODE_0]), + ), + uci.AppConfigTlv( + cfg_id=uci.AppConfigTlvType.MULTI_NODE_MODE, + v=bytes([uci.MultiNodeMode.ONE_TO_ONE]), + ), + uci.AppConfigTlv( + cfg_id=uci.AppConfigTlvType.SCHEDULE_MODE, + v=bytes([uci.ScheduleMode.CONTENTION_BASED]), + ), + uci.AppConfigTlv( + cfg_id=uci.AppConfigTlvType.RANGING_ROUND_USAGE, + v=bytes([ranging_round_usage]), ), uci.AppConfigTlv( cfg_id=uci.AppConfigTlvType.RANGING_DURATION, v=ranging_duration ), uci.AppConfigTlv( - cfg_id=uci.AppConfigTlvType.NO_OF_CONTROLEE, v=bytes([1]) + cfg_id=uci.AppConfigTlvType.NUMBER_OF_CONTROLEES, v=bytes([1]) ), uci.AppConfigTlv( cfg_id=uci.AppConfigTlvType.DST_MAC_ADDRESS, v=peer.mac_address -- cgit v1.2.3 From 1e3704d1bee3ac7c8795234234a837d7acd6b6a0 Mon Sep 17 00:00:00 2001 From: Henri Chataing Date: Fri, 15 Mar 2024 08:58:16 -0700 Subject: Move the definition of SubSessionKey to app_config module --- src/app_config.rs | 28 ++++++++++++++++++++++++---- src/session.rs | 7 +------ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/app_config.rs b/src/app_config.rs index 82cb738..40ac595 100644 --- a/src/app_config.rs +++ b/src/app_config.rs @@ -1,6 +1,16 @@ use crate::packets::uci; use crate::MacAddress; +/// [UCI] 8.3 Application Configuration Parameters. +/// Sub-session Key provided for Provisioned STS for Responder specific Key mode +/// (STS_CONFIG equal to 0x04). +#[derive(Clone, PartialEq, Eq)] +pub enum SubSessionKey { + None, + Short([u8; 16]), + Extended([u8; 32]), +} + /// [UCI] 8.3 Application Configuration Parameters. /// The configuration is initially filled with default values from the /// specification. @@ -75,7 +85,7 @@ pub struct AppConfig { mtu_size: u16, inter_frame_interval: u8, session_key: Vec, - sub_session_key: Vec, + sub_session_key: SubSessionKey, pub session_data_transfer_status_ntf_config: uci::SessionDataTransferStatusNtfConfig, session_time_base: [u8; 9], application_data_endpoint: u8, @@ -141,7 +151,7 @@ impl Default for AppConfig { mtu_size: 0, // XX inter_frame_interval: 1, session_key: vec![], - sub_session_key: vec![], + sub_session_key: SubSessionKey::None, session_data_transfer_status_ntf_config: uci::SessionDataTransferStatusNtfConfig::Disable, session_time_base: [0; 9], @@ -314,7 +324,13 @@ impl AppConfig { self.inter_frame_interval = try_parse_u8(value)? } uci::AppConfigTlvType::SessionKey => self.session_key = value.to_vec(), - uci::AppConfigTlvType::SubSessionKey => self.sub_session_key = value.to_vec(), + uci::AppConfigTlvType::SubSessionKey => { + self.sub_session_key = match value.len() { + 16 => SubSessionKey::Short(value.try_into().unwrap()), + 32 => SubSessionKey::Extended(value.try_into().unwrap()), + _ => anyhow::bail!("invalid sub-session key size {}", value.len()), + } + } uci::AppConfigTlvType::SessionDataTransferStatusNtfConfig => { self.session_data_transfer_status_ntf_config = try_parse(value)? } @@ -445,7 +461,11 @@ impl AppConfig { uci::AppConfigTlvType::MtuSize => Ok(self.mtu_size.to_le_bytes().to_vec()), uci::AppConfigTlvType::InterFrameInterval => Ok(vec![self.inter_frame_interval]), uci::AppConfigTlvType::SessionKey => Ok(self.session_key.clone()), - uci::AppConfigTlvType::SubSessionKey => Ok(self.sub_session_key.clone()), + uci::AppConfigTlvType::SubSessionKey => Ok(match self.sub_session_key { + SubSessionKey::None => vec![], + SubSessionKey::Short(key) => key.to_vec(), + SubSessionKey::Extended(key) => key.to_vec(), + }), uci::AppConfigTlvType::SessionDataTransferStatusNtfConfig => { Ok(vec![self.session_data_transfer_status_ntf_config.into()]) } diff --git a/src/session.rs b/src/session.rs index dc0ed30..a7e2d44 100644 --- a/src/session.rs +++ b/src/session.rs @@ -24,18 +24,13 @@ use tokio::sync::mpsc; use tokio::task::JoinHandle; use tokio::time; +use super::app_config::SubSessionKey; use super::UciPacket; pub const MAX_SESSION: usize = 255; /// cf. [UCI] 8.3 Table 29 pub const MAX_NUMBER_OF_CONTROLEES: usize = 8; -enum SubSessionKey { - None, - Short([u8; 16]), - Extended([u8; 32]), -} - struct Controlee { short_address: MacAddress, sub_session_id: u32, -- cgit v1.2.3 From dda69c9d230c687468a4383ba29538030f1b3600 Mon Sep 17 00:00:00 2001 From: Henri Chataing Date: Fri, 15 Mar 2024 17:30:53 -0700 Subject: Move all session config command implementations from session to device Previous implementation stretched some way to be able to report invalid session ids, this format makes it easier to read and understand each command implementation individually. --- src/device.rs | 489 +++++++++++++++++++++++++++++++++++++++++++++++++-------- src/lib.rs | 3 +- src/session.rs | 391 +-------------------------------------------- 3 files changed, 426 insertions(+), 457 deletions(-) diff --git a/src/device.rs b/src/device.rs index 48d2bd2..483bacd 100644 --- a/src/device.rs +++ b/src/device.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::packets::uci::*; +use crate::packets::uci::{self, *}; use crate::MacAddress; use crate::PicaCommand; @@ -23,16 +23,21 @@ use std::time::Duration; use tokio::sync::mpsc; use tokio::time; -use super::session::{Session, MAX_SESSION}; +use super::app_config::SubSessionKey; +use super::session::Session; use super::UciPacket; pub const MAX_DEVICE: usize = 4; +pub const MAX_SESSION: usize = 255; const UCI_VERSION: u16 = 0x0002; // Version 2.0 const MAC_VERSION: u16 = 0x3001; // Version 1.3.0 const PHY_VERSION: u16 = 0x3001; // Version 1.3.0 const TEST_VERSION: u16 = 0x1001; // Version 1.1 +/// cf. [UCI] 8.3 Table 29 +pub const MAX_NUMBER_OF_CONTROLEES: usize = 8; + // Capabilities are vendor defined // Android compliant: FIRA-287 UCI_Generic_Specification controlee capabilities_r4 // Android parses capabilities, according to these definitions: @@ -363,6 +368,375 @@ impl Device { .build() } + fn command_session_set_app_config( + &mut self, + cmd: SessionSetAppConfigCmd, + ) -> SessionSetAppConfigRsp { + let session_handle = cmd.get_session_token(); + + log::debug!( + "[{}:0x{:x}] Session Set App Config", + self.handle, + session_handle + ); + + let Some(session) = self.sessions.get_mut(&session_handle) else { + return SessionSetAppConfigRspBuilder { + cfg_status: Vec::new(), + status: StatusCode::UciStatusSessionNotExist, + } + .build(); + }; + + assert!( + session.session_type == SessionType::FiraRangingSession + || session.session_type == SessionType::FiraRangingAndInBandDataSession + ); + + if session.state == SessionState::SessionStateActive { + const IMMUTABLE_PARAMETERS: &[AppConfigTlvType] = &[AppConfigTlvType::AoaResultReq]; + if cmd + .get_tlvs() + .iter() + .any(|cfg| IMMUTABLE_PARAMETERS.contains(&cfg.cfg_id)) + { + return SessionSetAppConfigRspBuilder { + status: StatusCode::UciStatusSessionActive, + cfg_status: vec![], + } + .build(); + } + } + + let (status, invalid_parameters) = if session.state != SessionState::SessionStateInit + && session.state != SessionState::SessionStateActive + { + (StatusCode::UciStatusRejected, Vec::new()) + } else { + let mut app_config = session.app_config.clone(); + let mut invalid_parameters = vec![]; + for cfg in cmd.get_tlvs() { + match app_config.set(cfg.cfg_id, &cfg.v) { + Ok(_) => (), + Err(_) => invalid_parameters.push(AppConfigStatus { + cfg_id: cfg.cfg_id, + status: uci::StatusCode::UciStatusInvalidParam, + }), + } + } + + // [UCI] 7.5.1 Configuration of a Session + // This section defines the mandatory APP Configuration Parameters to be applied + // by the Host for FiRa defined UWB Session types. The Host shall apply these + // mandatory configurations to move the Session State from SESSION_STATE_INIT + // to SESSION_STATE_IDLE. + // + // - DEVICE_ROLE + // - MULTI_NODE_MODE + // - RANGING_ROUND_USAGE + // - DEVICE_MAC_ADDRESS + // - DEVICE_TYPE (see Note1) + // - SCHEDULE_MODE + if app_config.device_role.is_none() + || app_config.multi_node_mode.is_none() + || app_config.ranging_round_usage.is_none() + || app_config.device_mac_address.is_none() + || app_config.schedule_mode.is_none() + { + log::error!( + "[{}:0x{:x}] missing mandatory APP config parameters", + self.handle, + session_handle + ); + return SessionSetAppConfigRspBuilder { + status: uci::StatusCode::UciStatusRejected, + cfg_status: vec![], + } + .build(); + } + + if invalid_parameters.is_empty() { + session.app_config = app_config; + if session.state == SessionState::SessionStateInit { + session.set_state( + SessionState::SessionStateIdle, + ReasonCode::StateChangeWithSessionManagementCommands, + ); + } + (StatusCode::UciStatusOk, invalid_parameters) + } else { + (StatusCode::UciStatusInvalidParam, invalid_parameters) + } + }; + + SessionSetAppConfigRspBuilder { + status, + cfg_status: invalid_parameters, + } + .build() + } + + fn command_session_get_app_config( + &self, + cmd: SessionGetAppConfigCmd, + ) -> SessionGetAppConfigRsp { + let session_handle = cmd.get_session_token(); + + log::debug!( + "[{}:0x{:x}] Session Get App Config", + self.handle, + session_handle + ); + + let Some(session) = self.sessions.get(&session_handle) else { + return SessionGetAppConfigRspBuilder { + tlvs: vec![], + status: StatusCode::UciStatusSessionNotExist, + } + .build(); + }; + + let (status, valid_parameters) = { + let mut valid_parameters = vec![]; + let mut invalid_parameters = vec![]; + for id in cmd.get_app_cfg() { + match session.app_config.get(*id) { + Ok(value) => valid_parameters.push(AppConfigTlv { + cfg_id: *id, + v: value, + }), + Err(_) => invalid_parameters.push(AppConfigTlv { + cfg_id: *id, + v: vec![], + }), + } + } + + if invalid_parameters.is_empty() { + (StatusCode::UciStatusOk, valid_parameters) + } else { + (StatusCode::UciStatusFailed, Vec::new()) + } + }; + + SessionGetAppConfigRspBuilder { + status, + tlvs: valid_parameters, + } + .build() + } + + fn command_session_get_state(&self, cmd: SessionGetStateCmd) -> SessionGetStateRsp { + let session_handle = cmd.get_session_token(); + + log::debug!("[{}:0x{:x}] Session Get State", self.handle, session_handle); + + let Some(session) = self.sessions.get(&session_handle) else { + return SessionGetStateRspBuilder { + session_state: SessionState::SessionStateInit, + status: StatusCode::UciStatusSessionNotExist, + } + .build(); + }; + + SessionGetStateRspBuilder { + status: StatusCode::UciStatusOk, + session_state: session.state, + } + .build() + } + + fn command_session_update_controller_multicast_list( + &mut self, + cmd: SessionUpdateControllerMulticastListCmd, + ) -> SessionUpdateControllerMulticastListRsp { + let session_handle = cmd.get_session_token(); + + log::debug!( + "[{}:0x{:x}] Session Update Controller Multicast List", + self.handle, + session_handle + ); + + let Some(session) = self.sessions.get_mut(&session_handle) else { + return SessionUpdateControllerMulticastListRspBuilder { + status: StatusCode::UciStatusSessionNotExist, + } + .build(); + }; + + if (session.state != SessionState::SessionStateActive + && session.state != SessionState::SessionStateIdle) + || session.app_config.device_type != Some(DeviceType::Controller) + || session.app_config.multi_node_mode != Some(MultiNodeMode::OneToMany) + { + return SessionUpdateControllerMulticastListRspBuilder { + status: StatusCode::UciStatusRejected, + } + .build(); + } + let action = cmd.get_action(); + let mut dst_addresses = session.app_config.dst_mac_address.clone(); + let new_controlees: Vec = match action { + UpdateMulticastListAction::AddControlee + | UpdateMulticastListAction::RemoveControlee => { + if let Ok(packet) = + SessionUpdateControllerMulticastListCmdPayload::parse(cmd.get_payload()) + { + packet + .controlees + .iter() + .map(|controlee| controlee.into()) + .collect() + } else { + return SessionUpdateControllerMulticastListRspBuilder { + status: StatusCode::UciStatusSyntaxError, + } + .build(); + } + } + UpdateMulticastListAction::AddControleeWithShortSubSessionKey => { + if let Ok(packet) = + SessionUpdateControllerMulticastListCmd_2_0_16_Byte_Payload::parse( + cmd.get_payload(), + ) + { + packet + .controlees + .iter() + .map(|controlee| controlee.into()) + .collect() + } else { + return SessionUpdateControllerMulticastListRspBuilder { + status: StatusCode::UciStatusSyntaxError, + } + .build(); + } + } + UpdateMulticastListAction::AddControleeWithExtendedSubSessionKey => { + if let Ok(packet) = + SessionUpdateControllerMulticastListCmd_2_0_32_Byte_Payload::parse( + cmd.get_payload(), + ) + { + packet + .controlees + .iter() + .map(|controlee| controlee.into()) + .collect() + } else { + return SessionUpdateControllerMulticastListRspBuilder { + status: StatusCode::UciStatusSyntaxError, + } + .build(); + } + } + }; + let mut controlee_status = Vec::new(); + let mut status = StatusCode::UciStatusOk; + + match action { + UpdateMulticastListAction::AddControlee + | UpdateMulticastListAction::AddControleeWithShortSubSessionKey + | UpdateMulticastListAction::AddControleeWithExtendedSubSessionKey => { + new_controlees.iter().for_each(|controlee| { + let mut update_status = MulticastUpdateStatusCode::StatusOkMulticastListUpdate; + if !dst_addresses.contains(&controlee.short_address) { + if dst_addresses.len() == MAX_NUMBER_OF_CONTROLEES { + status = StatusCode::UciStatusMulticastListFull; + update_status = MulticastUpdateStatusCode::StatusErrorMulticastListFull; + } else if (action + == UpdateMulticastListAction::AddControleeWithShortSubSessionKey + || action + == UpdateMulticastListAction::AddControleeWithExtendedSubSessionKey) + && session.app_config.sts_config + != uci::StsConfig::ProvisionedForResponderSubSessionKey + { + // If Action is 0x02 or 0x03 for STS_CONFIG values other than + // 0x04, the UWBS shall return SESSION_UPDATE_CONTROLLER_MULTICAST_LIST_NTF + // with Status set to STATUS_ERROR_SUB_SESSION_KEY_NOT_APPLICABLE for each + // Controlee in the Controlee List. + status = StatusCode::UciStatusFailed; + update_status = + MulticastUpdateStatusCode::StatusErrorSubSessionKeyNotApplicable; + } else { + dst_addresses.push(controlee.short_address); + }; + } + controlee_status.push(ControleeStatus { + mac_address: match controlee.short_address { + MacAddress::Short(address) => address, + MacAddress::Extended(_) => panic!("Extended address is not supported!"), + }, + subsession_id: controlee.sub_session_id, + status: update_status, + }); + }); + } + UpdateMulticastListAction::RemoveControlee => { + new_controlees.iter().for_each(|controlee: &Controlee| { + let pica_tx = self.pica_tx.clone(); + let address = controlee.short_address; + let attempt_count = session.app_config.in_band_termination_attempt_count; + let mut update_status = MulticastUpdateStatusCode::StatusOkMulticastListUpdate; + if !dst_addresses.contains(&address) { + status = StatusCode::UciStatusAddressNotFound; + update_status = MulticastUpdateStatusCode::StatusErrorKeyFetchFail; + } else { + dst_addresses.retain(|value| *value != address); + // If IN_BAND_TERMINATION_ATTEMPT_COUNT is not equal to 0x00, then the + // UWBS shall transmit the RCM with the “Stop Ranging” bit set to ‘1’ + // for IN_BAND_TERMINATION_ATTEMPT_COUNT times to the corresponding + // Controlee. + if attempt_count != 0 { + tokio::spawn(async move { + for _ in 0..attempt_count { + pica_tx + .send(PicaCommand::StopRanging(address, session_handle)) + .await + .unwrap() + } + }); + } + } + controlee_status.push(ControleeStatus { + mac_address: match address { + MacAddress::Short(addr) => addr, + MacAddress::Extended(_) => panic!("Extended address is not supported!"), + }, + subsession_id: controlee.sub_session_id, + status: update_status, + }); + }); + } + } + session.app_config.number_of_controlees = dst_addresses.len() as u8; + session.app_config.dst_mac_address = dst_addresses.clone(); + // If the multicast list becomes empty, the UWBS shall move the session to + // SESSION_STATE_IDLE by sending the SESSION_STATUS_NTF with Reason Code + // set to ERROR_INVALID_NUM_OF_CONTROLEES. + if session.app_config.dst_mac_address.is_empty() { + session.set_state( + SessionState::SessionStateIdle, + ReasonCode::ErrorInvalidNumOfControlees, + ) + } + let tx = self.tx.clone(); + tokio::spawn(async move { + tx.send( + SessionUpdateControllerMulticastListNtfBuilder { + controlee_status, + remaining_multicast_list_size: dst_addresses.len() as u8, + session_token: session_handle, + } + .build() + .into(), + ) + .unwrap() + }); + SessionUpdateControllerMulticastListRspBuilder { status }.build() + } + fn command_set_country_code( &mut self, cmd: AndroidSetCountryCodeCmd, @@ -448,79 +822,27 @@ impl Device { UciCommandChild::SessionConfigCommand(session_command) => { match session_command.specialize() { SessionConfigCommandChild::SessionInitCmd(cmd) => { - return self.command_session_init(cmd).into(); + self.command_session_init(cmd).into() } SessionConfigCommandChild::SessionDeinitCmd(cmd) => { - return self.command_session_deinit(cmd).into(); + self.command_session_deinit(cmd).into() } SessionConfigCommandChild::SessionGetCountCmd(cmd) => { - return self.command_session_get_count(cmd).into(); + self.command_session_get_count(cmd).into() } - _ => {} - } - - // Common code for retrieving the session_id in the command - let session_id = match session_command.specialize() { SessionConfigCommandChild::SessionSetAppConfigCmd(cmd) => { - cmd.get_session_token() + self.command_session_set_app_config(cmd).into() } SessionConfigCommandChild::SessionGetAppConfigCmd(cmd) => { - cmd.get_session_token() + self.command_session_get_app_config(cmd).into() } - SessionConfigCommandChild::SessionGetStateCmd(cmd) => cmd.get_session_token(), - SessionConfigCommandChild::SessionUpdateControllerMulticastListCmd(cmd) => { - cmd.get_session_token() - } - _ => panic!("Unsupported session command type"), - }; - - if let Some(session) = self.session_mut(session_id) { - // There is a session matching the session_id in the command - // Pass the command through - match session_command.specialize() { - SessionConfigCommandChild::SessionSetAppConfigCmd(_) - | SessionConfigCommandChild::SessionGetAppConfigCmd(_) - | SessionConfigCommandChild::SessionGetStateCmd(_) - | SessionConfigCommandChild::SessionUpdateControllerMulticastListCmd(_) => { - session.session_command(session_command).into() - } - _ => panic!("Unsupported session command"), - } - } else { - // There is no session matching the session_id in the command - let status = StatusCode::UciStatusSessionNotExist; - match session_command.specialize() { - SessionConfigCommandChild::SessionSetAppConfigCmd(_) => { - SessionSetAppConfigRspBuilder { - cfg_status: Vec::new(), - status, - } - .build() - .into() - } - SessionConfigCommandChild::SessionGetAppConfigCmd(_) => { - SessionGetAppConfigRspBuilder { - status, - tlvs: Vec::new(), - } - .build() - .into() - } - SessionConfigCommandChild::SessionGetStateCmd(_) => { - SessionGetStateRspBuilder { - status, - session_state: SessionState::SessionStateDeinit, - } - .build() - .into() - } - SessionConfigCommandChild::SessionUpdateControllerMulticastListCmd(_) => { - SessionUpdateControllerMulticastListRspBuilder { status } - .build() - .into() - } - _ => panic!("Unsupported session command"), + SessionConfigCommandChild::SessionGetStateCmd(cmd) => { + self.command_session_get_state(cmd).into() } + SessionConfigCommandChild::SessionUpdateControllerMulticastListCmd(cmd) => self + .command_session_update_controller_multicast_list(cmd) + .into(), + _ => panic!("Unsupported session command"), } } UciCommandChild::SessionControlCommand(ranging_command) => { @@ -672,3 +994,40 @@ impl Device { } } } + +struct Controlee { + short_address: MacAddress, + sub_session_id: u32, + #[allow(dead_code)] + session_key: SubSessionKey, +} + +impl From<&uci::Controlee> for Controlee { + fn from(value: &uci::Controlee) -> Self { + Controlee { + short_address: MacAddress::Short(value.short_address), + sub_session_id: value.subsession_id, + session_key: SubSessionKey::None, + } + } +} + +impl From<&uci::Controlee_V2_0_16_Byte_Version> for Controlee { + fn from(value: &uci::Controlee_V2_0_16_Byte_Version) -> Self { + Controlee { + short_address: MacAddress::Short(value.short_address), + sub_session_id: value.subsession_id, + session_key: SubSessionKey::Short(value.subsession_key), + } + } +} + +impl From<&uci::Controlee_V2_0_32_Byte_Version> for Controlee { + fn from(value: &uci::Controlee_V2_0_32_Byte_Version) -> Self { + Controlee { + short_address: MacAddress::Short(value.short_address), + sub_session_id: value.subsession_id, + session_key: SubSessionKey::Extended(value.subsession_key), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 25a09d0..a11fe75 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,10 +28,9 @@ use packets::uci::StatusCode as UciStatusCode; use packets::uci::*; mod device; -use device::{Device, MAX_DEVICE}; +use device::{Device, MAX_DEVICE, MAX_SESSION}; mod session; -use session::MAX_SESSION; mod mac_address; pub use mac_address::MacAddress; diff --git a/src/session.rs b/src/session.rs index a7e2d44..8a326f5 100644 --- a/src/session.rs +++ b/src/session.rs @@ -24,50 +24,8 @@ use tokio::sync::mpsc; use tokio::task::JoinHandle; use tokio::time; -use super::app_config::SubSessionKey; use super::UciPacket; -pub const MAX_SESSION: usize = 255; -/// cf. [UCI] 8.3 Table 29 -pub const MAX_NUMBER_OF_CONTROLEES: usize = 8; - -struct Controlee { - short_address: MacAddress, - sub_session_id: u32, - #[allow(dead_code)] - session_key: SubSessionKey, -} - -impl From<&uci::Controlee> for Controlee { - fn from(value: &uci::Controlee) -> Self { - Controlee { - short_address: MacAddress::Short(value.short_address), - sub_session_id: value.subsession_id, - session_key: SubSessionKey::None, - } - } -} - -impl From<&uci::Controlee_V2_0_16_Byte_Version> for Controlee { - fn from(value: &uci::Controlee_V2_0_16_Byte_Version) -> Self { - Controlee { - short_address: MacAddress::Short(value.short_address), - sub_session_id: value.subsession_id, - session_key: SubSessionKey::Short(value.subsession_key), - } - } -} - -impl From<&uci::Controlee_V2_0_32_Byte_Version> for Controlee { - fn from(value: &uci::Controlee_V2_0_32_Byte_Version) -> Self { - Controlee { - short_address: MacAddress::Short(value.short_address), - sub_session_id: value.subsession_id, - session_key: SubSessionKey::Extended(value.subsession_key), - } - } -} - pub struct Session { /// cf. [UCI] 7.1 pub state: SessionState, @@ -76,7 +34,7 @@ pub struct Session { device_handle: usize, data: BytesMut, - session_type: SessionType, + pub session_type: SessionType, pub sequence_number: u32, pub app_config: AppConfig, ranging_task: Option>, @@ -168,335 +126,6 @@ impl Session { ); } - fn command_set_app_config(&mut self, cmd: SessionSetAppConfigCmd) -> SessionSetAppConfigRsp { - // TODO properly handle these asserts - log::debug!( - "[{}:0x{:x}] Session Set App Config", - self.device_handle, - self.id - ); - assert_eq!(self.id, cmd.get_session_token()); - assert!( - self.session_type.eq(&SessionType::FiraRangingSession) - || self - .session_type - .eq(&SessionType::FiraRangingAndInBandDataSession) - ); - - if self.state == SessionState::SessionStateActive { - const IMMUTABLE_PARAMETERS: &[AppConfigTlvType] = &[AppConfigTlvType::AoaResultReq]; - if cmd - .get_tlvs() - .iter() - .any(|cfg| IMMUTABLE_PARAMETERS.contains(&cfg.cfg_id)) - { - return SessionSetAppConfigRspBuilder { - status: StatusCode::UciStatusSessionActive, - cfg_status: vec![], - } - .build(); - } - } - - let (status, invalid_parameters) = if self.state != SessionState::SessionStateInit - && self.state != SessionState::SessionStateActive - { - (StatusCode::UciStatusRejected, Vec::new()) - } else { - let mut app_config = self.app_config.clone(); - let mut invalid_parameters = vec![]; - for cfg in cmd.get_tlvs() { - match app_config.set(cfg.cfg_id, &cfg.v) { - Ok(_) => (), - Err(_) => invalid_parameters.push(AppConfigStatus { - cfg_id: cfg.cfg_id, - status: uci::StatusCode::UciStatusInvalidParam, - }), - } - } - - // [UCI] 7.5.1 Configuration of a Session - // This section defines the mandatory APP Configuration Parameters to be applied - // by the Host for FiRa defined UWB Session types. The Host shall apply these - // mandatory configurations to move the Session State from SESSION_STATE_INIT - // to SESSION_STATE_IDLE. - // - // - DEVICE_ROLE - // - MULTI_NODE_MODE - // - RANGING_ROUND_USAGE - // - DEVICE_MAC_ADDRESS - // - DEVICE_TYPE (see Note1) - // - SCHEDULE_MODE - if app_config.device_role.is_none() - || app_config.multi_node_mode.is_none() - || app_config.ranging_round_usage.is_none() - || app_config.device_mac_address.is_none() - || app_config.schedule_mode.is_none() - { - log::error!( - "[{}:0x{:x}] missing mandatory APP config parameters", - self.device_handle, - self.id - ); - return SessionSetAppConfigRspBuilder { - status: uci::StatusCode::UciStatusRejected, - cfg_status: vec![], - } - .build(); - } - - if invalid_parameters.is_empty() { - self.app_config = app_config; - if self.state == SessionState::SessionStateInit { - self.set_state( - SessionState::SessionStateIdle, - ReasonCode::StateChangeWithSessionManagementCommands, - ); - } - (StatusCode::UciStatusOk, invalid_parameters) - } else { - (StatusCode::UciStatusInvalidParam, invalid_parameters) - } - }; - - SessionSetAppConfigRspBuilder { - status, - cfg_status: invalid_parameters, - } - .build() - } - - fn command_get_app_config(&self, cmd: SessionGetAppConfigCmd) -> SessionGetAppConfigRsp { - log::debug!( - "[{}:0x{:x}] Session Get App Config", - self.device_handle, - self.id - ); - assert_eq!(self.id, cmd.get_session_token()); - - let (status, valid_parameters) = { - let mut valid_parameters = vec![]; - let mut invalid_parameters = vec![]; - for id in cmd.get_app_cfg() { - match self.app_config.get(*id) { - Ok(value) => valid_parameters.push(AppConfigTlv { - cfg_id: *id, - v: value, - }), - Err(_) => invalid_parameters.push(AppConfigTlv { - cfg_id: *id, - v: vec![], - }), - } - } - - if invalid_parameters.is_empty() { - (StatusCode::UciStatusOk, valid_parameters) - } else { - (StatusCode::UciStatusFailed, Vec::new()) - } - }; - SessionGetAppConfigRspBuilder { - status, - tlvs: valid_parameters, - } - .build() - } - - fn command_get_state(&self, cmd: SessionGetStateCmd) -> SessionGetStateRsp { - log::debug!("[{}:0x{:x}] Session Get State", self.device_handle, self.id); - assert_eq!(self.id, cmd.get_session_token()); - SessionGetStateRspBuilder { - status: StatusCode::UciStatusOk, - session_state: self.state, - } - .build() - } - - fn command_update_controller_multicast_list( - &mut self, - cmd: SessionUpdateControllerMulticastListCmd, - ) -> SessionUpdateControllerMulticastListRsp { - log::debug!( - "[{}:0x{:x}] Session Update Controller Multicast List", - self.device_handle, - self.id - ); - assert_eq!(self.id, cmd.get_session_token()); - if (self.state != SessionState::SessionStateActive - && self.state != SessionState::SessionStateIdle) - || self.app_config.device_type != Some(DeviceType::Controller) - || self.app_config.multi_node_mode != Some(MultiNodeMode::OneToMany) - { - return SessionUpdateControllerMulticastListRspBuilder { - status: StatusCode::UciStatusRejected, - } - .build(); - } - let action = cmd.get_action(); - let mut dst_addresses = self.app_config.dst_mac_address.clone(); - let new_controlees: Vec = match action { - UpdateMulticastListAction::AddControlee - | UpdateMulticastListAction::RemoveControlee => { - if let Ok(packet) = - SessionUpdateControllerMulticastListCmdPayload::parse(cmd.get_payload()) - { - packet - .controlees - .iter() - .map(|controlee| controlee.into()) - .collect() - } else { - return SessionUpdateControllerMulticastListRspBuilder { - status: StatusCode::UciStatusSyntaxError, - } - .build(); - } - } - UpdateMulticastListAction::AddControleeWithShortSubSessionKey => { - if let Ok(packet) = - SessionUpdateControllerMulticastListCmd_2_0_16_Byte_Payload::parse( - cmd.get_payload(), - ) - { - packet - .controlees - .iter() - .map(|controlee| controlee.into()) - .collect() - } else { - return SessionUpdateControllerMulticastListRspBuilder { - status: StatusCode::UciStatusSyntaxError, - } - .build(); - } - } - UpdateMulticastListAction::AddControleeWithExtendedSubSessionKey => { - if let Ok(packet) = - SessionUpdateControllerMulticastListCmd_2_0_32_Byte_Payload::parse( - cmd.get_payload(), - ) - { - packet - .controlees - .iter() - .map(|controlee| controlee.into()) - .collect() - } else { - return SessionUpdateControllerMulticastListRspBuilder { - status: StatusCode::UciStatusSyntaxError, - } - .build(); - } - } - }; - let mut controlee_status = Vec::new(); - - let session_id = self.id; - let mut status = StatusCode::UciStatusOk; - - match action { - UpdateMulticastListAction::AddControlee - | UpdateMulticastListAction::AddControleeWithShortSubSessionKey - | UpdateMulticastListAction::AddControleeWithExtendedSubSessionKey => { - new_controlees.iter().for_each(|controlee| { - let mut update_status = MulticastUpdateStatusCode::StatusOkMulticastListUpdate; - if !dst_addresses.contains(&controlee.short_address) { - if dst_addresses.len() == MAX_NUMBER_OF_CONTROLEES { - status = StatusCode::UciStatusMulticastListFull; - update_status = MulticastUpdateStatusCode::StatusErrorMulticastListFull; - } else if (action - == UpdateMulticastListAction::AddControleeWithShortSubSessionKey - || action - == UpdateMulticastListAction::AddControleeWithExtendedSubSessionKey) - && self.app_config.sts_config - != uci::StsConfig::ProvisionedForResponderSubSessionKey - { - // If Action is 0x02 or 0x03 for STS_CONFIG values other than - // 0x04, the UWBS shall return SESSION_UPDATE_CONTROLLER_MULTICAST_LIST_NTF - // with Status set to STATUS_ERROR_SUB_SESSION_KEY_NOT_APPLICABLE for each - // Controlee in the Controlee List. - status = StatusCode::UciStatusFailed; - update_status = - MulticastUpdateStatusCode::StatusErrorSubSessionKeyNotApplicable; - } else { - dst_addresses.push(controlee.short_address); - }; - } - controlee_status.push(ControleeStatus { - mac_address: match controlee.short_address { - MacAddress::Short(address) => address, - MacAddress::Extended(_) => panic!("Extended address is not supported!"), - }, - subsession_id: controlee.sub_session_id, - status: update_status, - }); - }); - } - UpdateMulticastListAction::RemoveControlee => { - new_controlees.iter().for_each(|controlee: &Controlee| { - let pica_tx = self.pica_tx.clone(); - let address = controlee.short_address; - let attempt_count = self.app_config.in_band_termination_attempt_count; - let mut update_status = MulticastUpdateStatusCode::StatusOkMulticastListUpdate; - if !dst_addresses.contains(&address) { - status = StatusCode::UciStatusAddressNotFound; - update_status = MulticastUpdateStatusCode::StatusErrorKeyFetchFail; - } else { - dst_addresses.retain(|value| *value != address); - // If IN_BAND_TERMINATION_ATTEMPT_COUNT is not equal to 0x00, then the - // UWBS shall transmit the RCM with the “Stop Ranging” bit set to ‘1’ - // for IN_BAND_TERMINATION_ATTEMPT_COUNT times to the corresponding - // Controlee. - if attempt_count != 0 { - tokio::spawn(async move { - for _ in 0..attempt_count { - pica_tx - .send(PicaCommand::StopRanging(address, session_id)) - .await - .unwrap() - } - }); - } - } - controlee_status.push(ControleeStatus { - mac_address: match address { - MacAddress::Short(addr) => addr, - MacAddress::Extended(_) => panic!("Extended address is not supported!"), - }, - subsession_id: controlee.sub_session_id, - status: update_status, - }); - }); - } - } - self.app_config.number_of_controlees = dst_addresses.len() as u8; - self.app_config.dst_mac_address = dst_addresses.clone(); - // If the multicast list becomes empty, the UWBS shall move the session to - // SESSION_STATE_IDLE by sending the SESSION_STATUS_NTF with Reason Code - // set to ERROR_INVALID_NUM_OF_CONTROLEES. - if self.app_config.dst_mac_address.is_empty() { - self.set_state( - SessionState::SessionStateIdle, - ReasonCode::ErrorInvalidNumOfControlees, - ) - } - let tx = self.tx.clone(); - tokio::spawn(async move { - tx.send( - SessionUpdateControllerMulticastListNtfBuilder { - controlee_status, - remaining_multicast_list_size: dst_addresses.len() as u8, - session_token: session_id, - } - .build() - .into(), - ) - .unwrap() - }); - SessionUpdateControllerMulticastListRspBuilder { status }.build() - } - fn command_range_start(&mut self, cmd: SessionStartCmd) -> SessionStartRsp { log::debug!("[{}:0x{:x}] Range Start", self.device_handle, self.id); assert_eq!(self.id, cmd.get_session_id()); @@ -570,24 +199,6 @@ impl Session { .build() } - pub fn session_command(&mut self, cmd: SessionConfigCommand) -> SessionConfigResponse { - match cmd.specialize() { - SessionConfigCommandChild::SessionSetAppConfigCmd(cmd) => { - self.command_set_app_config(cmd).into() - } - SessionConfigCommandChild::SessionGetAppConfigCmd(cmd) => { - self.command_get_app_config(cmd).into() - } - SessionConfigCommandChild::SessionGetStateCmd(cmd) => { - self.command_get_state(cmd).into() - } - SessionConfigCommandChild::SessionUpdateControllerMulticastListCmd(cmd) => { - self.command_update_controller_multicast_list(cmd).into() - } - _ => panic!("Unsupported session command"), - } - } - pub fn ranging_command(&mut self, cmd: SessionControlCommand) -> SessionControlResponse { match cmd.specialize() { SessionControlCommandChild::SessionStartCmd(cmd) => { -- cgit v1.2.3 From ce187f305f68dae34128def4b101af11eadd2c2d Mon Sep 17 00:00:00 2001 From: Henri Chataing Date: Mon, 18 Mar 2024 11:14:27 -0700 Subject: Move all session control command implementations from session to device Previous implementation stretched some way to be able to report invalid session ids, this format makes it easier to read and understand each command implementation individually. --- src/device.rs | 222 ++++++++++++++++++++++++++++++++++++++------------------- src/session.rs | 86 +--------------------- 2 files changed, 150 insertions(+), 158 deletions(-) diff --git a/src/device.rs b/src/device.rs index 483bacd..363c081 100644 --- a/src/device.rs +++ b/src/device.rs @@ -317,13 +317,7 @@ impl Device { } else { match self.sessions.insert( session_id, - Session::new( - session_id, - session_type, - self.handle, - self.tx.clone(), - self.pica_tx.clone(), - ), + Session::new(session_id, session_type, self.handle, self.tx.clone()), ) { Some(_) => StatusCode::UciStatusSessionDuplicate, None => { @@ -737,6 +731,118 @@ impl Device { SessionUpdateControllerMulticastListRspBuilder { status }.build() } + fn command_session_start(&mut self, cmd: SessionStartCmd) -> SessionStartRsp { + let session_id = cmd.get_session_id(); + + log::debug!("[{}:0x{:x}] Session Start", self.handle, session_id); + + let Some(session) = self.sessions.get_mut(&session_id) else { + return SessionStartRspBuilder { + status: StatusCode::UciStatusSessionNotExist, + } + .build(); + }; + + if session.state != SessionState::SessionStateIdle { + return SessionStartRspBuilder { + status: StatusCode::UciStatusSessionNotConfigured, + } + .build(); + } + + assert!(session.ranging_task.is_none()); + + let ranging_interval = + time::Duration::from_millis(session.app_config.ranging_duration as u64); + + let tx = self.pica_tx.clone(); + let handle = self.handle; + session.ranging_task = Some(tokio::spawn(async move { + loop { + time::sleep(ranging_interval).await; + tx.send(PicaCommand::Ranging(handle, session_id)) + .await + .unwrap(); + } + })); + + session.set_state( + SessionState::SessionStateActive, + ReasonCode::StateChangeWithSessionManagementCommands, + ); + + self.n_active_sessions += 1; + self.set_state(DeviceState::DeviceStateActive); + + SessionStartRspBuilder { + status: StatusCode::UciStatusOk, + } + .build() + } + + fn command_session_stop(&mut self, cmd: SessionStopCmd) -> SessionStopRsp { + let session_id = cmd.get_session_id(); + + log::debug!("[{}:0x{:x}] Session Stop", self.handle, session_id); + + let Some(session) = self.sessions.get_mut(&session_id) else { + return SessionStopRspBuilder { + status: StatusCode::UciStatusSessionNotExist, + } + .build(); + }; + + if session.state != SessionState::SessionStateActive { + return SessionStopRspBuilder { + status: StatusCode::UciStatusSessionActive, + } + .build(); + } + + session.stop_ranging_task(); + session.set_state( + SessionState::SessionStateIdle, + ReasonCode::StateChangeWithSessionManagementCommands, + ); + + self.n_active_sessions -= 1; + if self.n_active_sessions == 0 { + self.set_state(DeviceState::DeviceStateReady); + } + + SessionStopRspBuilder { + status: StatusCode::UciStatusOk, + } + .build() + } + + fn command_session_get_ranging_count( + &self, + cmd: SessionGetRangingCountCmd, + ) -> SessionGetRangingCountRsp { + let session_id = cmd.get_session_id(); + + log::debug!( + "[{}:0x{:x}] Session Get Ranging Count", + self.handle, + session_id + ); + + let Some(session) = self.sessions.get(&session_id) else { + return SessionGetRangingCountRspBuilder { + status: StatusCode::UciStatusSessionNotExist, + count: 0, + } + .build(); + }; + + SessionGetRangingCountRspBuilder { + status: StatusCode::UciStatusOk, + count: session.sequence_number, + } + .build() + } + fn command_set_country_code( &mut self, cmd: AndroidSetCountryCodeCmd, @@ -810,7 +916,7 @@ impl Device { fn receive_command(&mut self, cmd: UciCommand) -> UciResponse { match cmd.specialize() { // Handle commands for this device - UciCommandChild::CoreCommand(core_command) => match core_command.specialize() { + UciCommandChild::CoreCommand(cmd) => match cmd.specialize() { CoreCommandChild::DeviceResetCmd(cmd) => self.command_device_reset(cmd).into(), CoreCommandChild::GetDeviceInfoCmd(cmd) => self.command_get_device_info(cmd).into(), CoreCommandChild::GetCapsInfoCmd(cmd) => self.command_get_caps_info(cmd).into(), @@ -819,74 +925,42 @@ impl Device { _ => panic!("Unsupported core command"), }, // Handle commands for session management - UciCommandChild::SessionConfigCommand(session_command) => { - match session_command.specialize() { - SessionConfigCommandChild::SessionInitCmd(cmd) => { - self.command_session_init(cmd).into() - } - SessionConfigCommandChild::SessionDeinitCmd(cmd) => { - self.command_session_deinit(cmd).into() - } - SessionConfigCommandChild::SessionGetCountCmd(cmd) => { - self.command_session_get_count(cmd).into() - } - SessionConfigCommandChild::SessionSetAppConfigCmd(cmd) => { - self.command_session_set_app_config(cmd).into() - } - SessionConfigCommandChild::SessionGetAppConfigCmd(cmd) => { - self.command_session_get_app_config(cmd).into() - } - SessionConfigCommandChild::SessionGetStateCmd(cmd) => { - self.command_session_get_state(cmd).into() - } - SessionConfigCommandChild::SessionUpdateControllerMulticastListCmd(cmd) => self - .command_session_update_controller_multicast_list(cmd) - .into(), - _ => panic!("Unsupported session command"), + UciCommandChild::SessionConfigCommand(cmd) => match cmd.specialize() { + SessionConfigCommandChild::SessionInitCmd(cmd) => { + self.command_session_init(cmd).into() } - } - UciCommandChild::SessionControlCommand(ranging_command) => { - let session_id = ranging_command.get_session_id(); - if let Some(session) = self.session_mut(session_id) { - // Forward to the proper session - let response = session.ranging_command(ranging_command); - match response.specialize() { - SessionControlResponseChild::SessionStartRsp(rsp) - if rsp.get_status() == StatusCode::UciStatusOk => - { - self.n_active_sessions += 1; - self.set_state(DeviceState::DeviceStateActive); - } - SessionControlResponseChild::SessionStopRsp(rsp) - if rsp.get_status() == StatusCode::UciStatusOk => - { - assert!(self.n_active_sessions > 0); - self.n_active_sessions -= 1; - if self.n_active_sessions == 0 { - self.set_state(DeviceState::DeviceStateReady); - } - } - _ => {} - } - response.into() - } else { - let status = StatusCode::UciStatusSessionNotExist; - match ranging_command.specialize() { - SessionControlCommandChild::SessionStartCmd(_) => { - SessionStartRspBuilder { status }.build().into() - } - SessionControlCommandChild::SessionStopCmd(_) => { - SessionStopRspBuilder { status }.build().into() - } - SessionControlCommandChild::SessionGetRangingCountCmd(_) => { - SessionGetRangingCountRspBuilder { status, count: 0 } - .build() - .into() - } - _ => panic!("Unsupported ranging command"), - } + SessionConfigCommandChild::SessionDeinitCmd(cmd) => { + self.command_session_deinit(cmd).into() } - } + SessionConfigCommandChild::SessionGetCountCmd(cmd) => { + self.command_session_get_count(cmd).into() + } + SessionConfigCommandChild::SessionSetAppConfigCmd(cmd) => { + self.command_session_set_app_config(cmd).into() + } + SessionConfigCommandChild::SessionGetAppConfigCmd(cmd) => { + self.command_session_get_app_config(cmd).into() + } + SessionConfigCommandChild::SessionGetStateCmd(cmd) => { + self.command_session_get_state(cmd).into() + } + SessionConfigCommandChild::SessionUpdateControllerMulticastListCmd(cmd) => self + .command_session_update_controller_multicast_list(cmd) + .into(), + _ => panic!("Unsupported session config command"), + }, + UciCommandChild::SessionControlCommand(cmd) => match cmd.specialize() { + SessionControlCommandChild::SessionStartCmd(cmd) => { + self.command_session_start(cmd).into() + } + SessionControlCommandChild::SessionStopCmd(cmd) => { + self.command_session_stop(cmd).into() + } + SessionControlCommandChild::SessionGetRangingCountCmd(cmd) => { + self.command_session_get_ranging_count(cmd).into() + } + _ => panic!("Unsupported session control command"), + }, UciCommandChild::AndroidCommand(android_command) => { match android_command.specialize() { diff --git a/src/session.rs b/src/session.rs index 8a326f5..4a67593 100644 --- a/src/session.rs +++ b/src/session.rs @@ -17,7 +17,7 @@ //! - [UCI] FiRa Consortium UWB Command Interface Generic Technical specification use crate::packets::uci::{self, *}; -use crate::{AppConfig, MacAddress, PicaCommand}; +use crate::{AppConfig, MacAddress}; use bytes::BytesMut; use std::time::Duration; use tokio::sync::mpsc; @@ -37,9 +37,8 @@ pub struct Session { pub session_type: SessionType, pub sequence_number: u32, pub app_config: AppConfig, - ranging_task: Option>, + pub ranging_task: Option>, tx: mpsc::UnboundedSender, - pica_tx: mpsc::Sender, } impl Session { @@ -48,7 +47,6 @@ impl Session { session_type: SessionType, device_handle: usize, tx: mpsc::UnboundedSender, - pica_tx: mpsc::Sender, ) -> Self { Self { state: SessionState::SessionStateDeinit, @@ -60,7 +58,6 @@ impl Session { app_config: AppConfig::default(), ranging_task: None, tx, - pica_tx, } } @@ -126,91 +123,12 @@ impl Session { ); } - fn command_range_start(&mut self, cmd: SessionStartCmd) -> SessionStartRsp { - log::debug!("[{}:0x{:x}] Range Start", self.device_handle, self.id); - assert_eq!(self.id, cmd.get_session_id()); - - let status = if self.state != SessionState::SessionStateIdle { - StatusCode::UciStatusSessionNotConfigured - } else { - assert!(self.ranging_task.is_none()); - assert_eq!(self.state, SessionState::SessionStateIdle); - - let session_id = self.id; - let ranging_interval = - time::Duration::from_millis(self.app_config.ranging_duration as u64); - let device_handle = self.device_handle; - let tx = self.pica_tx.clone(); - self.ranging_task = Some(tokio::spawn(async move { - loop { - time::sleep(ranging_interval).await; - tx.send(PicaCommand::Ranging(device_handle, session_id)) - .await - .unwrap(); - } - })); - self.set_state( - SessionState::SessionStateActive, - ReasonCode::StateChangeWithSessionManagementCommands, - ); - StatusCode::UciStatusOk - }; - SessionStartRspBuilder { status }.build() - } - pub fn stop_ranging_task(&mut self) { if let Some(handle) = &self.ranging_task { handle.abort(); self.ranging_task = None; } } - fn command_range_stop(&mut self, cmd: SessionStopCmd) -> SessionStopRsp { - log::debug!("[{}:0x{:x}] Range Stop", self.device_handle, self.id); - assert_eq!(self.id, cmd.get_session_id()); - - let status = if self.state != SessionState::SessionStateActive { - StatusCode::UciStatusSessionActive - } else { - self.stop_ranging_task(); - self.set_state( - SessionState::SessionStateIdle, - ReasonCode::StateChangeWithSessionManagementCommands, - ); - StatusCode::UciStatusOk - }; - SessionStopRspBuilder { status }.build() - } - - fn command_get_ranging_count( - &self, - cmd: SessionGetRangingCountCmd, - ) -> SessionGetRangingCountRsp { - log::debug!( - "[{}:0x{:x}] Range Get Ranging Count", - self.device_handle, - self.id - ); - assert_eq!(self.id, cmd.get_session_id()); - - SessionGetRangingCountRspBuilder { - status: StatusCode::UciStatusOk, - count: self.sequence_number, - } - .build() - } - - pub fn ranging_command(&mut self, cmd: SessionControlCommand) -> SessionControlResponse { - match cmd.specialize() { - SessionControlCommandChild::SessionStartCmd(cmd) => { - self.command_range_start(cmd).into() - } - SessionControlCommandChild::SessionStopCmd(cmd) => self.command_range_stop(cmd).into(), - SessionControlCommandChild::SessionGetRangingCountCmd(cmd) => { - self.command_get_ranging_count(cmd).into() - } - _ => panic!("Unsupported ranging command"), - } - } pub fn data_message_snd(&mut self, data: DataMessageSnd) -> SessionControlNotification { log::debug!("[{}] data_message_snd", self.device_handle); -- cgit v1.2.3 From 8775b85020cef370652bf4ac7e20833b22c946e9 Mon Sep 17 00:00:00 2001 From: Henri Chataing Date: Mon, 18 Mar 2024 15:12:28 -0700 Subject: Minify the command matching block --- src/device.rs | 95 ++++++++++++++++++++++++----------------------------------- 1 file changed, 38 insertions(+), 57 deletions(-) diff --git a/src/device.rs b/src/device.rs index 363c081..bf74a98 100644 --- a/src/device.rs +++ b/src/device.rs @@ -914,90 +914,71 @@ impl Device { } fn receive_command(&mut self, cmd: UciCommand) -> UciResponse { + use AndroidCommandChild::*; + use CoreCommandChild::*; + use SessionConfigCommandChild::*; + use SessionControlCommandChild::*; + use UciCommandChild::*; + match cmd.specialize() { - // Handle commands for this device - UciCommandChild::CoreCommand(cmd) => match cmd.specialize() { - CoreCommandChild::DeviceResetCmd(cmd) => self.command_device_reset(cmd).into(), - CoreCommandChild::GetDeviceInfoCmd(cmd) => self.command_get_device_info(cmd).into(), - CoreCommandChild::GetCapsInfoCmd(cmd) => self.command_get_caps_info(cmd).into(), - CoreCommandChild::SetConfigCmd(cmd) => self.command_set_config(cmd).into(), - CoreCommandChild::GetConfigCmd(cmd) => self.command_get_config(cmd).into(), - _ => panic!("Unsupported core command"), + CoreCommand(cmd) => match cmd.specialize() { + DeviceResetCmd(cmd) => self.command_device_reset(cmd).into(), + GetDeviceInfoCmd(cmd) => self.command_get_device_info(cmd).into(), + GetCapsInfoCmd(cmd) => self.command_get_caps_info(cmd).into(), + SetConfigCmd(cmd) => self.command_set_config(cmd).into(), + GetConfigCmd(cmd) => self.command_get_config(cmd).into(), + _ => panic!("Unsupported Core command"), }, - // Handle commands for session management - UciCommandChild::SessionConfigCommand(cmd) => match cmd.specialize() { - SessionConfigCommandChild::SessionInitCmd(cmd) => { - self.command_session_init(cmd).into() - } - SessionConfigCommandChild::SessionDeinitCmd(cmd) => { - self.command_session_deinit(cmd).into() - } - SessionConfigCommandChild::SessionGetCountCmd(cmd) => { - self.command_session_get_count(cmd).into() - } - SessionConfigCommandChild::SessionSetAppConfigCmd(cmd) => { - self.command_session_set_app_config(cmd).into() - } - SessionConfigCommandChild::SessionGetAppConfigCmd(cmd) => { - self.command_session_get_app_config(cmd).into() - } - SessionConfigCommandChild::SessionGetStateCmd(cmd) => { - self.command_session_get_state(cmd).into() - } - SessionConfigCommandChild::SessionUpdateControllerMulticastListCmd(cmd) => self + SessionConfigCommand(cmd) => match cmd.specialize() { + SessionInitCmd(cmd) => self.command_session_init(cmd).into(), + SessionDeinitCmd(cmd) => self.command_session_deinit(cmd).into(), + SessionGetCountCmd(cmd) => self.command_session_get_count(cmd).into(), + SessionSetAppConfigCmd(cmd) => self.command_session_set_app_config(cmd).into(), + SessionGetAppConfigCmd(cmd) => self.command_session_get_app_config(cmd).into(), + SessionGetStateCmd(cmd) => self.command_session_get_state(cmd).into(), + SessionUpdateControllerMulticastListCmd(cmd) => self .command_session_update_controller_multicast_list(cmd) .into(), - _ => panic!("Unsupported session config command"), + _ => panic!("Unsupported Session Config command"), }, - UciCommandChild::SessionControlCommand(cmd) => match cmd.specialize() { - SessionControlCommandChild::SessionStartCmd(cmd) => { - self.command_session_start(cmd).into() - } - SessionControlCommandChild::SessionStopCmd(cmd) => { - self.command_session_stop(cmd).into() - } - SessionControlCommandChild::SessionGetRangingCountCmd(cmd) => { + SessionControlCommand(cmd) => match cmd.specialize() { + SessionStartCmd(cmd) => self.command_session_start(cmd).into(), + SessionStopCmd(cmd) => self.command_session_stop(cmd).into(), + SessionGetRangingCountCmd(cmd) => { self.command_session_get_ranging_count(cmd).into() } - _ => panic!("Unsupported session control command"), + _ => panic!("Unsupported Session Control command"), }, - - UciCommandChild::AndroidCommand(android_command) => { - match android_command.specialize() { - AndroidCommandChild::AndroidSetCountryCodeCmd(cmd) => { - self.command_set_country_code(cmd).into() - } - AndroidCommandChild::AndroidGetPowerStatsCmd(cmd) => { - self.command_get_power_stats(cmd).into() - } - _ => panic!("Unsupported Android command"), - } - } - UciCommandChild::UciVendor_9_Command(vendor_command) => UciVendor_9_ResponseBuilder { + AndroidCommand(cmd) => match cmd.specialize() { + AndroidSetCountryCodeCmd(cmd) => self.command_set_country_code(cmd).into(), + AndroidGetPowerStatsCmd(cmd) => self.command_get_power_stats(cmd).into(), + _ => panic!("Unsupported Android command"), + }, + UciVendor_9_Command(vendor_command) => UciVendor_9_ResponseBuilder { opcode: vendor_command.get_opcode(), payload: Some(vec![u8::from(StatusCode::UciStatusRejected)].into()), } .build() .into(), - UciCommandChild::UciVendor_A_Command(vendor_command) => UciVendor_A_ResponseBuilder { + UciVendor_A_Command(vendor_command) => UciVendor_A_ResponseBuilder { opcode: vendor_command.get_opcode(), payload: Some(vec![u8::from(StatusCode::UciStatusRejected)].into()), } .build() .into(), - UciCommandChild::UciVendor_B_Command(vendor_command) => UciVendor_B_ResponseBuilder { + UciVendor_B_Command(vendor_command) => UciVendor_B_ResponseBuilder { opcode: vendor_command.get_opcode(), payload: Some(vec![u8::from(StatusCode::UciStatusRejected)].into()), } .build() .into(), - UciCommandChild::UciVendor_E_Command(vendor_command) => UciVendor_E_ResponseBuilder { + UciVendor_E_Command(vendor_command) => UciVendor_E_ResponseBuilder { opcode: vendor_command.get_opcode(), payload: Some(vec![u8::from(StatusCode::UciStatusRejected)].into()), } .build() .into(), - UciCommandChild::UciVendor_F_Command(vendor_command) => UciVendor_F_ResponseBuilder { + UciVendor_F_Command(vendor_command) => UciVendor_F_ResponseBuilder { opcode: vendor_command.get_opcode(), payload: Some(vec![u8::from(StatusCode::UciStatusRejected)].into()), } @@ -1007,7 +988,7 @@ impl Device { _ => UciResponseBuilder { gid: GroupId::Core, opcode: 0, - payload: None, + payload: Option::None, } .build(), } -- cgit v1.2.3 From da707e73dd6bf617222f765bd3e6f92d50801d30 Mon Sep 17 00:00:00 2001 From: Henri Chataing Date: Mon, 18 Mar 2024 15:17:43 -0700 Subject: Rename command handlers according to their group --- src/device.rs | 94 +++++++++++++++++++++++++++++------------------------------ 1 file changed, 46 insertions(+), 48 deletions(-) diff --git a/src/device.rs b/src/device.rs index bf74a98..c255cc4 100644 --- a/src/device.rs +++ b/src/device.rs @@ -180,7 +180,7 @@ impl Device { // The fira norm specify to send a response, then reset, then // send a notification once the reset is done - fn command_device_reset(&mut self, cmd: DeviceResetCmd) -> DeviceResetRsp { + fn core_device_reset(&mut self, cmd: DeviceResetCmd) -> DeviceResetRsp { let reset_config = cmd.get_reset_config(); log::debug!("[{}] DeviceReset", self.handle); log::debug!(" reset_config={:?}", reset_config); @@ -199,7 +199,7 @@ impl Device { DeviceResetRspBuilder { status }.build() } - fn command_get_device_info(&self, _cmd: GetDeviceInfoCmd) -> GetDeviceInfoRsp { + fn core_get_device_info(&self, _cmd: GetDeviceInfoCmd) -> GetDeviceInfoRsp { // TODO: Implement a fancy build time state machine instead of crash at runtime log::debug!("[{}] GetDeviceInfo", self.handle); assert_eq!(self.state, DeviceState::DeviceStateReady); @@ -214,7 +214,7 @@ impl Device { .build() } - pub fn command_get_caps_info(&self, _cmd: GetCapsInfoCmd) -> GetCapsInfoRsp { + pub fn core_get_caps_info(&self, _cmd: GetCapsInfoCmd) -> GetCapsInfoRsp { log::debug!("[{}] GetCapsInfo", self.handle); let caps = DEFAULT_CAPS_INFO @@ -232,7 +232,7 @@ impl Device { .build() } - pub fn command_set_config(&mut self, cmd: SetConfigCmd) -> SetConfigRsp { + pub fn core_set_config(&mut self, cmd: SetConfigCmd) -> SetConfigRsp { log::debug!("[{}] SetConfig", self.handle); assert_eq!(self.state, DeviceState::DeviceStateReady); // UCI 6.3 @@ -260,7 +260,7 @@ impl Device { .build() } - pub fn command_get_config(&self, cmd: GetConfigCmd) -> GetConfigRsp { + pub fn core_get_config(&self, cmd: GetConfigCmd) -> GetConfigRsp { log::debug!("[{}] GetConfig", self.handle); // TODO: do this config shall be set on device reset @@ -304,7 +304,7 @@ impl Device { .build() } - fn command_session_init(&mut self, cmd: SessionInitCmd) -> SessionInitRsp { + fn session_init(&mut self, cmd: SessionInitCmd) -> SessionInitRsp { let session_id = cmd.get_session_id(); let session_type = cmd.get_session_type(); @@ -331,7 +331,7 @@ impl Device { SessionInitRspBuilder { status }.build() } - fn command_session_deinit(&mut self, cmd: SessionDeinitCmd) -> SessionDeinitRsp { + fn session_deinit(&mut self, cmd: SessionDeinitCmd) -> SessionDeinitRsp { let session_id = cmd.get_session_token(); log::debug!("[{}] Session deinit", self.handle); log::debug!(" session_id=0x{:x}", session_id); @@ -352,7 +352,7 @@ impl Device { SessionDeinitRspBuilder { status }.build() } - fn command_session_get_count(&self, _cmd: SessionGetCountCmd) -> SessionGetCountRsp { + fn session_get_count(&self, _cmd: SessionGetCountCmd) -> SessionGetCountRsp { log::debug!("[{}] Session get count", self.handle); SessionGetCountRspBuilder { @@ -362,10 +362,7 @@ impl Device { .build() } - fn command_session_set_app_config( - &mut self, - cmd: SessionSetAppConfigCmd, - ) -> SessionSetAppConfigRsp { + fn session_set_app_config(&mut self, cmd: SessionSetAppConfigCmd) -> SessionSetAppConfigRsp { let session_handle = cmd.get_session_token(); log::debug!( @@ -470,10 +467,7 @@ impl Device { .build() } - fn command_session_get_app_config( - &self, - cmd: SessionGetAppConfigCmd, - ) -> SessionGetAppConfigRsp { + fn session_get_app_config(&self, cmd: SessionGetAppConfigCmd) -> SessionGetAppConfigRsp { let session_handle = cmd.get_session_token(); log::debug!( @@ -520,7 +514,7 @@ impl Device { .build() } - fn command_session_get_state(&self, cmd: SessionGetStateCmd) -> SessionGetStateRsp { + fn session_get_state(&self, cmd: SessionGetStateCmd) -> SessionGetStateRsp { let session_handle = cmd.get_session_token(); log::debug!("[{}:0x{:x}] Session Get State", self.handle, session_handle); @@ -540,7 +534,7 @@ impl Device { .build() } - fn command_session_update_controller_multicast_list( + fn session_update_controller_multicast_list( &mut self, cmd: SessionUpdateControllerMulticastListCmd, ) -> SessionUpdateControllerMulticastListRsp { @@ -731,7 +725,7 @@ impl Device { SessionUpdateControllerMulticastListRspBuilder { status }.build() } - fn command_session_start(&mut self, cmd: SessionStartCmd) -> SessionStartRsp { + fn session_start(&mut self, cmd: SessionStartCmd) -> SessionStartRsp { let session_id = cmd.get_session_id(); log::debug!("[{}:0x{:x}] Session Start", self.handle, session_id); @@ -780,7 +774,7 @@ impl Device { .build() } - fn command_session_stop(&mut self, cmd: SessionStopCmd) -> SessionStopRsp { + fn session_stop(&mut self, cmd: SessionStopCmd) -> SessionStopRsp { let session_id = cmd.get_session_id(); log::debug!("[{}:0x{:x}] Session Stop", self.handle, session_id); @@ -816,7 +810,7 @@ impl Device { .build() } - fn command_session_get_ranging_count( + fn session_get_ranging_count( &self, cmd: SessionGetRangingCountCmd, ) -> SessionGetRangingCountRsp { @@ -843,7 +837,7 @@ impl Device { .build() } - fn command_set_country_code( + fn android_set_country_code( &mut self, cmd: AndroidSetCountryCodeCmd, ) -> AndroidSetCountryCodeRsp { @@ -858,7 +852,7 @@ impl Device { .build() } - fn command_get_power_stats( + fn android_get_power_stats( &mut self, _cmd: AndroidGetPowerStatsCmd, ) -> AndroidGetPowerStatsRsp { @@ -922,37 +916,41 @@ impl Device { match cmd.specialize() { CoreCommand(cmd) => match cmd.specialize() { - DeviceResetCmd(cmd) => self.command_device_reset(cmd).into(), - GetDeviceInfoCmd(cmd) => self.command_get_device_info(cmd).into(), - GetCapsInfoCmd(cmd) => self.command_get_caps_info(cmd).into(), - SetConfigCmd(cmd) => self.command_set_config(cmd).into(), - GetConfigCmd(cmd) => self.command_get_config(cmd).into(), - _ => panic!("Unsupported Core command"), + DeviceResetCmd(cmd) => self.core_device_reset(cmd).into(), + GetDeviceInfoCmd(cmd) => self.core_get_device_info(cmd).into(), + GetCapsInfoCmd(cmd) => self.core_get_caps_info(cmd).into(), + SetConfigCmd(cmd) => self.core_set_config(cmd).into(), + GetConfigCmd(cmd) => self.core_get_config(cmd).into(), + _ => panic!("Unsupported Core command 0x{:02x}", cmd.get_opcode()), }, SessionConfigCommand(cmd) => match cmd.specialize() { - SessionInitCmd(cmd) => self.command_session_init(cmd).into(), - SessionDeinitCmd(cmd) => self.command_session_deinit(cmd).into(), - SessionGetCountCmd(cmd) => self.command_session_get_count(cmd).into(), - SessionSetAppConfigCmd(cmd) => self.command_session_set_app_config(cmd).into(), - SessionGetAppConfigCmd(cmd) => self.command_session_get_app_config(cmd).into(), - SessionGetStateCmd(cmd) => self.command_session_get_state(cmd).into(), - SessionUpdateControllerMulticastListCmd(cmd) => self - .command_session_update_controller_multicast_list(cmd) - .into(), - _ => panic!("Unsupported Session Config command"), + SessionInitCmd(cmd) => self.session_init(cmd).into(), + SessionDeinitCmd(cmd) => self.session_deinit(cmd).into(), + SessionGetCountCmd(cmd) => self.session_get_count(cmd).into(), + SessionSetAppConfigCmd(cmd) => self.session_set_app_config(cmd).into(), + SessionGetAppConfigCmd(cmd) => self.session_get_app_config(cmd).into(), + SessionGetStateCmd(cmd) => self.session_get_state(cmd).into(), + SessionUpdateControllerMulticastListCmd(cmd) => { + self.session_update_controller_multicast_list(cmd).into() + } + _ => panic!( + "Unsupported Session Config command 0x{:02x}", + cmd.get_opcode() + ), }, SessionControlCommand(cmd) => match cmd.specialize() { - SessionStartCmd(cmd) => self.command_session_start(cmd).into(), - SessionStopCmd(cmd) => self.command_session_stop(cmd).into(), - SessionGetRangingCountCmd(cmd) => { - self.command_session_get_ranging_count(cmd).into() - } - _ => panic!("Unsupported Session Control command"), + SessionStartCmd(cmd) => self.session_start(cmd).into(), + SessionStopCmd(cmd) => self.session_stop(cmd).into(), + SessionGetRangingCountCmd(cmd) => self.session_get_ranging_count(cmd).into(), + _ => panic!( + "Unsupported Session Control command 0x{:02x}", + cmd.get_opcode() + ), }, AndroidCommand(cmd) => match cmd.specialize() { - AndroidSetCountryCodeCmd(cmd) => self.command_set_country_code(cmd).into(), - AndroidGetPowerStatsCmd(cmd) => self.command_get_power_stats(cmd).into(), - _ => panic!("Unsupported Android command"), + AndroidSetCountryCodeCmd(cmd) => self.android_set_country_code(cmd).into(), + AndroidGetPowerStatsCmd(cmd) => self.android_get_power_stats(cmd).into(), + _ => panic!("Unsupported Android command 0x{:02x}", cmd.get_opcode()), }, UciVendor_9_Command(vendor_command) => UciVendor_9_ResponseBuilder { opcode: vendor_command.get_opcode(), -- cgit v1.2.3 From 0bc8f45c9a0a501287b0b1b6fac8c70e6e30603c Mon Sep 17 00:00:00 2001 From: Henri Chataing Date: Fri, 23 Feb 2024 16:42:46 -0800 Subject: Reorganize definitions in uci_packets.pdl Instead of deriving packets based on the message type first then the gid + oid, which prevents proper typing of the oid; packets are written to derive from the gid first, then mt + oid. This way, the oid can have the appropriate enum type and commands do not have to be written with hardcoded oids. --- src/device.rs | 225 +++++++++----------- src/lib.rs | 7 +- src/session.rs | 6 +- src/uci_packets.pdl | 598 +++++++++++++++++++++------------------------------- 4 files changed, 346 insertions(+), 490 deletions(-) diff --git a/src/device.rs b/src/device.rs index c255cc4..390a29a 100644 --- a/src/device.rs +++ b/src/device.rs @@ -122,7 +122,7 @@ impl Device { let tx = self.tx.clone(); tokio::spawn(async move { time::sleep(Duration::from_millis(5)).await; - tx.send(DeviceStatusNtfBuilder { device_state }.build().into()) + tx.send(CoreDeviceStatusNtfBuilder { device_state }.build().into()) .unwrap() }); } @@ -180,13 +180,13 @@ impl Device { // The fira norm specify to send a response, then reset, then // send a notification once the reset is done - fn core_device_reset(&mut self, cmd: DeviceResetCmd) -> DeviceResetRsp { + fn core_device_reset(&mut self, cmd: CoreDeviceResetCmd) -> CoreDeviceResetRsp { let reset_config = cmd.get_reset_config(); log::debug!("[{}] DeviceReset", self.handle); log::debug!(" reset_config={:?}", reset_config); let status = match reset_config { - ResetConfig::UwbsReset => StatusCode::UciStatusOk, + ResetConfig::UwbsReset => uci::Status::Ok, }; *self = Device::new( self.handle, @@ -196,15 +196,15 @@ impl Device { ); self.init(); - DeviceResetRspBuilder { status }.build() + CoreDeviceResetRspBuilder { status }.build() } - fn core_get_device_info(&self, _cmd: GetDeviceInfoCmd) -> GetDeviceInfoRsp { + fn core_get_device_info(&self, _cmd: CoreGetDeviceInfoCmd) -> CoreGetDeviceInfoRsp { // TODO: Implement a fancy build time state machine instead of crash at runtime log::debug!("[{}] GetDeviceInfo", self.handle); assert_eq!(self.state, DeviceState::DeviceStateReady); - GetDeviceInfoRspBuilder { - status: StatusCode::UciStatusOk, + CoreGetDeviceInfoRspBuilder { + status: uci::Status::Ok, uci_version: UCI_VERSION, mac_version: MAC_VERSION, phy_version: PHY_VERSION, @@ -214,7 +214,7 @@ impl Device { .build() } - pub fn core_get_caps_info(&self, _cmd: GetCapsInfoCmd) -> GetCapsInfoRsp { + pub fn core_get_caps_info(&self, _cmd: CoreGetCapsInfoCmd) -> CoreGetCapsInfoRsp { log::debug!("[{}] GetCapsInfo", self.handle); let caps = DEFAULT_CAPS_INFO @@ -225,14 +225,14 @@ impl Device { }) .collect(); - GetCapsInfoRspBuilder { - status: StatusCode::UciStatusOk, + CoreGetCapsInfoRspBuilder { + status: uci::Status::Ok, tlvs: caps, } .build() } - pub fn core_set_config(&mut self, cmd: SetConfigCmd) -> SetConfigRsp { + pub fn core_set_config(&mut self, cmd: CoreSetConfigCmd) -> CoreSetConfigRsp { log::debug!("[{}] SetConfig", self.handle); assert_eq!(self.state, DeviceState::DeviceStateReady); // UCI 6.3 @@ -248,19 +248,19 @@ impl Device { let (status, parameters) = if invalid_config_status.is_empty() { self.config.extend(valid_parameters); - (StatusCode::UciStatusOk, Vec::new()) + (uci::Status::Ok, Vec::new()) } else { - (StatusCode::UciStatusInvalidParam, invalid_config_status) + (uci::Status::InvalidParam, invalid_config_status) }; - SetConfigRspBuilder { + CoreSetConfigRspBuilder { cfg_status: parameters, status, } .build() } - pub fn core_get_config(&self, cmd: GetConfigCmd) -> GetConfigRsp { + pub fn core_get_config(&self, cmd: CoreGetConfigCmd) -> CoreGetConfigRsp { log::debug!("[{}] GetConfig", self.handle); // TODO: do this config shall be set on device reset @@ -292,12 +292,12 @@ impl Device { ); let (status, parameters) = if invalid_parameters.is_empty() { - (StatusCode::UciStatusOk, valid_parameters) + (uci::Status::Ok, valid_parameters) } else { - (StatusCode::UciStatusInvalidParam, invalid_parameters) + (uci::Status::InvalidParam, invalid_parameters) }; - GetConfigRspBuilder { + CoreGetConfigRspBuilder { status, tlvs: parameters, } @@ -313,17 +313,17 @@ impl Device { log::debug!(" session_type={:?}", session_type); let status = if self.sessions.len() >= MAX_SESSION { - StatusCode::UciStatusMaxSessionsExceeded + uci::Status::MaxSessionsExceeded } else { match self.sessions.insert( session_id, Session::new(session_id, session_type, self.handle, self.tx.clone()), ) { - Some(_) => StatusCode::UciStatusSessionDuplicate, + Some(_) => uci::Status::SessionDuplicate, None => { // Should not fail self.session_mut(session_id).unwrap().init(); - StatusCode::UciStatusOk + uci::Status::Ok } } }; @@ -345,9 +345,9 @@ impl Device { } } self.sessions.remove(&session_id); - StatusCode::UciStatusOk + uci::Status::Ok } - None => StatusCode::UciStatusSessionNotExist, + None => uci::Status::SessionNotExist, }; SessionDeinitRspBuilder { status }.build() } @@ -356,7 +356,7 @@ impl Device { log::debug!("[{}] Session get count", self.handle); SessionGetCountRspBuilder { - status: StatusCode::UciStatusOk, + status: uci::Status::Ok, session_count: self.sessions.len() as u8, } .build() @@ -374,7 +374,7 @@ impl Device { let Some(session) = self.sessions.get_mut(&session_handle) else { return SessionSetAppConfigRspBuilder { cfg_status: Vec::new(), - status: StatusCode::UciStatusSessionNotExist, + status: uci::Status::SessionNotExist, } .build(); }; @@ -392,7 +392,7 @@ impl Device { .any(|cfg| IMMUTABLE_PARAMETERS.contains(&cfg.cfg_id)) { return SessionSetAppConfigRspBuilder { - status: StatusCode::UciStatusSessionActive, + status: uci::Status::SessionActive, cfg_status: vec![], } .build(); @@ -402,7 +402,7 @@ impl Device { let (status, invalid_parameters) = if session.state != SessionState::SessionStateInit && session.state != SessionState::SessionStateActive { - (StatusCode::UciStatusRejected, Vec::new()) + (uci::Status::Rejected, Vec::new()) } else { let mut app_config = session.app_config.clone(); let mut invalid_parameters = vec![]; @@ -411,7 +411,7 @@ impl Device { Ok(_) => (), Err(_) => invalid_parameters.push(AppConfigStatus { cfg_id: cfg.cfg_id, - status: uci::StatusCode::UciStatusInvalidParam, + status: uci::Status::InvalidParam, }), } } @@ -440,7 +440,7 @@ impl Device { session_handle ); return SessionSetAppConfigRspBuilder { - status: uci::StatusCode::UciStatusRejected, + status: uci::Status::Rejected, cfg_status: vec![], } .build(); @@ -454,9 +454,9 @@ impl Device { ReasonCode::StateChangeWithSessionManagementCommands, ); } - (StatusCode::UciStatusOk, invalid_parameters) + (uci::Status::Ok, invalid_parameters) } else { - (StatusCode::UciStatusInvalidParam, invalid_parameters) + (uci::Status::InvalidParam, invalid_parameters) } }; @@ -479,7 +479,7 @@ impl Device { let Some(session) = self.sessions.get(&session_handle) else { return SessionGetAppConfigRspBuilder { tlvs: vec![], - status: StatusCode::UciStatusSessionNotExist, + status: uci::Status::SessionNotExist, } .build(); }; @@ -501,9 +501,9 @@ impl Device { } if invalid_parameters.is_empty() { - (StatusCode::UciStatusOk, valid_parameters) + (uci::Status::Ok, valid_parameters) } else { - (StatusCode::UciStatusFailed, Vec::new()) + (uci::Status::Failed, Vec::new()) } }; @@ -522,13 +522,13 @@ impl Device { let Some(session) = self.sessions.get(&session_handle) else { return SessionGetStateRspBuilder { session_state: SessionState::SessionStateInit, - status: StatusCode::UciStatusSessionNotExist, + status: uci::Status::SessionNotExist, } .build(); }; SessionGetStateRspBuilder { - status: StatusCode::UciStatusOk, + status: uci::Status::Ok, session_state: session.state, } .build() @@ -548,7 +548,7 @@ impl Device { let Some(session) = self.sessions.get_mut(&session_handle) else { return SessionUpdateControllerMulticastListRspBuilder { - status: StatusCode::UciStatusSessionNotExist, + status: uci::Status::SessionNotExist, } .build(); }; @@ -559,7 +559,7 @@ impl Device { || session.app_config.multi_node_mode != Some(MultiNodeMode::OneToMany) { return SessionUpdateControllerMulticastListRspBuilder { - status: StatusCode::UciStatusRejected, + status: uci::Status::Rejected, } .build(); } @@ -578,7 +578,7 @@ impl Device { .collect() } else { return SessionUpdateControllerMulticastListRspBuilder { - status: StatusCode::UciStatusSyntaxError, + status: uci::Status::SyntaxError, } .build(); } @@ -596,7 +596,7 @@ impl Device { .collect() } else { return SessionUpdateControllerMulticastListRspBuilder { - status: StatusCode::UciStatusSyntaxError, + status: uci::Status::SyntaxError, } .build(); } @@ -614,14 +614,14 @@ impl Device { .collect() } else { return SessionUpdateControllerMulticastListRspBuilder { - status: StatusCode::UciStatusSyntaxError, + status: uci::Status::SyntaxError, } .build(); } } }; let mut controlee_status = Vec::new(); - let mut status = StatusCode::UciStatusOk; + let mut status = uci::Status::Ok; match action { UpdateMulticastListAction::AddControlee @@ -631,7 +631,7 @@ impl Device { let mut update_status = MulticastUpdateStatusCode::StatusOkMulticastListUpdate; if !dst_addresses.contains(&controlee.short_address) { if dst_addresses.len() == MAX_NUMBER_OF_CONTROLEES { - status = StatusCode::UciStatusMulticastListFull; + status = uci::Status::MulticastListFull; update_status = MulticastUpdateStatusCode::StatusErrorMulticastListFull; } else if (action == UpdateMulticastListAction::AddControleeWithShortSubSessionKey @@ -644,7 +644,7 @@ impl Device { // 0x04, the UWBS shall return SESSION_UPDATE_CONTROLLER_MULTICAST_LIST_NTF // with Status set to STATUS_ERROR_SUB_SESSION_KEY_NOT_APPLICABLE for each // Controlee in the Controlee List. - status = StatusCode::UciStatusFailed; + status = uci::Status::Failed; update_status = MulticastUpdateStatusCode::StatusErrorSubSessionKeyNotApplicable; } else { @@ -668,7 +668,7 @@ impl Device { let attempt_count = session.app_config.in_band_termination_attempt_count; let mut update_status = MulticastUpdateStatusCode::StatusOkMulticastListUpdate; if !dst_addresses.contains(&address) { - status = StatusCode::UciStatusAddressNotFound; + status = uci::Status::AddressNotFound; update_status = MulticastUpdateStatusCode::StatusErrorKeyFetchFail; } else { dst_addresses.retain(|value| *value != address); @@ -732,14 +732,14 @@ impl Device { let Some(session) = self.sessions.get_mut(&session_id) else { return SessionStartRspBuilder { - status: StatusCode::UciStatusSessionNotExist, + status: uci::Status::SessionNotExist, } .build(); }; if session.state != SessionState::SessionStateIdle { return SessionStartRspBuilder { - status: StatusCode::UciStatusSessionNotConfigured, + status: uci::Status::SessionNotConfigured, } .build(); } @@ -769,7 +769,7 @@ impl Device { self.set_state(DeviceState::DeviceStateActive); SessionStartRspBuilder { - status: StatusCode::UciStatusOk, + status: uci::Status::Ok, } .build() } @@ -781,14 +781,14 @@ impl Device { let Some(session) = self.sessions.get_mut(&session_id) else { return SessionStopRspBuilder { - status: StatusCode::UciStatusSessionNotExist, + status: uci::Status::SessionNotExist, } .build(); }; if session.state != SessionState::SessionStateActive { return SessionStopRspBuilder { - status: StatusCode::UciStatusSessionActive, + status: uci::Status::SessionActive, } .build(); } @@ -805,7 +805,7 @@ impl Device { } SessionStopRspBuilder { - status: StatusCode::UciStatusOk, + status: uci::Status::Ok, } .build() } @@ -824,14 +824,14 @@ impl Device { let Some(session) = self.sessions.get(&session_id) else { return SessionGetRangingCountRspBuilder { - status: StatusCode::UciStatusSessionNotExist, + status: uci::Status::SessionNotExist, count: 0, } .build(); }; SessionGetRangingCountRspBuilder { - status: StatusCode::UciStatusOk, + status: uci::Status::Ok, count: session.sequence_number, } .build() @@ -847,7 +847,7 @@ impl Device { self.country_code = country_code; AndroidSetCountryCodeRspBuilder { - status: StatusCode::UciStatusOk, + status: uci::Status::Ok, } .build() } @@ -861,7 +861,7 @@ impl Device { // TODO AndroidGetPowerStatsRspBuilder { stats: PowerStats { - status: StatusCode::UciStatusOk, + status: uci::Status::Ok, idle_time_ms: 0, tx_time_ms: 0, rx_time_ms: 0, @@ -871,7 +871,7 @@ impl Device { .build() } - pub fn data_message_snd(&mut self, data: DataPacket) -> SessionControlNotification { + pub fn data_message_snd(&mut self, data: DataPacket) -> ControlPacket { log::debug!("[{}] data_message_send", self.handle); match data.specialize() { DataPacketChild::DataMessageSnd(data_msg_snd) => { @@ -879,7 +879,7 @@ impl Device { if let Some(session) = self.session_mut(session_token) { session.data_message_snd(data_msg_snd) } else { - DataTransferStatusNtfBuilder { + SessionDataTransferStatusNtfBuilder { session_token, status: DataTransferNtfStatusCode::UciDataTransferStatusErrorRejected, tx_count: 1, // TODO: support for retries? @@ -892,7 +892,7 @@ impl Device { DataPacketChild::DataMessageRcv(data_msg_rcv) => { // This function should not be passed anything besides DataMessageSnd let session_token = data_msg_rcv.get_session_handle(); - DataTransferStatusNtfBuilder { + SessionDataTransferStatusNtfBuilder { session_token, status: DataTransferNtfStatusCode::UciDataTransferStatusInvalidFormat, tx_count: 1, // TODO: support for retries? @@ -907,23 +907,23 @@ impl Device { } } - fn receive_command(&mut self, cmd: UciCommand) -> UciResponse { - use AndroidCommandChild::*; - use CoreCommandChild::*; - use SessionConfigCommandChild::*; - use SessionControlCommandChild::*; - use UciCommandChild::*; + fn receive_command(&mut self, cmd: ControlPacket) -> ControlPacket { + use AndroidPacketChild::*; + use ControlPacketChild::*; + use CorePacketChild::*; + use SessionConfigPacketChild::*; + use SessionControlPacketChild::*; match cmd.specialize() { - CoreCommand(cmd) => match cmd.specialize() { - DeviceResetCmd(cmd) => self.core_device_reset(cmd).into(), - GetDeviceInfoCmd(cmd) => self.core_get_device_info(cmd).into(), - GetCapsInfoCmd(cmd) => self.core_get_caps_info(cmd).into(), - SetConfigCmd(cmd) => self.core_set_config(cmd).into(), - GetConfigCmd(cmd) => self.core_get_config(cmd).into(), - _ => panic!("Unsupported Core command 0x{:02x}", cmd.get_opcode()), + CorePacket(cmd) => match cmd.specialize() { + CoreDeviceResetCmd(cmd) => self.core_device_reset(cmd).into(), + CoreGetDeviceInfoCmd(cmd) => self.core_get_device_info(cmd).into(), + CoreGetCapsInfoCmd(cmd) => self.core_get_caps_info(cmd).into(), + CoreSetConfigCmd(cmd) => self.core_set_config(cmd).into(), + CoreGetConfigCmd(cmd) => self.core_get_config(cmd).into(), + _ => unimplemented!("Unsupported Core oid {:?}", cmd.get_oid()), }, - SessionConfigCommand(cmd) => match cmd.specialize() { + SessionConfigPacket(cmd) => match cmd.specialize() { SessionInitCmd(cmd) => self.session_init(cmd).into(), SessionDeinitCmd(cmd) => self.session_deinit(cmd).into(), SessionGetCountCmd(cmd) => self.session_get_count(cmd).into(), @@ -933,62 +933,47 @@ impl Device { SessionUpdateControllerMulticastListCmd(cmd) => { self.session_update_controller_multicast_list(cmd).into() } - _ => panic!( - "Unsupported Session Config command 0x{:02x}", - cmd.get_opcode() - ), + _ => unimplemented!("Unsupported Session Config oid {:?}", cmd.get_oid()), }, - SessionControlCommand(cmd) => match cmd.specialize() { + SessionControlPacket(cmd) => match cmd.specialize() { SessionStartCmd(cmd) => self.session_start(cmd).into(), SessionStopCmd(cmd) => self.session_stop(cmd).into(), SessionGetRangingCountCmd(cmd) => self.session_get_ranging_count(cmd).into(), - _ => panic!( - "Unsupported Session Control command 0x{:02x}", - cmd.get_opcode() - ), + _ => unimplemented!("Unsupported Session Control oid {:?}", cmd.get_oid()), }, - AndroidCommand(cmd) => match cmd.specialize() { + AndroidPacket(cmd) => match cmd.specialize() { AndroidSetCountryCodeCmd(cmd) => self.android_set_country_code(cmd).into(), AndroidGetPowerStatsCmd(cmd) => self.android_get_power_stats(cmd).into(), - _ => panic!("Unsupported Android command 0x{:02x}", cmd.get_opcode()), + _ => unimplemented!("Unsupported Android oid {:?}", cmd.get_oid()), }, - UciVendor_9_Command(vendor_command) => UciVendor_9_ResponseBuilder { - opcode: vendor_command.get_opcode(), - payload: Some(vec![u8::from(StatusCode::UciStatusRejected)].into()), - } - .build() - .into(), - UciVendor_A_Command(vendor_command) => UciVendor_A_ResponseBuilder { - opcode: vendor_command.get_opcode(), - payload: Some(vec![u8::from(StatusCode::UciStatusRejected)].into()), - } - .build() - .into(), - UciVendor_B_Command(vendor_command) => UciVendor_B_ResponseBuilder { - opcode: vendor_command.get_opcode(), - payload: Some(vec![u8::from(StatusCode::UciStatusRejected)].into()), - } - .build() - .into(), - UciVendor_E_Command(vendor_command) => UciVendor_E_ResponseBuilder { - opcode: vendor_command.get_opcode(), - payload: Some(vec![u8::from(StatusCode::UciStatusRejected)].into()), + ControlPacketChild::Payload(_) + if matches!( + cmd.get_mt(), + uci::MessageType::Response | uci::MessageType::Notification + ) => + { + unreachable!("Unhandled control messsage with type {:?}", cmd.get_mt()); } - .build() - .into(), - UciVendor_F_Command(vendor_command) => UciVendor_F_ResponseBuilder { - opcode: vendor_command.get_opcode(), - payload: Some(vec![u8::from(StatusCode::UciStatusRejected)].into()), + ControlPacketChild::Payload(payload) => { + // [UCI] 4.3.2 Exception Handling for Control Messages + // The UWBS shall respond to an unknown Command (unknown GID + // or OID) with a Response having the same GID and OID field + // values as the Command, followed by a Status field with the + // value of STATUS_UNKNOWN_GID/STATUS_UNKNOWN_OID respectively + // and no additional fields. + log::error!("Unsupported gid {:?}", cmd.get_gid()); + ControlPacketBuilder { + mt: uci::MessageType::Response, + gid: cmd.get_gid(), + payload: Some( + vec![payload[0], payload[1], 0x1, uci::Status::UnknownGid.into()].into(), + ), + } + .build() } - .build() - .into(), - // TODO: Handle properly without panic - _ => UciResponseBuilder { - gid: GroupId::Core, - opcode: 0, - payload: Option::None, + ControlPacketChild::None => { + unreachable!() } - .build(), } } @@ -1017,9 +1002,9 @@ impl Device { let opcode_id = packet[1] & 0x3f; let status = if GroupId::try_from(group_id).is_ok() { - StatusCode::UciStatusUnknownOid + uci::Status::UnknownOid } else { - StatusCode::UciStatusUnknownGid + uci::Status::UnknownGid }; // The PDL generated code cannot be used to generate // responses with invalid group identifiers. @@ -1035,7 +1020,7 @@ impl Device { // Parsing success, ignore non command packets. Ok(cmd) => { - let response = self.receive_command(cmd.try_into().unwrap()); + let response = self.receive_command(cmd); self.send_control(response) } } diff --git a/src/lib.rs b/src/lib.rs index a11fe75..d3168c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,8 +24,7 @@ use tokio::sync::{broadcast, mpsc, oneshot}; pub mod packets; mod pcapng; -use packets::uci::StatusCode as UciStatusCode; -use packets::uci::*; +use packets::uci::{self, *}; mod device; use device::{Device, MAX_DEVICE, MAX_SESSION}; @@ -162,7 +161,7 @@ fn make_measurement( if let MacAddress::Short(address) = mac_address { ShortAddressTwoWayRangingMeasurement { mac_address: u16::from_le_bytes(*address), - status: UciStatusCode::UciStatusOk, + status: uci::Status::Ok, nlos: 0, // in Line Of Sight distance: local.range, aoa_azimuth: local.azimuth as u16, @@ -469,7 +468,7 @@ impl Pica { pbf: PacketBoundaryFlag::Complete, session_handle: session_id, source_address: device.mac_address.into(), - status: UciStatusCode::UciStatusOk, + status: uci::Status::Ok, } .build() .into(), diff --git a/src/session.rs b/src/session.rs index 4a67593..f3ee408 100644 --- a/src/session.rs +++ b/src/session.rs @@ -130,13 +130,13 @@ impl Session { } } - pub fn data_message_snd(&mut self, data: DataMessageSnd) -> SessionControlNotification { + pub fn data_message_snd(&mut self, data: DataMessageSnd) -> ControlPacket { log::debug!("[{}] data_message_snd", self.device_handle); let session_token = data.get_session_handle(); let uci_sequence_number = data.get_data_sequence_number() as u8; if self.session_type != SessionType::FiraRangingAndInBandDataSession { - return DataTransferStatusNtfBuilder { + return SessionDataTransferStatusNtfBuilder { session_token, status: DataTransferNtfStatusCode::UciDataTransferStatusSessionTypeNotSupported, tx_count: 1, // TODO: support for retries? @@ -150,7 +150,7 @@ impl Session { self.data.extend_from_slice(data.get_application_data()); - DataCreditNtfBuilder { + SessionDataCreditNtfBuilder { credit_availability: CreditAvailability::CreditAvailable, session_token, } diff --git a/src/uci_packets.pdl b/src/uci_packets.pdl index ebb8db2..7d1fc2e 100644 --- a/src/uci_packets.pdl +++ b/src/uci_packets.pdl @@ -15,150 +15,131 @@ little_endian_packets enum PacketBoundaryFlag : 1 { - COMPLETE = 0x00, - NOT_COMPLETE = 0x01, + COMPLETE = 0, + NOT_COMPLETE = 1, +} + +enum MessageType : 3 { + DATA = 0x0, + COMMAND = 0x1, + RESPONSE = 0x2, + NOTIFICATION = 0x3, } enum GroupId : 4 { - CORE = 0x00, - SESSION_CONFIG = 0x01, - SESSION_CONTROL = 0x02, - DATA_CONTROL = 0x03, - TEST = 0x0d, - VENDOR_RESERVED_9 = 0x09, - VENDOR_RESERVED_A = 0x0a, - VENDOR_RESERVED_B = 0x0b, - VENDOR_ANDROID = 0x0c, - VENDOR_RESERVED_E = 0x0e, - VENDOR_RESERVED_F = 0x0f, -} - -enum DataPacketFormat: 4 { + CORE = 0x0, + SESSION_CONFIG = 0x1, + SESSION_CONTROL = 0x2, + DATA_CONTROL = 0x3, + VENDOR_RESERVED_9 = 0x9, + VENDOR_RESERVED_A = 0xa, + VENDOR_RESERVED_B = 0xb, + VENDOR_ANDROID = 0xc, + TEST = 0xd, + VENDOR_RESERVED_E = 0xe, + VENDOR_RESERVED_F = 0xf, +} + +enum DataPacketFormat : 4 { DATA_SND = 0x01, DATA_RCV = 0x02, } -// Define a merged enum across GroupId & DataPacketFormat as they are at the same bits in -// |UciPacketHal|. -enum GroupIdOrDataPacketFormat : 4 { - CORE = 0x00, - SESSION_CONFIG_OR_DATA_SND = 0x01, - SESSION_CONTROL_OR_DATA_RCV = 0x02, - DATA_CONTROL = 0x03, - TEST = 0x0d, - VENDOR_RESERVED_9 = 0x09, - VENDOR_RESERVED_A = 0x0a, - VENDOR_RESERVED_B = 0x0b, - VENDOR_ANDROID = 0x0c, - VENDOR_RESERVED_E = 0x0e, - VENDOR_RESERVED_F = 0x0f, -} - -enum CoreOpCode : 6 { - CORE_DEVICE_RESET = 0x00, - CORE_DEVICE_STATUS_NTF = 0x01, - CORE_DEVICE_INFO = 0x02, - CORE_GET_CAPS_INFO = 0x03, - CORE_SET_CONFIG = 0x04, - CORE_GET_CONFIG = 0x05, - CORE_DEVICE_SUSPEND = 0x06, - CORE_GENERIC_ERROR_NTF = 0x07, - CORE_QUERY_UWBS_TIMESTAMP = 0x08, -} - -enum SessionConfigOpCode : 6 { - SESSION_INIT = 0x00, - SESSION_DEINIT = 0x01, - SESSION_STATUS_NTF = 0x02, - SESSION_SET_APP_CONFIG = 0x03, - SESSION_GET_APP_CONFIG = 0x04, - SESSION_GET_COUNT = 0x05, - SESSION_GET_STATE = 0x06, - SESSION_UPDATE_CONTROLLER_MULTICAST_LIST = 0x07, - SESSION_UPDATE_ACTIVE_ROUNDS_ANCHOR = 0x08, - SESSION_UPDATE_ACTIVE_ROUNDS_DT_TAG = 0x09, - SESSION_SET_INITIATOR_DT_ANCHOR_RR_RDM_LIST = 0x0a, - SESSION_QUERY_DATA_SIZE_IN_RANGING = 0x0b, - SESSION_SET_HUS_CONFIG = 0x0c, -} - -enum SessionControlOpCode : 6 { - SESSION_START = 0x00, - SESSION_STOP = 0x01, - SESSION_RESERVED = 0x02, - SESSION_GET_RANGING_COUNT = 0x03, - SESSION_DATA_CREDIT_NTF = 0x04, - SESSION_DATA_TRANSFER_STATUS_NTF = 0x05, -} - -enum AppDataOpCode : 6 { - APP_DATA_TX = 0x00, - APP_DATA_RX = 0x01, -} - -// Android vendor commands -enum AndroidOpCode : 6 { - ANDROID_GET_POWER_STATS = 0x0, - ANDROID_SET_COUNTRY_CODE = 0x1, - ANDROID_FIRA_RANGE_DIAGNOSTICS = 0x2, -} - -enum StatusCode : 8 { +enum CoreOpcodeId : 6 { + DEVICE_RESET = 0x00, + DEVICE_STATUS = 0x01, + GET_DEVICE_INFO = 0x02, + GET_CAPS_INFO = 0x03, + SET_CONFIG = 0x04, + GET_CONFIG = 0x05, + GENERIC_ERROR = 0x07, + QUERY_UWBS_TIMESTAMP = 0x08, +} + +enum SessionConfigOpcodeId : 6 { + INIT = 0x00, + DEINIT = 0x01, + STATUS = 0x02, + SET_APP_CONFIG = 0x03, + GET_APP_CONFIG = 0x04, + GET_COUNT = 0x05, + GET_STATE = 0x06, + UPDATE_CONTROLLER_MULTICAST_LIST = 0x07, + UPDATE_DT_ANCHOR_RANGING_ROUNDS = 0x08, + UPDATE_DT_TAG_RANGING_ROUNDS = 0x09, + QUERY_DATA_SIZE_IN_RANGING = 0x0b, +} + +enum SessionControlOpcodeId : 6 { + START = 0x00, // INFO_NTF + STOP = 0x01, + GET_RANGING_COUNT = 0x03, + DATA_CREDIT = 0x04, + DATA_TRANSFER_STATUS = 0x05, +} + +enum AndroidOpcodeId : 6 { + GET_POWER_STATS = 0x00, + SET_COUNTRY_CODE = 0x01, + FIRA_RANGE_DIAGNOSTICS = 0x02, +} + +enum Status : 8 { // Generic Status Codes - UCI_STATUS_OK = 0x00, - UCI_STATUS_REJECTED = 0x01, - UCI_STATUS_FAILED = 0x02, - UCI_STATUS_SYNTAX_ERROR = 0x03, - UCI_STATUS_INVALID_PARAM = 0x04, - UCI_STATUS_INVALID_RANGE = 0x05, - UCI_STATUS_INVALID_MSG_SIZE = 0x06, - UCI_STATUS_UNKNOWN_GID = 0x07, - UCI_STATUS_UNKNOWN_OID = 0x08, - UCI_STATUS_READ_ONLY = 0x09, - UCI_STATUS_COMMAND_RETRY = 0x0A, - UCI_STATUS_UNKNOWN = 0x0B, - UCI_STATUS_NOT_APPLICABLE = 0x0C, + OK = 0x00, + REJECTED = 0x01, + FAILED = 0x02, + SYNTAX_ERROR = 0x03, + INVALID_PARAM = 0x04, + INVALID_RANGE = 0x05, + INVALID_MSG_SIZE = 0x06, + UNKNOWN_GID = 0x07, + UNKNOWN_OID = 0x08, + READ_ONLY = 0x09, + COMMAND_RETRY = 0x0A, + UNKNOWN = 0x0B, + NOT_APPLICABLE = 0x0C, RFU_STATUS_CODE_RANGE_1 = 0x0D..0x10, // UWB Session Specific Status Codes - UCI_STATUS_SESSION_NOT_EXIST = 0x11, - UCI_STATUS_SESSION_DUPLICATE = 0x12, - UCI_STATUS_SESSION_ACTIVE = 0x13, - UCI_STATUS_MAX_SESSIONS_EXCEEDED = 0x14, - UCI_STATUS_SESSION_NOT_CONFIGURED = 0x15, - UCI_STATUS_ACTIVE_SESSIONS_ONGOING = 0x16, - UCI_STATUS_MULTICAST_LIST_FULL = 0x17, - UCI_STATUS_ADDRESS_NOT_FOUND = 0x18, - UCI_STATUS_ADDRESS_ALREADY_PRESENT = 0x19, - UCI_STATUS_ERROR_UWB_INITIATION_TIME_TOO_OLD = 0x1A, - UCI_STATUS_OK_NEGATIVE_DISTANCE_REPORT = 0x1B, + SESSION_NOT_EXIST = 0x11, + SESSION_DUPLICATE = 0x12, + SESSION_ACTIVE = 0x13, + MAX_SESSIONS_EXCEEDED = 0x14, + SESSION_NOT_CONFIGURED = 0x15, + ACTIVE_SESSIONS_ONGOING = 0x16, + MULTICAST_LIST_FULL = 0x17, + ADDRESS_NOT_FOUND = 0x18, + ADDRESS_ALREADY_PRESENT = 0x19, + ERROR_UWB_INITIATION_TIME_TOO_OLD = 0x1A, + OK_NEGATIVE_DISTANCE_REPORT = 0x1B, RFU_STATUS_CODE_RANGE_2 = 0x1C..0x1F, // UWB Ranging Session Specific Status Codes - UCI_STATUS_RANGING_TX_FAILED = 0x20, - UCI_STATUS_RANGING_RX_TIMEOUT = 0x21, - UCI_STATUS_RANGING_RX_PHY_DEC_FAILED = 0x22, - UCI_STATUS_RANGING_RX_PHY_TOA_FAILED = 0x23, - UCI_STATUS_RANGING_RX_PHY_STS_FAILED = 0x24, - UCI_STATUS_RANGING_RX_MAC_DEC_FAILED = 0x25, - UCI_STATUS_RANGING_RX_MAC_IE_DEC_FAILED = 0x26, - UCI_STATUS_RANGING_RX_MAC_IE_MISSING = 0x27, - UCI_STATUS_ERROR_ROUND_INDEX_NOT_ACTIVATED = 0x28, - UCI_STATUS_ERROR_NUMBER_OF_ACTIVE_RANGING_ROUNDS_EXCEEDED = 0x29, - UCI_STATUS_ERROR_DL_TDOA_DEVICE_ADDRESS_NOT_MATCHING_IN_REPLY_TIME_LIST = 0x2A, + RANGING_TX_FAILED = 0x20, + RANGING_RX_TIMEOUT = 0x21, + RANGING_RX_PHY_DEC_FAILED = 0x22, + RANGING_RX_PHY_TOA_FAILED = 0x23, + RANGING_RX_PHY_STS_FAILED = 0x24, + RANGING_RX_MAC_DEC_FAILED = 0x25, + RANGING_RX_MAC_IE_DEC_FAILED = 0x26, + RANGING_RX_MAC_IE_MISSING = 0x27, + ERROR_ROUND_INDEX_NOT_ACTIVATED = 0x28, + ERROR_NUMBER_OF_ACTIVE_RANGING_ROUNDS_EXCEEDED = 0x29, + ERROR_DL_TDOA_DEVICE_ADDRESS_NOT_MATCHING_IN_REPLY_TIME_LIST = 0x2A, RFU_STATUS_CODE_RANGE_3 = 0x2B..0x2F, // UWB Data Session Specific Status Codes - UCI_STATUS_DATA_MAX_TX_PSDU_SIZE_EXCEEDED = 0x30, - UCI_STATUS_DATA_RX_CRC_ERROR = 0x31, + DATA_MAX_TX_PSDU_SIZE_EXCEEDED = 0x30, + DATA_RX_CRC_ERROR = 0x31, RFU_STATUS_CODE_RANGE_4 = 0x32..0x4F, // Vendor Specific Status Codes VENDOR_SPECIFIC_STATUS_CODE_RANGE_1 = 0x50..0xFE { - UCI_STATUS_ERROR_CCC_SE_BUSY = 0x50, - UCI_STATUS_ERROR_CCC_LIFECYCLE = 0x51, - UCI_STATUS_ERROR_STOPPED_DUE_TO_OTHER_SESSION_CONFLICT = 0x52, - UCI_STATUS_REGULATION_UWB_OFF = 0x53, + ERROR_CCC_SE_BUSY = 0x50, + ERROR_CCC_LIFECYCLE = 0x51, + ERROR_STOPPED_DUE_TO_OTHER_SESSION_CONFLICT = 0x52, + REGULATION_UWB_OFF = 0x53, }, // For internal usage, we will use 0xFF as default. @@ -640,15 +621,6 @@ enum SessionType: 8 { DEVICE_TEST_MODE = 0xD0, } -enum MessageType: 3 { - DATA = 0x00, - COMMAND = 0x01, - RESPONSE = 0x02, - NOTIFICATION = 0x03, - RESERVED_FOR_TESTING_1 = 0x04, - RESERVED_FOR_TESTING_2 = 0x05, -} - // Used to parse message type packet CommonPacketHeader { _reserved_ : 4, @@ -681,9 +653,6 @@ packet ControlPacket { gid : GroupId, _reserved_ : 1, mt : MessageType, - opcode: 6, - _reserved_: 2, - _reserved_: 16, _payload_, } @@ -706,109 +675,76 @@ packet DataMessageSnd : DataPacket (dpf = DATA_SND, mt = DATA) { packet DataMessageRcv : DataPacket (dpf = DATA_RCV, mt = DATA) { session_handle: 32, - status : StatusCode, + status: Status, source_address: 64, data_sequence_number: 16, _size_(application_data): 16, application_data: 8[] } -// TODO(b/202760099): Handle fragmentation of packets if the size exceed max allowed. -packet UciCommand : ControlPacket (mt = COMMAND) { - _payload_, -} - -packet UciResponse : ControlPacket (mt = RESPONSE) { - _payload_, -} - -packet UciNotification : ControlPacket (mt = NOTIFICATION) { - _payload_, -} - -packet CoreCommand : UciCommand (gid = CORE) { - _body_, -} - -packet CoreResponse : UciResponse (gid = CORE) { - _body_, -} - -packet CoreNotification : UciNotification (gid = CORE) { - _body_, -} - -packet SessionConfigCommand : UciCommand (gid = SESSION_CONFIG) { - _body_, -} - -packet SessionConfigResponse : UciResponse (gid = SESSION_CONFIG) { - _body_, -} - -packet SessionConfigNotification : UciNotification (gid = SESSION_CONFIG) { - _body_, -} - -packet SessionControlCommand : UciCommand (gid = SESSION_CONTROL) { - session_id: 32, - _body_, -} - -packet SessionControlResponse : UciResponse (gid = SESSION_CONTROL) { - _body_, +packet CorePacket : ControlPacket (gid = CORE) { + oid : CoreOpcodeId, + _reserved_ : 2, + _reserved_ : 16, + _payload_, } -packet SessionControlNotification : UciNotification (gid = SESSION_CONTROL) { - _body_, +packet SessionConfigPacket : ControlPacket (gid = SESSION_CONFIG) { + oid : SessionConfigOpcodeId, + _reserved_ : 2, + _reserved_ : 16, + _payload_, } -packet AndroidCommand : UciCommand (gid = VENDOR_ANDROID) { - _body_, +packet SessionControlPacket : ControlPacket (gid = SESSION_CONTROL) { + oid : SessionControlOpcodeId, + _reserved_ : 2, + _reserved_ : 16, + _payload_, } -packet AndroidResponse : UciResponse (gid = VENDOR_ANDROID) { - _body_, +packet AndroidPacket : ControlPacket (gid = VENDOR_ANDROID) { + oid : AndroidOpcodeId, + _reserved_ : 2, + _reserved_ : 16, + _payload_, } -packet AndroidNotification : UciNotification (gid = VENDOR_ANDROID) { - _body_, -} +// ---------------------------- Core group ---------------------------------- // -// TODO: b/202760099: Use the correspnding opcode enum instead of the raw value in the |opcode| field. -packet DeviceResetCmd : CoreCommand (opcode = 0x0) { //CORE_DEVICE_RESET +packet CoreDeviceResetCmd : CorePacket (mt = COMMAND, oid = DEVICE_RESET) { reset_config: ResetConfig, } -test DeviceResetCmd { +test CoreDeviceResetCmd { "\x20\x00\x00\x01\x00\x00\x00\x00", } -packet DeviceResetRsp : CoreResponse (opcode = 0x0) { //CORE_DEVICE_RESET - status: StatusCode, +packet CoreDeviceResetRsp : CorePacket (mt = RESPONSE, oid = DEVICE_RESET) { + status: Status, } -test DeviceResetRsp { +test CoreDeviceResetRsp { "\x40\x00\x00\x01\x00\x00\x00\x00", } -packet DeviceStatusNtf : CoreNotification (opcode = 0x1) { //CORE_DEVICE_STATUS_NTF +packet CoreDeviceStatusNtf : CorePacket (mt = NOTIFICATION, oid = DEVICE_STATUS) { device_state: DeviceState, } -test DeviceStatusNtf { +test CoreDeviceStatusNtf { "\x60\x01\x00\x01\x00\x00\x00\x01", } -packet GetDeviceInfoCmd : CoreCommand (opcode = 0x2) { //CORE_DEVICE_INFO +packet CoreGetDeviceInfoCmd : CorePacket (mt = COMMAND, oid = GET_DEVICE_INFO) { } -test GetDeviceInfoCmd { +test CoreGetDeviceInfoCmd { "\x20\x02\x00\x00\x00\x00\x00", } -packet GetDeviceInfoRsp : CoreResponse (opcode = 0x2) { //CORE_DEVICE_INFO - status: StatusCode, +packet CoreGetDeviceInfoRsp : CorePacket (mt = RESPONSE, oid = GET_DEVICE_INFO) { + status: Status, uci_version: 16, mac_version: 16, phy_version: 16, @@ -817,14 +753,14 @@ packet GetDeviceInfoRsp : CoreResponse (opcode = 0x2) { //CORE_DEVICE_INFO vendor_spec_info: 8[], } -test GetDeviceInfoRsp { +test CoreGetDeviceInfoRsp { "\x40\x02\x00\x0b\x00\x00\x00\x01\x01\x00\x02\x00\x03\x00\x04\x00\x01\x0a", } -packet GetCapsInfoCmd : CoreCommand (opcode = 0x3) { //CORE_GET_CAPS_INFO +packet CoreGetCapsInfoCmd : CorePacket (mt = COMMAND, oid = GET_CAPS_INFO) { } -test GetCapsInfoCmd { +test CoreGetCapsInfoCmd { "\x20\x03\x00\x00\x00\x00\x00", } @@ -835,13 +771,13 @@ struct CapTlv { } -packet GetCapsInfoRsp : CoreResponse (opcode = 0x3) { //CORE_GET_CAPS_INFO - status: StatusCode, +packet CoreGetCapsInfoRsp : CorePacket (mt = RESPONSE, oid = GET_CAPS_INFO) { + status: Status, _count_(tlvs): 8, tlvs: CapTlv[], } -test GetCapsInfoRsp { +test CoreGetCapsInfoRsp { "\x40\x03\x00\x05\x00\x00\x00\x00\x01\x00\x01\x01", } @@ -851,68 +787,68 @@ struct DeviceConfigTlv { v: 8[], } -packet SetConfigCmd : CoreCommand (opcode = 0x4) { //CORE_SET_CONFIG +packet CoreSetConfigCmd : CorePacket (mt = COMMAND, oid = SET_CONFIG) { _count_(tlvs): 8, tlvs: DeviceConfigTlv[], } -test SetConfigCmd { +test CoreSetConfigCmd { "\x20\x04\x00\x03\x00\x00\x00\x01\x01\x00", } struct DeviceConfigStatus { cfg_id: DeviceConfigId, - status: StatusCode, + status: Status, } -packet SetConfigRsp : CoreResponse (opcode = 0x4) { //CORE_SET_CONFIG - status: StatusCode, +packet CoreSetConfigRsp : CorePacket (mt = RESPONSE, oid = SET_CONFIG) { + status: Status, _count_(cfg_status): 8, cfg_status: DeviceConfigStatus[], } -test SetConfigRsp { +test CoreSetConfigRsp { "\x40\x04\x00\x04\x00\x00\x00\x01\x01\x01\x01", "\x40\x04\x00\x04\x00\x00\x00\x01\x01\x01\x0B", } -packet GetConfigCmd : CoreCommand (opcode = 0x5) { //CORE_GET_CONFIG +packet CoreGetConfigCmd : CorePacket (mt = COMMAND, oid = GET_CONFIG) { _count_(cfg_id): 8, cfg_id: 8[], // DeviceConfigId } -test GetConfigCmd { +test CoreGetConfigCmd { "\x20\x05\x00\x02\x00\x00\x00\x01\x01", } -packet GetConfigRsp : CoreResponse (opcode = 0x5) { //CORE_GET_CONFIG - status: StatusCode, +packet CoreGetConfigRsp : CorePacket (mt = RESPONSE, oid = GET_CONFIG) { + status: Status, _count_(tlvs): 8, tlvs: DeviceConfigTlv[] } -test GetConfigRsp { +test CoreGetConfigRsp { "\x40\x05\x00\x05\x00\x00\x00\x01\x01\x00\x01\x01", } -packet GenericError : CoreNotification (opcode = 0x7) { //CORE_GENERIC_ERROR_NTF - status: StatusCode, +packet CoreGenericErrorNtf : CorePacket (mt = NOTIFICATION, oid = GENERIC_ERROR) { + status: Status, } -test GenericError { +test CoreGenericErrorNtf { "\x60\x07\x00\x01\x00\x00\x00\x01", } -packet CoreQueryTimeStampCmd : CoreCommand (opcode = 0x8) { //CORE_QUERY_UWBS_TIMESTAMP +packet CoreQueryTimeStampCmd : CorePacket (mt = COMMAND, oid = QUERY_UWBS_TIMESTAMP) { } test CoreQueryTimeStampCmd { "\x20\x08\x00\\x00", } -packet CoreQueryTimeStampRsp : CoreResponse (opcode = 0x8) { //CORE_QUERY_UWBS_TIMESTAMP - status: StatusCode, +packet CoreQueryTimeStampRsp : CorePacket (mt = RESPONSE, oid = QUERY_UWBS_TIMESTAMP) { + status: Status, timeStamp: 64, } @@ -920,7 +856,9 @@ test CoreQueryTimeStampRsp { "\x40\x08\x00\x09\x00\x00\x00\x01\x01\x00\x01\x01\x01", } -packet SessionInitCmd : SessionConfigCommand (opcode = 0x0) { //SESSION_INIT +// ---------------------- Session Config group ------------------------------ // + +packet SessionInitCmd : SessionConfigPacket (mt = COMMAND, oid = INIT) { session_id: 32, session_type: SessionType, } @@ -931,8 +869,8 @@ test SessionInitCmd { // FIRA version 2 introduces a new version of SESSION_INIT_RSP which // includes UWBS generated session handle. -packet SessionInitRsp_V2 : SessionConfigResponse (opcode = 0x0) { //SESSION_INIT - status: StatusCode, +packet SessionInitRsp_V2 : SessionConfigPacket (mt = RESPONSE, oid = INIT) { + status: Status, session_handle: 32, } @@ -940,15 +878,15 @@ test SessionInitRsp_V2 { "\x41\x00\x00\x01\x00\x00\x00\x11\x00\x00\x00\x01", } -packet SessionInitRsp : SessionConfigResponse (opcode = 0x0) { //SESSION_INIT - status: StatusCode, +packet SessionInitRsp : SessionConfigPacket (mt = RESPONSE, oid = INIT) { + status: Status, } test SessionInitRsp { "\x41\x00\x00\x01\x00\x00\x00\x11", } -packet SessionDeinitCmd : SessionConfigCommand (opcode = 0x1) { //SESSION_DEINIT +packet SessionDeinitCmd : SessionConfigPacket (mt = COMMAND, oid = DEINIT) { session_token: 32, // Session ID or Session Handle (based on UWBS version) } @@ -956,15 +894,15 @@ test SessionDeinitCmd { "\x21\x01\x00\x04\x00\x00\x00\x01\x02\x03\x04", } -packet SessionDeinitRsp : SessionConfigResponse (opcode = 0x1) { //SESSION_DEINIT - status: StatusCode, +packet SessionDeinitRsp : SessionConfigPacket (mt = RESPONSE, oid = DEINIT) { + status: Status, } test SessionDeinitRsp { "\x41\x01\x00\x01\x00\x00\x00\x00", } -packet SessionStatusNtf : SessionConfigNotification (opcode = 0x2) { //SESSION_STATUS_NTF +packet SessionStatusNtf : SessionConfigPacket (mt = NOTIFICATION, oid = STATUS) { session_token: 32, // Session ID or Session Handle (based on UWBS version) session_state: SessionState, // TODO(b/272775225): Switch back to the enum type ReasonCode, once PDL supports defining a @@ -983,7 +921,7 @@ struct AppConfigTlv { v: 8[], } -packet SessionSetAppConfigCmd : SessionConfigCommand (opcode = 0x3) { //SESSION_SET_APP_CONFIG +packet SessionSetAppConfigCmd : SessionConfigPacket (mt = COMMAND, oid = SET_APP_CONFIG) { session_token: 32, // Session ID or Session Handle (based on UWBS version) _count_(tlvs): 8, tlvs: AppConfigTlv[] @@ -995,11 +933,11 @@ test SessionSetAppConfigCmd { struct AppConfigStatus { cfg_id: AppConfigTlvType, - status: StatusCode, + status: Status, } -packet SessionSetAppConfigRsp : SessionConfigResponse (opcode = 0x3) { //SESSION_SET_APP_CONFIG - status: StatusCode, +packet SessionSetAppConfigRsp : SessionConfigPacket (mt = RESPONSE, oid = SET_APP_CONFIG) { + status: Status, _count_(cfg_status): 8, cfg_status: AppConfigStatus[], } @@ -1008,7 +946,7 @@ test SessionSetAppConfigRsp { "\x41\x03\x00\x04\x00\x00\x00\x01\x01\x01\x00", } -packet SessionGetAppConfigCmd : SessionConfigCommand (opcode = 0x4) { //SESSION_GET_APP_CONFIG +packet SessionGetAppConfigCmd : SessionConfigPacket (mt = COMMAND, oid = GET_APP_CONFIG) { session_token: 32, // Session ID or Session Handle (based on UWBS version) _count_(app_cfg): 8, app_cfg: AppConfigTlvType[], @@ -1018,8 +956,8 @@ test SessionGetAppConfigCmd { "\x21\x04\x00\x05\x00\x00\x00\x01\x02\x03\x04\x00", } -packet SessionGetAppConfigRsp : SessionConfigResponse (opcode = 0x4) { //SESSION_GET_APP_CONFIG - status: StatusCode, +packet SessionGetAppConfigRsp : SessionConfigPacket (mt = RESPONSE, oid = GET_APP_CONFIG) { + status: Status, _count_(tlvs): 8, tlvs: AppConfigTlv[], } @@ -1028,15 +966,15 @@ test SessionGetAppConfigRsp { "\x41\x04\x00\x02\x00\x00\x00\x01\x00", } -packet SessionGetCountCmd : SessionConfigCommand (opcode = 0x5) { //SESSION_GET_COUNT +packet SessionGetCountCmd : SessionConfigPacket (mt = COMMAND, oid = GET_COUNT) { } test SessionGetCountCmd { "\x21\x05\x00\x00\x00\x00\x00", } -packet SessionGetCountRsp : SessionConfigResponse (opcode = 0x5) { //SESSION_GET_COUNT - status: StatusCode, +packet SessionGetCountRsp : SessionConfigPacket (mt = RESPONSE, oid = GET_COUNT) { + status: Status, session_count: 8, } @@ -1044,7 +982,7 @@ test SessionGetCountRsp { "\x41\x05\x00\x02\x00\x00\x00\x00\x01", } -packet SessionGetStateCmd : SessionConfigCommand (opcode = 0x6) { //SESSION_GET_STATE +packet SessionGetStateCmd : SessionConfigPacket (mt = COMMAND, oid = GET_STATE) { session_token: 32, // Session ID or Session Handle (based on UWBS version) } @@ -1052,8 +990,8 @@ test SessionGetStateCmd { "\x21\x06\x00\x04\x00\x00\x00\x00\x01\x02\x03", } -packet SessionGetStateRsp : SessionConfigResponse (opcode = 0x6) { //SESSION_GET_STATE - status: StatusCode, +packet SessionGetStateRsp : SessionConfigPacket (mt = RESPONSE, oid = GET_STATE) { + status: Status, session_state: SessionState, } @@ -1061,7 +999,15 @@ test SessionGetStateRsp { "\x41\x06\x00\x02\x00\x00\x00\x00\x01", } -packet SessionUpdateDtTagRangingRoundsCmd : SessionConfigCommand (opcode = 0x9) { //SESSION_UPDATE_ACTIVE_ROUNDS_DT_TAG +packet SessionUpdateDtAnchorRangingRoundsCmd : SessionConfigPacket (mt = COMMAND, oid = UPDATE_DT_ANCHOR_RANGING_ROUNDS) { + // TODO +} + +packet SessionUpdateDtAnchorRangingRoundsRsp : SessionConfigPacket (mt = RESPONSE, oid = UPDATE_DT_ANCHOR_RANGING_ROUNDS) { + // TODO +} + +packet SessionUpdateDtTagRangingRoundsCmd : SessionConfigPacket (mt = COMMAND, oid = UPDATE_DT_TAG_RANGING_ROUNDS) { session_token: 32, // Session ID or Session Handle (based on UWBS version) _count_(ranging_round_indexes): 8, ranging_round_indexes: 8[], @@ -1071,8 +1017,8 @@ test SessionUpdateDtTagRangingRoundsCmd { "\x21\x09\x00\x0a\x00\x00\x00\x03\x03\x0f\x0c\x05\x08\x00\x00\x00\x00", } -packet SessionUpdateDtTagRangingRoundsRsp : SessionConfigResponse (opcode = 0x9) { //SESSION_UPDATE_ACTIVE_ROUNDS_DT_TAG - status: StatusCode, +packet SessionUpdateDtTagRangingRoundsRsp : SessionConfigPacket (mt = RESPONSE, oid = UPDATE_DT_TAG_RANGING_ROUNDS) { + status: Status, _count_(ranging_round_indexes): 8, ranging_round_indexes: 8[], } @@ -1106,29 +1052,12 @@ enum UpdateMulticastListAction: 8 { ADD_CONTROLEE_WITH_EXTENDED_SUB_SESSION_KEY = 0x03, } -packet SessionUpdateControllerMulticastListCmd : SessionConfigCommand (opcode = 0x7) { //SESSION_UPDATE_CONTROLLER_MULTICAST_LIST +packet SessionUpdateControllerMulticastListCmd : SessionConfigPacket (mt = COMMAND, oid = UPDATE_CONTROLLER_MULTICAST_LIST) { session_token: 32, // Session ID or Session Handle (based on UWBS version) action: UpdateMulticastListAction, _payload_, } -struct PhaseList { - session_token: 32, - start_slot_index: 16, - end_slot_index: 16, -} - -packet SessionSetHybridConfigCmd : SessionConfigCommand (opcode = 0x0c) { //SESSION_SET_HUS_CONFIG - session_token: 32, - number_of_phases: 8, - update_time: 8[8], - phase_list: PhaseList[], -} - -packet SessionSetHybridConfigRsp : SessionConfigResponse (opcode = 0x0c) { //SESSION_SET_HUS_CONFIG - status: StatusCode, -} - struct SessionUpdateControllerMulticastListCmdPayload { _count_(controlees): 8, controlees: Controlee[], @@ -1144,8 +1073,8 @@ struct SessionUpdateControllerMulticastListCmd_2_0_32_Byte_Payload { controlees: Controlee_V2_0_32_Byte_Version[], } -packet SessionUpdateControllerMulticastListRsp : SessionConfigResponse (opcode = 0x7) { //SESSION_UPDATE_CONTROLLER_MULTICAST_LIST - status: StatusCode, +packet SessionUpdateControllerMulticastListRsp : SessionConfigPacket (mt = RESPONSE, oid = UPDATE_CONTROLLER_MULTICAST_LIST) { + status: Status, } test SessionUpdateControllerMulticastListRsp { @@ -1158,7 +1087,7 @@ struct ControleeStatus { status: MulticastUpdateStatusCode, } -packet SessionUpdateControllerMulticastListNtf : SessionConfigNotification (opcode = 0x7) { //SESSION_UPDATE_CONTROLLER_MULTICAST_LIST +packet SessionUpdateControllerMulticastListNtf : SessionConfigPacket (mt = NOTIFICATION, oid = UPDATE_CONTROLLER_MULTICAST_LIST) { session_token: 32, // Session ID or Session Handle (based on UWBS version) remaining_multicast_list_size: 8, _count_(controlee_status): 8, @@ -1169,52 +1098,55 @@ test SessionUpdateControllerMulticastListNtf { "\x61\x07\x00\x06\x00\x00\x00\x00\x01\x02\x03\x04\x00", } -packet DataCreditNtf : SessionControlNotification (opcode = 0x04) { // SESSION_DATA_CREDIT_NTF +// ---------------------- Session Control group ----------------------------- // + +packet SessionDataCreditNtf : SessionControlPacket (mt = NOTIFICATION, oid = DATA_CREDIT) { session_token: 32, // Session ID or Session Handle (based on UWBS version) credit_availability: CreditAvailability, } -test DataCreditNtf { +test SessionDataCreditNtf { "\x62\x04\x00\x05\x00\x00\x00\x00\x00\x00\x01\x01", } -packet DataTransferStatusNtf : SessionControlNotification (opcode = 0x05) { // SESSION_DATA_TRANSFER_STATUS_NTF +packet SessionDataTransferStatusNtf : SessionControlPacket (mt = NOTIFICATION, oid = DATA_TRANSFER_STATUS) { session_token: 32, // Session ID or Session Handle (based on UWBS version) uci_sequence_number: 8, status: DataTransferNtfStatusCode, tx_count: 8, } -test DataTransferStatusNtf { +test SessionDataTransferStatusNtf { "\x62\x05\x00\x06\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00", } -packet SessionQueryMaxDataSizeCmd : SessionConfigCommand (opcode = 0xB) { //QUERY_MAX_DATA_SIZE +packet SessionQueryMaxDataSizeInRangingCmd : SessionConfigPacket (mt = COMMAND, oid = QUERY_DATA_SIZE_IN_RANGING) { session_token: 32, // Session ID or Session Handle (based on UWBS version) } -test SessionQueryMaxDataSizeCmd { +test SessionQueryMaxDataSizeInRangingCmd { "\x21\x0B\x00\x04\x00\x00\x00\x00", } -packet SessionQueryMaxDataSizeRsp : SessionConfigResponse (opcode = 0xB) { //QUER_MAX_DATA_SIZE +packet SessionQueryMaxDataSizeInRangingRsp : SessionConfigPacket (mt = RESPONSE, oid = QUERY_DATA_SIZE_IN_RANGING) { session_token: 32, // Session ID or Session Handle (based on UWBS version) max_data_size: 16, } -test SessionQueryMaxDataSizeRsp { +test SessionQueryMaxDataSizeInRangingRsp { "\x41\x0B\x00\x06\x00\x00\x00\x00\x0E7\0x07", } -packet SessionStartCmd : SessionControlCommand (opcode = 0x0) { //RANGE_START +packet SessionStartCmd : SessionControlPacket (mt = COMMAND, oid = START) { + session_id: 32, } test SessionStartCmd { "\x22\x00\x00\x04\x00\x00\x00\x00\x01\x02\x03", } -packet SessionStartRsp : SessionControlResponse (opcode = 0x0) { //RANGE_START - status: StatusCode, +packet SessionStartRsp : SessionControlPacket (mt = RESPONSE, oid = START) { + status: Status, } test SessionStartRsp { @@ -1223,7 +1155,7 @@ test SessionStartRsp { struct ShortAddressTwoWayRangingMeasurement { mac_address: 16, - status: StatusCode, + status: Status, nlos: 8, distance: 16, aoa_azimuth: 16, @@ -1245,7 +1177,7 @@ struct ShortAddressTwoWayRangingMeasurement { struct ExtendedAddressTwoWayRangingMeasurement { mac_address: 64, - status: StatusCode, + status: Status, nlos: 8, distance: 16, aoa_azimuth: 16, @@ -1263,7 +1195,7 @@ struct ExtendedAddressTwoWayRangingMeasurement { struct ShortAddressOwrAoaRangingMeasurement { mac_address: 16, - status: StatusCode, + status: Status, nlos: 8, frame_sequence_number: 8, block_index: 16, @@ -1275,7 +1207,7 @@ struct ShortAddressOwrAoaRangingMeasurement { struct ExtendedAddressOwrAoaRangingMeasurement { mac_address: 64, - status: StatusCode, + status: Status, nlos: 8, frame_sequence_number: 8, block_index: 16, @@ -1292,7 +1224,7 @@ enum RangingMeasurementType : 8 { OWR_AOA = 0x03, } -packet SessionInfoNtf : SessionControlNotification (opcode = 0x0) { // SESSION_INFO +packet SessionInfoNtf : SessionControlPacket (mt = NOTIFICATION, oid = START) { sequence_number: 32, session_token: 32, // Session ID or Session Handle (based on UWBS version) rcr_indicator: 8, @@ -1364,30 +1296,32 @@ test ExtendedMacOwrAoaSessionInfoNtf { "\x62\x00\x00\x2c\x00\x00\x00\x00\x02\x03\x04\x05\x06\x07\x08\x00\x0a\x01\x01\x01\x03\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\xaa\xbb\xcc\xdd\x01\x02\x03\x04\x00\x00\x01\x01\x00\x03\x04\x60\x05\x06\x50", } -packet SessionStopCmd : SessionControlCommand (opcode = 0x1) { // SESSION_STOP +packet SessionStopCmd : SessionControlPacket (mt = COMMAND, oid = STOP) { + session_id: 32, } test SessionStopCmd { "\x22\x01\x00\x04\x00\x00\x00\x00\x02\x03\x04", } -packet SessionStopRsp : SessionControlResponse (opcode = 0x1) { // SESSION_STOP - status: StatusCode, +packet SessionStopRsp : SessionControlPacket (mt = RESPONSE, oid = STOP) { + status: Status, } test SessionStopRsp { "\x42\x01\x00\x01\x00\x00\x00\x00", } -packet SessionGetRangingCountCmd : SessionControlCommand (opcode = 0x3) { // SESSION_GET_RANGING_COUNT +packet SessionGetRangingCountCmd : SessionControlPacket (mt = COMMAND, oid = GET_RANGING_COUNT) { + session_id: 32, } test SessionGetRangingCountCmd { "\x22\x03\x00\x04\x00\x00\x00\x00\x02\x03\x04", } -packet SessionGetRangingCountRsp : SessionControlResponse (opcode = 0x3) { // SESSION_GET_RANGING_COUNT - status: StatusCode, +packet SessionGetRangingCountRsp : SessionControlPacket (mt = RESPONSE, oid = GET_RANGING_COUNT) { + status: Status, count: 32, } @@ -1395,7 +1329,9 @@ test SessionGetRangingCountRsp { "\x42\x03\x00\x05\x00\x00\x00\x00\x02\x03\x04\x05", } -packet AndroidGetPowerStatsCmd: AndroidCommand (opcode = 0x0) { //ANDROID_GET_POWER_STATS +// -------------------------- Android group --------------------------------- // + +packet AndroidGetPowerStatsCmd : AndroidPacket (mt = COMMAND, oid = GET_POWER_STATS) { } test AndroidGetPowerStatsCmd { @@ -1403,14 +1339,14 @@ test AndroidGetPowerStatsCmd { } struct PowerStats { - status: StatusCode, + status: Status, idle_time_ms: 32, tx_time_ms: 32, rx_time_ms: 32, total_wake_count:32, } -packet AndroidGetPowerStatsRsp : AndroidResponse (opcode = 0x0) { //ANDROID_GET_POWER_STATS +packet AndroidGetPowerStatsRsp : AndroidPacket (mt = RESPONSE, oid = GET_POWER_STATS) { stats: PowerStats, } @@ -1418,7 +1354,7 @@ test AndroidGetPowerStatsRsp { "\x4c\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", } -packet AndroidSetCountryCodeCmd: AndroidCommand (opcode = 0x1) { //ANDROID_SET_COUNTRY_CODE +packet AndroidSetCountryCodeCmd: AndroidPacket (mt = COMMAND, oid = SET_COUNTRY_CODE) { country_code : 8[2], } @@ -1427,8 +1363,8 @@ test AndroidSetCountryCodeCmd { "\x2c\x01\x00\x02\x00\x00\x00\x55\x53", } -packet AndroidSetCountryCodeRsp : AndroidResponse (opcode = 0x1) { //ANDROID_SET_COUNTRY_CODE - status: StatusCode, +packet AndroidSetCountryCodeRsp : AndroidPacket (mt = RESPONSE, oid = SET_COUNTRY_CODE) { + status: Status, } test AndroidSetCountryCodeRsp { @@ -1503,7 +1439,7 @@ struct FrameReport { frame_report_tlvs: FrameReportTlv[], } -packet AndroidRangeDiagnosticsNtf : AndroidNotification (opcode = 0x2) { //FIRA_RANGE_DIAGNOSTICS +packet AndroidRangeDiagnosticsNtf : AndroidPacket (mt = NOTIFICATION, oid = FIRA_RANGE_DIAGNOSTICS) { session_token: 32, // Session ID or Session Handle (based on UWBS version) sequence_number: 32, _count_(frame_reports): 8, @@ -1514,67 +1450,3 @@ test AndroidRangeDiagnosticsNtf { "\x6c\x02\x00\x11\x00\x00\x00\x01\x01\x01\x01\x02\x02\x02\x02\x01\x00\x01\x02\x01\x00\x01\x00\x00", "\x6c\x02\x00\x34\x00\x00\x00\x01\x01\x01\x01\x02\x02\x02\x02\x01\x00\x01\x02\x03\x01\x08\x00\x01\x02\x01\x02\x01\x02\x01\x01\x02\x15\x00\x01\x01\x02\x01\x02\x01\x02\x01\x02\x01\x02\x01\x02\x00\x02\x04\x00\x01\x02\x03\x04\x00\x01\x00\x00", } - -packet UciVendor_9_Command : UciCommand (gid = VENDOR_RESERVED_9) { - _payload_, -} - -packet UciVendor_A_Command : UciCommand (gid = VENDOR_RESERVED_A) { - _payload_, -} - -packet UciVendor_B_Command : UciCommand (gid = VENDOR_RESERVED_B) { - _payload_, -} - -packet UciVendor_E_Command : UciCommand (gid = VENDOR_RESERVED_E) { - _payload_, -} - -packet UciVendor_F_Command : UciCommand (gid = VENDOR_RESERVED_F) { - _payload_, -} - -packet UciVendor_9_Response : UciResponse (gid = VENDOR_RESERVED_9) { - _payload_, -} - -packet UciVendor_A_Response : UciResponse (gid = VENDOR_RESERVED_A) { - _payload_, -} - -packet UciVendor_B_Response : UciResponse (gid = VENDOR_RESERVED_B) { - _payload_, -} - -packet UciVendor_E_Response : UciResponse (gid = VENDOR_RESERVED_E) { - _payload_, -} - -packet UciVendor_F_Response : UciResponse (gid = VENDOR_RESERVED_F) { - _payload_, -} - -packet UciVendor_9_Notification : UciNotification (gid = VENDOR_RESERVED_9) { - _payload_, -} - -packet UciVendor_A_Notification : UciNotification (gid = VENDOR_RESERVED_A) { - _payload_, -} - -packet UciVendor_B_Notification : UciNotification (gid = VENDOR_RESERVED_B) { - _payload_, -} - -packet UciVendor_E_Notification : UciNotification (gid = VENDOR_RESERVED_E) { - _payload_, -} - -packet UciVendor_F_Notification : UciNotification (gid = VENDOR_RESERVED_F) { - _payload_, -} - -packet TestNotification : UciNotification (gid = TEST) { - _payload_, -} -- cgit v1.2.3 From 0e22b87562e0848046fe81dbec558dd792e6903b Mon Sep 17 00:00:00 2001 From: Henri Chataing Date: Wed, 20 Mar 2024 11:24:48 -0700 Subject: Sync definitions for UCI status codes with UCI 2.0 --- src/device.rs | 30 +++++++++++++++--------------- src/uci_packets.pdl | 35 +++++++++++++---------------------- 2 files changed, 28 insertions(+), 37 deletions(-) diff --git a/src/device.rs b/src/device.rs index 390a29a..d304be7 100644 --- a/src/device.rs +++ b/src/device.rs @@ -313,13 +313,13 @@ impl Device { log::debug!(" session_type={:?}", session_type); let status = if self.sessions.len() >= MAX_SESSION { - uci::Status::MaxSessionsExceeded + uci::Status::ErrorMaxSessionsExceeded } else { match self.sessions.insert( session_id, Session::new(session_id, session_type, self.handle, self.tx.clone()), ) { - Some(_) => uci::Status::SessionDuplicate, + Some(_) => uci::Status::ErrorSessionDuplicate, None => { // Should not fail self.session_mut(session_id).unwrap().init(); @@ -347,7 +347,7 @@ impl Device { self.sessions.remove(&session_id); uci::Status::Ok } - None => uci::Status::SessionNotExist, + None => uci::Status::ErrorSessionNotExist, }; SessionDeinitRspBuilder { status }.build() } @@ -374,7 +374,7 @@ impl Device { let Some(session) = self.sessions.get_mut(&session_handle) else { return SessionSetAppConfigRspBuilder { cfg_status: Vec::new(), - status: uci::Status::SessionNotExist, + status: uci::Status::ErrorSessionNotExist, } .build(); }; @@ -392,7 +392,7 @@ impl Device { .any(|cfg| IMMUTABLE_PARAMETERS.contains(&cfg.cfg_id)) { return SessionSetAppConfigRspBuilder { - status: uci::Status::SessionActive, + status: uci::Status::ErrorSessionActive, cfg_status: vec![], } .build(); @@ -479,7 +479,7 @@ impl Device { let Some(session) = self.sessions.get(&session_handle) else { return SessionGetAppConfigRspBuilder { tlvs: vec![], - status: uci::Status::SessionNotExist, + status: uci::Status::ErrorSessionNotExist, } .build(); }; @@ -522,7 +522,7 @@ impl Device { let Some(session) = self.sessions.get(&session_handle) else { return SessionGetStateRspBuilder { session_state: SessionState::SessionStateInit, - status: uci::Status::SessionNotExist, + status: uci::Status::ErrorSessionNotExist, } .build(); }; @@ -548,7 +548,7 @@ impl Device { let Some(session) = self.sessions.get_mut(&session_handle) else { return SessionUpdateControllerMulticastListRspBuilder { - status: uci::Status::SessionNotExist, + status: uci::Status::ErrorSessionNotExist, } .build(); }; @@ -631,7 +631,7 @@ impl Device { let mut update_status = MulticastUpdateStatusCode::StatusOkMulticastListUpdate; if !dst_addresses.contains(&controlee.short_address) { if dst_addresses.len() == MAX_NUMBER_OF_CONTROLEES { - status = uci::Status::MulticastListFull; + status = uci::Status::ErrorMulticastListFull; update_status = MulticastUpdateStatusCode::StatusErrorMulticastListFull; } else if (action == UpdateMulticastListAction::AddControleeWithShortSubSessionKey @@ -668,7 +668,7 @@ impl Device { let attempt_count = session.app_config.in_band_termination_attempt_count; let mut update_status = MulticastUpdateStatusCode::StatusOkMulticastListUpdate; if !dst_addresses.contains(&address) { - status = uci::Status::AddressNotFound; + status = uci::Status::Failed; update_status = MulticastUpdateStatusCode::StatusErrorKeyFetchFail; } else { dst_addresses.retain(|value| *value != address); @@ -732,14 +732,14 @@ impl Device { let Some(session) = self.sessions.get_mut(&session_id) else { return SessionStartRspBuilder { - status: uci::Status::SessionNotExist, + status: uci::Status::ErrorSessionNotExist, } .build(); }; if session.state != SessionState::SessionStateIdle { return SessionStartRspBuilder { - status: uci::Status::SessionNotConfigured, + status: uci::Status::ErrorSessionNotConfigured, } .build(); } @@ -781,14 +781,14 @@ impl Device { let Some(session) = self.sessions.get_mut(&session_id) else { return SessionStopRspBuilder { - status: uci::Status::SessionNotExist, + status: uci::Status::ErrorSessionNotExist, } .build(); }; if session.state != SessionState::SessionStateActive { return SessionStopRspBuilder { - status: uci::Status::SessionActive, + status: uci::Status::ErrorSessionActive, } .build(); } @@ -824,7 +824,7 @@ impl Device { let Some(session) = self.sessions.get(&session_id) else { return SessionGetRangingCountRspBuilder { - status: uci::Status::SessionNotExist, + status: uci::Status::ErrorSessionNotExist, count: 0, } .build(); diff --git a/src/uci_packets.pdl b/src/uci_packets.pdl index 7d1fc2e..e4403cb 100644 --- a/src/uci_packets.pdl +++ b/src/uci_packets.pdl @@ -84,6 +84,7 @@ enum AndroidOpcodeId : 6 { FIRA_RANGE_DIAGNOSTICS = 0x02, } +/// [UCI] 8.5 Status Codes enum Status : 8 { // Generic Status Codes OK = 0x00, @@ -92,28 +93,24 @@ enum Status : 8 { SYNTAX_ERROR = 0x03, INVALID_PARAM = 0x04, INVALID_RANGE = 0x05, - INVALID_MSG_SIZE = 0x06, + INVALID_MESSAGE_SIZE = 0x06, UNKNOWN_GID = 0x07, UNKNOWN_OID = 0x08, READ_ONLY = 0x09, - COMMAND_RETRY = 0x0A, + UCI_MESSAGE_RETRY = 0x0A, UNKNOWN = 0x0B, NOT_APPLICABLE = 0x0C, - RFU_STATUS_CODE_RANGE_1 = 0x0D..0x10, // UWB Session Specific Status Codes - SESSION_NOT_EXIST = 0x11, - SESSION_DUPLICATE = 0x12, - SESSION_ACTIVE = 0x13, - MAX_SESSIONS_EXCEEDED = 0x14, - SESSION_NOT_CONFIGURED = 0x15, - ACTIVE_SESSIONS_ONGOING = 0x16, - MULTICAST_LIST_FULL = 0x17, - ADDRESS_NOT_FOUND = 0x18, - ADDRESS_ALREADY_PRESENT = 0x19, + ERROR_SESSION_NOT_EXIST = 0x11, + ERROR_SESSION_DUPLICATE = 0x12, + ERROR_SESSION_ACTIVE = 0x13, + ERROR_MAX_SESSIONS_EXCEEDED = 0x14, + ERROR_SESSION_NOT_CONFIGURED = 0x15, + ERROR_ACTIVE_SESSIONS_ONGOING = 0x16, + ERROR_MULTICAST_LIST_FULL = 0x17, ERROR_UWB_INITIATION_TIME_TOO_OLD = 0x1A, OK_NEGATIVE_DISTANCE_REPORT = 0x1B, - RFU_STATUS_CODE_RANGE_2 = 0x1C..0x1F, // UWB Ranging Session Specific Status Codes RANGING_TX_FAILED = 0x20, @@ -127,23 +124,17 @@ enum Status : 8 { ERROR_ROUND_INDEX_NOT_ACTIVATED = 0x28, ERROR_NUMBER_OF_ACTIVE_RANGING_ROUNDS_EXCEEDED = 0x29, ERROR_DL_TDOA_DEVICE_ADDRESS_NOT_MATCHING_IN_REPLY_TIME_LIST = 0x2A, - RFU_STATUS_CODE_RANGE_3 = 0x2B..0x2F, - - // UWB Data Session Specific Status Codes - DATA_MAX_TX_PSDU_SIZE_EXCEEDED = 0x30, - DATA_RX_CRC_ERROR = 0x31, - RFU_STATUS_CODE_RANGE_4 = 0x32..0x4F, // Vendor Specific Status Codes - VENDOR_SPECIFIC_STATUS_CODE_RANGE_1 = 0x50..0xFE { + VENDOR_SPECIFIC = 0x50..0xFF { ERROR_CCC_SE_BUSY = 0x50, ERROR_CCC_LIFECYCLE = 0x51, ERROR_STOPPED_DUE_TO_OTHER_SESSION_CONFLICT = 0x52, REGULATION_UWB_OFF = 0x53, }, - // For internal usage, we will use 0xFF as default. - VENDOR_SPECIFIC_STATUS_CODE_2 = 0xFF, + // All others reserved for future use + RFU = .., } // This needs a separate StatusCode as the Status code values in the DATA_RCV packet have -- cgit v1.2.3 From 45c2327092a065d4e8c675e14e62dfbfe99fd47c Mon Sep 17 00:00:00 2001 From: Henri Chataing Date: Wed, 20 Mar 2024 11:29:18 -0700 Subject: Sync definitions for UCI Multicast Update Status with UCI 2.0 All definitions after ERROR_SUB_SESSION_KEY_NOT_FOUND were offset by 1 --- src/device.rs | 11 +++++------ src/uci_packets.pdl | 22 ++++++++++++---------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/device.rs b/src/device.rs index d304be7..449ec7b 100644 --- a/src/device.rs +++ b/src/device.rs @@ -628,11 +628,11 @@ impl Device { | UpdateMulticastListAction::AddControleeWithShortSubSessionKey | UpdateMulticastListAction::AddControleeWithExtendedSubSessionKey => { new_controlees.iter().for_each(|controlee| { - let mut update_status = MulticastUpdateStatusCode::StatusOkMulticastListUpdate; + let mut update_status = MulticastUpdateStatus::OkMulticastListUpdate; if !dst_addresses.contains(&controlee.short_address) { if dst_addresses.len() == MAX_NUMBER_OF_CONTROLEES { status = uci::Status::ErrorMulticastListFull; - update_status = MulticastUpdateStatusCode::StatusErrorMulticastListFull; + update_status = MulticastUpdateStatus::ErrorMulticastListFull; } else if (action == UpdateMulticastListAction::AddControleeWithShortSubSessionKey || action @@ -645,8 +645,7 @@ impl Device { // with Status set to STATUS_ERROR_SUB_SESSION_KEY_NOT_APPLICABLE for each // Controlee in the Controlee List. status = uci::Status::Failed; - update_status = - MulticastUpdateStatusCode::StatusErrorSubSessionKeyNotApplicable; + update_status = MulticastUpdateStatus::ErrorSubSessionKeyNotApplicable; } else { dst_addresses.push(controlee.short_address); }; @@ -666,10 +665,10 @@ impl Device { let pica_tx = self.pica_tx.clone(); let address = controlee.short_address; let attempt_count = session.app_config.in_band_termination_attempt_count; - let mut update_status = MulticastUpdateStatusCode::StatusOkMulticastListUpdate; + let mut update_status = MulticastUpdateStatus::OkMulticastListUpdate; if !dst_addresses.contains(&address) { status = uci::Status::Failed; - update_status = MulticastUpdateStatusCode::StatusErrorKeyFetchFail; + update_status = MulticastUpdateStatus::ErrorKeyFetchFail; } else { dst_addresses.retain(|value| *value != address); // If IN_BAND_TERMINATION_ATTEMPT_COUNT is not equal to 0x00, then the diff --git a/src/uci_packets.pdl b/src/uci_packets.pdl index e4403cb..06fd9b0 100644 --- a/src/uci_packets.pdl +++ b/src/uci_packets.pdl @@ -585,15 +585,17 @@ enum ReasonCode : 8 { VENDOR_SPECIFIC_REASON_CODE_2 = 0xFF, } -enum MulticastUpdateStatusCode : 8 { - STATUS_OK_MULTICAST_LIST_UPDATE = 0x00, - STATUS_ERROR_MULTICAST_LIST_FULL = 0x01, - STATUS_ERROR_KEY_FETCH_FAIL = 0x02, - STATUS_ERROR_SUB_SESSION_ID_NOT_FOUND = 0x03, - STATUS_ERROR_SUB_SESSION_KEY_NOT_FOUND = 0x05, - STATUS_ERROR_SUB_SESSION_KEY_NOT_APPLICABLE = 0x06, - STATUS_ERROR_SESSION_KEY_NOT_FOUND = 0x07, - STATUS_ERROR_ADDRESS_ALREADY_PRESENT = 0x08, +/// [UCI] Table 40: Multicast list update status codes +enum MulticastUpdateStatus : 8 { + OK_MULTICAST_LIST_UPDATE = 0x00, + ERROR_MULTICAST_LIST_FULL = 0x01, + ERROR_KEY_FETCH_FAIL = 0x02, + ERROR_SUB_SESSION_ID_NOT_FOUND = 0x03, + ERROR_SUB_SESSION_KEY_NOT_FOUND = 0x04, + ERROR_SUB_SESSION_KEY_NOT_APPLICABLE = 0x05, + ERROR_SESSION_KEY_NOT_FOUND = 0x06, + ERROR_ADDRESS_NOT_FOUND = 0x07, + ERROR_ADDRESS_ALREADY_PRESENT = 0x08, } enum MacAddressIndicator : 8 { @@ -1075,7 +1077,7 @@ test SessionUpdateControllerMulticastListRsp { struct ControleeStatus { mac_address: 8[2], subsession_id: 32, - status: MulticastUpdateStatusCode, + status: MulticastUpdateStatus, } packet SessionUpdateControllerMulticastListNtf : SessionConfigPacket (mt = NOTIFICATION, oid = UPDATE_CONTROLLER_MULTICAST_LIST) { -- cgit v1.2.3 From b5006afdc4533af8fa4d5e6e0674f2a6a5c1af45 Mon Sep 17 00:00:00 2001 From: Henri Chataing Date: Wed, 27 Mar 2024 17:12:42 -0700 Subject: Review the implementation of Core Set Config and Core Get Config --- py/pica/pica/packets/uci.py | 5927 +++++++++++++++++-------------------------- src/device.rs | 149 +- src/lib.rs | 2 +- src/mac_address.rs | 14 +- src/uci_packets.pdl | 40 +- tests/data_transfer.py | 40 +- tests/helper.py | 8 +- tests/ranging.py | 28 +- 8 files changed, 2544 insertions(+), 3664 deletions(-) diff --git a/py/pica/pica/packets/uci.py b/py/pica/pica/packets/uci.py index 0043c45..574feab 100644 --- a/py/pica/pica/packets/uci.py +++ b/py/pica/pica/packets/uci.py @@ -94,16 +94,30 @@ class PacketBoundaryFlag(enum.IntEnum): raise exn +class MessageType(enum.IntEnum): + DATA = 0x0 + COMMAND = 0x1 + RESPONSE = 0x2 + NOTIFICATION = 0x3 + + @staticmethod + def from_int(v: int) -> Union[int, 'MessageType']: + try: + return MessageType(v) + except ValueError as exn: + raise exn + + class GroupId(enum.IntEnum): CORE = 0x0 SESSION_CONFIG = 0x1 SESSION_CONTROL = 0x2 DATA_CONTROL = 0x3 - TEST = 0xd VENDOR_RESERVED_9 = 0x9 VENDOR_RESERVED_A = 0xa VENDOR_RESERVED_B = 0xb VENDOR_ANDROID = 0xc + TEST = 0xd VENDOR_RESERVED_E = 0xe VENDOR_RESERVED_F = 0xf @@ -127,154 +141,112 @@ class DataPacketFormat(enum.IntEnum): raise exn -class GroupIdOrDataPacketFormat(enum.IntEnum): - CORE = 0x0 - SESSION_CONFIG_OR_DATA_SND = 0x1 - SESSION_CONTROL_OR_DATA_RCV = 0x2 - DATA_CONTROL = 0x3 - TEST = 0xd - VENDOR_RESERVED_9 = 0x9 - VENDOR_RESERVED_A = 0xa - VENDOR_RESERVED_B = 0xb - VENDOR_ANDROID = 0xc - VENDOR_RESERVED_E = 0xe - VENDOR_RESERVED_F = 0xf - - @staticmethod - def from_int(v: int) -> Union[int, 'GroupIdOrDataPacketFormat']: - try: - return GroupIdOrDataPacketFormat(v) - except ValueError as exn: - raise exn - - -class CoreOpCode(enum.IntEnum): - CORE_DEVICE_RESET = 0x0 - CORE_DEVICE_STATUS_NTF = 0x1 - CORE_DEVICE_INFO = 0x2 - CORE_GET_CAPS_INFO = 0x3 - CORE_SET_CONFIG = 0x4 - CORE_GET_CONFIG = 0x5 - CORE_DEVICE_SUSPEND = 0x6 - CORE_GENERIC_ERROR_NTF = 0x7 - CORE_QUERY_UWBS_TIMESTAMP = 0x8 +class CoreOpcodeId(enum.IntEnum): + DEVICE_RESET = 0x0 + DEVICE_STATUS = 0x1 + GET_DEVICE_INFO = 0x2 + GET_CAPS_INFO = 0x3 + SET_CONFIG = 0x4 + GET_CONFIG = 0x5 + GENERIC_ERROR = 0x7 + QUERY_UWBS_TIMESTAMP = 0x8 @staticmethod - def from_int(v: int) -> Union[int, 'CoreOpCode']: + def from_int(v: int) -> Union[int, 'CoreOpcodeId']: try: - return CoreOpCode(v) + return CoreOpcodeId(v) except ValueError as exn: raise exn -class SessionConfigOpCode(enum.IntEnum): - SESSION_INIT = 0x0 - SESSION_DEINIT = 0x1 - SESSION_STATUS_NTF = 0x2 - SESSION_SET_APP_CONFIG = 0x3 - SESSION_GET_APP_CONFIG = 0x4 - SESSION_GET_COUNT = 0x5 - SESSION_GET_STATE = 0x6 - SESSION_UPDATE_CONTROLLER_MULTICAST_LIST = 0x7 - SESSION_UPDATE_ACTIVE_ROUNDS_ANCHOR = 0x8 - SESSION_UPDATE_ACTIVE_ROUNDS_DT_TAG = 0x9 - SESSION_SET_INITIATOR_DT_ANCHOR_RR_RDM_LIST = 0xa - SESSION_QUERY_DATA_SIZE_IN_RANGING = 0xb - SESSION_SET_HUS_CONFIG = 0xc +class SessionConfigOpcodeId(enum.IntEnum): + INIT = 0x0 + DEINIT = 0x1 + STATUS = 0x2 + SET_APP_CONFIG = 0x3 + GET_APP_CONFIG = 0x4 + GET_COUNT = 0x5 + GET_STATE = 0x6 + UPDATE_CONTROLLER_MULTICAST_LIST = 0x7 + UPDATE_DT_ANCHOR_RANGING_ROUNDS = 0x8 + UPDATE_DT_TAG_RANGING_ROUNDS = 0x9 + QUERY_DATA_SIZE_IN_RANGING = 0xb @staticmethod - def from_int(v: int) -> Union[int, 'SessionConfigOpCode']: + def from_int(v: int) -> Union[int, 'SessionConfigOpcodeId']: try: - return SessionConfigOpCode(v) + return SessionConfigOpcodeId(v) except ValueError as exn: raise exn -class SessionControlOpCode(enum.IntEnum): - SESSION_START = 0x0 - SESSION_STOP = 0x1 - SESSION_RESERVED = 0x2 - SESSION_GET_RANGING_COUNT = 0x3 - SESSION_DATA_CREDIT_NTF = 0x4 - SESSION_DATA_TRANSFER_STATUS_NTF = 0x5 +class SessionControlOpcodeId(enum.IntEnum): + START = 0x0 + STOP = 0x1 + GET_RANGING_COUNT = 0x3 + DATA_CREDIT = 0x4 + DATA_TRANSFER_STATUS = 0x5 @staticmethod - def from_int(v: int) -> Union[int, 'SessionControlOpCode']: + def from_int(v: int) -> Union[int, 'SessionControlOpcodeId']: try: - return SessionControlOpCode(v) + return SessionControlOpcodeId(v) except ValueError as exn: raise exn -class AppDataOpCode(enum.IntEnum): - APP_DATA_TX = 0x0 - APP_DATA_RX = 0x1 +class AndroidOpcodeId(enum.IntEnum): + GET_POWER_STATS = 0x0 + SET_COUNTRY_CODE = 0x1 + FIRA_RANGE_DIAGNOSTICS = 0x2 @staticmethod - def from_int(v: int) -> Union[int, 'AppDataOpCode']: + def from_int(v: int) -> Union[int, 'AndroidOpcodeId']: try: - return AppDataOpCode(v) + return AndroidOpcodeId(v) except ValueError as exn: raise exn -class AndroidOpCode(enum.IntEnum): - ANDROID_GET_POWER_STATS = 0x0 - ANDROID_SET_COUNTRY_CODE = 0x1 - ANDROID_FIRA_RANGE_DIAGNOSTICS = 0x2 - - @staticmethod - def from_int(v: int) -> Union[int, 'AndroidOpCode']: - try: - return AndroidOpCode(v) - except ValueError as exn: - raise exn - - -class StatusCode(enum.IntEnum): - UCI_STATUS_OK = 0x0 - UCI_STATUS_REJECTED = 0x1 - UCI_STATUS_FAILED = 0x2 - UCI_STATUS_SYNTAX_ERROR = 0x3 - UCI_STATUS_INVALID_PARAM = 0x4 - UCI_STATUS_INVALID_RANGE = 0x5 - UCI_STATUS_INVALID_MSG_SIZE = 0x6 - UCI_STATUS_UNKNOWN_GID = 0x7 - UCI_STATUS_UNKNOWN_OID = 0x8 - UCI_STATUS_READ_ONLY = 0x9 - UCI_STATUS_COMMAND_RETRY = 0xa - UCI_STATUS_UNKNOWN = 0xb - UCI_STATUS_NOT_APPLICABLE = 0xc - UCI_STATUS_SESSION_NOT_EXIST = 0x11 - UCI_STATUS_SESSION_DUPLICATE = 0x12 - UCI_STATUS_SESSION_ACTIVE = 0x13 - UCI_STATUS_MAX_SESSIONS_EXCEEDED = 0x14 - UCI_STATUS_SESSION_NOT_CONFIGURED = 0x15 - UCI_STATUS_ACTIVE_SESSIONS_ONGOING = 0x16 - UCI_STATUS_MULTICAST_LIST_FULL = 0x17 - UCI_STATUS_ADDRESS_NOT_FOUND = 0x18 - UCI_STATUS_ADDRESS_ALREADY_PRESENT = 0x19 - UCI_STATUS_ERROR_UWB_INITIATION_TIME_TOO_OLD = 0x1a - UCI_STATUS_OK_NEGATIVE_DISTANCE_REPORT = 0x1b - UCI_STATUS_RANGING_TX_FAILED = 0x20 - UCI_STATUS_RANGING_RX_TIMEOUT = 0x21 - UCI_STATUS_RANGING_RX_PHY_DEC_FAILED = 0x22 - UCI_STATUS_RANGING_RX_PHY_TOA_FAILED = 0x23 - UCI_STATUS_RANGING_RX_PHY_STS_FAILED = 0x24 - UCI_STATUS_RANGING_RX_MAC_DEC_FAILED = 0x25 - UCI_STATUS_RANGING_RX_MAC_IE_DEC_FAILED = 0x26 - UCI_STATUS_RANGING_RX_MAC_IE_MISSING = 0x27 - UCI_STATUS_ERROR_ROUND_INDEX_NOT_ACTIVATED = 0x28 - UCI_STATUS_ERROR_NUMBER_OF_ACTIVE_RANGING_ROUNDS_EXCEEDED = 0x29 - UCI_STATUS_ERROR_DL_TDOA_DEVICE_ADDRESS_NOT_MATCHING_IN_REPLY_TIME_LIST = 0x2a - UCI_STATUS_DATA_MAX_TX_PSDU_SIZE_EXCEEDED = 0x30 - UCI_STATUS_DATA_RX_CRC_ERROR = 0x31 - VENDOR_SPECIFIC_STATUS_CODE_2 = 0xff - - @staticmethod - def from_int(v: int) -> Union[int, 'StatusCode']: - try: - return StatusCode(v) +class Status(enum.IntEnum): + OK = 0x0 + REJECTED = 0x1 + FAILED = 0x2 + SYNTAX_ERROR = 0x3 + INVALID_PARAM = 0x4 + INVALID_RANGE = 0x5 + INVALID_MESSAGE_SIZE = 0x6 + UNKNOWN_GID = 0x7 + UNKNOWN_OID = 0x8 + READ_ONLY = 0x9 + UCI_MESSAGE_RETRY = 0xa + UNKNOWN = 0xb + NOT_APPLICABLE = 0xc + ERROR_SESSION_NOT_EXIST = 0x11 + ERROR_SESSION_DUPLICATE = 0x12 + ERROR_SESSION_ACTIVE = 0x13 + ERROR_MAX_SESSIONS_EXCEEDED = 0x14 + ERROR_SESSION_NOT_CONFIGURED = 0x15 + ERROR_ACTIVE_SESSIONS_ONGOING = 0x16 + ERROR_MULTICAST_LIST_FULL = 0x17 + ERROR_UWB_INITIATION_TIME_TOO_OLD = 0x1a + OK_NEGATIVE_DISTANCE_REPORT = 0x1b + RANGING_TX_FAILED = 0x20 + RANGING_RX_TIMEOUT = 0x21 + RANGING_RX_PHY_DEC_FAILED = 0x22 + RANGING_RX_PHY_TOA_FAILED = 0x23 + RANGING_RX_PHY_STS_FAILED = 0x24 + RANGING_RX_MAC_DEC_FAILED = 0x25 + RANGING_RX_MAC_IE_DEC_FAILED = 0x26 + RANGING_RX_MAC_IE_MISSING = 0x27 + ERROR_ROUND_INDEX_NOT_ACTIVATED = 0x28 + ERROR_NUMBER_OF_ACTIVE_RANGING_ROUNDS_EXCEEDED = 0x29 + ERROR_DL_TDOA_DEVICE_ADDRESS_NOT_MATCHING_IN_REPLY_TIME_LIST = 0x2a + + @staticmethod + def from_int(v: int) -> Union[int, 'Status']: + try: + return Status(v) except ValueError as exn: return v @@ -333,18 +305,6 @@ class ResetConfig(enum.IntEnum): raise exn -class DeviceConfigId(enum.IntEnum): - DEVICE_STATE = 0x0 - LOW_POWER_MODE = 0x1 - - @staticmethod - def from_int(v: int) -> Union[int, 'DeviceConfigId']: - try: - return DeviceConfigId(v) - except ValueError as exn: - raise exn - - class AppConfigTlvType(enum.IntEnum): DEVICE_TYPE = 0x0 RANGING_ROUND_USAGE = 0x1 @@ -982,20 +942,21 @@ class ReasonCode(enum.IntEnum): return v -class MulticastUpdateStatusCode(enum.IntEnum): - STATUS_OK_MULTICAST_LIST_UPDATE = 0x0 - STATUS_ERROR_MULTICAST_LIST_FULL = 0x1 - STATUS_ERROR_KEY_FETCH_FAIL = 0x2 - STATUS_ERROR_SUB_SESSION_ID_NOT_FOUND = 0x3 - STATUS_ERROR_SUB_SESSION_KEY_NOT_FOUND = 0x5 - STATUS_ERROR_SUB_SESSION_KEY_NOT_APPLICABLE = 0x6 - STATUS_ERROR_SESSION_KEY_NOT_FOUND = 0x7 - STATUS_ERROR_ADDRESS_ALREADY_PRESENT = 0x8 +class MulticastUpdateStatus(enum.IntEnum): + OK_MULTICAST_LIST_UPDATE = 0x0 + ERROR_MULTICAST_LIST_FULL = 0x1 + ERROR_KEY_FETCH_FAIL = 0x2 + ERROR_SUB_SESSION_ID_NOT_FOUND = 0x3 + ERROR_SUB_SESSION_KEY_NOT_FOUND = 0x4 + ERROR_SUB_SESSION_KEY_NOT_APPLICABLE = 0x5 + ERROR_SESSION_KEY_NOT_FOUND = 0x6 + ERROR_ADDRESS_NOT_FOUND = 0x7 + ERROR_ADDRESS_ALREADY_PRESENT = 0x8 @staticmethod - def from_int(v: int) -> Union[int, 'MulticastUpdateStatusCode']: + def from_int(v: int) -> Union[int, 'MulticastUpdateStatus']: try: - return MulticastUpdateStatusCode(v) + return MulticastUpdateStatus(v) except ValueError as exn: raise exn @@ -1030,22 +991,6 @@ class SessionType(enum.IntEnum): raise exn -class MessageType(enum.IntEnum): - DATA = 0x0 - COMMAND = 0x1 - RESPONSE = 0x2 - NOTIFICATION = 0x3 - RESERVED_FOR_TESTING_1 = 0x4 - RESERVED_FOR_TESTING_2 = 0x5 - - @staticmethod - def from_int(v: int) -> Union[int, 'MessageType']: - try: - return MessageType(v) - except ValueError as exn: - raise exn - - @dataclass class CommonPacketHeader(Packet): pbf: PacketBoundaryFlag = field(kw_only=True, default=PacketBoundaryFlag.COMPLETE) @@ -1159,7 +1104,6 @@ class DataPacketHeader(Packet): class ControlPacket(Packet): gid: GroupId = field(kw_only=True, default=GroupId.CORE) mt: MessageType = field(kw_only=True, default=MessageType.DATA) - opcode: int = field(kw_only=True, default=0) def __post_init__(self): pass @@ -1167,210 +1111,28 @@ class ControlPacket(Packet): @staticmethod def parse(span: bytes) -> Tuple['ControlPacket', bytes]: fields = {'payload': None} - if len(span) < 4: + if len(span) < 1: raise Exception('Invalid packet size') fields['gid'] = GroupId.from_int((span[0] >> 0) & 0xf) fields['mt'] = MessageType.from_int((span[0] >> 5) & 0x7) - fields['opcode'] = (span[1] >> 0) & 0x3f - value_ = int.from_bytes(span[2:4], byteorder='little') - span = span[4:] + span = span[1:] payload = span span = bytes([]) fields['payload'] = payload try: - return DeviceResetCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return GetDeviceInfoCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return GetCapsInfoCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SetConfigCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return GetConfigCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return CoreQueryTimeStampCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionInitCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionDeinitCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionSetAppConfigCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionGetAppConfigCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionGetCountCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionGetStateCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionUpdateDtTagRangingRoundsCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionUpdateControllerMulticastListCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionSetHybridConfigCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionQueryMaxDataSizeCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionControlCommand.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return AndroidGetPowerStatsCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return AndroidSetCountryCodeCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return DeviceResetRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return GetDeviceInfoRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return GetCapsInfoRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SetConfigRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return GetConfigRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return CoreQueryTimeStampRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionInitRsp_V2.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionInitRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionDeinitRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionSetAppConfigRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionGetAppConfigRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionGetCountRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionGetStateRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionUpdateDtTagRangingRoundsRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionSetHybridConfigRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionUpdateControllerMulticastListRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionQueryMaxDataSizeRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionStartRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionStopRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionGetRangingCountRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return AndroidGetPowerStatsRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return AndroidSetCountryCodeRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return DeviceStatusNtf.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return GenericError.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionStatusNtf.parse(fields.copy(), payload) + return CorePacket.parse(fields.copy(), payload) except Exception as exn: pass try: - return SessionUpdateControllerMulticastListNtf.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return DataCreditNtf.parse(fields.copy(), payload) + return SessionConfigPacket.parse(fields.copy(), payload) except Exception as exn: pass try: - return DataTransferStatusNtf.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionInfoNtf.parse(fields.copy(), payload) + return SessionControlPacket.parse(fields.copy(), payload) except Exception as exn: pass try: - return AndroidRangeDiagnosticsNtf.parse(fields.copy(), payload) + return AndroidPacket.parse(fields.copy(), payload) except Exception as exn: pass return ControlPacket(**fields), span @@ -1382,17 +1144,12 @@ class ControlPacket(Packet): (self.mt << 5) ) _span.append(_value) - if self.opcode > 63: - print(f"Invalid value for field ControlPacket::opcode: {self.opcode} > 63; the value will be truncated") - self.opcode &= 63 - _span.append((self.opcode << 0)) - _span.extend([0] * 2) _span.extend(payload or self.payload or []) return bytes(_span) @property def size(self) -> int: - return len(self.payload) + 4 + return len(self.payload) + 1 @dataclass class DataPacket(Packet): @@ -1500,7 +1257,7 @@ class DataMessageSnd(DataPacket): @dataclass class DataMessageRcv(DataPacket): session_handle: int = field(kw_only=True, default=0) - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) + status: Status = field(kw_only=True, default=Status.OK) source_address: int = field(kw_only=True, default=0) data_sequence_number: int = field(kw_only=True, default=0) application_data: bytearray = field(kw_only=True, default_factory=bytearray) @@ -1517,7 +1274,7 @@ class DataMessageRcv(DataPacket): raise Exception('Invalid packet size') value_ = int.from_bytes(span[0:4], byteorder='little') fields['session_handle'] = value_ - fields['status'] = StatusCode.from_int(span[4]) + fields['status'] = Status.from_int(span[4]) value_ = int.from_bytes(span[5:13], byteorder='little') fields['source_address'] = value_ value_ = int.from_bytes(span[13:15], byteorder='little') @@ -1555,4619 +1312,3699 @@ class DataMessageRcv(DataPacket): return len(self.application_data) * 1 + 17 @dataclass -class UciCommand(ControlPacket): - +class CorePacket(ControlPacket): + oid: CoreOpcodeId = field(kw_only=True, default=CoreOpcodeId.DEVICE_RESET) def __post_init__(self): - self.mt = MessageType.COMMAND + self.gid = GroupId.CORE @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['UciCommand', bytes]: - if fields['mt'] != MessageType.COMMAND: + def parse(fields: dict, span: bytes) -> Tuple['CorePacket', bytes]: + if fields['gid'] != GroupId.CORE: raise Exception("Invalid constraint field values") + if len(span) < 3: + raise Exception('Invalid packet size') + fields['oid'] = CoreOpcodeId.from_int((span[0] >> 0) & 0x3f) + value_ = int.from_bytes(span[1:3], byteorder='little') + span = span[3:] payload = span span = bytes([]) fields['payload'] = payload try: - return DeviceResetCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return GetDeviceInfoCmd.parse(fields.copy(), payload) + return CoreDeviceResetCmd.parse(fields.copy(), payload) except Exception as exn: pass try: - return GetCapsInfoCmd.parse(fields.copy(), payload) + return CoreDeviceResetRsp.parse(fields.copy(), payload) except Exception as exn: pass try: - return SetConfigCmd.parse(fields.copy(), payload) + return CoreDeviceStatusNtf.parse(fields.copy(), payload) except Exception as exn: pass try: - return GetConfigCmd.parse(fields.copy(), payload) + return CoreGetDeviceInfoCmd.parse(fields.copy(), payload) except Exception as exn: pass try: - return CoreQueryTimeStampCmd.parse(fields.copy(), payload) + return CoreGetDeviceInfoRsp.parse(fields.copy(), payload) except Exception as exn: pass try: - return SessionInitCmd.parse(fields.copy(), payload) + return CoreGetCapsInfoCmd.parse(fields.copy(), payload) except Exception as exn: pass try: - return SessionDeinitCmd.parse(fields.copy(), payload) + return CoreGetCapsInfoRsp.parse(fields.copy(), payload) except Exception as exn: pass try: - return SessionSetAppConfigCmd.parse(fields.copy(), payload) + return CoreSetConfigCmd.parse(fields.copy(), payload) except Exception as exn: pass try: - return SessionGetAppConfigCmd.parse(fields.copy(), payload) + return CoreSetConfigRsp.parse(fields.copy(), payload) except Exception as exn: pass try: - return SessionGetCountCmd.parse(fields.copy(), payload) + return CoreGetConfigCmd.parse(fields.copy(), payload) except Exception as exn: pass try: - return SessionGetStateCmd.parse(fields.copy(), payload) + return CoreGetConfigRsp.parse(fields.copy(), payload) except Exception as exn: pass try: - return SessionUpdateDtTagRangingRoundsCmd.parse(fields.copy(), payload) + return CoreGenericErrorNtf.parse(fields.copy(), payload) except Exception as exn: pass try: - return SessionUpdateControllerMulticastListCmd.parse(fields.copy(), payload) + return CoreQueryTimeStampCmd.parse(fields.copy(), payload) except Exception as exn: pass try: - return SessionSetHybridConfigCmd.parse(fields.copy(), payload) + return CoreQueryTimeStampRsp.parse(fields.copy(), payload) except Exception as exn: pass - try: - return SessionQueryMaxDataSizeCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionControlCommand.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return AndroidGetPowerStatsCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return AndroidSetCountryCodeCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - return UciCommand(**fields), span + return CorePacket(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() + _span.append((self.oid << 0)) + _span.extend([0] * 2) _span.extend(payload or self.payload or []) return ControlPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.payload) + return len(self.payload) + 3 @dataclass -class UciResponse(ControlPacket): - +class SessionConfigPacket(ControlPacket): + oid: SessionConfigOpcodeId = field(kw_only=True, default=SessionConfigOpcodeId.INIT) def __post_init__(self): - self.mt = MessageType.RESPONSE + self.gid = GroupId.SESSION_CONFIG @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['UciResponse', bytes]: - if fields['mt'] != MessageType.RESPONSE: + def parse(fields: dict, span: bytes) -> Tuple['SessionConfigPacket', bytes]: + if fields['gid'] != GroupId.SESSION_CONFIG: raise Exception("Invalid constraint field values") + if len(span) < 3: + raise Exception('Invalid packet size') + fields['oid'] = SessionConfigOpcodeId.from_int((span[0] >> 0) & 0x3f) + value_ = int.from_bytes(span[1:3], byteorder='little') + span = span[3:] payload = span span = bytes([]) fields['payload'] = payload try: - return DeviceResetRsp.parse(fields.copy(), payload) + return SessionInitCmd.parse(fields.copy(), payload) except Exception as exn: pass try: - return GetDeviceInfoRsp.parse(fields.copy(), payload) + return SessionInitRsp_V2.parse(fields.copy(), payload) except Exception as exn: pass try: - return GetCapsInfoRsp.parse(fields.copy(), payload) + return SessionInitRsp.parse(fields.copy(), payload) except Exception as exn: pass try: - return SetConfigRsp.parse(fields.copy(), payload) + return SessionDeinitCmd.parse(fields.copy(), payload) except Exception as exn: pass try: - return GetConfigRsp.parse(fields.copy(), payload) + return SessionDeinitRsp.parse(fields.copy(), payload) except Exception as exn: pass try: - return CoreQueryTimeStampRsp.parse(fields.copy(), payload) + return SessionStatusNtf.parse(fields.copy(), payload) except Exception as exn: pass try: - return SessionInitRsp_V2.parse(fields.copy(), payload) + return SessionSetAppConfigCmd.parse(fields.copy(), payload) except Exception as exn: pass try: - return SessionInitRsp.parse(fields.copy(), payload) + return SessionSetAppConfigRsp.parse(fields.copy(), payload) except Exception as exn: pass try: - return SessionDeinitRsp.parse(fields.copy(), payload) + return SessionGetAppConfigCmd.parse(fields.copy(), payload) except Exception as exn: pass try: - return SessionSetAppConfigRsp.parse(fields.copy(), payload) + return SessionGetAppConfigRsp.parse(fields.copy(), payload) except Exception as exn: pass try: - return SessionGetAppConfigRsp.parse(fields.copy(), payload) + return SessionGetCountCmd.parse(fields.copy(), payload) except Exception as exn: pass try: return SessionGetCountRsp.parse(fields.copy(), payload) except Exception as exn: pass + try: + return SessionGetStateCmd.parse(fields.copy(), payload) + except Exception as exn: + pass try: return SessionGetStateRsp.parse(fields.copy(), payload) except Exception as exn: pass try: - return SessionUpdateDtTagRangingRoundsRsp.parse(fields.copy(), payload) + return SessionUpdateDtAnchorRangingRoundsCmd.parse(fields.copy(), payload) except Exception as exn: pass try: - return SessionSetHybridConfigRsp.parse(fields.copy(), payload) + return SessionUpdateDtAnchorRangingRoundsRsp.parse(fields.copy(), payload) except Exception as exn: pass try: - return SessionUpdateControllerMulticastListRsp.parse(fields.copy(), payload) + return SessionUpdateDtTagRangingRoundsCmd.parse(fields.copy(), payload) except Exception as exn: pass try: - return SessionQueryMaxDataSizeRsp.parse(fields.copy(), payload) + return SessionUpdateDtTagRangingRoundsRsp.parse(fields.copy(), payload) except Exception as exn: pass try: - return SessionStartRsp.parse(fields.copy(), payload) + return SessionUpdateControllerMulticastListCmd.parse(fields.copy(), payload) except Exception as exn: pass try: - return SessionStopRsp.parse(fields.copy(), payload) + return SessionUpdateControllerMulticastListRsp.parse(fields.copy(), payload) except Exception as exn: pass try: - return SessionGetRangingCountRsp.parse(fields.copy(), payload) + return SessionUpdateControllerMulticastListNtf.parse(fields.copy(), payload) except Exception as exn: pass try: - return AndroidGetPowerStatsRsp.parse(fields.copy(), payload) + return SessionQueryMaxDataSizeInRangingCmd.parse(fields.copy(), payload) except Exception as exn: pass try: - return AndroidSetCountryCodeRsp.parse(fields.copy(), payload) + return SessionQueryMaxDataSizeInRangingRsp.parse(fields.copy(), payload) except Exception as exn: pass - return UciResponse(**fields), span + return SessionConfigPacket(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() + _span.append((self.oid << 0)) + _span.extend([0] * 2) _span.extend(payload or self.payload or []) return ControlPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.payload) + return len(self.payload) + 3 @dataclass -class UciNotification(ControlPacket): - +class SessionControlPacket(ControlPacket): + oid: SessionControlOpcodeId = field(kw_only=True, default=SessionControlOpcodeId.START) def __post_init__(self): - self.mt = MessageType.NOTIFICATION + self.gid = GroupId.SESSION_CONTROL @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['UciNotification', bytes]: - if fields['mt'] != MessageType.NOTIFICATION: + def parse(fields: dict, span: bytes) -> Tuple['SessionControlPacket', bytes]: + if fields['gid'] != GroupId.SESSION_CONTROL: raise Exception("Invalid constraint field values") + if len(span) < 3: + raise Exception('Invalid packet size') + fields['oid'] = SessionControlOpcodeId.from_int((span[0] >> 0) & 0x3f) + value_ = int.from_bytes(span[1:3], byteorder='little') + span = span[3:] payload = span span = bytes([]) fields['payload'] = payload try: - return DeviceStatusNtf.parse(fields.copy(), payload) + return SessionDataCreditNtf.parse(fields.copy(), payload) except Exception as exn: pass try: - return GenericError.parse(fields.copy(), payload) + return SessionDataTransferStatusNtf.parse(fields.copy(), payload) except Exception as exn: pass try: - return SessionStatusNtf.parse(fields.copy(), payload) + return SessionStartCmd.parse(fields.copy(), payload) except Exception as exn: pass try: - return SessionUpdateControllerMulticastListNtf.parse(fields.copy(), payload) + return SessionStartRsp.parse(fields.copy(), payload) except Exception as exn: pass try: - return DataCreditNtf.parse(fields.copy(), payload) + return SessionInfoNtf.parse(fields.copy(), payload) except Exception as exn: pass try: - return DataTransferStatusNtf.parse(fields.copy(), payload) + return SessionStopCmd.parse(fields.copy(), payload) except Exception as exn: pass try: - return SessionInfoNtf.parse(fields.copy(), payload) + return SessionStopRsp.parse(fields.copy(), payload) except Exception as exn: pass try: - return AndroidRangeDiagnosticsNtf.parse(fields.copy(), payload) + return SessionGetRangingCountCmd.parse(fields.copy(), payload) + except Exception as exn: + pass + try: + return SessionGetRangingCountRsp.parse(fields.copy(), payload) except Exception as exn: pass - return UciNotification(**fields), span + return SessionControlPacket(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() + _span.append((self.oid << 0)) + _span.extend([0] * 2) _span.extend(payload or self.payload or []) return ControlPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.payload) + return len(self.payload) + 3 @dataclass -class CoreCommand(UciCommand): - +class AndroidPacket(ControlPacket): + oid: AndroidOpcodeId = field(kw_only=True, default=AndroidOpcodeId.GET_POWER_STATS) def __post_init__(self): - self.gid = GroupId.CORE - self.mt = MessageType.COMMAND + self.gid = GroupId.VENDOR_ANDROID @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['CoreCommand', bytes]: - if fields['gid'] != GroupId.CORE or fields['mt'] != MessageType.COMMAND: + def parse(fields: dict, span: bytes) -> Tuple['AndroidPacket', bytes]: + if fields['gid'] != GroupId.VENDOR_ANDROID: raise Exception("Invalid constraint field values") + if len(span) < 3: + raise Exception('Invalid packet size') + fields['oid'] = AndroidOpcodeId.from_int((span[0] >> 0) & 0x3f) + value_ = int.from_bytes(span[1:3], byteorder='little') + span = span[3:] payload = span span = bytes([]) fields['payload'] = payload try: - return DeviceResetCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return GetDeviceInfoCmd.parse(fields.copy(), payload) + return AndroidGetPowerStatsCmd.parse(fields.copy(), payload) except Exception as exn: pass try: - return GetCapsInfoCmd.parse(fields.copy(), payload) + return AndroidGetPowerStatsRsp.parse(fields.copy(), payload) except Exception as exn: pass try: - return SetConfigCmd.parse(fields.copy(), payload) + return AndroidSetCountryCodeCmd.parse(fields.copy(), payload) except Exception as exn: pass try: - return GetConfigCmd.parse(fields.copy(), payload) + return AndroidSetCountryCodeRsp.parse(fields.copy(), payload) except Exception as exn: pass try: - return CoreQueryTimeStampCmd.parse(fields.copy(), payload) + return AndroidRangeDiagnosticsNtf.parse(fields.copy(), payload) except Exception as exn: pass - return CoreCommand(**fields), span + return AndroidPacket(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() + _span.append((self.oid << 0)) + _span.extend([0] * 2) _span.extend(payload or self.payload or []) - return UciCommand.serialize(self, payload = bytes(_span)) + return ControlPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.payload) + return len(self.payload) + 3 @dataclass -class CoreResponse(UciResponse): - +class CoreDeviceResetCmd(CorePacket): + reset_config: ResetConfig = field(kw_only=True, default=ResetConfig.UWBS_RESET) def __post_init__(self): + self.mt = MessageType.COMMAND + self.oid = CoreOpcodeId.DEVICE_RESET self.gid = GroupId.CORE - self.mt = MessageType.RESPONSE @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['CoreResponse', bytes]: - if fields['gid'] != GroupId.CORE or fields['mt'] != MessageType.RESPONSE: + def parse(fields: dict, span: bytes) -> Tuple['CoreDeviceResetCmd', bytes]: + if fields['mt'] != MessageType.COMMAND or fields['oid'] != CoreOpcodeId.DEVICE_RESET or fields['gid'] != GroupId.CORE: raise Exception("Invalid constraint field values") - payload = span - span = bytes([]) - fields['payload'] = payload - try: - return DeviceResetRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return GetDeviceInfoRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return GetCapsInfoRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SetConfigRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return GetConfigRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return CoreQueryTimeStampRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - return CoreResponse(**fields), span + if len(span) < 1: + raise Exception('Invalid packet size') + fields['reset_config'] = ResetConfig.from_int(span[0]) + span = span[1:] + return CoreDeviceResetCmd(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(payload or self.payload or []) - return UciResponse.serialize(self, payload = bytes(_span)) + _span.append((self.reset_config << 0)) + return CorePacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.payload) + return 1 @dataclass -class CoreNotification(UciNotification): - +class CoreDeviceResetRsp(CorePacket): + status: Status = field(kw_only=True, default=Status.OK) def __post_init__(self): + self.mt = MessageType.RESPONSE + self.oid = CoreOpcodeId.DEVICE_RESET self.gid = GroupId.CORE - self.mt = MessageType.NOTIFICATION @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['CoreNotification', bytes]: - if fields['gid'] != GroupId.CORE or fields['mt'] != MessageType.NOTIFICATION: + def parse(fields: dict, span: bytes) -> Tuple['CoreDeviceResetRsp', bytes]: + if fields['mt'] != MessageType.RESPONSE or fields['oid'] != CoreOpcodeId.DEVICE_RESET or fields['gid'] != GroupId.CORE: raise Exception("Invalid constraint field values") - payload = span - span = bytes([]) - fields['payload'] = payload - try: - return DeviceStatusNtf.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return GenericError.parse(fields.copy(), payload) - except Exception as exn: - pass - return CoreNotification(**fields), span + if len(span) < 1: + raise Exception('Invalid packet size') + fields['status'] = Status.from_int(span[0]) + span = span[1:] + return CoreDeviceResetRsp(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(payload or self.payload or []) - return UciNotification.serialize(self, payload = bytes(_span)) + _span.append((self.status << 0)) + return CorePacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.payload) + return 1 @dataclass -class SessionConfigCommand(UciCommand): - +class CoreDeviceStatusNtf(CorePacket): + device_state: DeviceState = field(kw_only=True, default=DeviceState.DEVICE_STATE_READY) def __post_init__(self): - self.gid = GroupId.SESSION_CONFIG - self.mt = MessageType.COMMAND + self.mt = MessageType.NOTIFICATION + self.oid = CoreOpcodeId.DEVICE_STATUS + self.gid = GroupId.CORE @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionConfigCommand', bytes]: - if fields['gid'] != GroupId.SESSION_CONFIG or fields['mt'] != MessageType.COMMAND: + def parse(fields: dict, span: bytes) -> Tuple['CoreDeviceStatusNtf', bytes]: + if fields['mt'] != MessageType.NOTIFICATION or fields['oid'] != CoreOpcodeId.DEVICE_STATUS or fields['gid'] != GroupId.CORE: raise Exception("Invalid constraint field values") - payload = span - span = bytes([]) - fields['payload'] = payload - try: - return SessionInitCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionDeinitCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionSetAppConfigCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionGetAppConfigCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionGetCountCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionGetStateCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionUpdateDtTagRangingRoundsCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionUpdateControllerMulticastListCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionSetHybridConfigCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionQueryMaxDataSizeCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - return SessionConfigCommand(**fields), span + if len(span) < 1: + raise Exception('Invalid packet size') + fields['device_state'] = DeviceState.from_int(span[0]) + span = span[1:] + return CoreDeviceStatusNtf(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(payload or self.payload or []) - return UciCommand.serialize(self, payload = bytes(_span)) + _span.append((self.device_state << 0)) + return CorePacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.payload) + return 1 @dataclass -class SessionConfigResponse(UciResponse): +class CoreGetDeviceInfoCmd(CorePacket): def __post_init__(self): - self.gid = GroupId.SESSION_CONFIG - self.mt = MessageType.RESPONSE + self.mt = MessageType.COMMAND + self.oid = CoreOpcodeId.GET_DEVICE_INFO + self.gid = GroupId.CORE @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionConfigResponse', bytes]: - if fields['gid'] != GroupId.SESSION_CONFIG or fields['mt'] != MessageType.RESPONSE: + def parse(fields: dict, span: bytes) -> Tuple['CoreGetDeviceInfoCmd', bytes]: + if fields['mt'] != MessageType.COMMAND or fields['oid'] != CoreOpcodeId.GET_DEVICE_INFO or fields['gid'] != GroupId.CORE: raise Exception("Invalid constraint field values") - payload = span - span = bytes([]) - fields['payload'] = payload - try: - return SessionInitRsp_V2.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionInitRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionDeinitRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionSetAppConfigRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionGetAppConfigRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionGetCountRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionGetStateRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionUpdateDtTagRangingRoundsRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionSetHybridConfigRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionUpdateControllerMulticastListRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionQueryMaxDataSizeRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - return SessionConfigResponse(**fields), span + return CoreGetDeviceInfoCmd(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(payload or self.payload or []) - return UciResponse.serialize(self, payload = bytes(_span)) + return CorePacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.payload) + return 0 @dataclass -class SessionConfigNotification(UciNotification): - +class CoreGetDeviceInfoRsp(CorePacket): + status: Status = field(kw_only=True, default=Status.OK) + uci_version: int = field(kw_only=True, default=0) + mac_version: int = field(kw_only=True, default=0) + phy_version: int = field(kw_only=True, default=0) + uci_test_version: int = field(kw_only=True, default=0) + vendor_spec_info: bytearray = field(kw_only=True, default_factory=bytearray) def __post_init__(self): - self.gid = GroupId.SESSION_CONFIG - self.mt = MessageType.NOTIFICATION + self.mt = MessageType.RESPONSE + self.oid = CoreOpcodeId.GET_DEVICE_INFO + self.gid = GroupId.CORE @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionConfigNotification', bytes]: - if fields['gid'] != GroupId.SESSION_CONFIG or fields['mt'] != MessageType.NOTIFICATION: + def parse(fields: dict, span: bytes) -> Tuple['CoreGetDeviceInfoRsp', bytes]: + if fields['mt'] != MessageType.RESPONSE or fields['oid'] != CoreOpcodeId.GET_DEVICE_INFO or fields['gid'] != GroupId.CORE: raise Exception("Invalid constraint field values") - payload = span - span = bytes([]) - fields['payload'] = payload - try: - return SessionStatusNtf.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionUpdateControllerMulticastListNtf.parse(fields.copy(), payload) - except Exception as exn: - pass - return SessionConfigNotification(**fields), span + if len(span) < 10: + raise Exception('Invalid packet size') + fields['status'] = Status.from_int(span[0]) + value_ = int.from_bytes(span[1:3], byteorder='little') + fields['uci_version'] = value_ + value_ = int.from_bytes(span[3:5], byteorder='little') + fields['mac_version'] = value_ + value_ = int.from_bytes(span[5:7], byteorder='little') + fields['phy_version'] = value_ + value_ = int.from_bytes(span[7:9], byteorder='little') + fields['uci_test_version'] = value_ + vendor_spec_info_count = span[9] + span = span[10:] + if len(span) < vendor_spec_info_count: + raise Exception('Invalid packet size') + fields['vendor_spec_info'] = list(span[:vendor_spec_info_count]) + span = span[vendor_spec_info_count:] + return CoreGetDeviceInfoRsp(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(payload or self.payload or []) - return UciNotification.serialize(self, payload = bytes(_span)) + _span.append((self.status << 0)) + if self.uci_version > 65535: + print(f"Invalid value for field CoreGetDeviceInfoRsp::uci_version: {self.uci_version} > 65535; the value will be truncated") + self.uci_version &= 65535 + _span.extend(int.to_bytes((self.uci_version << 0), length=2, byteorder='little')) + if self.mac_version > 65535: + print(f"Invalid value for field CoreGetDeviceInfoRsp::mac_version: {self.mac_version} > 65535; the value will be truncated") + self.mac_version &= 65535 + _span.extend(int.to_bytes((self.mac_version << 0), length=2, byteorder='little')) + if self.phy_version > 65535: + print(f"Invalid value for field CoreGetDeviceInfoRsp::phy_version: {self.phy_version} > 65535; the value will be truncated") + self.phy_version &= 65535 + _span.extend(int.to_bytes((self.phy_version << 0), length=2, byteorder='little')) + if self.uci_test_version > 65535: + print(f"Invalid value for field CoreGetDeviceInfoRsp::uci_test_version: {self.uci_test_version} > 65535; the value will be truncated") + self.uci_test_version &= 65535 + _span.extend(int.to_bytes((self.uci_test_version << 0), length=2, byteorder='little')) + if len(self.vendor_spec_info) > 255: + print(f"Invalid length for field CoreGetDeviceInfoRsp::vendor_spec_info: {len(self.vendor_spec_info)} > 255; the array will be truncated") + del self.vendor_spec_info[255:] + _span.append((len(self.vendor_spec_info) << 0)) + _span.extend(self.vendor_spec_info) + return CorePacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.payload) + return len(self.vendor_spec_info) * 1 + 10 @dataclass -class SessionControlCommand(UciCommand): - session_id: int = field(kw_only=True, default=0) +class CoreGetCapsInfoCmd(CorePacket): + def __post_init__(self): - self.gid = GroupId.SESSION_CONTROL self.mt = MessageType.COMMAND + self.oid = CoreOpcodeId.GET_CAPS_INFO + self.gid = GroupId.CORE @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionControlCommand', bytes]: - if fields['gid'] != GroupId.SESSION_CONTROL or fields['mt'] != MessageType.COMMAND: + def parse(fields: dict, span: bytes) -> Tuple['CoreGetCapsInfoCmd', bytes]: + if fields['mt'] != MessageType.COMMAND or fields['oid'] != CoreOpcodeId.GET_CAPS_INFO or fields['gid'] != GroupId.CORE: raise Exception("Invalid constraint field values") - if len(span) < 4: - raise Exception('Invalid packet size') - value_ = int.from_bytes(span[0:4], byteorder='little') - fields['session_id'] = value_ - span = span[4:] - payload = span - span = bytes([]) - fields['payload'] = payload - try: - return SessionStartCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionStopCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionGetRangingCountCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - return SessionControlCommand(**fields), span + return CoreGetCapsInfoCmd(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - if self.session_id > 4294967295: - print(f"Invalid value for field SessionControlCommand::session_id: {self.session_id} > 4294967295; the value will be truncated") - self.session_id &= 4294967295 - _span.extend(int.to_bytes((self.session_id << 0), length=4, byteorder='little')) - _span.extend(payload or self.payload or []) - return UciCommand.serialize(self, payload = bytes(_span)) + return CorePacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.payload) + 4 + return 0 @dataclass -class SessionControlResponse(UciResponse): - +class CapTlv(Packet): + t: CapTlvType = field(kw_only=True, default=CapTlvType.SUPPORTED_FIRA_PHY_VERSION_RANGE) + v: bytearray = field(kw_only=True, default_factory=bytearray) def __post_init__(self): - self.gid = GroupId.SESSION_CONTROL - self.mt = MessageType.RESPONSE + pass @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionControlResponse', bytes]: - if fields['gid'] != GroupId.SESSION_CONTROL or fields['mt'] != MessageType.RESPONSE: - raise Exception("Invalid constraint field values") - payload = span - span = bytes([]) - fields['payload'] = payload - try: - return SessionStartRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionStopRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionGetRangingCountRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - return SessionControlResponse(**fields), span + def parse(span: bytes) -> Tuple['CapTlv', bytes]: + fields = {'payload': None} + if len(span) < 2: + raise Exception('Invalid packet size') + fields['t'] = CapTlvType.from_int(span[0]) + v_count = span[1] + span = span[2:] + if len(span) < v_count: + raise Exception('Invalid packet size') + fields['v'] = list(span[:v_count]) + span = span[v_count:] + return CapTlv(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(payload or self.payload or []) - return UciResponse.serialize(self, payload = bytes(_span)) + _span.append((self.t << 0)) + if len(self.v) > 255: + print(f"Invalid length for field CapTlv::v: {len(self.v)} > 255; the array will be truncated") + del self.v[255:] + _span.append((len(self.v) << 0)) + _span.extend(self.v) + return bytes(_span) @property def size(self) -> int: - return len(self.payload) + return len(self.v) * 1 + 2 @dataclass -class SessionControlNotification(UciNotification): - +class CoreGetCapsInfoRsp(CorePacket): + status: Status = field(kw_only=True, default=Status.OK) + tlvs: List[CapTlv] = field(kw_only=True, default_factory=list) def __post_init__(self): - self.gid = GroupId.SESSION_CONTROL - self.mt = MessageType.NOTIFICATION + self.mt = MessageType.RESPONSE + self.oid = CoreOpcodeId.GET_CAPS_INFO + self.gid = GroupId.CORE @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionControlNotification', bytes]: - if fields['gid'] != GroupId.SESSION_CONTROL or fields['mt'] != MessageType.NOTIFICATION: + def parse(fields: dict, span: bytes) -> Tuple['CoreGetCapsInfoRsp', bytes]: + if fields['mt'] != MessageType.RESPONSE or fields['oid'] != CoreOpcodeId.GET_CAPS_INFO or fields['gid'] != GroupId.CORE: raise Exception("Invalid constraint field values") - payload = span - span = bytes([]) - fields['payload'] = payload - try: - return DataCreditNtf.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return DataTransferStatusNtf.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return SessionInfoNtf.parse(fields.copy(), payload) - except Exception as exn: - pass - return SessionControlNotification(**fields), span + if len(span) < 2: + raise Exception('Invalid packet size') + fields['status'] = Status.from_int(span[0]) + tlvs_count = span[1] + span = span[2:] + tlvs = [] + for n in range(tlvs_count): + element, span = CapTlv.parse(span) + tlvs.append(element) + fields['tlvs'] = tlvs + return CoreGetCapsInfoRsp(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(payload or self.payload or []) - return UciNotification.serialize(self, payload = bytes(_span)) + _span.append((self.status << 0)) + if len(self.tlvs) > 255: + print(f"Invalid length for field CoreGetCapsInfoRsp::tlvs: {len(self.tlvs)} > 255; the array will be truncated") + del self.tlvs[255:] + _span.append((len(self.tlvs) << 0)) + for _elt in self.tlvs: + _span.extend(_elt.serialize()) + return CorePacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.payload) + return sum([elt.size for elt in self.tlvs]) + 2 + +class ConfigParameterId(enum.IntEnum): + DEVICE_STATE = 0x0 + LOW_POWER_MODE = 0x1 + + @staticmethod + def from_int(v: int) -> Union[int, 'ConfigParameterId']: + try: + return ConfigParameterId(v) + except ValueError as exn: + return v + @dataclass -class AndroidCommand(UciCommand): - +class ConfigParameter(Packet): + id: ConfigParameterId = field(kw_only=True, default=ConfigParameterId.DEVICE_STATE) + value: bytearray = field(kw_only=True, default_factory=bytearray) def __post_init__(self): - self.gid = GroupId.VENDOR_ANDROID - self.mt = MessageType.COMMAND + pass @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['AndroidCommand', bytes]: - if fields['gid'] != GroupId.VENDOR_ANDROID or fields['mt'] != MessageType.COMMAND: - raise Exception("Invalid constraint field values") - payload = span - span = bytes([]) - fields['payload'] = payload - try: - return AndroidGetPowerStatsCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return AndroidSetCountryCodeCmd.parse(fields.copy(), payload) - except Exception as exn: - pass - return AndroidCommand(**fields), span + def parse(span: bytes) -> Tuple['ConfigParameter', bytes]: + fields = {'payload': None} + if len(span) < 2: + raise Exception('Invalid packet size') + fields['id'] = ConfigParameterId.from_int(span[0]) + value_size = span[1] + span = span[2:] + if len(span) < value_size: + raise Exception('Invalid packet size') + fields['value'] = list(span[:value_size]) + span = span[value_size:] + return ConfigParameter(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(payload or self.payload or []) - return UciCommand.serialize(self, payload = bytes(_span)) + _span.append((self.id << 0)) + _span.append(((len(self.value) * 1) << 0)) + _span.extend(self.value) + return bytes(_span) @property def size(self) -> int: - return len(self.payload) + return len(self.value) * 1 + 2 @dataclass -class AndroidResponse(UciResponse): - +class CoreSetConfigCmd(CorePacket): + parameters: List[ConfigParameter] = field(kw_only=True, default_factory=list) def __post_init__(self): - self.gid = GroupId.VENDOR_ANDROID - self.mt = MessageType.RESPONSE + self.mt = MessageType.COMMAND + self.oid = CoreOpcodeId.SET_CONFIG + self.gid = GroupId.CORE @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['AndroidResponse', bytes]: - if fields['gid'] != GroupId.VENDOR_ANDROID or fields['mt'] != MessageType.RESPONSE: + def parse(fields: dict, span: bytes) -> Tuple['CoreSetConfigCmd', bytes]: + if fields['mt'] != MessageType.COMMAND or fields['oid'] != CoreOpcodeId.SET_CONFIG or fields['gid'] != GroupId.CORE: raise Exception("Invalid constraint field values") - payload = span - span = bytes([]) - fields['payload'] = payload - try: - return AndroidGetPowerStatsRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return AndroidSetCountryCodeRsp.parse(fields.copy(), payload) - except Exception as exn: - pass - return AndroidResponse(**fields), span + if len(span) < 1: + raise Exception('Invalid packet size') + parameters_count = span[0] + span = span[1:] + parameters = [] + for n in range(parameters_count): + element, span = ConfigParameter.parse(span) + parameters.append(element) + fields['parameters'] = parameters + return CoreSetConfigCmd(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(payload or self.payload or []) - return UciResponse.serialize(self, payload = bytes(_span)) + if len(self.parameters) > 255: + print(f"Invalid length for field CoreSetConfigCmd::parameters: {len(self.parameters)} > 255; the array will be truncated") + del self.parameters[255:] + _span.append((len(self.parameters) << 0)) + for _elt in self.parameters: + _span.extend(_elt.serialize()) + return CorePacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.payload) + return sum([elt.size for elt in self.parameters]) + 1 @dataclass -class AndroidNotification(UciNotification): - +class ConfigParameterStatus(Packet): + id: ConfigParameterId = field(kw_only=True, default=ConfigParameterId.DEVICE_STATE) + status: Status = field(kw_only=True, default=Status.OK) def __post_init__(self): - self.gid = GroupId.VENDOR_ANDROID - self.mt = MessageType.NOTIFICATION + pass @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['AndroidNotification', bytes]: - if fields['gid'] != GroupId.VENDOR_ANDROID or fields['mt'] != MessageType.NOTIFICATION: - raise Exception("Invalid constraint field values") - payload = span - span = bytes([]) - fields['payload'] = payload - try: - return AndroidRangeDiagnosticsNtf.parse(fields.copy(), payload) - except Exception as exn: - pass - return AndroidNotification(**fields), span + def parse(span: bytes) -> Tuple['ConfigParameterStatus', bytes]: + fields = {'payload': None} + if len(span) < 2: + raise Exception('Invalid packet size') + fields['id'] = ConfigParameterId.from_int(span[0]) + fields['status'] = Status.from_int(span[1]) + span = span[2:] + return ConfigParameterStatus(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(payload or self.payload or []) - return UciNotification.serialize(self, payload = bytes(_span)) + _span.append((self.id << 0)) + _span.append((self.status << 0)) + return bytes(_span) @property def size(self) -> int: - return len(self.payload) + return 2 @dataclass -class DeviceResetCmd(CoreCommand): - reset_config: ResetConfig = field(kw_only=True, default=ResetConfig.UWBS_RESET) +class CoreSetConfigRsp(CorePacket): + status: Status = field(kw_only=True, default=Status.OK) + parameters: List[ConfigParameterStatus] = field(kw_only=True, default_factory=list) def __post_init__(self): - self.opcode = 0 + self.mt = MessageType.RESPONSE + self.oid = CoreOpcodeId.SET_CONFIG self.gid = GroupId.CORE - self.mt = MessageType.COMMAND @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['DeviceResetCmd', bytes]: - if fields['opcode'] != 0x0 or fields['gid'] != GroupId.CORE or fields['mt'] != MessageType.COMMAND: + def parse(fields: dict, span: bytes) -> Tuple['CoreSetConfigRsp', bytes]: + if fields['mt'] != MessageType.RESPONSE or fields['oid'] != CoreOpcodeId.SET_CONFIG or fields['gid'] != GroupId.CORE: raise Exception("Invalid constraint field values") - if len(span) < 1: + if len(span) < 2: raise Exception('Invalid packet size') - fields['reset_config'] = ResetConfig.from_int(span[0]) - span = span[1:] - return DeviceResetCmd(**fields), span + fields['status'] = Status.from_int(span[0]) + parameters_count = span[1] + span = span[2:] + if len(span) < parameters_count * 2: + raise Exception('Invalid packet size') + parameters = [] + for n in range(parameters_count): + parameters.append(ConfigParameterStatus.parse_all(span[n * 2:(n + 1) * 2])) + fields['parameters'] = parameters + span = span[parameters_count * 2:] + return CoreSetConfigRsp(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.append((self.reset_config << 0)) - return CoreCommand.serialize(self, payload = bytes(_span)) + _span.append((self.status << 0)) + if len(self.parameters) > 255: + print(f"Invalid length for field CoreSetConfigRsp::parameters: {len(self.parameters)} > 255; the array will be truncated") + del self.parameters[255:] + _span.append((len(self.parameters) << 0)) + for _elt in self.parameters: + _span.extend(_elt.serialize()) + return CorePacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return 1 + return sum([elt.size for elt in self.parameters]) + 2 @dataclass -class DeviceResetRsp(CoreResponse): - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) +class CoreGetConfigCmd(CorePacket): + parameter_ids: List[ConfigParameterId] = field(kw_only=True, default_factory=list) def __post_init__(self): - self.opcode = 0 + self.mt = MessageType.COMMAND + self.oid = CoreOpcodeId.GET_CONFIG self.gid = GroupId.CORE - self.mt = MessageType.RESPONSE @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['DeviceResetRsp', bytes]: - if fields['opcode'] != 0x0 or fields['gid'] != GroupId.CORE or fields['mt'] != MessageType.RESPONSE: + def parse(fields: dict, span: bytes) -> Tuple['CoreGetConfigCmd', bytes]: + if fields['mt'] != MessageType.COMMAND or fields['oid'] != CoreOpcodeId.GET_CONFIG or fields['gid'] != GroupId.CORE: raise Exception("Invalid constraint field values") if len(span) < 1: raise Exception('Invalid packet size') - fields['status'] = StatusCode.from_int(span[0]) + parameter_ids_count = span[0] span = span[1:] - return DeviceResetRsp(**fields), span + if len(span) < parameter_ids_count: + raise Exception('Invalid packet size') + parameter_ids = [] + for n in range(parameter_ids_count): + parameter_ids.append(ConfigParameterId(int.from_bytes(span[n:n + 1], byteorder='little'))) + fields['parameter_ids'] = parameter_ids + span = span[parameter_ids_count:] + return CoreGetConfigCmd(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.append((self.status << 0)) - return CoreResponse.serialize(self, payload = bytes(_span)) + if len(self.parameter_ids) > 255: + print(f"Invalid length for field CoreGetConfigCmd::parameter_ids: {len(self.parameter_ids)} > 255; the array will be truncated") + del self.parameter_ids[255:] + _span.append((len(self.parameter_ids) << 0)) + for _elt in self.parameter_ids: + _span.append(_elt) + return CorePacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return 1 + return len(self.parameter_ids) * 8 + 1 @dataclass -class DeviceStatusNtf(CoreNotification): - device_state: DeviceState = field(kw_only=True, default=DeviceState.DEVICE_STATE_READY) +class CoreGetConfigRsp(CorePacket): + status: Status = field(kw_only=True, default=Status.OK) + parameters: List[ConfigParameter] = field(kw_only=True, default_factory=list) def __post_init__(self): - self.opcode = 1 + self.mt = MessageType.RESPONSE + self.oid = CoreOpcodeId.GET_CONFIG self.gid = GroupId.CORE + + @staticmethod + def parse(fields: dict, span: bytes) -> Tuple['CoreGetConfigRsp', bytes]: + if fields['mt'] != MessageType.RESPONSE or fields['oid'] != CoreOpcodeId.GET_CONFIG or fields['gid'] != GroupId.CORE: + raise Exception("Invalid constraint field values") + if len(span) < 2: + raise Exception('Invalid packet size') + fields['status'] = Status.from_int(span[0]) + parameters_count = span[1] + span = span[2:] + parameters = [] + for n in range(parameters_count): + element, span = ConfigParameter.parse(span) + parameters.append(element) + fields['parameters'] = parameters + return CoreGetConfigRsp(**fields), span + + def serialize(self, payload: bytes = None) -> bytes: + _span = bytearray() + _span.append((self.status << 0)) + if len(self.parameters) > 255: + print(f"Invalid length for field CoreGetConfigRsp::parameters: {len(self.parameters)} > 255; the array will be truncated") + del self.parameters[255:] + _span.append((len(self.parameters) << 0)) + for _elt in self.parameters: + _span.extend(_elt.serialize()) + return CorePacket.serialize(self, payload = bytes(_span)) + + @property + def size(self) -> int: + return sum([elt.size for elt in self.parameters]) + 2 + +@dataclass +class CoreGenericErrorNtf(CorePacket): + status: Status = field(kw_only=True, default=Status.OK) + + def __post_init__(self): self.mt = MessageType.NOTIFICATION + self.oid = CoreOpcodeId.GENERIC_ERROR + self.gid = GroupId.CORE @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['DeviceStatusNtf', bytes]: - if fields['opcode'] != 0x1 or fields['gid'] != GroupId.CORE or fields['mt'] != MessageType.NOTIFICATION: + def parse(fields: dict, span: bytes) -> Tuple['CoreGenericErrorNtf', bytes]: + if fields['mt'] != MessageType.NOTIFICATION or fields['oid'] != CoreOpcodeId.GENERIC_ERROR or fields['gid'] != GroupId.CORE: raise Exception("Invalid constraint field values") if len(span) < 1: raise Exception('Invalid packet size') - fields['device_state'] = DeviceState.from_int(span[0]) + fields['status'] = Status.from_int(span[0]) span = span[1:] - return DeviceStatusNtf(**fields), span + return CoreGenericErrorNtf(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.append((self.device_state << 0)) - return CoreNotification.serialize(self, payload = bytes(_span)) + _span.append((self.status << 0)) + return CorePacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: return 1 @dataclass -class GetDeviceInfoCmd(CoreCommand): +class CoreQueryTimeStampCmd(CorePacket): def __post_init__(self): - self.opcode = 2 - self.gid = GroupId.CORE self.mt = MessageType.COMMAND + self.oid = CoreOpcodeId.QUERY_UWBS_TIMESTAMP + self.gid = GroupId.CORE @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['GetDeviceInfoCmd', bytes]: - if fields['opcode'] != 0x2 or fields['gid'] != GroupId.CORE or fields['mt'] != MessageType.COMMAND: + def parse(fields: dict, span: bytes) -> Tuple['CoreQueryTimeStampCmd', bytes]: + if fields['mt'] != MessageType.COMMAND or fields['oid'] != CoreOpcodeId.QUERY_UWBS_TIMESTAMP or fields['gid'] != GroupId.CORE: raise Exception("Invalid constraint field values") - return GetDeviceInfoCmd(**fields), span + return CoreQueryTimeStampCmd(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - return CoreCommand.serialize(self, payload = bytes(_span)) + return CorePacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: return 0 @dataclass -class GetDeviceInfoRsp(CoreResponse): - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) - uci_version: int = field(kw_only=True, default=0) - mac_version: int = field(kw_only=True, default=0) - phy_version: int = field(kw_only=True, default=0) - uci_test_version: int = field(kw_only=True, default=0) - vendor_spec_info: bytearray = field(kw_only=True, default_factory=bytearray) +class CoreQueryTimeStampRsp(CorePacket): + status: Status = field(kw_only=True, default=Status.OK) + timeStamp: int = field(kw_only=True, default=0) def __post_init__(self): - self.opcode = 2 - self.gid = GroupId.CORE self.mt = MessageType.RESPONSE + self.oid = CoreOpcodeId.QUERY_UWBS_TIMESTAMP + self.gid = GroupId.CORE @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['GetDeviceInfoRsp', bytes]: - if fields['opcode'] != 0x2 or fields['gid'] != GroupId.CORE or fields['mt'] != MessageType.RESPONSE: + def parse(fields: dict, span: bytes) -> Tuple['CoreQueryTimeStampRsp', bytes]: + if fields['mt'] != MessageType.RESPONSE or fields['oid'] != CoreOpcodeId.QUERY_UWBS_TIMESTAMP or fields['gid'] != GroupId.CORE: raise Exception("Invalid constraint field values") - if len(span) < 10: - raise Exception('Invalid packet size') - fields['status'] = StatusCode.from_int(span[0]) - value_ = int.from_bytes(span[1:3], byteorder='little') - fields['uci_version'] = value_ - value_ = int.from_bytes(span[3:5], byteorder='little') - fields['mac_version'] = value_ - value_ = int.from_bytes(span[5:7], byteorder='little') - fields['phy_version'] = value_ - value_ = int.from_bytes(span[7:9], byteorder='little') - fields['uci_test_version'] = value_ - vendor_spec_info_count = span[9] - span = span[10:] - if len(span) < vendor_spec_info_count: + if len(span) < 9: raise Exception('Invalid packet size') - fields['vendor_spec_info'] = list(span[:vendor_spec_info_count]) - span = span[vendor_spec_info_count:] - return GetDeviceInfoRsp(**fields), span + fields['status'] = Status.from_int(span[0]) + value_ = int.from_bytes(span[1:9], byteorder='little') + fields['timeStamp'] = value_ + span = span[9:] + return CoreQueryTimeStampRsp(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() _span.append((self.status << 0)) - if self.uci_version > 65535: - print(f"Invalid value for field GetDeviceInfoRsp::uci_version: {self.uci_version} > 65535; the value will be truncated") - self.uci_version &= 65535 - _span.extend(int.to_bytes((self.uci_version << 0), length=2, byteorder='little')) - if self.mac_version > 65535: - print(f"Invalid value for field GetDeviceInfoRsp::mac_version: {self.mac_version} > 65535; the value will be truncated") - self.mac_version &= 65535 - _span.extend(int.to_bytes((self.mac_version << 0), length=2, byteorder='little')) - if self.phy_version > 65535: - print(f"Invalid value for field GetDeviceInfoRsp::phy_version: {self.phy_version} > 65535; the value will be truncated") - self.phy_version &= 65535 - _span.extend(int.to_bytes((self.phy_version << 0), length=2, byteorder='little')) - if self.uci_test_version > 65535: - print(f"Invalid value for field GetDeviceInfoRsp::uci_test_version: {self.uci_test_version} > 65535; the value will be truncated") - self.uci_test_version &= 65535 - _span.extend(int.to_bytes((self.uci_test_version << 0), length=2, byteorder='little')) - if len(self.vendor_spec_info) > 255: - print(f"Invalid length for field GetDeviceInfoRsp::vendor_spec_info: {len(self.vendor_spec_info)} > 255; the array will be truncated") - del self.vendor_spec_info[255:] - _span.append((len(self.vendor_spec_info) << 0)) - _span.extend(self.vendor_spec_info) - return CoreResponse.serialize(self, payload = bytes(_span)) + if self.timeStamp > 18446744073709551615: + print(f"Invalid value for field CoreQueryTimeStampRsp::timeStamp: {self.timeStamp} > 18446744073709551615; the value will be truncated") + self.timeStamp &= 18446744073709551615 + _span.extend(int.to_bytes((self.timeStamp << 0), length=8, byteorder='little')) + return CorePacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.vendor_spec_info) * 1 + 10 + return 9 @dataclass -class GetCapsInfoCmd(CoreCommand): - +class SessionInitCmd(SessionConfigPacket): + session_id: int = field(kw_only=True, default=0) + session_type: SessionType = field(kw_only=True, default=SessionType.FIRA_RANGING_SESSION) def __post_init__(self): - self.opcode = 3 - self.gid = GroupId.CORE self.mt = MessageType.COMMAND + self.oid = SessionConfigOpcodeId.INIT + self.gid = GroupId.SESSION_CONFIG @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['GetCapsInfoCmd', bytes]: - if fields['opcode'] != 0x3 or fields['gid'] != GroupId.CORE or fields['mt'] != MessageType.COMMAND: + def parse(fields: dict, span: bytes) -> Tuple['SessionInitCmd', bytes]: + if fields['mt'] != MessageType.COMMAND or fields['oid'] != SessionConfigOpcodeId.INIT or fields['gid'] != GroupId.SESSION_CONFIG: raise Exception("Invalid constraint field values") - return GetCapsInfoCmd(**fields), span + if len(span) < 5: + raise Exception('Invalid packet size') + value_ = int.from_bytes(span[0:4], byteorder='little') + fields['session_id'] = value_ + fields['session_type'] = SessionType.from_int(span[4]) + span = span[5:] + return SessionInitCmd(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - return CoreCommand.serialize(self, payload = bytes(_span)) + if self.session_id > 4294967295: + print(f"Invalid value for field SessionInitCmd::session_id: {self.session_id} > 4294967295; the value will be truncated") + self.session_id &= 4294967295 + _span.extend(int.to_bytes((self.session_id << 0), length=4, byteorder='little')) + _span.append((self.session_type << 0)) + return SessionConfigPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return 0 + return 5 @dataclass -class CapTlv(Packet): - t: CapTlvType = field(kw_only=True, default=CapTlvType.SUPPORTED_FIRA_PHY_VERSION_RANGE) - v: bytearray = field(kw_only=True, default_factory=bytearray) +class SessionInitRsp_V2(SessionConfigPacket): + status: Status = field(kw_only=True, default=Status.OK) + session_handle: int = field(kw_only=True, default=0) def __post_init__(self): - pass + self.mt = MessageType.RESPONSE + self.oid = SessionConfigOpcodeId.INIT + self.gid = GroupId.SESSION_CONFIG @staticmethod - def parse(span: bytes) -> Tuple['CapTlv', bytes]: - fields = {'payload': None} - if len(span) < 2: - raise Exception('Invalid packet size') - fields['t'] = CapTlvType.from_int(span[0]) - v_count = span[1] - span = span[2:] - if len(span) < v_count: + def parse(fields: dict, span: bytes) -> Tuple['SessionInitRsp_V2', bytes]: + if fields['mt'] != MessageType.RESPONSE or fields['oid'] != SessionConfigOpcodeId.INIT or fields['gid'] != GroupId.SESSION_CONFIG: + raise Exception("Invalid constraint field values") + if len(span) < 5: raise Exception('Invalid packet size') - fields['v'] = list(span[:v_count]) - span = span[v_count:] - return CapTlv(**fields), span + fields['status'] = Status.from_int(span[0]) + value_ = int.from_bytes(span[1:5], byteorder='little') + fields['session_handle'] = value_ + span = span[5:] + return SessionInitRsp_V2(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.append((self.t << 0)) - if len(self.v) > 255: - print(f"Invalid length for field CapTlv::v: {len(self.v)} > 255; the array will be truncated") - del self.v[255:] - _span.append((len(self.v) << 0)) - _span.extend(self.v) - return bytes(_span) + _span.append((self.status << 0)) + if self.session_handle > 4294967295: + print(f"Invalid value for field SessionInitRsp_V2::session_handle: {self.session_handle} > 4294967295; the value will be truncated") + self.session_handle &= 4294967295 + _span.extend(int.to_bytes((self.session_handle << 0), length=4, byteorder='little')) + return SessionConfigPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.v) * 1 + 2 + return 5 @dataclass -class GetCapsInfoRsp(CoreResponse): - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) - tlvs: List[CapTlv] = field(kw_only=True, default_factory=list) +class SessionInitRsp(SessionConfigPacket): + status: Status = field(kw_only=True, default=Status.OK) def __post_init__(self): - self.opcode = 3 - self.gid = GroupId.CORE self.mt = MessageType.RESPONSE + self.oid = SessionConfigOpcodeId.INIT + self.gid = GroupId.SESSION_CONFIG @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['GetCapsInfoRsp', bytes]: - if fields['opcode'] != 0x3 or fields['gid'] != GroupId.CORE or fields['mt'] != MessageType.RESPONSE: + def parse(fields: dict, span: bytes) -> Tuple['SessionInitRsp', bytes]: + if fields['mt'] != MessageType.RESPONSE or fields['oid'] != SessionConfigOpcodeId.INIT or fields['gid'] != GroupId.SESSION_CONFIG: raise Exception("Invalid constraint field values") - if len(span) < 2: + if len(span) < 1: raise Exception('Invalid packet size') - fields['status'] = StatusCode.from_int(span[0]) - tlvs_count = span[1] - span = span[2:] - tlvs = [] - for n in range(tlvs_count): - element, span = CapTlv.parse(span) - tlvs.append(element) - fields['tlvs'] = tlvs - return GetCapsInfoRsp(**fields), span + fields['status'] = Status.from_int(span[0]) + span = span[1:] + return SessionInitRsp(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() _span.append((self.status << 0)) - if len(self.tlvs) > 255: - print(f"Invalid length for field GetCapsInfoRsp::tlvs: {len(self.tlvs)} > 255; the array will be truncated") - del self.tlvs[255:] - _span.append((len(self.tlvs) << 0)) - for _elt in self.tlvs: - _span.extend(_elt.serialize()) - return CoreResponse.serialize(self, payload = bytes(_span)) + return SessionConfigPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return sum([elt.size for elt in self.tlvs]) + 2 + return 1 @dataclass -class DeviceConfigTlv(Packet): - cfg_id: DeviceConfigId = field(kw_only=True, default=DeviceConfigId.DEVICE_STATE) - v: bytearray = field(kw_only=True, default_factory=bytearray) +class SessionDeinitCmd(SessionConfigPacket): + session_token: int = field(kw_only=True, default=0) def __post_init__(self): - pass + self.mt = MessageType.COMMAND + self.oid = SessionConfigOpcodeId.DEINIT + self.gid = GroupId.SESSION_CONFIG @staticmethod - def parse(span: bytes) -> Tuple['DeviceConfigTlv', bytes]: - fields = {'payload': None} - if len(span) < 2: - raise Exception('Invalid packet size') - fields['cfg_id'] = DeviceConfigId.from_int(span[0]) - v_count = span[1] - span = span[2:] - if len(span) < v_count: + def parse(fields: dict, span: bytes) -> Tuple['SessionDeinitCmd', bytes]: + if fields['mt'] != MessageType.COMMAND or fields['oid'] != SessionConfigOpcodeId.DEINIT or fields['gid'] != GroupId.SESSION_CONFIG: + raise Exception("Invalid constraint field values") + if len(span) < 4: raise Exception('Invalid packet size') - fields['v'] = list(span[:v_count]) - span = span[v_count:] - return DeviceConfigTlv(**fields), span + value_ = int.from_bytes(span[0:4], byteorder='little') + fields['session_token'] = value_ + span = span[4:] + return SessionDeinitCmd(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.append((self.cfg_id << 0)) - if len(self.v) > 255: - print(f"Invalid length for field DeviceConfigTlv::v: {len(self.v)} > 255; the array will be truncated") - del self.v[255:] - _span.append((len(self.v) << 0)) - _span.extend(self.v) - return bytes(_span) + if self.session_token > 4294967295: + print(f"Invalid value for field SessionDeinitCmd::session_token: {self.session_token} > 4294967295; the value will be truncated") + self.session_token &= 4294967295 + _span.extend(int.to_bytes((self.session_token << 0), length=4, byteorder='little')) + return SessionConfigPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.v) * 1 + 2 + return 4 @dataclass -class SetConfigCmd(CoreCommand): - tlvs: List[DeviceConfigTlv] = field(kw_only=True, default_factory=list) +class SessionDeinitRsp(SessionConfigPacket): + status: Status = field(kw_only=True, default=Status.OK) def __post_init__(self): - self.opcode = 4 - self.gid = GroupId.CORE - self.mt = MessageType.COMMAND + self.mt = MessageType.RESPONSE + self.oid = SessionConfigOpcodeId.DEINIT + self.gid = GroupId.SESSION_CONFIG @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SetConfigCmd', bytes]: - if fields['opcode'] != 0x4 or fields['gid'] != GroupId.CORE or fields['mt'] != MessageType.COMMAND: + def parse(fields: dict, span: bytes) -> Tuple['SessionDeinitRsp', bytes]: + if fields['mt'] != MessageType.RESPONSE or fields['oid'] != SessionConfigOpcodeId.DEINIT or fields['gid'] != GroupId.SESSION_CONFIG: raise Exception("Invalid constraint field values") if len(span) < 1: raise Exception('Invalid packet size') - tlvs_count = span[0] + fields['status'] = Status.from_int(span[0]) span = span[1:] - tlvs = [] - for n in range(tlvs_count): - element, span = DeviceConfigTlv.parse(span) - tlvs.append(element) - fields['tlvs'] = tlvs - return SetConfigCmd(**fields), span + return SessionDeinitRsp(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - if len(self.tlvs) > 255: - print(f"Invalid length for field SetConfigCmd::tlvs: {len(self.tlvs)} > 255; the array will be truncated") - del self.tlvs[255:] - _span.append((len(self.tlvs) << 0)) - for _elt in self.tlvs: - _span.extend(_elt.serialize()) - return CoreCommand.serialize(self, payload = bytes(_span)) + _span.append((self.status << 0)) + return SessionConfigPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return sum([elt.size for elt in self.tlvs]) + 1 + return 1 @dataclass -class DeviceConfigStatus(Packet): - cfg_id: DeviceConfigId = field(kw_only=True, default=DeviceConfigId.DEVICE_STATE) - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) +class SessionStatusNtf(SessionConfigPacket): + session_token: int = field(kw_only=True, default=0) + session_state: SessionState = field(kw_only=True, default=SessionState.SESSION_STATE_INIT) + reason_code: int = field(kw_only=True, default=0) def __post_init__(self): - pass + self.mt = MessageType.NOTIFICATION + self.oid = SessionConfigOpcodeId.STATUS + self.gid = GroupId.SESSION_CONFIG @staticmethod - def parse(span: bytes) -> Tuple['DeviceConfigStatus', bytes]: - fields = {'payload': None} - if len(span) < 2: + def parse(fields: dict, span: bytes) -> Tuple['SessionStatusNtf', bytes]: + if fields['mt'] != MessageType.NOTIFICATION or fields['oid'] != SessionConfigOpcodeId.STATUS or fields['gid'] != GroupId.SESSION_CONFIG: + raise Exception("Invalid constraint field values") + if len(span) < 6: raise Exception('Invalid packet size') - fields['cfg_id'] = DeviceConfigId.from_int(span[0]) - fields['status'] = StatusCode.from_int(span[1]) - span = span[2:] - return DeviceConfigStatus(**fields), span + value_ = int.from_bytes(span[0:4], byteorder='little') + fields['session_token'] = value_ + fields['session_state'] = SessionState.from_int(span[4]) + fields['reason_code'] = span[5] + span = span[6:] + return SessionStatusNtf(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.append((self.cfg_id << 0)) - _span.append((self.status << 0)) - return bytes(_span) + if self.session_token > 4294967295: + print(f"Invalid value for field SessionStatusNtf::session_token: {self.session_token} > 4294967295; the value will be truncated") + self.session_token &= 4294967295 + _span.extend(int.to_bytes((self.session_token << 0), length=4, byteorder='little')) + _span.append((self.session_state << 0)) + if self.reason_code > 255: + print(f"Invalid value for field SessionStatusNtf::reason_code: {self.reason_code} > 255; the value will be truncated") + self.reason_code &= 255 + _span.append((self.reason_code << 0)) + return SessionConfigPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return 2 + return 6 @dataclass -class SetConfigRsp(CoreResponse): - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) - cfg_status: List[DeviceConfigStatus] = field(kw_only=True, default_factory=list) +class AppConfigTlv(Packet): + cfg_id: AppConfigTlvType = field(kw_only=True, default=AppConfigTlvType.DEVICE_TYPE) + v: bytearray = field(kw_only=True, default_factory=bytearray) def __post_init__(self): - self.opcode = 4 - self.gid = GroupId.CORE - self.mt = MessageType.RESPONSE + pass @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SetConfigRsp', bytes]: - if fields['opcode'] != 0x4 or fields['gid'] != GroupId.CORE or fields['mt'] != MessageType.RESPONSE: - raise Exception("Invalid constraint field values") + def parse(span: bytes) -> Tuple['AppConfigTlv', bytes]: + fields = {'payload': None} if len(span) < 2: raise Exception('Invalid packet size') - fields['status'] = StatusCode.from_int(span[0]) - cfg_status_count = span[1] + fields['cfg_id'] = AppConfigTlvType.from_int(span[0]) + v_count = span[1] span = span[2:] - if len(span) < cfg_status_count * 2: + if len(span) < v_count: raise Exception('Invalid packet size') - cfg_status = [] - for n in range(cfg_status_count): - cfg_status.append(DeviceConfigStatus.parse_all(span[n * 2:(n + 1) * 2])) - fields['cfg_status'] = cfg_status - span = span[cfg_status_count * 2:] - return SetConfigRsp(**fields), span + fields['v'] = list(span[:v_count]) + span = span[v_count:] + return AppConfigTlv(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.append((self.status << 0)) - if len(self.cfg_status) > 255: - print(f"Invalid length for field SetConfigRsp::cfg_status: {len(self.cfg_status)} > 255; the array will be truncated") - del self.cfg_status[255:] - _span.append((len(self.cfg_status) << 0)) - for _elt in self.cfg_status: - _span.extend(_elt.serialize()) - return CoreResponse.serialize(self, payload = bytes(_span)) + _span.append((self.cfg_id << 0)) + if len(self.v) > 255: + print(f"Invalid length for field AppConfigTlv::v: {len(self.v)} > 255; the array will be truncated") + del self.v[255:] + _span.append((len(self.v) << 0)) + _span.extend(self.v) + return bytes(_span) @property def size(self) -> int: - return sum([elt.size for elt in self.cfg_status]) + 2 + return len(self.v) * 1 + 2 @dataclass -class GetConfigCmd(CoreCommand): - cfg_id: bytearray = field(kw_only=True, default_factory=bytearray) +class SessionSetAppConfigCmd(SessionConfigPacket): + session_token: int = field(kw_only=True, default=0) + tlvs: List[AppConfigTlv] = field(kw_only=True, default_factory=list) def __post_init__(self): - self.opcode = 5 - self.gid = GroupId.CORE self.mt = MessageType.COMMAND + self.oid = SessionConfigOpcodeId.SET_APP_CONFIG + self.gid = GroupId.SESSION_CONFIG @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['GetConfigCmd', bytes]: - if fields['opcode'] != 0x5 or fields['gid'] != GroupId.CORE or fields['mt'] != MessageType.COMMAND: - raise Exception("Invalid constraint field values") - if len(span) < 1: - raise Exception('Invalid packet size') - cfg_id_count = span[0] - span = span[1:] - if len(span) < cfg_id_count: - raise Exception('Invalid packet size') - fields['cfg_id'] = list(span[:cfg_id_count]) - span = span[cfg_id_count:] - return GetConfigCmd(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - if len(self.cfg_id) > 255: - print(f"Invalid length for field GetConfigCmd::cfg_id: {len(self.cfg_id)} > 255; the array will be truncated") - del self.cfg_id[255:] - _span.append((len(self.cfg_id) << 0)) - _span.extend(self.cfg_id) - return CoreCommand.serialize(self, payload = bytes(_span)) - - @property - def size(self) -> int: - return len(self.cfg_id) * 1 + 1 - -@dataclass -class GetConfigRsp(CoreResponse): - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) - tlvs: List[DeviceConfigTlv] = field(kw_only=True, default_factory=list) - - def __post_init__(self): - self.opcode = 5 - self.gid = GroupId.CORE - self.mt = MessageType.RESPONSE - - @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['GetConfigRsp', bytes]: - if fields['opcode'] != 0x5 or fields['gid'] != GroupId.CORE or fields['mt'] != MessageType.RESPONSE: + def parse(fields: dict, span: bytes) -> Tuple['SessionSetAppConfigCmd', bytes]: + if fields['mt'] != MessageType.COMMAND or fields['oid'] != SessionConfigOpcodeId.SET_APP_CONFIG or fields['gid'] != GroupId.SESSION_CONFIG: raise Exception("Invalid constraint field values") - if len(span) < 2: + if len(span) < 5: raise Exception('Invalid packet size') - fields['status'] = StatusCode.from_int(span[0]) - tlvs_count = span[1] - span = span[2:] + value_ = int.from_bytes(span[0:4], byteorder='little') + fields['session_token'] = value_ + tlvs_count = span[4] + span = span[5:] tlvs = [] for n in range(tlvs_count): - element, span = DeviceConfigTlv.parse(span) + element, span = AppConfigTlv.parse(span) tlvs.append(element) fields['tlvs'] = tlvs - return GetConfigRsp(**fields), span + return SessionSetAppConfigCmd(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.append((self.status << 0)) + if self.session_token > 4294967295: + print(f"Invalid value for field SessionSetAppConfigCmd::session_token: {self.session_token} > 4294967295; the value will be truncated") + self.session_token &= 4294967295 + _span.extend(int.to_bytes((self.session_token << 0), length=4, byteorder='little')) if len(self.tlvs) > 255: - print(f"Invalid length for field GetConfigRsp::tlvs: {len(self.tlvs)} > 255; the array will be truncated") + print(f"Invalid length for field SessionSetAppConfigCmd::tlvs: {len(self.tlvs)} > 255; the array will be truncated") del self.tlvs[255:] _span.append((len(self.tlvs) << 0)) for _elt in self.tlvs: _span.extend(_elt.serialize()) - return CoreResponse.serialize(self, payload = bytes(_span)) + return SessionConfigPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return sum([elt.size for elt in self.tlvs]) + 2 + return sum([elt.size for elt in self.tlvs]) + 5 @dataclass -class GenericError(CoreNotification): - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) +class AppConfigStatus(Packet): + cfg_id: AppConfigTlvType = field(kw_only=True, default=AppConfigTlvType.DEVICE_TYPE) + status: Status = field(kw_only=True, default=Status.OK) def __post_init__(self): - self.opcode = 7 - self.gid = GroupId.CORE - self.mt = MessageType.NOTIFICATION + pass @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['GenericError', bytes]: - if fields['opcode'] != 0x7 or fields['gid'] != GroupId.CORE or fields['mt'] != MessageType.NOTIFICATION: - raise Exception("Invalid constraint field values") - if len(span) < 1: + def parse(span: bytes) -> Tuple['AppConfigStatus', bytes]: + fields = {'payload': None} + if len(span) < 2: raise Exception('Invalid packet size') - fields['status'] = StatusCode.from_int(span[0]) - span = span[1:] - return GenericError(**fields), span + fields['cfg_id'] = AppConfigTlvType.from_int(span[0]) + fields['status'] = Status.from_int(span[1]) + span = span[2:] + return AppConfigStatus(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() + _span.append((self.cfg_id << 0)) _span.append((self.status << 0)) - return CoreNotification.serialize(self, payload = bytes(_span)) + return bytes(_span) @property def size(self) -> int: - return 1 + return 2 @dataclass -class CoreQueryTimeStampCmd(CoreCommand): - +class SessionSetAppConfigRsp(SessionConfigPacket): + status: Status = field(kw_only=True, default=Status.OK) + cfg_status: List[AppConfigStatus] = field(kw_only=True, default_factory=list) def __post_init__(self): - self.opcode = 8 - self.gid = GroupId.CORE - self.mt = MessageType.COMMAND + self.mt = MessageType.RESPONSE + self.oid = SessionConfigOpcodeId.SET_APP_CONFIG + self.gid = GroupId.SESSION_CONFIG @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['CoreQueryTimeStampCmd', bytes]: - if fields['opcode'] != 0x8 or fields['gid'] != GroupId.CORE or fields['mt'] != MessageType.COMMAND: + def parse(fields: dict, span: bytes) -> Tuple['SessionSetAppConfigRsp', bytes]: + if fields['mt'] != MessageType.RESPONSE or fields['oid'] != SessionConfigOpcodeId.SET_APP_CONFIG or fields['gid'] != GroupId.SESSION_CONFIG: raise Exception("Invalid constraint field values") - return CoreQueryTimeStampCmd(**fields), span + if len(span) < 2: + raise Exception('Invalid packet size') + fields['status'] = Status.from_int(span[0]) + cfg_status_count = span[1] + span = span[2:] + if len(span) < cfg_status_count * 2: + raise Exception('Invalid packet size') + cfg_status = [] + for n in range(cfg_status_count): + cfg_status.append(AppConfigStatus.parse_all(span[n * 2:(n + 1) * 2])) + fields['cfg_status'] = cfg_status + span = span[cfg_status_count * 2:] + return SessionSetAppConfigRsp(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - return CoreCommand.serialize(self, payload = bytes(_span)) + _span.append((self.status << 0)) + if len(self.cfg_status) > 255: + print(f"Invalid length for field SessionSetAppConfigRsp::cfg_status: {len(self.cfg_status)} > 255; the array will be truncated") + del self.cfg_status[255:] + _span.append((len(self.cfg_status) << 0)) + for _elt in self.cfg_status: + _span.extend(_elt.serialize()) + return SessionConfigPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return 0 + return sum([elt.size for elt in self.cfg_status]) + 2 @dataclass -class CoreQueryTimeStampRsp(CoreResponse): - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) - timeStamp: int = field(kw_only=True, default=0) +class SessionGetAppConfigCmd(SessionConfigPacket): + session_token: int = field(kw_only=True, default=0) + app_cfg: List[AppConfigTlvType] = field(kw_only=True, default_factory=list) def __post_init__(self): - self.opcode = 8 - self.gid = GroupId.CORE - self.mt = MessageType.RESPONSE + self.mt = MessageType.COMMAND + self.oid = SessionConfigOpcodeId.GET_APP_CONFIG + self.gid = GroupId.SESSION_CONFIG @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['CoreQueryTimeStampRsp', bytes]: - if fields['opcode'] != 0x8 or fields['gid'] != GroupId.CORE or fields['mt'] != MessageType.RESPONSE: + def parse(fields: dict, span: bytes) -> Tuple['SessionGetAppConfigCmd', bytes]: + if fields['mt'] != MessageType.COMMAND or fields['oid'] != SessionConfigOpcodeId.GET_APP_CONFIG or fields['gid'] != GroupId.SESSION_CONFIG: raise Exception("Invalid constraint field values") - if len(span) < 9: + if len(span) < 5: raise Exception('Invalid packet size') - fields['status'] = StatusCode.from_int(span[0]) - value_ = int.from_bytes(span[1:9], byteorder='little') - fields['timeStamp'] = value_ - span = span[9:] - return CoreQueryTimeStampRsp(**fields), span + value_ = int.from_bytes(span[0:4], byteorder='little') + fields['session_token'] = value_ + app_cfg_count = span[4] + span = span[5:] + if len(span) < app_cfg_count: + raise Exception('Invalid packet size') + app_cfg = [] + for n in range(app_cfg_count): + app_cfg.append(AppConfigTlvType(int.from_bytes(span[n:n + 1], byteorder='little'))) + fields['app_cfg'] = app_cfg + span = span[app_cfg_count:] + return SessionGetAppConfigCmd(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.append((self.status << 0)) - if self.timeStamp > 18446744073709551615: - print(f"Invalid value for field CoreQueryTimeStampRsp::timeStamp: {self.timeStamp} > 18446744073709551615; the value will be truncated") - self.timeStamp &= 18446744073709551615 - _span.extend(int.to_bytes((self.timeStamp << 0), length=8, byteorder='little')) - return CoreResponse.serialize(self, payload = bytes(_span)) + if self.session_token > 4294967295: + print(f"Invalid value for field SessionGetAppConfigCmd::session_token: {self.session_token} > 4294967295; the value will be truncated") + self.session_token &= 4294967295 + _span.extend(int.to_bytes((self.session_token << 0), length=4, byteorder='little')) + if len(self.app_cfg) > 255: + print(f"Invalid length for field SessionGetAppConfigCmd::app_cfg: {len(self.app_cfg)} > 255; the array will be truncated") + del self.app_cfg[255:] + _span.append((len(self.app_cfg) << 0)) + for _elt in self.app_cfg: + _span.append(_elt) + return SessionConfigPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return 9 + return len(self.app_cfg) * 8 + 5 @dataclass -class SessionInitCmd(SessionConfigCommand): - session_id: int = field(kw_only=True, default=0) - session_type: SessionType = field(kw_only=True, default=SessionType.FIRA_RANGING_SESSION) +class SessionGetAppConfigRsp(SessionConfigPacket): + status: Status = field(kw_only=True, default=Status.OK) + tlvs: List[AppConfigTlv] = field(kw_only=True, default_factory=list) def __post_init__(self): - self.opcode = 0 + self.mt = MessageType.RESPONSE + self.oid = SessionConfigOpcodeId.GET_APP_CONFIG self.gid = GroupId.SESSION_CONFIG - self.mt = MessageType.COMMAND @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionInitCmd', bytes]: - if fields['opcode'] != 0x0 or fields['gid'] != GroupId.SESSION_CONFIG or fields['mt'] != MessageType.COMMAND: + def parse(fields: dict, span: bytes) -> Tuple['SessionGetAppConfigRsp', bytes]: + if fields['mt'] != MessageType.RESPONSE or fields['oid'] != SessionConfigOpcodeId.GET_APP_CONFIG or fields['gid'] != GroupId.SESSION_CONFIG: raise Exception("Invalid constraint field values") - if len(span) < 5: + if len(span) < 2: raise Exception('Invalid packet size') - value_ = int.from_bytes(span[0:4], byteorder='little') - fields['session_id'] = value_ - fields['session_type'] = SessionType.from_int(span[4]) - span = span[5:] - return SessionInitCmd(**fields), span + fields['status'] = Status.from_int(span[0]) + tlvs_count = span[1] + span = span[2:] + tlvs = [] + for n in range(tlvs_count): + element, span = AppConfigTlv.parse(span) + tlvs.append(element) + fields['tlvs'] = tlvs + return SessionGetAppConfigRsp(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - if self.session_id > 4294967295: - print(f"Invalid value for field SessionInitCmd::session_id: {self.session_id} > 4294967295; the value will be truncated") - self.session_id &= 4294967295 - _span.extend(int.to_bytes((self.session_id << 0), length=4, byteorder='little')) - _span.append((self.session_type << 0)) - return SessionConfigCommand.serialize(self, payload = bytes(_span)) + _span.append((self.status << 0)) + if len(self.tlvs) > 255: + print(f"Invalid length for field SessionGetAppConfigRsp::tlvs: {len(self.tlvs)} > 255; the array will be truncated") + del self.tlvs[255:] + _span.append((len(self.tlvs) << 0)) + for _elt in self.tlvs: + _span.extend(_elt.serialize()) + return SessionConfigPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return 5 + return sum([elt.size for elt in self.tlvs]) + 2 @dataclass -class SessionInitRsp_V2(SessionConfigResponse): - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) - session_handle: int = field(kw_only=True, default=0) +class SessionGetCountCmd(SessionConfigPacket): + def __post_init__(self): - self.opcode = 0 + self.mt = MessageType.COMMAND + self.oid = SessionConfigOpcodeId.GET_COUNT self.gid = GroupId.SESSION_CONFIG - self.mt = MessageType.RESPONSE @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionInitRsp_V2', bytes]: - if fields['opcode'] != 0x0 or fields['gid'] != GroupId.SESSION_CONFIG or fields['mt'] != MessageType.RESPONSE: + def parse(fields: dict, span: bytes) -> Tuple['SessionGetCountCmd', bytes]: + if fields['mt'] != MessageType.COMMAND or fields['oid'] != SessionConfigOpcodeId.GET_COUNT or fields['gid'] != GroupId.SESSION_CONFIG: raise Exception("Invalid constraint field values") - if len(span) < 5: - raise Exception('Invalid packet size') - fields['status'] = StatusCode.from_int(span[0]) - value_ = int.from_bytes(span[1:5], byteorder='little') - fields['session_handle'] = value_ - span = span[5:] - return SessionInitRsp_V2(**fields), span + return SessionGetCountCmd(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.append((self.status << 0)) - if self.session_handle > 4294967295: - print(f"Invalid value for field SessionInitRsp_V2::session_handle: {self.session_handle} > 4294967295; the value will be truncated") - self.session_handle &= 4294967295 - _span.extend(int.to_bytes((self.session_handle << 0), length=4, byteorder='little')) - return SessionConfigResponse.serialize(self, payload = bytes(_span)) + return SessionConfigPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return 5 + return 0 @dataclass -class SessionInitRsp(SessionConfigResponse): - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) +class SessionGetCountRsp(SessionConfigPacket): + status: Status = field(kw_only=True, default=Status.OK) + session_count: int = field(kw_only=True, default=0) def __post_init__(self): - self.opcode = 0 - self.gid = GroupId.SESSION_CONFIG self.mt = MessageType.RESPONSE + self.oid = SessionConfigOpcodeId.GET_COUNT + self.gid = GroupId.SESSION_CONFIG @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionInitRsp', bytes]: - if fields['opcode'] != 0x0 or fields['gid'] != GroupId.SESSION_CONFIG or fields['mt'] != MessageType.RESPONSE: + def parse(fields: dict, span: bytes) -> Tuple['SessionGetCountRsp', bytes]: + if fields['mt'] != MessageType.RESPONSE or fields['oid'] != SessionConfigOpcodeId.GET_COUNT or fields['gid'] != GroupId.SESSION_CONFIG: raise Exception("Invalid constraint field values") - if len(span) < 1: + if len(span) < 2: raise Exception('Invalid packet size') - fields['status'] = StatusCode.from_int(span[0]) - span = span[1:] - return SessionInitRsp(**fields), span + fields['status'] = Status.from_int(span[0]) + fields['session_count'] = span[1] + span = span[2:] + return SessionGetCountRsp(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() _span.append((self.status << 0)) - return SessionConfigResponse.serialize(self, payload = bytes(_span)) + if self.session_count > 255: + print(f"Invalid value for field SessionGetCountRsp::session_count: {self.session_count} > 255; the value will be truncated") + self.session_count &= 255 + _span.append((self.session_count << 0)) + return SessionConfigPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return 1 + return 2 @dataclass -class SessionDeinitCmd(SessionConfigCommand): +class SessionGetStateCmd(SessionConfigPacket): session_token: int = field(kw_only=True, default=0) def __post_init__(self): - self.opcode = 1 - self.gid = GroupId.SESSION_CONFIG self.mt = MessageType.COMMAND + self.oid = SessionConfigOpcodeId.GET_STATE + self.gid = GroupId.SESSION_CONFIG @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionDeinitCmd', bytes]: - if fields['opcode'] != 0x1 or fields['gid'] != GroupId.SESSION_CONFIG or fields['mt'] != MessageType.COMMAND: + def parse(fields: dict, span: bytes) -> Tuple['SessionGetStateCmd', bytes]: + if fields['mt'] != MessageType.COMMAND or fields['oid'] != SessionConfigOpcodeId.GET_STATE or fields['gid'] != GroupId.SESSION_CONFIG: raise Exception("Invalid constraint field values") if len(span) < 4: raise Exception('Invalid packet size') value_ = int.from_bytes(span[0:4], byteorder='little') fields['session_token'] = value_ span = span[4:] - return SessionDeinitCmd(**fields), span + return SessionGetStateCmd(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() if self.session_token > 4294967295: - print(f"Invalid value for field SessionDeinitCmd::session_token: {self.session_token} > 4294967295; the value will be truncated") + print(f"Invalid value for field SessionGetStateCmd::session_token: {self.session_token} > 4294967295; the value will be truncated") self.session_token &= 4294967295 _span.extend(int.to_bytes((self.session_token << 0), length=4, byteorder='little')) - return SessionConfigCommand.serialize(self, payload = bytes(_span)) + return SessionConfigPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: return 4 @dataclass -class SessionDeinitRsp(SessionConfigResponse): - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) +class SessionGetStateRsp(SessionConfigPacket): + status: Status = field(kw_only=True, default=Status.OK) + session_state: SessionState = field(kw_only=True, default=SessionState.SESSION_STATE_INIT) def __post_init__(self): - self.opcode = 1 - self.gid = GroupId.SESSION_CONFIG self.mt = MessageType.RESPONSE + self.oid = SessionConfigOpcodeId.GET_STATE + self.gid = GroupId.SESSION_CONFIG @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionDeinitRsp', bytes]: - if fields['opcode'] != 0x1 or fields['gid'] != GroupId.SESSION_CONFIG or fields['mt'] != MessageType.RESPONSE: + def parse(fields: dict, span: bytes) -> Tuple['SessionGetStateRsp', bytes]: + if fields['mt'] != MessageType.RESPONSE or fields['oid'] != SessionConfigOpcodeId.GET_STATE or fields['gid'] != GroupId.SESSION_CONFIG: raise Exception("Invalid constraint field values") - if len(span) < 1: + if len(span) < 2: raise Exception('Invalid packet size') - fields['status'] = StatusCode.from_int(span[0]) - span = span[1:] - return SessionDeinitRsp(**fields), span + fields['status'] = Status.from_int(span[0]) + fields['session_state'] = SessionState.from_int(span[1]) + span = span[2:] + return SessionGetStateRsp(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() _span.append((self.status << 0)) - return SessionConfigResponse.serialize(self, payload = bytes(_span)) + _span.append((self.session_state << 0)) + return SessionConfigPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return 1 + return 2 @dataclass -class SessionStatusNtf(SessionConfigNotification): - session_token: int = field(kw_only=True, default=0) - session_state: SessionState = field(kw_only=True, default=SessionState.SESSION_STATE_INIT) - reason_code: int = field(kw_only=True, default=0) +class SessionUpdateDtAnchorRangingRoundsCmd(SessionConfigPacket): + def __post_init__(self): - self.opcode = 2 + self.mt = MessageType.COMMAND + self.oid = SessionConfigOpcodeId.UPDATE_DT_ANCHOR_RANGING_ROUNDS self.gid = GroupId.SESSION_CONFIG - self.mt = MessageType.NOTIFICATION @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionStatusNtf', bytes]: - if fields['opcode'] != 0x2 or fields['gid'] != GroupId.SESSION_CONFIG or fields['mt'] != MessageType.NOTIFICATION: + def parse(fields: dict, span: bytes) -> Tuple['SessionUpdateDtAnchorRangingRoundsCmd', bytes]: + if fields['mt'] != MessageType.COMMAND or fields['oid'] != SessionConfigOpcodeId.UPDATE_DT_ANCHOR_RANGING_ROUNDS or fields['gid'] != GroupId.SESSION_CONFIG: raise Exception("Invalid constraint field values") - if len(span) < 6: - raise Exception('Invalid packet size') - value_ = int.from_bytes(span[0:4], byteorder='little') - fields['session_token'] = value_ - fields['session_state'] = SessionState.from_int(span[4]) - fields['reason_code'] = span[5] - span = span[6:] - return SessionStatusNtf(**fields), span + return SessionUpdateDtAnchorRangingRoundsCmd(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - if self.session_token > 4294967295: - print(f"Invalid value for field SessionStatusNtf::session_token: {self.session_token} > 4294967295; the value will be truncated") - self.session_token &= 4294967295 - _span.extend(int.to_bytes((self.session_token << 0), length=4, byteorder='little')) - _span.append((self.session_state << 0)) - if self.reason_code > 255: - print(f"Invalid value for field SessionStatusNtf::reason_code: {self.reason_code} > 255; the value will be truncated") - self.reason_code &= 255 - _span.append((self.reason_code << 0)) - return SessionConfigNotification.serialize(self, payload = bytes(_span)) + return SessionConfigPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return 6 + return 0 @dataclass -class AppConfigTlv(Packet): - cfg_id: AppConfigTlvType = field(kw_only=True, default=AppConfigTlvType.DEVICE_TYPE) - v: bytearray = field(kw_only=True, default_factory=bytearray) +class SessionUpdateDtAnchorRangingRoundsRsp(SessionConfigPacket): + def __post_init__(self): - pass + self.mt = MessageType.RESPONSE + self.oid = SessionConfigOpcodeId.UPDATE_DT_ANCHOR_RANGING_ROUNDS + self.gid = GroupId.SESSION_CONFIG @staticmethod - def parse(span: bytes) -> Tuple['AppConfigTlv', bytes]: - fields = {'payload': None} - if len(span) < 2: - raise Exception('Invalid packet size') - fields['cfg_id'] = AppConfigTlvType.from_int(span[0]) - v_count = span[1] - span = span[2:] - if len(span) < v_count: - raise Exception('Invalid packet size') - fields['v'] = list(span[:v_count]) - span = span[v_count:] - return AppConfigTlv(**fields), span + def parse(fields: dict, span: bytes) -> Tuple['SessionUpdateDtAnchorRangingRoundsRsp', bytes]: + if fields['mt'] != MessageType.RESPONSE or fields['oid'] != SessionConfigOpcodeId.UPDATE_DT_ANCHOR_RANGING_ROUNDS or fields['gid'] != GroupId.SESSION_CONFIG: + raise Exception("Invalid constraint field values") + return SessionUpdateDtAnchorRangingRoundsRsp(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.append((self.cfg_id << 0)) - if len(self.v) > 255: - print(f"Invalid length for field AppConfigTlv::v: {len(self.v)} > 255; the array will be truncated") - del self.v[255:] - _span.append((len(self.v) << 0)) - _span.extend(self.v) - return bytes(_span) + return SessionConfigPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.v) * 1 + 2 + return 0 @dataclass -class SessionSetAppConfigCmd(SessionConfigCommand): +class SessionUpdateDtTagRangingRoundsCmd(SessionConfigPacket): session_token: int = field(kw_only=True, default=0) - tlvs: List[AppConfigTlv] = field(kw_only=True, default_factory=list) + ranging_round_indexes: bytearray = field(kw_only=True, default_factory=bytearray) def __post_init__(self): - self.opcode = 3 - self.gid = GroupId.SESSION_CONFIG self.mt = MessageType.COMMAND + self.oid = SessionConfigOpcodeId.UPDATE_DT_TAG_RANGING_ROUNDS + self.gid = GroupId.SESSION_CONFIG @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionSetAppConfigCmd', bytes]: - if fields['opcode'] != 0x3 or fields['gid'] != GroupId.SESSION_CONFIG or fields['mt'] != MessageType.COMMAND: + def parse(fields: dict, span: bytes) -> Tuple['SessionUpdateDtTagRangingRoundsCmd', bytes]: + if fields['mt'] != MessageType.COMMAND or fields['oid'] != SessionConfigOpcodeId.UPDATE_DT_TAG_RANGING_ROUNDS or fields['gid'] != GroupId.SESSION_CONFIG: raise Exception("Invalid constraint field values") if len(span) < 5: raise Exception('Invalid packet size') value_ = int.from_bytes(span[0:4], byteorder='little') fields['session_token'] = value_ - tlvs_count = span[4] + ranging_round_indexes_count = span[4] span = span[5:] - tlvs = [] - for n in range(tlvs_count): - element, span = AppConfigTlv.parse(span) - tlvs.append(element) - fields['tlvs'] = tlvs - return SessionSetAppConfigCmd(**fields), span + if len(span) < ranging_round_indexes_count: + raise Exception('Invalid packet size') + fields['ranging_round_indexes'] = list(span[:ranging_round_indexes_count]) + span = span[ranging_round_indexes_count:] + return SessionUpdateDtTagRangingRoundsCmd(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() if self.session_token > 4294967295: - print(f"Invalid value for field SessionSetAppConfigCmd::session_token: {self.session_token} > 4294967295; the value will be truncated") + print(f"Invalid value for field SessionUpdateDtTagRangingRoundsCmd::session_token: {self.session_token} > 4294967295; the value will be truncated") self.session_token &= 4294967295 _span.extend(int.to_bytes((self.session_token << 0), length=4, byteorder='little')) - if len(self.tlvs) > 255: - print(f"Invalid length for field SessionSetAppConfigCmd::tlvs: {len(self.tlvs)} > 255; the array will be truncated") - del self.tlvs[255:] - _span.append((len(self.tlvs) << 0)) - for _elt in self.tlvs: - _span.extend(_elt.serialize()) - return SessionConfigCommand.serialize(self, payload = bytes(_span)) + if len(self.ranging_round_indexes) > 255: + print(f"Invalid length for field SessionUpdateDtTagRangingRoundsCmd::ranging_round_indexes: {len(self.ranging_round_indexes)} > 255; the array will be truncated") + del self.ranging_round_indexes[255:] + _span.append((len(self.ranging_round_indexes) << 0)) + _span.extend(self.ranging_round_indexes) + return SessionConfigPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return sum([elt.size for elt in self.tlvs]) + 5 + return len(self.ranging_round_indexes) * 1 + 5 @dataclass -class AppConfigStatus(Packet): - cfg_id: AppConfigTlvType = field(kw_only=True, default=AppConfigTlvType.DEVICE_TYPE) - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) +class SessionUpdateDtTagRangingRoundsRsp(SessionConfigPacket): + status: Status = field(kw_only=True, default=Status.OK) + ranging_round_indexes: bytearray = field(kw_only=True, default_factory=bytearray) def __post_init__(self): - pass + self.mt = MessageType.RESPONSE + self.oid = SessionConfigOpcodeId.UPDATE_DT_TAG_RANGING_ROUNDS + self.gid = GroupId.SESSION_CONFIG @staticmethod - def parse(span: bytes) -> Tuple['AppConfigStatus', bytes]: - fields = {'payload': None} + def parse(fields: dict, span: bytes) -> Tuple['SessionUpdateDtTagRangingRoundsRsp', bytes]: + if fields['mt'] != MessageType.RESPONSE or fields['oid'] != SessionConfigOpcodeId.UPDATE_DT_TAG_RANGING_ROUNDS or fields['gid'] != GroupId.SESSION_CONFIG: + raise Exception("Invalid constraint field values") if len(span) < 2: raise Exception('Invalid packet size') - fields['cfg_id'] = AppConfigTlvType.from_int(span[0]) - fields['status'] = StatusCode.from_int(span[1]) + fields['status'] = Status.from_int(span[0]) + ranging_round_indexes_count = span[1] span = span[2:] - return AppConfigStatus(**fields), span + if len(span) < ranging_round_indexes_count: + raise Exception('Invalid packet size') + fields['ranging_round_indexes'] = list(span[:ranging_round_indexes_count]) + span = span[ranging_round_indexes_count:] + return SessionUpdateDtTagRangingRoundsRsp(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.append((self.cfg_id << 0)) _span.append((self.status << 0)) - return bytes(_span) + if len(self.ranging_round_indexes) > 255: + print(f"Invalid length for field SessionUpdateDtTagRangingRoundsRsp::ranging_round_indexes: {len(self.ranging_round_indexes)} > 255; the array will be truncated") + del self.ranging_round_indexes[255:] + _span.append((len(self.ranging_round_indexes) << 0)) + _span.extend(self.ranging_round_indexes) + return SessionConfigPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return 2 + return len(self.ranging_round_indexes) * 1 + 2 @dataclass -class SessionSetAppConfigRsp(SessionConfigResponse): - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) - cfg_status: List[AppConfigStatus] = field(kw_only=True, default_factory=list) +class Controlee(Packet): + short_address: bytearray = field(kw_only=True, default_factory=bytearray) + subsession_id: int = field(kw_only=True, default=0) def __post_init__(self): - self.opcode = 3 - self.gid = GroupId.SESSION_CONFIG - self.mt = MessageType.RESPONSE + pass @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionSetAppConfigRsp', bytes]: - if fields['opcode'] != 0x3 or fields['gid'] != GroupId.SESSION_CONFIG or fields['mt'] != MessageType.RESPONSE: - raise Exception("Invalid constraint field values") + def parse(span: bytes) -> Tuple['Controlee', bytes]: + fields = {'payload': None} if len(span) < 2: raise Exception('Invalid packet size') - fields['status'] = StatusCode.from_int(span[0]) - cfg_status_count = span[1] + fields['short_address'] = list(span[:2]) span = span[2:] - if len(span) < cfg_status_count * 2: + if len(span) < 4: raise Exception('Invalid packet size') - cfg_status = [] - for n in range(cfg_status_count): - cfg_status.append(AppConfigStatus.parse_all(span[n * 2:(n + 1) * 2])) - fields['cfg_status'] = cfg_status - span = span[cfg_status_count * 2:] - return SessionSetAppConfigRsp(**fields), span + value_ = int.from_bytes(span[0:4], byteorder='little') + fields['subsession_id'] = value_ + span = span[4:] + return Controlee(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.append((self.status << 0)) - if len(self.cfg_status) > 255: - print(f"Invalid length for field SessionSetAppConfigRsp::cfg_status: {len(self.cfg_status)} > 255; the array will be truncated") - del self.cfg_status[255:] - _span.append((len(self.cfg_status) << 0)) - for _elt in self.cfg_status: - _span.extend(_elt.serialize()) - return SessionConfigResponse.serialize(self, payload = bytes(_span)) + _span.extend(self.short_address) + if self.subsession_id > 4294967295: + print(f"Invalid value for field Controlee::subsession_id: {self.subsession_id} > 4294967295; the value will be truncated") + self.subsession_id &= 4294967295 + _span.extend(int.to_bytes((self.subsession_id << 0), length=4, byteorder='little')) + return bytes(_span) @property def size(self) -> int: - return sum([elt.size for elt in self.cfg_status]) + 2 + return 6 @dataclass -class SessionGetAppConfigCmd(SessionConfigCommand): - session_token: int = field(kw_only=True, default=0) - app_cfg: List[AppConfigTlvType] = field(kw_only=True, default_factory=list) +class Controlee_V2_0_16_Byte_Version(Packet): + short_address: bytearray = field(kw_only=True, default_factory=bytearray) + subsession_id: int = field(kw_only=True, default=0) + subsession_key: bytearray = field(kw_only=True, default_factory=bytearray) def __post_init__(self): - self.opcode = 4 - self.gid = GroupId.SESSION_CONFIG - self.mt = MessageType.COMMAND + pass @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionGetAppConfigCmd', bytes]: - if fields['opcode'] != 0x4 or fields['gid'] != GroupId.SESSION_CONFIG or fields['mt'] != MessageType.COMMAND: - raise Exception("Invalid constraint field values") - if len(span) < 5: + def parse(span: bytes) -> Tuple['Controlee_V2_0_16_Byte_Version', bytes]: + fields = {'payload': None} + if len(span) < 2: + raise Exception('Invalid packet size') + fields['short_address'] = list(span[:2]) + span = span[2:] + if len(span) < 4: raise Exception('Invalid packet size') value_ = int.from_bytes(span[0:4], byteorder='little') - fields['session_token'] = value_ - app_cfg_count = span[4] - span = span[5:] - if len(span) < app_cfg_count: + fields['subsession_id'] = value_ + span = span[4:] + if len(span) < 16: raise Exception('Invalid packet size') - app_cfg = [] - for n in range(app_cfg_count): - app_cfg.append(AppConfigTlvType(int.from_bytes(span[n:n + 1], byteorder='little'))) - fields['app_cfg'] = app_cfg - span = span[app_cfg_count:] - return SessionGetAppConfigCmd(**fields), span + fields['subsession_key'] = list(span[:16]) + span = span[16:] + return Controlee_V2_0_16_Byte_Version(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - if self.session_token > 4294967295: - print(f"Invalid value for field SessionGetAppConfigCmd::session_token: {self.session_token} > 4294967295; the value will be truncated") - self.session_token &= 4294967295 - _span.extend(int.to_bytes((self.session_token << 0), length=4, byteorder='little')) - if len(self.app_cfg) > 255: - print(f"Invalid length for field SessionGetAppConfigCmd::app_cfg: {len(self.app_cfg)} > 255; the array will be truncated") - del self.app_cfg[255:] - _span.append((len(self.app_cfg) << 0)) - for _elt in self.app_cfg: - _span.append(_elt) - return SessionConfigCommand.serialize(self, payload = bytes(_span)) + _span.extend(self.short_address) + if self.subsession_id > 4294967295: + print(f"Invalid value for field Controlee_V2_0_16_Byte_Version::subsession_id: {self.subsession_id} > 4294967295; the value will be truncated") + self.subsession_id &= 4294967295 + _span.extend(int.to_bytes((self.subsession_id << 0), length=4, byteorder='little')) + _span.extend(self.subsession_key) + return bytes(_span) @property def size(self) -> int: - return len(self.app_cfg) * 8 + 5 + return 22 @dataclass -class SessionGetAppConfigRsp(SessionConfigResponse): - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) - tlvs: List[AppConfigTlv] = field(kw_only=True, default_factory=list) +class Controlee_V2_0_32_Byte_Version(Packet): + short_address: bytearray = field(kw_only=True, default_factory=bytearray) + subsession_id: int = field(kw_only=True, default=0) + subsession_key: bytearray = field(kw_only=True, default_factory=bytearray) def __post_init__(self): - self.opcode = 4 - self.gid = GroupId.SESSION_CONFIG - self.mt = MessageType.RESPONSE + pass @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionGetAppConfigRsp', bytes]: - if fields['opcode'] != 0x4 or fields['gid'] != GroupId.SESSION_CONFIG or fields['mt'] != MessageType.RESPONSE: - raise Exception("Invalid constraint field values") + def parse(span: bytes) -> Tuple['Controlee_V2_0_32_Byte_Version', bytes]: + fields = {'payload': None} if len(span) < 2: raise Exception('Invalid packet size') - fields['status'] = StatusCode.from_int(span[0]) - tlvs_count = span[1] + fields['short_address'] = list(span[:2]) span = span[2:] - tlvs = [] - for n in range(tlvs_count): - element, span = AppConfigTlv.parse(span) - tlvs.append(element) - fields['tlvs'] = tlvs - return SessionGetAppConfigRsp(**fields), span + if len(span) < 4: + raise Exception('Invalid packet size') + value_ = int.from_bytes(span[0:4], byteorder='little') + fields['subsession_id'] = value_ + span = span[4:] + if len(span) < 32: + raise Exception('Invalid packet size') + fields['subsession_key'] = list(span[:32]) + span = span[32:] + return Controlee_V2_0_32_Byte_Version(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.append((self.status << 0)) - if len(self.tlvs) > 255: - print(f"Invalid length for field SessionGetAppConfigRsp::tlvs: {len(self.tlvs)} > 255; the array will be truncated") - del self.tlvs[255:] - _span.append((len(self.tlvs) << 0)) - for _elt in self.tlvs: - _span.extend(_elt.serialize()) - return SessionConfigResponse.serialize(self, payload = bytes(_span)) + _span.extend(self.short_address) + if self.subsession_id > 4294967295: + print(f"Invalid value for field Controlee_V2_0_32_Byte_Version::subsession_id: {self.subsession_id} > 4294967295; the value will be truncated") + self.subsession_id &= 4294967295 + _span.extend(int.to_bytes((self.subsession_id << 0), length=4, byteorder='little')) + _span.extend(self.subsession_key) + return bytes(_span) @property def size(self) -> int: - return sum([elt.size for elt in self.tlvs]) + 2 + return 38 -@dataclass -class SessionGetCountCmd(SessionConfigCommand): - - - def __post_init__(self): - self.opcode = 5 - self.gid = GroupId.SESSION_CONFIG - self.mt = MessageType.COMMAND +class UpdateMulticastListAction(enum.IntEnum): + ADD_CONTROLEE = 0x0 + REMOVE_CONTROLEE = 0x1 + ADD_CONTROLEE_WITH_SHORT_SUB_SESSION_KEY = 0x2 + ADD_CONTROLEE_WITH_EXTENDED_SUB_SESSION_KEY = 0x3 @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionGetCountCmd', bytes]: - if fields['opcode'] != 0x5 or fields['gid'] != GroupId.SESSION_CONFIG or fields['mt'] != MessageType.COMMAND: - raise Exception("Invalid constraint field values") - return SessionGetCountCmd(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - return SessionConfigCommand.serialize(self, payload = bytes(_span)) + def from_int(v: int) -> Union[int, 'UpdateMulticastListAction']: + try: + return UpdateMulticastListAction(v) + except ValueError as exn: + raise exn - @property - def size(self) -> int: - return 0 @dataclass -class SessionGetCountRsp(SessionConfigResponse): - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) - session_count: int = field(kw_only=True, default=0) +class SessionUpdateControllerMulticastListCmd(SessionConfigPacket): + session_token: int = field(kw_only=True, default=0) + action: UpdateMulticastListAction = field(kw_only=True, default=UpdateMulticastListAction.ADD_CONTROLEE) def __post_init__(self): - self.opcode = 5 + self.mt = MessageType.COMMAND + self.oid = SessionConfigOpcodeId.UPDATE_CONTROLLER_MULTICAST_LIST self.gid = GroupId.SESSION_CONFIG - self.mt = MessageType.RESPONSE @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionGetCountRsp', bytes]: - if fields['opcode'] != 0x5 or fields['gid'] != GroupId.SESSION_CONFIG or fields['mt'] != MessageType.RESPONSE: + def parse(fields: dict, span: bytes) -> Tuple['SessionUpdateControllerMulticastListCmd', bytes]: + if fields['mt'] != MessageType.COMMAND or fields['oid'] != SessionConfigOpcodeId.UPDATE_CONTROLLER_MULTICAST_LIST or fields['gid'] != GroupId.SESSION_CONFIG: raise Exception("Invalid constraint field values") - if len(span) < 2: + if len(span) < 5: raise Exception('Invalid packet size') - fields['status'] = StatusCode.from_int(span[0]) - fields['session_count'] = span[1] - span = span[2:] - return SessionGetCountRsp(**fields), span + value_ = int.from_bytes(span[0:4], byteorder='little') + fields['session_token'] = value_ + fields['action'] = UpdateMulticastListAction.from_int(span[4]) + span = span[5:] + payload = span + span = bytes([]) + fields['payload'] = payload + return SessionUpdateControllerMulticastListCmd(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.append((self.status << 0)) - if self.session_count > 255: - print(f"Invalid value for field SessionGetCountRsp::session_count: {self.session_count} > 255; the value will be truncated") - self.session_count &= 255 - _span.append((self.session_count << 0)) - return SessionConfigResponse.serialize(self, payload = bytes(_span)) + if self.session_token > 4294967295: + print(f"Invalid value for field SessionUpdateControllerMulticastListCmd::session_token: {self.session_token} > 4294967295; the value will be truncated") + self.session_token &= 4294967295 + _span.extend(int.to_bytes((self.session_token << 0), length=4, byteorder='little')) + _span.append((self.action << 0)) + _span.extend(payload or self.payload or []) + return SessionConfigPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return 2 + return len(self.payload) + 5 @dataclass -class SessionGetStateCmd(SessionConfigCommand): - session_token: int = field(kw_only=True, default=0) +class SessionUpdateControllerMulticastListCmdPayload(Packet): + controlees: List[Controlee] = field(kw_only=True, default_factory=list) def __post_init__(self): - self.opcode = 6 - self.gid = GroupId.SESSION_CONFIG - self.mt = MessageType.COMMAND + pass @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionGetStateCmd', bytes]: - if fields['opcode'] != 0x6 or fields['gid'] != GroupId.SESSION_CONFIG or fields['mt'] != MessageType.COMMAND: - raise Exception("Invalid constraint field values") - if len(span) < 4: + def parse(span: bytes) -> Tuple['SessionUpdateControllerMulticastListCmdPayload', bytes]: + fields = {'payload': None} + if len(span) < 1: raise Exception('Invalid packet size') - value_ = int.from_bytes(span[0:4], byteorder='little') - fields['session_token'] = value_ - span = span[4:] - return SessionGetStateCmd(**fields), span + controlees_count = span[0] + span = span[1:] + if len(span) < controlees_count * 6: + raise Exception('Invalid packet size') + controlees = [] + for n in range(controlees_count): + controlees.append(Controlee.parse_all(span[n * 6:(n + 1) * 6])) + fields['controlees'] = controlees + span = span[controlees_count * 6:] + return SessionUpdateControllerMulticastListCmdPayload(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - if self.session_token > 4294967295: - print(f"Invalid value for field SessionGetStateCmd::session_token: {self.session_token} > 4294967295; the value will be truncated") - self.session_token &= 4294967295 - _span.extend(int.to_bytes((self.session_token << 0), length=4, byteorder='little')) - return SessionConfigCommand.serialize(self, payload = bytes(_span)) + if len(self.controlees) > 255: + print(f"Invalid length for field SessionUpdateControllerMulticastListCmdPayload::controlees: {len(self.controlees)} > 255; the array will be truncated") + del self.controlees[255:] + _span.append((len(self.controlees) << 0)) + for _elt in self.controlees: + _span.extend(_elt.serialize()) + return bytes(_span) @property def size(self) -> int: - return 4 + return sum([elt.size for elt in self.controlees]) + 1 @dataclass -class SessionGetStateRsp(SessionConfigResponse): - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) - session_state: SessionState = field(kw_only=True, default=SessionState.SESSION_STATE_INIT) +class SessionUpdateControllerMulticastListCmd_2_0_16_Byte_Payload(Packet): + controlees: List[Controlee_V2_0_16_Byte_Version] = field(kw_only=True, default_factory=list) def __post_init__(self): - self.opcode = 6 - self.gid = GroupId.SESSION_CONFIG - self.mt = MessageType.RESPONSE + pass @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionGetStateRsp', bytes]: - if fields['opcode'] != 0x6 or fields['gid'] != GroupId.SESSION_CONFIG or fields['mt'] != MessageType.RESPONSE: - raise Exception("Invalid constraint field values") - if len(span) < 2: + def parse(span: bytes) -> Tuple['SessionUpdateControllerMulticastListCmd_2_0_16_Byte_Payload', bytes]: + fields = {'payload': None} + if len(span) < 1: raise Exception('Invalid packet size') - fields['status'] = StatusCode.from_int(span[0]) - fields['session_state'] = SessionState.from_int(span[1]) - span = span[2:] - return SessionGetStateRsp(**fields), span + controlees_count = span[0] + span = span[1:] + if len(span) < controlees_count * 22: + raise Exception('Invalid packet size') + controlees = [] + for n in range(controlees_count): + controlees.append(Controlee_V2_0_16_Byte_Version.parse_all(span[n * 22:(n + 1) * 22])) + fields['controlees'] = controlees + span = span[controlees_count * 22:] + return SessionUpdateControllerMulticastListCmd_2_0_16_Byte_Payload(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.append((self.status << 0)) - _span.append((self.session_state << 0)) - return SessionConfigResponse.serialize(self, payload = bytes(_span)) + if len(self.controlees) > 255: + print(f"Invalid length for field SessionUpdateControllerMulticastListCmd_2_0_16_Byte_Payload::controlees: {len(self.controlees)} > 255; the array will be truncated") + del self.controlees[255:] + _span.append((len(self.controlees) << 0)) + for _elt in self.controlees: + _span.extend(_elt.serialize()) + return bytes(_span) @property def size(self) -> int: - return 2 + return sum([elt.size for elt in self.controlees]) + 1 @dataclass -class SessionUpdateDtTagRangingRoundsCmd(SessionConfigCommand): - session_token: int = field(kw_only=True, default=0) - ranging_round_indexes: bytearray = field(kw_only=True, default_factory=bytearray) +class SessionUpdateControllerMulticastListCmd_2_0_32_Byte_Payload(Packet): + controlees: List[Controlee_V2_0_32_Byte_Version] = field(kw_only=True, default_factory=list) def __post_init__(self): - self.opcode = 9 - self.gid = GroupId.SESSION_CONFIG - self.mt = MessageType.COMMAND + pass @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionUpdateDtTagRangingRoundsCmd', bytes]: - if fields['opcode'] != 0x9 or fields['gid'] != GroupId.SESSION_CONFIG or fields['mt'] != MessageType.COMMAND: - raise Exception("Invalid constraint field values") - if len(span) < 5: + def parse(span: bytes) -> Tuple['SessionUpdateControllerMulticastListCmd_2_0_32_Byte_Payload', bytes]: + fields = {'payload': None} + if len(span) < 1: raise Exception('Invalid packet size') - value_ = int.from_bytes(span[0:4], byteorder='little') - fields['session_token'] = value_ - ranging_round_indexes_count = span[4] - span = span[5:] - if len(span) < ranging_round_indexes_count: + controlees_count = span[0] + span = span[1:] + if len(span) < controlees_count * 38: raise Exception('Invalid packet size') - fields['ranging_round_indexes'] = list(span[:ranging_round_indexes_count]) - span = span[ranging_round_indexes_count:] - return SessionUpdateDtTagRangingRoundsCmd(**fields), span + controlees = [] + for n in range(controlees_count): + controlees.append(Controlee_V2_0_32_Byte_Version.parse_all(span[n * 38:(n + 1) * 38])) + fields['controlees'] = controlees + span = span[controlees_count * 38:] + return SessionUpdateControllerMulticastListCmd_2_0_32_Byte_Payload(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - if self.session_token > 4294967295: - print(f"Invalid value for field SessionUpdateDtTagRangingRoundsCmd::session_token: {self.session_token} > 4294967295; the value will be truncated") - self.session_token &= 4294967295 - _span.extend(int.to_bytes((self.session_token << 0), length=4, byteorder='little')) - if len(self.ranging_round_indexes) > 255: - print(f"Invalid length for field SessionUpdateDtTagRangingRoundsCmd::ranging_round_indexes: {len(self.ranging_round_indexes)} > 255; the array will be truncated") - del self.ranging_round_indexes[255:] - _span.append((len(self.ranging_round_indexes) << 0)) - _span.extend(self.ranging_round_indexes) - return SessionConfigCommand.serialize(self, payload = bytes(_span)) + if len(self.controlees) > 255: + print(f"Invalid length for field SessionUpdateControllerMulticastListCmd_2_0_32_Byte_Payload::controlees: {len(self.controlees)} > 255; the array will be truncated") + del self.controlees[255:] + _span.append((len(self.controlees) << 0)) + for _elt in self.controlees: + _span.extend(_elt.serialize()) + return bytes(_span) @property def size(self) -> int: - return len(self.ranging_round_indexes) * 1 + 5 + return sum([elt.size for elt in self.controlees]) + 1 @dataclass -class SessionUpdateDtTagRangingRoundsRsp(SessionConfigResponse): - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) - ranging_round_indexes: bytearray = field(kw_only=True, default_factory=bytearray) +class SessionUpdateControllerMulticastListRsp(SessionConfigPacket): + status: Status = field(kw_only=True, default=Status.OK) def __post_init__(self): - self.opcode = 9 - self.gid = GroupId.SESSION_CONFIG self.mt = MessageType.RESPONSE + self.oid = SessionConfigOpcodeId.UPDATE_CONTROLLER_MULTICAST_LIST + self.gid = GroupId.SESSION_CONFIG @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionUpdateDtTagRangingRoundsRsp', bytes]: - if fields['opcode'] != 0x9 or fields['gid'] != GroupId.SESSION_CONFIG or fields['mt'] != MessageType.RESPONSE: + def parse(fields: dict, span: bytes) -> Tuple['SessionUpdateControllerMulticastListRsp', bytes]: + if fields['mt'] != MessageType.RESPONSE or fields['oid'] != SessionConfigOpcodeId.UPDATE_CONTROLLER_MULTICAST_LIST or fields['gid'] != GroupId.SESSION_CONFIG: raise Exception("Invalid constraint field values") - if len(span) < 2: - raise Exception('Invalid packet size') - fields['status'] = StatusCode.from_int(span[0]) - ranging_round_indexes_count = span[1] - span = span[2:] - if len(span) < ranging_round_indexes_count: + if len(span) < 1: raise Exception('Invalid packet size') - fields['ranging_round_indexes'] = list(span[:ranging_round_indexes_count]) - span = span[ranging_round_indexes_count:] - return SessionUpdateDtTagRangingRoundsRsp(**fields), span + fields['status'] = Status.from_int(span[0]) + span = span[1:] + return SessionUpdateControllerMulticastListRsp(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() _span.append((self.status << 0)) - if len(self.ranging_round_indexes) > 255: - print(f"Invalid length for field SessionUpdateDtTagRangingRoundsRsp::ranging_round_indexes: {len(self.ranging_round_indexes)} > 255; the array will be truncated") - del self.ranging_round_indexes[255:] - _span.append((len(self.ranging_round_indexes) << 0)) - _span.extend(self.ranging_round_indexes) - return SessionConfigResponse.serialize(self, payload = bytes(_span)) + return SessionConfigPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.ranging_round_indexes) * 1 + 2 + return 1 @dataclass -class Controlee(Packet): - short_address: bytearray = field(kw_only=True, default_factory=bytearray) +class ControleeStatus(Packet): + mac_address: bytearray = field(kw_only=True, default_factory=bytearray) subsession_id: int = field(kw_only=True, default=0) + status: MulticastUpdateStatus = field(kw_only=True, default=MulticastUpdateStatus.OK_MULTICAST_LIST_UPDATE) def __post_init__(self): pass @staticmethod - def parse(span: bytes) -> Tuple['Controlee', bytes]: + def parse(span: bytes) -> Tuple['ControleeStatus', bytes]: fields = {'payload': None} if len(span) < 2: raise Exception('Invalid packet size') - fields['short_address'] = list(span[:2]) + fields['mac_address'] = list(span[:2]) span = span[2:] - if len(span) < 4: + if len(span) < 5: raise Exception('Invalid packet size') value_ = int.from_bytes(span[0:4], byteorder='little') fields['subsession_id'] = value_ - span = span[4:] - return Controlee(**fields), span + fields['status'] = MulticastUpdateStatus.from_int(span[4]) + span = span[5:] + return ControleeStatus(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(self.short_address) + _span.extend(self.mac_address) if self.subsession_id > 4294967295: - print(f"Invalid value for field Controlee::subsession_id: {self.subsession_id} > 4294967295; the value will be truncated") + print(f"Invalid value for field ControleeStatus::subsession_id: {self.subsession_id} > 4294967295; the value will be truncated") self.subsession_id &= 4294967295 _span.extend(int.to_bytes((self.subsession_id << 0), length=4, byteorder='little')) + _span.append((self.status << 0)) return bytes(_span) @property def size(self) -> int: - return 6 + return 7 @dataclass -class Controlee_V2_0_16_Byte_Version(Packet): - short_address: bytearray = field(kw_only=True, default_factory=bytearray) - subsession_id: int = field(kw_only=True, default=0) - subsession_key: bytearray = field(kw_only=True, default_factory=bytearray) +class SessionUpdateControllerMulticastListNtf(SessionConfigPacket): + session_token: int = field(kw_only=True, default=0) + remaining_multicast_list_size: int = field(kw_only=True, default=0) + controlee_status: List[ControleeStatus] = field(kw_only=True, default_factory=list) def __post_init__(self): - pass + self.mt = MessageType.NOTIFICATION + self.oid = SessionConfigOpcodeId.UPDATE_CONTROLLER_MULTICAST_LIST + self.gid = GroupId.SESSION_CONFIG @staticmethod - def parse(span: bytes) -> Tuple['Controlee_V2_0_16_Byte_Version', bytes]: - fields = {'payload': None} - if len(span) < 2: - raise Exception('Invalid packet size') - fields['short_address'] = list(span[:2]) - span = span[2:] - if len(span) < 4: + def parse(fields: dict, span: bytes) -> Tuple['SessionUpdateControllerMulticastListNtf', bytes]: + if fields['mt'] != MessageType.NOTIFICATION or fields['oid'] != SessionConfigOpcodeId.UPDATE_CONTROLLER_MULTICAST_LIST or fields['gid'] != GroupId.SESSION_CONFIG: + raise Exception("Invalid constraint field values") + if len(span) < 6: raise Exception('Invalid packet size') value_ = int.from_bytes(span[0:4], byteorder='little') - fields['subsession_id'] = value_ - span = span[4:] - if len(span) < 16: + fields['session_token'] = value_ + fields['remaining_multicast_list_size'] = span[4] + controlee_status_count = span[5] + span = span[6:] + if len(span) < controlee_status_count * 7: raise Exception('Invalid packet size') - fields['subsession_key'] = list(span[:16]) - span = span[16:] - return Controlee_V2_0_16_Byte_Version(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - _span.extend(self.short_address) - if self.subsession_id > 4294967295: - print(f"Invalid value for field Controlee_V2_0_16_Byte_Version::subsession_id: {self.subsession_id} > 4294967295; the value will be truncated") - self.subsession_id &= 4294967295 - _span.extend(int.to_bytes((self.subsession_id << 0), length=4, byteorder='little')) - _span.extend(self.subsession_key) - return bytes(_span) - - @property - def size(self) -> int: - return 22 - -@dataclass -class Controlee_V2_0_32_Byte_Version(Packet): - short_address: bytearray = field(kw_only=True, default_factory=bytearray) - subsession_id: int = field(kw_only=True, default=0) - subsession_key: bytearray = field(kw_only=True, default_factory=bytearray) - - def __post_init__(self): - pass - - @staticmethod - def parse(span: bytes) -> Tuple['Controlee_V2_0_32_Byte_Version', bytes]: - fields = {'payload': None} - if len(span) < 2: - raise Exception('Invalid packet size') - fields['short_address'] = list(span[:2]) - span = span[2:] - if len(span) < 4: - raise Exception('Invalid packet size') - value_ = int.from_bytes(span[0:4], byteorder='little') - fields['subsession_id'] = value_ - span = span[4:] - if len(span) < 32: - raise Exception('Invalid packet size') - fields['subsession_key'] = list(span[:32]) - span = span[32:] - return Controlee_V2_0_32_Byte_Version(**fields), span + controlee_status = [] + for n in range(controlee_status_count): + controlee_status.append(ControleeStatus.parse_all(span[n * 7:(n + 1) * 7])) + fields['controlee_status'] = controlee_status + span = span[controlee_status_count * 7:] + return SessionUpdateControllerMulticastListNtf(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(self.short_address) - if self.subsession_id > 4294967295: - print(f"Invalid value for field Controlee_V2_0_32_Byte_Version::subsession_id: {self.subsession_id} > 4294967295; the value will be truncated") - self.subsession_id &= 4294967295 - _span.extend(int.to_bytes((self.subsession_id << 0), length=4, byteorder='little')) - _span.extend(self.subsession_key) - return bytes(_span) + if self.session_token > 4294967295: + print(f"Invalid value for field SessionUpdateControllerMulticastListNtf::session_token: {self.session_token} > 4294967295; the value will be truncated") + self.session_token &= 4294967295 + _span.extend(int.to_bytes((self.session_token << 0), length=4, byteorder='little')) + if self.remaining_multicast_list_size > 255: + print(f"Invalid value for field SessionUpdateControllerMulticastListNtf::remaining_multicast_list_size: {self.remaining_multicast_list_size} > 255; the value will be truncated") + self.remaining_multicast_list_size &= 255 + _span.append((self.remaining_multicast_list_size << 0)) + if len(self.controlee_status) > 255: + print(f"Invalid length for field SessionUpdateControllerMulticastListNtf::controlee_status: {len(self.controlee_status)} > 255; the array will be truncated") + del self.controlee_status[255:] + _span.append((len(self.controlee_status) << 0)) + for _elt in self.controlee_status: + _span.extend(_elt.serialize()) + return SessionConfigPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return 38 - -class UpdateMulticastListAction(enum.IntEnum): - ADD_CONTROLEE = 0x0 - REMOVE_CONTROLEE = 0x1 - ADD_CONTROLEE_WITH_SHORT_SUB_SESSION_KEY = 0x2 - ADD_CONTROLEE_WITH_EXTENDED_SUB_SESSION_KEY = 0x3 - - @staticmethod - def from_int(v: int) -> Union[int, 'UpdateMulticastListAction']: - try: - return UpdateMulticastListAction(v) - except ValueError as exn: - raise exn - + return sum([elt.size for elt in self.controlee_status]) + 6 @dataclass -class SessionUpdateControllerMulticastListCmd(SessionConfigCommand): +class SessionDataCreditNtf(SessionControlPacket): session_token: int = field(kw_only=True, default=0) - action: UpdateMulticastListAction = field(kw_only=True, default=UpdateMulticastListAction.ADD_CONTROLEE) + credit_availability: CreditAvailability = field(kw_only=True, default=CreditAvailability.CREDIT_NOT_AVAILABLE) def __post_init__(self): - self.opcode = 7 - self.gid = GroupId.SESSION_CONFIG - self.mt = MessageType.COMMAND + self.mt = MessageType.NOTIFICATION + self.oid = SessionControlOpcodeId.DATA_CREDIT + self.gid = GroupId.SESSION_CONTROL @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionUpdateControllerMulticastListCmd', bytes]: - if fields['opcode'] != 0x7 or fields['gid'] != GroupId.SESSION_CONFIG or fields['mt'] != MessageType.COMMAND: + def parse(fields: dict, span: bytes) -> Tuple['SessionDataCreditNtf', bytes]: + if fields['mt'] != MessageType.NOTIFICATION or fields['oid'] != SessionControlOpcodeId.DATA_CREDIT or fields['gid'] != GroupId.SESSION_CONTROL: raise Exception("Invalid constraint field values") if len(span) < 5: raise Exception('Invalid packet size') value_ = int.from_bytes(span[0:4], byteorder='little') fields['session_token'] = value_ - fields['action'] = UpdateMulticastListAction.from_int(span[4]) + fields['credit_availability'] = CreditAvailability.from_int(span[4]) span = span[5:] - payload = span - span = bytes([]) - fields['payload'] = payload - return SessionUpdateControllerMulticastListCmd(**fields), span + return SessionDataCreditNtf(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() if self.session_token > 4294967295: - print(f"Invalid value for field SessionUpdateControllerMulticastListCmd::session_token: {self.session_token} > 4294967295; the value will be truncated") + print(f"Invalid value for field SessionDataCreditNtf::session_token: {self.session_token} > 4294967295; the value will be truncated") self.session_token &= 4294967295 _span.extend(int.to_bytes((self.session_token << 0), length=4, byteorder='little')) - _span.append((self.action << 0)) - _span.extend(payload or self.payload or []) - return SessionConfigCommand.serialize(self, payload = bytes(_span)) + _span.append((self.credit_availability << 0)) + return SessionControlPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.payload) + 5 + return 5 @dataclass -class PhaseList(Packet): +class SessionDataTransferStatusNtf(SessionControlPacket): session_token: int = field(kw_only=True, default=0) - start_slot_index: int = field(kw_only=True, default=0) - end_slot_index: int = field(kw_only=True, default=0) + uci_sequence_number: int = field(kw_only=True, default=0) + status: DataTransferNtfStatusCode = field(kw_only=True, default=DataTransferNtfStatusCode.UCI_DATA_TRANSFER_STATUS_REPETITION_OK) + tx_count: int = field(kw_only=True, default=0) def __post_init__(self): - pass + self.mt = MessageType.NOTIFICATION + self.oid = SessionControlOpcodeId.DATA_TRANSFER_STATUS + self.gid = GroupId.SESSION_CONTROL @staticmethod - def parse(span: bytes) -> Tuple['PhaseList', bytes]: - fields = {'payload': None} - if len(span) < 8: + def parse(fields: dict, span: bytes) -> Tuple['SessionDataTransferStatusNtf', bytes]: + if fields['mt'] != MessageType.NOTIFICATION or fields['oid'] != SessionControlOpcodeId.DATA_TRANSFER_STATUS or fields['gid'] != GroupId.SESSION_CONTROL: + raise Exception("Invalid constraint field values") + if len(span) < 7: raise Exception('Invalid packet size') value_ = int.from_bytes(span[0:4], byteorder='little') fields['session_token'] = value_ - value_ = int.from_bytes(span[4:6], byteorder='little') - fields['start_slot_index'] = value_ - value_ = int.from_bytes(span[6:8], byteorder='little') - fields['end_slot_index'] = value_ - span = span[8:] - return PhaseList(**fields), span + fields['uci_sequence_number'] = span[4] + fields['status'] = DataTransferNtfStatusCode.from_int(span[5]) + fields['tx_count'] = span[6] + span = span[7:] + return SessionDataTransferStatusNtf(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() if self.session_token > 4294967295: - print(f"Invalid value for field PhaseList::session_token: {self.session_token} > 4294967295; the value will be truncated") + print(f"Invalid value for field SessionDataTransferStatusNtf::session_token: {self.session_token} > 4294967295; the value will be truncated") self.session_token &= 4294967295 _span.extend(int.to_bytes((self.session_token << 0), length=4, byteorder='little')) - if self.start_slot_index > 65535: - print(f"Invalid value for field PhaseList::start_slot_index: {self.start_slot_index} > 65535; the value will be truncated") - self.start_slot_index &= 65535 - _span.extend(int.to_bytes((self.start_slot_index << 0), length=2, byteorder='little')) - if self.end_slot_index > 65535: - print(f"Invalid value for field PhaseList::end_slot_index: {self.end_slot_index} > 65535; the value will be truncated") - self.end_slot_index &= 65535 - _span.extend(int.to_bytes((self.end_slot_index << 0), length=2, byteorder='little')) - return bytes(_span) + if self.uci_sequence_number > 255: + print(f"Invalid value for field SessionDataTransferStatusNtf::uci_sequence_number: {self.uci_sequence_number} > 255; the value will be truncated") + self.uci_sequence_number &= 255 + _span.append((self.uci_sequence_number << 0)) + _span.append((self.status << 0)) + if self.tx_count > 255: + print(f"Invalid value for field SessionDataTransferStatusNtf::tx_count: {self.tx_count} > 255; the value will be truncated") + self.tx_count &= 255 + _span.append((self.tx_count << 0)) + return SessionControlPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return 8 + return 7 @dataclass -class SessionSetHybridConfigCmd(SessionConfigCommand): +class SessionQueryMaxDataSizeInRangingCmd(SessionConfigPacket): session_token: int = field(kw_only=True, default=0) - number_of_phases: int = field(kw_only=True, default=0) - update_time: bytearray = field(kw_only=True, default_factory=bytearray) - phase_list: List[PhaseList] = field(kw_only=True, default_factory=list) def __post_init__(self): - self.opcode = 12 - self.gid = GroupId.SESSION_CONFIG self.mt = MessageType.COMMAND + self.oid = SessionConfigOpcodeId.QUERY_DATA_SIZE_IN_RANGING + self.gid = GroupId.SESSION_CONFIG @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionSetHybridConfigCmd', bytes]: - if fields['opcode'] != 0xc or fields['gid'] != GroupId.SESSION_CONFIG or fields['mt'] != MessageType.COMMAND: + def parse(fields: dict, span: bytes) -> Tuple['SessionQueryMaxDataSizeInRangingCmd', bytes]: + if fields['mt'] != MessageType.COMMAND or fields['oid'] != SessionConfigOpcodeId.QUERY_DATA_SIZE_IN_RANGING or fields['gid'] != GroupId.SESSION_CONFIG: raise Exception("Invalid constraint field values") - if len(span) < 5: + if len(span) < 4: raise Exception('Invalid packet size') value_ = int.from_bytes(span[0:4], byteorder='little') fields['session_token'] = value_ - fields['number_of_phases'] = span[4] - span = span[5:] - if len(span) < 8: - raise Exception('Invalid packet size') - fields['update_time'] = list(span[:8]) - span = span[8:] - if len(span) % 8 != 0: - raise Exception('Array size is not a multiple of the element size') - phase_list_count = int(len(span) / 8) - phase_list = [] - for n in range(phase_list_count): - phase_list.append(PhaseList.parse_all(span[n * 8:(n + 1) * 8])) - fields['phase_list'] = phase_list - span = bytes() - return SessionSetHybridConfigCmd(**fields), span + span = span[4:] + return SessionQueryMaxDataSizeInRangingCmd(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() if self.session_token > 4294967295: - print(f"Invalid value for field SessionSetHybridConfigCmd::session_token: {self.session_token} > 4294967295; the value will be truncated") + print(f"Invalid value for field SessionQueryMaxDataSizeInRangingCmd::session_token: {self.session_token} > 4294967295; the value will be truncated") self.session_token &= 4294967295 _span.extend(int.to_bytes((self.session_token << 0), length=4, byteorder='little')) - if self.number_of_phases > 255: - print(f"Invalid value for field SessionSetHybridConfigCmd::number_of_phases: {self.number_of_phases} > 255; the value will be truncated") - self.number_of_phases &= 255 - _span.append((self.number_of_phases << 0)) - _span.extend(self.update_time) - for _elt in self.phase_list: - _span.extend(_elt.serialize()) - return SessionConfigCommand.serialize(self, payload = bytes(_span)) + return SessionConfigPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return sum([elt.size for elt in self.phase_list]) + 13 + return 4 @dataclass -class SessionSetHybridConfigRsp(SessionConfigResponse): - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) +class SessionQueryMaxDataSizeInRangingRsp(SessionConfigPacket): + session_token: int = field(kw_only=True, default=0) + max_data_size: int = field(kw_only=True, default=0) def __post_init__(self): - self.opcode = 12 - self.gid = GroupId.SESSION_CONFIG self.mt = MessageType.RESPONSE + self.oid = SessionConfigOpcodeId.QUERY_DATA_SIZE_IN_RANGING + self.gid = GroupId.SESSION_CONFIG @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionSetHybridConfigRsp', bytes]: - if fields['opcode'] != 0xc or fields['gid'] != GroupId.SESSION_CONFIG or fields['mt'] != MessageType.RESPONSE: + def parse(fields: dict, span: bytes) -> Tuple['SessionQueryMaxDataSizeInRangingRsp', bytes]: + if fields['mt'] != MessageType.RESPONSE or fields['oid'] != SessionConfigOpcodeId.QUERY_DATA_SIZE_IN_RANGING or fields['gid'] != GroupId.SESSION_CONFIG: raise Exception("Invalid constraint field values") - if len(span) < 1: + if len(span) < 6: raise Exception('Invalid packet size') - fields['status'] = StatusCode.from_int(span[0]) - span = span[1:] - return SessionSetHybridConfigRsp(**fields), span + value_ = int.from_bytes(span[0:4], byteorder='little') + fields['session_token'] = value_ + value_ = int.from_bytes(span[4:6], byteorder='little') + fields['max_data_size'] = value_ + span = span[6:] + return SessionQueryMaxDataSizeInRangingRsp(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.append((self.status << 0)) - return SessionConfigResponse.serialize(self, payload = bytes(_span)) + if self.session_token > 4294967295: + print(f"Invalid value for field SessionQueryMaxDataSizeInRangingRsp::session_token: {self.session_token} > 4294967295; the value will be truncated") + self.session_token &= 4294967295 + _span.extend(int.to_bytes((self.session_token << 0), length=4, byteorder='little')) + if self.max_data_size > 65535: + print(f"Invalid value for field SessionQueryMaxDataSizeInRangingRsp::max_data_size: {self.max_data_size} > 65535; the value will be truncated") + self.max_data_size &= 65535 + _span.extend(int.to_bytes((self.max_data_size << 0), length=2, byteorder='little')) + return SessionConfigPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return 1 + return 6 @dataclass -class SessionUpdateControllerMulticastListCmdPayload(Packet): - controlees: List[Controlee] = field(kw_only=True, default_factory=list) +class SessionStartCmd(SessionControlPacket): + session_id: int = field(kw_only=True, default=0) def __post_init__(self): - pass + self.mt = MessageType.COMMAND + self.oid = SessionControlOpcodeId.START + self.gid = GroupId.SESSION_CONTROL @staticmethod - def parse(span: bytes) -> Tuple['SessionUpdateControllerMulticastListCmdPayload', bytes]: - fields = {'payload': None} - if len(span) < 1: - raise Exception('Invalid packet size') - controlees_count = span[0] - span = span[1:] - if len(span) < controlees_count * 6: + def parse(fields: dict, span: bytes) -> Tuple['SessionStartCmd', bytes]: + if fields['mt'] != MessageType.COMMAND or fields['oid'] != SessionControlOpcodeId.START or fields['gid'] != GroupId.SESSION_CONTROL: + raise Exception("Invalid constraint field values") + if len(span) < 4: raise Exception('Invalid packet size') - controlees = [] - for n in range(controlees_count): - controlees.append(Controlee.parse_all(span[n * 6:(n + 1) * 6])) - fields['controlees'] = controlees - span = span[controlees_count * 6:] - return SessionUpdateControllerMulticastListCmdPayload(**fields), span + value_ = int.from_bytes(span[0:4], byteorder='little') + fields['session_id'] = value_ + span = span[4:] + return SessionStartCmd(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - if len(self.controlees) > 255: - print(f"Invalid length for field SessionUpdateControllerMulticastListCmdPayload::controlees: {len(self.controlees)} > 255; the array will be truncated") - del self.controlees[255:] - _span.append((len(self.controlees) << 0)) - for _elt in self.controlees: - _span.extend(_elt.serialize()) - return bytes(_span) + if self.session_id > 4294967295: + print(f"Invalid value for field SessionStartCmd::session_id: {self.session_id} > 4294967295; the value will be truncated") + self.session_id &= 4294967295 + _span.extend(int.to_bytes((self.session_id << 0), length=4, byteorder='little')) + return SessionControlPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return sum([elt.size for elt in self.controlees]) + 1 + return 4 @dataclass -class SessionUpdateControllerMulticastListCmd_2_0_16_Byte_Payload(Packet): - controlees: List[Controlee_V2_0_16_Byte_Version] = field(kw_only=True, default_factory=list) +class SessionStartRsp(SessionControlPacket): + status: Status = field(kw_only=True, default=Status.OK) def __post_init__(self): - pass + self.mt = MessageType.RESPONSE + self.oid = SessionControlOpcodeId.START + self.gid = GroupId.SESSION_CONTROL @staticmethod - def parse(span: bytes) -> Tuple['SessionUpdateControllerMulticastListCmd_2_0_16_Byte_Payload', bytes]: - fields = {'payload': None} + def parse(fields: dict, span: bytes) -> Tuple['SessionStartRsp', bytes]: + if fields['mt'] != MessageType.RESPONSE or fields['oid'] != SessionControlOpcodeId.START or fields['gid'] != GroupId.SESSION_CONTROL: + raise Exception("Invalid constraint field values") if len(span) < 1: raise Exception('Invalid packet size') - controlees_count = span[0] + fields['status'] = Status.from_int(span[0]) span = span[1:] - if len(span) < controlees_count * 22: - raise Exception('Invalid packet size') - controlees = [] - for n in range(controlees_count): - controlees.append(Controlee_V2_0_16_Byte_Version.parse_all(span[n * 22:(n + 1) * 22])) - fields['controlees'] = controlees - span = span[controlees_count * 22:] - return SessionUpdateControllerMulticastListCmd_2_0_16_Byte_Payload(**fields), span + return SessionStartRsp(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - if len(self.controlees) > 255: - print(f"Invalid length for field SessionUpdateControllerMulticastListCmd_2_0_16_Byte_Payload::controlees: {len(self.controlees)} > 255; the array will be truncated") - del self.controlees[255:] - _span.append((len(self.controlees) << 0)) - for _elt in self.controlees: - _span.extend(_elt.serialize()) - return bytes(_span) + _span.append((self.status << 0)) + return SessionControlPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return sum([elt.size for elt in self.controlees]) + 1 + return 1 @dataclass -class SessionUpdateControllerMulticastListCmd_2_0_32_Byte_Payload(Packet): - controlees: List[Controlee_V2_0_32_Byte_Version] = field(kw_only=True, default_factory=list) +class ShortAddressTwoWayRangingMeasurement(Packet): + mac_address: int = field(kw_only=True, default=0) + status: Status = field(kw_only=True, default=Status.OK) + nlos: int = field(kw_only=True, default=0) + distance: int = field(kw_only=True, default=0) + aoa_azimuth: int = field(kw_only=True, default=0) + aoa_azimuth_fom: int = field(kw_only=True, default=0) + aoa_elevation: int = field(kw_only=True, default=0) + aoa_elevation_fom: int = field(kw_only=True, default=0) + aoa_destination_azimuth: int = field(kw_only=True, default=0) + aoa_destination_azimuth_fom: int = field(kw_only=True, default=0) + aoa_destination_elevation: int = field(kw_only=True, default=0) + aoa_destination_elevation_fom: int = field(kw_only=True, default=0) + slot_index: int = field(kw_only=True, default=0) + rssi: int = field(kw_only=True, default=0) def __post_init__(self): pass @staticmethod - def parse(span: bytes) -> Tuple['SessionUpdateControllerMulticastListCmd_2_0_32_Byte_Payload', bytes]: + def parse(span: bytes) -> Tuple['ShortAddressTwoWayRangingMeasurement', bytes]: fields = {'payload': None} - if len(span) < 1: - raise Exception('Invalid packet size') - controlees_count = span[0] - span = span[1:] - if len(span) < controlees_count * 38: + if len(span) < 31: raise Exception('Invalid packet size') - controlees = [] - for n in range(controlees_count): - controlees.append(Controlee_V2_0_32_Byte_Version.parse_all(span[n * 38:(n + 1) * 38])) - fields['controlees'] = controlees - span = span[controlees_count * 38:] - return SessionUpdateControllerMulticastListCmd_2_0_32_Byte_Payload(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - if len(self.controlees) > 255: - print(f"Invalid length for field SessionUpdateControllerMulticastListCmd_2_0_32_Byte_Payload::controlees: {len(self.controlees)} > 255; the array will be truncated") - del self.controlees[255:] - _span.append((len(self.controlees) << 0)) - for _elt in self.controlees: - _span.extend(_elt.serialize()) - return bytes(_span) - - @property - def size(self) -> int: - return sum([elt.size for elt in self.controlees]) + 1 - -@dataclass -class SessionUpdateControllerMulticastListRsp(SessionConfigResponse): - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) - - def __post_init__(self): - self.opcode = 7 - self.gid = GroupId.SESSION_CONFIG - self.mt = MessageType.RESPONSE - - @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionUpdateControllerMulticastListRsp', bytes]: - if fields['opcode'] != 0x7 or fields['gid'] != GroupId.SESSION_CONFIG or fields['mt'] != MessageType.RESPONSE: - raise Exception("Invalid constraint field values") - if len(span) < 1: - raise Exception('Invalid packet size') - fields['status'] = StatusCode.from_int(span[0]) - span = span[1:] - return SessionUpdateControllerMulticastListRsp(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - _span.append((self.status << 0)) - return SessionConfigResponse.serialize(self, payload = bytes(_span)) - - @property - def size(self) -> int: - return 1 - -@dataclass -class ControleeStatus(Packet): - mac_address: bytearray = field(kw_only=True, default_factory=bytearray) - subsession_id: int = field(kw_only=True, default=0) - status: MulticastUpdateStatusCode = field(kw_only=True, default=MulticastUpdateStatusCode.STATUS_OK_MULTICAST_LIST_UPDATE) - - def __post_init__(self): - pass - - @staticmethod - def parse(span: bytes) -> Tuple['ControleeStatus', bytes]: - fields = {'payload': None} - if len(span) < 2: - raise Exception('Invalid packet size') - fields['mac_address'] = list(span[:2]) - span = span[2:] - if len(span) < 5: - raise Exception('Invalid packet size') - value_ = int.from_bytes(span[0:4], byteorder='little') - fields['subsession_id'] = value_ - fields['status'] = MulticastUpdateStatusCode.from_int(span[4]) - span = span[5:] - return ControleeStatus(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - _span.extend(self.mac_address) - if self.subsession_id > 4294967295: - print(f"Invalid value for field ControleeStatus::subsession_id: {self.subsession_id} > 4294967295; the value will be truncated") - self.subsession_id &= 4294967295 - _span.extend(int.to_bytes((self.subsession_id << 0), length=4, byteorder='little')) - _span.append((self.status << 0)) - return bytes(_span) - - @property - def size(self) -> int: - return 7 - -@dataclass -class SessionUpdateControllerMulticastListNtf(SessionConfigNotification): - session_token: int = field(kw_only=True, default=0) - remaining_multicast_list_size: int = field(kw_only=True, default=0) - controlee_status: List[ControleeStatus] = field(kw_only=True, default_factory=list) - - def __post_init__(self): - self.opcode = 7 - self.gid = GroupId.SESSION_CONFIG - self.mt = MessageType.NOTIFICATION - - @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionUpdateControllerMulticastListNtf', bytes]: - if fields['opcode'] != 0x7 or fields['gid'] != GroupId.SESSION_CONFIG or fields['mt'] != MessageType.NOTIFICATION: - raise Exception("Invalid constraint field values") - if len(span) < 6: - raise Exception('Invalid packet size') - value_ = int.from_bytes(span[0:4], byteorder='little') - fields['session_token'] = value_ - fields['remaining_multicast_list_size'] = span[4] - controlee_status_count = span[5] - span = span[6:] - if len(span) < controlee_status_count * 7: - raise Exception('Invalid packet size') - controlee_status = [] - for n in range(controlee_status_count): - controlee_status.append(ControleeStatus.parse_all(span[n * 7:(n + 1) * 7])) - fields['controlee_status'] = controlee_status - span = span[controlee_status_count * 7:] - return SessionUpdateControllerMulticastListNtf(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - if self.session_token > 4294967295: - print(f"Invalid value for field SessionUpdateControllerMulticastListNtf::session_token: {self.session_token} > 4294967295; the value will be truncated") - self.session_token &= 4294967295 - _span.extend(int.to_bytes((self.session_token << 0), length=4, byteorder='little')) - if self.remaining_multicast_list_size > 255: - print(f"Invalid value for field SessionUpdateControllerMulticastListNtf::remaining_multicast_list_size: {self.remaining_multicast_list_size} > 255; the value will be truncated") - self.remaining_multicast_list_size &= 255 - _span.append((self.remaining_multicast_list_size << 0)) - if len(self.controlee_status) > 255: - print(f"Invalid length for field SessionUpdateControllerMulticastListNtf::controlee_status: {len(self.controlee_status)} > 255; the array will be truncated") - del self.controlee_status[255:] - _span.append((len(self.controlee_status) << 0)) - for _elt in self.controlee_status: - _span.extend(_elt.serialize()) - return SessionConfigNotification.serialize(self, payload = bytes(_span)) - - @property - def size(self) -> int: - return sum([elt.size for elt in self.controlee_status]) + 6 - -@dataclass -class DataCreditNtf(SessionControlNotification): - session_token: int = field(kw_only=True, default=0) - credit_availability: CreditAvailability = field(kw_only=True, default=CreditAvailability.CREDIT_NOT_AVAILABLE) - - def __post_init__(self): - self.opcode = 4 - self.gid = GroupId.SESSION_CONTROL - self.mt = MessageType.NOTIFICATION - - @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['DataCreditNtf', bytes]: - if fields['opcode'] != 0x4 or fields['gid'] != GroupId.SESSION_CONTROL or fields['mt'] != MessageType.NOTIFICATION: - raise Exception("Invalid constraint field values") - if len(span) < 5: - raise Exception('Invalid packet size') - value_ = int.from_bytes(span[0:4], byteorder='little') - fields['session_token'] = value_ - fields['credit_availability'] = CreditAvailability.from_int(span[4]) - span = span[5:] - return DataCreditNtf(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - if self.session_token > 4294967295: - print(f"Invalid value for field DataCreditNtf::session_token: {self.session_token} > 4294967295; the value will be truncated") - self.session_token &= 4294967295 - _span.extend(int.to_bytes((self.session_token << 0), length=4, byteorder='little')) - _span.append((self.credit_availability << 0)) - return SessionControlNotification.serialize(self, payload = bytes(_span)) - - @property - def size(self) -> int: - return 5 - -@dataclass -class DataTransferStatusNtf(SessionControlNotification): - session_token: int = field(kw_only=True, default=0) - uci_sequence_number: int = field(kw_only=True, default=0) - status: DataTransferNtfStatusCode = field(kw_only=True, default=DataTransferNtfStatusCode.UCI_DATA_TRANSFER_STATUS_REPETITION_OK) - tx_count: int = field(kw_only=True, default=0) - - def __post_init__(self): - self.opcode = 5 - self.gid = GroupId.SESSION_CONTROL - self.mt = MessageType.NOTIFICATION - - @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['DataTransferStatusNtf', bytes]: - if fields['opcode'] != 0x5 or fields['gid'] != GroupId.SESSION_CONTROL or fields['mt'] != MessageType.NOTIFICATION: - raise Exception("Invalid constraint field values") - if len(span) < 7: - raise Exception('Invalid packet size') - value_ = int.from_bytes(span[0:4], byteorder='little') - fields['session_token'] = value_ - fields['uci_sequence_number'] = span[4] - fields['status'] = DataTransferNtfStatusCode.from_int(span[5]) - fields['tx_count'] = span[6] - span = span[7:] - return DataTransferStatusNtf(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - if self.session_token > 4294967295: - print(f"Invalid value for field DataTransferStatusNtf::session_token: {self.session_token} > 4294967295; the value will be truncated") - self.session_token &= 4294967295 - _span.extend(int.to_bytes((self.session_token << 0), length=4, byteorder='little')) - if self.uci_sequence_number > 255: - print(f"Invalid value for field DataTransferStatusNtf::uci_sequence_number: {self.uci_sequence_number} > 255; the value will be truncated") - self.uci_sequence_number &= 255 - _span.append((self.uci_sequence_number << 0)) - _span.append((self.status << 0)) - if self.tx_count > 255: - print(f"Invalid value for field DataTransferStatusNtf::tx_count: {self.tx_count} > 255; the value will be truncated") - self.tx_count &= 255 - _span.append((self.tx_count << 0)) - return SessionControlNotification.serialize(self, payload = bytes(_span)) - - @property - def size(self) -> int: - return 7 - -@dataclass -class SessionQueryMaxDataSizeCmd(SessionConfigCommand): - session_token: int = field(kw_only=True, default=0) - - def __post_init__(self): - self.opcode = 11 - self.gid = GroupId.SESSION_CONFIG - self.mt = MessageType.COMMAND - - @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionQueryMaxDataSizeCmd', bytes]: - if fields['opcode'] != 0xb or fields['gid'] != GroupId.SESSION_CONFIG or fields['mt'] != MessageType.COMMAND: - raise Exception("Invalid constraint field values") - if len(span) < 4: - raise Exception('Invalid packet size') - value_ = int.from_bytes(span[0:4], byteorder='little') - fields['session_token'] = value_ - span = span[4:] - return SessionQueryMaxDataSizeCmd(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - if self.session_token > 4294967295: - print(f"Invalid value for field SessionQueryMaxDataSizeCmd::session_token: {self.session_token} > 4294967295; the value will be truncated") - self.session_token &= 4294967295 - _span.extend(int.to_bytes((self.session_token << 0), length=4, byteorder='little')) - return SessionConfigCommand.serialize(self, payload = bytes(_span)) - - @property - def size(self) -> int: - return 4 - -@dataclass -class SessionQueryMaxDataSizeRsp(SessionConfigResponse): - session_token: int = field(kw_only=True, default=0) - max_data_size: int = field(kw_only=True, default=0) - - def __post_init__(self): - self.opcode = 11 - self.gid = GroupId.SESSION_CONFIG - self.mt = MessageType.RESPONSE - - @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionQueryMaxDataSizeRsp', bytes]: - if fields['opcode'] != 0xb or fields['gid'] != GroupId.SESSION_CONFIG or fields['mt'] != MessageType.RESPONSE: - raise Exception("Invalid constraint field values") - if len(span) < 6: - raise Exception('Invalid packet size') - value_ = int.from_bytes(span[0:4], byteorder='little') - fields['session_token'] = value_ - value_ = int.from_bytes(span[4:6], byteorder='little') - fields['max_data_size'] = value_ - span = span[6:] - return SessionQueryMaxDataSizeRsp(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - if self.session_token > 4294967295: - print(f"Invalid value for field SessionQueryMaxDataSizeRsp::session_token: {self.session_token} > 4294967295; the value will be truncated") - self.session_token &= 4294967295 - _span.extend(int.to_bytes((self.session_token << 0), length=4, byteorder='little')) - if self.max_data_size > 65535: - print(f"Invalid value for field SessionQueryMaxDataSizeRsp::max_data_size: {self.max_data_size} > 65535; the value will be truncated") - self.max_data_size &= 65535 - _span.extend(int.to_bytes((self.max_data_size << 0), length=2, byteorder='little')) - return SessionConfigResponse.serialize(self, payload = bytes(_span)) - - @property - def size(self) -> int: - return 6 - -@dataclass -class SessionStartCmd(SessionControlCommand): - - - def __post_init__(self): - self.opcode = 0 - self.gid = GroupId.SESSION_CONTROL - self.mt = MessageType.COMMAND - - @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionStartCmd', bytes]: - if fields['opcode'] != 0x0 or fields['gid'] != GroupId.SESSION_CONTROL or fields['mt'] != MessageType.COMMAND: - raise Exception("Invalid constraint field values") - return SessionStartCmd(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - return SessionControlCommand.serialize(self, payload = bytes(_span)) - - @property - def size(self) -> int: - return 0 - -@dataclass -class SessionStartRsp(SessionControlResponse): - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) - - def __post_init__(self): - self.opcode = 0 - self.gid = GroupId.SESSION_CONTROL - self.mt = MessageType.RESPONSE - - @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionStartRsp', bytes]: - if fields['opcode'] != 0x0 or fields['gid'] != GroupId.SESSION_CONTROL or fields['mt'] != MessageType.RESPONSE: - raise Exception("Invalid constraint field values") - if len(span) < 1: - raise Exception('Invalid packet size') - fields['status'] = StatusCode.from_int(span[0]) - span = span[1:] - return SessionStartRsp(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - _span.append((self.status << 0)) - return SessionControlResponse.serialize(self, payload = bytes(_span)) - - @property - def size(self) -> int: - return 1 - -@dataclass -class ShortAddressTwoWayRangingMeasurement(Packet): - mac_address: int = field(kw_only=True, default=0) - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) - nlos: int = field(kw_only=True, default=0) - distance: int = field(kw_only=True, default=0) - aoa_azimuth: int = field(kw_only=True, default=0) - aoa_azimuth_fom: int = field(kw_only=True, default=0) - aoa_elevation: int = field(kw_only=True, default=0) - aoa_elevation_fom: int = field(kw_only=True, default=0) - aoa_destination_azimuth: int = field(kw_only=True, default=0) - aoa_destination_azimuth_fom: int = field(kw_only=True, default=0) - aoa_destination_elevation: int = field(kw_only=True, default=0) - aoa_destination_elevation_fom: int = field(kw_only=True, default=0) - slot_index: int = field(kw_only=True, default=0) - rssi: int = field(kw_only=True, default=0) - - def __post_init__(self): - pass - - @staticmethod - def parse(span: bytes) -> Tuple['ShortAddressTwoWayRangingMeasurement', bytes]: - fields = {'payload': None} - if len(span) < 31: - raise Exception('Invalid packet size') - value_ = int.from_bytes(span[0:2], byteorder='little') - fields['mac_address'] = value_ - fields['status'] = StatusCode.from_int(span[2]) - fields['nlos'] = span[3] - value_ = int.from_bytes(span[4:6], byteorder='little') - fields['distance'] = value_ - value_ = int.from_bytes(span[6:8], byteorder='little') - fields['aoa_azimuth'] = value_ - fields['aoa_azimuth_fom'] = span[8] - value_ = int.from_bytes(span[9:11], byteorder='little') - fields['aoa_elevation'] = value_ - fields['aoa_elevation_fom'] = span[11] - value_ = int.from_bytes(span[12:14], byteorder='little') - fields['aoa_destination_azimuth'] = value_ - fields['aoa_destination_azimuth_fom'] = span[14] - value_ = int.from_bytes(span[15:17], byteorder='little') - fields['aoa_destination_elevation'] = value_ - fields['aoa_destination_elevation_fom'] = span[17] - fields['slot_index'] = span[18] - fields['rssi'] = span[19] - value_ = int.from_bytes(span[20:28], byteorder='little') - value_ = int.from_bytes(span[28:31], byteorder='little') - span = span[31:] - return ShortAddressTwoWayRangingMeasurement(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - if self.mac_address > 65535: - print(f"Invalid value for field ShortAddressTwoWayRangingMeasurement::mac_address: {self.mac_address} > 65535; the value will be truncated") - self.mac_address &= 65535 - _span.extend(int.to_bytes((self.mac_address << 0), length=2, byteorder='little')) - _span.append((self.status << 0)) - if self.nlos > 255: - print(f"Invalid value for field ShortAddressTwoWayRangingMeasurement::nlos: {self.nlos} > 255; the value will be truncated") - self.nlos &= 255 - _span.append((self.nlos << 0)) - if self.distance > 65535: - print(f"Invalid value for field ShortAddressTwoWayRangingMeasurement::distance: {self.distance} > 65535; the value will be truncated") - self.distance &= 65535 - _span.extend(int.to_bytes((self.distance << 0), length=2, byteorder='little')) - if self.aoa_azimuth > 65535: - print(f"Invalid value for field ShortAddressTwoWayRangingMeasurement::aoa_azimuth: {self.aoa_azimuth} > 65535; the value will be truncated") - self.aoa_azimuth &= 65535 - _span.extend(int.to_bytes((self.aoa_azimuth << 0), length=2, byteorder='little')) - if self.aoa_azimuth_fom > 255: - print(f"Invalid value for field ShortAddressTwoWayRangingMeasurement::aoa_azimuth_fom: {self.aoa_azimuth_fom} > 255; the value will be truncated") - self.aoa_azimuth_fom &= 255 - _span.append((self.aoa_azimuth_fom << 0)) - if self.aoa_elevation > 65535: - print(f"Invalid value for field ShortAddressTwoWayRangingMeasurement::aoa_elevation: {self.aoa_elevation} > 65535; the value will be truncated") - self.aoa_elevation &= 65535 - _span.extend(int.to_bytes((self.aoa_elevation << 0), length=2, byteorder='little')) - if self.aoa_elevation_fom > 255: - print(f"Invalid value for field ShortAddressTwoWayRangingMeasurement::aoa_elevation_fom: {self.aoa_elevation_fom} > 255; the value will be truncated") - self.aoa_elevation_fom &= 255 - _span.append((self.aoa_elevation_fom << 0)) - if self.aoa_destination_azimuth > 65535: - print(f"Invalid value for field ShortAddressTwoWayRangingMeasurement::aoa_destination_azimuth: {self.aoa_destination_azimuth} > 65535; the value will be truncated") - self.aoa_destination_azimuth &= 65535 - _span.extend(int.to_bytes((self.aoa_destination_azimuth << 0), length=2, byteorder='little')) - if self.aoa_destination_azimuth_fom > 255: - print(f"Invalid value for field ShortAddressTwoWayRangingMeasurement::aoa_destination_azimuth_fom: {self.aoa_destination_azimuth_fom} > 255; the value will be truncated") - self.aoa_destination_azimuth_fom &= 255 - _span.append((self.aoa_destination_azimuth_fom << 0)) - if self.aoa_destination_elevation > 65535: - print(f"Invalid value for field ShortAddressTwoWayRangingMeasurement::aoa_destination_elevation: {self.aoa_destination_elevation} > 65535; the value will be truncated") - self.aoa_destination_elevation &= 65535 - _span.extend(int.to_bytes((self.aoa_destination_elevation << 0), length=2, byteorder='little')) - if self.aoa_destination_elevation_fom > 255: - print(f"Invalid value for field ShortAddressTwoWayRangingMeasurement::aoa_destination_elevation_fom: {self.aoa_destination_elevation_fom} > 255; the value will be truncated") - self.aoa_destination_elevation_fom &= 255 - _span.append((self.aoa_destination_elevation_fom << 0)) - if self.slot_index > 255: - print(f"Invalid value for field ShortAddressTwoWayRangingMeasurement::slot_index: {self.slot_index} > 255; the value will be truncated") - self.slot_index &= 255 - _span.append((self.slot_index << 0)) - if self.rssi > 255: - print(f"Invalid value for field ShortAddressTwoWayRangingMeasurement::rssi: {self.rssi} > 255; the value will be truncated") - self.rssi &= 255 - _span.append((self.rssi << 0)) - _span.extend([0] * 8) - _span.extend([0] * 3) - return bytes(_span) - - @property - def size(self) -> int: - return 31 - -@dataclass -class ExtendedAddressTwoWayRangingMeasurement(Packet): - mac_address: int = field(kw_only=True, default=0) - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) - nlos: int = field(kw_only=True, default=0) - distance: int = field(kw_only=True, default=0) - aoa_azimuth: int = field(kw_only=True, default=0) - aoa_azimuth_fom: int = field(kw_only=True, default=0) - aoa_elevation: int = field(kw_only=True, default=0) - aoa_elevation_fom: int = field(kw_only=True, default=0) - aoa_destination_azimuth: int = field(kw_only=True, default=0) - aoa_destination_azimuth_fom: int = field(kw_only=True, default=0) - aoa_destination_elevation: int = field(kw_only=True, default=0) - aoa_destination_elevation_fom: int = field(kw_only=True, default=0) - slot_index: int = field(kw_only=True, default=0) - rssi: int = field(kw_only=True, default=0) - - def __post_init__(self): - pass - - @staticmethod - def parse(span: bytes) -> Tuple['ExtendedAddressTwoWayRangingMeasurement', bytes]: - fields = {'payload': None} - if len(span) < 31: - raise Exception('Invalid packet size') - value_ = int.from_bytes(span[0:8], byteorder='little') - fields['mac_address'] = value_ - fields['status'] = StatusCode.from_int(span[8]) - fields['nlos'] = span[9] - value_ = int.from_bytes(span[10:12], byteorder='little') - fields['distance'] = value_ - value_ = int.from_bytes(span[12:14], byteorder='little') - fields['aoa_azimuth'] = value_ - fields['aoa_azimuth_fom'] = span[14] - value_ = int.from_bytes(span[15:17], byteorder='little') - fields['aoa_elevation'] = value_ - fields['aoa_elevation_fom'] = span[17] - value_ = int.from_bytes(span[18:20], byteorder='little') - fields['aoa_destination_azimuth'] = value_ - fields['aoa_destination_azimuth_fom'] = span[20] - value_ = int.from_bytes(span[21:23], byteorder='little') - fields['aoa_destination_elevation'] = value_ - fields['aoa_destination_elevation_fom'] = span[23] - fields['slot_index'] = span[24] - fields['rssi'] = span[25] - value_ = int.from_bytes(span[26:31], byteorder='little') - span = span[31:] - return ExtendedAddressTwoWayRangingMeasurement(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - if self.mac_address > 18446744073709551615: - print(f"Invalid value for field ExtendedAddressTwoWayRangingMeasurement::mac_address: {self.mac_address} > 18446744073709551615; the value will be truncated") - self.mac_address &= 18446744073709551615 - _span.extend(int.to_bytes((self.mac_address << 0), length=8, byteorder='little')) - _span.append((self.status << 0)) - if self.nlos > 255: - print(f"Invalid value for field ExtendedAddressTwoWayRangingMeasurement::nlos: {self.nlos} > 255; the value will be truncated") - self.nlos &= 255 - _span.append((self.nlos << 0)) - if self.distance > 65535: - print(f"Invalid value for field ExtendedAddressTwoWayRangingMeasurement::distance: {self.distance} > 65535; the value will be truncated") - self.distance &= 65535 - _span.extend(int.to_bytes((self.distance << 0), length=2, byteorder='little')) - if self.aoa_azimuth > 65535: - print(f"Invalid value for field ExtendedAddressTwoWayRangingMeasurement::aoa_azimuth: {self.aoa_azimuth} > 65535; the value will be truncated") - self.aoa_azimuth &= 65535 - _span.extend(int.to_bytes((self.aoa_azimuth << 0), length=2, byteorder='little')) - if self.aoa_azimuth_fom > 255: - print(f"Invalid value for field ExtendedAddressTwoWayRangingMeasurement::aoa_azimuth_fom: {self.aoa_azimuth_fom} > 255; the value will be truncated") - self.aoa_azimuth_fom &= 255 - _span.append((self.aoa_azimuth_fom << 0)) - if self.aoa_elevation > 65535: - print(f"Invalid value for field ExtendedAddressTwoWayRangingMeasurement::aoa_elevation: {self.aoa_elevation} > 65535; the value will be truncated") - self.aoa_elevation &= 65535 - _span.extend(int.to_bytes((self.aoa_elevation << 0), length=2, byteorder='little')) - if self.aoa_elevation_fom > 255: - print(f"Invalid value for field ExtendedAddressTwoWayRangingMeasurement::aoa_elevation_fom: {self.aoa_elevation_fom} > 255; the value will be truncated") - self.aoa_elevation_fom &= 255 - _span.append((self.aoa_elevation_fom << 0)) - if self.aoa_destination_azimuth > 65535: - print(f"Invalid value for field ExtendedAddressTwoWayRangingMeasurement::aoa_destination_azimuth: {self.aoa_destination_azimuth} > 65535; the value will be truncated") - self.aoa_destination_azimuth &= 65535 - _span.extend(int.to_bytes((self.aoa_destination_azimuth << 0), length=2, byteorder='little')) - if self.aoa_destination_azimuth_fom > 255: - print(f"Invalid value for field ExtendedAddressTwoWayRangingMeasurement::aoa_destination_azimuth_fom: {self.aoa_destination_azimuth_fom} > 255; the value will be truncated") - self.aoa_destination_azimuth_fom &= 255 - _span.append((self.aoa_destination_azimuth_fom << 0)) - if self.aoa_destination_elevation > 65535: - print(f"Invalid value for field ExtendedAddressTwoWayRangingMeasurement::aoa_destination_elevation: {self.aoa_destination_elevation} > 65535; the value will be truncated") - self.aoa_destination_elevation &= 65535 - _span.extend(int.to_bytes((self.aoa_destination_elevation << 0), length=2, byteorder='little')) - if self.aoa_destination_elevation_fom > 255: - print(f"Invalid value for field ExtendedAddressTwoWayRangingMeasurement::aoa_destination_elevation_fom: {self.aoa_destination_elevation_fom} > 255; the value will be truncated") - self.aoa_destination_elevation_fom &= 255 - _span.append((self.aoa_destination_elevation_fom << 0)) - if self.slot_index > 255: - print(f"Invalid value for field ExtendedAddressTwoWayRangingMeasurement::slot_index: {self.slot_index} > 255; the value will be truncated") - self.slot_index &= 255 - _span.append((self.slot_index << 0)) - if self.rssi > 255: - print(f"Invalid value for field ExtendedAddressTwoWayRangingMeasurement::rssi: {self.rssi} > 255; the value will be truncated") - self.rssi &= 255 - _span.append((self.rssi << 0)) - _span.extend([0] * 5) - return bytes(_span) - - @property - def size(self) -> int: - return 31 - -@dataclass -class ShortAddressOwrAoaRangingMeasurement(Packet): - mac_address: int = field(kw_only=True, default=0) - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) - nlos: int = field(kw_only=True, default=0) - frame_sequence_number: int = field(kw_only=True, default=0) - block_index: int = field(kw_only=True, default=0) - aoa_azimuth: int = field(kw_only=True, default=0) - aoa_azimuth_fom: int = field(kw_only=True, default=0) - aoa_elevation: int = field(kw_only=True, default=0) - aoa_elevation_fom: int = field(kw_only=True, default=0) - - def __post_init__(self): - pass - - @staticmethod - def parse(span: bytes) -> Tuple['ShortAddressOwrAoaRangingMeasurement', bytes]: - fields = {'payload': None} - if len(span) < 13: - raise Exception('Invalid packet size') - value_ = int.from_bytes(span[0:2], byteorder='little') - fields['mac_address'] = value_ - fields['status'] = StatusCode.from_int(span[2]) - fields['nlos'] = span[3] - fields['frame_sequence_number'] = span[4] - value_ = int.from_bytes(span[5:7], byteorder='little') - fields['block_index'] = value_ - value_ = int.from_bytes(span[7:9], byteorder='little') - fields['aoa_azimuth'] = value_ - fields['aoa_azimuth_fom'] = span[9] - value_ = int.from_bytes(span[10:12], byteorder='little') - fields['aoa_elevation'] = value_ - fields['aoa_elevation_fom'] = span[12] - span = span[13:] - return ShortAddressOwrAoaRangingMeasurement(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - if self.mac_address > 65535: - print(f"Invalid value for field ShortAddressOwrAoaRangingMeasurement::mac_address: {self.mac_address} > 65535; the value will be truncated") - self.mac_address &= 65535 - _span.extend(int.to_bytes((self.mac_address << 0), length=2, byteorder='little')) - _span.append((self.status << 0)) - if self.nlos > 255: - print(f"Invalid value for field ShortAddressOwrAoaRangingMeasurement::nlos: {self.nlos} > 255; the value will be truncated") - self.nlos &= 255 - _span.append((self.nlos << 0)) - if self.frame_sequence_number > 255: - print(f"Invalid value for field ShortAddressOwrAoaRangingMeasurement::frame_sequence_number: {self.frame_sequence_number} > 255; the value will be truncated") - self.frame_sequence_number &= 255 - _span.append((self.frame_sequence_number << 0)) - if self.block_index > 65535: - print(f"Invalid value for field ShortAddressOwrAoaRangingMeasurement::block_index: {self.block_index} > 65535; the value will be truncated") - self.block_index &= 65535 - _span.extend(int.to_bytes((self.block_index << 0), length=2, byteorder='little')) - if self.aoa_azimuth > 65535: - print(f"Invalid value for field ShortAddressOwrAoaRangingMeasurement::aoa_azimuth: {self.aoa_azimuth} > 65535; the value will be truncated") - self.aoa_azimuth &= 65535 - _span.extend(int.to_bytes((self.aoa_azimuth << 0), length=2, byteorder='little')) - if self.aoa_azimuth_fom > 255: - print(f"Invalid value for field ShortAddressOwrAoaRangingMeasurement::aoa_azimuth_fom: {self.aoa_azimuth_fom} > 255; the value will be truncated") - self.aoa_azimuth_fom &= 255 - _span.append((self.aoa_azimuth_fom << 0)) - if self.aoa_elevation > 65535: - print(f"Invalid value for field ShortAddressOwrAoaRangingMeasurement::aoa_elevation: {self.aoa_elevation} > 65535; the value will be truncated") - self.aoa_elevation &= 65535 - _span.extend(int.to_bytes((self.aoa_elevation << 0), length=2, byteorder='little')) - if self.aoa_elevation_fom > 255: - print(f"Invalid value for field ShortAddressOwrAoaRangingMeasurement::aoa_elevation_fom: {self.aoa_elevation_fom} > 255; the value will be truncated") - self.aoa_elevation_fom &= 255 - _span.append((self.aoa_elevation_fom << 0)) - return bytes(_span) - - @property - def size(self) -> int: - return 13 - -@dataclass -class ExtendedAddressOwrAoaRangingMeasurement(Packet): - mac_address: int = field(kw_only=True, default=0) - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) - nlos: int = field(kw_only=True, default=0) - frame_sequence_number: int = field(kw_only=True, default=0) - block_index: int = field(kw_only=True, default=0) - aoa_azimuth: int = field(kw_only=True, default=0) - aoa_azimuth_fom: int = field(kw_only=True, default=0) - aoa_elevation: int = field(kw_only=True, default=0) - aoa_elevation_fom: int = field(kw_only=True, default=0) - - def __post_init__(self): - pass - - @staticmethod - def parse(span: bytes) -> Tuple['ExtendedAddressOwrAoaRangingMeasurement', bytes]: - fields = {'payload': None} - if len(span) < 19: - raise Exception('Invalid packet size') - value_ = int.from_bytes(span[0:8], byteorder='little') - fields['mac_address'] = value_ - fields['status'] = StatusCode.from_int(span[8]) - fields['nlos'] = span[9] - fields['frame_sequence_number'] = span[10] - value_ = int.from_bytes(span[11:13], byteorder='little') - fields['block_index'] = value_ - value_ = int.from_bytes(span[13:15], byteorder='little') - fields['aoa_azimuth'] = value_ - fields['aoa_azimuth_fom'] = span[15] - value_ = int.from_bytes(span[16:18], byteorder='little') - fields['aoa_elevation'] = value_ - fields['aoa_elevation_fom'] = span[18] - span = span[19:] - return ExtendedAddressOwrAoaRangingMeasurement(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - if self.mac_address > 18446744073709551615: - print(f"Invalid value for field ExtendedAddressOwrAoaRangingMeasurement::mac_address: {self.mac_address} > 18446744073709551615; the value will be truncated") - self.mac_address &= 18446744073709551615 - _span.extend(int.to_bytes((self.mac_address << 0), length=8, byteorder='little')) - _span.append((self.status << 0)) - if self.nlos > 255: - print(f"Invalid value for field ExtendedAddressOwrAoaRangingMeasurement::nlos: {self.nlos} > 255; the value will be truncated") - self.nlos &= 255 - _span.append((self.nlos << 0)) - if self.frame_sequence_number > 255: - print(f"Invalid value for field ExtendedAddressOwrAoaRangingMeasurement::frame_sequence_number: {self.frame_sequence_number} > 255; the value will be truncated") - self.frame_sequence_number &= 255 - _span.append((self.frame_sequence_number << 0)) - if self.block_index > 65535: - print(f"Invalid value for field ExtendedAddressOwrAoaRangingMeasurement::block_index: {self.block_index} > 65535; the value will be truncated") - self.block_index &= 65535 - _span.extend(int.to_bytes((self.block_index << 0), length=2, byteorder='little')) - if self.aoa_azimuth > 65535: - print(f"Invalid value for field ExtendedAddressOwrAoaRangingMeasurement::aoa_azimuth: {self.aoa_azimuth} > 65535; the value will be truncated") - self.aoa_azimuth &= 65535 - _span.extend(int.to_bytes((self.aoa_azimuth << 0), length=2, byteorder='little')) - if self.aoa_azimuth_fom > 255: - print(f"Invalid value for field ExtendedAddressOwrAoaRangingMeasurement::aoa_azimuth_fom: {self.aoa_azimuth_fom} > 255; the value will be truncated") - self.aoa_azimuth_fom &= 255 - _span.append((self.aoa_azimuth_fom << 0)) - if self.aoa_elevation > 65535: - print(f"Invalid value for field ExtendedAddressOwrAoaRangingMeasurement::aoa_elevation: {self.aoa_elevation} > 65535; the value will be truncated") - self.aoa_elevation &= 65535 - _span.extend(int.to_bytes((self.aoa_elevation << 0), length=2, byteorder='little')) - if self.aoa_elevation_fom > 255: - print(f"Invalid value for field ExtendedAddressOwrAoaRangingMeasurement::aoa_elevation_fom: {self.aoa_elevation_fom} > 255; the value will be truncated") - self.aoa_elevation_fom &= 255 - _span.append((self.aoa_elevation_fom << 0)) - return bytes(_span) - - @property - def size(self) -> int: - return 19 - -class RangingMeasurementType(enum.IntEnum): - ONE_WAY = 0x0 - TWO_WAY = 0x1 - DL_TDOA = 0x2 - OWR_AOA = 0x3 - - @staticmethod - def from_int(v: int) -> Union[int, 'RangingMeasurementType']: - try: - return RangingMeasurementType(v) - except ValueError as exn: - raise exn - - -@dataclass -class SessionInfoNtf(SessionControlNotification): - sequence_number: int = field(kw_only=True, default=0) - session_token: int = field(kw_only=True, default=0) - rcr_indicator: int = field(kw_only=True, default=0) - current_ranging_interval: int = field(kw_only=True, default=0) - ranging_measurement_type: RangingMeasurementType = field(kw_only=True, default=RangingMeasurementType.ONE_WAY) - mac_address_indicator: MacAddressIndicator = field(kw_only=True, default=MacAddressIndicator.SHORT_ADDRESS) - - def __post_init__(self): - self.opcode = 0 - self.gid = GroupId.SESSION_CONTROL - self.mt = MessageType.NOTIFICATION - - @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionInfoNtf', bytes]: - if fields['opcode'] != 0x0 or fields['gid'] != GroupId.SESSION_CONTROL or fields['mt'] != MessageType.NOTIFICATION: - raise Exception("Invalid constraint field values") - if len(span) < 24: - raise Exception('Invalid packet size') - value_ = int.from_bytes(span[0:4], byteorder='little') - fields['sequence_number'] = value_ - value_ = int.from_bytes(span[4:8], byteorder='little') - fields['session_token'] = value_ - fields['rcr_indicator'] = span[8] - value_ = int.from_bytes(span[9:13], byteorder='little') - fields['current_ranging_interval'] = value_ - fields['ranging_measurement_type'] = RangingMeasurementType.from_int(span[13]) - fields['mac_address_indicator'] = MacAddressIndicator.from_int(span[15]) - value_ = int.from_bytes(span[16:24], byteorder='little') - span = span[24:] - payload = span - span = bytes([]) - fields['payload'] = payload - try: - return ShortMacTwoWaySessionInfoNtf.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return ExtendedMacTwoWaySessionInfoNtf.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return ShortMacDlTDoASessionInfoNtf.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return ExtendedMacDlTDoASessionInfoNtf.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return ShortMacOwrAoaSessionInfoNtf.parse(fields.copy(), payload) - except Exception as exn: - pass - try: - return ExtendedMacOwrAoaSessionInfoNtf.parse(fields.copy(), payload) - except Exception as exn: - pass - return SessionInfoNtf(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - if self.sequence_number > 4294967295: - print(f"Invalid value for field SessionInfoNtf::sequence_number: {self.sequence_number} > 4294967295; the value will be truncated") - self.sequence_number &= 4294967295 - _span.extend(int.to_bytes((self.sequence_number << 0), length=4, byteorder='little')) - if self.session_token > 4294967295: - print(f"Invalid value for field SessionInfoNtf::session_token: {self.session_token} > 4294967295; the value will be truncated") - self.session_token &= 4294967295 - _span.extend(int.to_bytes((self.session_token << 0), length=4, byteorder='little')) - if self.rcr_indicator > 255: - print(f"Invalid value for field SessionInfoNtf::rcr_indicator: {self.rcr_indicator} > 255; the value will be truncated") - self.rcr_indicator &= 255 - _span.append((self.rcr_indicator << 0)) - if self.current_ranging_interval > 4294967295: - print(f"Invalid value for field SessionInfoNtf::current_ranging_interval: {self.current_ranging_interval} > 4294967295; the value will be truncated") - self.current_ranging_interval &= 4294967295 - _span.extend(int.to_bytes((self.current_ranging_interval << 0), length=4, byteorder='little')) - _span.append((self.ranging_measurement_type << 0)) - _span.extend([0] * 1) - _span.append((self.mac_address_indicator << 0)) - _span.extend([0] * 8) - _span.extend(payload or self.payload or []) - return SessionControlNotification.serialize(self, payload = bytes(_span)) - - @property - def size(self) -> int: - return len(self.payload) + 24 - -@dataclass -class ShortMacTwoWaySessionInfoNtf(SessionInfoNtf): - two_way_ranging_measurements: List[ShortAddressTwoWayRangingMeasurement] = field(kw_only=True, default_factory=list) - vendor_data: bytearray = field(kw_only=True, default_factory=bytearray) - - def __post_init__(self): - self.ranging_measurement_type = RangingMeasurementType.TWO_WAY - self.mac_address_indicator = MacAddressIndicator.SHORT_ADDRESS - self.opcode = 0 - self.gid = GroupId.SESSION_CONTROL - self.mt = MessageType.NOTIFICATION - - @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['ShortMacTwoWaySessionInfoNtf', bytes]: - if fields['ranging_measurement_type'] != RangingMeasurementType.TWO_WAY or fields['mac_address_indicator'] != MacAddressIndicator.SHORT_ADDRESS or fields['opcode'] != 0x0 or fields['gid'] != GroupId.SESSION_CONTROL or fields['mt'] != MessageType.NOTIFICATION: - raise Exception("Invalid constraint field values") - if len(span) < 1: - raise Exception('Invalid packet size') - two_way_ranging_measurements_count = span[0] - span = span[1:] - if len(span) < two_way_ranging_measurements_count * 31: - raise Exception('Invalid packet size') - two_way_ranging_measurements = [] - for n in range(two_way_ranging_measurements_count): - two_way_ranging_measurements.append(ShortAddressTwoWayRangingMeasurement.parse_all(span[n * 31:(n + 1) * 31])) - fields['two_way_ranging_measurements'] = two_way_ranging_measurements - span = span[two_way_ranging_measurements_count * 31:] - fields['vendor_data'] = list(span) - span = bytes() - return ShortMacTwoWaySessionInfoNtf(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - if len(self.two_way_ranging_measurements) > 255: - print(f"Invalid length for field ShortMacTwoWaySessionInfoNtf::two_way_ranging_measurements: {len(self.two_way_ranging_measurements)} > 255; the array will be truncated") - del self.two_way_ranging_measurements[255:] - _span.append((len(self.two_way_ranging_measurements) << 0)) - for _elt in self.two_way_ranging_measurements: - _span.extend(_elt.serialize()) - _span.extend(self.vendor_data) - return SessionInfoNtf.serialize(self, payload = bytes(_span)) - - @property - def size(self) -> int: - return 1 + ( - sum([elt.size for elt in self.two_way_ranging_measurements]) + - len(self.vendor_data) * 1 - ) - -@dataclass -class ExtendedMacTwoWaySessionInfoNtf(SessionInfoNtf): - two_way_ranging_measurements: List[ExtendedAddressTwoWayRangingMeasurement] = field(kw_only=True, default_factory=list) - vendor_data: bytearray = field(kw_only=True, default_factory=bytearray) - - def __post_init__(self): - self.ranging_measurement_type = RangingMeasurementType.TWO_WAY - self.mac_address_indicator = MacAddressIndicator.EXTENDED_ADDRESS - self.opcode = 0 - self.gid = GroupId.SESSION_CONTROL - self.mt = MessageType.NOTIFICATION - - @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['ExtendedMacTwoWaySessionInfoNtf', bytes]: - if fields['ranging_measurement_type'] != RangingMeasurementType.TWO_WAY or fields['mac_address_indicator'] != MacAddressIndicator.EXTENDED_ADDRESS or fields['opcode'] != 0x0 or fields['gid'] != GroupId.SESSION_CONTROL or fields['mt'] != MessageType.NOTIFICATION: - raise Exception("Invalid constraint field values") - if len(span) < 1: - raise Exception('Invalid packet size') - two_way_ranging_measurements_count = span[0] - span = span[1:] - if len(span) < two_way_ranging_measurements_count * 31: - raise Exception('Invalid packet size') - two_way_ranging_measurements = [] - for n in range(two_way_ranging_measurements_count): - two_way_ranging_measurements.append(ExtendedAddressTwoWayRangingMeasurement.parse_all(span[n * 31:(n + 1) * 31])) - fields['two_way_ranging_measurements'] = two_way_ranging_measurements - span = span[two_way_ranging_measurements_count * 31:] - fields['vendor_data'] = list(span) - span = bytes() - return ExtendedMacTwoWaySessionInfoNtf(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - if len(self.two_way_ranging_measurements) > 255: - print(f"Invalid length for field ExtendedMacTwoWaySessionInfoNtf::two_way_ranging_measurements: {len(self.two_way_ranging_measurements)} > 255; the array will be truncated") - del self.two_way_ranging_measurements[255:] - _span.append((len(self.two_way_ranging_measurements) << 0)) - for _elt in self.two_way_ranging_measurements: - _span.extend(_elt.serialize()) - _span.extend(self.vendor_data) - return SessionInfoNtf.serialize(self, payload = bytes(_span)) - - @property - def size(self) -> int: - return 1 + ( - sum([elt.size for elt in self.two_way_ranging_measurements]) + - len(self.vendor_data) * 1 - ) - -@dataclass -class ShortMacDlTDoASessionInfoNtf(SessionInfoNtf): - no_of_ranging_measurements: int = field(kw_only=True, default=0) - dl_tdoa_measurements: bytearray = field(kw_only=True, default_factory=bytearray) - - def __post_init__(self): - self.ranging_measurement_type = RangingMeasurementType.DL_TDOA - self.mac_address_indicator = MacAddressIndicator.SHORT_ADDRESS - self.opcode = 0 - self.gid = GroupId.SESSION_CONTROL - self.mt = MessageType.NOTIFICATION - - @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['ShortMacDlTDoASessionInfoNtf', bytes]: - if fields['ranging_measurement_type'] != RangingMeasurementType.DL_TDOA or fields['mac_address_indicator'] != MacAddressIndicator.SHORT_ADDRESS or fields['opcode'] != 0x0 or fields['gid'] != GroupId.SESSION_CONTROL or fields['mt'] != MessageType.NOTIFICATION: - raise Exception("Invalid constraint field values") - if len(span) < 1: - raise Exception('Invalid packet size') - fields['no_of_ranging_measurements'] = span[0] - span = span[1:] - fields['dl_tdoa_measurements'] = list(span) - span = bytes() - return ShortMacDlTDoASessionInfoNtf(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - if self.no_of_ranging_measurements > 255: - print(f"Invalid value for field ShortMacDlTDoASessionInfoNtf::no_of_ranging_measurements: {self.no_of_ranging_measurements} > 255; the value will be truncated") - self.no_of_ranging_measurements &= 255 - _span.append((self.no_of_ranging_measurements << 0)) - _span.extend(self.dl_tdoa_measurements) - return SessionInfoNtf.serialize(self, payload = bytes(_span)) - - @property - def size(self) -> int: - return len(self.dl_tdoa_measurements) * 1 + 1 - -@dataclass -class ExtendedMacDlTDoASessionInfoNtf(SessionInfoNtf): - no_of_ranging_measurements: int = field(kw_only=True, default=0) - dl_tdoa_measurements: bytearray = field(kw_only=True, default_factory=bytearray) - - def __post_init__(self): - self.ranging_measurement_type = RangingMeasurementType.DL_TDOA - self.mac_address_indicator = MacAddressIndicator.EXTENDED_ADDRESS - self.opcode = 0 - self.gid = GroupId.SESSION_CONTROL - self.mt = MessageType.NOTIFICATION - - @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['ExtendedMacDlTDoASessionInfoNtf', bytes]: - if fields['ranging_measurement_type'] != RangingMeasurementType.DL_TDOA or fields['mac_address_indicator'] != MacAddressIndicator.EXTENDED_ADDRESS or fields['opcode'] != 0x0 or fields['gid'] != GroupId.SESSION_CONTROL or fields['mt'] != MessageType.NOTIFICATION: - raise Exception("Invalid constraint field values") - if len(span) < 1: - raise Exception('Invalid packet size') - fields['no_of_ranging_measurements'] = span[0] - span = span[1:] - fields['dl_tdoa_measurements'] = list(span) - span = bytes() - return ExtendedMacDlTDoASessionInfoNtf(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - if self.no_of_ranging_measurements > 255: - print(f"Invalid value for field ExtendedMacDlTDoASessionInfoNtf::no_of_ranging_measurements: {self.no_of_ranging_measurements} > 255; the value will be truncated") - self.no_of_ranging_measurements &= 255 - _span.append((self.no_of_ranging_measurements << 0)) - _span.extend(self.dl_tdoa_measurements) - return SessionInfoNtf.serialize(self, payload = bytes(_span)) - - @property - def size(self) -> int: - return len(self.dl_tdoa_measurements) * 1 + 1 - -@dataclass -class ShortMacOwrAoaSessionInfoNtf(SessionInfoNtf): - owr_aoa_ranging_measurements: List[ShortAddressOwrAoaRangingMeasurement] = field(kw_only=True, default_factory=list) - vendor_data: bytearray = field(kw_only=True, default_factory=bytearray) - - def __post_init__(self): - self.ranging_measurement_type = RangingMeasurementType.OWR_AOA - self.mac_address_indicator = MacAddressIndicator.SHORT_ADDRESS - self.opcode = 0 - self.gid = GroupId.SESSION_CONTROL - self.mt = MessageType.NOTIFICATION - - @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['ShortMacOwrAoaSessionInfoNtf', bytes]: - if fields['ranging_measurement_type'] != RangingMeasurementType.OWR_AOA or fields['mac_address_indicator'] != MacAddressIndicator.SHORT_ADDRESS or fields['opcode'] != 0x0 or fields['gid'] != GroupId.SESSION_CONTROL or fields['mt'] != MessageType.NOTIFICATION: - raise Exception("Invalid constraint field values") - if len(span) < 1: - raise Exception('Invalid packet size') - owr_aoa_ranging_measurements_count = span[0] - span = span[1:] - if len(span) < owr_aoa_ranging_measurements_count * 13: - raise Exception('Invalid packet size') - owr_aoa_ranging_measurements = [] - for n in range(owr_aoa_ranging_measurements_count): - owr_aoa_ranging_measurements.append(ShortAddressOwrAoaRangingMeasurement.parse_all(span[n * 13:(n + 1) * 13])) - fields['owr_aoa_ranging_measurements'] = owr_aoa_ranging_measurements - span = span[owr_aoa_ranging_measurements_count * 13:] - fields['vendor_data'] = list(span) - span = bytes() - return ShortMacOwrAoaSessionInfoNtf(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - if len(self.owr_aoa_ranging_measurements) > 255: - print(f"Invalid length for field ShortMacOwrAoaSessionInfoNtf::owr_aoa_ranging_measurements: {len(self.owr_aoa_ranging_measurements)} > 255; the array will be truncated") - del self.owr_aoa_ranging_measurements[255:] - _span.append((len(self.owr_aoa_ranging_measurements) << 0)) - for _elt in self.owr_aoa_ranging_measurements: - _span.extend(_elt.serialize()) - _span.extend(self.vendor_data) - return SessionInfoNtf.serialize(self, payload = bytes(_span)) - - @property - def size(self) -> int: - return 1 + ( - sum([elt.size for elt in self.owr_aoa_ranging_measurements]) + - len(self.vendor_data) * 1 - ) - -@dataclass -class ExtendedMacOwrAoaSessionInfoNtf(SessionInfoNtf): - owr_aoa_ranging_measurements: List[ExtendedAddressOwrAoaRangingMeasurement] = field(kw_only=True, default_factory=list) - vendor_data: bytearray = field(kw_only=True, default_factory=bytearray) - - def __post_init__(self): - self.ranging_measurement_type = RangingMeasurementType.OWR_AOA - self.mac_address_indicator = MacAddressIndicator.EXTENDED_ADDRESS - self.opcode = 0 - self.gid = GroupId.SESSION_CONTROL - self.mt = MessageType.NOTIFICATION - - @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['ExtendedMacOwrAoaSessionInfoNtf', bytes]: - if fields['ranging_measurement_type'] != RangingMeasurementType.OWR_AOA or fields['mac_address_indicator'] != MacAddressIndicator.EXTENDED_ADDRESS or fields['opcode'] != 0x0 or fields['gid'] != GroupId.SESSION_CONTROL or fields['mt'] != MessageType.NOTIFICATION: - raise Exception("Invalid constraint field values") - if len(span) < 1: - raise Exception('Invalid packet size') - owr_aoa_ranging_measurements_count = span[0] - span = span[1:] - if len(span) < owr_aoa_ranging_measurements_count * 19: - raise Exception('Invalid packet size') - owr_aoa_ranging_measurements = [] - for n in range(owr_aoa_ranging_measurements_count): - owr_aoa_ranging_measurements.append(ExtendedAddressOwrAoaRangingMeasurement.parse_all(span[n * 19:(n + 1) * 19])) - fields['owr_aoa_ranging_measurements'] = owr_aoa_ranging_measurements - span = span[owr_aoa_ranging_measurements_count * 19:] - fields['vendor_data'] = list(span) - span = bytes() - return ExtendedMacOwrAoaSessionInfoNtf(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - if len(self.owr_aoa_ranging_measurements) > 255: - print(f"Invalid length for field ExtendedMacOwrAoaSessionInfoNtf::owr_aoa_ranging_measurements: {len(self.owr_aoa_ranging_measurements)} > 255; the array will be truncated") - del self.owr_aoa_ranging_measurements[255:] - _span.append((len(self.owr_aoa_ranging_measurements) << 0)) - for _elt in self.owr_aoa_ranging_measurements: - _span.extend(_elt.serialize()) - _span.extend(self.vendor_data) - return SessionInfoNtf.serialize(self, payload = bytes(_span)) - - @property - def size(self) -> int: - return 1 + ( - sum([elt.size for elt in self.owr_aoa_ranging_measurements]) + - len(self.vendor_data) * 1 - ) - -@dataclass -class SessionStopCmd(SessionControlCommand): - - - def __post_init__(self): - self.opcode = 1 - self.gid = GroupId.SESSION_CONTROL - self.mt = MessageType.COMMAND - - @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionStopCmd', bytes]: - if fields['opcode'] != 0x1 or fields['gid'] != GroupId.SESSION_CONTROL or fields['mt'] != MessageType.COMMAND: - raise Exception("Invalid constraint field values") - return SessionStopCmd(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - return SessionControlCommand.serialize(self, payload = bytes(_span)) - - @property - def size(self) -> int: - return 0 - -@dataclass -class SessionStopRsp(SessionControlResponse): - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) - - def __post_init__(self): - self.opcode = 1 - self.gid = GroupId.SESSION_CONTROL - self.mt = MessageType.RESPONSE - - @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionStopRsp', bytes]: - if fields['opcode'] != 0x1 or fields['gid'] != GroupId.SESSION_CONTROL or fields['mt'] != MessageType.RESPONSE: - raise Exception("Invalid constraint field values") - if len(span) < 1: - raise Exception('Invalid packet size') - fields['status'] = StatusCode.from_int(span[0]) - span = span[1:] - return SessionStopRsp(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - _span.append((self.status << 0)) - return SessionControlResponse.serialize(self, payload = bytes(_span)) - - @property - def size(self) -> int: - return 1 - -@dataclass -class SessionGetRangingCountCmd(SessionControlCommand): - - - def __post_init__(self): - self.opcode = 3 - self.gid = GroupId.SESSION_CONTROL - self.mt = MessageType.COMMAND - - @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionGetRangingCountCmd', bytes]: - if fields['opcode'] != 0x3 or fields['gid'] != GroupId.SESSION_CONTROL or fields['mt'] != MessageType.COMMAND: - raise Exception("Invalid constraint field values") - return SessionGetRangingCountCmd(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - return SessionControlCommand.serialize(self, payload = bytes(_span)) - - @property - def size(self) -> int: - return 0 - -@dataclass -class SessionGetRangingCountRsp(SessionControlResponse): - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) - count: int = field(kw_only=True, default=0) - - def __post_init__(self): - self.opcode = 3 - self.gid = GroupId.SESSION_CONTROL - self.mt = MessageType.RESPONSE - - @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['SessionGetRangingCountRsp', bytes]: - if fields['opcode'] != 0x3 or fields['gid'] != GroupId.SESSION_CONTROL or fields['mt'] != MessageType.RESPONSE: - raise Exception("Invalid constraint field values") - if len(span) < 5: - raise Exception('Invalid packet size') - fields['status'] = StatusCode.from_int(span[0]) - value_ = int.from_bytes(span[1:5], byteorder='little') - fields['count'] = value_ - span = span[5:] - return SessionGetRangingCountRsp(**fields), span + value_ = int.from_bytes(span[0:2], byteorder='little') + fields['mac_address'] = value_ + fields['status'] = Status.from_int(span[2]) + fields['nlos'] = span[3] + value_ = int.from_bytes(span[4:6], byteorder='little') + fields['distance'] = value_ + value_ = int.from_bytes(span[6:8], byteorder='little') + fields['aoa_azimuth'] = value_ + fields['aoa_azimuth_fom'] = span[8] + value_ = int.from_bytes(span[9:11], byteorder='little') + fields['aoa_elevation'] = value_ + fields['aoa_elevation_fom'] = span[11] + value_ = int.from_bytes(span[12:14], byteorder='little') + fields['aoa_destination_azimuth'] = value_ + fields['aoa_destination_azimuth_fom'] = span[14] + value_ = int.from_bytes(span[15:17], byteorder='little') + fields['aoa_destination_elevation'] = value_ + fields['aoa_destination_elevation_fom'] = span[17] + fields['slot_index'] = span[18] + fields['rssi'] = span[19] + value_ = int.from_bytes(span[20:28], byteorder='little') + value_ = int.from_bytes(span[28:31], byteorder='little') + span = span[31:] + return ShortAddressTwoWayRangingMeasurement(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() + if self.mac_address > 65535: + print(f"Invalid value for field ShortAddressTwoWayRangingMeasurement::mac_address: {self.mac_address} > 65535; the value will be truncated") + self.mac_address &= 65535 + _span.extend(int.to_bytes((self.mac_address << 0), length=2, byteorder='little')) _span.append((self.status << 0)) - if self.count > 4294967295: - print(f"Invalid value for field SessionGetRangingCountRsp::count: {self.count} > 4294967295; the value will be truncated") - self.count &= 4294967295 - _span.extend(int.to_bytes((self.count << 0), length=4, byteorder='little')) - return SessionControlResponse.serialize(self, payload = bytes(_span)) - - @property - def size(self) -> int: - return 5 - -@dataclass -class AndroidGetPowerStatsCmd(AndroidCommand): - - - def __post_init__(self): - self.opcode = 0 - self.gid = GroupId.VENDOR_ANDROID - self.mt = MessageType.COMMAND - - @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['AndroidGetPowerStatsCmd', bytes]: - if fields['opcode'] != 0x0 or fields['gid'] != GroupId.VENDOR_ANDROID or fields['mt'] != MessageType.COMMAND: - raise Exception("Invalid constraint field values") - return AndroidGetPowerStatsCmd(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - return AndroidCommand.serialize(self, payload = bytes(_span)) + if self.nlos > 255: + print(f"Invalid value for field ShortAddressTwoWayRangingMeasurement::nlos: {self.nlos} > 255; the value will be truncated") + self.nlos &= 255 + _span.append((self.nlos << 0)) + if self.distance > 65535: + print(f"Invalid value for field ShortAddressTwoWayRangingMeasurement::distance: {self.distance} > 65535; the value will be truncated") + self.distance &= 65535 + _span.extend(int.to_bytes((self.distance << 0), length=2, byteorder='little')) + if self.aoa_azimuth > 65535: + print(f"Invalid value for field ShortAddressTwoWayRangingMeasurement::aoa_azimuth: {self.aoa_azimuth} > 65535; the value will be truncated") + self.aoa_azimuth &= 65535 + _span.extend(int.to_bytes((self.aoa_azimuth << 0), length=2, byteorder='little')) + if self.aoa_azimuth_fom > 255: + print(f"Invalid value for field ShortAddressTwoWayRangingMeasurement::aoa_azimuth_fom: {self.aoa_azimuth_fom} > 255; the value will be truncated") + self.aoa_azimuth_fom &= 255 + _span.append((self.aoa_azimuth_fom << 0)) + if self.aoa_elevation > 65535: + print(f"Invalid value for field ShortAddressTwoWayRangingMeasurement::aoa_elevation: {self.aoa_elevation} > 65535; the value will be truncated") + self.aoa_elevation &= 65535 + _span.extend(int.to_bytes((self.aoa_elevation << 0), length=2, byteorder='little')) + if self.aoa_elevation_fom > 255: + print(f"Invalid value for field ShortAddressTwoWayRangingMeasurement::aoa_elevation_fom: {self.aoa_elevation_fom} > 255; the value will be truncated") + self.aoa_elevation_fom &= 255 + _span.append((self.aoa_elevation_fom << 0)) + if self.aoa_destination_azimuth > 65535: + print(f"Invalid value for field ShortAddressTwoWayRangingMeasurement::aoa_destination_azimuth: {self.aoa_destination_azimuth} > 65535; the value will be truncated") + self.aoa_destination_azimuth &= 65535 + _span.extend(int.to_bytes((self.aoa_destination_azimuth << 0), length=2, byteorder='little')) + if self.aoa_destination_azimuth_fom > 255: + print(f"Invalid value for field ShortAddressTwoWayRangingMeasurement::aoa_destination_azimuth_fom: {self.aoa_destination_azimuth_fom} > 255; the value will be truncated") + self.aoa_destination_azimuth_fom &= 255 + _span.append((self.aoa_destination_azimuth_fom << 0)) + if self.aoa_destination_elevation > 65535: + print(f"Invalid value for field ShortAddressTwoWayRangingMeasurement::aoa_destination_elevation: {self.aoa_destination_elevation} > 65535; the value will be truncated") + self.aoa_destination_elevation &= 65535 + _span.extend(int.to_bytes((self.aoa_destination_elevation << 0), length=2, byteorder='little')) + if self.aoa_destination_elevation_fom > 255: + print(f"Invalid value for field ShortAddressTwoWayRangingMeasurement::aoa_destination_elevation_fom: {self.aoa_destination_elevation_fom} > 255; the value will be truncated") + self.aoa_destination_elevation_fom &= 255 + _span.append((self.aoa_destination_elevation_fom << 0)) + if self.slot_index > 255: + print(f"Invalid value for field ShortAddressTwoWayRangingMeasurement::slot_index: {self.slot_index} > 255; the value will be truncated") + self.slot_index &= 255 + _span.append((self.slot_index << 0)) + if self.rssi > 255: + print(f"Invalid value for field ShortAddressTwoWayRangingMeasurement::rssi: {self.rssi} > 255; the value will be truncated") + self.rssi &= 255 + _span.append((self.rssi << 0)) + _span.extend([0] * 8) + _span.extend([0] * 3) + return bytes(_span) @property def size(self) -> int: - return 0 + return 31 @dataclass -class PowerStats(Packet): - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) - idle_time_ms: int = field(kw_only=True, default=0) - tx_time_ms: int = field(kw_only=True, default=0) - rx_time_ms: int = field(kw_only=True, default=0) - total_wake_count: int = field(kw_only=True, default=0) +class ExtendedAddressTwoWayRangingMeasurement(Packet): + mac_address: int = field(kw_only=True, default=0) + status: Status = field(kw_only=True, default=Status.OK) + nlos: int = field(kw_only=True, default=0) + distance: int = field(kw_only=True, default=0) + aoa_azimuth: int = field(kw_only=True, default=0) + aoa_azimuth_fom: int = field(kw_only=True, default=0) + aoa_elevation: int = field(kw_only=True, default=0) + aoa_elevation_fom: int = field(kw_only=True, default=0) + aoa_destination_azimuth: int = field(kw_only=True, default=0) + aoa_destination_azimuth_fom: int = field(kw_only=True, default=0) + aoa_destination_elevation: int = field(kw_only=True, default=0) + aoa_destination_elevation_fom: int = field(kw_only=True, default=0) + slot_index: int = field(kw_only=True, default=0) + rssi: int = field(kw_only=True, default=0) def __post_init__(self): pass @staticmethod - def parse(span: bytes) -> Tuple['PowerStats', bytes]: + def parse(span: bytes) -> Tuple['ExtendedAddressTwoWayRangingMeasurement', bytes]: fields = {'payload': None} - if len(span) < 17: + if len(span) < 31: raise Exception('Invalid packet size') - fields['status'] = StatusCode.from_int(span[0]) - value_ = int.from_bytes(span[1:5], byteorder='little') - fields['idle_time_ms'] = value_ - value_ = int.from_bytes(span[5:9], byteorder='little') - fields['tx_time_ms'] = value_ - value_ = int.from_bytes(span[9:13], byteorder='little') - fields['rx_time_ms'] = value_ - value_ = int.from_bytes(span[13:17], byteorder='little') - fields['total_wake_count'] = value_ - span = span[17:] - return PowerStats(**fields), span + value_ = int.from_bytes(span[0:8], byteorder='little') + fields['mac_address'] = value_ + fields['status'] = Status.from_int(span[8]) + fields['nlos'] = span[9] + value_ = int.from_bytes(span[10:12], byteorder='little') + fields['distance'] = value_ + value_ = int.from_bytes(span[12:14], byteorder='little') + fields['aoa_azimuth'] = value_ + fields['aoa_azimuth_fom'] = span[14] + value_ = int.from_bytes(span[15:17], byteorder='little') + fields['aoa_elevation'] = value_ + fields['aoa_elevation_fom'] = span[17] + value_ = int.from_bytes(span[18:20], byteorder='little') + fields['aoa_destination_azimuth'] = value_ + fields['aoa_destination_azimuth_fom'] = span[20] + value_ = int.from_bytes(span[21:23], byteorder='little') + fields['aoa_destination_elevation'] = value_ + fields['aoa_destination_elevation_fom'] = span[23] + fields['slot_index'] = span[24] + fields['rssi'] = span[25] + value_ = int.from_bytes(span[26:31], byteorder='little') + span = span[31:] + return ExtendedAddressTwoWayRangingMeasurement(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() + if self.mac_address > 18446744073709551615: + print(f"Invalid value for field ExtendedAddressTwoWayRangingMeasurement::mac_address: {self.mac_address} > 18446744073709551615; the value will be truncated") + self.mac_address &= 18446744073709551615 + _span.extend(int.to_bytes((self.mac_address << 0), length=8, byteorder='little')) _span.append((self.status << 0)) - if self.idle_time_ms > 4294967295: - print(f"Invalid value for field PowerStats::idle_time_ms: {self.idle_time_ms} > 4294967295; the value will be truncated") - self.idle_time_ms &= 4294967295 - _span.extend(int.to_bytes((self.idle_time_ms << 0), length=4, byteorder='little')) - if self.tx_time_ms > 4294967295: - print(f"Invalid value for field PowerStats::tx_time_ms: {self.tx_time_ms} > 4294967295; the value will be truncated") - self.tx_time_ms &= 4294967295 - _span.extend(int.to_bytes((self.tx_time_ms << 0), length=4, byteorder='little')) - if self.rx_time_ms > 4294967295: - print(f"Invalid value for field PowerStats::rx_time_ms: {self.rx_time_ms} > 4294967295; the value will be truncated") - self.rx_time_ms &= 4294967295 - _span.extend(int.to_bytes((self.rx_time_ms << 0), length=4, byteorder='little')) - if self.total_wake_count > 4294967295: - print(f"Invalid value for field PowerStats::total_wake_count: {self.total_wake_count} > 4294967295; the value will be truncated") - self.total_wake_count &= 4294967295 - _span.extend(int.to_bytes((self.total_wake_count << 0), length=4, byteorder='little')) + if self.nlos > 255: + print(f"Invalid value for field ExtendedAddressTwoWayRangingMeasurement::nlos: {self.nlos} > 255; the value will be truncated") + self.nlos &= 255 + _span.append((self.nlos << 0)) + if self.distance > 65535: + print(f"Invalid value for field ExtendedAddressTwoWayRangingMeasurement::distance: {self.distance} > 65535; the value will be truncated") + self.distance &= 65535 + _span.extend(int.to_bytes((self.distance << 0), length=2, byteorder='little')) + if self.aoa_azimuth > 65535: + print(f"Invalid value for field ExtendedAddressTwoWayRangingMeasurement::aoa_azimuth: {self.aoa_azimuth} > 65535; the value will be truncated") + self.aoa_azimuth &= 65535 + _span.extend(int.to_bytes((self.aoa_azimuth << 0), length=2, byteorder='little')) + if self.aoa_azimuth_fom > 255: + print(f"Invalid value for field ExtendedAddressTwoWayRangingMeasurement::aoa_azimuth_fom: {self.aoa_azimuth_fom} > 255; the value will be truncated") + self.aoa_azimuth_fom &= 255 + _span.append((self.aoa_azimuth_fom << 0)) + if self.aoa_elevation > 65535: + print(f"Invalid value for field ExtendedAddressTwoWayRangingMeasurement::aoa_elevation: {self.aoa_elevation} > 65535; the value will be truncated") + self.aoa_elevation &= 65535 + _span.extend(int.to_bytes((self.aoa_elevation << 0), length=2, byteorder='little')) + if self.aoa_elevation_fom > 255: + print(f"Invalid value for field ExtendedAddressTwoWayRangingMeasurement::aoa_elevation_fom: {self.aoa_elevation_fom} > 255; the value will be truncated") + self.aoa_elevation_fom &= 255 + _span.append((self.aoa_elevation_fom << 0)) + if self.aoa_destination_azimuth > 65535: + print(f"Invalid value for field ExtendedAddressTwoWayRangingMeasurement::aoa_destination_azimuth: {self.aoa_destination_azimuth} > 65535; the value will be truncated") + self.aoa_destination_azimuth &= 65535 + _span.extend(int.to_bytes((self.aoa_destination_azimuth << 0), length=2, byteorder='little')) + if self.aoa_destination_azimuth_fom > 255: + print(f"Invalid value for field ExtendedAddressTwoWayRangingMeasurement::aoa_destination_azimuth_fom: {self.aoa_destination_azimuth_fom} > 255; the value will be truncated") + self.aoa_destination_azimuth_fom &= 255 + _span.append((self.aoa_destination_azimuth_fom << 0)) + if self.aoa_destination_elevation > 65535: + print(f"Invalid value for field ExtendedAddressTwoWayRangingMeasurement::aoa_destination_elevation: {self.aoa_destination_elevation} > 65535; the value will be truncated") + self.aoa_destination_elevation &= 65535 + _span.extend(int.to_bytes((self.aoa_destination_elevation << 0), length=2, byteorder='little')) + if self.aoa_destination_elevation_fom > 255: + print(f"Invalid value for field ExtendedAddressTwoWayRangingMeasurement::aoa_destination_elevation_fom: {self.aoa_destination_elevation_fom} > 255; the value will be truncated") + self.aoa_destination_elevation_fom &= 255 + _span.append((self.aoa_destination_elevation_fom << 0)) + if self.slot_index > 255: + print(f"Invalid value for field ExtendedAddressTwoWayRangingMeasurement::slot_index: {self.slot_index} > 255; the value will be truncated") + self.slot_index &= 255 + _span.append((self.slot_index << 0)) + if self.rssi > 255: + print(f"Invalid value for field ExtendedAddressTwoWayRangingMeasurement::rssi: {self.rssi} > 255; the value will be truncated") + self.rssi &= 255 + _span.append((self.rssi << 0)) + _span.extend([0] * 5) return bytes(_span) @property def size(self) -> int: - return 17 - -@dataclass -class AndroidGetPowerStatsRsp(AndroidResponse): - stats: PowerStats = field(kw_only=True, default_factory=PowerStats) - - def __post_init__(self): - self.opcode = 0 - self.gid = GroupId.VENDOR_ANDROID - self.mt = MessageType.RESPONSE - - @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['AndroidGetPowerStatsRsp', bytes]: - if fields['opcode'] != 0x0 or fields['gid'] != GroupId.VENDOR_ANDROID or fields['mt'] != MessageType.RESPONSE: - raise Exception("Invalid constraint field values") - if len(span) < 17: - raise Exception('Invalid packet size') - fields['stats'] = PowerStats.parse_all(span[0:17]) - span = span[17:] - return AndroidGetPowerStatsRsp(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - _span.extend(self.stats.serialize()) - return AndroidResponse.serialize(self, payload = bytes(_span)) - - @property - def size(self) -> int: - return 17 + return 31 @dataclass -class AndroidSetCountryCodeCmd(AndroidCommand): - country_code: bytearray = field(kw_only=True, default_factory=bytearray) +class ShortAddressOwrAoaRangingMeasurement(Packet): + mac_address: int = field(kw_only=True, default=0) + status: Status = field(kw_only=True, default=Status.OK) + nlos: int = field(kw_only=True, default=0) + frame_sequence_number: int = field(kw_only=True, default=0) + block_index: int = field(kw_only=True, default=0) + aoa_azimuth: int = field(kw_only=True, default=0) + aoa_azimuth_fom: int = field(kw_only=True, default=0) + aoa_elevation: int = field(kw_only=True, default=0) + aoa_elevation_fom: int = field(kw_only=True, default=0) def __post_init__(self): - self.opcode = 1 - self.gid = GroupId.VENDOR_ANDROID - self.mt = MessageType.COMMAND + pass @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['AndroidSetCountryCodeCmd', bytes]: - if fields['opcode'] != 0x1 or fields['gid'] != GroupId.VENDOR_ANDROID or fields['mt'] != MessageType.COMMAND: - raise Exception("Invalid constraint field values") - if len(span) < 2: + def parse(span: bytes) -> Tuple['ShortAddressOwrAoaRangingMeasurement', bytes]: + fields = {'payload': None} + if len(span) < 13: raise Exception('Invalid packet size') - fields['country_code'] = list(span[:2]) - span = span[2:] - return AndroidSetCountryCodeCmd(**fields), span + value_ = int.from_bytes(span[0:2], byteorder='little') + fields['mac_address'] = value_ + fields['status'] = Status.from_int(span[2]) + fields['nlos'] = span[3] + fields['frame_sequence_number'] = span[4] + value_ = int.from_bytes(span[5:7], byteorder='little') + fields['block_index'] = value_ + value_ = int.from_bytes(span[7:9], byteorder='little') + fields['aoa_azimuth'] = value_ + fields['aoa_azimuth_fom'] = span[9] + value_ = int.from_bytes(span[10:12], byteorder='little') + fields['aoa_elevation'] = value_ + fields['aoa_elevation_fom'] = span[12] + span = span[13:] + return ShortAddressOwrAoaRangingMeasurement(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(self.country_code) - return AndroidCommand.serialize(self, payload = bytes(_span)) + if self.mac_address > 65535: + print(f"Invalid value for field ShortAddressOwrAoaRangingMeasurement::mac_address: {self.mac_address} > 65535; the value will be truncated") + self.mac_address &= 65535 + _span.extend(int.to_bytes((self.mac_address << 0), length=2, byteorder='little')) + _span.append((self.status << 0)) + if self.nlos > 255: + print(f"Invalid value for field ShortAddressOwrAoaRangingMeasurement::nlos: {self.nlos} > 255; the value will be truncated") + self.nlos &= 255 + _span.append((self.nlos << 0)) + if self.frame_sequence_number > 255: + print(f"Invalid value for field ShortAddressOwrAoaRangingMeasurement::frame_sequence_number: {self.frame_sequence_number} > 255; the value will be truncated") + self.frame_sequence_number &= 255 + _span.append((self.frame_sequence_number << 0)) + if self.block_index > 65535: + print(f"Invalid value for field ShortAddressOwrAoaRangingMeasurement::block_index: {self.block_index} > 65535; the value will be truncated") + self.block_index &= 65535 + _span.extend(int.to_bytes((self.block_index << 0), length=2, byteorder='little')) + if self.aoa_azimuth > 65535: + print(f"Invalid value for field ShortAddressOwrAoaRangingMeasurement::aoa_azimuth: {self.aoa_azimuth} > 65535; the value will be truncated") + self.aoa_azimuth &= 65535 + _span.extend(int.to_bytes((self.aoa_azimuth << 0), length=2, byteorder='little')) + if self.aoa_azimuth_fom > 255: + print(f"Invalid value for field ShortAddressOwrAoaRangingMeasurement::aoa_azimuth_fom: {self.aoa_azimuth_fom} > 255; the value will be truncated") + self.aoa_azimuth_fom &= 255 + _span.append((self.aoa_azimuth_fom << 0)) + if self.aoa_elevation > 65535: + print(f"Invalid value for field ShortAddressOwrAoaRangingMeasurement::aoa_elevation: {self.aoa_elevation} > 65535; the value will be truncated") + self.aoa_elevation &= 65535 + _span.extend(int.to_bytes((self.aoa_elevation << 0), length=2, byteorder='little')) + if self.aoa_elevation_fom > 255: + print(f"Invalid value for field ShortAddressOwrAoaRangingMeasurement::aoa_elevation_fom: {self.aoa_elevation_fom} > 255; the value will be truncated") + self.aoa_elevation_fom &= 255 + _span.append((self.aoa_elevation_fom << 0)) + return bytes(_span) @property def size(self) -> int: - return 2 + return 13 @dataclass -class AndroidSetCountryCodeRsp(AndroidResponse): - status: StatusCode = field(kw_only=True, default=StatusCode.UCI_STATUS_OK) +class ExtendedAddressOwrAoaRangingMeasurement(Packet): + mac_address: int = field(kw_only=True, default=0) + status: Status = field(kw_only=True, default=Status.OK) + nlos: int = field(kw_only=True, default=0) + frame_sequence_number: int = field(kw_only=True, default=0) + block_index: int = field(kw_only=True, default=0) + aoa_azimuth: int = field(kw_only=True, default=0) + aoa_azimuth_fom: int = field(kw_only=True, default=0) + aoa_elevation: int = field(kw_only=True, default=0) + aoa_elevation_fom: int = field(kw_only=True, default=0) def __post_init__(self): - self.opcode = 1 - self.gid = GroupId.VENDOR_ANDROID - self.mt = MessageType.RESPONSE + pass @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['AndroidSetCountryCodeRsp', bytes]: - if fields['opcode'] != 0x1 or fields['gid'] != GroupId.VENDOR_ANDROID or fields['mt'] != MessageType.RESPONSE: - raise Exception("Invalid constraint field values") - if len(span) < 1: + def parse(span: bytes) -> Tuple['ExtendedAddressOwrAoaRangingMeasurement', bytes]: + fields = {'payload': None} + if len(span) < 19: raise Exception('Invalid packet size') - fields['status'] = StatusCode.from_int(span[0]) - span = span[1:] - return AndroidSetCountryCodeRsp(**fields), span + value_ = int.from_bytes(span[0:8], byteorder='little') + fields['mac_address'] = value_ + fields['status'] = Status.from_int(span[8]) + fields['nlos'] = span[9] + fields['frame_sequence_number'] = span[10] + value_ = int.from_bytes(span[11:13], byteorder='little') + fields['block_index'] = value_ + value_ = int.from_bytes(span[13:15], byteorder='little') + fields['aoa_azimuth'] = value_ + fields['aoa_azimuth_fom'] = span[15] + value_ = int.from_bytes(span[16:18], byteorder='little') + fields['aoa_elevation'] = value_ + fields['aoa_elevation_fom'] = span[18] + span = span[19:] + return ExtendedAddressOwrAoaRangingMeasurement(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() + if self.mac_address > 18446744073709551615: + print(f"Invalid value for field ExtendedAddressOwrAoaRangingMeasurement::mac_address: {self.mac_address} > 18446744073709551615; the value will be truncated") + self.mac_address &= 18446744073709551615 + _span.extend(int.to_bytes((self.mac_address << 0), length=8, byteorder='little')) _span.append((self.status << 0)) - return AndroidResponse.serialize(self, payload = bytes(_span)) + if self.nlos > 255: + print(f"Invalid value for field ExtendedAddressOwrAoaRangingMeasurement::nlos: {self.nlos} > 255; the value will be truncated") + self.nlos &= 255 + _span.append((self.nlos << 0)) + if self.frame_sequence_number > 255: + print(f"Invalid value for field ExtendedAddressOwrAoaRangingMeasurement::frame_sequence_number: {self.frame_sequence_number} > 255; the value will be truncated") + self.frame_sequence_number &= 255 + _span.append((self.frame_sequence_number << 0)) + if self.block_index > 65535: + print(f"Invalid value for field ExtendedAddressOwrAoaRangingMeasurement::block_index: {self.block_index} > 65535; the value will be truncated") + self.block_index &= 65535 + _span.extend(int.to_bytes((self.block_index << 0), length=2, byteorder='little')) + if self.aoa_azimuth > 65535: + print(f"Invalid value for field ExtendedAddressOwrAoaRangingMeasurement::aoa_azimuth: {self.aoa_azimuth} > 65535; the value will be truncated") + self.aoa_azimuth &= 65535 + _span.extend(int.to_bytes((self.aoa_azimuth << 0), length=2, byteorder='little')) + if self.aoa_azimuth_fom > 255: + print(f"Invalid value for field ExtendedAddressOwrAoaRangingMeasurement::aoa_azimuth_fom: {self.aoa_azimuth_fom} > 255; the value will be truncated") + self.aoa_azimuth_fom &= 255 + _span.append((self.aoa_azimuth_fom << 0)) + if self.aoa_elevation > 65535: + print(f"Invalid value for field ExtendedAddressOwrAoaRangingMeasurement::aoa_elevation: {self.aoa_elevation} > 65535; the value will be truncated") + self.aoa_elevation &= 65535 + _span.extend(int.to_bytes((self.aoa_elevation << 0), length=2, byteorder='little')) + if self.aoa_elevation_fom > 255: + print(f"Invalid value for field ExtendedAddressOwrAoaRangingMeasurement::aoa_elevation_fom: {self.aoa_elevation_fom} > 255; the value will be truncated") + self.aoa_elevation_fom &= 255 + _span.append((self.aoa_elevation_fom << 0)) + return bytes(_span) @property def size(self) -> int: - return 1 + return 19 -class FrameReportTlvType(enum.IntEnum): - RSSI = 0x0 - AOA = 0x1 - CIR = 0x2 +class RangingMeasurementType(enum.IntEnum): + ONE_WAY = 0x0 + TWO_WAY = 0x1 + DL_TDOA = 0x2 + OWR_AOA = 0x3 @staticmethod - def from_int(v: int) -> Union[int, 'FrameReportTlvType']: + def from_int(v: int) -> Union[int, 'RangingMeasurementType']: try: - return FrameReportTlvType(v) + return RangingMeasurementType(v) except ValueError as exn: raise exn @dataclass -class FrameReportTlv(Packet): - t: FrameReportTlvType = field(kw_only=True, default=FrameReportTlvType.RSSI) - v: bytearray = field(kw_only=True, default_factory=bytearray) - - def __post_init__(self): - pass - - @staticmethod - def parse(span: bytes) -> Tuple['FrameReportTlv', bytes]: - fields = {'payload': None} - if len(span) < 3: - raise Exception('Invalid packet size') - fields['t'] = FrameReportTlvType.from_int(span[0]) - value_ = int.from_bytes(span[1:3], byteorder='little') - v_size = value_ - span = span[3:] - if len(span) < v_size: - raise Exception('Invalid packet size') - fields['v'] = list(span[:v_size]) - span = span[v_size:] - return FrameReportTlv(**fields), span - - def serialize(self, payload: bytes = None) -> bytes: - _span = bytearray() - _span.append((self.t << 0)) - _span.extend(int.to_bytes(((len(self.v) * 1) << 0), length=2, byteorder='little')) - _span.extend(self.v) - return bytes(_span) - - @property - def size(self) -> int: - return len(self.v) * 1 + 3 - -@dataclass -class FrameReportTlvPacket(Packet): - t: FrameReportTlvType = field(kw_only=True, default=FrameReportTlvType.RSSI) +class SessionInfoNtf(SessionControlPacket): + sequence_number: int = field(kw_only=True, default=0) + session_token: int = field(kw_only=True, default=0) + rcr_indicator: int = field(kw_only=True, default=0) + current_ranging_interval: int = field(kw_only=True, default=0) + ranging_measurement_type: RangingMeasurementType = field(kw_only=True, default=RangingMeasurementType.ONE_WAY) + mac_address_indicator: MacAddressIndicator = field(kw_only=True, default=MacAddressIndicator.SHORT_ADDRESS) def __post_init__(self): - pass + self.mt = MessageType.NOTIFICATION + self.oid = SessionControlOpcodeId.START + self.gid = GroupId.SESSION_CONTROL @staticmethod - def parse(span: bytes) -> Tuple['FrameReportTlvPacket', bytes]: - fields = {'payload': None} - if len(span) < 3: - raise Exception('Invalid packet size') - fields['t'] = FrameReportTlvType.from_int(span[0]) - value_ = int.from_bytes(span[1:3], byteorder='little') - _body__size = value_ - span = span[3:] - if len(span) < _body__size: + def parse(fields: dict, span: bytes) -> Tuple['SessionInfoNtf', bytes]: + if fields['mt'] != MessageType.NOTIFICATION or fields['oid'] != SessionControlOpcodeId.START or fields['gid'] != GroupId.SESSION_CONTROL: + raise Exception("Invalid constraint field values") + if len(span) < 24: raise Exception('Invalid packet size') - payload = span[:_body__size] - span = span[_body__size:] + value_ = int.from_bytes(span[0:4], byteorder='little') + fields['sequence_number'] = value_ + value_ = int.from_bytes(span[4:8], byteorder='little') + fields['session_token'] = value_ + fields['rcr_indicator'] = span[8] + value_ = int.from_bytes(span[9:13], byteorder='little') + fields['current_ranging_interval'] = value_ + fields['ranging_measurement_type'] = RangingMeasurementType.from_int(span[13]) + fields['mac_address_indicator'] = MacAddressIndicator.from_int(span[15]) + value_ = int.from_bytes(span[16:24], byteorder='little') + span = span[24:] + payload = span + span = bytes([]) fields['payload'] = payload try: - return Rssi.parse(fields.copy(), payload) + return ShortMacTwoWaySessionInfoNtf.parse(fields.copy(), payload) except Exception as exn: pass try: - return Aoa.parse(fields.copy(), payload) + return ExtendedMacTwoWaySessionInfoNtf.parse(fields.copy(), payload) except Exception as exn: pass try: - return Cir.parse(fields.copy(), payload) + return ShortMacDlTDoASessionInfoNtf.parse(fields.copy(), payload) except Exception as exn: pass - return FrameReportTlvPacket(**fields), span + try: + return ExtendedMacDlTDoASessionInfoNtf.parse(fields.copy(), payload) + except Exception as exn: + pass + try: + return ShortMacOwrAoaSessionInfoNtf.parse(fields.copy(), payload) + except Exception as exn: + pass + try: + return ExtendedMacOwrAoaSessionInfoNtf.parse(fields.copy(), payload) + except Exception as exn: + pass + return SessionInfoNtf(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.append((self.t << 0)) - _payload_size = len(payload or self.payload or []) - if _payload_size > 65535: - print(f"Invalid length for payload field: {_payload_size} > 65535; the packet cannot be generated") - raise Exception("Invalid payload length") - _span.extend(int.to_bytes((_payload_size << 0), length=2, byteorder='little')) + if self.sequence_number > 4294967295: + print(f"Invalid value for field SessionInfoNtf::sequence_number: {self.sequence_number} > 4294967295; the value will be truncated") + self.sequence_number &= 4294967295 + _span.extend(int.to_bytes((self.sequence_number << 0), length=4, byteorder='little')) + if self.session_token > 4294967295: + print(f"Invalid value for field SessionInfoNtf::session_token: {self.session_token} > 4294967295; the value will be truncated") + self.session_token &= 4294967295 + _span.extend(int.to_bytes((self.session_token << 0), length=4, byteorder='little')) + if self.rcr_indicator > 255: + print(f"Invalid value for field SessionInfoNtf::rcr_indicator: {self.rcr_indicator} > 255; the value will be truncated") + self.rcr_indicator &= 255 + _span.append((self.rcr_indicator << 0)) + if self.current_ranging_interval > 4294967295: + print(f"Invalid value for field SessionInfoNtf::current_ranging_interval: {self.current_ranging_interval} > 4294967295; the value will be truncated") + self.current_ranging_interval &= 4294967295 + _span.extend(int.to_bytes((self.current_ranging_interval << 0), length=4, byteorder='little')) + _span.append((self.ranging_measurement_type << 0)) + _span.extend([0] * 1) + _span.append((self.mac_address_indicator << 0)) + _span.extend([0] * 8) _span.extend(payload or self.payload or []) - return bytes(_span) + return SessionControlPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.payload) + 3 + return len(self.payload) + 24 @dataclass -class Rssi(FrameReportTlvPacket): - rssi: bytearray = field(kw_only=True, default_factory=bytearray) +class ShortMacTwoWaySessionInfoNtf(SessionInfoNtf): + two_way_ranging_measurements: List[ShortAddressTwoWayRangingMeasurement] = field(kw_only=True, default_factory=list) + vendor_data: bytearray = field(kw_only=True, default_factory=bytearray) def __post_init__(self): - self.t = FrameReportTlvType.RSSI + self.ranging_measurement_type = RangingMeasurementType.TWO_WAY + self.mac_address_indicator = MacAddressIndicator.SHORT_ADDRESS + self.mt = MessageType.NOTIFICATION + self.oid = SessionControlOpcodeId.START + self.gid = GroupId.SESSION_CONTROL @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['Rssi', bytes]: - if fields['t'] != FrameReportTlvType.RSSI: + def parse(fields: dict, span: bytes) -> Tuple['ShortMacTwoWaySessionInfoNtf', bytes]: + if fields['ranging_measurement_type'] != RangingMeasurementType.TWO_WAY or fields['mac_address_indicator'] != MacAddressIndicator.SHORT_ADDRESS or fields['mt'] != MessageType.NOTIFICATION or fields['oid'] != SessionControlOpcodeId.START or fields['gid'] != GroupId.SESSION_CONTROL: raise Exception("Invalid constraint field values") - fields['rssi'] = list(span) + if len(span) < 1: + raise Exception('Invalid packet size') + two_way_ranging_measurements_count = span[0] + span = span[1:] + if len(span) < two_way_ranging_measurements_count * 31: + raise Exception('Invalid packet size') + two_way_ranging_measurements = [] + for n in range(two_way_ranging_measurements_count): + two_way_ranging_measurements.append(ShortAddressTwoWayRangingMeasurement.parse_all(span[n * 31:(n + 1) * 31])) + fields['two_way_ranging_measurements'] = two_way_ranging_measurements + span = span[two_way_ranging_measurements_count * 31:] + fields['vendor_data'] = list(span) span = bytes() - return Rssi(**fields), span + return ShortMacTwoWaySessionInfoNtf(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(self.rssi) - return FrameReportTlvPacket.serialize(self, payload = bytes(_span)) + if len(self.two_way_ranging_measurements) > 255: + print(f"Invalid length for field ShortMacTwoWaySessionInfoNtf::two_way_ranging_measurements: {len(self.two_way_ranging_measurements)} > 255; the array will be truncated") + del self.two_way_ranging_measurements[255:] + _span.append((len(self.two_way_ranging_measurements) << 0)) + for _elt in self.two_way_ranging_measurements: + _span.extend(_elt.serialize()) + _span.extend(self.vendor_data) + return SessionInfoNtf.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.rssi) * 1 + return 1 + ( + sum([elt.size for elt in self.two_way_ranging_measurements]) + + len(self.vendor_data) * 1 + ) @dataclass -class AoaMeasurement(Packet): - tdoa: int = field(kw_only=True, default=0) - pdoa: int = field(kw_only=True, default=0) - aoa: int = field(kw_only=True, default=0) - fom: int = field(kw_only=True, default=0) - t: int = field(kw_only=True, default=0) +class ExtendedMacTwoWaySessionInfoNtf(SessionInfoNtf): + two_way_ranging_measurements: List[ExtendedAddressTwoWayRangingMeasurement] = field(kw_only=True, default_factory=list) + vendor_data: bytearray = field(kw_only=True, default_factory=bytearray) def __post_init__(self): - pass + self.ranging_measurement_type = RangingMeasurementType.TWO_WAY + self.mac_address_indicator = MacAddressIndicator.EXTENDED_ADDRESS + self.mt = MessageType.NOTIFICATION + self.oid = SessionControlOpcodeId.START + self.gid = GroupId.SESSION_CONTROL @staticmethod - def parse(span: bytes) -> Tuple['AoaMeasurement', bytes]: - fields = {'payload': None} - if len(span) < 8: + def parse(fields: dict, span: bytes) -> Tuple['ExtendedMacTwoWaySessionInfoNtf', bytes]: + if fields['ranging_measurement_type'] != RangingMeasurementType.TWO_WAY or fields['mac_address_indicator'] != MacAddressIndicator.EXTENDED_ADDRESS or fields['mt'] != MessageType.NOTIFICATION or fields['oid'] != SessionControlOpcodeId.START or fields['gid'] != GroupId.SESSION_CONTROL: + raise Exception("Invalid constraint field values") + if len(span) < 1: raise Exception('Invalid packet size') - value_ = int.from_bytes(span[0:2], byteorder='little') - fields['tdoa'] = value_ - value_ = int.from_bytes(span[2:4], byteorder='little') - fields['pdoa'] = value_ - value_ = int.from_bytes(span[4:6], byteorder='little') - fields['aoa'] = value_ - fields['fom'] = span[6] - fields['t'] = span[7] - span = span[8:] - return AoaMeasurement(**fields), span + two_way_ranging_measurements_count = span[0] + span = span[1:] + if len(span) < two_way_ranging_measurements_count * 31: + raise Exception('Invalid packet size') + two_way_ranging_measurements = [] + for n in range(two_way_ranging_measurements_count): + two_way_ranging_measurements.append(ExtendedAddressTwoWayRangingMeasurement.parse_all(span[n * 31:(n + 1) * 31])) + fields['two_way_ranging_measurements'] = two_way_ranging_measurements + span = span[two_way_ranging_measurements_count * 31:] + fields['vendor_data'] = list(span) + span = bytes() + return ExtendedMacTwoWaySessionInfoNtf(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - if self.tdoa > 65535: - print(f"Invalid value for field AoaMeasurement::tdoa: {self.tdoa} > 65535; the value will be truncated") - self.tdoa &= 65535 - _span.extend(int.to_bytes((self.tdoa << 0), length=2, byteorder='little')) - if self.pdoa > 65535: - print(f"Invalid value for field AoaMeasurement::pdoa: {self.pdoa} > 65535; the value will be truncated") - self.pdoa &= 65535 - _span.extend(int.to_bytes((self.pdoa << 0), length=2, byteorder='little')) - if self.aoa > 65535: - print(f"Invalid value for field AoaMeasurement::aoa: {self.aoa} > 65535; the value will be truncated") - self.aoa &= 65535 - _span.extend(int.to_bytes((self.aoa << 0), length=2, byteorder='little')) - if self.fom > 255: - print(f"Invalid value for field AoaMeasurement::fom: {self.fom} > 255; the value will be truncated") - self.fom &= 255 - _span.append((self.fom << 0)) - if self.t > 255: - print(f"Invalid value for field AoaMeasurement::t: {self.t} > 255; the value will be truncated") - self.t &= 255 - _span.append((self.t << 0)) - return bytes(_span) + if len(self.two_way_ranging_measurements) > 255: + print(f"Invalid length for field ExtendedMacTwoWaySessionInfoNtf::two_way_ranging_measurements: {len(self.two_way_ranging_measurements)} > 255; the array will be truncated") + del self.two_way_ranging_measurements[255:] + _span.append((len(self.two_way_ranging_measurements) << 0)) + for _elt in self.two_way_ranging_measurements: + _span.extend(_elt.serialize()) + _span.extend(self.vendor_data) + return SessionInfoNtf.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return 8 + return 1 + ( + sum([elt.size for elt in self.two_way_ranging_measurements]) + + len(self.vendor_data) * 1 + ) @dataclass -class Aoa(FrameReportTlvPacket): - aoa: List[AoaMeasurement] = field(kw_only=True, default_factory=list) +class ShortMacDlTDoASessionInfoNtf(SessionInfoNtf): + no_of_ranging_measurements: int = field(kw_only=True, default=0) + dl_tdoa_measurements: bytearray = field(kw_only=True, default_factory=bytearray) def __post_init__(self): - self.t = FrameReportTlvType.AOA + self.ranging_measurement_type = RangingMeasurementType.DL_TDOA + self.mac_address_indicator = MacAddressIndicator.SHORT_ADDRESS + self.mt = MessageType.NOTIFICATION + self.oid = SessionControlOpcodeId.START + self.gid = GroupId.SESSION_CONTROL @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['Aoa', bytes]: - if fields['t'] != FrameReportTlvType.AOA: + def parse(fields: dict, span: bytes) -> Tuple['ShortMacDlTDoASessionInfoNtf', bytes]: + if fields['ranging_measurement_type'] != RangingMeasurementType.DL_TDOA or fields['mac_address_indicator'] != MacAddressIndicator.SHORT_ADDRESS or fields['mt'] != MessageType.NOTIFICATION or fields['oid'] != SessionControlOpcodeId.START or fields['gid'] != GroupId.SESSION_CONTROL: raise Exception("Invalid constraint field values") - if len(span) % 8 != 0: - raise Exception('Array size is not a multiple of the element size') - aoa_count = int(len(span) / 8) - aoa = [] - for n in range(aoa_count): - aoa.append(AoaMeasurement.parse_all(span[n * 8:(n + 1) * 8])) - fields['aoa'] = aoa + if len(span) < 1: + raise Exception('Invalid packet size') + fields['no_of_ranging_measurements'] = span[0] + span = span[1:] + fields['dl_tdoa_measurements'] = list(span) span = bytes() - return Aoa(**fields), span + return ShortMacDlTDoASessionInfoNtf(**fields), span + + def serialize(self, payload: bytes = None) -> bytes: + _span = bytearray() + if self.no_of_ranging_measurements > 255: + print(f"Invalid value for field ShortMacDlTDoASessionInfoNtf::no_of_ranging_measurements: {self.no_of_ranging_measurements} > 255; the value will be truncated") + self.no_of_ranging_measurements &= 255 + _span.append((self.no_of_ranging_measurements << 0)) + _span.extend(self.dl_tdoa_measurements) + return SessionInfoNtf.serialize(self, payload = bytes(_span)) + + @property + def size(self) -> int: + return len(self.dl_tdoa_measurements) * 1 + 1 + +@dataclass +class ExtendedMacDlTDoASessionInfoNtf(SessionInfoNtf): + no_of_ranging_measurements: int = field(kw_only=True, default=0) + dl_tdoa_measurements: bytearray = field(kw_only=True, default_factory=bytearray) + + def __post_init__(self): + self.ranging_measurement_type = RangingMeasurementType.DL_TDOA + self.mac_address_indicator = MacAddressIndicator.EXTENDED_ADDRESS + self.mt = MessageType.NOTIFICATION + self.oid = SessionControlOpcodeId.START + self.gid = GroupId.SESSION_CONTROL + + @staticmethod + def parse(fields: dict, span: bytes) -> Tuple['ExtendedMacDlTDoASessionInfoNtf', bytes]: + if fields['ranging_measurement_type'] != RangingMeasurementType.DL_TDOA or fields['mac_address_indicator'] != MacAddressIndicator.EXTENDED_ADDRESS or fields['mt'] != MessageType.NOTIFICATION or fields['oid'] != SessionControlOpcodeId.START or fields['gid'] != GroupId.SESSION_CONTROL: + raise Exception("Invalid constraint field values") + if len(span) < 1: + raise Exception('Invalid packet size') + fields['no_of_ranging_measurements'] = span[0] + span = span[1:] + fields['dl_tdoa_measurements'] = list(span) + span = bytes() + return ExtendedMacDlTDoASessionInfoNtf(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - for _elt in self.aoa: - _span.extend(_elt.serialize()) - return FrameReportTlvPacket.serialize(self, payload = bytes(_span)) + if self.no_of_ranging_measurements > 255: + print(f"Invalid value for field ExtendedMacDlTDoASessionInfoNtf::no_of_ranging_measurements: {self.no_of_ranging_measurements} > 255; the value will be truncated") + self.no_of_ranging_measurements &= 255 + _span.append((self.no_of_ranging_measurements << 0)) + _span.extend(self.dl_tdoa_measurements) + return SessionInfoNtf.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return sum([elt.size for elt in self.aoa]) + return len(self.dl_tdoa_measurements) * 1 + 1 @dataclass -class CirValue(Packet): - first_path_index: int = field(kw_only=True, default=0) - first_path_snr: int = field(kw_only=True, default=0) - first_path_ns: int = field(kw_only=True, default=0) - peak_path_index: int = field(kw_only=True, default=0) - peak_path_snr: int = field(kw_only=True, default=0) - peak_path_ns: int = field(kw_only=True, default=0) - first_path_sample_offset: int = field(kw_only=True, default=0) - samples_number: int = field(kw_only=True, default=0) - sample_window: bytearray = field(kw_only=True, default_factory=bytearray) +class ShortMacOwrAoaSessionInfoNtf(SessionInfoNtf): + owr_aoa_ranging_measurements: List[ShortAddressOwrAoaRangingMeasurement] = field(kw_only=True, default_factory=list) + vendor_data: bytearray = field(kw_only=True, default_factory=bytearray) def __post_init__(self): - pass + self.ranging_measurement_type = RangingMeasurementType.OWR_AOA + self.mac_address_indicator = MacAddressIndicator.SHORT_ADDRESS + self.mt = MessageType.NOTIFICATION + self.oid = SessionControlOpcodeId.START + self.gid = GroupId.SESSION_CONTROL @staticmethod - def parse(span: bytes) -> Tuple['CirValue', bytes]: - fields = {'payload': None} - if len(span) < 16: + def parse(fields: dict, span: bytes) -> Tuple['ShortMacOwrAoaSessionInfoNtf', bytes]: + if fields['ranging_measurement_type'] != RangingMeasurementType.OWR_AOA or fields['mac_address_indicator'] != MacAddressIndicator.SHORT_ADDRESS or fields['mt'] != MessageType.NOTIFICATION or fields['oid'] != SessionControlOpcodeId.START or fields['gid'] != GroupId.SESSION_CONTROL: + raise Exception("Invalid constraint field values") + if len(span) < 1: raise Exception('Invalid packet size') - value_ = int.from_bytes(span[0:2], byteorder='little') - fields['first_path_index'] = value_ - value_ = int.from_bytes(span[2:4], byteorder='little') - fields['first_path_snr'] = value_ - value_ = int.from_bytes(span[4:6], byteorder='little') - fields['first_path_ns'] = value_ - value_ = int.from_bytes(span[6:8], byteorder='little') - fields['peak_path_index'] = value_ - value_ = int.from_bytes(span[8:10], byteorder='little') - fields['peak_path_snr'] = value_ - value_ = int.from_bytes(span[10:12], byteorder='little') - fields['peak_path_ns'] = value_ - fields['first_path_sample_offset'] = span[12] - fields['samples_number'] = span[13] - value_ = int.from_bytes(span[14:16], byteorder='little') - sample_window_size = value_ - span = span[16:] - if len(span) < sample_window_size: + owr_aoa_ranging_measurements_count = span[0] + span = span[1:] + if len(span) < owr_aoa_ranging_measurements_count * 13: raise Exception('Invalid packet size') - fields['sample_window'] = list(span[:sample_window_size]) - span = span[sample_window_size:] - return CirValue(**fields), span + owr_aoa_ranging_measurements = [] + for n in range(owr_aoa_ranging_measurements_count): + owr_aoa_ranging_measurements.append(ShortAddressOwrAoaRangingMeasurement.parse_all(span[n * 13:(n + 1) * 13])) + fields['owr_aoa_ranging_measurements'] = owr_aoa_ranging_measurements + span = span[owr_aoa_ranging_measurements_count * 13:] + fields['vendor_data'] = list(span) + span = bytes() + return ShortMacOwrAoaSessionInfoNtf(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - if self.first_path_index > 65535: - print(f"Invalid value for field CirValue::first_path_index: {self.first_path_index} > 65535; the value will be truncated") - self.first_path_index &= 65535 - _span.extend(int.to_bytes((self.first_path_index << 0), length=2, byteorder='little')) - if self.first_path_snr > 65535: - print(f"Invalid value for field CirValue::first_path_snr: {self.first_path_snr} > 65535; the value will be truncated") - self.first_path_snr &= 65535 - _span.extend(int.to_bytes((self.first_path_snr << 0), length=2, byteorder='little')) - if self.first_path_ns > 65535: - print(f"Invalid value for field CirValue::first_path_ns: {self.first_path_ns} > 65535; the value will be truncated") - self.first_path_ns &= 65535 - _span.extend(int.to_bytes((self.first_path_ns << 0), length=2, byteorder='little')) - if self.peak_path_index > 65535: - print(f"Invalid value for field CirValue::peak_path_index: {self.peak_path_index} > 65535; the value will be truncated") - self.peak_path_index &= 65535 - _span.extend(int.to_bytes((self.peak_path_index << 0), length=2, byteorder='little')) - if self.peak_path_snr > 65535: - print(f"Invalid value for field CirValue::peak_path_snr: {self.peak_path_snr} > 65535; the value will be truncated") - self.peak_path_snr &= 65535 - _span.extend(int.to_bytes((self.peak_path_snr << 0), length=2, byteorder='little')) - if self.peak_path_ns > 65535: - print(f"Invalid value for field CirValue::peak_path_ns: {self.peak_path_ns} > 65535; the value will be truncated") - self.peak_path_ns &= 65535 - _span.extend(int.to_bytes((self.peak_path_ns << 0), length=2, byteorder='little')) - if self.first_path_sample_offset > 255: - print(f"Invalid value for field CirValue::first_path_sample_offset: {self.first_path_sample_offset} > 255; the value will be truncated") - self.first_path_sample_offset &= 255 - _span.append((self.first_path_sample_offset << 0)) - if self.samples_number > 255: - print(f"Invalid value for field CirValue::samples_number: {self.samples_number} > 255; the value will be truncated") - self.samples_number &= 255 - _span.append((self.samples_number << 0)) - _span.extend(int.to_bytes(((len(self.sample_window) * 1) << 0), length=2, byteorder='little')) - _span.extend(self.sample_window) - return bytes(_span) + if len(self.owr_aoa_ranging_measurements) > 255: + print(f"Invalid length for field ShortMacOwrAoaSessionInfoNtf::owr_aoa_ranging_measurements: {len(self.owr_aoa_ranging_measurements)} > 255; the array will be truncated") + del self.owr_aoa_ranging_measurements[255:] + _span.append((len(self.owr_aoa_ranging_measurements) << 0)) + for _elt in self.owr_aoa_ranging_measurements: + _span.extend(_elt.serialize()) + _span.extend(self.vendor_data) + return SessionInfoNtf.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.sample_window) * 1 + 16 + return 1 + ( + sum([elt.size for elt in self.owr_aoa_ranging_measurements]) + + len(self.vendor_data) * 1 + ) @dataclass -class Cir(FrameReportTlvPacket): - cir_value: List[CirValue] = field(kw_only=True, default_factory=list) +class ExtendedMacOwrAoaSessionInfoNtf(SessionInfoNtf): + owr_aoa_ranging_measurements: List[ExtendedAddressOwrAoaRangingMeasurement] = field(kw_only=True, default_factory=list) + vendor_data: bytearray = field(kw_only=True, default_factory=bytearray) def __post_init__(self): - self.t = FrameReportTlvType.CIR + self.ranging_measurement_type = RangingMeasurementType.OWR_AOA + self.mac_address_indicator = MacAddressIndicator.EXTENDED_ADDRESS + self.mt = MessageType.NOTIFICATION + self.oid = SessionControlOpcodeId.START + self.gid = GroupId.SESSION_CONTROL @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['Cir', bytes]: - if fields['t'] != FrameReportTlvType.CIR: + def parse(fields: dict, span: bytes) -> Tuple['ExtendedMacOwrAoaSessionInfoNtf', bytes]: + if fields['ranging_measurement_type'] != RangingMeasurementType.OWR_AOA or fields['mac_address_indicator'] != MacAddressIndicator.EXTENDED_ADDRESS or fields['mt'] != MessageType.NOTIFICATION or fields['oid'] != SessionControlOpcodeId.START or fields['gid'] != GroupId.SESSION_CONTROL: raise Exception("Invalid constraint field values") if len(span) < 1: raise Exception('Invalid packet size') - cir_value_count = span[0] + owr_aoa_ranging_measurements_count = span[0] span = span[1:] - cir_value = [] - for n in range(cir_value_count): - element, span = CirValue.parse(span) - cir_value.append(element) - fields['cir_value'] = cir_value - return Cir(**fields), span + if len(span) < owr_aoa_ranging_measurements_count * 19: + raise Exception('Invalid packet size') + owr_aoa_ranging_measurements = [] + for n in range(owr_aoa_ranging_measurements_count): + owr_aoa_ranging_measurements.append(ExtendedAddressOwrAoaRangingMeasurement.parse_all(span[n * 19:(n + 1) * 19])) + fields['owr_aoa_ranging_measurements'] = owr_aoa_ranging_measurements + span = span[owr_aoa_ranging_measurements_count * 19:] + fields['vendor_data'] = list(span) + span = bytes() + return ExtendedMacOwrAoaSessionInfoNtf(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - if len(self.cir_value) > 255: - print(f"Invalid length for field Cir::cir_value: {len(self.cir_value)} > 255; the array will be truncated") - del self.cir_value[255:] - _span.append((len(self.cir_value) << 0)) - for _elt in self.cir_value: + if len(self.owr_aoa_ranging_measurements) > 255: + print(f"Invalid length for field ExtendedMacOwrAoaSessionInfoNtf::owr_aoa_ranging_measurements: {len(self.owr_aoa_ranging_measurements)} > 255; the array will be truncated") + del self.owr_aoa_ranging_measurements[255:] + _span.append((len(self.owr_aoa_ranging_measurements) << 0)) + for _elt in self.owr_aoa_ranging_measurements: _span.extend(_elt.serialize()) - return FrameReportTlvPacket.serialize(self, payload = bytes(_span)) + _span.extend(self.vendor_data) + return SessionInfoNtf.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return sum([elt.size for elt in self.cir_value]) + 1 + return 1 + ( + sum([elt.size for elt in self.owr_aoa_ranging_measurements]) + + len(self.vendor_data) * 1 + ) @dataclass -class FrameReport(Packet): - uwb_msg_id: int = field(kw_only=True, default=0) - action: int = field(kw_only=True, default=0) - antenna_set: int = field(kw_only=True, default=0) - frame_report_tlvs: List[FrameReportTlv] = field(kw_only=True, default_factory=list) +class SessionStopCmd(SessionControlPacket): + session_id: int = field(kw_only=True, default=0) def __post_init__(self): - pass + self.mt = MessageType.COMMAND + self.oid = SessionControlOpcodeId.STOP + self.gid = GroupId.SESSION_CONTROL @staticmethod - def parse(span: bytes) -> Tuple['FrameReport', bytes]: - fields = {'payload': None} + def parse(fields: dict, span: bytes) -> Tuple['SessionStopCmd', bytes]: + if fields['mt'] != MessageType.COMMAND or fields['oid'] != SessionControlOpcodeId.STOP or fields['gid'] != GroupId.SESSION_CONTROL: + raise Exception("Invalid constraint field values") if len(span) < 4: raise Exception('Invalid packet size') - fields['uwb_msg_id'] = span[0] - fields['action'] = span[1] - fields['antenna_set'] = span[2] - frame_report_tlvs_count = span[3] + value_ = int.from_bytes(span[0:4], byteorder='little') + fields['session_id'] = value_ span = span[4:] - frame_report_tlvs = [] - for n in range(frame_report_tlvs_count): - element, span = FrameReportTlv.parse(span) - frame_report_tlvs.append(element) - fields['frame_report_tlvs'] = frame_report_tlvs - return FrameReport(**fields), span + return SessionStopCmd(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - if self.uwb_msg_id > 255: - print(f"Invalid value for field FrameReport::uwb_msg_id: {self.uwb_msg_id} > 255; the value will be truncated") - self.uwb_msg_id &= 255 - _span.append((self.uwb_msg_id << 0)) - if self.action > 255: - print(f"Invalid value for field FrameReport::action: {self.action} > 255; the value will be truncated") - self.action &= 255 - _span.append((self.action << 0)) - if self.antenna_set > 255: - print(f"Invalid value for field FrameReport::antenna_set: {self.antenna_set} > 255; the value will be truncated") - self.antenna_set &= 255 - _span.append((self.antenna_set << 0)) - if len(self.frame_report_tlvs) > 255: - print(f"Invalid length for field FrameReport::frame_report_tlvs: {len(self.frame_report_tlvs)} > 255; the array will be truncated") - del self.frame_report_tlvs[255:] - _span.append((len(self.frame_report_tlvs) << 0)) - for _elt in self.frame_report_tlvs: - _span.extend(_elt.serialize()) - return bytes(_span) + if self.session_id > 4294967295: + print(f"Invalid value for field SessionStopCmd::session_id: {self.session_id} > 4294967295; the value will be truncated") + self.session_id &= 4294967295 + _span.extend(int.to_bytes((self.session_id << 0), length=4, byteorder='little')) + return SessionControlPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return sum([elt.size for elt in self.frame_report_tlvs]) + 4 + return 4 @dataclass -class AndroidRangeDiagnosticsNtf(AndroidNotification): - session_token: int = field(kw_only=True, default=0) - sequence_number: int = field(kw_only=True, default=0) - frame_reports: List[FrameReport] = field(kw_only=True, default_factory=list) +class SessionStopRsp(SessionControlPacket): + status: Status = field(kw_only=True, default=Status.OK) def __post_init__(self): - self.opcode = 2 - self.gid = GroupId.VENDOR_ANDROID - self.mt = MessageType.NOTIFICATION + self.mt = MessageType.RESPONSE + self.oid = SessionControlOpcodeId.STOP + self.gid = GroupId.SESSION_CONTROL @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['AndroidRangeDiagnosticsNtf', bytes]: - if fields['opcode'] != 0x2 or fields['gid'] != GroupId.VENDOR_ANDROID or fields['mt'] != MessageType.NOTIFICATION: + def parse(fields: dict, span: bytes) -> Tuple['SessionStopRsp', bytes]: + if fields['mt'] != MessageType.RESPONSE or fields['oid'] != SessionControlOpcodeId.STOP or fields['gid'] != GroupId.SESSION_CONTROL: raise Exception("Invalid constraint field values") - if len(span) < 9: + if len(span) < 1: raise Exception('Invalid packet size') - value_ = int.from_bytes(span[0:4], byteorder='little') - fields['session_token'] = value_ - value_ = int.from_bytes(span[4:8], byteorder='little') - fields['sequence_number'] = value_ - frame_reports_count = span[8] - span = span[9:] - frame_reports = [] - for n in range(frame_reports_count): - element, span = FrameReport.parse(span) - frame_reports.append(element) - fields['frame_reports'] = frame_reports - return AndroidRangeDiagnosticsNtf(**fields), span + fields['status'] = Status.from_int(span[0]) + span = span[1:] + return SessionStopRsp(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - if self.session_token > 4294967295: - print(f"Invalid value for field AndroidRangeDiagnosticsNtf::session_token: {self.session_token} > 4294967295; the value will be truncated") - self.session_token &= 4294967295 - _span.extend(int.to_bytes((self.session_token << 0), length=4, byteorder='little')) - if self.sequence_number > 4294967295: - print(f"Invalid value for field AndroidRangeDiagnosticsNtf::sequence_number: {self.sequence_number} > 4294967295; the value will be truncated") - self.sequence_number &= 4294967295 - _span.extend(int.to_bytes((self.sequence_number << 0), length=4, byteorder='little')) - if len(self.frame_reports) > 255: - print(f"Invalid length for field AndroidRangeDiagnosticsNtf::frame_reports: {len(self.frame_reports)} > 255; the array will be truncated") - del self.frame_reports[255:] - _span.append((len(self.frame_reports) << 0)) - for _elt in self.frame_reports: - _span.extend(_elt.serialize()) - return AndroidNotification.serialize(self, payload = bytes(_span)) + _span.append((self.status << 0)) + return SessionControlPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return sum([elt.size for elt in self.frame_reports]) + 9 + return 1 @dataclass -class UciVendor_9_Command(UciCommand): - +class SessionGetRangingCountCmd(SessionControlPacket): + session_id: int = field(kw_only=True, default=0) def __post_init__(self): - self.gid = GroupId.VENDOR_RESERVED_9 self.mt = MessageType.COMMAND + self.oid = SessionControlOpcodeId.GET_RANGING_COUNT + self.gid = GroupId.SESSION_CONTROL @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['UciVendor_9_Command', bytes]: - if fields['gid'] != GroupId.VENDOR_RESERVED_9 or fields['mt'] != MessageType.COMMAND: + def parse(fields: dict, span: bytes) -> Tuple['SessionGetRangingCountCmd', bytes]: + if fields['mt'] != MessageType.COMMAND or fields['oid'] != SessionControlOpcodeId.GET_RANGING_COUNT or fields['gid'] != GroupId.SESSION_CONTROL: raise Exception("Invalid constraint field values") - payload = span - span = bytes([]) - fields['payload'] = payload - return UciVendor_9_Command(**fields), span + if len(span) < 4: + raise Exception('Invalid packet size') + value_ = int.from_bytes(span[0:4], byteorder='little') + fields['session_id'] = value_ + span = span[4:] + return SessionGetRangingCountCmd(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(payload or self.payload or []) - return UciCommand.serialize(self, payload = bytes(_span)) + if self.session_id > 4294967295: + print(f"Invalid value for field SessionGetRangingCountCmd::session_id: {self.session_id} > 4294967295; the value will be truncated") + self.session_id &= 4294967295 + _span.extend(int.to_bytes((self.session_id << 0), length=4, byteorder='little')) + return SessionControlPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.payload) + return 4 @dataclass -class UciVendor_A_Command(UciCommand): - +class SessionGetRangingCountRsp(SessionControlPacket): + status: Status = field(kw_only=True, default=Status.OK) + count: int = field(kw_only=True, default=0) def __post_init__(self): - self.gid = GroupId.VENDOR_RESERVED_A - self.mt = MessageType.COMMAND + self.mt = MessageType.RESPONSE + self.oid = SessionControlOpcodeId.GET_RANGING_COUNT + self.gid = GroupId.SESSION_CONTROL @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['UciVendor_A_Command', bytes]: - if fields['gid'] != GroupId.VENDOR_RESERVED_A or fields['mt'] != MessageType.COMMAND: + def parse(fields: dict, span: bytes) -> Tuple['SessionGetRangingCountRsp', bytes]: + if fields['mt'] != MessageType.RESPONSE or fields['oid'] != SessionControlOpcodeId.GET_RANGING_COUNT or fields['gid'] != GroupId.SESSION_CONTROL: raise Exception("Invalid constraint field values") - payload = span - span = bytes([]) - fields['payload'] = payload - return UciVendor_A_Command(**fields), span + if len(span) < 5: + raise Exception('Invalid packet size') + fields['status'] = Status.from_int(span[0]) + value_ = int.from_bytes(span[1:5], byteorder='little') + fields['count'] = value_ + span = span[5:] + return SessionGetRangingCountRsp(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(payload or self.payload or []) - return UciCommand.serialize(self, payload = bytes(_span)) + _span.append((self.status << 0)) + if self.count > 4294967295: + print(f"Invalid value for field SessionGetRangingCountRsp::count: {self.count} > 4294967295; the value will be truncated") + self.count &= 4294967295 + _span.extend(int.to_bytes((self.count << 0), length=4, byteorder='little')) + return SessionControlPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.payload) + return 5 @dataclass -class UciVendor_B_Command(UciCommand): +class AndroidGetPowerStatsCmd(AndroidPacket): def __post_init__(self): - self.gid = GroupId.VENDOR_RESERVED_B self.mt = MessageType.COMMAND + self.oid = AndroidOpcodeId.GET_POWER_STATS + self.gid = GroupId.VENDOR_ANDROID @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['UciVendor_B_Command', bytes]: - if fields['gid'] != GroupId.VENDOR_RESERVED_B or fields['mt'] != MessageType.COMMAND: + def parse(fields: dict, span: bytes) -> Tuple['AndroidGetPowerStatsCmd', bytes]: + if fields['mt'] != MessageType.COMMAND or fields['oid'] != AndroidOpcodeId.GET_POWER_STATS or fields['gid'] != GroupId.VENDOR_ANDROID: raise Exception("Invalid constraint field values") - payload = span - span = bytes([]) - fields['payload'] = payload - return UciVendor_B_Command(**fields), span + return AndroidGetPowerStatsCmd(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(payload or self.payload or []) - return UciCommand.serialize(self, payload = bytes(_span)) + return AndroidPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.payload) + return 0 @dataclass -class UciVendor_E_Command(UciCommand): - +class PowerStats(Packet): + status: Status = field(kw_only=True, default=Status.OK) + idle_time_ms: int = field(kw_only=True, default=0) + tx_time_ms: int = field(kw_only=True, default=0) + rx_time_ms: int = field(kw_only=True, default=0) + total_wake_count: int = field(kw_only=True, default=0) def __post_init__(self): - self.gid = GroupId.VENDOR_RESERVED_E - self.mt = MessageType.COMMAND + pass @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['UciVendor_E_Command', bytes]: - if fields['gid'] != GroupId.VENDOR_RESERVED_E or fields['mt'] != MessageType.COMMAND: - raise Exception("Invalid constraint field values") - payload = span - span = bytes([]) - fields['payload'] = payload - return UciVendor_E_Command(**fields), span + def parse(span: bytes) -> Tuple['PowerStats', bytes]: + fields = {'payload': None} + if len(span) < 17: + raise Exception('Invalid packet size') + fields['status'] = Status.from_int(span[0]) + value_ = int.from_bytes(span[1:5], byteorder='little') + fields['idle_time_ms'] = value_ + value_ = int.from_bytes(span[5:9], byteorder='little') + fields['tx_time_ms'] = value_ + value_ = int.from_bytes(span[9:13], byteorder='little') + fields['rx_time_ms'] = value_ + value_ = int.from_bytes(span[13:17], byteorder='little') + fields['total_wake_count'] = value_ + span = span[17:] + return PowerStats(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(payload or self.payload or []) - return UciCommand.serialize(self, payload = bytes(_span)) + _span.append((self.status << 0)) + if self.idle_time_ms > 4294967295: + print(f"Invalid value for field PowerStats::idle_time_ms: {self.idle_time_ms} > 4294967295; the value will be truncated") + self.idle_time_ms &= 4294967295 + _span.extend(int.to_bytes((self.idle_time_ms << 0), length=4, byteorder='little')) + if self.tx_time_ms > 4294967295: + print(f"Invalid value for field PowerStats::tx_time_ms: {self.tx_time_ms} > 4294967295; the value will be truncated") + self.tx_time_ms &= 4294967295 + _span.extend(int.to_bytes((self.tx_time_ms << 0), length=4, byteorder='little')) + if self.rx_time_ms > 4294967295: + print(f"Invalid value for field PowerStats::rx_time_ms: {self.rx_time_ms} > 4294967295; the value will be truncated") + self.rx_time_ms &= 4294967295 + _span.extend(int.to_bytes((self.rx_time_ms << 0), length=4, byteorder='little')) + if self.total_wake_count > 4294967295: + print(f"Invalid value for field PowerStats::total_wake_count: {self.total_wake_count} > 4294967295; the value will be truncated") + self.total_wake_count &= 4294967295 + _span.extend(int.to_bytes((self.total_wake_count << 0), length=4, byteorder='little')) + return bytes(_span) @property def size(self) -> int: - return len(self.payload) + return 17 @dataclass -class UciVendor_F_Command(UciCommand): - +class AndroidGetPowerStatsRsp(AndroidPacket): + stats: PowerStats = field(kw_only=True, default_factory=PowerStats) def __post_init__(self): - self.gid = GroupId.VENDOR_RESERVED_F - self.mt = MessageType.COMMAND + self.mt = MessageType.RESPONSE + self.oid = AndroidOpcodeId.GET_POWER_STATS + self.gid = GroupId.VENDOR_ANDROID @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['UciVendor_F_Command', bytes]: - if fields['gid'] != GroupId.VENDOR_RESERVED_F or fields['mt'] != MessageType.COMMAND: + def parse(fields: dict, span: bytes) -> Tuple['AndroidGetPowerStatsRsp', bytes]: + if fields['mt'] != MessageType.RESPONSE or fields['oid'] != AndroidOpcodeId.GET_POWER_STATS or fields['gid'] != GroupId.VENDOR_ANDROID: raise Exception("Invalid constraint field values") - payload = span - span = bytes([]) - fields['payload'] = payload - return UciVendor_F_Command(**fields), span + if len(span) < 17: + raise Exception('Invalid packet size') + fields['stats'] = PowerStats.parse_all(span[0:17]) + span = span[17:] + return AndroidGetPowerStatsRsp(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(payload or self.payload or []) - return UciCommand.serialize(self, payload = bytes(_span)) + _span.extend(self.stats.serialize()) + return AndroidPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.payload) + return 17 @dataclass -class UciVendor_9_Response(UciResponse): - +class AndroidSetCountryCodeCmd(AndroidPacket): + country_code: bytearray = field(kw_only=True, default_factory=bytearray) def __post_init__(self): - self.gid = GroupId.VENDOR_RESERVED_9 - self.mt = MessageType.RESPONSE + self.mt = MessageType.COMMAND + self.oid = AndroidOpcodeId.SET_COUNTRY_CODE + self.gid = GroupId.VENDOR_ANDROID @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['UciVendor_9_Response', bytes]: - if fields['gid'] != GroupId.VENDOR_RESERVED_9 or fields['mt'] != MessageType.RESPONSE: + def parse(fields: dict, span: bytes) -> Tuple['AndroidSetCountryCodeCmd', bytes]: + if fields['mt'] != MessageType.COMMAND or fields['oid'] != AndroidOpcodeId.SET_COUNTRY_CODE or fields['gid'] != GroupId.VENDOR_ANDROID: raise Exception("Invalid constraint field values") - payload = span - span = bytes([]) - fields['payload'] = payload - return UciVendor_9_Response(**fields), span + if len(span) < 2: + raise Exception('Invalid packet size') + fields['country_code'] = list(span[:2]) + span = span[2:] + return AndroidSetCountryCodeCmd(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(payload or self.payload or []) - return UciResponse.serialize(self, payload = bytes(_span)) + _span.extend(self.country_code) + return AndroidPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.payload) + return 2 @dataclass -class UciVendor_A_Response(UciResponse): - +class AndroidSetCountryCodeRsp(AndroidPacket): + status: Status = field(kw_only=True, default=Status.OK) def __post_init__(self): - self.gid = GroupId.VENDOR_RESERVED_A self.mt = MessageType.RESPONSE + self.oid = AndroidOpcodeId.SET_COUNTRY_CODE + self.gid = GroupId.VENDOR_ANDROID @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['UciVendor_A_Response', bytes]: - if fields['gid'] != GroupId.VENDOR_RESERVED_A or fields['mt'] != MessageType.RESPONSE: + def parse(fields: dict, span: bytes) -> Tuple['AndroidSetCountryCodeRsp', bytes]: + if fields['mt'] != MessageType.RESPONSE or fields['oid'] != AndroidOpcodeId.SET_COUNTRY_CODE or fields['gid'] != GroupId.VENDOR_ANDROID: raise Exception("Invalid constraint field values") - payload = span - span = bytes([]) - fields['payload'] = payload - return UciVendor_A_Response(**fields), span + if len(span) < 1: + raise Exception('Invalid packet size') + fields['status'] = Status.from_int(span[0]) + span = span[1:] + return AndroidSetCountryCodeRsp(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(payload or self.payload or []) - return UciResponse.serialize(self, payload = bytes(_span)) + _span.append((self.status << 0)) + return AndroidPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.payload) + return 1 + +class FrameReportTlvType(enum.IntEnum): + RSSI = 0x0 + AOA = 0x1 + CIR = 0x2 + + @staticmethod + def from_int(v: int) -> Union[int, 'FrameReportTlvType']: + try: + return FrameReportTlvType(v) + except ValueError as exn: + raise exn + @dataclass -class UciVendor_B_Response(UciResponse): - +class FrameReportTlv(Packet): + t: FrameReportTlvType = field(kw_only=True, default=FrameReportTlvType.RSSI) + v: bytearray = field(kw_only=True, default_factory=bytearray) def __post_init__(self): - self.gid = GroupId.VENDOR_RESERVED_B - self.mt = MessageType.RESPONSE + pass @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['UciVendor_B_Response', bytes]: - if fields['gid'] != GroupId.VENDOR_RESERVED_B or fields['mt'] != MessageType.RESPONSE: - raise Exception("Invalid constraint field values") - payload = span - span = bytes([]) - fields['payload'] = payload - return UciVendor_B_Response(**fields), span + def parse(span: bytes) -> Tuple['FrameReportTlv', bytes]: + fields = {'payload': None} + if len(span) < 3: + raise Exception('Invalid packet size') + fields['t'] = FrameReportTlvType.from_int(span[0]) + value_ = int.from_bytes(span[1:3], byteorder='little') + v_size = value_ + span = span[3:] + if len(span) < v_size: + raise Exception('Invalid packet size') + fields['v'] = list(span[:v_size]) + span = span[v_size:] + return FrameReportTlv(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(payload or self.payload or []) - return UciResponse.serialize(self, payload = bytes(_span)) + _span.append((self.t << 0)) + _span.extend(int.to_bytes(((len(self.v) * 1) << 0), length=2, byteorder='little')) + _span.extend(self.v) + return bytes(_span) @property def size(self) -> int: - return len(self.payload) + return len(self.v) * 1 + 3 @dataclass -class UciVendor_E_Response(UciResponse): - +class FrameReportTlvPacket(Packet): + t: FrameReportTlvType = field(kw_only=True, default=FrameReportTlvType.RSSI) def __post_init__(self): - self.gid = GroupId.VENDOR_RESERVED_E - self.mt = MessageType.RESPONSE + pass @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['UciVendor_E_Response', bytes]: - if fields['gid'] != GroupId.VENDOR_RESERVED_E or fields['mt'] != MessageType.RESPONSE: - raise Exception("Invalid constraint field values") - payload = span - span = bytes([]) + def parse(span: bytes) -> Tuple['FrameReportTlvPacket', bytes]: + fields = {'payload': None} + if len(span) < 3: + raise Exception('Invalid packet size') + fields['t'] = FrameReportTlvType.from_int(span[0]) + value_ = int.from_bytes(span[1:3], byteorder='little') + _body__size = value_ + span = span[3:] + if len(span) < _body__size: + raise Exception('Invalid packet size') + payload = span[:_body__size] + span = span[_body__size:] fields['payload'] = payload - return UciVendor_E_Response(**fields), span + try: + return Rssi.parse(fields.copy(), payload) + except Exception as exn: + pass + try: + return Aoa.parse(fields.copy(), payload) + except Exception as exn: + pass + try: + return Cir.parse(fields.copy(), payload) + except Exception as exn: + pass + return FrameReportTlvPacket(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() + _span.append((self.t << 0)) + _payload_size = len(payload or self.payload or []) + if _payload_size > 65535: + print(f"Invalid length for payload field: {_payload_size} > 65535; the packet cannot be generated") + raise Exception("Invalid payload length") + _span.extend(int.to_bytes((_payload_size << 0), length=2, byteorder='little')) _span.extend(payload or self.payload or []) - return UciResponse.serialize(self, payload = bytes(_span)) + return bytes(_span) @property def size(self) -> int: - return len(self.payload) + return len(self.payload) + 3 @dataclass -class UciVendor_F_Response(UciResponse): - +class Rssi(FrameReportTlvPacket): + rssi: bytearray = field(kw_only=True, default_factory=bytearray) def __post_init__(self): - self.gid = GroupId.VENDOR_RESERVED_F - self.mt = MessageType.RESPONSE + self.t = FrameReportTlvType.RSSI @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['UciVendor_F_Response', bytes]: - if fields['gid'] != GroupId.VENDOR_RESERVED_F or fields['mt'] != MessageType.RESPONSE: + def parse(fields: dict, span: bytes) -> Tuple['Rssi', bytes]: + if fields['t'] != FrameReportTlvType.RSSI: raise Exception("Invalid constraint field values") - payload = span - span = bytes([]) - fields['payload'] = payload - return UciVendor_F_Response(**fields), span + fields['rssi'] = list(span) + span = bytes() + return Rssi(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(payload or self.payload or []) - return UciResponse.serialize(self, payload = bytes(_span)) + _span.extend(self.rssi) + return FrameReportTlvPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.payload) + return len(self.rssi) * 1 @dataclass -class UciVendor_9_Notification(UciNotification): - +class AoaMeasurement(Packet): + tdoa: int = field(kw_only=True, default=0) + pdoa: int = field(kw_only=True, default=0) + aoa: int = field(kw_only=True, default=0) + fom: int = field(kw_only=True, default=0) + t: int = field(kw_only=True, default=0) def __post_init__(self): - self.gid = GroupId.VENDOR_RESERVED_9 - self.mt = MessageType.NOTIFICATION + pass @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['UciVendor_9_Notification', bytes]: - if fields['gid'] != GroupId.VENDOR_RESERVED_9 or fields['mt'] != MessageType.NOTIFICATION: - raise Exception("Invalid constraint field values") - payload = span - span = bytes([]) - fields['payload'] = payload - return UciVendor_9_Notification(**fields), span + def parse(span: bytes) -> Tuple['AoaMeasurement', bytes]: + fields = {'payload': None} + if len(span) < 8: + raise Exception('Invalid packet size') + value_ = int.from_bytes(span[0:2], byteorder='little') + fields['tdoa'] = value_ + value_ = int.from_bytes(span[2:4], byteorder='little') + fields['pdoa'] = value_ + value_ = int.from_bytes(span[4:6], byteorder='little') + fields['aoa'] = value_ + fields['fom'] = span[6] + fields['t'] = span[7] + span = span[8:] + return AoaMeasurement(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(payload or self.payload or []) - return UciNotification.serialize(self, payload = bytes(_span)) + if self.tdoa > 65535: + print(f"Invalid value for field AoaMeasurement::tdoa: {self.tdoa} > 65535; the value will be truncated") + self.tdoa &= 65535 + _span.extend(int.to_bytes((self.tdoa << 0), length=2, byteorder='little')) + if self.pdoa > 65535: + print(f"Invalid value for field AoaMeasurement::pdoa: {self.pdoa} > 65535; the value will be truncated") + self.pdoa &= 65535 + _span.extend(int.to_bytes((self.pdoa << 0), length=2, byteorder='little')) + if self.aoa > 65535: + print(f"Invalid value for field AoaMeasurement::aoa: {self.aoa} > 65535; the value will be truncated") + self.aoa &= 65535 + _span.extend(int.to_bytes((self.aoa << 0), length=2, byteorder='little')) + if self.fom > 255: + print(f"Invalid value for field AoaMeasurement::fom: {self.fom} > 255; the value will be truncated") + self.fom &= 255 + _span.append((self.fom << 0)) + if self.t > 255: + print(f"Invalid value for field AoaMeasurement::t: {self.t} > 255; the value will be truncated") + self.t &= 255 + _span.append((self.t << 0)) + return bytes(_span) @property def size(self) -> int: - return len(self.payload) + return 8 @dataclass -class UciVendor_A_Notification(UciNotification): - +class Aoa(FrameReportTlvPacket): + aoa: List[AoaMeasurement] = field(kw_only=True, default_factory=list) def __post_init__(self): - self.gid = GroupId.VENDOR_RESERVED_A - self.mt = MessageType.NOTIFICATION + self.t = FrameReportTlvType.AOA @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['UciVendor_A_Notification', bytes]: - if fields['gid'] != GroupId.VENDOR_RESERVED_A or fields['mt'] != MessageType.NOTIFICATION: + def parse(fields: dict, span: bytes) -> Tuple['Aoa', bytes]: + if fields['t'] != FrameReportTlvType.AOA: raise Exception("Invalid constraint field values") - payload = span - span = bytes([]) - fields['payload'] = payload - return UciVendor_A_Notification(**fields), span + if len(span) % 8 != 0: + raise Exception('Array size is not a multiple of the element size') + aoa_count = int(len(span) / 8) + aoa = [] + for n in range(aoa_count): + aoa.append(AoaMeasurement.parse_all(span[n * 8:(n + 1) * 8])) + fields['aoa'] = aoa + span = bytes() + return Aoa(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(payload or self.payload or []) - return UciNotification.serialize(self, payload = bytes(_span)) + for _elt in self.aoa: + _span.extend(_elt.serialize()) + return FrameReportTlvPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.payload) + return sum([elt.size for elt in self.aoa]) @dataclass -class UciVendor_B_Notification(UciNotification): - +class CirValue(Packet): + first_path_index: int = field(kw_only=True, default=0) + first_path_snr: int = field(kw_only=True, default=0) + first_path_ns: int = field(kw_only=True, default=0) + peak_path_index: int = field(kw_only=True, default=0) + peak_path_snr: int = field(kw_only=True, default=0) + peak_path_ns: int = field(kw_only=True, default=0) + first_path_sample_offset: int = field(kw_only=True, default=0) + samples_number: int = field(kw_only=True, default=0) + sample_window: bytearray = field(kw_only=True, default_factory=bytearray) def __post_init__(self): - self.gid = GroupId.VENDOR_RESERVED_B - self.mt = MessageType.NOTIFICATION + pass @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['UciVendor_B_Notification', bytes]: - if fields['gid'] != GroupId.VENDOR_RESERVED_B or fields['mt'] != MessageType.NOTIFICATION: - raise Exception("Invalid constraint field values") - payload = span - span = bytes([]) - fields['payload'] = payload - return UciVendor_B_Notification(**fields), span + def parse(span: bytes) -> Tuple['CirValue', bytes]: + fields = {'payload': None} + if len(span) < 16: + raise Exception('Invalid packet size') + value_ = int.from_bytes(span[0:2], byteorder='little') + fields['first_path_index'] = value_ + value_ = int.from_bytes(span[2:4], byteorder='little') + fields['first_path_snr'] = value_ + value_ = int.from_bytes(span[4:6], byteorder='little') + fields['first_path_ns'] = value_ + value_ = int.from_bytes(span[6:8], byteorder='little') + fields['peak_path_index'] = value_ + value_ = int.from_bytes(span[8:10], byteorder='little') + fields['peak_path_snr'] = value_ + value_ = int.from_bytes(span[10:12], byteorder='little') + fields['peak_path_ns'] = value_ + fields['first_path_sample_offset'] = span[12] + fields['samples_number'] = span[13] + value_ = int.from_bytes(span[14:16], byteorder='little') + sample_window_size = value_ + span = span[16:] + if len(span) < sample_window_size: + raise Exception('Invalid packet size') + fields['sample_window'] = list(span[:sample_window_size]) + span = span[sample_window_size:] + return CirValue(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(payload or self.payload or []) - return UciNotification.serialize(self, payload = bytes(_span)) + if self.first_path_index > 65535: + print(f"Invalid value for field CirValue::first_path_index: {self.first_path_index} > 65535; the value will be truncated") + self.first_path_index &= 65535 + _span.extend(int.to_bytes((self.first_path_index << 0), length=2, byteorder='little')) + if self.first_path_snr > 65535: + print(f"Invalid value for field CirValue::first_path_snr: {self.first_path_snr} > 65535; the value will be truncated") + self.first_path_snr &= 65535 + _span.extend(int.to_bytes((self.first_path_snr << 0), length=2, byteorder='little')) + if self.first_path_ns > 65535: + print(f"Invalid value for field CirValue::first_path_ns: {self.first_path_ns} > 65535; the value will be truncated") + self.first_path_ns &= 65535 + _span.extend(int.to_bytes((self.first_path_ns << 0), length=2, byteorder='little')) + if self.peak_path_index > 65535: + print(f"Invalid value for field CirValue::peak_path_index: {self.peak_path_index} > 65535; the value will be truncated") + self.peak_path_index &= 65535 + _span.extend(int.to_bytes((self.peak_path_index << 0), length=2, byteorder='little')) + if self.peak_path_snr > 65535: + print(f"Invalid value for field CirValue::peak_path_snr: {self.peak_path_snr} > 65535; the value will be truncated") + self.peak_path_snr &= 65535 + _span.extend(int.to_bytes((self.peak_path_snr << 0), length=2, byteorder='little')) + if self.peak_path_ns > 65535: + print(f"Invalid value for field CirValue::peak_path_ns: {self.peak_path_ns} > 65535; the value will be truncated") + self.peak_path_ns &= 65535 + _span.extend(int.to_bytes((self.peak_path_ns << 0), length=2, byteorder='little')) + if self.first_path_sample_offset > 255: + print(f"Invalid value for field CirValue::first_path_sample_offset: {self.first_path_sample_offset} > 255; the value will be truncated") + self.first_path_sample_offset &= 255 + _span.append((self.first_path_sample_offset << 0)) + if self.samples_number > 255: + print(f"Invalid value for field CirValue::samples_number: {self.samples_number} > 255; the value will be truncated") + self.samples_number &= 255 + _span.append((self.samples_number << 0)) + _span.extend(int.to_bytes(((len(self.sample_window) * 1) << 0), length=2, byteorder='little')) + _span.extend(self.sample_window) + return bytes(_span) @property def size(self) -> int: - return len(self.payload) + return len(self.sample_window) * 1 + 16 @dataclass -class UciVendor_E_Notification(UciNotification): - +class Cir(FrameReportTlvPacket): + cir_value: List[CirValue] = field(kw_only=True, default_factory=list) def __post_init__(self): - self.gid = GroupId.VENDOR_RESERVED_E - self.mt = MessageType.NOTIFICATION + self.t = FrameReportTlvType.CIR @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['UciVendor_E_Notification', bytes]: - if fields['gid'] != GroupId.VENDOR_RESERVED_E or fields['mt'] != MessageType.NOTIFICATION: + def parse(fields: dict, span: bytes) -> Tuple['Cir', bytes]: + if fields['t'] != FrameReportTlvType.CIR: raise Exception("Invalid constraint field values") - payload = span - span = bytes([]) - fields['payload'] = payload - return UciVendor_E_Notification(**fields), span + if len(span) < 1: + raise Exception('Invalid packet size') + cir_value_count = span[0] + span = span[1:] + cir_value = [] + for n in range(cir_value_count): + element, span = CirValue.parse(span) + cir_value.append(element) + fields['cir_value'] = cir_value + return Cir(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(payload or self.payload or []) - return UciNotification.serialize(self, payload = bytes(_span)) + if len(self.cir_value) > 255: + print(f"Invalid length for field Cir::cir_value: {len(self.cir_value)} > 255; the array will be truncated") + del self.cir_value[255:] + _span.append((len(self.cir_value) << 0)) + for _elt in self.cir_value: + _span.extend(_elt.serialize()) + return FrameReportTlvPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.payload) + return sum([elt.size for elt in self.cir_value]) + 1 @dataclass -class UciVendor_F_Notification(UciNotification): - +class FrameReport(Packet): + uwb_msg_id: int = field(kw_only=True, default=0) + action: int = field(kw_only=True, default=0) + antenna_set: int = field(kw_only=True, default=0) + frame_report_tlvs: List[FrameReportTlv] = field(kw_only=True, default_factory=list) def __post_init__(self): - self.gid = GroupId.VENDOR_RESERVED_F - self.mt = MessageType.NOTIFICATION + pass @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['UciVendor_F_Notification', bytes]: - if fields['gid'] != GroupId.VENDOR_RESERVED_F or fields['mt'] != MessageType.NOTIFICATION: - raise Exception("Invalid constraint field values") - payload = span - span = bytes([]) - fields['payload'] = payload - return UciVendor_F_Notification(**fields), span + def parse(span: bytes) -> Tuple['FrameReport', bytes]: + fields = {'payload': None} + if len(span) < 4: + raise Exception('Invalid packet size') + fields['uwb_msg_id'] = span[0] + fields['action'] = span[1] + fields['antenna_set'] = span[2] + frame_report_tlvs_count = span[3] + span = span[4:] + frame_report_tlvs = [] + for n in range(frame_report_tlvs_count): + element, span = FrameReportTlv.parse(span) + frame_report_tlvs.append(element) + fields['frame_report_tlvs'] = frame_report_tlvs + return FrameReport(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(payload or self.payload or []) - return UciNotification.serialize(self, payload = bytes(_span)) + if self.uwb_msg_id > 255: + print(f"Invalid value for field FrameReport::uwb_msg_id: {self.uwb_msg_id} > 255; the value will be truncated") + self.uwb_msg_id &= 255 + _span.append((self.uwb_msg_id << 0)) + if self.action > 255: + print(f"Invalid value for field FrameReport::action: {self.action} > 255; the value will be truncated") + self.action &= 255 + _span.append((self.action << 0)) + if self.antenna_set > 255: + print(f"Invalid value for field FrameReport::antenna_set: {self.antenna_set} > 255; the value will be truncated") + self.antenna_set &= 255 + _span.append((self.antenna_set << 0)) + if len(self.frame_report_tlvs) > 255: + print(f"Invalid length for field FrameReport::frame_report_tlvs: {len(self.frame_report_tlvs)} > 255; the array will be truncated") + del self.frame_report_tlvs[255:] + _span.append((len(self.frame_report_tlvs) << 0)) + for _elt in self.frame_report_tlvs: + _span.extend(_elt.serialize()) + return bytes(_span) @property def size(self) -> int: - return len(self.payload) + return sum([elt.size for elt in self.frame_report_tlvs]) + 4 @dataclass -class TestNotification(UciNotification): - +class AndroidRangeDiagnosticsNtf(AndroidPacket): + session_token: int = field(kw_only=True, default=0) + sequence_number: int = field(kw_only=True, default=0) + frame_reports: List[FrameReport] = field(kw_only=True, default_factory=list) def __post_init__(self): - self.gid = GroupId.TEST self.mt = MessageType.NOTIFICATION + self.oid = AndroidOpcodeId.FIRA_RANGE_DIAGNOSTICS + self.gid = GroupId.VENDOR_ANDROID @staticmethod - def parse(fields: dict, span: bytes) -> Tuple['TestNotification', bytes]: - if fields['gid'] != GroupId.TEST or fields['mt'] != MessageType.NOTIFICATION: + def parse(fields: dict, span: bytes) -> Tuple['AndroidRangeDiagnosticsNtf', bytes]: + if fields['mt'] != MessageType.NOTIFICATION or fields['oid'] != AndroidOpcodeId.FIRA_RANGE_DIAGNOSTICS or fields['gid'] != GroupId.VENDOR_ANDROID: raise Exception("Invalid constraint field values") - payload = span - span = bytes([]) - fields['payload'] = payload - return TestNotification(**fields), span + if len(span) < 9: + raise Exception('Invalid packet size') + value_ = int.from_bytes(span[0:4], byteorder='little') + fields['session_token'] = value_ + value_ = int.from_bytes(span[4:8], byteorder='little') + fields['sequence_number'] = value_ + frame_reports_count = span[8] + span = span[9:] + frame_reports = [] + for n in range(frame_reports_count): + element, span = FrameReport.parse(span) + frame_reports.append(element) + fields['frame_reports'] = frame_reports + return AndroidRangeDiagnosticsNtf(**fields), span def serialize(self, payload: bytes = None) -> bytes: _span = bytearray() - _span.extend(payload or self.payload or []) - return UciNotification.serialize(self, payload = bytes(_span)) + if self.session_token > 4294967295: + print(f"Invalid value for field AndroidRangeDiagnosticsNtf::session_token: {self.session_token} > 4294967295; the value will be truncated") + self.session_token &= 4294967295 + _span.extend(int.to_bytes((self.session_token << 0), length=4, byteorder='little')) + if self.sequence_number > 4294967295: + print(f"Invalid value for field AndroidRangeDiagnosticsNtf::sequence_number: {self.sequence_number} > 4294967295; the value will be truncated") + self.sequence_number &= 4294967295 + _span.extend(int.to_bytes((self.sequence_number << 0), length=4, byteorder='little')) + if len(self.frame_reports) > 255: + print(f"Invalid length for field AndroidRangeDiagnosticsNtf::frame_reports: {len(self.frame_reports)} > 255; the array will be truncated") + del self.frame_reports[255:] + _span.append((len(self.frame_reports) << 0)) + for _elt in self.frame_reports: + _span.extend(_elt.serialize()) + return AndroidPacket.serialize(self, payload = bytes(_span)) @property def size(self) -> int: - return len(self.payload) + return sum([elt.size for elt in self.frame_reports]) + 9 diff --git a/src/device.rs b/src/device.rs index 449ec7b..e0fcf61 100644 --- a/src/device.rs +++ b/src/device.rs @@ -17,7 +17,6 @@ use crate::MacAddress; use crate::PicaCommand; use std::collections::HashMap; -use std::iter::Extend; use std::time::Duration; use tokio::sync::mpsc; @@ -77,15 +76,36 @@ pub const DEFAULT_CAPS_INFO: &[(CapTlvType, &[u8])] = &[ ), ]; +/// [UCI] 8.2 Device Configuration Parameters +pub struct DeviceConfig { + device_state: DeviceState, + // This config is used to enable/disable the low power mode. + // 0x00 = Disable low power mode + // 0x01 = Enable low power mode (default) + low_power_mode: bool, +} + +// [UCI] 6.3.1 Setting the Configuration +// All device configuration parameters within the UWBS are set to +// default values at Table 44 [..]. +impl Default for DeviceConfig { + fn default() -> Self { + DeviceConfig { + device_state: DeviceState::DeviceStateError, + low_power_mode: true, + } + } +} + pub struct Device { pub handle: usize, pub mac_address: MacAddress, + config: DeviceConfig, /// [UCI] 5. UWBS Device State Machine state: DeviceState, sessions: HashMap, pub tx: mpsc::UnboundedSender, pica_tx: mpsc::Sender, - config: HashMap>, country_code: [u8; 2], pub n_active_sessions: usize, @@ -101,11 +121,11 @@ impl Device { Device { handle, mac_address, + config: Default::default(), state: DeviceState::DeviceStateError, // Will be overwitten sessions: Default::default(), tx, pica_tx, - config: HashMap::new(), country_code: Default::default(), n_active_sessions: 0, } @@ -236,26 +256,41 @@ impl Device { log::debug!("[{}] SetConfig", self.handle); assert_eq!(self.state, DeviceState::DeviceStateReady); // UCI 6.3 - let (valid_parameters, invalid_config_status) = cmd.get_tlvs().iter().fold( - (HashMap::new(), Vec::new()), - |(mut valid_parameters, invalid_config_status), param| { - // TODO: DeviceState is a read only parameter - valid_parameters.insert(param.cfg_id, param.v.clone()); - - (valid_parameters, invalid_config_status) - }, - ); - - let (status, parameters) = if invalid_config_status.is_empty() { - self.config.extend(valid_parameters); - (uci::Status::Ok, Vec::new()) - } else { - (uci::Status::InvalidParam, invalid_config_status) - }; + // [UCI] 6.3.1 Setting the Configuration + // The UWBS shall respond with CORE_SET_CONFIG_RSP setting the Status + // field of STATUS_INVALID_PARAM and including one or more invalid + // Parameter ID(s) If the Host tries to set a parameter that is not + // available in the UWBS. All other configuration parameters should + // have been set to the new values within the UWBS. + let mut invalid_parameters = vec![]; + for parameter in cmd.get_parameters() { + match parameter.id { + uci::ConfigParameterId::DeviceState => { + invalid_parameters.push(uci::ConfigParameterStatus { + id: parameter.id, + status: uci::Status::ReadOnly, + }) + } + uci::ConfigParameterId::LowPowerMode => { + self.config.low_power_mode = parameter.value.first().copied().unwrap_or(1) != 0; + } + uci::ConfigParameterId::Rfu(id) => { + log::warn!("unknown config parameter id 0x{:02x}", *id); + invalid_parameters.push(uci::ConfigParameterStatus { + id: parameter.id, + status: uci::Status::InvalidParam, + }) + } + } + } CoreSetConfigRspBuilder { - cfg_status: parameters, - status, + status: if invalid_parameters.is_empty() { + uci::Status::Ok + } else { + uci::Status::InvalidParam + }, + parameters: invalid_parameters, } .build() } @@ -263,45 +298,45 @@ impl Device { pub fn core_get_config(&self, cmd: CoreGetConfigCmd) -> CoreGetConfigRsp { log::debug!("[{}] GetConfig", self.handle); - // TODO: do this config shall be set on device reset - let ids = cmd.get_cfg_id(); - let (valid_parameters, invalid_parameters) = ids.iter().fold( - (Vec::new(), Vec::new()), - |(mut valid_parameters, mut invalid_parameters), id| { - // UCI Core Section 6.3.2 Table 8 - // UCI Core Section 6.3.2 - Return the Configuration - // If the status code is ok, return the params - // If there is at least one invalid param, return the list of invalid params - // If the ID is not present in our config, return the Type with length = 0 - match DeviceConfigId::try_from(*id) { - Ok(cfg_id) => match self.config.get(&cfg_id) { - Some(value) => valid_parameters.push(DeviceConfigTlv { - cfg_id, - v: value.clone(), - }), - None => invalid_parameters.push(DeviceConfigTlv { - cfg_id, - v: Vec::new(), - }), - }, - Err(_) => log::error!("Failed to parse config id: {:?}", id), - } - - (valid_parameters, invalid_parameters) - }, - ); + // [UCI] 6.3.2 Retrieve the Configuration + // If the Host tries to retrieve any Parameter(s) that are not available + // in the UWBS, the UWBS shall respond with a CORE_GET_CONFIG_RSP with + // a Status field of STATUS_INVALID_PARAM, containing each unavailable + // Device Configuration Parameter Type with Length field is zero. In + // this case, the CORE_GET_CONFIG_RSP shall not include any parameter(s) + // that are available in the UWBS. + let mut valid_parameters = vec![]; + let mut invalid_parameters = vec![]; + for id in cmd.get_parameter_ids() { + match id { + ConfigParameterId::DeviceState => valid_parameters.push(ConfigParameter { + id: *id, + value: vec![self.config.device_state.into()], + }), + ConfigParameterId::LowPowerMode => valid_parameters.push(ConfigParameter { + id: *id, + value: vec![self.config.low_power_mode.into()], + }), + ConfigParameterId::Rfu(_) => invalid_parameters.push(ConfigParameter { + id: *id, + value: vec![], + }), + } + } - let (status, parameters) = if invalid_parameters.is_empty() { - (uci::Status::Ok, valid_parameters) + if invalid_parameters.is_empty() { + CoreGetConfigRspBuilder { + status: uci::Status::Ok, + parameters: valid_parameters, + } + .build() } else { - (uci::Status::InvalidParam, invalid_parameters) - }; - - CoreGetConfigRspBuilder { - status, - tlvs: parameters, + CoreGetConfigRspBuilder { + status: uci::Status::InvalidParam, + parameters: invalid_parameters, + } + .build() } - .build() } fn session_init(&mut self, cmd: SessionInitCmd) -> SessionInitRsp { diff --git a/src/lib.rs b/src/lib.rs index d3168c4..2c46733 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -467,7 +467,7 @@ impl Pica { data_sequence_number: 0x01, pbf: PacketBoundaryFlag::Complete, session_handle: session_id, - source_address: device.mac_address.into(), + source_address: session.app_config.device_mac_address.unwrap().into(), status: uci::Status::Ok, } .build() diff --git a/src/mac_address.rs b/src/mac_address.rs index 6b67e7e..6c93981 100644 --- a/src/mac_address.rs +++ b/src/mac_address.rs @@ -43,15 +43,21 @@ impl MacAddress { } } -impl From for u64 { - fn from(mac_address: MacAddress) -> Self { +impl From<&MacAddress> for u64 { + fn from(mac_address: &MacAddress) -> Self { match mac_address { - MacAddress::Short(addr) => u16::from_le_bytes(addr) as u64, - MacAddress::Extended(addr) => u64::from_le_bytes(addr), + MacAddress::Short(addr) => u16::from_le_bytes(*addr) as u64, + MacAddress::Extended(addr) => u64::from_le_bytes(*addr), } } } +impl From for u64 { + fn from(mac_address: MacAddress) -> Self { + u64::from(&mac_address) + } +} + impl From<&MacAddress> for Vec { fn from(mac_address: &MacAddress) -> Self { match mac_address { diff --git a/src/uci_packets.pdl b/src/uci_packets.pdl index 06fd9b0..4a2f69f 100644 --- a/src/uci_packets.pdl +++ b/src/uci_packets.pdl @@ -166,11 +166,6 @@ enum ResetConfig : 8 { UWBS_RESET = 0x00, } -enum DeviceConfigId : 8 { - DEVICE_STATE = 0x00, - LOW_POWER_MODE = 0x01, -} - // [UCI] Table 45: APP Configuration Parameters IDs enum AppConfigTlvType : 8 { DEVICE_TYPE = 0x00, @@ -774,30 +769,37 @@ test CoreGetCapsInfoRsp { "\x40\x03\x00\x05\x00\x00\x00\x00\x01\x00\x01\x01", } -struct DeviceConfigTlv { - cfg_id: DeviceConfigId, - _count_(v): 8, - v: 8[], +// [UCI] Table 44: Device Configuration Parameters +enum ConfigParameterId : 8 { + DEVICE_STATE = 0x00, + LOW_POWER_MODE = 0x01, + RFU = .., +} + +struct ConfigParameter { + id: ConfigParameterId, + _size_(value): 8, + value: 8[], } packet CoreSetConfigCmd : CorePacket (mt = COMMAND, oid = SET_CONFIG) { - _count_(tlvs): 8, - tlvs: DeviceConfigTlv[], + _count_(parameters): 8, + parameters: ConfigParameter[], } test CoreSetConfigCmd { "\x20\x04\x00\x03\x00\x00\x00\x01\x01\x00", } -struct DeviceConfigStatus { - cfg_id: DeviceConfigId, +struct ConfigParameterStatus { + id: ConfigParameterId, status: Status, } packet CoreSetConfigRsp : CorePacket (mt = RESPONSE, oid = SET_CONFIG) { status: Status, - _count_(cfg_status): 8, - cfg_status: DeviceConfigStatus[], + _count_(parameters): 8, + parameters: ConfigParameterStatus[], } test CoreSetConfigRsp { @@ -806,8 +808,8 @@ test CoreSetConfigRsp { } packet CoreGetConfigCmd : CorePacket (mt = COMMAND, oid = GET_CONFIG) { - _count_(cfg_id): 8, - cfg_id: 8[], // DeviceConfigId + _count_(parameter_ids): 8, + parameter_ids: ConfigParameterId[], } test CoreGetConfigCmd { @@ -816,8 +818,8 @@ test CoreGetConfigCmd { packet CoreGetConfigRsp : CorePacket (mt = RESPONSE, oid = GET_CONFIG) { status: Status, - _count_(tlvs): 8, - tlvs: DeviceConfigTlv[] + _count_(parameters): 8, + parameters: ConfigParameter[] } test CoreGetConfigRsp { diff --git a/tests/data_transfer.py b/tests/data_transfer.py index 7449a35..6d4fb8c 100755 --- a/tests/data_transfer.py +++ b/tests/data_transfer.py @@ -35,7 +35,7 @@ async def controller(host: Host, peer: Host, file: Path): ) ) - await host.expect_control(uci.SessionInitRsp(status=uci.StatusCode.UCI_STATUS_OK)) + await host.expect_control(uci.SessionInitRsp(status=uci.Status.OK)) await host.expect_control( uci.SessionStatusNtf( @@ -93,7 +93,7 @@ async def controller(host: Host, peer: Host, file: Path): ) await host.expect_control( - uci.SessionSetAppConfigRsp(status=uci.StatusCode.UCI_STATUS_OK, cfg_status=[]) + uci.SessionSetAppConfigRsp(status=uci.Status.OK, cfg_status=[]) ) await host.expect_control( @@ -109,7 +109,7 @@ async def controller(host: Host, peer: Host, file: Path): # START SESSION CMD host.send_control(uci.SessionStartCmd(session_id=0)) - await host.expect_control(uci.SessionStartRsp(status=uci.StatusCode.UCI_STATUS_OK)) + await host.expect_control(uci.SessionStartRsp(status=uci.Status.OK)) await host.expect_control( uci.SessionStatusNtf( @@ -120,7 +120,7 @@ async def controller(host: Host, peer: Host, file: Path): ) await host.expect_control( - uci.DeviceStatusNtf(device_state=uci.DeviceState.DEVICE_STATE_ACTIVE) + uci.CoreDeviceStatusNtf(device_state=uci.DeviceState.DEVICE_STATE_ACTIVE) ) event = await host.expect_control(uci.ShortMacTwoWaySessionInfoNtf, timeout=2.0) @@ -132,7 +132,7 @@ async def controller(host: Host, peer: Host, file: Path): # STOP SESSION host.send_control(uci.SessionStopCmd(session_id=0)) - await host.expect_control(uci.SessionStopRsp(status=uci.StatusCode.UCI_STATUS_OK)) + await host.expect_control(uci.SessionStopRsp(status=uci.Status.OK)) await host.expect_control( uci.SessionStatusNtf( @@ -143,13 +143,13 @@ async def controller(host: Host, peer: Host, file: Path): ) await host.expect_control( - uci.DeviceStatusNtf(device_state=uci.DeviceState.DEVICE_STATE_READY) + uci.CoreDeviceStatusNtf(device_state=uci.DeviceState.DEVICE_STATE_READY) ) # DEINIT host.send_control(uci.SessionDeinitCmd(session_token=0)) - await host.expect_control(uci.SessionDeinitRsp(status=uci.StatusCode.UCI_STATUS_OK)) + await host.expect_control(uci.SessionDeinitRsp(status=uci.Status.OK)) async def controlee(host: Host, peer: Host, file: Path): @@ -162,7 +162,7 @@ async def controlee(host: Host, peer: Host, file: Path): ) ) - await host.expect_control(uci.SessionInitRsp(status=uci.StatusCode.UCI_STATUS_OK)) + await host.expect_control(uci.SessionInitRsp(status=uci.Status.OK)) await host.expect_control( uci.SessionStatusNtf( @@ -220,7 +220,7 @@ async def controlee(host: Host, peer: Host, file: Path): ) await host.expect_control( - uci.SessionSetAppConfigRsp(status=uci.StatusCode.UCI_STATUS_OK, cfg_status=[]) + uci.SessionSetAppConfigRsp(status=uci.Status.OK, cfg_status=[]) ) await host.expect_control( @@ -233,7 +233,7 @@ async def controlee(host: Host, peer: Host, file: Path): host.send_control(uci.SessionStartCmd(session_id=0)) - await host.expect_control(uci.SessionStartRsp(status=uci.StatusCode.UCI_STATUS_OK)) + await host.expect_control(uci.SessionStartRsp(status=uci.Status.OK)) await host.expect_control( uci.SessionStatusNtf( @@ -244,7 +244,7 @@ async def controlee(host: Host, peer: Host, file: Path): ) await host.expect_control( - uci.DeviceStatusNtf(device_state=uci.DeviceState.DEVICE_STATE_ACTIVE) + uci.CoreDeviceStatusNtf(device_state=uci.DeviceState.DEVICE_STATE_ACTIVE) ) with file.open("rb") as f: @@ -252,8 +252,8 @@ async def controlee(host: Host, peer: Host, file: Path): event = await host.expect_data( uci.DataMessageRcv( session_handle=0, - status=uci.StatusCode.UCI_STATUS_OK, - source_address=int.from_bytes(peer.mac_address, "big"), + status=uci.Status.OK, + source_address=int.from_bytes(peer.mac_address, "little"), data_sequence_number=0x01, application_data=application_data, ), @@ -266,7 +266,7 @@ async def controlee(host: Host, peer: Host, file: Path): host.send_control(uci.SessionStopCmd(session_id=0)) - await host.expect_control(uci.SessionStopRsp(status=uci.StatusCode.UCI_STATUS_OK)) + await host.expect_control(uci.SessionStopRsp(status=uci.Status.OK)) await host.expect_control( uci.SessionStatusNtf( @@ -277,12 +277,12 @@ async def controlee(host: Host, peer: Host, file: Path): ) await host.expect_control( - uci.DeviceStatusNtf(device_state=uci.DeviceState.DEVICE_STATE_READY) + uci.CoreDeviceStatusNtf(device_state=uci.DeviceState.DEVICE_STATE_READY) ) host.send_control(uci.SessionDeinitCmd(session_token=0)) - await host.expect_control(uci.SessionDeinitRsp(status=uci.StatusCode.UCI_STATUS_OK)) + await host.expect_control(uci.SessionDeinitRsp(status=uci.Status.OK)) async def data_transfer( @@ -322,7 +322,7 @@ async def data_transfer( seq_num = 0 event = await host.expect_control( - uci.DataCreditNtf( + uci.SessionDataCreditNtf( session_token=int(session_id), credit_availability=uci.CreditAvailability.CREDIT_AVAILABLE, ) @@ -338,7 +338,7 @@ async def data_transfer( ) ) event = await host.expect_control( - uci.DataCreditNtf( + uci.SessionDataCreditNtf( session_token=int(session_id), credit_availability=uci.CreditAvailability.CREDIT_AVAILABLE, ) @@ -351,8 +351,8 @@ async def data_transfer( async def run(address: str, uci_port: int, file: Path): try: - host0 = await Host.connect(address, uci_port, bytes([0, 0])) - host1 = await Host.connect(address, uci_port, bytes([0, 1])) + host0 = await Host.connect(address, uci_port, bytes([0x34, 0x12])) + host1 = await Host.connect(address, uci_port, bytes([0x78, 0x56])) except Exception as e: raise Exception( f"Failed to connect to Pica server at address {address}:{uci_port}\n" diff --git a/tests/helper.py b/tests/helper.py index bbce7ad..f5d9db5 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -18,13 +18,13 @@ from pica.packets import uci async def init(host: Host): await host.expect_control( - uci.DeviceStatusNtf(device_state=uci.DeviceState.DEVICE_STATE_READY) + uci.CoreDeviceStatusNtf(device_state=uci.DeviceState.DEVICE_STATE_READY) ) - host.send_control(uci.DeviceResetCmd(reset_config=uci.ResetConfig.UWBS_RESET)) + host.send_control(uci.CoreDeviceResetCmd(reset_config=uci.ResetConfig.UWBS_RESET)) - await host.expect_control(uci.DeviceResetRsp(status=uci.StatusCode.UCI_STATUS_OK)) + await host.expect_control(uci.CoreDeviceResetRsp(status=uci.Status.OK)) await host.expect_control( - uci.DeviceStatusNtf(device_state=uci.DeviceState.DEVICE_STATE_READY) + uci.CoreDeviceStatusNtf(device_state=uci.DeviceState.DEVICE_STATE_READY) ) diff --git a/tests/ranging.py b/tests/ranging.py index 9af4376..165c5e3 100755 --- a/tests/ranging.py +++ b/tests/ranging.py @@ -32,7 +32,7 @@ async def controller(host: Host, peer: Host): ) ) - await host.expect_control(uci.SessionInitRsp(status=uci.StatusCode.UCI_STATUS_OK)) + await host.expect_control(uci.SessionInitRsp(status=uci.Status.OK)) await host.expect_control( uci.SessionStatusNtf( @@ -90,7 +90,7 @@ async def controller(host: Host, peer: Host): ) await host.expect_control( - uci.SessionSetAppConfigRsp(status=uci.StatusCode.UCI_STATUS_OK, cfg_status=[]) + uci.SessionSetAppConfigRsp(status=uci.Status.OK, cfg_status=[]) ) await host.expect_control( @@ -103,7 +103,7 @@ async def controller(host: Host, peer: Host): host.send_control(uci.SessionStartCmd(session_id=0)) - await host.expect_control(uci.SessionStartRsp(status=uci.StatusCode.UCI_STATUS_OK)) + await host.expect_control(uci.SessionStartRsp(status=uci.Status.OK)) await host.expect_control( uci.SessionStatusNtf( @@ -114,7 +114,7 @@ async def controller(host: Host, peer: Host): ) await host.expect_control( - uci.DeviceStatusNtf(device_state=uci.DeviceState.DEVICE_STATE_ACTIVE) + uci.CoreDeviceStatusNtf(device_state=uci.DeviceState.DEVICE_STATE_ACTIVE) ) for _ in range(1, 3): @@ -123,7 +123,7 @@ async def controller(host: Host, peer: Host): host.send_control(uci.SessionStopCmd(session_id=0)) - await host.expect_control(uci.SessionStopRsp(status=uci.StatusCode.UCI_STATUS_OK)) + await host.expect_control(uci.SessionStopRsp(status=uci.Status.OK)) await host.expect_control( uci.SessionStatusNtf( @@ -134,12 +134,12 @@ async def controller(host: Host, peer: Host): ) await host.expect_control( - uci.DeviceStatusNtf(device_state=uci.DeviceState.DEVICE_STATE_READY) + uci.CoreDeviceStatusNtf(device_state=uci.DeviceState.DEVICE_STATE_READY) ) host.send_control(uci.SessionDeinitCmd(session_token=0)) - await host.expect_control(uci.SessionDeinitRsp(status=uci.StatusCode.UCI_STATUS_OK)) + await host.expect_control(uci.SessionDeinitRsp(status=uci.Status.OK)) async def controlee(host: Host, peer: Host): @@ -151,7 +151,7 @@ async def controlee(host: Host, peer: Host): ) ) - await host.expect_control(uci.SessionInitRsp(status=uci.StatusCode.UCI_STATUS_OK)) + await host.expect_control(uci.SessionInitRsp(status=uci.Status.OK)) await host.expect_control( uci.SessionStatusNtf( @@ -209,7 +209,7 @@ async def controlee(host: Host, peer: Host): ) await host.expect_control( - uci.SessionSetAppConfigRsp(status=uci.StatusCode.UCI_STATUS_OK, cfg_status=[]) + uci.SessionSetAppConfigRsp(status=uci.Status.OK, cfg_status=[]) ) await host.expect_control( @@ -222,7 +222,7 @@ async def controlee(host: Host, peer: Host): host.send_control(uci.SessionStartCmd(session_id=0)) - await host.expect_control(uci.SessionStartRsp(status=uci.StatusCode.UCI_STATUS_OK)) + await host.expect_control(uci.SessionStartRsp(status=uci.Status.OK)) await host.expect_control( uci.SessionStatusNtf( @@ -233,7 +233,7 @@ async def controlee(host: Host, peer: Host): ) await host.expect_control( - uci.DeviceStatusNtf(device_state=uci.DeviceState.DEVICE_STATE_ACTIVE) + uci.CoreDeviceStatusNtf(device_state=uci.DeviceState.DEVICE_STATE_ACTIVE) ) for _ in range(1, 3): @@ -242,7 +242,7 @@ async def controlee(host: Host, peer: Host): host.send_control(uci.SessionStopCmd(session_id=0)) - await host.expect_control(uci.SessionStopRsp(status=uci.StatusCode.UCI_STATUS_OK)) + await host.expect_control(uci.SessionStopRsp(status=uci.Status.OK)) await host.expect_control( uci.SessionStatusNtf( @@ -253,12 +253,12 @@ async def controlee(host: Host, peer: Host): ) await host.expect_control( - uci.DeviceStatusNtf(device_state=uci.DeviceState.DEVICE_STATE_READY) + uci.CoreDeviceStatusNtf(device_state=uci.DeviceState.DEVICE_STATE_READY) ) host.send_control(uci.SessionDeinitCmd(session_token=0)) - await host.expect_control(uci.SessionDeinitRsp(status=uci.StatusCode.UCI_STATUS_OK)) + await host.expect_control(uci.SessionDeinitRsp(status=uci.Status.OK)) async def run(address: str, uci_port: int): -- cgit v1.2.3 From 8f104b02993bde68148f2dcbb38211af1c9ce6ac Mon Sep 17 00:00:00 2001 From: Henri Chataing Date: Tue, 2 Apr 2024 10:28:44 -0700 Subject: Update the format of the notification Update Controller Multicast List The previous version implemented UCI 1.0, now that pica implements UCI 2.0 the format needs to be updated. --- src/device.rs | 8 +++++--- src/uci_packets.pdl | 2 -- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/device.rs b/src/device.rs index e0fcf61..eec1c7f 100644 --- a/src/device.rs +++ b/src/device.rs @@ -690,7 +690,6 @@ impl Device { MacAddress::Short(address) => address, MacAddress::Extended(_) => panic!("Extended address is not supported!"), }, - subsession_id: controlee.sub_session_id, status: update_status, }); }); @@ -726,7 +725,6 @@ impl Device { MacAddress::Short(addr) => addr, MacAddress::Extended(_) => panic!("Extended address is not supported!"), }, - subsession_id: controlee.sub_session_id, status: update_status, }); }); @@ -745,10 +743,13 @@ impl Device { } let tx = self.tx.clone(); tokio::spawn(async move { + // Sleep for 5ms to make sure the notification is not being + // sent before the response. + // TODO(#84) remove the sleep. + time::sleep(Duration::from_millis(5)).await; tx.send( SessionUpdateControllerMulticastListNtfBuilder { controlee_status, - remaining_multicast_list_size: dst_addresses.len() as u8, session_token: session_handle, } .build() @@ -1069,6 +1070,7 @@ impl Device { struct Controlee { short_address: MacAddress, + #[allow(dead_code)] sub_session_id: u32, #[allow(dead_code)] session_key: SubSessionKey, diff --git a/src/uci_packets.pdl b/src/uci_packets.pdl index 4a2f69f..05ba3f3 100644 --- a/src/uci_packets.pdl +++ b/src/uci_packets.pdl @@ -1078,13 +1078,11 @@ test SessionUpdateControllerMulticastListRsp { struct ControleeStatus { mac_address: 8[2], - subsession_id: 32, status: MulticastUpdateStatus, } packet SessionUpdateControllerMulticastListNtf : SessionConfigPacket (mt = NOTIFICATION, oid = UPDATE_CONTROLLER_MULTICAST_LIST) { session_token: 32, // Session ID or Session Handle (based on UWBS version) - remaining_multicast_list_size: 8, _count_(controlee_status): 8, controlee_status: ControleeStatus[], } -- cgit v1.2.3 From 1c4360d02d1496589fb1597a0e49f1b042eaf96c Mon Sep 17 00:00:00 2001 From: Henri Chataing Date: Fri, 5 Apr 2024 16:38:17 -0700 Subject: Update version to 0.1.9 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 67143ef..ca8765e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -648,7 +648,7 @@ dependencies = [ [[package]] name = "pica" -version = "0.1.8" +version = "0.1.9" dependencies = [ "anyhow", "bytes", diff --git a/Cargo.toml b/Cargo.toml index 7fea374..d27cd71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pica" -version = "0.1.8" +version = "0.1.9" edition = "2021" description = "Pica is a virtual UWB Controller implementing the FiRa UCI specification." repository = "https://github.com/google/pica" -- cgit v1.2.3