blob: 3d2f27df04df7a8d2953ba8b4d0f3c84fe331666 [file] [log] [blame]
// 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.
use crate::position::Position;
use crate::uci_packets::*;
use crate::MacAddress;
use crate::PicaCommand;
use std::collections::HashMap;
use std::iter::Extend;
use tokio::sync::mpsc;
use num_traits::FromPrimitive;
use super::session::{Session, MAX_SESSION};
pub const MAX_DEVICE: usize = 4;
const UCI_VERSION: u16 = 0x1001; // Version 1.1.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
// Capabilities are vendor defined, Android parses capabilities
// according to these definitions:
// /android/packages/modules/Uwb/service/java/com/android/server/uwb/config/CapabilityParam.java
pub const DEFAULT_CAPS_INFO: &[(CapTlvType, &[u8])] = &[
// Fira params
(CapTlvType::SupportedFiraPhyVersionRange, &[1, 1, 1, 3]), // 1.1 - 1.3
(CapTlvType::SupportedFiraMacVersionRange, &[1, 1, 1, 3]), // 1.1 - 1.3
(CapTlvType::SupportedDeviceRoles, &[0x3]), // INTIATOR | RESPONDER
(CapTlvType::SupportedRangingMethod, &[0x1f]), // DS_TWR_NON_DEFERRED | SS_TWR_NON_DEFERRED | DS_TWR_DEFERRED | SS_TWR_DEFERRED | OWR
(CapTlvType::SupportedStsConfig, &[0x7]), // STATIC_STS | DYNAMIC_STS | DYNAMIC_STS_RESPONDER_SPECIFIC_SUBSESSION_KEY
(CapTlvType::SupportedMultiNodeModes, &[0xff]),
(CapTlvType::SupportedBlockStriding, &[0x1]),
(CapTlvType::SupportedUwbInitiationTime, &[0x01]),
(CapTlvType::SupportedChannels, &[0xff]),
(CapTlvType::SupportedRframeConfig, &[0xff]),
(CapTlvType::SupportedBprfParameterSets, &[0xff]),
(CapTlvType::SupportedHprfParameterSets, &[0xff]),
(CapTlvType::SupportedCcConstraintLength, &[0xff]),
(CapTlvType::SupportedAoa, &[0xff]),
(CapTlvType::SupportedAoaResultReqAntennaInterleaving, &[0x1]),
(CapTlvType::SupportedExtendedMacAddress, &[0x1]),
// CCC params
(CapTlvType::CccSupportedVersions, &[1, 0]),
(CapTlvType::CccSupportedUwbConfigs, &[0]),
(CapTlvType::CccSupportedPulseShapeCombos, &[0]),
(CapTlvType::CccSupportedRanMultiplier, &[0, 0, 0, 0]),
(CapTlvType::CccSupportedChapsPerSlot, &[0xff]),
(CapTlvType::CccSupportedSyncCodes, &[0xff, 0xff, 0xff, 0xff]),
(CapTlvType::CccSupportedChannels, &[0xff]),
(
CapTlvType::CccSupportedHoppingConfigModesAndSequences,
&[0xff],
),
];
pub struct Device {
handle: usize,
pub mac_address: MacAddress,
pub position: Position,
/// [UCI] 5. UWBS Device State Machine
state: DeviceState,
sessions: HashMap<u32, Session>,
pub tx: mpsc::Sender<UciPacketPacket>,
pica_tx: mpsc::Sender<PicaCommand>,
config: HashMap<u8, Vec<u8>>,
country_code: [u8; 2],
n_active_sessions: usize,
}
impl Device {
pub fn new(
device_handle: usize,
tx: mpsc::Sender<UciPacketPacket>,
pica_tx: mpsc::Sender<PicaCommand>,
) -> Self {
let mac_address = {
let handle = device_handle as u16;
MacAddress::Short(handle.to_be_bytes())
};
Device {
handle: device_handle,
mac_address,
position: Position::default(),
state: DeviceState::DeviceStateError, // Will be overwitten
sessions: Default::default(),
tx,
pica_tx,
config: HashMap::new(),
country_code: Default::default(),
n_active_sessions: 0,
}
}
fn set_state(&mut self, device_state: DeviceState) {
// No transition: ignore
if device_state == self.state {
return;
}
// Send status notification
self.state = device_state;
let tx = self.tx.clone();
tokio::spawn(async move {
tx.send(DeviceStatusNtfBuilder { device_state }.build().into())
.await
.unwrap()
});
}
pub fn init(&mut self) {
self.set_state(DeviceState::DeviceStateReady);
}
pub fn get_session(&self, session_id: u32) -> Option<&Session> {
self.sessions.get(&session_id)
}
pub fn get_session_mut(&mut self, session_id: u32) -> Option<&mut Session> {
self.sessions.get_mut(&session_id)
}
// 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: DeviceResetCmdPacket) -> DeviceResetRspPacket {
let reset_config = cmd.get_reset_config();
println!("[{}] DeviceReset", self.handle);
println!(" reset_config={}", reset_config);
let status = match reset_config {
ResetConfig::UwbsReset => StatusCode::UciStatusOk,
};
*self = Device::new(self.handle, self.tx.clone(), self.pica_tx.clone());
DeviceResetRspBuilder { status }.build()
}
fn command_get_device_info(&self, _cmd: GetDeviceInfoCmdPacket) -> GetDeviceInfoRspPacket {
// TODO: Implement a fancy build time state machine instead of crash at runtime
println!("[{}] GetDeviceInfo", self.handle);
assert_eq!(self.state, DeviceState::DeviceStateReady);
GetDeviceInfoRspBuilder {
status: StatusCode::UciStatusOk,
uci_version: UCI_VERSION,
mac_version: MAC_VERSION,
phy_version: PHY_VERSION,
uci_test_version: TEST_VERSION,
vendor_spec_info: Vec::new(),
}
.build()
}
pub fn command_get_caps_info(&self, cmd: GetCapsInfoCmdPacket) -> GetCapsInfoRspPacket {
println!("[{}] GetCapsInfo", self.handle);
assert_eq!(
cmd.get_packet_boundary_flag(),
PacketBoundaryFlag::Complete,
"Boundary flag is true, implement fragmentation"
);
let caps = DEFAULT_CAPS_INFO
.iter()
.map(|(id, value)| CapTlv {
t: *id,
v: (*value).into(),
})
.collect();
GetCapsInfoRspBuilder {
status: StatusCode::UciStatusOk,
tlvs: caps,
}
.build()
}
pub fn command_set_config(&mut self, cmd: SetConfigCmdPacket) -> SetConfigRspPacket {
println!("[{}] SetConfig", self.handle);
assert_eq!(self.state, DeviceState::DeviceStateReady); // UCI 6.3
assert_eq!(
cmd.get_packet_boundary_flag(),
PacketBoundaryFlag::Complete,
"Boundary flag is true, implement fragmentation"
);
let (valid_parameters, invalid_config_status) = cmd.get_parameters().iter().fold(
(HashMap::new(), Vec::new()),
|(mut valid_parameters, mut invalid_config_status), param| {
let id = param.id;
match DeviceConfigId::from_u8(id) {
Some(_) => {
// TODO: DeviceState is a read only parameter
valid_parameters.insert(param.id, param.value.clone());
}
None => {
// TODO: silently ignore vendor parameter
invalid_config_status.push(DeviceConfigStatus {
parameter_id: id,
status: StatusCode::UciStatusInvalidParam,
})
}
};
(valid_parameters, invalid_config_status)
},
);
let (status, parameters) = if invalid_config_status.is_empty() {
self.config.extend(valid_parameters.into_iter());
(StatusCode::UciStatusOk, Vec::new())
} else {
(StatusCode::UciStatusInvalidParam, invalid_config_status)
};
SetConfigRspBuilder { status, parameters }.build()
}
pub fn command_get_config(&self, cmd: GetConfigCmdPacket) -> GetConfigRspPacket {
println!("[{}] GetConfig", self.handle);
assert_eq!(
cmd.get_packet_boundary_flag(),
PacketBoundaryFlag::Complete,
"Boundary flag is true, implement fragmentation"
);
let ids = cmd.get_parameter_ids();
// TODO: do this config shall be set on device reset
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 self.config.get(id) {
Some(value) => valid_parameters.push(DeviceParameter {
id: *id,
value: value.clone(),
}),
None => invalid_parameters.push(DeviceParameter {
id: *id,
value: Vec::new(),
}),
}
(valid_parameters, invalid_parameters)
},
);
let (status, parameters) = if invalid_parameters.is_empty() {
(StatusCode::UciStatusOk, valid_parameters)
} else {
(StatusCode::UciStatusInvalidParam, invalid_parameters)
};
GetConfigRspBuilder { status, parameters }.build()
}
fn command_session_init(&mut self, cmd: SessionInitCmdPacket) -> SessionInitRspPacket {
let session_id = cmd.get_session_id();
let session_type = cmd.get_session_type();
println!("[{}] Session init", self.handle);
println!(" session_id=0x{:x}", session_id);
println!(" session_type={}", session_type);
let status = if self.sessions.len() >= MAX_SESSION {
StatusCode::UciStatusMaxSessionsExceeded
} else {
match self.sessions.insert(
session_id,
Session::new(
session_id,
session_type,
self.handle,
self.tx.clone(),
self.pica_tx.clone(),
),
) {
Some(_) => StatusCode::UciStatusSessionDuplicate,
None => {
// Should not fail
self.get_session_mut(session_id).unwrap().init();
StatusCode::UciStatusOk
}
}
};
SessionInitRspBuilder { status }.build()
}
fn command_session_deinit(&mut self, cmd: SessionDeinitCmdPacket) -> SessionDeinitRspPacket {
let session_id = cmd.get_session_id();
println!("[{}] Session deinit", self.handle);
println!(" session_id=0x{:x}", session_id);
let status = if self.sessions.remove(&session_id).is_some() {
StatusCode::UciStatusOk
} else {
StatusCode::UciStatusSessionNotExist
};
SessionDeinitRspBuilder { status }.build()
}
fn command_session_get_count(
&self,
_cmd: SessionGetCountCmdPacket,
) -> SessionGetCountRspPacket {
println!("[{}] Session get count", self.handle);
SessionGetCountRspBuilder {
status: StatusCode::UciStatusOk,
session_count: self.sessions.len() as u8,
}
.build()
}
fn command_set_country_code(
&mut self,
cmd: AndroidSetCountryCodeCmdPacket,
) -> AndroidSetCountryCodeRspPacket {
let country_code = *cmd.get_country_code();
println!("[{}] Set country code", self.handle);
println!(" country_code={},{}", country_code[0], country_code[1]);
self.country_code = country_code;
AndroidSetCountryCodeRspBuilder {
status: StatusCode::UciStatusOk,
}
.build()
}
fn command_get_power_stats(
&mut self,
_cmd: AndroidGetPowerStatsCmdPacket,
) -> AndroidGetPowerStatsRspPacket {
println!("[{}] Get power stats", self.handle);
// TODO
AndroidGetPowerStatsRspBuilder {
stats: PowerStats {
status: StatusCode::UciStatusOk,
idle_time_ms: 0,
tx_time_ms: 0,
rx_time_ms: 0,
total_wake_count: 0,
},
}
.build()
}
pub fn command(&mut self, cmd: UciCommandPacket) -> UciResponsePacket {
match cmd.specialize() {
// Handle commands for this device
UciCommandChild::CoreCommand(core_command) => match core_command.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"),
},
// Handle commands for session management
UciCommandChild::SessionCommand(session_command) => {
// Session commands directly handled at Device level
match session_command.specialize() {
SessionCommandChild::SessionInitCmd(cmd) => {
return self.command_session_init(cmd).into();
}
SessionCommandChild::SessionDeinitCmd(cmd) => {
return self.command_session_deinit(cmd).into();
}
SessionCommandChild::SessionGetCountCmd(cmd) => {
return self.command_session_get_count(cmd).into();
}
_ => {}
}
// Common code for retrieving the session_id in the command
let session_id = match session_command.specialize() {
SessionCommandChild::SessionSetAppConfigCmd(cmd) => cmd.get_session_id(),
SessionCommandChild::SessionGetAppConfigCmd(cmd) => cmd.get_session_id(),
SessionCommandChild::SessionGetStateCmd(cmd) => cmd.get_session_id(),
SessionCommandChild::SessionUpdateControllerMulticastListCmd(cmd) => {
cmd.get_session_id()
}
_ => panic!("Unsupported session command type"),
};
if let Some(session) = self.get_session_mut(session_id) {
// There is a session matching the session_id in the command
// Pass the command through
match session_command.specialize() {
SessionCommandChild::SessionSetAppConfigCmd(_)
| SessionCommandChild::SessionGetAppConfigCmd(_)
| SessionCommandChild::SessionGetStateCmd(_)
| SessionCommandChild::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() {
SessionCommandChild::SessionSetAppConfigCmd(_) => {
SessionSetAppConfigRspBuilder {
status,
parameters: Vec::new(),
}
.build()
.into()
}
SessionCommandChild::SessionGetAppConfigCmd(_) => {
SessionGetAppConfigRspBuilder {
status,
parameters: Vec::new(),
}
.build()
.into()
}
SessionCommandChild::SessionGetStateCmd(_) => SessionGetStateRspBuilder {
status,
session_state: SessionState::SessionStateDeinit,
}
.build()
.into(),
SessionCommandChild::SessionUpdateControllerMulticastListCmd(_) => {
SessionUpdateControllerMulticastListRspBuilder { status }
.build()
.into()
}
_ => panic!("Unsupported session command"),
}
}
}
UciCommandChild::RangingCommand(ranging_command) => {
let session_id = ranging_command.get_session_id();
if let Some(session) = self.get_session_mut(session_id) {
// Forward to the proper session
let response = session.ranging_command(ranging_command);
match response.specialize() {
RangingResponseChild::RangeStartRsp(rsp)
if rsp.get_status() == StatusCode::UciStatusOk =>
{
self.n_active_sessions += 1;
self.set_state(DeviceState::DeviceStateActive);
}
RangingResponseChild::RangeStopRsp(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() {
RangingCommandChild::RangeStartCmd(_) => {
RangeStartRspBuilder { status }.build().into()
}
RangingCommandChild::RangeStopCmd(_) => {
RangeStopRspBuilder { status }.build().into()
}
RangingCommandChild::RangeGetRangingCountCmd(_) => {
RangeGetRangingCountRspBuilder { status, count: 0 }
.build()
.into()
}
_ => panic!("Unsupported ranging 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"),
}
}
// TODO: Handle properly without panic
_ => UciResponseBuilder {
group_id: GroupId::Core,
opcode: 0,
payload: None,
}
.build(),
}
}
}