| // Copyright 2022 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| //! ## Specifications |
| //! - [MAC] FiRa Consortium UWB MAC Technical Requirements |
| //! - [UCI] FiRa Consortium UWB Command Interface Generic Technical specification |
| |
| use crate::uci_packets::AppConfigTlvType; |
| use crate::uci_packets::*; |
| use crate::{MacAddress, PicaCommand}; |
| 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; |
| |
| 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; |
| |
| #[derive(Copy, Clone, FromPrimitive, PartialEq, Eq)] |
| pub enum DeviceType { |
| /// [MAC] 5.1.1 Device controlling the ranging features through Control Messages |
| Controller, |
| /// [MAC] 5.1.2 Device utilizing the ranging features set through Control Messages |
| Controlee, |
| } |
| |
| #[derive(Copy, Clone, FromPrimitive)] |
| 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, |
| } |
| |
| /// cf. [UCI] 8.3 Table 29 |
| #[derive(Clone)] |
| pub struct AppConfig { |
| /// Copy of the valid App Configuration parameters provided by host |
| raw: HashMap<AppConfigTlvType, Vec<u8>>, |
| |
| device_type: DeviceType, |
| _device_role: DeviceRole, |
| mac_address_mode: MacAddressMode, |
| device_mac_address: MacAddress, |
| number_of_controlees: usize, |
| dst_mac_addresses: Vec<MacAddress>, |
| ranging_interval: time::Duration, |
| slot_duration: u16, |
| channel_number: ChannelNumber, |
| multi_node_mode: MultiNodeMode, |
| } |
| |
| 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, |
| } |
| } |
| } |
| |
| fn app_config_has_mandatory_parameters(configs: &[AppConfigParameter]) -> 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 == AppConfigTlvType::from_u8(param.id).unwrap()) |
| }) |
| } |
| |
| 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::RangingInterval => { |
| 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::Extend(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::Extend(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() |
| } |
| id => { |
| println!("Ignored AppConfig parameter {}", id); |
| return Err(StatusCode::UciStatusInvalidParam); |
| } |
| }; |
| |
| self.raw.insert(id, value.to_vec()); |
| |
| Ok(()) |
| } |
| |
| fn get_config(&self, id: AppConfigTlvType) -> Option<Vec<u8>> { |
| self.raw.get(&id).cloned() |
| } |
| |
| fn extend(&mut self, configs: &[AppConfigParameter]) -> Vec<AppConfigStatus> { |
| 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 AppConfigTlvType::from_u8(config.id) { |
| Some(id) => match self.set_config(id, &config.value) { |
| Ok(_) => (), |
| Err(status) => invalid_parameters.push(AppConfigStatus { |
| config_id: config.id, |
| status, |
| }), |
| }, |
| None => invalid_parameters.push(AppConfigStatus { |
| config_id: config.id, |
| status: StatusCode::UciStatusInvalidParam, |
| }), |
| }; |
| invalid_parameters |
| }) |
| } |
| } |
| |
| pub struct Session { |
| /// cf. [UCI] 7.1 |
| state: SessionState, |
| /// cf. [UCI] 7.2 Table 13: 4 octets unique random number generated by application |
| id: u32, |
| device_handle: usize, |
| |
| session_type: SessionType, |
| pub sequence_number: u32, |
| app_config: AppConfig, |
| ranging_task: Option<JoinHandle<()>>, |
| tx: mpsc::Sender<UciPacketPacket>, |
| pica_tx: mpsc::Sender<PicaCommand>, |
| } |
| |
| impl Session { |
| pub fn new( |
| id: u32, |
| session_type: SessionType, |
| device_handle: usize, |
| tx: mpsc::Sender<UciPacketPacket>, |
| pica_tx: mpsc::Sender<PicaCommand>, |
| ) -> Self { |
| Self { |
| state: SessionState::SessionStateDeinit, |
| id, |
| device_handle, |
| session_type, |
| sequence_number: 0, |
| app_config: AppConfig::default(), |
| ranging_task: None, |
| tx, |
| pica_tx, |
| } |
| } |
| |
| fn set_state(&mut self, session_state: SessionState) { |
| // No transition: ignore |
| if session_state == self.state { |
| return; |
| } |
| |
| // Send status notification |
| self.state = session_state; |
| let tx = self.tx.clone(); |
| let session_id = self.id; |
| tokio::spawn(async move { |
| tx.send( |
| SessionStatusNtfBuilder { |
| session_id, |
| session_state, |
| reason_code: ReasonCode::StateChangeWithSessionManagementCommands, |
| } |
| .build() |
| .into(), |
| ) |
| .await |
| .unwrap() |
| }); |
| } |
| |
| pub fn get_dst_mac_addresses(&self) -> &Vec<MacAddress> { |
| &self.app_config.dst_mac_addresses |
| } |
| |
| pub fn init(&mut self) { |
| self.set_state(SessionState::SessionStateInit); |
| } |
| |
| fn command_set_app_config( |
| &mut self, |
| cmd: SessionSetAppConfigCmdPacket, |
| ) -> SessionSetAppConfigRspPacket { |
| // TODO properly handle these asserts |
| println!( |
| "[{}:0x{:x}] Session Set App Config", |
| self.device_handle, self.id |
| ); |
| assert_eq!(self.id, cmd.get_session_id()); |
| assert_eq!(self.session_type, SessionType::FiraRangingSession); |
| |
| let (status, invalid_parameters) = if self.state != SessionState::SessionStateInit { |
| (StatusCode::UciStatusRejected, Vec::new()) |
| } else { |
| let mut app_config = self.app_config.clone(); |
| let invalid_parameters = app_config.extend(cmd.get_parameters()); |
| if invalid_parameters.is_empty() { |
| self.app_config = app_config; |
| self.set_state(SessionState::SessionStateIdle); |
| (StatusCode::UciStatusOk, invalid_parameters) |
| } else { |
| (StatusCode::UciStatusInvalidParam, invalid_parameters) |
| } |
| }; |
| |
| SessionSetAppConfigRspBuilder { |
| status, |
| parameters: invalid_parameters, |
| } |
| .build() |
| } |
| |
| fn command_get_app_config( |
| &self, |
| cmd: SessionGetAppConfigCmdPacket, |
| ) -> SessionGetAppConfigRspPacket { |
| println!( |
| "[{}:0x{:x}] Session Get App Config", |
| self.device_handle, self.id |
| ); |
| assert_eq!(self.id, cmd.get_session_id()); |
| |
| let (status, valid_parameters) = { |
| let (valid_parameters, invalid_parameters) = cmd.get_parameters().iter().fold( |
| (Vec::new(), Vec::new()), |
| |(mut valid_parameters, mut invalid_parameters), config_id| { |
| match AppConfigTlvType::from_u8(*config_id) |
| .and_then(|id| self.app_config.get_config(id)) |
| { |
| Some(value) => valid_parameters.push(AppConfigParameter { |
| id: *config_id, |
| value, |
| }), |
| None => invalid_parameters.push(AppConfigParameter { |
| id: *config_id, |
| value: Vec::new(), |
| }), |
| } |
| (valid_parameters, invalid_parameters) |
| }, |
| ); |
| if invalid_parameters.is_empty() { |
| (StatusCode::UciStatusOk, valid_parameters) |
| } else { |
| (StatusCode::UciStatusFailed, Vec::new()) |
| } |
| }; |
| SessionGetAppConfigRspBuilder { |
| status, |
| parameters: valid_parameters, |
| } |
| .build() |
| } |
| |
| fn command_get_state(&self, cmd: SessionGetStateCmdPacket) -> SessionGetStateRspPacket { |
| println!("[{}:0x{:x}] Session Get State", self.device_handle, self.id); |
| assert_eq!(self.id, cmd.get_session_id()); |
| SessionGetStateRspBuilder { |
| status: StatusCode::UciStatusOk, |
| session_state: self.state, |
| } |
| .build() |
| } |
| |
| fn command_update_controller_multicast_list( |
| &mut self, |
| cmd: SessionUpdateControllerMulticastListCmdPacket, |
| ) -> SessionUpdateControllerMulticastListRspPacket { |
| println!( |
| "[{}:0x{:x}] Session Update Controller Multicast List", |
| self.device_handle, self.id |
| ); |
| assert_eq!(self.id, cmd.get_session_id()); |
| let status = { |
| 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) |
| { |
| StatusCode::UciStatusRejected |
| } else { |
| let action = UpdateMulticastListAction::from_u8(cmd.get_action()).unwrap(); |
| let controlees = cmd.get_controlees(); |
| |
| if action == UpdateMulticastListAction::Add |
| && (controlees.len() + self.app_config.number_of_controlees) |
| > MAX_NUMBER_OF_CONTROLEES |
| { |
| StatusCode::UciStatusMulticastListFull |
| } else { |
| // TODO properly Add/Remove the controlees and send notif (?) |
| StatusCode::UciStatusOk |
| } |
| } |
| }; |
| SessionUpdateControllerMulticastListRspBuilder { status }.build() |
| } |
| |
| fn command_range_start(&mut self, cmd: RangeStartCmdPacket) -> RangeStartRspPacket { |
| println!("[{}: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 = self.app_config.ranging_interval; |
| 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); |
| StatusCode::UciStatusOk |
| }; |
| RangeStartRspBuilder { status }.build() |
| } |
| |
| 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: RangeStopCmdPacket) -> RangeStopRspPacket { |
| println!("[{}: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); |
| StatusCode::UciStatusOk |
| }; |
| RangeStopRspBuilder { status }.build() |
| } |
| |
| fn command_get_ranging_count( |
| &self, |
| cmd: RangeGetRangingCountCmdPacket, |
| ) -> RangeGetRangingCountRspPacket { |
| println!( |
| "[{}:0x{:x}] Range Get Ranging Count", |
| self.device_handle, self.id |
| ); |
| assert_eq!(self.id, cmd.get_session_id()); |
| |
| RangeGetRangingCountRspBuilder { |
| status: StatusCode::UciStatusOk, |
| count: self.sequence_number, |
| } |
| .build() |
| } |
| |
| pub fn session_command(&mut self, cmd: SessionCommandPacket) -> SessionResponsePacket { |
| match cmd.specialize() { |
| SessionCommandChild::SessionSetAppConfigCmd(cmd) => { |
| self.command_set_app_config(cmd).into() |
| } |
| SessionCommandChild::SessionGetAppConfigCmd(cmd) => { |
| self.command_get_app_config(cmd).into() |
| } |
| SessionCommandChild::SessionGetStateCmd(cmd) => self.command_get_state(cmd).into(), |
| SessionCommandChild::SessionUpdateControllerMulticastListCmd(cmd) => { |
| self.command_update_controller_multicast_list(cmd).into() |
| } |
| _ => panic!("Unsupported session command"), |
| } |
| } |
| |
| pub fn ranging_command(&mut self, cmd: RangingCommandPacket) -> RangingResponsePacket { |
| match cmd.specialize() { |
| RangingCommandChild::RangeStartCmd(cmd) => self.command_range_start(cmd).into(), |
| RangingCommandChild::RangeStopCmd(cmd) => self.command_range_stop(cmd).into(), |
| RangingCommandChild::RangeGetRangingCountCmd(cmd) => { |
| self.command_get_ranging_count(cmd).into() |
| } |
| _ => panic!("Unsupported ranging command"), |
| } |
| } |
| } |
| |
| impl Drop for Session { |
| fn drop(&mut self) { |
| // Make sure to abort the ranging task when dropping the session, |
| // the default behaviour when dropping a task handle is to detach |
| // the task, which is undesirable. |
| self.stop_ranging_task(); |
| self.set_state(SessionState::SessionStateDeinit); |
| } |
| } |