summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoralexmoon <alex.r.moon@gmail.com>2022-04-22 11:38:18 -0400
committeralexmoon <alex.r.moon@gmail.com>2022-07-12 14:34:16 -0400
commit5f651f57f6d5d04e82a7733fe6b7b2b4fbbff15c (patch)
tree46992227472ffc80f8537d1b9b99f68e080cebc2
parent5de2bab964549cd481062d3590f1d0c930c4f2f0 (diff)
downloadnrf-softdevice-5f651f57f6d5d04e82a7733fe6b7b2b4fbbff15c.zip
Add service builder
-rw-r--r--nrf-softdevice/src/ble/gatt_server.rs33
-rw-r--r--nrf-softdevice/src/ble/gatt_server/builder.rs208
-rw-r--r--nrf-softdevice/src/ble/gatt_server/characteristic.rs264
-rw-r--r--nrf-softdevice/src/ble/types.rs49
4 files changed, 554 insertions, 0 deletions
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<Self, RegisterError>;
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<Self, RegisterError> {
+ 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<T: AsRef<[u8]>>(
+ &mut self,
+ uuid: Uuid,
+ attr: characteristic::Attribute<T>,
+ md: characteristic::Metadata,
+ ) -> Result<CharacteristicBuilder<'_>, 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<Box<[u8]>>,
+ md: characteristic::Metadata,
+ ) -> Result<CharacteristicBuilder<'_>, 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<CharacteristicBuilder<'_>, 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<IncludedServiceHandle, RegisterError> {
+ 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<T: AsRef<[u8]>>(
+ &mut self,
+ uuid: Uuid,
+ attr: characteristic::Attribute<T>,
+ ) -> Result<DescriptorHandle, RegisterError> {
+ 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<Box<[u8]>>,
+ ) -> Result<DescriptorHandle, RegisterError> {
+ 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<DescriptorHandle, RegisterError> {
+ 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<T: AsRef<[u8]>> {
+ pub metadata: AttributeMetadata,
+ pub value: T,
+ pub max_len: u16,
+}
+
+impl<T: AsRef<[u8]>> Attribute<T> {
+ 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<AttributeMetadata>,
+ 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<UserDescription>,
+ pub cccd: Option<AttributeMetadata>,
+ pub sccd: Option<AttributeMetadata>,
+}
+
+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<Self> {
+ 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))]