diff --git a/src/client/params.rs b/src/client/params.rs index 644e613..cb47536 100644 --- a/src/client/params.rs +++ b/src/client/params.rs @@ -1,3 +1,5 @@ +use std::hash::Hash; + use enumflags2::BitFlags; use crate::Address; @@ -199,3 +201,41 @@ pub enum PhyFlag { LECodedTx = 1 << 13, LECodedRx = 1 << 14, } + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, FromPrimitive)] +#[repr(u16)] +pub enum SystemConfigParameterType { + BREDRPageScanType = 0x0000, + BREDRPageScanInterval, + BREDRPageScanWindow, + BREDRInquiryScanType, + BREDRInquiryScanInterval, + BREDRInquiryScanWindow, + BREDRLinkSupervisionTimeout, + BREDRPageTimeout, + BREDRMinSniffInterval, + BREDRMaxSniffInterval, + LEAdvertisementMinInterval, + LEAdvertisementMaxInterval, + LEMultiAdvertisementRotationInterval, + LEScanningIntervalForAutoConnect, + LEScanningWindowForAutoConnect, + LEScanningIntervalForWakeScenarios, + LEScanningWindowForWakeScenarios, + LEScanningIntervalForDiscovery, + LEScanningWindowForDiscovery, + LEScanningIntervalForAdvMonitoring, + LEScanningWindowForAdvMonitoring, + LEScanningIntervalForConnect, + LEScanningWindowForConnect, + LEMinConnectionInterval, + LEMaxConnectionInterval, + LEConnectionLatency, + LEConnectionSupervisionTimeout, + LEAutoconnectTimeout, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, FromPrimitive)] +//#[repr(u16)] once there are known variants +#[non_exhaustive] +pub enum RuntimeConfigParameterType {} diff --git a/src/client/query.rs b/src/client/query.rs index 75ddec6..2b94b5d 100644 --- a/src/client/query.rs +++ b/src/client/query.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use crate::interface::class::from_bytes as class_from_bytes; use crate::interface::controller::ControllerInfoExt; use crate::util::BufExt2; @@ -382,4 +384,33 @@ impl<'a> BlueZClient<'a> { }) .await } + + /// Currently no Parameter_Type values are defined and an empty list + /// will be returned. + /// + /// This command can be used at any time and will return a list of + /// supported default parameters as well as their current value. + pub async fn get_default_runtime_config( + &mut self, + controller: Controller, + ) -> Result>> { + self.exec_command(Command::ReadDefaultRuntimeConfig, controller, None, |_, param| { + let mut param = param.unwrap(); + Ok(param.get_tlv_map()) + }) + .await + } + + /// This command can be used at any time and will return a list of + /// supported default parameters as well as their current value. + pub async fn get_default_system_config( + &mut self, + controller: Controller, + ) -> Result>> { + self.exec_command(Command::ReadDefaultSystemConfig, controller, None, |_, param| { + let mut param = param.unwrap(); + Ok(param.get_tlv_map()) + }) + .await + } } diff --git a/src/client/settings.rs b/src/client/settings.rs index 09299c2..c21279d 100644 --- a/src/client/settings.rs +++ b/src/client/settings.rs @@ -796,4 +796,94 @@ impl<'a> BlueZClient<'a> { ) .await } + + /// This command is used to enable/disable Wideband Speech + /// support for a controller. + /// + /// This command is only available for BR/EDR capable controllers and + /// require controller specific support. + /// + /// This command can be used when the controller is not powered and + /// all settings will be programmed once powered. + /// + /// In case the controller does not support Wideband Speech + /// the command will fail regardless with Not Supported error. + pub async fn set_wideband_speech( + &mut self, + controller: Controller, + enabled: bool, + ) -> Result { + let mut param = BytesMut::with_capacity(1); + param.put_u8(enabled as u8); + + self.exec_command( + Command::SetWidebandSpeech, + controller, + Some(param.to_bytes()), + settings_callback, + ) + .await + } + + /// This command is used to set a list of default runtime parameters. + /// + /// This command can be used at any time and will change the runtime + /// default. Changes however will not apply to existing connections or + /// currently active operations. + /// + /// When providing unsupported values or invalid values, no parameter + /// value will be changed and all values discarded. + pub async fn set_default_runtime_config( + &mut self, + controller: Controller, + params: &[(RuntimeConfigParameterType, Vec)], + ) -> Result<()> { + let size = params.iter().fold(0, |acc, (_, value)| acc + 3 + value.len()); + let mut param = BytesMut::with_capacity(size); + + #[allow(unreachable_code,unused_variables)] // until we have constants in RuntimeConfigParameterType + for (parameter_type, value) in params { + param.put_u16_le(unimplemented!("*parameter_type as u16")); + param.put_u8(value.len() as u8); + param.put_slice(value); + } + + self.exec_command( + Command::SetDefaultSystemConfig, + controller, + Some(param.to_bytes()), + |_, _| Ok(()), + ) + .await + } + + /// This command is used to set a list of default controller parameters. + /// + /// This command can be used when the controller is not powered and + /// all supported parameters will be programmed once powered. + /// + /// When providing unsupported values or invalid values, no parameter + /// value will be changed and all values discarded. + pub async fn set_default_system_config( + &mut self, + controller: Controller, + params: &[(SystemConfigParameterType, Vec)], + ) -> Result<()> { + let size = params.iter().fold(0, |acc, (_, value)| acc + 3 + value.len()); + let mut param = BytesMut::with_capacity(size); + + for (parameter_type, value) in params { + param.put_u16_le(*parameter_type as u16); + param.put_u8(value.len() as u8); + param.put_slice(value); + } + + self.exec_command( + Command::SetDefaultSystemConfig, + controller, + Some(param.to_bytes()), + |_, _| Ok(()), + ) + .await + } } diff --git a/src/interface/command.rs b/src/interface/command.rs index cd5e02b..c68d415 100644 --- a/src/interface/command.rs +++ b/src/interface/command.rs @@ -99,6 +99,19 @@ pub enum Command { GetPhyConfig, SetPhyConfig, LoadBlockedKeys, + SetWidebandSpeech, + ReadSecurityInfo, + ReadExperimentalFeaturesInfo, + SetExperimentalFeature, + ReadDefaultSystemConfig, + SetDefaultSystemConfig, + ReadDefaultRuntimeConfig, + SetDefaultRuntimeConfig, + GetDeviceFlags, + SetDeviceFlags, + ReadAdvertisementMonitorFeatures, + AddAdvertisementPatternsMonitor, + RemoveAdvertisementMonitor, } impl fmt::LowerHex for CommandStatus { diff --git a/src/interface/controller.rs b/src/interface/controller.rs index 000b667..6f29964 100644 --- a/src/interface/controller.rs +++ b/src/interface/controller.rs @@ -77,6 +77,8 @@ pub enum ControllerSetting { Privacy = 1 << 13, Configuration = 1 << 14, StaticAddress = 1 << 15, + PhyConfiguration = 1 << 16, + WidebandSpeech = 1 << 17, } pub type ControllerSettings = BitFlags; diff --git a/src/interface/event.rs b/src/interface/event.rs index af45655..a4fdd07 100644 --- a/src/interface/event.rs +++ b/src/interface/event.rs @@ -8,6 +8,7 @@ use crate::interface::class::{DeviceClass, ServiceClasses}; use crate::interface::controller::ControllerSettings; use crate::interface::{Command, CommandStatus}; use crate::Address; +use std::collections::HashMap; #[derive(Debug)] pub enum Event { @@ -433,4 +434,30 @@ pub enum Event { /// The event will only be sent to management sockets other than the /// one through which the command was sent. PhyConfigChanged { selected_phys: BitFlags }, + + /// This event indicates that the status of an experimental feature + /// has been changed. + /// + /// The event will only be sent to management sockets other than the + /// one through which the change was triggered. + ExperimentalFeatureChanged { + uuid: [u8; 16], + flags: u32, + }, + + /// This event indicates the change of default system parameter values. + /// + /// The event will only be sent to management sockets other than the + /// one through which the change was trigged. In addition it will + /// only be sent to sockets that have issues the Read Default System + /// Configuration command. + DefaultSystemConfigChanged { params: HashMap> }, + + /// This event indicates the change of default runtime parameter values. + /// + /// The event will only be sent to management sockets other than the + /// one through which the change was trigged. In addition it will + /// only be sent to sockets that have issues the Read Default Runtime + /// Configuration command. + DefaultRuntimeConfigChanged { params: HashMap> }, } diff --git a/src/interface/response.rs b/src/interface/response.rs index 8500a80..076b547 100644 --- a/src/interface/response.rs +++ b/src/interface/response.rs @@ -219,7 +219,17 @@ impl Response { 0x0026 => Event::PhyConfigChanged { selected_phys: BitFlags::from_bits_truncate(buf.get_u32_le()), }, - _ => todo!("throw error instead of panicking"), + 0x0027 => Event::ExperimentalFeatureChanged { + uuid: buf.get_u8x16(), + flags: buf.get_u32_le(), + }, + 0x0028 => Event::DefaultSystemConfigChanged { + params: buf.get_tlv_map() + }, + 0x0029 => Event::DefaultRuntimeConfigChanged { + params: buf.get_tlv_map() + }, + _ => return Err(Error::UnknownEventCode { evt_code }), }, }) } diff --git a/src/result.rs b/src/result.rs index f15015a..89eaad3 100644 --- a/src/result.rs +++ b/src/result.rs @@ -13,7 +13,7 @@ pub enum Error { #[source] source: ::std::io::Error, }, - #[error("Command {:?} returned {:?}.", status, opcode)] + #[error("Command {:?} returned {:?}.", opcode, status)] CommandError { opcode: Command, status: CommandStatus, @@ -22,6 +22,8 @@ pub enum Error { UnknownOpcode { opcode: u16 }, #[error("Unknown command status: {:x}.", status)] UnknownStatus { status: u8 }, + #[error("Unknown event code: {:x}.", evt_code)] + UnknownEventCode { evt_code: u16 }, #[error("Timed out.")] TimedOut, #[error("The socket received invalid data.")] diff --git a/src/util.rs b/src/util.rs index c171499..fbf616c 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,4 +1,6 @@ +use std::collections::HashMap; use std::ffi::CString; +use std::hash::Hash; use bytes::Buf; use enumflags2::BitFlags; @@ -20,6 +22,12 @@ pub(crate) trait BufExt2: Buf { arr } + fn get_vec_u8(&mut self, len: usize) -> Vec { + let mut ret = vec![0; len]; + self.copy_to_slice(ret.as_mut_slice()); + ret + } + fn get_bool(&mut self) -> bool { self.get_u8() != 0 } @@ -53,6 +61,34 @@ pub(crate) trait BufExt2: Buf { } return unsafe { CString::from_vec_unchecked(bytes) }; } + + /// Parses a list of Type/Length/Value entries into a map keyed by type + /// + /// This parses a list of mgmt_tlv entries (as defined in mgmt.h) and converts them + /// into a map of Type => Vec. + /// + /// # Bytes layout + /// + /// The layout as described in the mgmt-api documentation is: + /// ```plain + /// Parameter1 { + /// Parameter_Type (2 Octet) + /// Value_Length (1 Octet) + /// Value (0-255 Octets) + /// } + /// Parameter2 { } + /// ... + /// ``` + /// + fn get_tlv_map(&mut self) -> HashMap> { + let mut parameters = HashMap::new(); + while self.has_remaining() { + let parameter_type: T = self.get_primitive_u16_le(); + let value_size = self.get_u8() as usize; + parameters.insert(parameter_type, self.get_vec_u8(value_size)); + } + parameters + } } impl BufExt2 for T {}