blob: 51cbdcb6af1daf675f5f3bd28b3e28a3ba3375b6 [file] [log] [blame]
// Copyright 2022, The Android Open Source Project
//
// 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
//
// http://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 std::convert::TryFrom;
use bytes::Bytes;
use log::error;
use crate::error::{Error, Result};
use crate::params::uci_packets::{
AppConfigTlv, AppConfigTlvType, Controlees, CountryCode, DeviceConfigId, DeviceConfigTlv,
RadarConfigTlv, RadarConfigTlvType, ResetConfig, SessionId, SessionToken, SessionType,
UpdateMulticastListAction, UpdateTime,
};
use uwb_uci_packets::{
build_data_transfer_phase_config_cmd, build_session_set_hybrid_controller_config_cmd,
build_session_update_controller_multicast_list_cmd, ControleePhaseList, GroupId, MessageType,
PhaseList,
};
/// The enum to represent the UCI commands. The definition of each field should follow UCI spec.
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq)]
pub enum UciCommand {
DeviceReset {
reset_config: ResetConfig,
},
CoreGetDeviceInfo,
CoreGetCapsInfo,
CoreSetConfig {
config_tlvs: Vec<DeviceConfigTlv>,
},
CoreGetConfig {
cfg_id: Vec<DeviceConfigId>,
},
CoreQueryTimeStamp,
SessionInit {
session_id: SessionId,
session_type: SessionType,
},
SessionDeinit {
session_token: SessionToken,
},
SessionSetAppConfig {
session_token: SessionToken,
config_tlvs: Vec<AppConfigTlv>,
},
SessionGetAppConfig {
session_token: SessionToken,
app_cfg: Vec<AppConfigTlvType>,
},
SessionGetCount,
SessionGetState {
session_token: SessionToken,
},
SessionUpdateControllerMulticastList {
session_token: SessionToken,
action: UpdateMulticastListAction,
controlees: Controlees,
is_multicast_list_ntf_v2_supported: bool,
},
SessionUpdateDtTagRangingRounds {
session_token: u32,
ranging_round_indexes: Vec<u8>,
},
SessionQueryMaxDataSize {
session_token: SessionToken,
},
SessionStart {
session_token: SessionToken,
},
SessionStop {
session_token: SessionToken,
},
SessionGetRangingCount {
session_token: SessionToken,
},
SessionSetHybridControllerConfig {
session_token: SessionToken,
message_control: u8,
number_of_phases: u8,
update_time: UpdateTime,
phase_list: PhaseList,
},
SessionSetHybridControleeConfig {
session_token: SessionToken,
controlee_phase_list: Vec<ControleePhaseList>,
},
SessionDataTransferPhaseConfig {
session_token: SessionToken,
dtpcm_repetition: u8,
data_transfer_control: u8,
dtpml_size: u8,
mac_address: Vec<u8>,
slot_bitmap: Vec<u8>,
},
AndroidSetCountryCode {
country_code: CountryCode,
},
AndroidGetPowerStats,
AndroidSetRadarConfig {
session_token: SessionToken,
config_tlvs: Vec<RadarConfigTlv>,
},
AndroidGetRadarConfig {
session_token: SessionToken,
radar_cfg: Vec<RadarConfigTlvType>,
},
RawUciCmd {
mt: u32,
gid: u32,
oid: u32,
payload: Vec<u8>,
},
}
impl TryFrom<UciCommand> for uwb_uci_packets::UciControlPacket {
type Error = Error;
fn try_from(cmd: UciCommand) -> std::result::Result<Self, Self::Error> {
let packet = match cmd {
// UCI Session Config Commands
UciCommand::SessionInit { session_id, session_type } => {
uwb_uci_packets::SessionInitCmdBuilder { session_id, session_type }.build().into()
}
UciCommand::SessionDeinit { session_token } => {
uwb_uci_packets::SessionDeinitCmdBuilder { session_token }.build().into()
}
UciCommand::CoreGetDeviceInfo => {
uwb_uci_packets::GetDeviceInfoCmdBuilder {}.build().into()
}
UciCommand::CoreGetCapsInfo => uwb_uci_packets::GetCapsInfoCmdBuilder {}.build().into(),
UciCommand::SessionGetState { session_token } => {
uwb_uci_packets::SessionGetStateCmdBuilder { session_token }.build().into()
}
UciCommand::SessionUpdateControllerMulticastList {
session_token,
action,
controlees,
..
} => build_session_update_controller_multicast_list_cmd(
session_token,
action,
controlees,
)
.map_err(|_| Error::BadParameters)?
.into(),
UciCommand::CoreSetConfig { config_tlvs } => {
uwb_uci_packets::SetConfigCmdBuilder { tlvs: config_tlvs }.build().into()
}
UciCommand::CoreGetConfig { cfg_id } => uwb_uci_packets::GetConfigCmdBuilder {
cfg_id: cfg_id.into_iter().map(u8::from).collect(),
}
.build()
.into(),
UciCommand::CoreQueryTimeStamp {} => {
uwb_uci_packets::CoreQueryTimeStampCmdBuilder {}.build().into()
}
UciCommand::SessionSetAppConfig { session_token, config_tlvs } => {
uwb_uci_packets::SessionSetAppConfigCmdBuilder {
session_token,
tlvs: config_tlvs.into_iter().map(|tlv| tlv.into_inner()).collect(),
}
.build()
.into()
}
UciCommand::SessionGetAppConfig { session_token, app_cfg } => {
uwb_uci_packets::SessionGetAppConfigCmdBuilder {
session_token,
app_cfg: app_cfg.into_iter().map(u8::from).collect(),
}
.build()
.into()
}
UciCommand::AndroidSetRadarConfig { session_token, config_tlvs } => {
uwb_uci_packets::AndroidSetRadarConfigCmdBuilder {
session_token,
tlvs: config_tlvs,
}
.build()
.into()
}
UciCommand::AndroidGetRadarConfig { session_token, radar_cfg } => {
uwb_uci_packets::AndroidGetRadarConfigCmdBuilder {
session_token,
tlvs: radar_cfg.into_iter().map(u8::from).collect(),
}
.build()
.into()
}
UciCommand::SessionUpdateDtTagRangingRounds {
session_token,
ranging_round_indexes,
} => uwb_uci_packets::SessionUpdateDtTagRangingRoundsCmdBuilder {
session_token,
ranging_round_indexes,
}
.build()
.into(),
UciCommand::AndroidGetPowerStats => {
uwb_uci_packets::AndroidGetPowerStatsCmdBuilder {}.build().into()
}
UciCommand::RawUciCmd { mt, gid, oid, payload } => {
build_raw_uci_cmd_packet(mt, gid, oid, payload)?
}
UciCommand::SessionGetCount => {
uwb_uci_packets::SessionGetCountCmdBuilder {}.build().into()
}
UciCommand::AndroidSetCountryCode { country_code } => {
uwb_uci_packets::AndroidSetCountryCodeCmdBuilder {
country_code: country_code.into(),
}
.build()
.into()
}
UciCommand::DeviceReset { reset_config } => {
uwb_uci_packets::DeviceResetCmdBuilder { reset_config }.build().into()
}
// UCI Session Control Commands
UciCommand::SessionStart { session_token } => {
uwb_uci_packets::SessionStartCmdBuilder { session_token }.build().into()
}
UciCommand::SessionStop { session_token } => {
uwb_uci_packets::SessionStopCmdBuilder { session_token }.build().into()
}
UciCommand::SessionGetRangingCount { session_token } => {
uwb_uci_packets::SessionGetRangingCountCmdBuilder { session_token }.build().into()
}
UciCommand::SessionQueryMaxDataSize { session_token } => {
uwb_uci_packets::SessionQueryMaxDataSizeCmdBuilder { session_token }.build().into()
}
UciCommand::SessionSetHybridControllerConfig {
session_token,
message_control,
number_of_phases,
update_time,
phase_list,
} => build_session_set_hybrid_controller_config_cmd(
session_token,
message_control,
number_of_phases,
update_time.into(),
phase_list,
)
.map_err(|_| Error::BadParameters)?
.into(),
UciCommand::SessionSetHybridControleeConfig { session_token, controlee_phase_list } => {
uwb_uci_packets::SessionSetHybridControleeConfigCmdBuilder {
session_token,
controlee_phase_list,
}
.build()
.into()
}
UciCommand::SessionDataTransferPhaseConfig {
session_token,
dtpcm_repetition,
data_transfer_control,
dtpml_size,
mac_address,
slot_bitmap,
} => build_data_transfer_phase_config_cmd(
session_token,
dtpcm_repetition,
data_transfer_control,
dtpml_size,
mac_address,
slot_bitmap,
)
.map_err(|_| Error::BadParameters)?
.into(),
};
Ok(packet)
}
}
fn build_raw_uci_cmd_packet(
mt: u32,
gid: u32,
oid: u32,
payload: Vec<u8>,
) -> Result<uwb_uci_packets::UciControlPacket> {
let group_id = u8::try_from(gid).or(Err(0)).and_then(GroupId::try_from).map_err(|_| {
error!("Invalid GroupId: {}", gid);
Error::BadParameters
})?;
let payload = if payload.is_empty() { None } else { Some(Bytes::from(payload)) };
let opcode = u8::try_from(oid).map_err(|_| {
error!("Invalid opcod: {}", oid);
Error::BadParameters
})?;
let message_type =
u8::try_from(mt).or(Err(0)).and_then(MessageType::try_from).map_err(|_| {
error!("Invalid MessageType: {}", mt);
Error::BadParameters
})?;
match uwb_uci_packets::build_uci_control_packet(message_type, group_id, opcode, payload) {
Some(cmd) => Ok(cmd),
None => Err(Error::BadParameters),
}
}
#[cfg(test)]
mod tests {
use super::*;
use uwb_uci_packets::PhaseListShortMacAddress;
#[test]
fn test_build_raw_uci_cmd() {
let payload = vec![0x01, 0x02];
let cmd_packet = build_raw_uci_cmd_packet(1, 9, 0, payload.clone()).unwrap();
assert_eq!(payload, cmd_packet.to_raw_payload());
}
#[test]
fn test_convert_uci_cmd_to_packets() {
let mut cmd = UciCommand::DeviceReset { reset_config: ResetConfig::UwbsReset };
let mut packet = uwb_uci_packets::UciControlPacket::try_from(cmd.clone()).unwrap();
assert_eq!(
packet,
uwb_uci_packets::DeviceResetCmdBuilder { reset_config: ResetConfig::UwbsReset }
.build()
.into()
);
cmd = UciCommand::CoreGetDeviceInfo {};
packet = uwb_uci_packets::UciControlPacket::try_from(cmd.clone()).unwrap();
assert_eq!(packet, uwb_uci_packets::GetDeviceInfoCmdBuilder {}.build().into());
cmd = UciCommand::CoreGetCapsInfo {};
packet = uwb_uci_packets::UciControlPacket::try_from(cmd.clone()).unwrap();
assert_eq!(packet, uwb_uci_packets::GetCapsInfoCmdBuilder {}.build().into());
let device_cfg_tlv = DeviceConfigTlv { cfg_id: DeviceConfigId::DeviceState, v: vec![0] };
cmd = UciCommand::CoreSetConfig { config_tlvs: vec![device_cfg_tlv.clone()] };
packet = uwb_uci_packets::UciControlPacket::try_from(cmd.clone()).unwrap();
assert_eq!(
packet,
uwb_uci_packets::SetConfigCmdBuilder { tlvs: vec![device_cfg_tlv] }.build().into()
);
cmd = UciCommand::CoreGetConfig { cfg_id: vec![DeviceConfigId::DeviceState] };
packet = uwb_uci_packets::UciControlPacket::try_from(cmd.clone()).unwrap();
assert_eq!(packet, uwb_uci_packets::GetConfigCmdBuilder { cfg_id: vec![0] }.build().into());
cmd = UciCommand::SessionInit {
session_id: 1,
session_type: SessionType::FiraRangingSession,
};
packet = uwb_uci_packets::UciControlPacket::try_from(cmd.clone()).unwrap();
assert_eq!(
packet,
uwb_uci_packets::SessionInitCmdBuilder {
session_id: 1,
session_type: SessionType::FiraRangingSession
}
.build()
.into()
);
cmd = UciCommand::SessionDeinit { session_token: 1 };
packet = uwb_uci_packets::UciControlPacket::try_from(cmd.clone()).unwrap();
assert_eq!(
packet,
uwb_uci_packets::SessionDeinitCmdBuilder { session_token: 1 }.build().into()
);
cmd = UciCommand::SessionSetAppConfig { session_token: 1, config_tlvs: vec![] };
packet = uwb_uci_packets::UciControlPacket::try_from(cmd.clone()).unwrap();
assert_eq!(
packet,
uwb_uci_packets::SessionSetAppConfigCmdBuilder { session_token: 1, tlvs: vec![] }
.build()
.into()
);
cmd = UciCommand::SessionGetAppConfig { session_token: 1, app_cfg: vec![] };
packet = uwb_uci_packets::UciControlPacket::try_from(cmd.clone()).unwrap();
assert_eq!(
packet,
uwb_uci_packets::SessionGetAppConfigCmdBuilder { session_token: 1, app_cfg: vec![] }
.build()
.into()
);
cmd = UciCommand::SessionGetCount {};
packet = uwb_uci_packets::UciControlPacket::try_from(cmd.clone()).unwrap();
assert_eq!(packet, uwb_uci_packets::SessionGetCountCmdBuilder {}.build().into());
cmd = UciCommand::SessionGetState { session_token: 1 };
packet = uwb_uci_packets::UciControlPacket::try_from(cmd.clone()).unwrap();
assert_eq!(
packet,
uwb_uci_packets::SessionGetStateCmdBuilder { session_token: 1 }.build().into()
);
cmd = UciCommand::SessionUpdateControllerMulticastList {
session_token: 1,
action: UpdateMulticastListAction::AddControlee,
controlees: Controlees::NoSessionKey(vec![]),
is_multicast_list_ntf_v2_supported: false,
};
packet = uwb_uci_packets::UciControlPacket::try_from(cmd.clone()).unwrap();
assert_eq!(
packet,
build_session_update_controller_multicast_list_cmd(
1,
UpdateMulticastListAction::AddControlee,
Controlees::NoSessionKey(vec![])
)
.map_err(|_| Error::BadParameters)
.unwrap()
.into()
);
cmd = UciCommand::SessionUpdateDtTagRangingRounds {
session_token: 1,
ranging_round_indexes: vec![0],
};
packet = uwb_uci_packets::UciControlPacket::try_from(cmd.clone()).unwrap();
assert_eq!(
packet,
uwb_uci_packets::SessionUpdateDtTagRangingRoundsCmdBuilder {
session_token: 1,
ranging_round_indexes: vec![0]
}
.build()
.into()
);
cmd = UciCommand::SessionQueryMaxDataSize { session_token: 1 };
packet = uwb_uci_packets::UciControlPacket::try_from(cmd.clone()).unwrap();
assert_eq!(
packet,
uwb_uci_packets::SessionQueryMaxDataSizeCmdBuilder { session_token: 1 }.build().into()
);
cmd = UciCommand::SessionStart { session_token: 1 };
packet = uwb_uci_packets::UciControlPacket::try_from(cmd.clone()).unwrap();
assert_eq!(
packet,
uwb_uci_packets::SessionStartCmdBuilder { session_token: 1 }.build().into()
);
cmd = UciCommand::SessionStop { session_token: 1 };
packet = uwb_uci_packets::UciControlPacket::try_from(cmd.clone()).unwrap();
assert_eq!(
packet,
uwb_uci_packets::SessionStopCmdBuilder { session_token: 1 }.build().into()
);
cmd = UciCommand::SessionGetRangingCount { session_token: 1 };
packet = uwb_uci_packets::UciControlPacket::try_from(cmd.clone()).unwrap();
assert_eq!(
packet,
uwb_uci_packets::SessionGetRangingCountCmdBuilder { session_token: 1 }.build().into()
);
let country_code: [u8; 2] = [85, 83];
cmd = UciCommand::AndroidSetCountryCode {
country_code: CountryCode::new(&country_code).unwrap(),
};
packet = uwb_uci_packets::UciControlPacket::try_from(cmd.clone()).unwrap();
assert_eq!(
packet,
uwb_uci_packets::AndroidSetCountryCodeCmdBuilder { country_code }.build().into()
);
cmd = UciCommand::AndroidGetPowerStats {};
packet = uwb_uci_packets::UciControlPacket::try_from(cmd).unwrap();
assert_eq!(packet, uwb_uci_packets::AndroidGetPowerStatsCmdBuilder {}.build().into());
cmd = UciCommand::AndroidSetRadarConfig { session_token: 1, config_tlvs: vec![] };
packet = uwb_uci_packets::UciControlPacket::try_from(cmd.clone()).unwrap();
assert_eq!(
packet,
uwb_uci_packets::AndroidSetRadarConfigCmdBuilder { session_token: 1, tlvs: vec![] }
.build()
.into()
);
cmd = UciCommand::AndroidGetRadarConfig { session_token: 1, radar_cfg: vec![] };
packet = uwb_uci_packets::UciControlPacket::try_from(cmd).unwrap();
assert_eq!(
packet,
uwb_uci_packets::AndroidGetRadarConfigCmdBuilder { session_token: 1, tlvs: vec![] }
.build()
.into()
);
cmd = UciCommand::CoreQueryTimeStamp {};
packet = uwb_uci_packets::UciControlPacket::try_from(cmd).unwrap();
assert_eq!(packet, uwb_uci_packets::CoreQueryTimeStampCmdBuilder {}.build().into());
cmd = UciCommand::RawUciCmd { mt: 1, gid: 0xa, oid: 0, payload: vec![0, 1, 2, 3] };
packet = uwb_uci_packets::UciControlPacket::try_from(cmd).unwrap();
assert_eq!(
packet,
build_raw_uci_cmd_packet(1, 0xa, 0, vec![0, 1, 2, 3])
.expect("Failed to build raw cmd packet.")
);
let phase_list_short_mac_address = PhaseListShortMacAddress {
session_token: 0x1324_3546,
start_slot_index: 0x1111,
end_slot_index: 0x1121,
phase_participation: 0x0,
mac_address: [0x1, 0x2],
};
cmd = UciCommand::SessionSetHybridControllerConfig {
session_token: 1,
message_control: 0,
number_of_phases: 0,
update_time: UpdateTime::new(&[1; 8]).unwrap(),
phase_list: PhaseList::ShortMacAddress(vec![phase_list_short_mac_address]),
};
packet = uwb_uci_packets::UciControlPacket::try_from(cmd).unwrap();
assert_eq!(
packet,
uwb_uci_packets::SessionSetHybridControllerConfigCmdBuilder {
message_control: 0,
number_of_phases: 0,
session_token: 1,
update_time: [1; 8],
payload: Some(
vec![
0x46, 0x35, 0x24, 0x13, // session id (LE)
0x11, 0x11, // start slot index (LE)
0x21, 0x11, // end slot index (LE)
0x00, // phase_participation
0x01, 0x02
]
.into()
)
}
.build()
.into()
);
cmd = UciCommand::SessionSetHybridControleeConfig {
session_token: 1,
controlee_phase_list: vec![],
};
packet = uwb_uci_packets::UciControlPacket::try_from(cmd).unwrap();
assert_eq!(
packet,
uwb_uci_packets::SessionSetHybridControleeConfigCmdBuilder {
controlee_phase_list: vec![],
session_token: 1,
}
.build()
.into()
);
cmd = UciCommand::SessionDataTransferPhaseConfig {
session_token: 1,
dtpcm_repetition: 0,
data_transfer_control: 2,
dtpml_size: 1,
mac_address: vec![0, 1],
slot_bitmap: vec![2, 3],
};
packet = uwb_uci_packets::UciControlPacket::try_from(cmd).unwrap();
assert_eq!(
packet,
uwb_uci_packets::SessionDataTransferPhaseConfigCmdBuilder {
session_token: 1,
dtpcm_repetition: 0,
data_transfer_control: 2,
dtpml_size: 1,
payload: Some(vec![0x00, 0x01, 0x02, 0x03].into()),
}
.build()
.into()
);
}
}