From 203c9ad4d425eecb365e42efa48bc6d91bed8c62 Mon Sep 17 00:00:00 2001 From: one-d-wide Date: Mon, 4 Mar 2024 17:16:44 +0000 Subject: [PATCH 1/2] Add support for characteristic descriptors --- bleps-macros/src/lib.rs | 235 +++++++++++++++++++++++++------ bleps-macros/tests/macro_test.rs | 36 +++++ bleps/src/att.rs | 4 +- 3 files changed, 228 insertions(+), 47 deletions(-) diff --git a/bleps-macros/src/lib.rs b/bleps-macros/src/lib.rs index b55854d..8ab8703 100644 --- a/bleps-macros/src/lib.rs +++ b/bleps-macros/src/lib.rs @@ -15,6 +15,12 @@ use syn::{parse_macro_input, Expr, Lit, Member, Path}; /// write: my_write_function, /// name: "characteristic1", /// description: "Characteristic accessible via functions", +/// descriptors: [ +/// descriptor { +/// uuid: "dfe57a9f-4495-45ba-9e02-df1a010b618c", +/// read: my_descriptor_read_function, +/// }, +/// ], /// }, /// characteristic { /// uuid: "96c05dff-2ff0-4080-ab41-f4d24bc6da85", @@ -25,6 +31,8 @@ use syn::{parse_macro_input, Expr, Lit, Member, Path}; /// ], /// }, /// ]); +/// +/// let notification_handle = characteristic1_handle; /// ``` /// #[proc_macro] @@ -156,6 +164,78 @@ pub fn gatt(input: TokenStream) -> TokenStream { return quote!{ compile_error!("Unexpected"); }.into(); } } + "descriptors" => { + if let Expr::Array(descriptors) = field.expr { + for descriptor in descriptors.elems { + if let Expr::Struct(s) = descriptor { + if path_to_string(s.path) + != "descriptor" + { + return quote! { compile_error!("Unexpected"); }.into(); + } + + let mut desc = + Descriptor::default(); + + for field in s.fields { + let name = + if let Member::Named(name) = + field.member + { + name.to_string() + } else { + return quote! { compile_error!("Unexpected"); }.into(); + }; + + match name.as_str() { + "uuid" => { + if let Expr::Lit(value) = field.expr { + if let Lit::Str(s) = value.lit { + desc.uuid = s.value(); + } else { + return quote!{ compile_error!("Unexpected"); }.into(); + } + } else { + return quote!{ compile_error!("Unexpected"); }.into(); + } + } + "read" => { + if let Expr::Path(p) = field.expr { + let name = path_to_string(p.path); + desc.read = Some(name); + } else { + return quote!{ compile_error!("Unexpected"); }.into(); + } + } + "write" => { + if let Expr::Path(p) = field.expr { + let name = path_to_string(p.path); + desc.write = Some(name); + } else { + return quote!{ compile_error!("Unexpected"); }.into(); + } + } + "value" => { + if let Expr::Path(p) = field.expr { + let name = path_to_string(p.path); + desc.value = Some(name); + } else { + return quote!{ compile_error!("Unexpected"); }.into(); + } + } + _ => { + return quote! { compile_error!("Unexpected"); } + .into() + } + } + } + charact.descriptors.push(desc); + } + } + } else { + return quote!{ compile_error!("Unexpected"); }.into(); + } + } _ => { return quote! { compile_error!("Unexpected"); } .into() @@ -186,7 +266,9 @@ pub fn gatt(input: TokenStream) -> TokenStream { let mut attribs: Vec<_> = Vec::new(); let mut post: Vec<_> = Vec::new(); let mut pre: Vec<_> = Vec::new(); - let mut current_handle: usize = 0; + // Keep handle value for next available attribute + let mut current_handle: u16 = 1; + for (i, service) in services.iter().enumerate() { let uuid_bytes = uuid_to_bytes(&service.uuid); let uuid_ident = format_ident!("_uuid{}", i); @@ -238,7 +320,16 @@ pub fn gatt(input: TokenStream) -> TokenStream { let gen_attr_att_data_ident = format_ident!("_gen_attr_att_data{}{}", i, j); decls.push( - if characteristic.read.is_some() || characteristic.write.is_some() { + if (characteristic.read.is_some() || characteristic.write.is_some()) as u32 + + characteristic.value.is_some() as u32 + + characteristic.data.is_some() as u32 + > 1 + { + return quote! { compile_error!( + "Characteristic data fields duplicated: 'read'/'write' or 'value' or 'data'" + ); } + .into(); + } else if characteristic.read.is_some() || characteristic.write.is_some() { let rfunction = if let Some(name) = &characteristic.read { let fname = format_ident!("{}", name); quote!(&mut #fname) @@ -256,14 +347,15 @@ pub fn gatt(input: TokenStream) -> TokenStream { quote!(let mut #gen_attr_att_data_ident = (#rfunction, #wfunction);) } else if let Some(name) = &characteristic.value { let vname = format_ident!("{}", name); - quote!(let mut #gen_attr_att_data_ident = (#vname,);) + quote!(let mut #gen_attr_att_data_ident = #vname;) } else if let Some(name) = &characteristic.data { let dname = format_ident!("{}", name); - quote!(let mut #gen_attr_att_data_ident = (#dname,);) + quote!(let mut #gen_attr_att_data_ident = #dname;) } else { - quote!(compile_error!( + return quote! { compile_error!( "Characteristic data fields missing: 'read'/'write' nor 'value' nor 'data'" - )) + ); } + .into(); }, ); @@ -278,29 +370,17 @@ pub fn gatt(input: TokenStream) -> TokenStream { quote!(let #gen_attr_ident = Attribute::new(#gen_attr_att_uuid, &mut #gen_attr_att_data_ident);) ); attribs.push(quote!(#gen_attr_ident)); - current_handle += 1; - if characteristic.description.is_some() { - let mut char_user_description_data: Vec = Vec::new(); - char_user_description_data - .extend(characteristic.description.as_ref().unwrap().bytes()); - let char_user_description_data_ident = - format_ident!("_char_user_description_data{}{}", i, j); - decls.push(quote!(let #char_user_description_data_ident = [ #(#char_user_description_data),* ] ;)); - - let char_user_description_data_attr = - format_ident!("_char_user_description_data_attr{}{}", i, j); - decls.push(quote!(let mut #char_user_description_data_attr = &#char_user_description_data_ident;)); - - let char_user_description_data_attribute = - format_ident!("_char_user_description_data_attribute{}{}", i, j); - decls.push( - quote!(let #char_user_description_data_attribute = Attribute::new(Uuid::Uuid16(0x2901), &mut #char_user_description_data_attr);) - ); - attribs.push(quote!(#char_user_description_data_attribute)); - current_handle += 1; + if let Some(name) = &characteristic.name { + let char_handle_ident = format_ident!("{}_handle", name); + post.push(quote!(let #char_handle_ident = #current_handle;)); } + current_handle += 1; + + // If "Client Characteristic Configuration" Descriptor is present, + // It must always be the first one after the characteristic attribute. + // This assumption arises in `../../bleps/src/async_attribute_server.rs` if characteristic.notify { let mut ccd_data: Vec = Vec::new(); ccd_data.extend(&[0u8, 0u8]); @@ -352,32 +432,88 @@ pub fn gatt(input: TokenStream) -> TokenStream { quote!(let #char_ccd_data_attribute = Attribute::new(Uuid::Uuid16(0x2902), &mut #char_ccd_data_attr);) ); attribs.push(quote!(#char_ccd_data_attribute)); + + if let Some(name) = &characteristic.name { + let char_notify_enable_handle_ident = + format_ident!("{}_notify_enable_handle", name); + post.push(quote!(let #char_notify_enable_handle_ident = #current_handle;)); + } + current_handle += 1; } - if let Some(name) = &characteristic.name { - let char_data_handle = (current_handle - - if characteristic.notify { 1 } else { 0 } - - if characteristic.description.is_some() { - 1 + if characteristic.description.is_some() { + let mut char_user_description_data: Vec = Vec::new(); + char_user_description_data + .extend(characteristic.description.as_ref().unwrap().bytes()); + let char_user_description_data_ident = + format_ident!("_char_user_description_data{}{}", i, j); + decls.push(quote!(let #char_user_description_data_ident = [ #(#char_user_description_data),* ] ;)); + + let char_user_description_data_attr = + format_ident!("_char_user_description_data_attr{}{}", i, j); + decls.push(quote!(let mut #char_user_description_data_attr = &#char_user_description_data_ident;)); + + let char_user_description_data_attribute = + format_ident!("_char_user_description_data_attribute{}{}", i, j); + decls.push( + quote!(let #char_user_description_data_attribute = Attribute::new(Uuid::Uuid16(0x2901), &mut #char_user_description_data_attr);) + ); + attribs.push(quote!(#char_user_description_data_attribute)); + current_handle += 1; + } + + for (k, descriptor) in characteristic.descriptors.iter().enumerate() { + let uuid_bytes = uuid_to_bytes(&descriptor.uuid); + let descriptor_uuid = if uuid_bytes.len() == 2 { + quote!(Uuid::Uuid16(u16::from_le_bytes([ #(#uuid_bytes),* ]))) + } else { + quote!(Uuid::Uuid128([ #(#uuid_bytes),* ])) + }; + + let char_desc_attribute = format_ident!("_char_desc_attr{}{}{}", i, j, k); + let char_desc_data_ident = format_ident!("_char_desc_data{}{}{}", i, j, k); + + if (descriptor.read.is_some() || descriptor.write.is_some()) as u32 + + descriptor.value.is_some() as u32 + > 1 + { + return quote! { compile_error!( + "Descriptor data fields duplicated: 'read'/'write' or 'value'" + ); } + .into(); + } else if descriptor.read.is_some() || descriptor.write.is_some() { + let rfunction = if let Some(name) = &descriptor.read { + let fname = format_ident!("{}", name); + quote!(&mut #fname) } else { - 0 - }) as u16; + quote!(()) + }; - let char_handle_name = format_ident!("{}_handle", name); - post.push(quote!(let #char_handle_name = #char_data_handle;)); + let wfunction = if let Some(name) = &descriptor.write { + let fname = format_ident!("{}", name); + quote!(&mut #fname) + } else { + quote!(()) + }; - if characteristic.notify { - let char_notify_enable_handle_name = - format_ident!("{}_notify_enable_handle", name); - let handle = char_data_handle - + if characteristic.description.is_some() { - 1 - } else { - 0 - }; - post.push(quote!(let #char_notify_enable_handle_name = #handle;)); + decls.push(quote!(let mut #char_desc_data_ident = (#rfunction, #wfunction);)); + } else if let Some(name) = &descriptor.value { + let vname = format_ident!("{}", name); + decls.push(quote!(let mut #char_desc_data_ident = #vname;)); + } else { + return quote! { compile_error!( + "Descriptor data fields missing: 'read'/'write' nor 'value'" + ); } + .into(); } + + decls.push( + quote!(let #char_desc_attribute = Attribute::new(#descriptor_uuid, &mut #char_desc_data_ident);) + ); + attribs.push(quote!(#char_desc_attribute)); + + current_handle += 1; } } } @@ -437,4 +573,13 @@ struct Characteristic { description: Option, notify: bool, name: Option, + descriptors: Vec, +} + +#[derive(Debug, Default)] +struct Descriptor { + uuid: String, + read: Option, + write: Option, + value: Option, } diff --git a/bleps-macros/tests/macro_test.rs b/bleps-macros/tests/macro_test.rs index 4679af1..4234bad 100644 --- a/bleps-macros/tests/macro_test.rs +++ b/bleps-macros/tests/macro_test.rs @@ -94,3 +94,39 @@ fn test4() { println!("{}", my_characteristic_handle); println!("{}", my_characteristic_notify_enable_handle); } + +#[test] +fn test5() { + let char_value = b"Hello!"; + + let mut my_read_function = |_offset: usize, data: &mut [u8]| { + data[..5].copy_from_slice(&b"Hola!"[..]); + 5 + }; + let mut my_write_function = |_offset, data: &[u8]| { + println!("{:?}", data); + }; + + let desc_value = b"Hallo!"; + + gatt!([service { + uuid: "9e7312e0-2354-11eb-9f10-fbc30a62cf38", + characteristics: [characteristic { + uuid: "9e7312e0-2354-11eb-9f10-fbc30a62cf38", + value: char_value, + descriptors: [ + descriptor { + uuid: "9e7312e0-0001-11eb-9f10-fbc30a62cf38", + read: my_read_function, + write: my_write_function, + }, + descriptor { + uuid: "9e7312e0-0002-11eb-9f10-fbc30a62cf38", + value: desc_value, + }, + ], + },], + },]); + + println!("{:x?}", gatt_attributes); +} diff --git a/bleps/src/att.rs b/bleps/src/att.rs index 3827ee8..630927b 100644 --- a/bleps/src/att.rs +++ b/bleps/src/att.rs @@ -200,7 +200,7 @@ impl Att { let group_type = if payload.len() == 6 { Uuid::Uuid16((payload[4] as u16) + ((payload[5] as u16) << 8)) } else if payload.len() == 20 { - let uuid = payload[4..21] + let uuid = payload[4..20] .try_into() .map_err(|_| AttDecodeError::Other)?; Uuid::Uuid128(uuid) @@ -221,7 +221,7 @@ impl Att { let attribute_type = if payload.len() == 6 { Uuid::Uuid16((payload[4] as u16) + ((payload[5] as u16) << 8)) } else if payload.len() == 20 { - let uuid = payload[4..21] + let uuid = payload[4..20] .try_into() .map_err(|_| AttDecodeError::Other)?; Uuid::Uuid128(uuid) From 8d02d4e549bbd1815746af345bb525f78ad48eda Mon Sep 17 00:00:00 2001 From: one-d-wide Date: Tue, 5 Mar 2024 16:03:51 +0000 Subject: [PATCH 2/2] Fix inconsistent attribute indexing in `get_characteristic_value` --- bleps/src/async_attribute_server.rs | 2 +- bleps/src/attribute_server.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bleps/src/async_attribute_server.rs b/bleps/src/async_attribute_server.rs index f5e2f70..272ca71 100644 --- a/bleps/src/async_attribute_server.rs +++ b/bleps/src/async_attribute_server.rs @@ -144,7 +144,7 @@ where { let mut cccd = [0u8; 1]; let cccd_len = - self.get_characteristic_value((idx + 1) as u16, 0, &mut cccd[..]); + self.get_characteristic_value((idx + 2) as u16, 0, &mut cccd[..]); if let Some(1) = cccd_len { cccd[0] == 1 } else { diff --git a/bleps/src/attribute_server.rs b/bleps/src/attribute_server.rs index ae33444..ccfc097 100644 --- a/bleps/src/attribute_server.rs +++ b/bleps/src/attribute_server.rs @@ -95,7 +95,7 @@ bleps_dedup::dedup! { offset: u16, buffer: &mut [u8], ) -> Option { - let att = &mut self.attributes[handle as usize]; + let att = &mut self.attributes[(handle as usize) - 1]; if att.data.readable() { att.data.read(offset as usize, buffer).ok()