| // Copyright 2024, 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 crate::derive::{Read, Write}; |
| use crate::reader::{Read, Reader}; |
| use crate::writer::{pack, Write, Writer}; |
| |
| /// HCI Command, as defined in Part E - 5.4.1 |
| #[derive(Debug)] |
| pub enum Command { |
| /// 7.3.2 Reset Command |
| Reset(Reset), |
| /// 7.8.97 LE Set CIG Parameters |
| LeSetCigParameters(LeSetCigParameters), |
| /// 7.8.99 LE Create CIS |
| LeCreateCis(LeCreateCis), |
| /// 7.8.100 LE Remove CIG |
| LeRemoveCig(LeRemoveCig), |
| /// 7.8.103 LE Create BIG |
| LeCreateBig(LeCreateBig), |
| /// 7.8.109 LE Setup ISO Data Path |
| LeSetupIsoDataPath(LeSetupIsoDataPath), |
| /// 7.8.110 LE Remove ISO Data Path |
| LeRemoveIsoDataPath(LeRemoveIsoDataPath), |
| /// Unknown command |
| Unknown(OpCode), |
| } |
| |
| /// HCI Command Return Parameters |
| #[derive(Debug, Read, Write)] |
| pub enum ReturnParameters { |
| /// 7.3.2 Reset Command |
| Reset(ResetComplete), |
| /// 7.8.2 LE Read Buffer Size [V1] |
| LeReadBufferSizeV1(LeReadBufferSizeV1Complete), |
| /// 7.8.2 LE Read Buffer Size [V2] |
| LeReadBufferSizeV2(LeReadBufferSizeV2Complete), |
| /// 7.8.97 LE Set CIG Parameters |
| LeSetCigParameters(LeSetCigParametersComplete), |
| /// 7.8.100 LE Remove CIG |
| LeRemoveCig(LeRemoveCigComplete), |
| /// 7.8.109 LE Setup ISO Data Path |
| LeSetupIsoDataPath(LeIsoDataPathComplete), |
| /// 7.8.110 LE Remove ISO Data Path |
| LeRemoveIsoDataPath(LeIsoDataPathComplete), |
| /// Unknown command |
| Unknown(OpCode), |
| } |
| |
| impl Command { |
| /// Read an HCI Command packet |
| pub fn from_bytes(data: &[u8]) -> Result<Self, Option<OpCode>> { |
| fn parse_packet(data: &[u8]) -> Option<(OpCode, Reader)> { |
| let mut r = Reader::new(data); |
| let opcode = r.read()?; |
| let len = r.read_u8()? as usize; |
| Some((opcode, Reader::new(r.get(len)?))) |
| } |
| |
| let Some((opcode, mut r)) = parse_packet(data) else { |
| return Err(None); |
| }; |
| Self::dispatch_read(opcode, &mut r).ok_or(Some(opcode)) |
| } |
| |
| fn dispatch_read(opcode: OpCode, r: &mut Reader) -> Option<Command> { |
| Some(match opcode { |
| Reset::OPCODE => Self::Reset(r.read()?), |
| LeSetCigParameters::OPCODE => Self::LeSetCigParameters(r.read()?), |
| LeCreateCis::OPCODE => Self::LeCreateCis(r.read()?), |
| LeRemoveCig::OPCODE => Self::LeRemoveCig(r.read()?), |
| LeCreateBig::OPCODE => Self::LeCreateBig(r.read()?), |
| LeSetupIsoDataPath::OPCODE => Self::LeSetupIsoDataPath(r.read()?), |
| LeRemoveIsoDataPath::OPCODE => Self::LeRemoveIsoDataPath(r.read()?), |
| opcode => Self::Unknown(opcode), |
| }) |
| } |
| |
| fn to_bytes<T: CommandOpCode + Write>(command: &T) -> Vec<u8> { |
| let mut w = Writer::new(Vec::with_capacity(3 + 255)); |
| w.write(&T::OPCODE); |
| w.write_u8(0); |
| w.write(command); |
| |
| let mut vec = w.into_vec(); |
| vec[2] = (vec.len() - 3).try_into().unwrap(); |
| vec |
| } |
| } |
| |
| /// OpCode of HCI Command, as defined in Part E - 5.4.1 |
| #[derive(Debug, Clone, Copy, PartialEq)] |
| pub struct OpCode(u16); |
| |
| impl OpCode { |
| /// OpCode from OpCode Group Field (OGF) and OpCode Command Field (OCF). |
| pub const fn from(ogf: u16, ocf: u16) -> Self { |
| Self(pack!((ocf, 10), (ogf, 6))) |
| } |
| } |
| |
| impl From<u16> for OpCode { |
| fn from(v: u16) -> Self { |
| OpCode(v) |
| } |
| } |
| |
| impl Read for OpCode { |
| fn read(r: &mut Reader) -> Option<Self> { |
| Some(r.read_u16()?.into()) |
| } |
| } |
| |
| impl Write for OpCode { |
| fn write(&self, w: &mut Writer) { |
| w.write_u16(self.0) |
| } |
| } |
| |
| /// Define command OpCode |
| pub trait CommandOpCode { |
| /// OpCode of the command |
| const OPCODE: OpCode; |
| } |
| |
| /// Build command from definition |
| pub trait CommandToBytes: CommandOpCode + Write { |
| /// Output the HCI Command packet |
| fn to_bytes(&self) -> Vec<u8> |
| where |
| Self: Sized + CommandOpCode + Write; |
| } |
| |
| pub use defs::*; |
| |
| #[allow(missing_docs)] |
| #[rustfmt::skip] |
| mod defs { |
| |
| use super::*; |
| use crate::derive::CommandToBytes; |
| use crate::status::*; |
| |
| #[cfg(test)] |
| use crate::{Event, EventToBytes}; |
| |
| |
| // 7.3.2 Reset Command |
| |
| impl CommandOpCode for Reset { |
| const OPCODE: OpCode = OpCode::from(0x03, 0x003); |
| } |
| |
| #[derive(Debug, Read, Write, CommandToBytes)] |
| pub struct Reset {} |
| |
| #[derive(Debug, Read, Write)] |
| pub struct ResetComplete { |
| pub status: Status, |
| } |
| |
| #[test] |
| fn test_reset() { |
| let dump = [0x03, 0x0c, 0x00]; |
| let Ok(Command::Reset(c)) = Command::from_bytes(&dump) else { panic!() }; |
| assert_eq!(c.to_bytes(), &dump[..]); |
| } |
| |
| #[test] |
| fn test_reset_complete() { |
| let dump = [0x0e, 0x04, 0x01, 0x03, 0x0c, 0x00]; |
| let Ok(Event::CommandComplete(e)) = Event::from_bytes(&dump) else { panic!() }; |
| let ReturnParameters::Reset(ref p) = e.return_parameters else { panic!() }; |
| assert_eq!(p.status, Status::Success); |
| assert_eq!(e.to_bytes(), &dump[..]); |
| } |
| |
| |
| // 7.8.2 LE Read Buffer Size |
| |
| impl CommandOpCode for LeReadBufferSizeV1 { |
| const OPCODE: OpCode = OpCode::from(0x08, 0x002); |
| } |
| |
| #[derive(Debug)] |
| pub struct LeReadBufferSizeV1; |
| |
| #[derive(Debug, Read, Write)] |
| pub struct LeReadBufferSizeV1Complete { |
| pub status: Status, |
| pub le_acl_data_packet_length: u16, |
| pub total_num_le_acl_data_packets: u8, |
| } |
| |
| #[test] |
| fn test_le_read_buffer_size_v1_complete() { |
| let dump = [0x0e, 0x07, 0x01, 0x02, 0x20, 0x00, 0xfb, 0x00, 0x0f]; |
| let Ok(Event::CommandComplete(e)) = Event::from_bytes(&dump) else { panic!() }; |
| let ReturnParameters::LeReadBufferSizeV1(ref p) = e.return_parameters else { panic!() }; |
| assert_eq!(p.status, Status::Success); |
| assert_eq!(p.le_acl_data_packet_length, 251); |
| assert_eq!(p.total_num_le_acl_data_packets, 15); |
| assert_eq!(e.to_bytes(), &dump[..]); |
| } |
| |
| impl CommandOpCode for LeReadBufferSizeV2 { |
| const OPCODE: OpCode = OpCode::from(0x08, 0x060); |
| } |
| |
| #[derive(Debug)] |
| pub struct LeReadBufferSizeV2; |
| |
| #[derive(Debug, Read, Write)] |
| pub struct LeReadBufferSizeV2Complete { |
| pub status: Status, |
| pub le_acl_data_packet_length: u16, |
| pub total_num_le_acl_data_packets: u8, |
| pub iso_data_packet_length: u16, |
| pub total_num_iso_data_packets: u8, |
| } |
| |
| #[test] |
| fn test_le_read_buffer_size_v2_complete() { |
| let dump = [0x0e, 0x0a, 0x01, 0x60, 0x20, 0x00, 0xfb, 0x00, 0x0f, 0xfd, 0x03, 0x18]; |
| let Ok(Event::CommandComplete(e)) = Event::from_bytes(&dump) else { panic!() }; |
| let ReturnParameters::LeReadBufferSizeV2(ref p) = e.return_parameters else { panic!() }; |
| assert_eq!(p.status, Status::Success); |
| assert_eq!(p.le_acl_data_packet_length, 251); |
| assert_eq!(p.total_num_le_acl_data_packets, 15); |
| assert_eq!(p.iso_data_packet_length, 1021); |
| assert_eq!(p.total_num_iso_data_packets, 24); |
| assert_eq!(e.to_bytes(), &dump[..]); |
| } |
| |
| |
| // 7.8.97 LE Set CIG Parameters |
| |
| impl CommandOpCode for LeSetCigParameters { |
| const OPCODE: OpCode = OpCode::from(0x08, 0x062); |
| } |
| |
| #[derive(Debug, Read, Write, CommandToBytes)] |
| pub struct LeSetCigParameters { |
| pub cig_id: u8, |
| #[N(3)] pub sdu_interval_c_to_p: u32, |
| #[N(3)] pub sdu_interval_p_to_c: u32, |
| pub worst_case_sca: u8, |
| pub packing: u8, |
| pub framing: u8, |
| pub max_transport_latency_c_to_p: u16, |
| pub max_transport_latency_p_to_c: u16, |
| pub cis: Vec<LeCisInCigParameters>, |
| } |
| |
| #[derive(Debug, Read, Write)] |
| pub struct LeCisInCigParameters { |
| pub cis_id: u8, |
| pub max_sdu_c_to_p: u16, |
| pub max_sdu_p_to_c: u16, |
| pub phy_c_to_p: u8, |
| pub phy_p_to_c: u8, |
| pub rtn_c_to_p: u8, |
| pub rtn_p_to_c: u8, |
| } |
| |
| #[test] |
| fn test_le_set_cig_parameters() { |
| let dump = [ |
| 0x62, 0x20, 0x21, 0x01, 0x10, 0x27, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x64, 0x00, 0x05, |
| 0x00, 0x02, 0x00, 0x78, 0x00, 0x00, 0x00, 0x02, 0x03, 0x0d, 0x00, 0x01, 0x78, 0x00, 0x00, 0x00, |
| 0x02, 0x03, 0x0d, 0x00 |
| ]; |
| let Ok(Command::LeSetCigParameters(c)) = Command::from_bytes(&dump) else { panic!() }; |
| assert_eq!(c.cig_id, 0x01); |
| assert_eq!(c.sdu_interval_c_to_p, 10_000); |
| assert_eq!(c.sdu_interval_p_to_c, 0); |
| assert_eq!(c.worst_case_sca, 1); |
| assert_eq!(c.packing, 0); |
| assert_eq!(c.framing, 0); |
| assert_eq!(c.max_transport_latency_c_to_p, 100); |
| assert_eq!(c.max_transport_latency_p_to_c, 5); |
| assert_eq!(c.cis.len(), 2); |
| assert_eq!(c.cis[0].cis_id, 0); |
| assert_eq!(c.cis[0].max_sdu_c_to_p, 120); |
| assert_eq!(c.cis[0].max_sdu_p_to_c, 0); |
| assert_eq!(c.cis[0].phy_c_to_p, 0x02); |
| assert_eq!(c.cis[0].phy_p_to_c, 0x03); |
| assert_eq!(c.cis[0].rtn_c_to_p, 13); |
| assert_eq!(c.cis[0].rtn_p_to_c, 0); |
| assert_eq!(c.cis[1].cis_id, 1); |
| assert_eq!(c.cis[1].max_sdu_c_to_p, 120); |
| assert_eq!(c.cis[1].max_sdu_p_to_c, 0); |
| assert_eq!(c.cis[1].phy_c_to_p, 0x02); |
| assert_eq!(c.cis[1].phy_p_to_c, 0x03); |
| assert_eq!(c.cis[1].rtn_c_to_p, 13); |
| assert_eq!(c.cis[1].rtn_p_to_c, 0); |
| assert_eq!(c.to_bytes(), &dump[..]); |
| } |
| |
| #[derive(Debug, Read, Write)] |
| pub struct LeSetCigParametersComplete { |
| pub status: Status, |
| pub cig_id: u8, |
| pub connection_handles: Vec<u16>, |
| } |
| |
| #[test] |
| fn test_le_set_cig_parameters_complete() { |
| let dump = [0x0e, 0x0a, 0x01, 0x62, 0x20, 0x00, 0x01, 0x02, 0x60, 0x00, 0x61, 0x00]; |
| let Ok(Event::CommandComplete(e)) = Event::from_bytes(&dump) else { panic!() }; |
| let ReturnParameters::LeSetCigParameters(ref p) = e.return_parameters else { panic!() }; |
| assert_eq!(p.status, Status::Success); |
| assert_eq!(p.cig_id, 1); |
| assert_eq!(p.connection_handles.len(), 2); |
| assert_eq!(p.connection_handles[0], 0x60); |
| assert_eq!(p.connection_handles[1], 0x61); |
| assert_eq!(e.to_bytes(), &dump[..]); |
| } |
| |
| |
| // 7.8.99 LE Create CIS |
| |
| impl CommandOpCode for LeCreateCis { |
| const OPCODE: OpCode = OpCode::from(0x08, 0x064); |
| } |
| |
| #[derive(Debug, Read, Write, CommandToBytes)] |
| pub struct LeCreateCis { |
| pub connection_handles: Vec<CisAclConnectionHandle>, |
| } |
| |
| #[derive(Debug, Read, Write)] |
| pub struct CisAclConnectionHandle { |
| pub cis: u16, |
| pub acl: u16, |
| } |
| |
| #[test] |
| fn test_le_create_cis () { |
| let dump = [0x64, 0x20, 0x09, 0x02, 0x60, 0x00, 0x40, 0x00, 0x61, 0x00, 0x41, 0x00]; |
| let Ok(Command::LeCreateCis(c)) = Command::from_bytes(&dump) else { panic!() }; |
| assert_eq!(c.connection_handles.len(), 2); |
| assert_eq!(c.connection_handles[0].cis, 0x60); |
| assert_eq!(c.connection_handles[0].acl, 0x40); |
| assert_eq!(c.connection_handles[1].cis, 0x61); |
| assert_eq!(c.connection_handles[1].acl, 0x41); |
| assert_eq!(c.to_bytes(), &dump[..]); |
| } |
| |
| |
| // 7.8.100 LE Remove CIG |
| |
| impl CommandOpCode for LeRemoveCig { |
| const OPCODE: OpCode = OpCode::from(0x08, 0x065); |
| } |
| |
| #[derive(Debug, Read, Write, CommandToBytes)] |
| pub struct LeRemoveCig { |
| pub cig_id: u8, |
| } |
| |
| #[derive(Debug, Read, Write)] |
| pub struct LeRemoveCigComplete { |
| pub status: Status, |
| pub cig_id: u8, |
| } |
| |
| #[test] |
| fn test_le_remove_cig() { |
| let dump = [0x65, 0x20, 0x01, 0x01]; |
| let Ok(Command::LeRemoveCig(c)) = Command::from_bytes(&dump) else { panic!() }; |
| assert_eq!(c.cig_id, 0x01); |
| assert_eq!(c.to_bytes(), &dump[..]); |
| } |
| |
| #[test] |
| fn test_le_remove_cig_complete() { |
| let dump = [0x0e, 0x05, 0x01, 0x65, 0x20, 0x00, 0x01]; |
| let Ok(Event::CommandComplete(e)) = Event::from_bytes(&dump) else { panic!() }; |
| let ReturnParameters::LeRemoveCig(ref p) = e.return_parameters else { panic!() }; |
| assert_eq!(p.status, Status::Success); |
| assert_eq!(p.cig_id, 0x01); |
| assert_eq!(e.to_bytes(), &dump[..]); |
| } |
| |
| |
| // 7.8.103 LE Create BIG |
| |
| impl CommandOpCode for LeCreateBig { |
| const OPCODE: OpCode = OpCode::from(0x08, 0x068); |
| } |
| |
| #[derive(Debug, Read, Write, CommandToBytes)] |
| pub struct LeCreateBig { |
| pub big_handle: u8, |
| pub advertising_handle: u8, |
| pub num_bis: u8, |
| #[N(3)] pub sdu_interval: u32, |
| pub max_sdu: u16, |
| pub max_transport_latency: u16, |
| pub rtn: u8, |
| pub phy: u8, |
| pub packing: u8, |
| pub framing: u8, |
| pub encryption: u8, |
| pub broadcast_code: [u8; 16], |
| } |
| |
| #[test] |
| fn test_le_create_big() { |
| let dump = [ |
| 0x68, 0x20, 0x1f, 0x00, 0x00, 0x02, 0x10, 0x27, 0x00, 0x78, 0x00, 0x3c, 0x00, 0x04, 0x02, 0x00, |
| 0x00, 0x01, 0x31, 0x32, 0x33, 0x34, 0x31, 0x32, 0x33, 0x34, 0x31, 0x32, 0x33, 0x34, 0x31, 0x32, |
| 0x33, 0x34 |
| ]; |
| let Ok(Command::LeCreateBig(c)) = Command::from_bytes(&dump) else { panic!() }; |
| assert_eq!(c.big_handle, 0x00); |
| assert_eq!(c.advertising_handle, 0x00); |
| assert_eq!(c.num_bis, 2); |
| assert_eq!(c.sdu_interval, 10_000); |
| assert_eq!(c.max_sdu, 120); |
| assert_eq!(c.max_transport_latency, 60); |
| assert_eq!(c.rtn, 4); |
| assert_eq!(c.phy, 0x02); |
| assert_eq!(c.packing, 0x00); |
| assert_eq!(c.framing, 0x00); |
| assert_eq!(c.encryption, 1); |
| assert_eq!(c.broadcast_code, [ |
| 0x31, 0x32, 0x33, 0x34, 0x31, 0x32, 0x33, 0x34, |
| 0x31, 0x32, 0x33, 0x34, 0x31, 0x32, 0x33, 0x34 |
| ]); |
| assert_eq!(c.to_bytes(), &dump[..]); |
| } |
| |
| |
| // 7.8.109 LE Setup ISO Data Path |
| |
| impl CommandOpCode for LeSetupIsoDataPath { |
| const OPCODE: OpCode = OpCode::from(0x08, 0x06e); |
| } |
| |
| #[derive(Debug, Read, Write, CommandToBytes)] |
| pub struct LeSetupIsoDataPath { |
| pub connection_handle: u16, |
| pub data_path_direction: LeDataPathDirection, |
| pub data_path_id: u8, |
| pub codec_id: LeCodecId, |
| #[N(3)] pub controller_delay: u32, |
| pub codec_configuration: Vec<u8>, |
| } |
| |
| #[derive(Debug, PartialEq, Read, Write)] |
| pub enum LeDataPathDirection { |
| Input = 0x00, |
| Output = 0x01, |
| } |
| |
| #[derive(Debug, Read, Write)] |
| pub struct LeCodecId { |
| pub coding_format: CodingFormat, |
| pub company_id: u16, |
| pub vendor_id: u16, |
| } |
| |
| #[derive(Debug, PartialEq, Read, Write)] |
| pub enum CodingFormat { |
| ULawLog = 0x00, |
| ALawLog = 0x01, |
| Cvsd = 0x02, |
| Transparent = 0x03, |
| LinearPcm = 0x04, |
| MSbc = 0x05, |
| Lc3 = 0x06, |
| G729A = 0x07, |
| VendorSpecific = 0xff, |
| } |
| |
| #[derive(Debug, Read, Write)] |
| pub struct LeIsoDataPathComplete { |
| pub status: Status, |
| pub connection_handle: u16, |
| } |
| |
| #[test] |
| fn test_le_setup_iso_data_path() { |
| let dump = [ |
| 0x6e, 0x20, 0x0d, 0x60, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 |
| ]; |
| let Ok(Command::LeSetupIsoDataPath(c)) = Command::from_bytes(&dump) else { panic!() }; |
| assert_eq!(c.connection_handle, 0x60); |
| assert_eq!(c.data_path_direction, LeDataPathDirection::Input); |
| assert_eq!(c.data_path_id, 0x00); |
| assert_eq!(c.codec_id.coding_format, CodingFormat::Transparent); |
| assert_eq!(c.codec_id.company_id, 0); |
| assert_eq!(c.codec_id.vendor_id, 0); |
| assert_eq!(c.controller_delay, 0); |
| assert_eq!(c.codec_configuration.len(), 0); |
| assert_eq!(c.to_bytes(), &dump[..]); |
| } |
| |
| #[test] |
| fn test_le_setup_iso_data_path_complete() { |
| let dump = [0x0e, 0x06, 0x01, 0x6e, 0x20, 0x00, 0x60, 0x00]; |
| let Ok(Event::CommandComplete(e)) = Event::from_bytes(&dump) else { panic!() }; |
| let ReturnParameters::LeSetupIsoDataPath(ref p) = e.return_parameters else { panic!() }; |
| assert_eq!(p.status, Status::Success); |
| assert_eq!(p.connection_handle, 0x60); |
| assert_eq!(e.to_bytes(), &dump[..]); |
| } |
| |
| |
| // 7.8.110 LE Remove ISO Data Path |
| |
| impl CommandOpCode for LeRemoveIsoDataPath { |
| const OPCODE: OpCode = OpCode::from(0x08, 0x06f); |
| } |
| |
| #[derive(Debug, Read, Write, CommandToBytes)] |
| pub struct LeRemoveIsoDataPath { |
| pub connection_handle: u16, |
| pub data_path_direction: u8, |
| } |
| |
| #[test] |
| fn test_le_remove_iso_data_path() { |
| let dump = [0x6f, 0x20, 0x03, 0x60, 0x00, 0x01]; |
| let Ok(Command::LeRemoveIsoDataPath(c)) = Command::from_bytes(&dump) else { panic!() }; |
| assert_eq!(c.connection_handle, 0x60); |
| assert_eq!(c.data_path_direction, 0x01); |
| assert_eq!(c.to_bytes(), &dump[..]); |
| } |
| |
| } |