From 5f651f57f6d5d04e82a7733fe6b7b2b4fbbff15c Mon Sep 17 00:00:00 2001 From: alexmoon Date: Fri, 22 Apr 2022 11:38:18 -0400 Subject: Add service builder --- nrf-softdevice/src/ble/gatt_server.rs | 33 +++ nrf-softdevice/src/ble/gatt_server/builder.rs | 208 ++++++++++++++++ .../src/ble/gatt_server/characteristic.rs | 264 +++++++++++++++++++++ nrf-softdevice/src/ble/types.rs | 49 ++++ 4 files changed, 554 insertions(+) create mode 100644 nrf-softdevice/src/ble/gatt_server/builder.rs create mode 100644 nrf-softdevice/src/ble/gatt_server/characteristic.rs diff --git a/nrf-softdevice/src/ble/gatt_server.rs b/nrf-softdevice/src/ble/gatt_server.rs index 99d22dd..494b540 100644 --- a/nrf-softdevice/src/ble/gatt_server.rs +++ b/nrf-softdevice/src/ble/gatt_server.rs @@ -11,6 +11,9 @@ use crate::util::{get_flexarray, get_union_field, Portal}; use crate::RawError; use crate::Softdevice; +pub mod builder; +pub mod characteristic; + pub struct Characteristic { pub uuid: Uuid, pub can_read: bool, @@ -29,6 +32,36 @@ pub struct CharacteristicHandles { pub sccd_handle: u16, } +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ServiceHandle(u16); + +impl ServiceHandle { + pub fn handle(&self) -> u16 { + self.0 + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct IncludedServiceHandle(u16); + +impl IncludedServiceHandle { + pub fn handle(&self) -> u16 { + self.0 + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DescriptorHandle(u16); + +impl DescriptorHandle { + pub fn handle(&self) -> u16 { + self.0 + } +} + pub trait Server: Sized { type Event; fn register(sd: &Softdevice) -> Result; diff --git a/nrf-softdevice/src/ble/gatt_server/builder.rs b/nrf-softdevice/src/ble/gatt_server/builder.rs new file mode 100644 index 0000000..c0bff21 --- /dev/null +++ b/nrf-softdevice/src/ble/gatt_server/builder.rs @@ -0,0 +1,208 @@ +#![allow(dead_code)] + +use core::{convert::TryInto, marker::PhantomData, mem, ptr::null}; + +#[cfg(feature = "alloc")] +extern crate alloc; + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +use crate::{ble::Uuid, raw, RawError, Softdevice}; + +use super::{ + characteristic::{self, AttributeMetadata}, + CharacteristicHandles, DescriptorHandle, IncludedServiceHandle, RegisterError, ServiceHandle, +}; + +pub struct ServiceBuilder<'a> { + handle: u16, + sd: PhantomData<&'a mut Softdevice>, +} + +pub struct CharacteristicBuilder<'a> { + handles: CharacteristicHandles, + sb: PhantomData<&'a ServiceBuilder<'a>>, +} + +impl<'a> ServiceBuilder<'a> { + pub fn new(_sd: &'a mut Softdevice, uuid: Uuid) -> Result { + let mut service_handle: u16 = 0; + let ret = unsafe { + raw::sd_ble_gatts_service_add( + raw::BLE_GATTS_SRVC_TYPE_PRIMARY as u8, + uuid.as_raw_ptr(), + &mut service_handle as _, + ) + }; + RawError::convert(ret)?; + + Ok(ServiceBuilder { + handle: service_handle, + sd: PhantomData, + }) + } + + pub fn add_characteristic>( + &mut self, + uuid: Uuid, + attr: characteristic::Attribute, + md: characteristic::Metadata, + ) -> Result, RegisterError> { + let value = attr.value.as_ref(); + let attr_md = attr.metadata.into_raw(); + self.add_characteristic_inner(uuid, value, attr.max_len, &attr_md, md) + } + + #[cfg(feature = "alloc")] + pub fn add_characteristic_app( + &mut self, + uuid: Uuid, + attr: characteristic::Attribute>, + md: characteristic::Metadata, + ) -> Result, RegisterError> { + let value = Box::leak(attr.value); + let attr_md = attr.metadata.into_raw_user(); + self.add_characteristic_inner(uuid, value, attr.max_len, &attr_md, md) + } + + fn add_characteristic_inner( + &mut self, + uuid: Uuid, + value: &[u8], + max_len: u16, + attr_md: &raw::ble_gatts_attr_md_t, + char_md: characteristic::Metadata, + ) -> Result, RegisterError> { + assert!(value.len() <= usize::from(max_len)); + assert!(char_md + .user_description + .map_or(true, |x| x.value.len() <= usize::from(x.max_len))); + + let (char_props, char_ext_props) = char_md.properties.into_raw(); + let user_desc_md = char_md + .user_description + .and_then(|x| x.metadata.map(AttributeMetadata::into_raw)); + let cccd_md = char_md.cccd.map(AttributeMetadata::into_raw); + let sccd_md = char_md.sccd.map(AttributeMetadata::into_raw); + + let mut char_md = raw::ble_gatts_char_md_t { + char_props, + char_ext_props, + p_char_user_desc: char_md + .user_description + .map_or(null(), |x| x.value.as_ptr()), + char_user_desc_max_size: char_md.user_description.map_or(0, |x| x.max_len), + char_user_desc_size: char_md.user_description.map_or(0, |x| x.value.len() as u16), + p_char_pf: null(), + p_user_desc_md: user_desc_md.as_ref().map_or(null(), |x| x as _), + p_cccd_md: cccd_md.as_ref().map_or(null(), |x| x as _), + p_sccd_md: sccd_md.as_ref().map_or(null(), |x| x as _), + }; + + let mut attr = raw::ble_gatts_attr_t { + p_uuid: unsafe { uuid.as_raw_ptr() }, + p_attr_md: attr_md as _, + init_len: unwrap!(value.len().try_into()), + init_offs: 0, + max_len, + p_value: value.as_ptr() as *mut _, + }; + + let mut handles: raw::ble_gatts_char_handles_t = unsafe { mem::zeroed() }; + + let ret = unsafe { + raw::sd_ble_gatts_characteristic_add( + self.handle, + &mut char_md as _, + &mut attr as _, + &mut handles as _, + ) + }; + RawError::convert(ret)?; + + let handles = CharacteristicHandles { + value_handle: handles.value_handle, + user_desc_handle: handles.user_desc_handle, + cccd_handle: handles.cccd_handle, + sccd_handle: handles.sccd_handle, + }; + + Ok(CharacteristicBuilder { + handles, + sb: PhantomData, + }) + } + + pub fn include_service( + &mut self, + service: &ServiceHandle, + ) -> Result { + let mut handle = 0; + let ret = + unsafe { raw::sd_ble_gatts_include_add(self.handle, service.0, &mut handle as _) }; + RawError::convert(ret)?; + + Ok(IncludedServiceHandle(handle)) + } + + pub fn build(self) -> ServiceHandle { + ServiceHandle(self.handle) + } +} + +impl<'a> CharacteristicBuilder<'a> { + pub fn add_descriptor>( + &mut self, + uuid: Uuid, + attr: characteristic::Attribute, + ) -> Result { + let value = attr.value.as_ref(); + let attr_md = attr.metadata.into_raw(); + self.add_descriptor_inner(uuid, value, attr.max_len, &attr_md) + } + + #[cfg(feature = "alloc")] + pub fn add_descriptor_app( + &mut self, + uuid: Uuid, + attr: characteristic::Attribute>, + ) -> Result { + let value = Box::leak(attr.value); + let attr_md = attr.metadata.into_raw_user(); + self.add_descriptor_inner(uuid, value, attr.max_len, &attr_md) + } + + fn add_descriptor_inner( + &mut self, + uuid: Uuid, + value: &[u8], + max_len: u16, + attr_md: &raw::ble_gatts_attr_md_t, + ) -> Result { + let attr = raw::ble_gatts_attr_t { + p_uuid: unsafe { uuid.as_raw_ptr() }, + p_attr_md: attr_md as _, + init_len: unwrap!(value.len().try_into()), + init_offs: 0, + max_len, + p_value: value.as_ptr() as *mut _, + }; + + let mut handle = 0; + let ret = unsafe { + raw::sd_ble_gatts_descriptor_add( + self.handles.value_handle, + &attr as _, + &mut handle as _, + ) + }; + RawError::convert(ret)?; + + Ok(DescriptorHandle(handle)) + } + + pub fn build(self) -> CharacteristicHandles { + self.handles + } +} diff --git a/nrf-softdevice/src/ble/gatt_server/characteristic.rs b/nrf-softdevice/src/ble/gatt_server/characteristic.rs new file mode 100644 index 0000000..6bc33c2 --- /dev/null +++ b/nrf-softdevice/src/ble/gatt_server/characteristic.rs @@ -0,0 +1,264 @@ +use core::convert::TryInto; + +use crate::ble::SecurityMode; +use crate::raw; + +// Missing: +// - Characteristic presentation format + +#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AttributeMetadata { + pub read: SecurityMode, + pub write: SecurityMode, + pub variable_len: bool, + pub deferred_read: bool, + pub deferred_write: bool, +} + +impl AttributeMetadata { + pub fn with_security(security: SecurityMode) -> Self { + AttributeMetadata { + read: security, + write: security, + ..Default::default() + } + } + + pub fn read_security(mut self, security: SecurityMode) -> Self { + self.read = security; + self + } + + pub fn write_security(mut self, security: SecurityMode) -> Self { + self.write = security; + self + } + + pub(crate) fn into_raw(self) -> raw::ble_gatts_attr_md_t { + self.into_raw_inner(raw::BLE_GATTS_VLOC_STACK as u8) + } + + #[cfg(feature = "alloc")] + pub(crate) fn into_raw_user(self) -> raw::ble_gatts_attr_md_t { + self.into_raw_inner(raw::BLE_GATTS_VLOC_USER as u8) + } + + fn into_raw_inner(self, vloc: u8) -> raw::ble_gatts_attr_md_t { + raw::ble_gatts_attr_md_t { + read_perm: self.read.into_raw(), + write_perm: self.write.into_raw(), + _bitfield_1: raw::ble_gatts_attr_md_t::new_bitfield_1( + self.variable_len.into(), + vloc, + self.deferred_read.into(), + self.deferred_write.into(), + ), + } + } +} + +#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Attribute> { + pub metadata: AttributeMetadata, + pub value: T, + pub max_len: u16, +} + +impl> Attribute { + pub fn new(value: T) -> Self { + let max_len = unwrap!(value.as_ref().len().try_into()); + Attribute { + max_len, + value, + metadata: Default::default(), + } + } + + pub fn security(mut self, security: SecurityMode) -> Self { + self.metadata.read = security; + self.metadata.write = security; + self + } + + pub fn read_security(mut self, security: SecurityMode) -> Self { + self.metadata.read = security; + self + } + + pub fn write_security(mut self, security: SecurityMode) -> Self { + self.metadata.write = security; + self + } + + pub fn variable_len(mut self, max_len: u16) -> Self { + self.max_len = max_len; + self.metadata.variable_len = true; + self + } + + pub fn deferred(mut self) -> Self { + self.metadata.deferred_read = true; + self.metadata.deferred_write = true; + self + } + + pub fn deferred_read(mut self) -> Self { + self.metadata.deferred_read = true; + self + } + + pub fn deferred_write(mut self) -> Self { + self.metadata.deferred_write = true; + self + } +} + +#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct UserDescription { + pub metadata: Option, + pub value: &'static [u8], + pub max_len: u16, +} + +#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Properties { + pub broadcast: bool, + pub read: bool, + pub write_without_response: bool, + pub write: bool, + pub notify: bool, + pub indicate: bool, + pub signed_write: bool, + pub queued_write: bool, + pub write_user_description: bool, +} + +impl Properties { + pub fn new() -> Self { + Self::default() + } + + pub fn broadcast(mut self) -> Self { + self.broadcast = true; + self + } + + pub fn read(mut self) -> Self { + self.read = true; + self + } + + pub fn write_without_response(mut self) -> Self { + self.write_without_response = true; + self + } + + pub fn write(mut self) -> Self { + self.write = true; + self + } + + pub fn notify(mut self) -> Self { + self.notify = true; + self + } + + pub fn indicate(mut self) -> Self { + self.indicate = true; + self + } + + pub fn signed_write(mut self) -> Self { + self.signed_write = true; + self + } + + pub fn queued_write(mut self) -> Self { + self.queued_write = true; + self + } + + pub fn write_user_description(mut self) -> Self { + self.write_user_description = true; + self + } + + pub(crate) fn into_raw(self) -> (raw::ble_gatt_char_props_t, raw::ble_gatt_char_ext_props_t) { + ( + raw::ble_gatt_char_props_t { + _bitfield_1: raw::ble_gatt_char_props_t::new_bitfield_1( + self.broadcast.into(), + self.read.into(), + self.write_without_response.into(), + self.write.into(), + self.notify.into(), + self.indicate.into(), + self.signed_write.into(), + ), + }, + raw::ble_gatt_char_ext_props_t { + _bitfield_1: raw::ble_gatt_char_ext_props_t::new_bitfield_1( + self.queued_write.into(), + self.write_user_description.into(), + ), + }, + ) + } +} + +#[derive(Default, Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Metadata { + pub properties: Properties, + pub user_description: Option, + pub cccd: Option, + pub sccd: Option, +} + +impl Metadata { + pub fn new(properties: Properties) -> Self { + let cccd = if properties.indicate || properties.notify { + Some(AttributeMetadata::default()) + } else { + None + }; + + let sccd = if properties.broadcast { + Some(AttributeMetadata::default()) + } else { + None + }; + + Metadata { + properties, + cccd, + sccd, + ..Default::default() + } + } + + pub fn with_security(properties: Properties, write_security: SecurityMode) -> Self { + let cccd = if properties.indicate || properties.notify { + Some(AttributeMetadata::default().write_security(write_security)) + } else { + None + }; + + let sccd = if properties.broadcast { + Some(AttributeMetadata::default().write_security(write_security)) + } else { + None + }; + + Metadata { + properties, + cccd, + sccd, + ..Default::default() + } + } +} diff --git a/nrf-softdevice/src/ble/types.rs b/nrf-softdevice/src/ble/types.rs index 7763ebc..6011808 100644 --- a/nrf-softdevice/src/ble/types.rs +++ b/nrf-softdevice/src/ble/types.rs @@ -84,6 +84,55 @@ impl Role { } } +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SecurityMode { + NoAccess, + Open, + JustWorks, + Mitm, + LescMitm, + Signed, + SignedMitm, +} + +impl Default for SecurityMode { + fn default() -> Self { + Self::Open + } +} + +impl SecurityMode { + pub fn try_from_raw(raw: raw::ble_gap_conn_sec_mode_t) -> Option { + match (raw.sm(), raw.lv()) { + (0, 0) => Some(SecurityMode::NoAccess), + (1, 1) => Some(SecurityMode::Open), + (1, 2) => Some(SecurityMode::JustWorks), + (1, 3) => Some(SecurityMode::Mitm), + (1, 4) => Some(SecurityMode::LescMitm), + (2, 1) => Some(SecurityMode::Signed), + (2, 2) => Some(SecurityMode::SignedMitm), + _ => None, + } + } + + pub fn into_raw(self) -> raw::ble_gap_conn_sec_mode_t { + let (sm, lv) = match self { + SecurityMode::NoAccess => (0, 0), + SecurityMode::Open => (1, 1), + SecurityMode::JustWorks => (1, 2), + SecurityMode::Mitm => (1, 3), + SecurityMode::LescMitm => (1, 4), + SecurityMode::Signed => (2, 1), + SecurityMode::SignedMitm => (2, 2), + }; + + raw::ble_gap_conn_sec_mode_t { + _bitfield_1: raw::ble_gap_conn_sec_mode_t::new_bitfield_1(sm, lv), + } + } +} + #[repr(u8)] #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -- cgit v1.2.3