├── .gitignore ├── Cargo.toml ├── src ├── l2cap.rs ├── command.rs ├── acl.rs ├── ad_structure.rs ├── event.rs ├── lib.rs ├── attribute_server.rs └── att.rs └── tests └── test_ble.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ble-hci" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/l2cap.rs: -------------------------------------------------------------------------------- 1 | use crate::{acl::AclPacket, Data}; 2 | 3 | #[derive(Debug)] 4 | pub struct L2capPacket { 5 | pub length: u16, 6 | pub channel: u16, 7 | pub payload: Data, 8 | } 9 | 10 | #[derive(Debug)] 11 | pub enum L2capParseError { 12 | Other, 13 | } 14 | 15 | pub fn parse_l2cap(packet: AclPacket) -> Result { 16 | let data = packet.data.to_slice(); 17 | let length = (data[0] as u16) + ((data[1] as u16) << 8); 18 | let channel = (data[2] as u16) + ((data[3] as u16) << 8); 19 | let payload = Data::new(&data[4..]); 20 | 21 | Ok(L2capPacket { 22 | length, 23 | channel, 24 | payload, 25 | }) 26 | } 27 | 28 | pub fn encode_l2cap(att_data: Data) -> Data { 29 | let mut data = Data::default(); 30 | data.append(&[0, 0]); // len set later 31 | data.append(&[0x04, 0x00]); // channel 32 | data.append(att_data.to_slice()); 33 | 34 | let len = data.len - 4; 35 | data.set(0, (len & 0xff) as u8); 36 | data.set(1, ((len >> 8) & 0xff) as u8); 37 | 38 | data 39 | } 40 | -------------------------------------------------------------------------------- /src/command.rs: -------------------------------------------------------------------------------- 1 | use crate::Data; 2 | 3 | pub const CONTROLLER_OGF: u8 = 0x03; 4 | pub const RESET_OCF: u16 = 0x03; 5 | 6 | pub const LE_OGF: u8 = 0x08; 7 | pub const SET_ADVERTISING_PARAMETERS_OCF: u16 = 0x06; 8 | pub const SET_ADVERTISING_DATA_OCF: u16 = 0x08; 9 | pub const SET_ADVERTISE_ENABLE_OCF: u16 = 0x0a; 10 | 11 | #[derive(Debug)] 12 | pub struct CommandHeader { 13 | pub opcode: u16, 14 | pub len: u8, 15 | } 16 | 17 | pub const fn opcode(ogf: u8, ocf: u16) -> u16 { 18 | ((ogf as u16) << 10) + ocf as u16 19 | } 20 | 21 | impl CommandHeader { 22 | pub fn from_bytes(bytes: &[u8]) -> CommandHeader { 23 | CommandHeader { 24 | opcode: ((bytes[1] as u16) << 8) + bytes[0] as u16, 25 | len: bytes[2], 26 | } 27 | } 28 | 29 | pub fn from_ogf_ocf(ogf: u8, ocf: u16, len: u8) -> CommandHeader { 30 | let opcode = opcode(ogf, ocf); 31 | CommandHeader { opcode, len } 32 | } 33 | 34 | pub fn write_into(&self, dst: &mut [u8]) { 35 | dst[0] = (self.opcode & 0xff) as u8; 36 | dst[1] = ((self.opcode & 0xff00) >> 8) as u8; 37 | dst[2] = self.len; 38 | } 39 | 40 | pub fn ogf(&self) -> u8 { 41 | ((self.opcode & 0b1111110000000000) >> 10) as u8 42 | } 43 | 44 | pub fn ocf(&self) -> u16 { 45 | self.opcode & 0b1111111111 46 | } 47 | } 48 | 49 | pub enum Command { 50 | Reset, 51 | LeSetAdvertisingParameters, 52 | LeSetAdvertisingData { data: Data }, 53 | LeSetAdvertiseEnable(bool), 54 | } 55 | 56 | pub fn create_command_data(command: Command) -> Data { 57 | match command { 58 | Command::Reset => { 59 | let mut data = [0u8; 4]; 60 | data[0] = 0x01; 61 | CommandHeader::from_ogf_ocf(CONTROLLER_OGF, RESET_OCF, 0x00).write_into(&mut data[1..]); 62 | Data::new(&data) 63 | } 64 | Command::LeSetAdvertisingParameters => { 65 | let mut data = [0u8; 4 + 0xf]; 66 | data[0] = 0x01; 67 | CommandHeader::from_ogf_ocf(LE_OGF, SET_ADVERTISING_PARAMETERS_OCF, 0x0f) 68 | .write_into(&mut data[1..]); 69 | // TODO create this - not hardcoded 70 | data[4..].copy_from_slice(&[0x90, 1, 0x20, 3, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0]); 71 | Data::new(&data) 72 | } 73 | Command::LeSetAdvertisingData { ref data } => { 74 | let mut header = [0u8; 4]; 75 | header[0] = 0x01; 76 | CommandHeader::from_ogf_ocf(LE_OGF, SET_ADVERTISING_DATA_OCF, data.len as u8) 77 | .write_into(&mut header[1..]); 78 | let mut res = Data::new(&header); 79 | res.append(data.to_slice()); 80 | res 81 | } 82 | Command::LeSetAdvertiseEnable(enable) => { 83 | let mut data = [0u8; 5]; 84 | data[0] = 0x01; 85 | CommandHeader::from_ogf_ocf(LE_OGF, SET_ADVERTISE_ENABLE_OCF, 0x01) 86 | .write_into(&mut data[1..]); 87 | data[4] = if enable { 1 } else { 0 }; 88 | Data::new(&data) 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/acl.rs: -------------------------------------------------------------------------------- 1 | use crate::{read_to_data, Data, HciConnector}; 2 | 3 | #[derive(Debug, Clone, Copy)] 4 | pub struct AclPacket { 5 | pub handle: u16, 6 | pub boundary_flag: BoundaryFlag, 7 | pub bc_flag: ControllerBroadcastFlag, 8 | pub data: Data, 9 | } 10 | 11 | #[derive(Debug, Clone, Copy)] 12 | pub enum BoundaryFlag { 13 | FirstNonAutoFlushable, 14 | Continuing, 15 | FirstAutoFlushable, 16 | Complete, 17 | } 18 | 19 | /// BC flag from controller to host 20 | #[derive(Debug, Clone, Copy)] 21 | pub enum ControllerBroadcastFlag { 22 | PointToPoint, 23 | NotParkedState, 24 | ParkedState, 25 | Reserved, 26 | } 27 | 28 | /// BC flag from host to controller 29 | #[derive(Debug, Clone, Copy)] 30 | pub enum HostBroadcastFlag { 31 | NoBroadcast, 32 | ActiveSlaveBroadcast, 33 | ParkedSlaveBroadcast, 34 | Reserved, 35 | } 36 | 37 | pub fn parse_acl_packet(connector: &dyn HciConnector) -> AclPacket { 38 | let raw_handle = connector.read().unwrap() as u16 + ((connector.read().unwrap() as u16) << 8); 39 | 40 | let pb = (raw_handle & 0b0011000000000000) >> 12; 41 | let pb = match pb { 42 | 0b00 => BoundaryFlag::FirstNonAutoFlushable, 43 | 0b01 => BoundaryFlag::Continuing, 44 | 0b10 => BoundaryFlag::FirstAutoFlushable, 45 | 0b11 => BoundaryFlag::Complete, 46 | _ => panic!("Unexpected boundary flag"), 47 | }; 48 | 49 | let bc = (raw_handle & 0b1100000000000000) >> 14; 50 | let bc = match bc { 51 | 0b00 => ControllerBroadcastFlag::PointToPoint, 52 | 0b01 => ControllerBroadcastFlag::NotParkedState, 53 | 0b10 => ControllerBroadcastFlag::ParkedState, 54 | 0b11 => ControllerBroadcastFlag::Reserved, 55 | _ => panic!("Unexpected broadcast flag"), 56 | }; 57 | 58 | let handle = raw_handle & 0b111111111111; 59 | 60 | let len = connector.read().unwrap() as u16 + ((connector.read().unwrap() as u16) << 8); 61 | let data = read_to_data(connector, len as usize); 62 | 63 | AclPacket { 64 | handle: handle, 65 | boundary_flag: pb, 66 | bc_flag: bc, 67 | data: data, 68 | } 69 | } 70 | 71 | // including type (0x02) 72 | pub fn encode_acl_packet( 73 | handle: u16, 74 | pb: BoundaryFlag, 75 | bc: HostBroadcastFlag, 76 | payload: Data, 77 | ) -> Data { 78 | let mut data = Data::default(); 79 | 80 | data.append(&[0x02]); 81 | 82 | let mut raw_handle = handle; 83 | 84 | raw_handle |= match pb { 85 | BoundaryFlag::FirstNonAutoFlushable => 0b00, 86 | BoundaryFlag::Continuing => 0b01, 87 | BoundaryFlag::FirstAutoFlushable => 0b10, 88 | BoundaryFlag::Complete => 0b11, 89 | } << 12; 90 | 91 | raw_handle |= match bc { 92 | HostBroadcastFlag::NoBroadcast => 0b00, 93 | HostBroadcastFlag::ActiveSlaveBroadcast => 0b01, 94 | HostBroadcastFlag::ParkedSlaveBroadcast => 0b10, 95 | HostBroadcastFlag::Reserved => 0b11, 96 | } << 14; 97 | 98 | data.append(&[(raw_handle & 0xff) as u8, ((raw_handle >> 8) & 0xff) as u8]); 99 | 100 | let len = payload.len; 101 | data.append(&[(len & 0xff) as u8, ((len >> 8) & 0xff) as u8]); 102 | 103 | data.append(payload.to_slice()); 104 | 105 | data 106 | } 107 | -------------------------------------------------------------------------------- /src/ad_structure.rs: -------------------------------------------------------------------------------- 1 | use crate::{att::Uuid, Data}; 2 | 3 | pub const AD_FLAG_LE_LIMITED_DISCOVERABLE: u8 = 0b00000001; 4 | pub const LE_GENERAL_DISCOVERABLE: u8 = 0b00000010; 5 | pub const BR_EDR_NOT_SUPPORTED: u8 = 0b00000100; 6 | pub const SIMUL_LE_BR_CONTROLLER: u8 = 0b00001000; 7 | pub const SIMUL_LE_BR_HOST: u8 = 0b00010000; 8 | 9 | #[derive(Debug, Copy, Clone)] 10 | pub enum AdStructure<'a> { 11 | /// Device flags and baseband capabilities. 12 | /// 13 | /// This should be sent if any flags apply to the device. If not (ie. the value sent would be 14 | /// 0), this may be omitted. 15 | /// 16 | /// Must not be used in scan response data. 17 | Flags(u8), 18 | 19 | ServiceUuids16(&'a [Uuid]), 20 | ServiceUuids128(&'a [Uuid]), 21 | 22 | /// Service data with 16-bit service UUID. 23 | ServiceData16 { 24 | /// The 16-bit service UUID. 25 | uuid: u16, 26 | /// The associated service data. May be empty. 27 | data: &'a [u8], 28 | }, 29 | 30 | /// Sets the full (unabbreviated) device name. 31 | /// 32 | /// This will be shown to the user when this device is found. 33 | CompleteLocalName(&'a str), 34 | 35 | /// Sets the shortened device name. 36 | ShortenedLocalName(&'a str), 37 | 38 | /// Set manufacturer specific data 39 | ManufacturerSpecificData { 40 | company_identifier: u16, 41 | payload: &'a [u8], 42 | }, 43 | 44 | /// An unknown or unimplemented AD structure stored as raw bytes. 45 | Unknown { 46 | /// Type byte. 47 | ty: u8, 48 | /// Raw data transmitted after the type. 49 | data: &'a [u8], 50 | }, 51 | } 52 | 53 | impl<'a> AdStructure<'a> { 54 | pub fn encode(&self) -> Data { 55 | let mut data = Data::default(); 56 | match self { 57 | AdStructure::Flags(flags) => { 58 | data.append(&[0x02, 0x01, *flags]); 59 | } 60 | AdStructure::ServiceUuids16(uuids) => { 61 | data.append(&[(uuids.len() * 2 + 1) as u8, 0x02]); 62 | for uuid in uuids.iter() { 63 | data.append(uuid.encode().to_slice()); 64 | } 65 | } 66 | AdStructure::ServiceUuids128(_) => todo!(), 67 | AdStructure::ServiceData16 { uuid, data } => todo!(), 68 | AdStructure::CompleteLocalName(name) => { 69 | data.append(&[(name.len() + 1) as u8, 0x09]); 70 | data.append(name.as_bytes()); 71 | } 72 | AdStructure::ShortenedLocalName(_) => todo!(), 73 | AdStructure::ManufacturerSpecificData { 74 | company_identifier, 75 | payload, 76 | } => todo!(), 77 | AdStructure::Unknown { ty, data } => todo!(), 78 | } 79 | 80 | data 81 | } 82 | } 83 | 84 | pub fn create_advertising_data(ad: &[AdStructure]) -> Data { 85 | let mut data = Data::default(); 86 | data.append(&[0]); 87 | 88 | for item in ad.iter() { 89 | data.append(item.encode().to_slice()); 90 | } 91 | 92 | let len = data.len - 1; 93 | data.set(0, len as u8); 94 | 95 | for _ in 0..(31 - len) { 96 | data.append(&[0]); 97 | } 98 | 99 | data 100 | } 101 | -------------------------------------------------------------------------------- /src/event.rs: -------------------------------------------------------------------------------- 1 | use crate::{read_to_data, Data, HciConnector}; 2 | 3 | #[derive(Debug)] 4 | pub struct Event { 5 | code: u8, 6 | data: Data, 7 | } 8 | 9 | #[derive(Debug, Clone, Copy)] 10 | pub enum EventType { 11 | CommandComplete { 12 | num_packets: u8, 13 | opcode: u16, 14 | data: Data, 15 | }, 16 | DisconnectComplete { 17 | handle: u16, 18 | status: ErrorCode, 19 | reason: ErrorCode, 20 | }, 21 | NumberOfCompletedPackets { 22 | number_of_connection_handles: u8, 23 | connection_handles: u16, // should be list 24 | completed_packets: u16, // should be list 25 | }, 26 | Unknown, 27 | } 28 | 29 | #[derive(Debug, Clone, Copy)] 30 | pub enum ErrorCode { 31 | Okay = 0x00, 32 | UnknownHciCommand = 0x01, 33 | UnknownConnectionIdentifier = 0x02, 34 | HardwareFailure = 0x03, 35 | PageTimeout = 0x04, 36 | AuthenticationFailure = 0x05, 37 | PinOrKeyMissing = 0x06, 38 | MemoryCapacityExceeded = 0x07, 39 | ConnectionTimeout = 0x08, 40 | ConnectionLimitExceeded = 0x09, 41 | AclConnectionAlreadyExists = 0x0b, 42 | CommandDisallowed = 0x0c, 43 | RemoteUserTerminatedConnection = 0x13, 44 | // see Error Codes Description in spec 45 | } 46 | 47 | impl ErrorCode { 48 | pub fn from_u8(value: u8) -> ErrorCode { 49 | match value { 50 | 0x00 => ErrorCode::Okay, 51 | 0x01 => ErrorCode::UnknownHciCommand, 52 | 0x02 => ErrorCode::UnknownConnectionIdentifier, 53 | 0x03 => ErrorCode::HardwareFailure, 54 | 0x04 => ErrorCode::PageTimeout, 55 | 0x05 => ErrorCode::AuthenticationFailure, 56 | 0x06 => ErrorCode::PinOrKeyMissing, 57 | 0x07 => ErrorCode::MemoryCapacityExceeded, 58 | 0x08 => ErrorCode::ConnectionTimeout, 59 | 0x09 => ErrorCode::ConnectionLimitExceeded, 60 | 0x0b => ErrorCode::AclConnectionAlreadyExists, 61 | 0x0c => ErrorCode::CommandDisallowed, 62 | 0x13 => ErrorCode::RemoteUserTerminatedConnection, 63 | 64 | _ => panic!("Unknown error code {}", value), 65 | } 66 | } 67 | } 68 | 69 | const EVENT_COMMAND_COMPLETE: u8 = 0x0e; 70 | const EVENT_DISCONNECTION_COMPLETE: u8 = 0x05; 71 | const EVENT_NUMBER_OF_COMPLETED_PACKETS: u8 = 0x13; 72 | 73 | /// Parses a command and assumes the packet type (0x04) is already read. 74 | pub fn parse_event(connector: &dyn HciConnector) -> EventType { 75 | let event = read_to_event(connector); 76 | 77 | match event.code { 78 | EVENT_COMMAND_COMPLETE => { 79 | let data = event.data.to_slice(); 80 | let num_packets = data[0]; 81 | let opcode = ((data[2] as u16) << 8) + data[1] as u16; 82 | let data = event.data.subdata_from(3); 83 | EventType::CommandComplete { 84 | num_packets, 85 | opcode, 86 | data, 87 | } 88 | } 89 | EVENT_DISCONNECTION_COMPLETE => { 90 | let data = event.data.to_slice(); 91 | let status = data[0]; 92 | let handle = ((data[2] as u16) << 8) + data[1] as u16; 93 | let reason = data[3]; 94 | let status = ErrorCode::from_u8(status); 95 | let reason = ErrorCode::from_u8(reason); 96 | EventType::DisconnectComplete { 97 | handle, 98 | status, 99 | reason, 100 | } 101 | } 102 | EVENT_NUMBER_OF_COMPLETED_PACKETS => { 103 | let data = event.data.to_slice(); 104 | let num_handles = data[0]; 105 | let connection_handle = ((data[2] as u16) << 8) + data[1] as u16; 106 | let completed_packet = ((data[4] as u16) << 8) + data[3] as u16; 107 | EventType::NumberOfCompletedPackets { 108 | number_of_connection_handles: num_handles, 109 | connection_handles: connection_handle, 110 | completed_packets: completed_packet, 111 | } 112 | } 113 | _ => { 114 | panic!("Unknown event {:x}", event.code); // should be an error returned 115 | } 116 | } 117 | } 118 | 119 | fn read_to_event(connector: &dyn HciConnector) -> Event { 120 | let code = connector.read().unwrap() as u8; 121 | let len = connector.read().unwrap() as usize; 122 | let data = read_to_data(connector, len); 123 | Event { code, data } 124 | } 125 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(assert_matches)] 3 | 4 | use acl::{parse_acl_packet, AclPacket}; 5 | use att::Uuid; 6 | use command::{ 7 | create_command_data, opcode, Command, SET_ADVERTISE_ENABLE_OCF, SET_ADVERTISING_DATA_OCF, 8 | }; 9 | use command::{LE_OGF, SET_ADVERTISING_PARAMETERS_OCF}; 10 | use event::{parse_event, EventType}; 11 | 12 | pub mod acl; 13 | pub mod att; 14 | pub mod l2cap; 15 | 16 | pub mod command; 17 | pub mod event; 18 | 19 | pub mod ad_structure; 20 | 21 | pub mod attribute_server; 22 | 23 | use command::CONTROLLER_OGF; 24 | use command::RESET_OCF; 25 | 26 | const TIMEOUT_MILLIS: u64 = 1000; 27 | 28 | #[derive(Debug)] 29 | pub enum Error { 30 | Timeout, 31 | Failed, 32 | } 33 | 34 | #[derive(Debug)] 35 | pub enum PollResult { 36 | Event(EventType), 37 | AsyncData(AclPacket), 38 | } 39 | 40 | #[derive(Clone, Copy)] 41 | pub struct Data { 42 | pub data: [u8; 128], 43 | pub len: usize, 44 | } 45 | 46 | impl Data { 47 | pub fn new(bytes: &[u8]) -> Data { 48 | let mut data = [0u8; 128]; 49 | data[..bytes.len()].copy_from_slice(bytes); 50 | Data { 51 | data, 52 | len: bytes.len(), 53 | } 54 | } 55 | 56 | pub fn to_slice(&self) -> &[u8] { 57 | &self.data[0..self.len] 58 | } 59 | 60 | pub fn subdata_from(&self, from: usize) -> Data { 61 | let mut data = [0u8; 128]; 62 | let new_len = self.len - from; 63 | data[..new_len].copy_from_slice(&self.data[from..(from + new_len)]); 64 | Data { data, len: new_len } 65 | } 66 | 67 | pub fn append(&mut self, bytes: &[u8]) { 68 | self.data[self.len..(self.len + bytes.len())].copy_from_slice(bytes); 69 | self.len += bytes.len(); 70 | } 71 | 72 | pub fn set(&mut self, index: usize, byte: u8) { 73 | self.data[index] = byte; 74 | } 75 | } 76 | 77 | impl Default for Data { 78 | fn default() -> Self { 79 | Data::new(&[]) 80 | } 81 | } 82 | 83 | impl core::fmt::Debug for Data { 84 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 85 | write!(f, "{:x?}", &self.data[..self.len]).expect("Failed to format Data"); 86 | Ok(()) 87 | } 88 | } 89 | 90 | const PACKET_TYPE_COMMAND: u8 = 0x01; 91 | const PACKET_TYPE_ASYNC_DATA: u8 = 0x02; 92 | const PACKET_TYPE_EVENT: u8 = 0x04; 93 | 94 | fn check_command_completed(event: EventType) -> Result { 95 | if let EventType::CommandComplete { 96 | num_packets: _, 97 | opcode: _, 98 | data, 99 | } = event 100 | { 101 | if data.to_slice()[0] != 0 { 102 | return Err(Error::Failed); 103 | } 104 | } 105 | 106 | Ok(event) 107 | } 108 | 109 | pub struct Ble<'a> { 110 | connector: &'a dyn HciConnector, 111 | } 112 | 113 | impl<'a> Ble<'a> { 114 | pub fn new(connector: &'a dyn HciConnector) -> Ble<'a> { 115 | Ble { connector } 116 | } 117 | 118 | pub fn init(&mut self) -> Result 119 | where 120 | Self: Sized, 121 | { 122 | Ok(self.cmd_reset()?) 123 | } 124 | 125 | pub fn cmd_reset(&mut self) -> Result 126 | where 127 | Self: Sized, 128 | { 129 | self.write_bytes(create_command_data(Command::Reset).to_slice()); 130 | check_command_completed(self.wait_for_command_complete(CONTROLLER_OGF, RESET_OCF)?) 131 | } 132 | 133 | pub fn cmd_set_le_advertising_parameters(&mut self) -> Result 134 | where 135 | Self: Sized, 136 | { 137 | self.write_bytes(create_command_data(Command::LeSetAdvertisingParameters).to_slice()); 138 | check_command_completed( 139 | self.wait_for_command_complete(LE_OGF, SET_ADVERTISING_PARAMETERS_OCF)?, 140 | ) 141 | } 142 | 143 | pub fn cmd_set_le_advertising_data(&mut self, data: Data) -> Result 144 | where 145 | Self: Sized, 146 | { 147 | self.write_bytes(create_command_data(Command::LeSetAdvertisingData { data }).to_slice()); 148 | check_command_completed(self.wait_for_command_complete(LE_OGF, SET_ADVERTISING_DATA_OCF)?) 149 | } 150 | 151 | pub fn cmd_set_le_advertise_enable(&mut self, enable: bool) -> Result 152 | where 153 | Self: Sized, 154 | { 155 | self.write_bytes(create_command_data(Command::LeSetAdvertiseEnable(enable)).to_slice()); 156 | check_command_completed(self.wait_for_command_complete(LE_OGF, SET_ADVERTISE_ENABLE_OCF)?) 157 | } 158 | 159 | fn wait_for_command_complete(&mut self, ogf: u8, ocf: u16) -> Result 160 | where 161 | Self: Sized, 162 | { 163 | let timeout_at = self.connector.millis() + TIMEOUT_MILLIS; 164 | loop { 165 | let res = self.poll(); 166 | 167 | match res { 168 | Some(PollResult::Event(event)) => match event { 169 | EventType::CommandComplete { opcode: code, .. } if code == opcode(ogf, ocf) => { 170 | return Ok(event); 171 | } 172 | _ => (), 173 | }, 174 | _ => (), 175 | } 176 | 177 | if self.connector.millis() > timeout_at { 178 | return Err(Error::Timeout); 179 | } 180 | } 181 | } 182 | 183 | pub fn poll(&mut self) -> Option 184 | where 185 | Self: Sized, 186 | { 187 | // poll & process input 188 | let packet_type = self.connector.read(); 189 | 190 | match packet_type { 191 | Some(packet_type) => match packet_type { 192 | PACKET_TYPE_COMMAND => {} 193 | PACKET_TYPE_ASYNC_DATA => { 194 | let acl_packet = parse_acl_packet(self.connector); 195 | return Some(PollResult::AsyncData(acl_packet)); 196 | } 197 | PACKET_TYPE_EVENT => { 198 | let event = parse_event(self.connector); 199 | return Some(PollResult::Event(event)); 200 | } 201 | _ => { 202 | // error 203 | } 204 | }, 205 | None => {} 206 | } 207 | 208 | None 209 | } 210 | 211 | fn write_bytes(&mut self, bytes: &[u8]) { 212 | for b in bytes { 213 | self.connector.write(*b); 214 | } 215 | } 216 | } 217 | 218 | fn read_to_data(connector: &dyn HciConnector, len: usize) -> Data { 219 | let mut data = [0u8; 128]; 220 | for i in 0..len { 221 | data[i] = connector.read().unwrap(); 222 | } 223 | let mut data = Data::new(&data); 224 | data.len = len; 225 | data 226 | } 227 | 228 | pub trait HciConnector { 229 | fn read(&self) -> Option; 230 | 231 | fn write(&self, data: u8); 232 | 233 | fn millis(&self) -> u64; 234 | } 235 | -------------------------------------------------------------------------------- /src/attribute_server.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | acl::{encode_acl_packet, BoundaryFlag, HostBroadcastFlag}, 3 | att::{ 4 | att_encode_error_response, att_encode_read_by_group_type_response, 5 | att_encode_read_by_type_response, att_encode_read_response, att_encode_write_response, 6 | parse_att, Att, AttErrorCode, AttParseError, AttributeData, AttributePayloadData, Uuid, 7 | ATT_READ_BY_GROUP_TYPE_REQUEST_OPCODE, ATT_READ_BY_TYPE_REQUEST_OPCODE, 8 | }, 9 | l2cap::{encode_l2cap, parse_l2cap, L2capParseError}, 10 | Ble, Data, 11 | }; 12 | 13 | const PRIMARY_SERVICE_UUID16: Uuid = Uuid::Uuid16(0x2800); 14 | const CHARACTERISTIC_UUID16: Uuid = Uuid::Uuid16(0x2803); 15 | 16 | #[derive(Debug)] 17 | pub enum AttributeServerError { 18 | L2capError(L2capParseError), 19 | AttError(AttParseError), 20 | } 21 | 22 | impl From for AttributeServerError { 23 | fn from(err: L2capParseError) -> Self { 24 | AttributeServerError::L2capError(err) 25 | } 26 | } 27 | 28 | impl From for AttributeServerError { 29 | fn from(err: AttParseError) -> Self { 30 | AttributeServerError::AttError(err) 31 | } 32 | } 33 | 34 | pub struct AttributeServer<'a> { 35 | ble: &'a mut Ble<'a>, 36 | services: &'a mut [Service<'a>], 37 | } 38 | 39 | impl<'a> AttributeServer<'a> { 40 | pub fn new(ble: &'a mut Ble<'a>, services: &'a mut [Service<'a>]) -> AttributeServer<'a> { 41 | let mut current_handle = 1; 42 | for service in services.iter_mut() { 43 | service.start_handle = current_handle; 44 | service.end_handle = current_handle + 2; 45 | service.characteristics_handle = current_handle + 2; 46 | current_handle += 3; 47 | } 48 | AttributeServer { ble, services } 49 | } 50 | 51 | pub fn do_work(&mut self) -> Result<(), AttributeServerError> { 52 | let packet = self.ble.poll(); 53 | 54 | match packet { 55 | None => Ok(()), 56 | Some(packet) => match packet { 57 | crate::PollResult::Event(_) => Ok(()), 58 | crate::PollResult::AsyncData(packet) => { 59 | let l2cap_packet = parse_l2cap(packet)?; 60 | let packet = parse_att(l2cap_packet)?; 61 | match packet { 62 | Att::ReadByGroupTypeReq { 63 | start, 64 | end, 65 | group_type, 66 | } => { 67 | self.handle_read_by_group_type_req(start, end, group_type); 68 | Ok(()) 69 | } 70 | 71 | Att::ReadByTypeReq { 72 | start, 73 | end, 74 | attribute_type, 75 | } => { 76 | self.handle_read_by_type_req(start, end, attribute_type); 77 | Ok(()) 78 | } 79 | 80 | Att::ReadReq { handle } => { 81 | self.handle_read_req(handle); 82 | Ok(()) 83 | } 84 | 85 | Att::WriteReq { handle, data } => { 86 | self.handle_write_req(handle, data); 87 | Ok(()) 88 | } 89 | } 90 | } 91 | }, 92 | } 93 | } 94 | 95 | fn handle_read_by_group_type_req(&mut self, start: u16, end: u16, group_type: Uuid) { 96 | if group_type == PRIMARY_SERVICE_UUID16 { 97 | // TODO respond with all finds - not just one 98 | for service in self.services.iter() { 99 | if service.start_handle >= start && service.end_handle <= end { 100 | let attribute_list = [AttributeData::new( 101 | service.start_handle, 102 | service.end_handle, 103 | group_type, 104 | )]; 105 | self.write_att(att_encode_read_by_group_type_response(&attribute_list)); 106 | return; 107 | } 108 | } 109 | } 110 | 111 | // respond with error 112 | self.write_att(att_encode_error_response( 113 | ATT_READ_BY_GROUP_TYPE_REQUEST_OPCODE, 114 | start, 115 | AttErrorCode::AttributeNotFound, 116 | )); 117 | } 118 | 119 | fn handle_read_by_type_req(&mut self, start: u16, end: u16, attribute_type: Uuid) { 120 | if attribute_type == CHARACTERISTIC_UUID16 { 121 | // TODO respond with all finds - not just one 122 | for service in self.services.iter() { 123 | if service.start_handle >= start && service.end_handle <= end { 124 | let mut data = Data::new(&[ 125 | service.permissions, 126 | // 2 byte handle pointing to characteristic value 127 | (service.characteristics_handle & 0xff) as u8, 128 | ((service.characteristics_handle & 0xff00) >> 8) as u8, 129 | // UUID of characteristic value 130 | ]); 131 | data.append((&service.uuid).encode().to_slice()); 132 | 133 | let attribute_list = 134 | [AttributePayloadData::new(service.start_handle + 1, data)]; 135 | self.write_att(att_encode_read_by_type_response(&attribute_list)); 136 | 137 | return; 138 | } 139 | } 140 | } 141 | 142 | // respond with error 143 | self.write_att(att_encode_error_response( 144 | ATT_READ_BY_TYPE_REQUEST_OPCODE, 145 | start, 146 | AttErrorCode::AttributeNotFound, 147 | )); 148 | } 149 | 150 | fn handle_read_req(&mut self, handle: u16) { 151 | let mut answer = None; 152 | for service in self.services.iter_mut() { 153 | if service.characteristics_handle == handle { 154 | answer = Some((*service.read_function)()); 155 | break; 156 | } 157 | } 158 | 159 | if let Some(answer) = answer { 160 | self.write_att(att_encode_read_response(&answer)); 161 | return; 162 | } 163 | 164 | panic!("should create a reasonable error instead of panic"); 165 | } 166 | 167 | fn handle_write_req(&mut self, handle: u16, data: Data) { 168 | let mut found = false; 169 | for service in self.services.iter_mut() { 170 | if service.characteristics_handle == handle { 171 | (*service.write_function)(data); 172 | found = true; 173 | break; 174 | } 175 | } 176 | 177 | if found { 178 | self.write_att(att_encode_write_response()); 179 | return; 180 | } 181 | 182 | panic!("should create a reasonable error instead of panic"); 183 | } 184 | 185 | fn write_att(&mut self, data: Data) { 186 | let res = encode_l2cap(data); 187 | let res = encode_acl_packet( 188 | 0x0000, 189 | BoundaryFlag::FirstAutoFlushable, 190 | HostBroadcastFlag::NoBroadcast, 191 | res, 192 | ); 193 | self.ble.write_bytes(res.to_slice()); 194 | } 195 | } 196 | 197 | pub const ATT_READABLE: u8 = 0x02; 198 | pub const ATT_WRITEABLE: u8 = 0x08; 199 | 200 | pub struct Service<'a> { 201 | pub uuid: Uuid, 202 | pub permissions: u8, 203 | pub read_function: &'a mut dyn FnMut() -> Data, 204 | pub write_function: &'a mut dyn FnMut(Data), 205 | start_handle: u16, 206 | end_handle: u16, 207 | characteristics_handle: u16, 208 | } 209 | 210 | impl<'a> Service<'a> { 211 | pub fn new( 212 | uuid: Uuid, 213 | permissions: u8, 214 | read_function: &'a mut dyn FnMut() -> Data, 215 | write_function: &'a mut dyn FnMut(Data), 216 | ) -> Service<'a> { 217 | Service { 218 | uuid, 219 | permissions, 220 | read_function, 221 | write_function, 222 | start_handle: 0, 223 | end_handle: 0, 224 | characteristics_handle: 0, 225 | } 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/att.rs: -------------------------------------------------------------------------------- 1 | use core::convert::TryInto; 2 | 3 | use crate::{l2cap::L2capPacket, Data}; 4 | 5 | pub const ATT_READ_BY_GROUP_TYPE_REQUEST_OPCODE: u8 = 0x10; 6 | const ATT_READ_BY_GROUP_TYPE_RESPONSE_OPCODE: u8 = 0x11; 7 | const ATT_ERROR_RESPONSE_OPCODE: u8 = 0x01; 8 | pub const ATT_READ_BY_TYPE_REQUEST_OPCODE: u8 = 0x08; 9 | const ATT_READ_BY_TYPE_RESPONSE_OPCODE: u8 = 0x09; 10 | pub const ATT_READ_REQUEST_OPCODE: u8 = 0x0a; 11 | const ATT_READ_RESPONSE_OPCODE: u8 = 0x0b; 12 | pub const ATT_WRITE_REQUEST_OPCODE: u8 = 0x12; 13 | const ATT_WRITE_RESPONSE_OPCODE: u8 = 0x13; 14 | 15 | #[derive(Debug, PartialEq)] 16 | pub enum Uuid { 17 | Uuid16(u16), 18 | Uuid128([u8; 16]), 19 | } 20 | 21 | impl Uuid { 22 | pub(crate) fn encode(&self) -> Data { 23 | let mut data = Data::default(); 24 | 25 | match self { 26 | Uuid::Uuid16(uuid) => { 27 | data.append(&[(uuid & 0xff) as u8, ((uuid >> 8) & 0xff) as u8]); 28 | } 29 | Uuid::Uuid128(uuid) => { 30 | let mut reversed = uuid.clone(); 31 | reversed.reverse(); 32 | data.append(&reversed); 33 | } 34 | } 35 | data 36 | } 37 | } 38 | 39 | #[derive(Debug)] 40 | pub enum AttErrorCode { 41 | /// Attempted to use an `Handle` that isn't valid on this server. 42 | InvalidHandle = 0x01, 43 | /// Attribute isn't readable. 44 | ReadNotPermitted = 0x02, 45 | /// Attribute isn't writable. 46 | WriteNotPermitted = 0x03, 47 | /// Attribute PDU is invalid. 48 | InvalidPdu = 0x04, 49 | /// Authentication needed before attribute can be read/written. 50 | InsufficientAuthentication = 0x05, 51 | /// Server doesn't support this operation. 52 | RequestNotSupported = 0x06, 53 | /// Offset was past the end of the attribute. 54 | InvalidOffset = 0x07, 55 | /// Authorization needed before attribute can be read/written. 56 | InsufficientAuthorization = 0x08, 57 | /// Too many "prepare write" requests have been queued. 58 | PrepareQueueFull = 0x09, 59 | /// No attribute found within the specified attribute handle range. 60 | AttributeNotFound = 0x0A, 61 | /// Attribute can't be read/written using *Read Key Blob* request. 62 | AttributeNotLong = 0x0B, 63 | /// The encryption key in use is too weak to access an attribute. 64 | InsufficientEncryptionKeySize = 0x0C, 65 | /// Attribute value has an incorrect length for the operation. 66 | InvalidAttributeValueLength = 0x0D, 67 | /// Request has encountered an "unlikely" error and could not be completed. 68 | UnlikelyError = 0x0E, 69 | /// Attribute cannot be read/written without an encrypted connection. 70 | InsufficientEncryption = 0x0F, 71 | /// Attribute type is an invalid grouping attribute according to a higher-layer spec. 72 | UnsupportedGroupType = 0x10, 73 | /// Server didn't have enough resources to complete a request. 74 | InsufficientResources = 0x11, 75 | } 76 | 77 | #[derive(Debug)] 78 | pub enum Att { 79 | ReadByGroupTypeReq { 80 | start: u16, 81 | end: u16, 82 | group_type: Uuid, 83 | }, 84 | ReadByTypeReq { 85 | start: u16, 86 | end: u16, 87 | attribute_type: Uuid, 88 | }, 89 | ReadReq { 90 | handle: u16, 91 | }, 92 | WriteReq { 93 | handle: u16, 94 | data: Data, 95 | }, 96 | } 97 | 98 | #[derive(Debug)] 99 | pub enum AttParseError { 100 | Other, 101 | UnknownOpcode(u8), 102 | UnexpectedPayload, 103 | } 104 | 105 | pub fn parse_att(packet: L2capPacket) -> Result { 106 | let opcode = packet.payload.to_slice()[0]; 107 | let payload = &packet.payload.to_slice()[1..]; 108 | 109 | match opcode { 110 | ATT_READ_BY_GROUP_TYPE_REQUEST_OPCODE => { 111 | let start_handle = (payload[0] as u16) + ((payload[1] as u16) << 8); 112 | let end_handle = (payload[2] as u16) + ((payload[3] as u16) << 8); 113 | 114 | let group_type = if payload.len() == 6 { 115 | Uuid::Uuid16((payload[4] as u16) + ((payload[5] as u16) << 8)) 116 | } else if payload.len() == 20 { 117 | let uuid = payload[4..21] 118 | .try_into() 119 | .map_err(|_| AttParseError::Other)?; 120 | Uuid::Uuid128(uuid) 121 | } else { 122 | return Err(AttParseError::UnexpectedPayload); 123 | }; 124 | 125 | Ok(Att::ReadByGroupTypeReq { 126 | start: start_handle, 127 | end: end_handle, 128 | group_type: group_type, 129 | }) 130 | } 131 | ATT_READ_BY_TYPE_REQUEST_OPCODE => { 132 | let start_handle = (payload[0] as u16) + ((payload[1] as u16) << 8); 133 | let end_handle = (payload[2] as u16) + ((payload[3] as u16) << 8); 134 | 135 | let attribute_type = if payload.len() == 6 { 136 | Uuid::Uuid16((payload[4] as u16) + ((payload[5] as u16) << 8)) 137 | } else if payload.len() == 20 { 138 | let uuid = payload[4..21] 139 | .try_into() 140 | .map_err(|_| AttParseError::Other)?; 141 | Uuid::Uuid128(uuid) 142 | } else { 143 | return Err(AttParseError::UnexpectedPayload); 144 | }; 145 | 146 | Ok(Att::ReadByTypeReq { 147 | start: start_handle, 148 | end: end_handle, 149 | attribute_type, 150 | }) 151 | } 152 | ATT_READ_REQUEST_OPCODE => { 153 | let handle = (payload[0] as u16) + ((payload[1] as u16) << 8); 154 | 155 | Ok(Att::ReadReq { handle }) 156 | } 157 | ATT_WRITE_REQUEST_OPCODE => { 158 | let handle = (payload[0] as u16) + ((payload[1] as u16) << 8); 159 | let mut data = Data::default(); 160 | data.append(&payload[2..]); 161 | 162 | Ok(Att::WriteReq { handle, data }) 163 | } 164 | _ => Err(AttParseError::UnknownOpcode(opcode)), 165 | } 166 | } 167 | 168 | #[derive(Debug)] 169 | pub struct AttributeData { 170 | attribute_handle: u16, 171 | end_group_handle: u16, 172 | attribute_value: Uuid, 173 | } 174 | 175 | impl AttributeData { 176 | pub fn new( 177 | attribute_handle: u16, 178 | end_group_handle: u16, 179 | attribute_value: Uuid, 180 | ) -> AttributeData { 181 | AttributeData { 182 | attribute_handle, 183 | end_group_handle, 184 | attribute_value, 185 | } 186 | } 187 | 188 | pub fn encode(&self) -> Data { 189 | let mut data = Data::default(); 190 | data.append(&[ 191 | (self.attribute_handle & 0xff) as u8, 192 | ((self.attribute_handle >> 8) & 0xff) as u8, 193 | ]); 194 | data.append(&[ 195 | (self.end_group_handle & 0xff) as u8, 196 | ((self.end_group_handle >> 8) & 0xff) as u8, 197 | ]); 198 | data.append(self.attribute_value.encode().to_slice()); 199 | data 200 | } 201 | } 202 | 203 | #[derive(Debug)] 204 | pub struct AttributePayloadData { 205 | attribute_handle: u16, 206 | attribute_value: Data, 207 | } 208 | 209 | impl AttributePayloadData { 210 | pub fn new(attribute_handle: u16, attribute_value: Data) -> AttributePayloadData { 211 | AttributePayloadData { 212 | attribute_handle, 213 | attribute_value, 214 | } 215 | } 216 | 217 | pub fn encode(&self) -> Data { 218 | let mut data = Data::default(); 219 | data.append(&[ 220 | (self.attribute_handle & 0xff) as u8, 221 | ((self.attribute_handle >> 8) & 0xff) as u8, 222 | ]); 223 | data.append(self.attribute_value.to_slice()); 224 | data 225 | } 226 | 227 | pub fn len(&self) -> usize { 228 | 2 + self.attribute_value.len 229 | } 230 | } 231 | 232 | pub fn att_encode_read_by_group_type_response(attribute_list: &[AttributeData]) -> Data { 233 | let attribute_data_size = match attribute_list[0].attribute_value { 234 | Uuid::Uuid16(_) => 6, 235 | Uuid::Uuid128(_) => 20, 236 | }; 237 | 238 | let mut data = Data::default(); 239 | data.append(&[ATT_READ_BY_GROUP_TYPE_RESPONSE_OPCODE]); 240 | data.append(&[attribute_data_size]); 241 | 242 | for att_data in attribute_list { 243 | data.append(att_data.encode().to_slice()); 244 | } 245 | 246 | data 247 | } 248 | 249 | pub fn att_encode_error_response(opcode: u8, handle: u16, code: AttErrorCode) -> Data { 250 | let mut data = Data::default(); 251 | data.append(&[ATT_ERROR_RESPONSE_OPCODE]); 252 | data.append(&[opcode]); 253 | data.append(&[(handle & 0xff) as u8, ((handle >> 8) & 0xff) as u8]); 254 | data.append(&[code as u8]); 255 | 256 | data 257 | } 258 | 259 | pub fn att_encode_read_by_type_response(attribute_list: &[AttributePayloadData]) -> Data { 260 | let attribute_data_size = attribute_list[0].len(); // check if empty 261 | 262 | let mut data = Data::default(); 263 | data.append(&[ATT_READ_BY_TYPE_RESPONSE_OPCODE]); 264 | data.append(&[attribute_data_size as u8]); 265 | 266 | for att_data in attribute_list { 267 | data.append(att_data.encode().to_slice()); 268 | } 269 | 270 | data 271 | } 272 | 273 | pub fn att_encode_read_response(payload: &Data) -> Data { 274 | let mut data = Data::default(); 275 | data.append(&[ATT_READ_RESPONSE_OPCODE]); 276 | data.append(payload.to_slice()); 277 | 278 | data 279 | } 280 | 281 | pub fn att_encode_write_response() -> Data { 282 | let mut data = Data::default(); 283 | data.append(&[ATT_WRITE_RESPONSE_OPCODE]); 284 | 285 | data 286 | } 287 | -------------------------------------------------------------------------------- /tests/test_ble.rs: -------------------------------------------------------------------------------- 1 | #![feature(assert_matches)] 2 | 3 | use std::cell::RefCell; 4 | 5 | use ble_hci::{ 6 | acl::{encode_acl_packet, AclPacket, BoundaryFlag, ControllerBroadcastFlag, HostBroadcastFlag}, 7 | ad_structure::{ 8 | create_advertising_data, AdStructure, BR_EDR_NOT_SUPPORTED, LE_GENERAL_DISCOVERABLE, 9 | }, 10 | att::{ 11 | att_encode_error_response, att_encode_read_by_group_type_response, 12 | att_encode_read_by_type_response, att_encode_read_response, att_encode_write_response, 13 | parse_att, Att, AttErrorCode, AttributeData, AttributePayloadData, Uuid, 14 | ATT_READ_BY_GROUP_TYPE_REQUEST_OPCODE, 15 | }, 16 | attribute_server::{AttributeServer, Service, ATT_READABLE, ATT_WRITEABLE}, 17 | command::{create_command_data, Command, CommandHeader}, 18 | event::{ErrorCode, EventType}, 19 | l2cap::{encode_l2cap, parse_l2cap}, 20 | Ble, Data, HciConnector, PollResult, 21 | }; 22 | 23 | extern crate std; 24 | 25 | struct TestConnector { 26 | to_read: RefCell<[u8; 128]>, 27 | to_write: RefCell<[u8; 128]>, 28 | read_idx: RefCell, 29 | read_max: RefCell, 30 | write_idx: RefCell, 31 | current_millis: RefCell<[u64; 128]>, 32 | current_millis_idx: RefCell, 33 | } 34 | 35 | impl TestConnector { 36 | fn reset(&self) { 37 | *(self.read_idx.borrow_mut()) = 0; 38 | *(self.read_max.borrow_mut()) = 0; 39 | *(self.write_idx.borrow_mut()) = 0; 40 | } 41 | 42 | fn provide_data_to_read(&self, data: &[u8]) { 43 | let len = data.len(); 44 | let from = *(self.read_max.borrow()); 45 | let to = from + len; 46 | (self.to_read.borrow_mut())[from..to].copy_from_slice(data); 47 | *(self.read_max.borrow_mut()) += len; 48 | } 49 | 50 | fn set_read_max(&self, v: usize) { 51 | *(self.read_max.borrow_mut()) = v; 52 | } 53 | 54 | fn set_read_idx(&self, v: usize) { 55 | *(self.read_idx.borrow_mut()) = v; 56 | } 57 | 58 | fn _set_write_idx(&self, v: usize) { 59 | *(self.write_idx.borrow_mut()) = v; 60 | } 61 | 62 | fn _get_read_max(&self) -> usize { 63 | *(self.read_max.borrow()) 64 | } 65 | 66 | fn _get_read_idx(&self) -> usize { 67 | *(self.read_idx.borrow()) 68 | } 69 | 70 | fn get_write_idx(&self) -> usize { 71 | *(self.write_idx.borrow()) 72 | } 73 | 74 | fn get_to_write_at(&self, idx: usize) -> u8 { 75 | (self.to_write.borrow())[idx] 76 | } 77 | 78 | fn set_current_millis_at(&self, idx: usize, v: u64) { 79 | (self.current_millis.borrow_mut())[idx] = v; 80 | } 81 | 82 | fn get_current_millis_idx(&self) -> usize { 83 | *(self.current_millis_idx.borrow()) 84 | } 85 | 86 | fn get_written_data(&self) -> Data { 87 | Data::new(&(self.to_write.borrow_mut())[..*(self.write_idx.borrow())]) 88 | } 89 | } 90 | 91 | impl HciConnector for TestConnector { 92 | fn read(&self) -> Option { 93 | if self.read_max > self.read_idx { 94 | let r = (self.to_read.borrow())[*(self.read_idx.borrow())]; 95 | *(self.read_idx.borrow_mut()) += 1; 96 | Some(r) 97 | } else { 98 | None 99 | } 100 | } 101 | 102 | fn write(&self, data: u8) { 103 | (self.to_write.borrow_mut())[*(self.write_idx.borrow())] = data; 104 | *(self.write_idx.borrow_mut()) += 1; 105 | } 106 | 107 | fn millis(&self) -> u64 { 108 | let r = (self.current_millis.borrow())[*(self.current_millis_idx.borrow())]; 109 | *(self.current_millis_idx.borrow_mut()) += 1; 110 | r 111 | } 112 | } 113 | 114 | fn connector() -> TestConnector { 115 | TestConnector { 116 | to_read: RefCell::new([0u8; 128]), 117 | to_write: RefCell::new([0u8; 128]), 118 | read_idx: RefCell::new(0), 119 | read_max: RefCell::new(0), 120 | write_idx: RefCell::new(0), 121 | current_millis: RefCell::new([0; 128]), 122 | current_millis_idx: RefCell::new(0), 123 | } 124 | } 125 | 126 | #[test] 127 | fn testing_will_work() { 128 | let connector = connector(); 129 | 130 | connector.set_read_max(1); 131 | assert_eq!(Some(0), connector.read()); 132 | assert_eq!(None, connector.read()); 133 | 134 | connector.set_read_idx(0); 135 | 136 | assert_eq!(Some(0), connector.read()); 137 | assert_eq!(None, connector.read()); 138 | 139 | connector.write(0xff); 140 | 141 | assert_eq!(connector.get_write_idx(), 1); 142 | assert_eq!(connector.get_to_write_at(0), 0xff); 143 | } 144 | 145 | #[test] 146 | fn parse_event() { 147 | let connector = connector(); 148 | let mut ble = Ble::new(&connector); 149 | 150 | connector.provide_data_to_read(&[0x04, 0x0e, 0x04, 0x05, 0x03, 0x0c, 0x00]); 151 | 152 | let res = ble.poll(); 153 | 154 | assert_matches!(res, Some(PollResult::Event(EventType::CommandComplete { num_packets: 5, opcode: 0x0c03, data})) if data.to_slice() == &[0] ); 155 | 156 | connector.reset(); 157 | } 158 | 159 | #[test] 160 | fn init_works() { 161 | let connector = connector(); 162 | let mut ble = Ble::new(&connector); 163 | 164 | connector.provide_data_to_read(&[0x04, 0x0e, 0x04, 0x05, 0x03, 0x0c, 0x00]); 165 | 166 | let res = ble.init(); 167 | 168 | assert_matches!(res, Ok(EventType::CommandComplete{ num_packets: 5, opcode: 0x0c03, data}) if data.to_slice() == &[0]); 169 | 170 | assert_eq!(connector.get_write_idx(), 4); 171 | assert_eq!(connector.get_to_write_at(0), 0x01); 172 | assert_eq!(connector.get_to_write_at(1), 0x03); 173 | assert_eq!(connector.get_to_write_at(2), 0x0c); 174 | assert_eq!(connector.get_to_write_at(3), 0x00); 175 | } 176 | 177 | #[test] 178 | fn init_fails_timeout() { 179 | let connector = connector(); 180 | let mut ble = Ble::new(&connector); 181 | 182 | connector.set_current_millis_at(0, 0); 183 | connector.set_current_millis_at(1, 100); 184 | connector.set_current_millis_at(2, 2000); 185 | 186 | let res = ble.init(); 187 | 188 | assert_matches!(res, Err(ble_hci::Error::Timeout)); 189 | assert_eq!(connector.get_current_millis_idx(), 3); 190 | } 191 | 192 | #[test] 193 | fn init_fails() { 194 | let connector = connector(); 195 | let mut ble = Ble::new(&connector); 196 | 197 | connector.provide_data_to_read(&[0x04, 0x0e, 0x04, 0x05, 0x03, 0x0c, 0xff]); 198 | 199 | let res = ble.init(); 200 | 201 | assert_matches!(res, Err(ble_hci::Error::Failed)); 202 | 203 | assert_eq!(connector.get_write_idx(), 4); 204 | assert_eq!(connector.get_to_write_at(0), 0x01); 205 | assert_eq!(connector.get_to_write_at(1), 0x03); 206 | assert_eq!(connector.get_to_write_at(2), 0x0c); 207 | assert_eq!(connector.get_to_write_at(3), 0x00); 208 | } 209 | 210 | #[test] 211 | pub fn command_header_reset_parse_works() { 212 | let header = CommandHeader::from_bytes(&[0x03, 0x0c, 0x00]); 213 | 214 | assert_eq!(header.ogf(), 0x03); 215 | assert_eq!(header.ocf(), 0x03); 216 | assert_eq!(header.len, 0x00); 217 | } 218 | 219 | #[test] 220 | pub fn command_header_let_set_adv_param_parse_works() { 221 | let header = CommandHeader::from_bytes(&[0x06, 0x20, 0x0f]); 222 | 223 | assert_eq!(header.ogf(), 0x08); 224 | assert_eq!(header.ocf(), 0x06); 225 | assert_eq!(header.len, 0x0f); 226 | } 227 | 228 | #[test] 229 | pub fn command_header_reset_works() { 230 | let header = CommandHeader::from_ogf_ocf(0x03, 0x03, 0x00); 231 | 232 | assert_eq!(header.ogf(), 0x03); 233 | assert_eq!(header.ocf(), 0x03); 234 | assert_eq!(header.opcode, 0x0c03); 235 | assert_eq!(header.len, 0x00); 236 | } 237 | 238 | #[test] 239 | pub fn command_header_set_adv_param_works() { 240 | let header = CommandHeader::from_ogf_ocf(0x08, 0x06, 0x0f); 241 | 242 | assert_eq!(header.ogf(), 0x08); 243 | assert_eq!(header.ocf(), 0x06); 244 | assert_eq!(header.opcode, 0x2006); 245 | assert_eq!(header.len, 0x0f); 246 | } 247 | 248 | #[test] 249 | fn create_reset_command_works() { 250 | let data = create_command_data(Command::Reset); 251 | assert_eq!(data.len, 4); 252 | assert_eq!(data.data[0..4], [0x01, 0x03, 0x0c, 0x00]); 253 | } 254 | 255 | #[test] 256 | fn create_le_set_advertising_parameters_works() { 257 | let data = create_command_data(Command::LeSetAdvertisingParameters); 258 | assert_eq!(data.len, 19); 259 | assert_eq!( 260 | data.data[..19], 261 | [0x01, 0x06, 0x20, 0x0f, 0x90, 1, 0x20, 3, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0] 262 | ); 263 | } 264 | 265 | #[test] 266 | fn set_advertising_parameters_works() { 267 | let connector = connector(); 268 | let mut ble = Ble::new(&connector); 269 | 270 | connector.provide_data_to_read(&[0x04, 0x0e, 0x04, 0x05, 0x06, 0x20, 0x00]); 271 | 272 | let res = ble.cmd_set_le_advertising_parameters(); 273 | 274 | assert_matches!(res, Ok(EventType::CommandComplete{ num_packets: 5, opcode: 0x2006, data}) if data.to_slice() == &[0]); 275 | } 276 | 277 | #[test] 278 | fn create_le_set_advertising_data_works() { 279 | let data = create_command_data(Command::LeSetAdvertisingData { 280 | data: Data::new(&[1, 2, 3, 4, 5]), 281 | }); 282 | assert_eq!(data.len, 9); 283 | assert_eq!(data.data[..9], [0x01, 0x08, 0x20, 0x05, 1, 2, 3, 4, 5]); 284 | } 285 | 286 | #[test] 287 | fn le_set_advertising_data_works() { 288 | let connector = connector(); 289 | let mut ble = Ble::new(&connector); 290 | 291 | connector.provide_data_to_read(&[0x04, 0x0e, 0x04, 0x05, 0x08, 0x20, 0x00]); 292 | 293 | let res = ble.cmd_set_le_advertising_data(Data::new(&[1, 2, 3, 4, 5])); 294 | 295 | assert_matches!(res, Ok(EventType::CommandComplete{ num_packets: 5, opcode: 0x2008, data}) if data.to_slice() == &[0]); 296 | } 297 | 298 | #[test] 299 | fn create_le_set_advertise_enable_works() { 300 | let data = create_command_data(Command::LeSetAdvertiseEnable(true)); 301 | assert_eq!(data.len, 5); 302 | assert_eq!(data.data[..5], [0x01, 0x0a, 0x20, 0x01, 0x01]); 303 | } 304 | 305 | #[test] 306 | fn le_set_advertise_enable_works() { 307 | let connector = connector(); 308 | let mut ble = Ble::new(&connector); 309 | 310 | connector.provide_data_to_read(&[0x04, 0x0e, 0x04, 0x05, 0x0a, 0x20, 0x00]); 311 | 312 | let res = ble.cmd_set_le_advertise_enable(false); 313 | 314 | assert_matches!(res, Ok(EventType::CommandComplete{ num_packets: 5, opcode: 0x200a, data}) if data.to_slice() == &[0]); 315 | } 316 | 317 | #[test] 318 | fn receiving_async_data_works() { 319 | let connector = connector(); 320 | let mut ble = Ble::new(&connector); 321 | 322 | connector.provide_data_to_read(&[ 323 | 0x02, 0x00, 0x20, 0x0b, 0x00, 0x07, 0x00, 0x04, 0x00, 0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 324 | 0x28, 325 | ]); 326 | 327 | let res = ble.poll(); 328 | 329 | assert_matches!(res, 330 | Some(PollResult::AsyncData(AclPacket { 331 | handle: 0, 332 | boundary_flag: BoundaryFlag::FirstAutoFlushable, 333 | bc_flag: ControllerBroadcastFlag::PointToPoint, 334 | data, 335 | })) if data.to_slice() == &[0x7, 0x0, 0x4, 0x0, 0x10, 0x1, 0x0, 0xff, 0xff, 0x0, 0x28] 336 | ); 337 | } 338 | 339 | #[test] 340 | fn receiving_disconnection_complete_works() { 341 | let connector = connector(); 342 | let mut ble = Ble::new(&connector); 343 | 344 | connector.provide_data_to_read(&[0x04, 0x05, 0x04, 0x00, 0x00, 0x00, 0x13]); 345 | 346 | let res = ble.poll(); 347 | 348 | assert_matches!( 349 | res, 350 | Some(PollResult::Event(EventType::DisconnectComplete { 351 | handle: 0, 352 | status: ErrorCode::Okay, 353 | reason: ErrorCode::RemoteUserTerminatedConnection 354 | })) 355 | ); 356 | } 357 | 358 | #[test] 359 | fn receiving_number_of_completed_packets_works() { 360 | let connector = connector(); 361 | let mut ble = Ble::new(&connector); 362 | 363 | connector.provide_data_to_read(&[0x04, 0x13, 0x05, 0x01, 0x00, 0x00, 0x01, 0x00]); 364 | 365 | let res = ble.poll(); 366 | 367 | assert_matches!( 368 | res, 369 | Some(PollResult::Event(EventType::NumberOfCompletedPackets { 370 | number_of_connection_handles: 1, 371 | connection_handles: 0, 372 | completed_packets: 1, 373 | })) 374 | ); 375 | } 376 | 377 | #[test] 378 | fn receiving_read_by_group_type_works() { 379 | let connector = connector(); 380 | let mut ble = Ble::new(&connector); 381 | 382 | connector.provide_data_to_read(&[ 383 | 0x02, 0x00, 0x20, 0x0b, 0x00, 0x07, 0x00, 0x04, 0x00, 0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 384 | 0x28, 385 | ]); 386 | 387 | let res = ble.poll(); 388 | match res { 389 | Some(res) => match res { 390 | PollResult::Event(_) => assert!(true, "Expected async data"), 391 | PollResult::AsyncData(res) => { 392 | let res = parse_att(parse_l2cap(res).unwrap()); 393 | assert_matches!( 394 | res, 395 | Ok(Att::ReadByGroupTypeReq { 396 | start: 0x0001, 397 | end: 0xffff, 398 | group_type: Uuid::Uuid16(0x2800), 399 | }) 400 | ) 401 | } 402 | }, 403 | None => assert!(true, "Expected result"), 404 | } 405 | } 406 | 407 | #[test] 408 | fn create_ready_by_group_type_resp_works() { 409 | let attribute_list = [ 410 | AttributeData::new(0x0001, 0x0010, Uuid::Uuid16(0x1801)), 411 | AttributeData::new(0x0020, 0x0030, Uuid::Uuid16(0x1802)), 412 | ]; 413 | let res = att_encode_read_by_group_type_response(&attribute_list); 414 | 415 | assert_matches!( 416 | res.to_slice(), 417 | &[0x11, 0x06, 0x01, 0x00, 0x10, 0x00, 0x01, 0x18, 0x20, 0x00, 0x30, 0x00, 0x02, 0x18,] 418 | ); 419 | } 420 | 421 | #[test] 422 | fn create_ready_by_group_type_resp_acl_works() { 423 | let attribute_list = [ 424 | AttributeData::new(0x0001, 0x0010, Uuid::Uuid16(0x1801)), 425 | AttributeData::new(0x0020, 0x0030, Uuid::Uuid16(0x1802)), 426 | ]; 427 | let res = att_encode_read_by_group_type_response(&attribute_list); 428 | let res = encode_l2cap(res); 429 | let res = encode_acl_packet( 430 | 0x0000, 431 | BoundaryFlag::FirstAutoFlushable, 432 | HostBroadcastFlag::NoBroadcast, 433 | res, 434 | ); 435 | 436 | assert_matches!( 437 | res.to_slice(), 438 | &[ 439 | 0x02, 0x00, 0x20, 0x12, 0x00, 0x0e, 0x00, 0x04, 0x00, 0x11, 0x06, 0x01, 0x00, 0x10, 440 | 0x00, 0x01, 0x18, 0x20, 0x00, 0x30, 0x00, 0x02, 0x18, 441 | ] 442 | ); 443 | } 444 | 445 | #[test] 446 | fn create_error_resp_works() { 447 | let res = att_encode_error_response( 448 | ATT_READ_BY_GROUP_TYPE_REQUEST_OPCODE, 449 | 0x1234, 450 | AttErrorCode::AttributeNotFound, 451 | ); 452 | 453 | assert_matches!(res.to_slice(), &[0x01, 0x10, 0x34, 0x12, 0x0a,]); 454 | } 455 | 456 | #[test] 457 | fn receiving_read_by_type_works() { 458 | let connector = connector(); 459 | let mut ble = Ble::new(&connector); 460 | 461 | connector.provide_data_to_read(&[ 462 | 0x02, 0x00, 0x20, 0x0b, 0x00, 0x07, 0x00, 0x04, 0x00, 0x08, 0x01, 0x00, 0x02, 0x00, 0x02, 463 | 0x28, 464 | ]); 465 | 466 | let res = ble.poll(); 467 | match res { 468 | Some(res) => match res { 469 | PollResult::Event(_) => assert!(true, "Expected async data"), 470 | PollResult::AsyncData(res) => { 471 | let res = parse_att(parse_l2cap(res).unwrap()); 472 | assert_matches!( 473 | res, 474 | Ok(Att::ReadByTypeReq { 475 | start: 0x0001, 476 | end: 0x0002, 477 | attribute_type: Uuid::Uuid16(0x2802), 478 | }) 479 | ) 480 | } 481 | }, 482 | None => assert!(true, "Expected result"), 483 | } 484 | } 485 | 486 | #[test] 487 | fn create_read_by_type_resp_works() { 488 | let attribute_list = [AttributePayloadData::new( 489 | 0x0002, 490 | Data::new(&[1u8, 2u8, 3u8, 4u8]), 491 | )]; 492 | let res = att_encode_read_by_type_response(&attribute_list); 493 | 494 | assert_matches!( 495 | res.to_slice(), 496 | &[0x09, 0x06, 0x02, 0x00, 0x01, 0x02, 0x03, 0x04,] 497 | ); 498 | } 499 | 500 | #[test] 501 | fn receiving_read_works() { 502 | let connector = connector(); 503 | let mut ble = Ble::new(&connector); 504 | 505 | connector.provide_data_to_read(&[ 506 | 0x02, 0x00, 0x20, 0x07, 0x00, 0x03, 0x00, 0x04, 0x00, 0x0a, 0x03, 0x00, 507 | ]); 508 | 509 | let res = ble.poll(); 510 | match res { 511 | Some(res) => match res { 512 | PollResult::Event(_) => assert!(true, "Expected async data"), 513 | PollResult::AsyncData(res) => { 514 | let res = parse_att(parse_l2cap(res).unwrap()); 515 | assert_matches!(res, Ok(Att::ReadReq { handle: 0x03 })) 516 | } 517 | }, 518 | None => assert!(true, "Expected result"), 519 | } 520 | } 521 | 522 | #[test] 523 | fn create_read_resp_works() { 524 | let res = att_encode_read_response(&Data::new(&[0x01, 0x02, 0x03, 0x04])); 525 | 526 | assert_matches!(res.to_slice(), &[0x0b, 0x01, 0x02, 0x03, 0x04,]); 527 | } 528 | 529 | #[test] 530 | fn receiving_write_works() { 531 | let connector = connector(); 532 | let mut ble = Ble::new(&connector); 533 | 534 | connector.provide_data_to_read(&[ 535 | 0x02, 0x00, 0x20, 0x08, 0x00, 0x04, 0x00, 0x04, 0x00, 0x12, 0x03, 0x00, 0x0ff, 536 | ]); 537 | 538 | let res = ble.poll(); 539 | match res { 540 | Some(res) => match res { 541 | PollResult::Event(_) => assert!(true, "Expected async data"), 542 | PollResult::AsyncData(res) => { 543 | let res = parse_att(parse_l2cap(res).unwrap()); 544 | assert_matches!( 545 | res, 546 | Ok(Att::WriteReq { 547 | handle: 0x03, 548 | data 549 | }) if data.to_slice() == &[0xff] 550 | ) 551 | } 552 | }, 553 | None => assert!(true, "Expected result"), 554 | } 555 | } 556 | 557 | #[test] 558 | fn create_write_resp_works() { 559 | let res = att_encode_write_response(); 560 | 561 | assert_matches!(res.to_slice(), &[0x13]); 562 | } 563 | 564 | #[test] 565 | fn create_advertising_data_works() { 566 | let res = create_advertising_data(&[ 567 | AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED), 568 | AdStructure::ServiceUuids16(&[Uuid::Uuid16(0x1809)]), 569 | AdStructure::CompleteLocalName("BL-602 Ble-Example!"), 570 | ]); 571 | 572 | println!("{:x?}", res); 573 | 574 | assert_matches!( 575 | res.to_slice(), 576 | &[ 577 | 0x1c, 0x02, 0x01, 0x06, 0x03, 0x02, 0x09, 0x18, 0x14, 0x09, 0x42, 0x4C, 0x2D, 0x36, 578 | 0x30, 0x32, 0x20, 0x42, 0x6C, 0x65, 0x2D, 0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, 579 | 0x21, 0x00, 0x00, 0x00 580 | ] 581 | ); 582 | } 583 | 584 | #[test] 585 | fn attribute_server_replies_to_group_type_requests() { 586 | let mut written = Vec::::new(); 587 | 588 | let connector = connector(); 589 | let mut ble = Ble::new(&connector); 590 | 591 | let mut rf = || Data::new(&[b'H', b'e', b'l', b'l', b'o']); 592 | let mut wf = |data: Data| { 593 | written.extend_from_slice(data.to_slice()); 594 | }; 595 | 596 | let srv = Service::new( 597 | Uuid::Uuid128([ 598 | 0xC9, 0x15, 0x15, 0x96, 0x54, 0x56, 0x64, 0xB3, 0x38, 0x45, 0x26, 0x5D, 0xF1, 0x62, 599 | 0x6A, 0xA8, 600 | ]), 601 | ATT_READABLE | ATT_WRITEABLE, 602 | &mut rf, 603 | &mut wf, 604 | ); 605 | 606 | let services = &mut [srv]; 607 | let mut srv = AttributeServer::new(&mut ble, services); 608 | 609 | // ReadByGroupTypeReq { start: 1, end: ffff, group_type: Uuid16(2800) } 610 | connector.provide_data_to_read(&[ 611 | 0x02, 0x00, 0x20, 0x0b, 0x00, 0x07, 0x00, 0x04, 0x00, 0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 612 | 0x28, 613 | ]); 614 | assert_matches!(srv.do_work(), Ok(())); 615 | // check response (1-3, 0x2800) 616 | let response_data = connector.get_written_data(); 617 | assert_eq!( 618 | response_data.to_slice(), 619 | &[ 620 | 0x02, 0x00, 0x20, 0x0c, 0x00, 0x08, 0x00, 0x04, 0x00, 0x11, 0x06, 0x01, 0x00, 0x03, 621 | 0x00, 0x00, 0x28 622 | ] 623 | ); 624 | 625 | // ReadByGroupTypeReq { start: 3, end: ffff, group_type: Uuid16(2800) } 626 | connector.reset(); 627 | connector.provide_data_to_read(&[ 628 | 0x02, 0x00, 0x20, 0x0b, 0x00, 0x07, 0x00, 0x04, 0x00, 0x10, 0x03, 0x00, 0xff, 0xff, 0x00, 629 | 0x28, 630 | ]); 631 | assert_matches!(srv.do_work(), Ok(())); 632 | // check response (not found) 633 | let response_data = connector.get_written_data(); 634 | assert_eq!( 635 | response_data.to_slice(), 636 | &[0x02, 0x00, 0x20, 0x09, 0x00, 0x05, 0x00, 0x04, 0x00, 0x01, 0x10, 0x03, 0x00, 0x0a] 637 | ); 638 | 639 | // ReadByTypeReq { start: 1, end: 3, attribute_type: Uuid16(2802) } 640 | connector.reset(); 641 | connector.provide_data_to_read(&[ 642 | 0x02, 0x00, 0x20, 0x0b, 0x00, 0x07, 0x00, 0x04, 0x00, 0x08, 0x01, 0x00, 0x02, 0x00, 0x02, 643 | 0x28, 644 | ]); 645 | assert_matches!(srv.do_work(), Ok(())); 646 | // check response (not found) 647 | let response_data = connector.get_written_data(); 648 | assert_eq!( 649 | response_data.to_slice(), 650 | &[0x02, 0x00, 0x20, 0x09, 0x00, 0x05, 0x00, 0x04, 0x00, 0x01, 0x08, 0x01, 0x00, 0x0a] 651 | ); 652 | 653 | // ReadByTypeReq { start: 1, end: 3, attribute_type: Uuid16(2803) } 654 | connector.reset(); 655 | connector.provide_data_to_read(&[ 656 | 0x02, 0x00, 0x20, 0x0b, 0x00, 0x07, 0x00, 0x04, 0x00, 0x08, 0x01, 0x00, 0x03, 0x00, 0x03, 657 | 0x28, 658 | ]); 659 | assert_matches!(srv.do_work(), Ok(())); 660 | // check response (not found) 661 | let response_data = connector.get_written_data(); 662 | assert_eq!( 663 | response_data.to_slice(), 664 | &[ 665 | 0x02, 0x00, 0x20, 0x1b, 0x00, 0x17, 0x0, 0x4, 0x0, 0x9, 0x15, 0x2, 0x0, 0xa, 0x3, 0x0, 666 | 0xa8, 0x6a, 0x62, 0xf1, 0x5d, 0x26, 0x45, 0x38, 0xb3, 0x64, 0x56, 0x54, 0x96, 0x15, 667 | 0x15, 0xc9, 668 | ] 669 | ); 670 | 671 | // ReadReq { handle: 3 } 672 | connector.reset(); 673 | connector.provide_data_to_read(&[ 674 | 0x02, 0x00, 0x20, 0x07, 0x00, 0x03, 0x00, 0x04, 0x00, 0x0a, 0x03, 0x00, 675 | ]); 676 | assert_matches!(srv.do_work(), Ok(())); 677 | // check response (read resp 'Hello') 678 | let response_data = connector.get_written_data(); 679 | assert_eq!( 680 | response_data.to_slice(), 681 | &[ 682 | 0x02, 0x00, 0x20, 0x0a, 0x00, 0x06, 0x00, 0x04, 0x00, 0x0b, 0x48, 0x65, 0x6c, 0x6c, 683 | 0x6f, 684 | ] 685 | ); 686 | 687 | // WriteReq { handle: 3, data: [0xab] } 688 | connector.reset(); 689 | connector.provide_data_to_read(&[ 690 | 0x02, 0x00, 0x20, 0x08, 0x00, 0x04, 0x00, 0x04, 0x00, 0x12, 0x03, 0x00, 0xab, 691 | ]); 692 | assert_matches!(srv.do_work(), Ok(())); 693 | // check response (write resp) 694 | let response_data = connector.get_written_data(); 695 | assert_eq!( 696 | response_data.to_slice(), 697 | &[0x02, 0x00, 0x20, 0x05, 0x00, 0x01, 0x00, 0x04, 0x00, 0x13] 698 | ); 699 | 700 | assert_eq!(&written[..], &[0xab_u8]); 701 | } 702 | 703 | #[test] 704 | fn attribute_server_discover_two_services() { 705 | let connector = connector(); 706 | let mut ble = Ble::new(&connector); 707 | 708 | let mut rf1 = || Data::default(); 709 | let mut wf1 = |_data: Data| {}; 710 | 711 | let srv1 = Service::new( 712 | Uuid::Uuid128([ 713 | 0xC9, 0x15, 0x15, 0x96, 0x54, 0x56, 0x64, 0xB3, 0x38, 0x45, 0x26, 0x5D, 0xF1, 0x62, 714 | 0x6A, 0xA8, 715 | ]), 716 | ATT_READABLE | ATT_WRITEABLE, 717 | &mut rf1, 718 | &mut wf1, 719 | ); 720 | 721 | let mut rf2 = || Data::default(); 722 | let mut wf2 = |_data: Data| {}; 723 | 724 | let srv2 = Service::new( 725 | Uuid::Uuid128([ 726 | 0xC8, 0x15, 0x15, 0x96, 0x54, 0x56, 0x64, 0xB3, 0x38, 0x45, 0x26, 0x5D, 0xF1, 0x62, 727 | 0x6A, 0xA8, 728 | ]), 729 | ATT_READABLE | ATT_WRITEABLE, 730 | &mut rf2, 731 | &mut wf2, 732 | ); 733 | 734 | let services = &mut [srv1, srv2]; 735 | let mut srv = AttributeServer::new(&mut ble, services); 736 | 737 | // ReadByGroupTypeReq { start: 1, end: ffff, group_type: Uuid16(2800) } 738 | connector.provide_data_to_read(&[ 739 | 0x02, 0x00, 0x20, 0x0b, 0x00, 0x07, 0x00, 0x04, 0x00, 0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 740 | 0x28, 741 | ]); 742 | assert_matches!(srv.do_work(), Ok(())); 743 | // check response (1-3, 0x2800) 744 | let response_data = connector.get_written_data(); 745 | assert_eq!( 746 | response_data.to_slice(), 747 | &[ 748 | 0x02, 0x00, 0x20, 0x0c, 0x00, 0x08, 0x00, 0x04, 0x00, 0x11, 0x06, 0x01, 0x00, 0x03, 749 | 0x00, 0x00, 0x28 750 | ] 751 | ); 752 | 753 | // ReadByGroupTypeReq { start: 4, end: ffff, group_type: Uuid16(2800) } 754 | connector.reset(); 755 | connector.provide_data_to_read(&[ 756 | 0x02, 0x00, 0x20, 0x0b, 0x00, 0x07, 0x00, 0x04, 0x00, 0x10, 0x04, 0x00, 0xff, 0xff, 0x00, 757 | 0x28, 758 | ]); 759 | assert_matches!(srv.do_work(), Ok(())); 760 | // check response (4-6, 0x2800) 761 | let response_data = connector.get_written_data(); 762 | assert_eq!( 763 | response_data.to_slice(), 764 | &[ 765 | 0x02, 0x00, 0x20, 0x0c, 0x00, 0x08, 0x00, 0x04, 0x00, 0x11, 0x06, 0x04, 0x00, 0x06, 766 | 0x00, 0x00, 0x28 767 | ] 768 | ); 769 | 770 | // ReadByGroupTypeReq { start: 7, end: ffff, group_type: Uuid16(2800) } 771 | connector.reset(); 772 | connector.provide_data_to_read(&[ 773 | 0x02, 0x00, 0x20, 0x0b, 0x00, 0x07, 0x00, 0x04, 0x00, 0x10, 0x07, 0x00, 0xff, 0xff, 0x00, 774 | 0x28, 775 | ]); 776 | assert_matches!(srv.do_work(), Ok(())); 777 | // check response (not found) 778 | let response_data = connector.get_written_data(); 779 | assert_eq!( 780 | response_data.to_slice(), 781 | &[0x02, 0x00, 0x20, 0x09, 0x00, 0x05, 0x00, 0x04, 0x00, 0x01, 0x10, 0x07, 0x00, 0x0a] 782 | ); 783 | } 784 | --------------------------------------------------------------------------------