Skip to content

Fix: Get cccd with FIND_INFORMATION instead of READ_BY_TYPE #350

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions host/src/att.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<Self, codec::Error> {
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<Result<(u16, Uuid), crate::Error>> {
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 {
Expand Down Expand Up @@ -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(),
Expand All @@ -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)?;
Expand Down Expand Up @@ -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 })
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
94 changes: 55 additions & 39 deletions host/src/gatt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -678,6 +678,8 @@ impl<'reference, C: Controller, const MAX_SERVICES: usize, const L2CAP_MTU: usiz
uuid: &Uuid,
) -> Result<Characteristic<T>, BleHostError<C::Error>> {
let mut start: u16 = service.start;
let mut found_indicate_or_notify_uuid = Option::None;

loop {
let data = att::AttReq::ReadByType {
start,
Expand All @@ -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());
}
Expand All @@ -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<C::Error>> {
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<u16, BleHostError<C::Error>> {
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.
Expand Down