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 + 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 52% rename from host/src/attribute.rs rename to host/src/attribute/mod.rs index 33f95863..3a96bade 100644 --- a/host/src/attribute.rs +++ b/host/src/attribute/mod.rs @@ -1,216 +1,68 @@ +mod consts; +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; -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)] +#[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 + /// + /// 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> { +#[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. pub handle: u16, - pub last_handle_in_group: u16, - pub data: AttributeData<'a>, -} - -impl<'a> Attribute<'a> { - const EMPTY: Option> = None; -} - -pub enum AttributeData<'d> { - Service { - uuid: Uuid, - }, - ReadOnlyData { - props: CharacteristicProps, - value: &'d [u8], - }, - Data { - props: CharacteristicProps, - value: &'d mut [u8], - }, - Declaration { - props: CharacteristicProps, - handle: u16, - uuid: Uuid, - }, - Cccd { - notifications: bool, - indications: bool, - }, + /// 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<'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, - } - } - - 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()) - } - } - } - - 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] & 0x01 != 0; - *indications = data[0] & 0x02 != 0; - Ok(()) - } - _ => Err(AttErrorCode::WriteNotPermitted), - } - } +impl<'d> Attribute<'d> { + const EMPTY: Option> = None; } impl<'a> fmt::Debug for Attribute<'a> { @@ -238,29 +90,17 @@ 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, -} + inner: Mutex, MAX>>>, -pub struct InnerTable<'d, const MAX: usize> { - attributes: [Option>; MAX], - 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; - } + /// Next available attribute handle value known by this table + next_handle: u16, } impl<'d, M: RawMutex, const MAX: usize> Default for AttributeTable<'d, M, MAX> { @@ -270,50 +110,147 @@ 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, - inner: Mutex::new(RefCell::new(InnerTable { - len: 0, - attributes: [Attribute::EMPTY; MAX], - })), + next_handle: 1, + 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(()) }) } + /// 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); + inner.push(attribute).expect("no more space for attributes"); }); - 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 len = self.inner.lock(|i| i.borrow().len()); + let handle = self.next_handle; self.push(Attribute { uuid: PRIMARY_SERVICE_UUID16, handle: 0, @@ -334,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()); @@ -348,16 +285,16 @@ 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. /// /// 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); } @@ -397,7 +334,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, } @@ -408,6 +345,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 +360,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, @@ -431,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, }, }); @@ -487,15 +425,15 @@ 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; + for item in inner[self.start..].iter_mut() { + item.last_handle_in_group = last_handle; } }); // 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)); } } @@ -512,24 +450,10 @@ 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. +#[derive(Clone, Debug)] pub struct Service { pub uuid: Uuid, } @@ -540,7 +464,11 @@ impl Service { } } -#[derive(Clone, Copy)] +/// 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)] pub struct CharacteristicProps(u8); impl<'a> From<&'a [CharacteristicProp]> for CharacteristicProps { diff --git a/host/src/attribute_server.rs b/host/src/attribute/server.rs similarity index 55% rename from host/src/attribute_server.rs rename to 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 8e2c4fc8..9ca4eb6c 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}; @@ -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?; 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. 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]),