Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Work on the Attribute module #99

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -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
nm17 marked this conversation as resolved.
Show resolved Hide resolved

# 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

123 changes: 102 additions & 21 deletions host/src/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,47 +22,90 @@ 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
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to propose this syntax for referencing Bluetooth core specification. This should help other devs cross reference things.

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>,
/// 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<Attribute<'a>> = None;
impl<'d> Attribute<'d> {
const EMPTY: Option<Attribute<'d>> = 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,
Expand Down Expand Up @@ -94,6 +137,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<usize, AttErrorCode> {
if !self.readable() {
return Err(AttErrorCode::ReadNotPermitted);
Expand Down Expand Up @@ -176,6 +227,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();

Expand Down Expand Up @@ -204,8 +258,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;
nm17 marked this conversation as resolved.
Show resolved Hide resolved
Ok(())
}
_ => Err(AttErrorCode::WriteNotPermitted),
Expand Down Expand Up @@ -238,18 +292,28 @@ 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<M, RefCell<InnerTable<'d, MAX>>>,
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
#[derive(Debug, PartialEq, Eq)]
pub struct InnerTable<'d, const MAX: usize> {
attributes: [Option<Attribute<'d>>; MAX],

/// Amount of attributes in the list.
len: usize,
}

Expand All @@ -270,15 +334,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<F: Fn(&mut InnerTable<'d, MAX>)>(&self, f: F) {
self.inner.lock(|inner| {
Expand All @@ -300,20 +366,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,
Expand Down Expand Up @@ -348,7 +420,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.
///
Expand Down Expand Up @@ -397,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,
}
Expand All @@ -408,6 +480,7 @@ impl From<u16> 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,
Expand All @@ -422,8 +495,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,
Expand Down Expand Up @@ -487,15 +560,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;
}
});

// 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));
}
}

Expand Down Expand Up @@ -530,6 +603,10 @@ impl<'a, 'd> AttributeIterator<'a, 'd> {
}
}

/// Service information.
///
/// Currently only has UUID.
#[derive(Clone, Debug)]
pub struct Service {
pub uuid: Uuid,
}
Expand All @@ -540,7 +617,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 {
Expand Down
2 changes: 1 addition & 1 deletion host/src/types/uuid.rs
Original file line number Diff line number Diff line change
@@ -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)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather keep the uuid not Copy if possible, as it can easily explode memory usage if copied uncritically throughout the usage.

pub enum Uuid {
Uuid16([u8; 2]),
Uuid128([u8; 16]),
Expand Down
Loading