diff --git a/host/src/att.rs b/host/src/att.rs index dbf5d4d1..cc6bc6ed 100644 --- a/host/src/att.rs +++ b/host/src/att.rs @@ -302,6 +302,11 @@ pub enum AttRsp<'d> { /// Iterator over the found handles it: FindByTypeValueIter<'d>, }, + /// Find Information Response + FindInformation { + /// Iterator over the found handles and UUIDs + it: FindInformationIter<'d>, + }, /// Error Response Error { /// Request opcode @@ -408,6 +413,58 @@ impl<'d> ReadByTypeIter<'d> { } } +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug, Copy, Clone)] +enum FindInformationUuidFormat { + Uuid16 = 1, + Uuid128 = 2, +} + +impl FindInformationUuidFormat { + fn num_bytes(self) -> usize { + match self { + Self::Uuid16 => 2, + Self::Uuid128 => 16, + } + } + + fn from(format: u8) -> Result { + match format { + 1 => Ok(Self::Uuid16), + 2 => Ok(Self::Uuid128), + _ => Err(codec::Error::InvalidValue), + } + } +} + +/// An Iterator-like type for iterating over the handle/UUID pairs in a Find Information Response +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Clone, Debug)] +pub struct FindInformationIter<'d> { + /// Format type: 1 = 16-bit UUIDs, 2 = 128-bit UUIDs + format: FindInformationUuidFormat, + cursor: ReadCursor<'d>, +} + +impl<'d> FindInformationIter<'d> { + /// Get the next pair of attribute handle and UUID + #[allow(clippy::should_implement_trait)] + pub fn next(&mut self) -> Option> { + let uuid_len = self.format.num_bytes(); + + if self.cursor.available() >= 2 + uuid_len { + let res = (|| { + let handle: u16 = self.cursor.read()?; + let uuid = Uuid::try_from(self.cursor.slice(uuid_len)?)?; + Ok((handle, uuid)) + })(); + Some(res) + } else { + None + } + } +} + impl<'d> AttServer<'d> { fn size(&self) -> usize { match self { @@ -437,6 +494,7 @@ impl<'d> AttRsp<'d> { 1 + match self { Self::ExchangeMtu { mtu: u16 } => 2, Self::FindByTypeValue { it } => it.cursor.len(), + Self::FindInformation { it } => 1 + it.cursor.len(), // 1 for format byte Self::Error { .. } => 4, Self::Read { data } => data.len(), Self::ReadByType { it } => it.cursor.len(), @@ -459,6 +517,15 @@ impl<'d> AttRsp<'d> { w.write(end)?; } } + Self::FindInformation { it } => { + w.write(ATT_FIND_INFORMATION_RSP)?; + w.write(it.format as u8)?; + let mut it = it.clone(); + while let Some(Ok((handle, uuid))) = it.next() { + w.write(handle)?; + w.append(uuid.as_raw())?; + } + } Self::Error { request, handle, code } => { w.write(ATT_ERROR_RSP)?; w.write(*request)?; @@ -490,6 +557,12 @@ impl<'d> AttRsp<'d> { ATT_FIND_BY_TYPE_VALUE_RSP => Ok(Self::FindByTypeValue { it: FindByTypeValueIter { cursor: r }, }), + ATT_FIND_INFORMATION_RSP => Ok(Self::FindInformation { + it: FindInformationIter { + format: FindInformationUuidFormat::from(r.read()?)?, + cursor: r, + }, + }), ATT_EXCHANGE_MTU_RSP => { let mtu: u16 = r.read()?; Ok(Self::ExchangeMtu { mtu }) @@ -599,6 +672,10 @@ impl<'d> AttReq<'d> { att_type, att_value, } => 6 + att_value.len(), + Self::FindInformation { + start_handle, + end_handle, + } => 4, Self::ReadByType { start, end, @@ -628,6 +705,14 @@ impl<'d> AttReq<'d> { w.write(*att_type)?; w.append(att_value)?; } + Self::FindInformation { + start_handle, + end_handle, + } => { + w.write(ATT_FIND_INFORMATION_REQ)?; + w.write(*start_handle)?; + w.write(*end_handle)?; + } Self::ReadByType { start, end, diff --git a/host/src/gatt.rs b/host/src/gatt.rs index 75a23aed..dfb6d6ba 100644 --- a/host/src/gatt.rs +++ b/host/src/gatt.rs @@ -15,7 +15,7 @@ use embassy_time::Duration; use heapless::Vec; use crate::att::{self, Att, AttClient, AttCmd, AttReq, AttRsp, AttServer, AttUns, ATT_HANDLE_VALUE_NTF}; -use crate::attribute::{AttributeData, Characteristic, CharacteristicProp, Uuid, CCCD}; +use crate::attribute::{AttributeData, Characteristic, CharacteristicProp, Uuid}; use crate::attribute_server::{AttributeServer, DynamicAttributeServer}; use crate::connection::Connection; use crate::connection_manager::ConnectionManager; @@ -678,6 +678,8 @@ impl<'reference, C: Controller, const MAX_SERVICES: usize, const L2CAP_MTU: usiz uuid: &Uuid, ) -> Result, BleHostError> { let mut start: u16 = service.start; + let mut found_indicate_or_notify_uuid = Option::None; + loop { let data = att::AttReq::ReadByType { start, @@ -698,22 +700,27 @@ impl<'reference, C: Controller, const MAX_SERVICES: usize, const L2CAP_MTU: usiz uuid: decl_uuid, } = AttributeData::decode_declaration(item)? { - if *uuid == decl_uuid { - // "notify" and "indicate" characteristic properties - let cccd_handle = - if props.any(&[CharacteristicProp::Indicate, CharacteristicProp::Notify]) { - Some(self.get_characteristic_cccd(handle).await?.0) - } else { - None - }; - + if let Some(start_handle) = found_indicate_or_notify_uuid { return Ok(Characteristic { handle, - cccd_handle, + cccd_handle: Some(self.get_characteristic_cccd(start_handle, handle).await?), phantom: PhantomData, }); } + if *uuid == decl_uuid { + // If there are "notify" and "indicate" characteristic properties we need to find the + // next characteristic so we can determine the search space for the CCCD + if !props.any(&[CharacteristicProp::Indicate, CharacteristicProp::Notify]) { + return Ok(Characteristic { + handle, + cccd_handle: None, + phantom: PhantomData, + }); + } + found_indicate_or_notify_uuid = Some(handle); + } + if handle == 0xFFFF { return Err(Error::NotFound.into()); } @@ -723,44 +730,53 @@ impl<'reference, C: Controller, const MAX_SERVICES: usize, const L2CAP_MTU: usiz } } } - AttRsp::Error { request, handle, code } => return Err(Error::Att(code).into()), - _ => { - return Err(Error::InvalidValue.into()); - } + AttRsp::Error { request, handle, code } => match code { + att::AttErrorCode::ATTRIBUTE_NOT_FOUND => match found_indicate_or_notify_uuid { + Some(handle) => { + return Ok(Characteristic { + handle, + cccd_handle: Some(self.get_characteristic_cccd(handle, service.end).await?), + phantom: PhantomData, + }) + } + None => return Err(Error::NotFound.into()), + }, + _ => return Err(Error::Att(code).into()), + }, + _ => return Err(Error::InvalidValue.into()), } } } - async fn get_characteristic_cccd(&self, char_handle: u16) -> Result<(u16, CCCD), BleHostError> { - let data = att::AttReq::ReadByType { - start: char_handle, - end: char_handle + 1, - attribute_type: CLIENT_CHARACTERISTIC_CONFIGURATION.into(), - }; + async fn get_characteristic_cccd( + &self, + char_start_handle: u16, + char_end_handle: u16, + ) -> Result> { + let mut start_handle = char_start_handle; + + while start_handle <= char_end_handle { + let data = att::AttReq::FindInformation { + start_handle, + end_handle: char_end_handle, + }; - let response = self.request(data).await?; + let response = self.request(data).await?; - match Self::response(response.pdu.as_ref())? { - AttRsp::ReadByType { mut it } => { - if let Some(Ok((handle, item))) = it.next() { - // As defined in the bluetooth spec [3.3.3.3. Client Characteristic Configuration](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host/generic-attribute-profile--gatt-.html#UUID-09487be3-178b-eeca-f49f-f783e8d462f6) - // "The Client Characteristic Configuration declaration is an optional characteristic descriptor" and - // "The default value for the Client Characteristic Configuration descriptor value shall be 0x0000." - if item.is_empty() { - Ok((handle, CCCD(0))) - } else { - Ok(( - handle, - CCCD(u16::from_le_bytes(item.try_into().map_err(|_| Error::InvalidValue)?)), - )) + match Self::response(response.pdu.as_ref())? { + AttRsp::FindInformation { mut it } => { + while let Some(Ok((handle, uuid))) = it.next() { + if uuid == CLIENT_CHARACTERISTIC_CONFIGURATION.into() { + return Ok(handle); + } + start_handle = handle + 1; } - } else { - Err(Error::NotFound.into()) } + AttRsp::Error { request, handle, code } => return Err(Error::Att(code).into()), + _ => return Err(Error::InvalidValue.into()), } - AttRsp::Error { request, handle, code } => Err(Error::Att(code).into()), - _ => Err(Error::InvalidValue.into()), } + Err(Error::NotFound.into()) } /// Read a characteristic described by a handle.