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

Automatically calculate attribute table size #176

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
11 changes: 4 additions & 7 deletions examples/apps/src/ble_bas_peripheral.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@ const MAX_ATTRIBUTES: usize = 10;

type Resources<C> = HostResources<C, CONNECTIONS_MAX, L2CAP_CHANNELS_MAX, L2CAP_MTU>;

const ATTRIBUTE_DATA_SIZE: usize = 10;

// GATT Server definition
#[gatt_server(attribute_data_size = ATTRIBUTE_DATA_SIZE)]
#[gatt_server]
struct Server {
battery_service: BatteryService,
}
Expand Down Expand Up @@ -116,20 +114,19 @@ async fn advertise_task<C: Controller>(
break;
}
ConnectionEvent::Gatt { event, .. } => match event {
GattEvent::Read { value_handle } => {
GattEvent::Read { value_handle } => {
if value_handle == level.handle {
let value = server.get(&level);
info!("[gatt] Read Event to Level Characteristic: {:?}", value);
}
},
}
GattEvent::Write { value_handle } => {
if value_handle == level.handle {
let value = server.get(&level);
info!("[gatt] Write Event to Level Characteristic: {:?}", value);
}
},
}
},

},
Either::Second(_) => {
tick = tick.wrapping_add(1);
Expand Down
45 changes: 27 additions & 18 deletions host-macros/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,13 @@ use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, quote_spanned};
use syn::{meta::ParseNestedMeta, parse_quote, spanned::Spanned, Expr, Result};

/// Default size for the memory block storing attribute data in bytes
const DEFAULT_ATTRIBUTE_DATA_SIZE: usize = 32;
/// MTU for a legacy BLE packet
const LEGACY_BLE_MTU: usize = 27;

#[derive(Default)]
pub(crate) struct ServerArgs {
mutex_type: Option<syn::Type>,
attribute_data_size: Option<Expr>,
attribute_table_size: Option<Expr>,
mtu: Option<Expr>,
}

Expand All @@ -33,15 +31,15 @@ impl ServerArgs {
let buffer = meta.value().map_err(|_| Error::custom("mutex_type must be followed by `= [type]`. e.g. mutex_type = NoopRawMutex".to_string()))?;
self.mutex_type = Some(buffer.parse()?);
}
"attribute_data_size" => {
let buffer = meta.value().map_err(|_| Error::custom("attribute_data_size msut be followed by `= [size]`. e.g. attribute_data_size = 32".to_string()))?;
self.attribute_data_size = Some(buffer.parse()?);
"attribute_table_size" => {
let buffer = meta.value().map_err(|_| Error::custom("attribute_table_size msut be followed by `= [size]`. e.g. attribute_table_size = 32".to_string()))?;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Typo there from older code "msut"

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good eye

self.attribute_table_size = Some(buffer.parse()?);
}
"mtu" => {
let buffer = meta.value().map_err(|_| Error::custom("mtu must be followed by `= [size]`. e.g. mtu = 27".to_string()))?;
self.mtu = Some(buffer.parse()?);
}
other => return Err(meta.error(format!("Unsupported server property: '{other}'.\nSupported properties are: mutex_type, attribute_data_size, mtu"))),
other => return Err(meta.error(format!("Unsupported server property: '{other}'.\nSupported properties are: mutex_type, attribute_table_size, mtu"))),
}
Ok(())
}
Expand All @@ -65,12 +63,6 @@ impl ServerBuilder {
let mutex_type = self.arguments.mutex_type.unwrap_or(syn::Type::Verbatim(quote!(
embassy_sync::blocking_mutex::raw::NoopRawMutex
)));
let attribute_data_size = if let Some(value) = self.arguments.attribute_data_size {
value
} else {
let tokens = quote!(#DEFAULT_ATTRIBUTE_DATA_SIZE);
parse_quote!(#tokens)
};
let mtu = if let Some(value) = self.arguments.mtu {
value
} else {
Expand All @@ -81,6 +73,7 @@ impl ServerBuilder {
let mut code_service_definition = TokenStream2::new();
let mut code_service_init = TokenStream2::new();
let mut code_server_populate = TokenStream2::new();
let mut code_attribute_summation = TokenStream2::new();
for service in &self.properties.fields {
let vis = &service.vis;
let service_span = service.span();
Expand All @@ -98,12 +91,28 @@ impl ServerBuilder {
code_server_populate.extend(quote_spanned! {service_span=>
#service_name,
});

code_attribute_summation.extend(quote_spanned! {service_span=>
+ #service_type::ATTRIBUTE_COUNT
})
}

let attribute_table_size = if let Some(value) = self.arguments.attribute_table_size {
value
} else {
parse_quote!(GAP_SERVICE_ATTRIBUTE_COUNT #code_attribute_summation)
};

quote! {
const _ATTRIBUTE_TABLE_SIZE: usize = #attribute_table_size;
// This pattern causes the assertion to happen at compile time
const _: () = {
core::assert!(_ATTRIBUTE_TABLE_SIZE >= GAP_SERVICE_ATTRIBUTE_COUNT #code_attribute_summation, "Specified attribute table size is insufficient. Please increase attribute_table_size or remove the argument entirely to allow automatic sizing of the attribute table.");
};

#visibility struct #name<'reference, 'values, C: Controller>
{
server: GattServer<'reference, 'values, C, #mutex_type, #attribute_data_size, #mtu>,
server: GattServer<'reference, 'values, C, #mutex_type, _ATTRIBUTE_TABLE_SIZE, #mtu>,
#code_service_definition
}

Expand All @@ -112,7 +121,7 @@ impl ServerBuilder {
/// Create a new Gatt Server instance.
///
/// Requires you to add your own GAP Service. Use `new_default(name)` or `new_with_config(name, gap_config)` if you want to add a GAP Service.
#visibility fn new(stack: Stack<'reference, C>, mut table: AttributeTable<'values, #mutex_type, #attribute_data_size>) -> Self {
#visibility fn new(stack: Stack<'reference, C>, mut table: AttributeTable<'values, #mutex_type, _ATTRIBUTE_TABLE_SIZE>) -> Self {

#code_service_init

Expand All @@ -127,7 +136,7 @@ impl ServerBuilder {
/// The maximum length which the name can be is 22 bytes (limited by the size of the advertising packet).
/// If a name longer than this is passed, Err() is returned.
#visibility fn new_default(stack: Stack<'reference, C>, name: &'values str) -> Result<Self, &'static str> {
let mut table: AttributeTable<'_, #mutex_type, #attribute_data_size> = AttributeTable::new();
let mut table: AttributeTable<'_, #mutex_type, _ATTRIBUTE_TABLE_SIZE> = AttributeTable::new();

GapConfig::default(name).build(&mut table)?;

Expand All @@ -145,7 +154,7 @@ impl ServerBuilder {
/// The maximum length which the device name can be is 22 bytes (limited by the size of the advertising packet).
/// If a name longer than this is passed, Err() is returned.
#visibility fn new_with_config(stack: Stack<'reference, C>, gap: GapConfig<'values>) -> Result<Self, &'static str> {
let mut table: AttributeTable<'_, #mutex_type, #attribute_data_size> = AttributeTable::new();
let mut table: AttributeTable<'_, #mutex_type, _ATTRIBUTE_TABLE_SIZE> = AttributeTable::new();

gap.build(&mut table)?;

Expand All @@ -168,7 +177,7 @@ impl ServerBuilder {

impl<'reference, 'values, C: Controller> core::ops::Deref for #name<'reference, 'values, C>
{
type Target = GattServer<'reference, 'values, C, #mutex_type, #attribute_data_size, #mtu>;
type Target = GattServer<'reference, 'values, C, #mutex_type, _ATTRIBUTE_TABLE_SIZE, #mtu>;

fn deref(&self) -> &Self::Target {
&self.server
Expand Down
13 changes: 13 additions & 0 deletions host-macros/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ impl syn::parse::Parse for ServiceArgs {
pub(crate) struct ServiceBuilder {
properties: syn::ItemStruct,
args: ServiceArgs,
attribute_count: usize,
code_impl: TokenStream2,
code_build_chars: TokenStream2,
code_struct_init: TokenStream2,
Expand All @@ -75,6 +76,7 @@ impl ServiceBuilder {
Self {
properties,
args,
attribute_count: 1, // Service counts as an attribute
code_struct_init: TokenStream2::new(),
code_impl: TokenStream2::new(),
code_fields: TokenStream2::new(),
Expand All @@ -91,6 +93,7 @@ impl ServiceBuilder {
let fields = self.code_fields;
let code_build_chars = self.code_build_chars;
let uuid = self.args.uuid;
let attribute_count = self.attribute_count;
let read_callback = self
.args
.on_read
Expand All @@ -104,6 +107,8 @@ impl ServiceBuilder {

#[allow(unused)]
impl #struct_name {
const ATTRIBUTE_COUNT: usize = #attribute_count;

#visibility fn new<M, const MAX_ATTRIBUTES: usize>(table: &mut AttributeTable<'_, M, MAX_ATTRIBUTES>) -> Self
where
M: embassy_sync::blocking_mutex::raw::RawMutex,
Expand Down Expand Up @@ -152,6 +157,7 @@ impl ServiceBuilder {
#write_callback

// TODO: Descriptors
// NOTE: Descriptors are attributes too - will need to increment self.attribute_count

builder.build()
};
Expand Down Expand Up @@ -193,6 +199,13 @@ impl ServiceBuilder {
mutability: syn::FieldMutability::None,
});

// At least two attributes will be added to the attribute table for each characteristic:
// - The characteristic declaration
// - The characteristic's value declaration
//
// If the characteristic has either the notify or indicate property, a Client Characteristic Configuration Descriptor (CCCD) declaration will also be added.
self.attribute_count += if ch.args.notify || ch.args.indicate { 3 } else { 2 };

self.construct_characteristic_static(ch);
}

Expand Down
9 changes: 9 additions & 0 deletions host/src/gap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ const APPEARANCE_UUID: u16 = 0x2a01;
/// Advertising packet is limited to 31 bytes. 9 of these are used by other GAP data, leaving 22 bytes for the Device Name characteristic
const DEVICE_NAME_MAX_LENGTH: usize = 22;

/// The number of attributes added by the GAP and GATT services
/// GAP_SERVICE: 1
/// ├── DEVICE_NAME: 2
/// └── APPEARANCE: 2
/// GATT_SERVICE: + 1
/// ---
/// = 6
pub const GAP_SERVICE_ATTRIBUTE_COUNT: usize = 6;

pub mod appearance {
//! The representation of the external appearance of the device.
//!
Expand Down
2 changes: 1 addition & 1 deletion host/tests/gatt_derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const VALUE_UUID: Uuid = Uuid::new_long([
0x00, 0x00, 0x10, 0x01, 0xb0, 0xcd, 0x11, 0xec, 0x87, 0x1f, 0xd4, 0x5d, 0xdf, 0x13, 0x88, 0x40,
]);

#[gatt_server(mutex_type = NoopRawMutex, attribute_data_size = 10, mtu = 27)]
#[gatt_server(mutex_type = NoopRawMutex, attribute_table_size = 10, mtu = 27)]
struct Server {
service: CustomService,
}
Expand Down
Loading