From e210bfc61b165e2e7f614e6d0a35133fd00bfc87 Mon Sep 17 00:00:00 2001 From: nm17 Date: Sat, 31 Aug 2024 20:41:21 +0400 Subject: [PATCH 1/7] Document attribute module --- host/src/attribute.rs | 109 +++++++++++++++++++++++++++++++++++------- 1 file changed, 92 insertions(+), 17 deletions(-) diff --git a/host/src/attribute.rs b/host/src/attribute.rs index 33f95863..58bc8e9b 100644 --- a/host/src/attribute.rs +++ b/host/src/attribute.rs @@ -24,45 +24,85 @@ pub const GENERIC_ATTRIBUTE_UUID16: Uuid = Uuid::Uuid16(0x1801u16.to_le_bytes()) #[derive(Debug, Clone, Copy)] #[repr(u8)] +/// An enum of possible characteristic properties +/// +/// Ref: BLUETOOTH CORE SPECIFICATION Version 6.0, Vol 3, Part G, Section 3.3.1.1 Characteristic Properties pub enum CharacteristicProp { + /// Permit broadcast of the Characteristic Value + /// + /// If set, permits broadcasts of the Characteristic Value using Server Characteristic + /// Configuration Descriptor. Broadcast = 0x01, + /// Permit read of the Characteristic Value Read = 0x02, + /// Permit writes to the Characteristic Value without response WriteWithoutResponse = 0x04, + /// Permit writes to the Characteristic Value Write = 0x08, + /// Permit notification of a Characteristic Value without acknowledgment Notify = 0x10, + /// Permit indication of a Characteristic Value with acknowledgment Indicate = 0x20, + /// Permit signed writes to the Characteristic Value AuthenticatedWrite = 0x40, + /// Permit writes to the Characteristic Value without response Extended = 0x80, } pub struct Attribute<'a> { + /// Attribute type UUID + /// + /// Do not mistake it with Characteristic UUID pub uuid: Uuid, + /// Handle for the Attribute + /// + /// In case of a push, this value is ignored and set to the + /// next available handle value in the attribute table. pub handle: u16, - pub last_handle_in_group: u16, - pub data: AttributeData<'a>, + /// Last handle value in the group + /// + /// When a [`ServiceBuilder`] finishes building, it returns the handle for the service, but also + pub(crate) last_handle_in_group: u16, + pub data: AttributeData<'d>, } -impl<'a> Attribute<'a> { - const EMPTY: Option> = None; +impl<'d> Attribute<'d> { + const EMPTY: Option> = None; } +/// The underlying data behind an attribute. pub enum AttributeData<'d> { + /// Service UUID Data + /// + /// Serializes to raw bytes of UUID. Service { uuid: Uuid, }, + /// Read only Data + /// + /// Implemented by storing a borrow of a slice. + /// The slice has to live at least as much as the device. ReadOnlyData { props: CharacteristicProps, value: &'d [u8], }, + /// Read and write data + /// + /// Implemented by storing a mutable borrow of a slice. + /// The slice has to live at least as much as the device. Data { props: CharacteristicProps, value: &'d mut [u8], }, + /// Characteristic declaration Declaration { props: CharacteristicProps, handle: u16, uuid: Uuid, }, + /// Client Characteristic Configuration Descriptor + /// + /// Ref: BLUETOOTH CORE SPECIFICATION Version 6.0, Vol 3, Part G, Section 3.3.3.3 Client Characteristic Configuration Cccd { notifications: bool, indications: bool, @@ -94,6 +134,14 @@ impl<'d> AttributeData<'d> { } } + /// Read the attribute value from some kind of a readable attribute data source + /// + /// Seek to to the `offset`-nth byte in the source data, fill the response data slice `data` up to the end or lower. + /// + /// The data buffer is always sized L2CAP_MTU, minus the 4 bytes for the L2CAP header) + /// The max stated value of an attribute in the GATT specification is 512 bytes. + /// + /// Returns the amount of bytes that have been written into `data`. pub fn read(&self, offset: usize, data: &mut [u8]) -> Result { if !self.readable() { return Err(AttErrorCode::ReadNotPermitted); @@ -176,6 +224,9 @@ impl<'d> AttributeData<'d> { } } + /// Write into the attribute value at 'offset' data from `data` buffer + /// + /// Expect the writes to be fragmented, like with [`AttributeData::read`] pub fn write(&mut self, offset: usize, data: &[u8]) -> Result<(), AttErrorCode> { let writable = self.writable(); @@ -204,8 +255,8 @@ impl<'d> AttributeData<'d> { return Err(AttErrorCode::UnlikelyError); } - *notifications = data[0] & 0x01 != 0; - *indications = data[0] & 0x02 != 0; + *notifications = data[0] & 0x00000001 != 0; + *indications = data[0] & 0x00000010 != 0; Ok(()) } _ => Err(AttErrorCode::WriteNotPermitted), @@ -238,18 +289,27 @@ impl<'a> Attribute<'a> { uuid, handle: 0, data, - last_handle_in_group: 0xffff, + last_handle_in_group: u16::MAX, } } } +/// Table of Attributes available to the [`crate::gatt::GattServer`]. pub struct AttributeTable<'d, M: RawMutex, const MAX: usize> { inner: Mutex>>, - handle: u16, + + /// Next available attribute handle value known by this table + next_handle: u16, } +/// Inner representation of [`AttributeTable`] +/// +/// Represented by a stack allocated list of attributes with a len field to keep track of how many are actually present. +// TODO: Switch to heapless Vec pub struct InnerTable<'d, const MAX: usize> { attributes: [Option>; MAX], + + /// Amount of attributes in the list. len: usize, } @@ -270,15 +330,17 @@ impl<'d, M: RawMutex, const MAX: usize> Default for AttributeTable<'d, M, MAX> { } impl<'d, M: RawMutex, const MAX: usize> AttributeTable<'d, M, MAX> { + /// Create an empty table pub fn new() -> Self { Self { - handle: 1, + next_handle: 1, inner: Mutex::new(RefCell::new(InnerTable { len: 0, attributes: [Attribute::EMPTY; MAX], })), } } + pub fn with_inner)>(&self, f: F) { self.inner.lock(|inner| { @@ -300,20 +362,26 @@ impl<'d, M: RawMutex, const MAX: usize> AttributeTable<'d, M, MAX> { }) } + /// Push into the table a given attribute. + /// + /// Returns the attribute handle. fn push(&mut self, mut attribute: Attribute<'d>) -> u16 { - let handle = self.handle; + let handle = self.next_handle; attribute.handle = handle; self.inner.lock(|inner| { let mut inner = inner.borrow_mut(); inner.push(attribute); }); - self.handle += 1; + self.next_handle += 1; handle } + /// Create a service with a given UUID and return the [`ServiceBuilder`]. + /// + /// Note: The service builder is tied to the AttributeTable. pub fn add_service(&mut self, service: Service) -> ServiceBuilder<'_, 'd, M, MAX> { let len = self.inner.lock(|i| i.borrow().len); - let handle = self.handle; + let handle = self.next_handle; self.push(Attribute { uuid: PRIMARY_SERVICE_UUID16, handle: 0, @@ -348,7 +416,7 @@ impl<'d, M: RawMutex, const MAX: usize> AttributeTable<'d, M, MAX> { }) } - /// Read the value of the characteristic and pass the value to the provided closure. + /// Read the value of the characteristic and pass the value to the provided closure /// /// The return value of the closure is returned in this function and is assumed to be infallible. /// @@ -408,6 +476,7 @@ impl From for AttributeHandle { } } +/// Builder type for creating a Service inside a given AttributeTable pub struct ServiceBuilder<'r, 'd, M: RawMutex, const MAX: usize> { handle: AttributeHandle, start: usize, @@ -422,8 +491,8 @@ impl<'r, 'd, M: RawMutex, const MAX: usize> ServiceBuilder<'r, 'd, M, MAX> { data: AttributeData<'d>, ) -> Characteristic { // First the characteristic declaration - let next = self.table.handle + 1; - let cccd = self.table.handle + 2; + let next = self.table.next_handle + 1; + let cccd = self.table.next_handle + 2; self.table.push(Attribute { uuid: CHARACTERISTIC_UUID16, handle: 0, @@ -487,7 +556,7 @@ impl<'r, 'd, M: RawMutex, const MAX: usize> ServiceBuilder<'r, 'd, M, MAX> { impl<'r, 'd, M: RawMutex, const MAX: usize> Drop for ServiceBuilder<'r, 'd, M, MAX> { fn drop(&mut self) { - let last_handle = self.table.handle + 1; + let last_handle = self.table.next_handle + 1; self.table.with_inner(|inner| { for item in inner.attributes[self.start..inner.len].iter_mut() { item.as_mut().unwrap().last_handle_in_group = last_handle; @@ -495,7 +564,7 @@ impl<'r, 'd, M: RawMutex, const MAX: usize> Drop for ServiceBuilder<'r, 'd, M, M }); // Jump to next 16-aligned - self.table.handle = self.table.handle + (0x10 - (self.table.handle % 0x10)); + self.table.next_handle = self.table.next_handle + (0x10 - (self.table.next_handle % 0x10)); } } @@ -530,6 +599,9 @@ impl<'a, 'd> AttributeIterator<'a, 'd> { } } +/// Service information. +/// +/// Currently only has UUID. pub struct Service { pub uuid: Uuid, } @@ -540,6 +612,9 @@ impl Service { } } +/// A bitfield of [`CharacteristicProp`]. +/// +/// See the [`From`] implementation for this struct. Props are applied in order they are given. #[derive(Clone, Copy)] pub struct CharacteristicProps(u8); From 5b7a205db1f2af3f461048731f406fbde834a467 Mon Sep 17 00:00:00 2001 From: nm17 Date: Sat, 31 Aug 2024 20:43:40 +0400 Subject: [PATCH 2/7] Add more derives in attribute and uuid modules --- host/src/attribute.rs | 16 +++++++++++----- host/src/types/uuid.rs | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/host/src/attribute.rs b/host/src/attribute.rs index 58bc8e9b..d2d228e5 100644 --- a/host/src/attribute.rs +++ b/host/src/attribute.rs @@ -22,7 +22,7 @@ pub const CHARACTERISTIC_UUID16: Uuid = Uuid::Uuid16(0x2803u16.to_le_bytes()); pub const CHARACTERISTIC_CCCD_UUID16: Uuid = Uuid::Uuid16(0x2902u16.to_le_bytes()); pub const GENERIC_ATTRIBUTE_UUID16: Uuid = Uuid::Uuid16(0x1801u16.to_le_bytes()); -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] /// An enum of possible characteristic properties /// @@ -49,7 +49,8 @@ pub enum CharacteristicProp { Extended = 0x80, } -pub struct Attribute<'a> { +#[derive(PartialEq, Eq)] +pub struct Attribute<'d> { /// Attribute type UUID /// /// Do not mistake it with Characteristic UUID @@ -71,6 +72,8 @@ impl<'d> Attribute<'d> { } /// The underlying data behind an attribute. +#[derive(Debug, PartialEq, Eq)] +#[non_exhaustive] pub enum AttributeData<'d> { /// Service UUID Data /// @@ -78,7 +81,7 @@ pub enum AttributeData<'d> { Service { uuid: Uuid, }, - /// Read only Data + /// Read only data /// /// Implemented by storing a borrow of a slice. /// The slice has to live at least as much as the device. @@ -306,6 +309,7 @@ pub struct AttributeTable<'d, M: RawMutex, const MAX: usize> { /// /// Represented by a stack allocated list of attributes with a len field to keep track of how many are actually present. // TODO: Switch to heapless Vec +#[derive(Debug, PartialEq, Eq)] pub struct InnerTable<'d, const MAX: usize> { attributes: [Option>; MAX], @@ -465,7 +469,7 @@ impl<'d, M: RawMutex, const MAX: usize> AttributeTable<'d, M, MAX> { } #[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct AttributeHandle { pub(crate) handle: u16, } @@ -602,6 +606,7 @@ impl<'a, 'd> AttributeIterator<'a, 'd> { /// Service information. /// /// Currently only has UUID. +#[derive(Clone, Debug)] pub struct Service { pub uuid: Uuid, } @@ -615,7 +620,8 @@ impl Service { /// A bitfield of [`CharacteristicProp`]. /// /// See the [`From`] implementation for this struct. Props are applied in order they are given. -#[derive(Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] pub struct CharacteristicProps(u8); impl<'a> From<&'a [CharacteristicProp]> for CharacteristicProps { diff --git a/host/src/types/uuid.rs b/host/src/types/uuid.rs index 26f87d65..58364649 100644 --- a/host/src/types/uuid.rs +++ b/host/src/types/uuid.rs @@ -1,7 +1,7 @@ use crate::codec::{Decode, Encode, Error, Type}; #[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub enum Uuid { Uuid16([u8; 2]), Uuid128([u8; 16]), From aa9c1697dec98c0613dec9a86dd608370c17fe89 Mon Sep 17 00:00:00 2001 From: nm17 Date: Sat, 31 Aug 2024 20:44:07 +0400 Subject: [PATCH 3/7] Add .gitignore --- .gitignore | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..3a77e427 --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +# Created by https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,rust-analyzer +# Edit at https://www.toptal.com/developers/gitignore?templates=rust,visualstudiocode,rust-analyzer + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +### rust-analyzer ### +# Can be generated by other build systems other than cargo (ex: bazelbuild/rust_rules) +rust-project.json + + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# End of https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,rust-analyzer + From 60152cc5e2f5b7f743f485fa4aa7886ac39b83a7 Mon Sep 17 00:00:00 2001 From: nm17 Date: Sun, 1 Sep 2024 14:05:53 +0400 Subject: [PATCH 4/7] Fix value typo in code --- host/src/attribute.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/host/src/attribute.rs b/host/src/attribute.rs index d2d228e5..728695b8 100644 --- a/host/src/attribute.rs +++ b/host/src/attribute.rs @@ -258,8 +258,8 @@ impl<'d> AttributeData<'d> { return Err(AttErrorCode::UnlikelyError); } - *notifications = data[0] & 0x00000001 != 0; - *indications = data[0] & 0x00000010 != 0; + *notifications = data[0] & 0b00000001 != 0; + *indications = data[0] & 0b00000010 != 0; Ok(()) } _ => Err(AttErrorCode::WriteNotPermitted), From af16bf48a2f4dd810de24e2c03b3d0782461ae44 Mon Sep 17 00:00:00 2001 From: nm17 Date: Sun, 1 Sep 2024 14:21:47 +0400 Subject: [PATCH 5/7] cargo fmt --- host/src/attribute.rs | 50 +++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/host/src/attribute.rs b/host/src/attribute.rs index 728695b8..760b0059 100644 --- a/host/src/attribute.rs +++ b/host/src/attribute.rs @@ -25,7 +25,7 @@ pub const GENERIC_ATTRIBUTE_UUID16: Uuid = Uuid::Uuid16(0x1801u16.to_le_bytes()) #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] /// An enum of possible characteristic properties -/// +/// /// Ref: BLUETOOTH CORE SPECIFICATION Version 6.0, Vol 3, Part G, Section 3.3.1.1 Characteristic Properties pub enum CharacteristicProp { /// Permit broadcast of the Characteristic Value @@ -52,16 +52,16 @@ pub enum CharacteristicProp { #[derive(PartialEq, Eq)] pub struct Attribute<'d> { /// Attribute type UUID - /// + /// /// Do not mistake it with Characteristic UUID pub uuid: Uuid, /// Handle for the Attribute /// /// In case of a push, this value is ignored and set to the - /// next available handle value in the attribute table. + /// next available handle value in the attribute table. pub handle: u16, /// Last handle value in the group - /// + /// /// When a [`ServiceBuilder`] finishes building, it returns the handle for the service, but also pub(crate) last_handle_in_group: u16, pub data: AttributeData<'d>, @@ -76,13 +76,11 @@ impl<'d> Attribute<'d> { #[non_exhaustive] pub enum AttributeData<'d> { /// Service UUID Data - /// - /// Serializes to raw bytes of UUID. - Service { - uuid: Uuid, - }, + /// + /// Serializes to raw bytes of UUID. + Service { uuid: Uuid }, /// Read only data - /// + /// /// Implemented by storing a borrow of a slice. /// The slice has to live at least as much as the device. ReadOnlyData { @@ -90,7 +88,7 @@ pub enum AttributeData<'d> { value: &'d [u8], }, /// Read and write data - /// + /// /// Implemented by storing a mutable borrow of a slice. /// The slice has to live at least as much as the device. Data { @@ -104,12 +102,9 @@ pub enum AttributeData<'d> { uuid: Uuid, }, /// Client Characteristic Configuration Descriptor - /// + /// /// Ref: BLUETOOTH CORE SPECIFICATION Version 6.0, Vol 3, Part G, Section 3.3.3.3 Client Characteristic Configuration - Cccd { - notifications: bool, - indications: bool, - }, + Cccd { notifications: bool, indications: bool }, } impl<'d> AttributeData<'d> { @@ -138,12 +133,12 @@ impl<'d> AttributeData<'d> { } /// Read the attribute value from some kind of a readable attribute data source - /// + /// /// Seek to to the `offset`-nth byte in the source data, fill the response data slice `data` up to the end or lower. - /// + /// /// The data buffer is always sized L2CAP_MTU, minus the 4 bytes for the L2CAP header) - /// The max stated value of an attribute in the GATT specification is 512 bytes. - /// + /// The max stated value of an attribute in the GATT specification is 512 bytes. + /// /// Returns the amount of bytes that have been written into `data`. pub fn read(&self, offset: usize, data: &mut [u8]) -> Result { if !self.readable() { @@ -228,7 +223,7 @@ impl<'d> AttributeData<'d> { } /// Write into the attribute value at 'offset' data from `data` buffer - /// + /// /// Expect the writes to be fragmented, like with [`AttributeData::read`] pub fn write(&mut self, offset: usize, data: &[u8]) -> Result<(), AttErrorCode> { let writable = self.writable(); @@ -306,8 +301,8 @@ pub struct AttributeTable<'d, M: RawMutex, const MAX: usize> { } /// Inner representation of [`AttributeTable`] -/// -/// Represented by a stack allocated list of attributes with a len field to keep track of how many are actually present. +/// +/// Represented by a stack allocated list of attributes with a len field to keep track of how many are actually present. // TODO: Switch to heapless Vec #[derive(Debug, PartialEq, Eq)] pub struct InnerTable<'d, const MAX: usize> { @@ -344,7 +339,6 @@ impl<'d, M: RawMutex, const MAX: usize> AttributeTable<'d, M, MAX> { })), } } - pub fn with_inner)>(&self, f: F) { self.inner.lock(|inner| { @@ -367,7 +361,7 @@ impl<'d, M: RawMutex, const MAX: usize> AttributeTable<'d, M, MAX> { } /// Push into the table a given attribute. - /// + /// /// Returns the attribute handle. fn push(&mut self, mut attribute: Attribute<'d>) -> u16 { let handle = self.next_handle; @@ -381,7 +375,7 @@ impl<'d, M: RawMutex, const MAX: usize> AttributeTable<'d, M, MAX> { } /// Create a service with a given UUID and return the [`ServiceBuilder`]. - /// + /// /// Note: The service builder is tied to the AttributeTable. pub fn add_service(&mut self, service: Service) -> ServiceBuilder<'_, 'd, M, MAX> { let len = self.inner.lock(|i| i.borrow().len); @@ -604,7 +598,7 @@ impl<'a, 'd> AttributeIterator<'a, 'd> { } /// Service information. -/// +/// /// Currently only has UUID. #[derive(Clone, Debug)] pub struct Service { @@ -618,7 +612,7 @@ impl Service { } /// A bitfield of [`CharacteristicProp`]. -/// +/// /// See the [`From`] implementation for this struct. Props are applied in order they are given. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(transparent)] From 8dd0af78a8d3d0135d086531c96d86b606c7cd8d Mon Sep 17 00:00:00 2001 From: nm17 Date: Sun, 1 Sep 2024 15:02:51 +0400 Subject: [PATCH 6/7] Refactor everything related to attributes --- host/src/attribute/consts.rs | 14 ++ host/src/attribute/data.rs | 196 ++++++++++++++++ host/src/{attribute.rs => attribute/mod.rs} | 212 +----------------- .../server.rs} | 0 host/src/gatt.rs | 2 +- host/src/host.rs | 2 +- host/src/lib.rs | 2 - 7 files changed, 218 insertions(+), 210 deletions(-) create mode 100644 host/src/attribute/consts.rs create mode 100644 host/src/attribute/data.rs rename host/src/{attribute.rs => attribute/mod.rs} (65%) rename host/src/{attribute_server.rs => attribute/server.rs} (100%) diff --git a/host/src/attribute/consts.rs b/host/src/attribute/consts.rs new file mode 100644 index 00000000..bd2d2b06 --- /dev/null +++ b/host/src/attribute/consts.rs @@ -0,0 +1,14 @@ +use super::Uuid; + +pub const GENERIC_ACCESS_SERVICE_UUID16: Uuid = Uuid::Uuid16(0x1800u16.to_le_bytes()); +pub const CHARACTERISTIC_DEVICE_NAME_UUID16: Uuid = Uuid::Uuid16(0x2A00u16.to_le_bytes()); +pub const CHARACTERISTIC_APPEARANCE_UUID16: Uuid = Uuid::Uuid16(0x2A03u16.to_le_bytes()); + +pub const GENERIC_ATTRIBUTE_SERVICE_UUID16: Uuid = Uuid::Uuid16(0x1801u16.to_le_bytes()); + +pub const PRIMARY_SERVICE_UUID16: Uuid = Uuid::Uuid16(0x2800u16.to_le_bytes()); +pub const SECONDARY_SERVICE_UUID16: Uuid = Uuid::Uuid16(0x2801u16.to_le_bytes()); +pub const INCLUDE_SERVICE_UUID16: Uuid = Uuid::Uuid16(0x2802u16.to_le_bytes()); +pub const CHARACTERISTIC_UUID16: Uuid = Uuid::Uuid16(0x2803u16.to_le_bytes()); +pub const CHARACTERISTIC_CCCD_UUID16: Uuid = Uuid::Uuid16(0x2902u16.to_le_bytes()); +pub const GENERIC_ATTRIBUTE_UUID16: Uuid = Uuid::Uuid16(0x1801u16.to_le_bytes()); diff --git a/host/src/attribute/data.rs b/host/src/attribute/data.rs new file mode 100644 index 00000000..d955aadc --- /dev/null +++ b/host/src/attribute/data.rs @@ -0,0 +1,196 @@ +use super::CharacteristicProp; +use super::CharacteristicProps; +use crate::att::AttErrorCode; +use crate::cursor::WriteCursor; +use crate::types::uuid::Uuid; + +/// The underlying data behind an attribute. +#[derive(Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum AttributeData<'d> { + /// Service UUID Data + /// + /// Serializes to raw bytes of UUID. + Service { uuid: Uuid }, + /// Read only data + /// + /// Implemented by storing a borrow of a slice. + /// The slice has to live at least as much as the device. + ReadOnlyData { + props: CharacteristicProps, + value: &'d [u8], + }, + /// Read and write data + /// + /// Implemented by storing a mutable borrow of a slice. + /// The slice has to live at least as much as the device. + Data { + props: CharacteristicProps, + value: &'d mut [u8], + }, + /// Characteristic declaration + Declaration { + props: CharacteristicProps, + handle: u16, + uuid: Uuid, + }, + /// Client Characteristic Configuration Descriptor + /// + /// Ref: BLUETOOTH CORE SPECIFICATION Version 6.0, Vol 3, Part G, Section 3.3.3.3 Client Characteristic Configuration + Cccd { notifications: bool, indications: bool }, +} + +impl<'d> AttributeData<'d> { + pub fn readable(&self) -> bool { + match self { + Self::Data { props, value } => props.0 & (CharacteristicProp::Read as u8) != 0, + _ => true, + } + } + + pub fn writable(&self) -> bool { + match self { + Self::Data { props, value } => { + props.0 + & (CharacteristicProp::Write as u8 + | CharacteristicProp::WriteWithoutResponse as u8 + | CharacteristicProp::AuthenticatedWrite as u8) + != 0 + } + Self::Cccd { + notifications, + indications, + } => true, + _ => false, + } + } + + /// Read the attribute value from some kind of a readable attribute data source + /// + /// Seek to to the `offset`-nth byte in the source data, fill the response data slice `data` up to the end or lower. + /// + /// The data buffer is always sized L2CAP_MTU, minus the 4 bytes for the L2CAP header) + /// The max stated value of an attribute in the GATT specification is 512 bytes. + /// + /// Returns the amount of bytes that have been written into `data`. + pub fn read(&self, offset: usize, data: &mut [u8]) -> Result { + if !self.readable() { + return Err(AttErrorCode::ReadNotPermitted); + } + match self { + Self::ReadOnlyData { props, value } => { + if offset > value.len() { + return Ok(0); + } + let len = data.len().min(value.len() - offset); + if len > 0 { + data[..len].copy_from_slice(&value[offset..offset + len]); + } + Ok(len) + } + Self::Data { props, value } => { + if offset > value.len() { + return Ok(0); + } + let len = data.len().min(value.len() - offset); + if len > 0 { + data[..len].copy_from_slice(&value[offset..offset + len]); + } + Ok(len) + } + Self::Service { uuid } => { + let val = uuid.as_raw(); + if offset > val.len() { + return Ok(0); + } + let len = data.len().min(val.len() - offset); + if len > 0 { + data[..len].copy_from_slice(&val[offset..offset + len]); + } + Ok(len) + } + Self::Cccd { + notifications, + indications, + } => { + if offset > 0 { + return Err(AttErrorCode::InvalidOffset); + } + if data.len() < 2 { + return Err(AttErrorCode::UnlikelyError); + } + let mut v = 0; + if *notifications { + v |= 0x01; + } + + if *indications { + v |= 0x02; + } + data[0] = v; + Ok(2) + } + Self::Declaration { props, handle, uuid } => { + let val = uuid.as_raw(); + if offset > val.len() + 3 { + return Ok(0); + } + let mut w = WriteCursor::new(data); + if offset == 0 { + w.write(props.0)?; + w.write(*handle)?; + } else if offset == 1 { + w.write(*handle)?; + } else if offset == 2 { + w.write(handle.to_le_bytes()[1])?; + } + + let to_write = w.available().min(val.len()); + + if to_write > 0 { + w.append(&val[..to_write])?; + } + Ok(w.len()) + } + } + } + + /// Write into the attribute value at 'offset' data from `data` buffer + /// + /// Expect the writes to be fragmented, like with [`AttributeData::read`] + pub fn write(&mut self, offset: usize, data: &[u8]) -> Result<(), AttErrorCode> { + let writable = self.writable(); + + match self { + Self::Data { value, props } => { + if !writable { + return Err(AttErrorCode::WriteNotPermitted); + } + + if offset + data.len() <= value.len() { + value[offset..offset + data.len()].copy_from_slice(data); + Ok(()) + } else { + Err(AttErrorCode::InvalidOffset) + } + } + Self::Cccd { + notifications, + indications, + } => { + if offset > 0 { + return Err(AttErrorCode::InvalidOffset); + } + + if data.is_empty() { + return Err(AttErrorCode::UnlikelyError); + } + + *notifications = data[0] & 0b00000001 != 0; + *indications = data[0] & 0b00000010 != 0; + Ok(()) + } + _ => Err(AttErrorCode::WriteNotPermitted), + } + } +} diff --git a/host/src/attribute.rs b/host/src/attribute/mod.rs similarity index 65% rename from host/src/attribute.rs rename to host/src/attribute/mod.rs index 760b0059..9dc55277 100644 --- a/host/src/attribute.rs +++ b/host/src/attribute/mod.rs @@ -1,27 +1,18 @@ +mod consts; +mod data; +pub mod server; +pub use consts::*; +pub use data::*; + use core::cell::RefCell; use core::fmt; use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::blocking_mutex::Mutex; -use crate::att::AttErrorCode; -use crate::cursor::WriteCursor; pub use crate::types::uuid::Uuid; use crate::Error; -pub const GENERIC_ACCESS_SERVICE_UUID16: Uuid = Uuid::Uuid16(0x1800u16.to_le_bytes()); -pub const CHARACTERISTIC_DEVICE_NAME_UUID16: Uuid = Uuid::Uuid16(0x2A00u16.to_le_bytes()); -pub const CHARACTERISTIC_APPEARANCE_UUID16: Uuid = Uuid::Uuid16(0x2A03u16.to_le_bytes()); - -pub const GENERIC_ATTRIBUTE_SERVICE_UUID16: Uuid = Uuid::Uuid16(0x1801u16.to_le_bytes()); - -pub const PRIMARY_SERVICE_UUID16: Uuid = Uuid::Uuid16(0x2800u16.to_le_bytes()); -pub const SECONDARY_SERVICE_UUID16: Uuid = Uuid::Uuid16(0x2801u16.to_le_bytes()); -pub const INCLUDE_SERVICE_UUID16: Uuid = Uuid::Uuid16(0x2802u16.to_le_bytes()); -pub const CHARACTERISTIC_UUID16: Uuid = Uuid::Uuid16(0x2803u16.to_le_bytes()); -pub const CHARACTERISTIC_CCCD_UUID16: Uuid = Uuid::Uuid16(0x2902u16.to_le_bytes()); -pub const GENERIC_ATTRIBUTE_UUID16: Uuid = Uuid::Uuid16(0x1801u16.to_le_bytes()); - #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] /// An enum of possible characteristic properties @@ -71,197 +62,6 @@ impl<'d> Attribute<'d> { const EMPTY: Option> = None; } -/// The underlying data behind an attribute. -#[derive(Debug, PartialEq, Eq)] -#[non_exhaustive] -pub enum AttributeData<'d> { - /// Service UUID Data - /// - /// Serializes to raw bytes of UUID. - Service { uuid: Uuid }, - /// Read only data - /// - /// Implemented by storing a borrow of a slice. - /// The slice has to live at least as much as the device. - ReadOnlyData { - props: CharacteristicProps, - value: &'d [u8], - }, - /// Read and write data - /// - /// Implemented by storing a mutable borrow of a slice. - /// The slice has to live at least as much as the device. - Data { - props: CharacteristicProps, - value: &'d mut [u8], - }, - /// Characteristic declaration - Declaration { - props: CharacteristicProps, - handle: u16, - uuid: Uuid, - }, - /// Client Characteristic Configuration Descriptor - /// - /// Ref: BLUETOOTH CORE SPECIFICATION Version 6.0, Vol 3, Part G, Section 3.3.3.3 Client Characteristic Configuration - Cccd { notifications: bool, indications: bool }, -} - -impl<'d> AttributeData<'d> { - pub fn readable(&self) -> bool { - match self { - Self::Data { props, value } => props.0 & (CharacteristicProp::Read as u8) != 0, - _ => true, - } - } - - pub fn writable(&self) -> bool { - match self { - Self::Data { props, value } => { - props.0 - & (CharacteristicProp::Write as u8 - | CharacteristicProp::WriteWithoutResponse as u8 - | CharacteristicProp::AuthenticatedWrite as u8) - != 0 - } - Self::Cccd { - notifications, - indications, - } => true, - _ => false, - } - } - - /// Read the attribute value from some kind of a readable attribute data source - /// - /// Seek to to the `offset`-nth byte in the source data, fill the response data slice `data` up to the end or lower. - /// - /// The data buffer is always sized L2CAP_MTU, minus the 4 bytes for the L2CAP header) - /// The max stated value of an attribute in the GATT specification is 512 bytes. - /// - /// Returns the amount of bytes that have been written into `data`. - pub fn read(&self, offset: usize, data: &mut [u8]) -> Result { - if !self.readable() { - return Err(AttErrorCode::ReadNotPermitted); - } - match self { - Self::ReadOnlyData { props, value } => { - if offset > value.len() { - return Ok(0); - } - let len = data.len().min(value.len() - offset); - if len > 0 { - data[..len].copy_from_slice(&value[offset..offset + len]); - } - Ok(len) - } - Self::Data { props, value } => { - if offset > value.len() { - return Ok(0); - } - let len = data.len().min(value.len() - offset); - if len > 0 { - data[..len].copy_from_slice(&value[offset..offset + len]); - } - Ok(len) - } - Self::Service { uuid } => { - let val = uuid.as_raw(); - if offset > val.len() { - return Ok(0); - } - let len = data.len().min(val.len() - offset); - if len > 0 { - data[..len].copy_from_slice(&val[offset..offset + len]); - } - Ok(len) - } - Self::Cccd { - notifications, - indications, - } => { - if offset > 0 { - return Err(AttErrorCode::InvalidOffset); - } - if data.len() < 2 { - return Err(AttErrorCode::UnlikelyError); - } - let mut v = 0; - if *notifications { - v |= 0x01; - } - - if *indications { - v |= 0x02; - } - data[0] = v; - Ok(2) - } - Self::Declaration { props, handle, uuid } => { - let val = uuid.as_raw(); - if offset > val.len() + 3 { - return Ok(0); - } - let mut w = WriteCursor::new(data); - if offset == 0 { - w.write(props.0)?; - w.write(*handle)?; - } else if offset == 1 { - w.write(*handle)?; - } else if offset == 2 { - w.write(handle.to_le_bytes()[1])?; - } - - let to_write = w.available().min(val.len()); - - if to_write > 0 { - w.append(&val[..to_write])?; - } - Ok(w.len()) - } - } - } - - /// Write into the attribute value at 'offset' data from `data` buffer - /// - /// Expect the writes to be fragmented, like with [`AttributeData::read`] - pub fn write(&mut self, offset: usize, data: &[u8]) -> Result<(), AttErrorCode> { - let writable = self.writable(); - - match self { - Self::Data { value, props } => { - if !writable { - return Err(AttErrorCode::WriteNotPermitted); - } - - if offset + data.len() <= value.len() { - value[offset..offset + data.len()].copy_from_slice(data); - Ok(()) - } else { - Err(AttErrorCode::InvalidOffset) - } - } - Self::Cccd { - notifications, - indications, - } => { - if offset > 0 { - return Err(AttErrorCode::InvalidOffset); - } - - if data.is_empty() { - return Err(AttErrorCode::UnlikelyError); - } - - *notifications = data[0] & 0b00000001 != 0; - *indications = data[0] & 0b00000010 != 0; - Ok(()) - } - _ => Err(AttErrorCode::WriteNotPermitted), - } - } -} - impl<'a> fmt::Debug for Attribute<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Attribute") diff --git a/host/src/attribute_server.rs b/host/src/attribute/server.rs similarity index 100% rename from host/src/attribute_server.rs rename to host/src/attribute/server.rs diff --git a/host/src/gatt.rs b/host/src/gatt.rs index 8e2c4fc8..cddd3576 100644 --- a/host/src/gatt.rs +++ b/host/src/gatt.rs @@ -5,8 +5,8 @@ use embassy_sync::channel::{DynamicReceiver, DynamicSender}; use heapless::Vec; use crate::att::{self, AttReq, AttRsp, ATT_HANDLE_VALUE_NTF}; +use crate::attribute::server::AttributeServer; use crate::attribute::{Characteristic, Uuid, CHARACTERISTIC_UUID16, PRIMARY_SERVICE_UUID16}; -use crate::attribute_server::AttributeServer; use crate::connection::Connection; use crate::connection_manager::DynamicConnectionManager; use crate::cursor::{ReadCursor, WriteCursor}; diff --git a/host/src/host.rs b/host/src/host.rs index 43eca225..6a5c1266 100644 --- a/host/src/host.rs +++ b/host/src/host.rs @@ -748,7 +748,7 @@ where table: &'reference AttributeTable<'values, M, MAX>, ) -> GattServer<'reference, 'values, M, MAX, L2CAP_MTU> { self.connections.set_default_att_mtu(L2CAP_MTU as u16 - 4); - use crate::attribute_server::AttributeServer; + use crate::attribute::server::AttributeServer; GattServer { server: AttributeServer::new(table), rx: self.att_inbound.receiver().into(), diff --git a/host/src/lib.rs b/host/src/lib.rs index c2a6b398..4d140f24 100644 --- a/host/src/lib.rs +++ b/host/src/lib.rs @@ -40,8 +40,6 @@ pub use host::*; #[cfg(feature = "gatt")] pub mod attribute; #[cfg(feature = "gatt")] -mod attribute_server; -#[cfg(feature = "gatt")] pub mod gatt; /// A BLE address. From c517772211f2e2d7212c587a9dbf88df9d3ba9be Mon Sep 17 00:00:00 2001 From: nm17 Date: Sun, 1 Sep 2024 22:07:25 +0400 Subject: [PATCH 7/7] Refactor away repeating parts of the attribute module This doesn't give a big improvement in terms of LOC, but IMHO makes the code a lot more readable. I added docstrings where I could, but I might have missed some. Also, the AttributeTable now uses heapless::Vec instead of what was before. Should have no impact on the performance, since the previous implementation was basically the same. This also means that we have an actual iterator, so we can use for loops instead of while-next loops. --- host/src/attribute/mod.rs | 183 +++++++++++++------- host/src/attribute/server.rs | 327 ++++++++++++++++------------------- host/src/gatt.rs | 4 +- 3 files changed, 273 insertions(+), 241 deletions(-) diff --git a/host/src/attribute/mod.rs b/host/src/attribute/mod.rs index 9dc55277..3a96bade 100644 --- a/host/src/attribute/mod.rs +++ b/host/src/attribute/mod.rs @@ -3,13 +3,16 @@ mod data; pub mod server; pub use consts::*; pub use data::*; +use heapless::Vec; use core::cell::RefCell; use core::fmt; +use core::ops::ControlFlow; use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::blocking_mutex::Mutex; +use crate::att::AttErrorCode; pub use crate::types::uuid::Uuid; use crate::Error; @@ -94,34 +97,12 @@ impl<'a> Attribute<'a> { /// Table of Attributes available to the [`crate::gatt::GattServer`]. pub struct AttributeTable<'d, M: RawMutex, const MAX: usize> { - inner: Mutex>>, + inner: Mutex, MAX>>>, /// Next available attribute handle value known by this table next_handle: u16, } -/// Inner representation of [`AttributeTable`] -/// -/// Represented by a stack allocated list of attributes with a len field to keep track of how many are actually present. -// TODO: Switch to heapless Vec -#[derive(Debug, PartialEq, Eq)] -pub struct InnerTable<'d, const MAX: usize> { - attributes: [Option>; MAX], - - /// Amount of attributes in the list. - len: usize, -} - -impl<'d, const MAX: usize> InnerTable<'d, MAX> { - fn push(&mut self, attribute: Attribute<'d>) { - if self.len == MAX { - panic!("no space for more attributes") - } - self.attributes[self.len].replace(attribute); - self.len += 1; - } -} - impl<'d, M: RawMutex, const MAX: usize> Default for AttributeTable<'d, M, MAX> { fn default() -> Self { Self::new() @@ -133,30 +114,120 @@ impl<'d, M: RawMutex, const MAX: usize> AttributeTable<'d, M, MAX> { pub fn new() -> Self { Self { next_handle: 1, - inner: Mutex::new(RefCell::new(InnerTable { - len: 0, - attributes: [Attribute::EMPTY; MAX], - })), + inner: Mutex::new(RefCell::new(Vec::new())), } } - pub fn with_inner)>(&self, f: F) { + pub fn with_inner])>(&self, mut f: F) { self.inner.lock(|inner| { let mut table = inner.borrow_mut(); f(&mut table); }) } - pub fn iterate) -> R, R>(&self, mut f: F) -> R { + /// Take a closure and call it with a mutable iterator over attributes. + /// + /// Returns whatever the given function returned. + pub fn iterate>) -> R, R>(&self, f: F) -> R { self.inner.lock(|inner| { let mut table = inner.borrow_mut(); - let len = table.len; - let it = AttributeIterator { - attributes: &mut table.attributes[..], - pos: 0, - len, - }; - f(it) + f(table.iter_mut()) + }) + } + + /// Call a function **once** if an attribute with a given handle has been found, returning its output. + /// + /// Returns `R` if the handle was found, [`AttErrorCode::AttributeNotFound`] otherwise. + /// + /// `condition` function takes a borrow of a [`Attribute`]. If it returns `Some(...)`, + /// a mutable reference to the same [`Attribute`] value gets passed to the main function `f`. + /// + /// Returns a [`Result`] with whatever the main function chooses to return + /// as it's [`Ok`] output, or [`AttErrorCode::AttributeNotFound`] as the [`Err`] output + /// (as the attribute with the same handle was not found). + pub fn on_handle) -> Result, R>( + &self, + handle: u16, + f: F, + ) -> Result { + self.iterate(|it| { + for att in it { + if att.handle == handle { + return f(att); + } + } + Err(AttErrorCode::AttributeNotFound) + }) + } + + /// Call a function **once** if a condition function chooses an attribute to process. + /// + /// `condition` function takes a borrow of a [`Attribute`]. If it returns `Some(...)`, + /// a mutable reference to the same [`Attribute`] value gets passed to the main function `f`. + /// + /// Returns a [`Result`] with whatever the main function chooses to return + /// as it's [`Ok`] output, or [`AttErrorCode`] as the [`Err`] output. + pub fn on_attribute< + FCondition: FnMut(&Attribute<'d>) -> Option, + F: FnOnce(&mut Attribute<'d>, RCondition) -> Result, + R, + RCondition, + >( + &self, + mut condition: FCondition, + f: F, + ) -> Result { + self.iterate(|it| { + for att in it { + let res = condition(att); + if let Some(r_cond_output) = res { + return f(att, r_cond_output); + } + } + Err(AttErrorCode::AttributeNotFound) + }) + } + + /// Call a function every time a condition function chooses to process an attribute, or break. + /// + /// `condition` function takes a borrow of a [`Attribute`]. + /// + /// ## Map of behaviour depending on what `condition` returns: + /// + /// - `ControlFlow::Continue(Some(RCondition))` - the main function + /// gets called with a mutable borrow of an attribute and `RCondition`. + /// Execution continues for other attributes. + /// - `ControlFlow::Continue(None)` - the main function is not called. + /// Execution continues for other attributes. + /// - `ControlFlow::Break` - the main function is not called. + /// Execution stops. + /// + /// Returns a [`Result`] with it's [`Ok`] output being `()` (if you need to keep + /// some kind of state between `f` runs, just modify stuff outside the closure), + /// or `E` as the [`Err`] output. + pub fn for_each_attribute< + FCondition: FnMut(&Attribute<'d>) -> ControlFlow<(), Option>, + F: FnMut(&mut Attribute<'d>, RCondition) -> Result<(), E>, + RCondition, + E, + >( + &self, + mut condition: FCondition, + mut f: F, + ) -> Result<(), E> { + self.iterate(|it| { + for att in it { + let res = condition(att); + match res { + ControlFlow::Continue(r_cond_output) => { + if let Some(r_cond_output) = r_cond_output { + f(att, r_cond_output)?; + } + } + ControlFlow::Break(_) => break, + } + } + Ok(()) }) } @@ -168,7 +239,7 @@ impl<'d, M: RawMutex, const MAX: usize> AttributeTable<'d, M, MAX> { attribute.handle = handle; self.inner.lock(|inner| { let mut inner = inner.borrow_mut(); - inner.push(attribute); + inner.push(attribute).expect("no more space for attributes"); }); self.next_handle += 1; handle @@ -178,7 +249,7 @@ impl<'d, M: RawMutex, const MAX: usize> AttributeTable<'d, M, MAX> { /// /// Note: The service builder is tied to the AttributeTable. pub fn add_service(&mut self, service: Service) -> ServiceBuilder<'_, 'd, M, MAX> { - let len = self.inner.lock(|i| i.borrow().len); + let len = self.inner.lock(|i| i.borrow().len()); let handle = self.next_handle; self.push(Attribute { uuid: PRIMARY_SERVICE_UUID16, @@ -200,8 +271,8 @@ impl<'d, M: RawMutex, const MAX: usize> AttributeTable<'d, M, MAX> { /// /// If the characteristic for the handle cannot be found, an error is returned. pub fn set(&self, handle: Characteristic, input: &[u8]) -> Result<(), Error> { - self.iterate(|mut it| { - while let Some(att) = it.next() { + self.iterate(|it| { + for att in it { if att.handle == handle.handle { if let AttributeData::Data { props, value } = &mut att.data { assert_eq!(value.len(), input.len()); @@ -219,11 +290,11 @@ impl<'d, M: RawMutex, const MAX: usize> AttributeTable<'d, M, MAX> { /// The return value of the closure is returned in this function and is assumed to be infallible. /// /// If the characteristic for the handle cannot be found, an error is returned. - pub fn get T, T>(&self, handle: Characteristic, mut f: F) -> Result { - self.iterate(|mut it| { - while let Some(att) = it.next() { + pub fn get T, T>(&self, handle: Characteristic, f: F) -> Result { + self.iterate(|it| { + for att in it { if att.handle == handle.handle { - if let AttributeData::Data { props, value } = &mut att.data { + if let AttributeData::Data { props, value } = &att.data { let v = f(value); return Ok(v); } @@ -298,7 +369,7 @@ impl<'r, 'd, M: RawMutex, const MAX: usize> ServiceBuilder<'r, 'd, M, MAX> { data: AttributeData::Declaration { props, handle: next, - uuid: uuid.clone(), + uuid, }, }); @@ -356,8 +427,8 @@ impl<'r, 'd, M: RawMutex, const MAX: usize> Drop for ServiceBuilder<'r, 'd, M, M fn drop(&mut self) { let last_handle = self.table.next_handle + 1; self.table.with_inner(|inner| { - for item in inner.attributes[self.start..inner.len].iter_mut() { - item.as_mut().unwrap().last_handle_in_group = last_handle; + for item in inner[self.start..].iter_mut() { + item.last_handle_in_group = last_handle; } }); @@ -379,24 +450,6 @@ pub struct DescriptorHandle { pub(crate) handle: u16, } -pub struct AttributeIterator<'a, 'd> { - attributes: &'a mut [Option>], - pos: usize, - len: usize, -} - -impl<'a, 'd> AttributeIterator<'a, 'd> { - pub fn next<'m>(&'m mut self) -> Option<&'m mut Attribute<'d>> { - if self.pos < self.len { - let i = self.attributes[self.pos].as_mut(); - self.pos += 1; - i - } else { - None - } - } -} - /// Service information. /// /// Currently only has UUID. diff --git a/host/src/attribute/server.rs b/host/src/attribute/server.rs index c131a47c..4d54c728 100644 --- a/host/src/attribute/server.rs +++ b/host/src/attribute/server.rs @@ -1,4 +1,5 @@ use core::cell::RefCell; +use core::ops::ControlFlow; use bt_hci::param::ConnHandle; use embassy_sync::blocking_mutex::raw::RawMutex; @@ -83,36 +84,33 @@ impl<'c, 'd, M: RawMutex, const MAX: usize> AttributeServer<'c, 'd, M, MAX> { let mut data = WriteCursor::new(buf); let (mut header, mut body) = data.split(2)?; - let err = self.table.iterate(|mut it| { - let mut err = Err(AttErrorCode::AttributeNotFound); - while let Some(att) = it.next() { - //trace!("Check attribute {:?} {}", att.uuid, att.handle); + + let len = self.table.on_attribute( + |att| { if &att.uuid == attribute_type && att.handle >= start && att.handle <= end { - body.write(att.handle)?; - handle = att.handle; + Some(()) + } else { + None + } + }, + |att, _| { + body.write(att.handle)?; + handle = att.handle; - if att.data.readable() { - err = att.data.read(0, body.write_buf()); - if let Ok(len) = &err { - body.commit(*len)?; - } - } + let len = att.data.read(0, body.write_buf())?; + body.commit(len)?; - // debug!("found! {:?} {}", att.uuid, att.handle); - break; - } - } - err - }); + Ok(len) + }, + ); - match err { - Ok(len) => { - header.write(att::ATT_READ_BY_TYPE_RSP)?; - header.write(2 + len as u8)?; - Ok(header.len() + body.len()) - } - Err(e) => Ok(Self::error_response(data, att::ATT_READ_BY_TYPE_REQ, handle, e)?), + if let Err(e) = len { + return Self::error_response(data, att::ATT_READ_BY_TYPE_REQ, handle, e); } + + header.write(att::ATT_READ_BY_TYPE_RSP)?; + header.write(2 + len.unwrap() as u8)?; + Ok(header.len() + body.len()) } fn handle_read_by_group_type_req( @@ -125,39 +123,39 @@ impl<'c, 'd, M: RawMutex, const MAX: usize> AttributeServer<'c, 'd, M, MAX> { // TODO respond with all finds - not just one let mut handle = start; let mut data = WriteCursor::new(buf); - + // trace!("Check attribute {:x} {}", att.uuid, att.handle); let (mut header, mut body) = data.split(2)?; - let err = self.table.iterate(|mut it| { - let mut err = Err(AttErrorCode::AttributeNotFound); - while let Some(att) = it.next() { - // trace!("Check attribute {:x} {}", att.uuid, att.handle); + let len = self.table.on_attribute( + |att| { if &att.uuid == group_type && att.handle >= start && att.handle <= end { - //debug!("found! {:x} {}", att.uuid, att.handle); - handle = att.handle; + Some(()) + } else { + None + } + }, + |att, _| { + handle = att.handle; - body.write(att.handle)?; - body.write(att.last_handle_in_group)?; + body.write(att.handle)?; + body.write(att.last_handle_in_group)?; - if att.data.readable() { - err = att.data.read(0, body.write_buf()); - if let Ok(len) = &err { - body.commit(*len)?; - } - } - break; - } - } - err - }); + let len = att.data.read(0, body.write_buf())?; - match err { - Ok(len) => { - header.write(att::ATT_READ_BY_GROUP_TYPE_RSP)?; - header.write(4 + len as u8)?; - Ok(header.len() + body.len()) - } - Err(e) => Ok(Self::error_response(data, att::ATT_READ_BY_GROUP_TYPE_REQ, handle, e)?), + body.commit(len)?; + + Ok(len) + }, + ); + //debug!("found! {:x} {}", att.uuid, att.handle); + + if let Err(e) = len { + return Self::error_response(data, att::ATT_READ_BY_GROUP_TYPE_REQ, handle, e); } + + header.write(att::ATT_READ_BY_GROUP_TYPE_RSP)?; + header.write(4 + len.unwrap() as u8)?; + + Ok(header.len() + body.len()) } fn handle_read_req(&self, buf: &mut [u8], handle: u16) -> Result { @@ -165,81 +163,62 @@ impl<'c, 'd, M: RawMutex, const MAX: usize> AttributeServer<'c, 'd, M, MAX> { data.write(att::ATT_READ_RSP)?; - let err = self.table.iterate(|mut it| { - let mut err = Err(AttErrorCode::AttributeNotFound); - while let Some(att) = it.next() { - if att.handle == handle { - if att.data.readable() { - err = att.data.read(0, data.write_buf()); - if let Ok(len) = err { - data.commit(len)?; - } - } - break; - } - } - err + let err = self.table.on_handle(handle, |att| { + let len = att.data.read(0, data.write_buf())?; + data.commit(len)?; + + Ok(()) }); - match err { - Ok(_) => Ok(data.len()), - Err(e) => Ok(Self::error_response(data, att::ATT_READ_REQ, handle, e)?), + if let Err(e) = err { + return Self::error_response(data, att::ATT_READ_REQ, handle, e); } + + Ok(data.len()) } fn handle_write_cmd(&self, buf: &mut [u8], handle: u16, data: &[u8]) -> Result { // TODO: Generate event - self.table.iterate(|mut it| { - while let Some(att) = it.next() { - if att.handle == handle { - if att.data.writable() { - // Write commands can't respond with an error. - att.data.write(0, data).unwrap(); - } - break; - } - } - Ok(0) - }) + self.table + .on_handle(handle, |att| { + // Write commands can't respond with an error. + att.data.write(0, data)?; + + Ok(()) + }) + .unwrap(); + + Ok(0) } fn handle_write_req( &self, conn: ConnHandle, - buf: &mut [u8], + resp_buf: &mut [u8], handle: u16, data: &[u8], ) -> Result { - let err = self.table.iterate(|mut it| { - let mut err = Err(AttErrorCode::AttributeNotFound); - while let Some(att) = it.next() { - if att.handle == handle { - if att.data.writable() { - err = att.data.write(0, data); - if err.is_ok() { - if let AttributeData::Cccd { - notifications, - indications, - } = att.data - { - self.set_notify(conn, handle, notifications); - } - } - } - break; - } + let res = self.table.on_handle(handle, |att| { + att.data.write(0, data)?; + if let AttributeData::Cccd { + notifications, + indications, + } = att.data + { + self.set_notify(conn, handle, notifications); } - err + + Ok(()) }); - let mut w = WriteCursor::new(buf); - match err { - Ok(()) => { - w.write(att::ATT_WRITE_RSP)?; - Ok(w.len()) - } - Err(e) => Ok(Self::error_response(w, att::ATT_WRITE_REQ, handle, e)?), + let mut w = WriteCursor::new(resp_buf); + + if let Err(e) = res { + return Self::error_response(w, att::ATT_WRITE_REQ, handle, e); } + + w.write(att::ATT_WRITE_RSP)?; + Ok(w.len()) } fn handle_find_type_value( @@ -254,23 +233,34 @@ impl<'c, 'd, M: RawMutex, const MAX: usize> AttributeServer<'c, 'd, M, MAX> { let attr_type = Uuid::new_short(attr_type); w.write(att::ATT_FIND_BY_TYPE_VALUE_RSP)?; - self.table.iterate(|mut it| { - while let Some(att) = it.next() { - if att.handle >= start && att.handle <= end && att.uuid == attr_type { - if let AttributeData::Service { uuid } = &att.data { - if uuid.as_raw() == attr_value { - if w.available() < 4 + uuid.as_raw().len() { - break; - } - w.write(att.handle)?; - w.write(att.last_handle_in_group)?; - w.write_ref(uuid)?; + + let available = w.available(); + + let _ = self.table.for_each_attribute( + |att| { + let a = att.handle >= start && att.handle <= end && att.uuid == attr_type; + if let AttributeData::Service { uuid } = &att.data { + let b = uuid.as_raw() == attr_value; + + if a && b { + if available < (4 + uuid.as_raw().len()) { + return ControlFlow::Break(()); } + return ControlFlow::Continue(Some(*uuid)); } } - } - Ok::<(), codec::Error>(()) - })?; + + ControlFlow::Continue(None) + }, + |att, uuid| { + w.write(att.handle)?; + w.write(att.last_handle_in_group)?; + w.write_ref(&uuid)?; + + Ok::<_, codec::Error>(()) + }, + ); + if w.len() > 1 { Ok(w.len()) } else { @@ -289,33 +279,36 @@ impl<'c, 'd, M: RawMutex, const MAX: usize> AttributeServer<'c, 'd, M, MAX> { let (mut header, mut body) = w.split(2)?; header.write(att::ATT_FIND_INFORMATION_RSP)?; - let mut t = 0; - - self.table.iterate(|mut it| { - while let Some(att) = it.next() { - if att.handle >= start && att.handle <= end { - if t == 0 { - t = att.uuid.get_type(); - } else if t != att.uuid.get_type() { - break; + let mut uuid_type = 0; + + self.table + .for_each_attribute( + |att| { + if att.handle >= start && att.handle <= end { + if uuid_type == 0 { + uuid_type = att.uuid.get_type(); + } else if uuid_type != att.uuid.get_type() { + return ControlFlow::Break(()); + } + return ControlFlow::Continue(Some(uuid_type)); } + ControlFlow::Continue(None) + }, + |att, _| { body.write(att.handle)?; body.append(att.uuid.as_raw())?; - } - } - Ok::<(), codec::Error>(()) - })?; - header.write(t)?; + + Ok::<(), codec::Error>(()) + }, + ) + .unwrap(); + + header.write(uuid_type)?; if body.len() > 2 { Ok(header.len() + body.len()) } else { - Ok(Self::error_response( - w, - att::ATT_FIND_INFORMATION_REQ, - start, - AttErrorCode::AttributeNotFound, - )?) + Self::error_response(w, att::ATT_FIND_INFORMATION_REQ, start, AttErrorCode::AttributeNotFound) } } @@ -345,24 +338,18 @@ impl<'c, 'd, M: RawMutex, const MAX: usize> AttributeServer<'c, 'd, M, MAX> { w.write(handle)?; w.write(offset)?; - let err = self.table.iterate(|mut it| { - let mut err = Err(AttErrorCode::AttributeNotFound); - while let Some(att) = it.next() { - if att.handle == handle { - if att.data.writable() { - err = att.data.write(offset as usize, value); - } - w.append(value)?; - break; - } - } - err + let err = self.table.on_handle(handle, |att| { + att.data.write(offset as usize, value)?; + w.append(value)?; + + Ok(()) }); - match err { - Ok(()) => Ok(w.len()), - Err(e) => Ok(Self::error_response(w, att::ATT_PREPARE_WRITE_REQ, handle, e)?), + if let Err(e) = err { + return Self::error_response(w, att::ATT_PREPARE_WRITE_REQ, handle, e); } + + Ok(w.len()) } fn handle_execute_write(&self, buf: &mut [u8], _flags: u8) -> Result { @@ -375,26 +362,18 @@ impl<'c, 'd, M: RawMutex, const MAX: usize> AttributeServer<'c, 'd, M, MAX> { let mut w = WriteCursor::new(buf); w.write(att::ATT_READ_BLOB_RSP)?; - let err = self.table.iterate(|mut it| { - let mut err = Err(AttErrorCode::AttributeNotFound); - while let Some(att) = it.next() { - if att.handle == handle { - if att.data.readable() { - err = att.data.read(offset as usize, w.write_buf()); - if let Ok(n) = &err { - w.commit(*n)?; - } - } - break; - } - } - err + let err = self.table.on_handle(handle, |att| { + let n = att.data.read(offset as usize, w.write_buf())?; + w.commit(n)?; + + Ok(()) }); - match err { - Ok(_) => Ok(w.len()), - Err(e) => Ok(Self::error_response(w, att::ATT_READ_BLOB_REQ, handle, e)?), + if let Err(e) = err { + return Self::error_response(w, att::ATT_READ_BLOB_REQ, handle, e); } + + Ok(w.len()) } fn handle_read_multiple(&self, buf: &mut [u8], handles: &[u8]) -> Result { diff --git a/host/src/gatt.rs b/host/src/gatt.rs index cddd3576..9ca4eb6c 100644 --- a/host/src/gatt.rs +++ b/host/src/gatt.rs @@ -204,7 +204,7 @@ impl<'reference, 'resources, T: Controller, const MAX: usize, const L2CAP_MTU: u .push(ServiceHandle { start: handle, end, - uuid: uuid.clone(), + uuid: *uuid, }) .unwrap(); } @@ -306,7 +306,7 @@ impl<'reference, 'resources, T: Controller, const MAX: usize, const L2CAP_MTU: u let data = att::AttReq::ReadByType { start: service.start, end: service.end, - attribute_type: uuid.clone(), + attribute_type: *uuid, }; let pdu = self.request(data).await?;