Skip to content

Commit

Permalink
Split from_gatt and to_gatt into separate traits and implement for &'…
Browse files Browse the repository at this point in the history
…static str (#289)

* Split from_gatt and to_gatt into separate traits for correct implementation on &str

* Remove redundant GattSized trait and consolidate with ToGatt

* Add FromGatt and ToGatt to prelude
  • Loading branch information
petekubiak authored Feb 20, 2025
1 parent 0691ef5 commit 23cb851
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 49 deletions.
4 changes: 2 additions & 2 deletions host-macros/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,11 +167,11 @@ impl ServerBuilder {
})
}

#visibility fn get<T: trouble_host::types::gatt_traits::GattValue>(&self, characteristic: &trouble_host::attribute::Characteristic<T>) -> Result<T, trouble_host::Error> {
#visibility fn get<T: trouble_host::types::gatt_traits::FromGatt>(&self, characteristic: &trouble_host::attribute::Characteristic<T>) -> Result<T, trouble_host::Error> {
self.server.table().get(characteristic)
}

#visibility fn set<T: trouble_host::types::gatt_traits::GattValue>(&self, characteristic: &trouble_host::attribute::Characteristic<T>, input: &T) -> Result<(), trouble_host::Error> {
#visibility fn set<T: trouble_host::types::gatt_traits::ToGatt>(&self, characteristic: &trouble_host::attribute::Characteristic<T>, input: &T) -> Result<(), trouble_host::Error> {
self.server.table().set(characteristic, input)
}
}
Expand Down
6 changes: 3 additions & 3 deletions host-macros/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,10 @@ impl ServiceBuilder {

self.code_build_chars.extend(quote_spanned! {characteristic.span=>
let #char_name = {
static #name_screaming: static_cell::StaticCell<[u8; <#ty as trouble_host::types::gatt_traits::GattValue>::MAX_SIZE]> = static_cell::StaticCell::new();
static #name_screaming: static_cell::StaticCell<[u8; <#ty as trouble_host::types::gatt_traits::ToGatt>::MAX_SIZE]> = static_cell::StaticCell::new();
let mut val = <#ty>::default(); // constrain the type of the value here
val = #default_value; // update the temporary value with our new default
let store = #name_screaming.init([0; <#ty as trouble_host::types::gatt_traits::GattValue>::MAX_SIZE]);
let store = #name_screaming.init([0; <#ty as trouble_host::types::gatt_traits::ToGatt>::MAX_SIZE]);
let mut builder = service
.add_characteristic(#uuid, &[#(#properties),*], val, store);
#descriptors
Expand Down Expand Up @@ -300,7 +300,7 @@ impl ServiceBuilder {
const CAPACITY: u8 = if (#capacity) < 16 { 16 } else { #capacity }; // minimum capacity is 16 bytes
static #name_screaming: static_cell::StaticCell<[u8; CAPACITY as usize]> = static_cell::StaticCell::new();
let store = #name_screaming.init([0; CAPACITY as usize]);
let value = trouble_host::types::gatt_traits::GattValue::to_gatt(&value);
let value = trouble_host::types::gatt_traits::ToGatt::to_gatt(&value);
store[..value.len()].copy_from_slice(value);
builder.add_descriptor(
#uuid,
Expand Down
24 changes: 12 additions & 12 deletions host/src/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::att::AttErrorCode;
use crate::attribute_server::AttributeServer;
use crate::cursor::{ReadCursor, WriteCursor};
use crate::prelude::Connection;
use crate::types::gatt_traits::GattValue;
use crate::types::gatt_traits::{FromGatt, ToGatt};
pub use crate::types::uuid::Uuid;
use crate::Error;
use heapless::Vec;
Expand Down Expand Up @@ -400,7 +400,7 @@ impl<'d, M: RawMutex, const MAX: usize> AttributeTable<'d, M, MAX> {
///
/// If the characteristic for the handle cannot be found, or the shape of the data does not match the type of the characterstic,
/// an error is returned
pub fn set<T: GattValue>(&self, characteristic: &Characteristic<T>, input: &T) -> Result<(), Error> {
pub fn set<T: ToGatt>(&self, characteristic: &Characteristic<T>, input: &T) -> Result<(), Error> {
let gatt_value = input.to_gatt();
self.set_raw(characteristic.handle, gatt_value)
}
Expand All @@ -410,7 +410,7 @@ 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: GattValue>(&self, characteristic: &Characteristic<T>) -> Result<T, Error> {
pub fn get<T: FromGatt>(&self, characteristic: &Characteristic<T>) -> Result<T, Error> {
self.iterate(|mut it| {
while let Some(att) = it.next() {
if att.handle == characteristic.handle {
Expand All @@ -422,7 +422,7 @@ impl<'d, M: RawMutex, const MAX: usize> AttributeTable<'d, M, MAX> {
} = &mut att.data
{
let value = if *variable_len { &value[..*len as usize] } else { value };
let v = <T as GattValue>::from_gatt(value).map_err(|_| Error::InvalidValue)?;
let v = T::from_gatt(value).map_err(|_| Error::InvalidValue)?;
return Ok(v);
}
}
Expand All @@ -434,7 +434,7 @@ impl<'d, M: RawMutex, const MAX: usize> AttributeTable<'d, M, MAX> {
/// Return the characteristic which corresponds to the supplied value handle
///
/// If no characteristic corresponding to the given value handle was found, returns an error
pub fn find_characteristic_by_value_handle<T: GattValue>(&self, handle: u16) -> Result<Characteristic<T>, Error> {
pub fn find_characteristic_by_value_handle<T: ToGatt>(&self, handle: u16) -> Result<Characteristic<T>, Error> {
self.iterate(|mut it| {
while let Some(att) = it.next() {
if att.handle == handle {
Expand Down Expand Up @@ -492,7 +492,7 @@ pub struct ServiceBuilder<'r, 'd, M: RawMutex, const MAX: usize> {
}

impl<'d, M: RawMutex, const MAX: usize> ServiceBuilder<'_, 'd, M, MAX> {
fn add_characteristic_internal<T: GattValue>(
fn add_characteristic_internal<T: ToGatt>(
&mut self,
uuid: Uuid,
props: CharacteristicProps,
Expand Down Expand Up @@ -547,7 +547,7 @@ impl<'d, M: RawMutex, const MAX: usize> ServiceBuilder<'_, 'd, M, MAX> {
}

/// Add a characteristic to this service with a refererence to a mutable storage buffer.
pub fn add_characteristic<T: GattValue, U: Into<Uuid>>(
pub fn add_characteristic<T: ToGatt, U: Into<Uuid>>(
&mut self,
uuid: U,
props: &[CharacteristicProp],
Expand All @@ -572,7 +572,7 @@ impl<'d, M: RawMutex, const MAX: usize> ServiceBuilder<'_, 'd, M, MAX> {
}

/// Add a characteristic to this service with a refererence to an immutable storage buffer.
pub fn add_characteristic_ro<T: GattValue, U: Into<Uuid>>(
pub fn add_characteristic_ro<T: ToGatt, U: Into<Uuid>>(
&mut self,
uuid: U,
value: &'d T,
Expand Down Expand Up @@ -611,15 +611,15 @@ impl<M: RawMutex, const MAX: usize> Drop for ServiceBuilder<'_, '_, M, MAX> {
/// A characteristic in the attribute table.
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Characteristic<T: GattValue> {
pub struct Characteristic<T: ToGatt> {
/// Handle value assigned to the Client Characteristic Configuration Descriptor (if any)
pub cccd_handle: Option<u16>,
/// Handle value assigned to this characteristic when it is added to the Gatt Attribute Table
pub handle: u16,
pub(crate) phantom: PhantomData<T>,
}

impl<T: GattValue> Characteristic<T> {
impl<T: FromGatt> Characteristic<T> {
/// Write a value to a characteristic, and notify a connection with the new value of the characteristic.
///
/// If the provided connection has not subscribed for this characteristic, it will not be notified.
Expand Down Expand Up @@ -678,12 +678,12 @@ impl<T: GattValue> Characteristic<T> {
}

/// Builder for characteristics.
pub struct CharacteristicBuilder<'r, 'd, T: GattValue, M: RawMutex, const MAX: usize> {
pub struct CharacteristicBuilder<'r, 'd, T: ToGatt, M: RawMutex, const MAX: usize> {
handle: Characteristic<T>,
table: &'r mut AttributeTable<'d, M, MAX>,
}

impl<'d, T: GattValue, M: RawMutex, const MAX: usize> CharacteristicBuilder<'_, 'd, T, M, MAX> {
impl<'d, T: ToGatt, M: RawMutex, const MAX: usize> CharacteristicBuilder<'_, 'd, T, M, MAX> {
fn add_descriptor_internal(
&mut self,
uuid: Uuid,
Expand Down
14 changes: 7 additions & 7 deletions host/src/gatt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::attribute_server::{AttributeServer, DynamicAttributeServer};
use crate::connection::Connection;
use crate::cursor::{ReadCursor, WriteCursor};
use crate::pdu::Pdu;
use crate::types::gatt_traits::{FromGattError, GattValue};
use crate::types::gatt_traits::{FromGatt, FromGattError, ToGatt};
use crate::types::l2cap::L2capHeader;
use crate::{config, BleHostError, Error, Stack};

Expand Down Expand Up @@ -193,7 +193,7 @@ impl<'stack> WriteEvent<'stack, '_> {
}

/// Characteristic data to be written
pub fn value<T: GattValue>(&self, _c: &Characteristic<T>) -> Result<T, FromGattError> {
pub fn value<T: FromGatt>(&self, _c: &Characteristic<T>) -> Result<T, FromGattError> {
T::from_gatt(self.data())
}

Expand Down Expand Up @@ -516,7 +516,7 @@ impl<'reference, C: Controller, const MAX_SERVICES: usize, const L2CAP_MTU: usiz
}

/// Discover characteristics in a given service using a UUID.
pub async fn characteristic_by_uuid<T: GattValue>(
pub async fn characteristic_by_uuid<T: ToGatt>(
&self,
service: &ServiceHandle,
uuid: &Uuid,
Expand Down Expand Up @@ -603,7 +603,7 @@ impl<'reference, C: Controller, const MAX_SERVICES: usize, const L2CAP_MTU: usiz
/// Read a characteristic described by a handle.
///
/// The number of bytes copied into the provided buffer is returned.
pub async fn read_characteristic<T: GattValue>(
pub async fn read_characteristic<T: ToGatt>(
&self,
characteristic: &Characteristic<T>,
dest: &mut [u8],
Expand Down Expand Up @@ -658,7 +658,7 @@ impl<'reference, C: Controller, const MAX_SERVICES: usize, const L2CAP_MTU: usiz
}

/// Write to a characteristic described by a handle.
pub async fn write_characteristic<T: GattValue>(
pub async fn write_characteristic<T: FromGatt>(
&self,
handle: &Characteristic<T>,
buf: &[u8],
Expand All @@ -679,7 +679,7 @@ impl<'reference, C: Controller, const MAX_SERVICES: usize, const L2CAP_MTU: usiz
/// Subscribe to indication/notification of a given Characteristic
///
/// A listener is returned, which has a `next()` method
pub async fn subscribe<T: GattValue>(
pub async fn subscribe<T: ToGatt>(
&self,
characteristic: &Characteristic<T>,
indication: bool,
Expand Down Expand Up @@ -711,7 +711,7 @@ impl<'reference, C: Controller, const MAX_SERVICES: usize, const L2CAP_MTU: usiz
}

/// Unsubscribe from a given Characteristic
pub async fn unsubscribe<T: GattValue>(
pub async fn unsubscribe<T: ToGatt>(
&self,
characteristic: &Characteristic<T>,
) -> Result<(), BleHostError<C::Error>> {
Expand Down
2 changes: 1 addition & 1 deletion host/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ pub mod prelude {
#[cfg(feature = "scan")]
pub use crate::scan::*;
#[cfg(feature = "gatt")]
pub use crate::types::gatt_traits::{FixedGattValue, GattValue};
pub use crate::types::gatt_traits::{FixedGattValue, FromGatt, ToGatt};
pub use crate::Address;
}

Expand Down
69 changes: 45 additions & 24 deletions host/src/types/gatt_traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ pub enum FromGattError {
}

/// Trait to allow conversion of a fixed size type to and from a byte slice
pub trait FixedGattValue: Sized {
#[allow(private_bounds)]
pub trait FixedGattValue: FromGatt {
/// Size of the type in bytes
const SIZE: usize;

Expand All @@ -27,29 +28,35 @@ pub trait FixedGattValue: Sized {
fn to_gatt(&self) -> &[u8];
}

/// Trait to allow conversion of a type to and from a byte slice
pub trait GattValue: Sized {
/// Trait to allow conversion of a type to gatt bytes
pub trait ToGatt: Sized {
/// The minimum size the type might be
const MIN_SIZE: usize;
/// The maximum size the type might be
const MAX_SIZE: usize;

/// Converts from gatt bytes.
/// Must return FromGattError::InvalidLength if data.len not in MIN_SIZE..=MAX_SIZE
fn from_gatt(data: &[u8]) -> Result<Self, FromGattError>;

/// Converts to gatt bytes.
/// Must return a slice of len in MIN_SIZE..=MAX_SIZE
fn to_gatt(&self) -> &[u8];
}

impl<T: FixedGattValue> GattValue for T {
const MIN_SIZE: usize = Self::SIZE;
const MAX_SIZE: usize = Self::SIZE;
/// Trait to allow conversion of gatt bytes into a type
///
/// Requires that the type implements ToGatt
pub trait FromGatt: ToGatt {
/// Converts from gatt bytes.
/// Must return FromGattError::InvalidLength if data.len not in MIN_SIZE..=MAX_SIZE
fn from_gatt(data: &[u8]) -> Result<Self, FromGattError>;
}

impl<T: FixedGattValue> FromGatt for T {
fn from_gatt(data: &[u8]) -> Result<Self, FromGattError> {
<Self as FixedGattValue>::from_gatt(data)
}
}

impl<T: FixedGattValue> ToGatt for T {
const MIN_SIZE: usize = Self::SIZE;
const MAX_SIZE: usize = Self::SIZE;

fn to_gatt(&self) -> &[u8] {
<Self as FixedGattValue>::to_gatt(self)
Expand All @@ -68,7 +75,6 @@ impl Primitive for i64 {}
impl Primitive for f32 {}
impl Primitive for f64 {}
impl Primitive for BluetoothUuid16 {} // ok as this is just a NewType(u16)
impl Primitive for &'_ str {}

impl<T: Primitive> FixedGattValue for T {
const SIZE: usize = mem::size_of::<Self>();
Expand Down Expand Up @@ -112,23 +118,22 @@ impl FixedGattValue for bool {
}
}

impl<const N: usize> GattValue for Vec<u8, N> {
const MIN_SIZE: usize = 0;
const MAX_SIZE: usize = N;

impl<const N: usize> FromGatt for Vec<u8, N> {
fn from_gatt(data: &[u8]) -> Result<Self, FromGattError> {
Self::from_slice(data).map_err(|_| FromGattError::InvalidLength)
}
}

impl<const N: usize> ToGatt for Vec<u8, N> {
const MIN_SIZE: usize = 0;
const MAX_SIZE: usize = N;

fn to_gatt(&self) -> &[u8] {
self
}
}

impl<const N: usize> GattValue for [u8; N] {
const MIN_SIZE: usize = 0;
const MAX_SIZE: usize = N;

impl<const N: usize> FromGatt for [u8; N] {
fn from_gatt(data: &[u8]) -> Result<Self, FromGattError> {
if data.len() <= Self::MAX_SIZE {
let mut actual = [0; N];
Expand All @@ -138,22 +143,38 @@ impl<const N: usize> GattValue for [u8; N] {
data.try_into().map_err(|_| FromGattError::InvalidLength)
}
}
}

impl<const N: usize> ToGatt for [u8; N] {
const MIN_SIZE: usize = 0;
const MAX_SIZE: usize = N;

fn to_gatt(&self) -> &[u8] {
self.as_slice()
}
}

impl<const N: usize> GattValue for String<N> {
const MIN_SIZE: usize = 0;
const MAX_SIZE: usize = N;

impl<const N: usize> FromGatt for String<N> {
fn from_gatt(data: &[u8]) -> Result<Self, FromGattError> {
String::from_utf8(unwrap!(Vec::from_slice(data).map_err(|_| FromGattError::InvalidLength)))
.map_err(|_| FromGattError::InvalidCharacter)
}
}

impl<const N: usize> ToGatt for String<N> {
const MIN_SIZE: usize = 0;
const MAX_SIZE: usize = N;

fn to_gatt(&self) -> &[u8] {
self.as_ref()
}
}

impl ToGatt for &'static str {
const MIN_SIZE: usize = 0;
const MAX_SIZE: usize = usize::MAX;

fn to_gatt(&self) -> &[u8] {
self.as_bytes()
}
}

0 comments on commit 23cb851

Please sign in to comment.