Skip to main content

esp32_nimble/server/
ble_characteristic.rs

1use alloc::{boxed::Box, sync::Arc, vec::Vec};
2use bitflags::bitflags;
3use core::{cell::UnsafeCell, ffi::c_void};
4use esp_idf_svc::sys;
5#[cfg(not(cpfd))]
6use zerocopy::IntoBytes;
7
8use crate::{
9    AttValue, BLEConnDesc, BLEDescriptor, BLEDevice, BLEError, DescriptorProperties, OnWriteArgs,
10    ble,
11    cpfd::Cpfd,
12    utilities::{
13        BleUuid, OsMBuf, ble_npl_hw_enter_critical, ble_npl_hw_exit_critical, mutex::Mutex,
14        voidp_to_ref,
15    },
16};
17
18cfg_if::cfg_if! {
19  if #[cfg(any(
20    all(
21      esp_idf_version_major = "5",
22      esp_idf_version_minor = "2",
23      not(any(esp_idf_version_patch = "0", esp_idf_version_patch = "1", esp_idf_version_patch="2"))),
24    all(
25      esp_idf_version_major = "5",
26      esp_idf_version_minor = "3",
27      not(any(esp_idf_version_patch = "0", esp_idf_version_patch = "1"))),
28    all(
29      esp_idf_version_major = "5",
30      esp_idf_version_minor = "4"),
31    all(
32      esp_idf_version_major = "5",
33      esp_idf_version_minor = "5"),
34    esp_idf_version_major = "6",
35  ))] {
36    type NotifyTxType = sys::ble_gap_event__bindgen_ty_1__bindgen_ty_12;
37    type Subscribe = sys::ble_gap_event__bindgen_ty_1__bindgen_ty_13;
38  } else {
39    type NotifyTxType = sys::ble_gap_event__bindgen_ty_1__bindgen_ty_11;
40    type Subscribe = sys::ble_gap_event__bindgen_ty_1__bindgen_ty_12;
41  }
42}
43
44const NULL_HANDLE: u16 = 0xFFFF;
45
46cfg_if::cfg_if! {
47  if #[cfg(any(
48    all(
49      esp_idf_version_major = "5",
50      esp_idf_version_minor = "3",
51      not(any(
52        esp_idf_version_patch = "0",
53        esp_idf_version_patch = "1",
54        esp_idf_version_patch = "2",
55        esp_idf_version_patch = "3",
56        esp_idf_version_patch = "4"))),
57    all(
58      esp_idf_version_major = "5",
59      esp_idf_version_minor = "4",
60      not(any(esp_idf_version_patch = "0", esp_idf_version_patch = "1"))),
61    all(
62      esp_idf_version_major = "5",
63      esp_idf_version_minor = "5",
64    ),
65    esp_idf_version_major = "6",
66  ))] {
67    type NimblePropertiesType = u32;
68  } else {
69    type NimblePropertiesType = u16;
70  }
71}
72
73bitflags! {
74  #[repr(transparent)]
75  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
76  pub struct NimbleProperties: NimblePropertiesType {
77    /// Read Access Permitted
78    const READ = sys::BLE_GATT_CHR_F_READ as _;
79    /// Read Requires Encryption
80    const READ_ENC = sys::BLE_GATT_CHR_F_READ_ENC as _;
81    /// Read requires Authentication
82    const READ_AUTHEN = sys::BLE_GATT_CHR_F_READ_AUTHEN as _;
83    /// Read requires Authorization
84    const READ_AUTHOR = sys::BLE_GATT_CHR_F_READ_AUTHOR as _;
85    /// Write Permited
86    const WRITE = sys::BLE_GATT_CHR_F_WRITE as _;
87    /// Write with no Ack Response
88    const WRITE_NO_RSP = sys::BLE_GATT_CHR_F_WRITE_NO_RSP as _;
89    /// Write Requires Encryption
90    const WRITE_ENC = sys::BLE_GATT_CHR_F_WRITE_ENC as _;
91    /// Write requires Authentication
92    const WRITE_AUTHEN = sys::BLE_GATT_CHR_F_WRITE_AUTHEN as _;
93    /// Write requires Authorization
94    const WRITE_AUTHOR = sys::BLE_GATT_CHR_F_WRITE_AUTHOR as _;
95    /// Broadcasts are included in the advertising data
96    const BROADCAST = sys::BLE_GATT_CHR_F_BROADCAST as _;
97    /// Notifications are Sent from Server to Client with no Response
98    const NOTIFY = sys::BLE_GATT_CHR_F_NOTIFY as _;
99    /// Indications are Sent from Server to Client where Server expects a Response
100    const INDICATE = sys::BLE_GATT_CHR_F_INDICATE as _;
101
102    #[cfg(esp_idf_version_at_least_5_4_2)]
103    /// CCCD Write Encrypted
104    const NOTIFY_INDICATE_ENC = sys::BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC as _;
105
106    #[cfg(esp_idf_version_at_least_5_4_2)]
107    /// CCCD Write Authenticated
108    const NOTIFY_INDICATE_AUTHEN = sys::BLE_GATT_CHR_F_NOTIFY_INDICATE_AUTHEN as _;
109
110    #[cfg(esp_idf_version_at_least_5_4_2)]
111    /// CCCD Write Authorized
112    const NOTIFY_INDICATE_AUTHOR = sys::BLE_GATT_CHR_F_NOTIFY_INDICATE_AUTHOR  as _;
113  }
114}
115
116#[derive(PartialEq, Debug)]
117pub enum NotifyTxStatus {
118    SuccessIndicate,
119    SuccessNotify,
120    ErrorIndicateDisabled,
121    ErrorNotifyDisabled,
122    ErrorGatt,
123    ErrorNoClient,
124    ErrorIndicateTimeout,
125    ErrorIndicateFailure,
126}
127
128pub struct NotifyTx<'a> {
129    pub(crate) notify_tx: &'a NotifyTxType,
130}
131
132impl NotifyTx<'_> {
133    pub fn status(&self) -> NotifyTxStatus {
134        if self.notify_tx.indication() > 0 {
135            match self.notify_tx.status as _ {
136                sys::BLE_HS_EDONE => NotifyTxStatus::SuccessIndicate,
137                sys::BLE_HS_ETIMEOUT => NotifyTxStatus::ErrorIndicateTimeout,
138                _ => NotifyTxStatus::ErrorIndicateFailure,
139            }
140        } else {
141            #[allow(clippy::collapsible_else_if)]
142            if self.notify_tx.status == 0 {
143                NotifyTxStatus::SuccessNotify
144            } else {
145                NotifyTxStatus::ErrorGatt
146            }
147        }
148    }
149
150    pub fn desc(&self) -> Result<BLEConnDesc, crate::BLEError> {
151        crate::utilities::ble_gap_conn_find(self.notify_tx.conn_handle)
152    }
153}
154
155bitflags! {
156  #[repr(transparent)]
157  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
158  ///Empty NimbleSub i.e. `NimbleSub::is_empty()==true` means Unsubscribe(d)
159  pub struct NimbleSub: u16 {
160    /// Subscribe if Notify
161    const NOTIFY = 0x0001;
162    /// Subscribe if Indicate
163    const INDICATE = 0x0002;
164  }
165}
166
167#[allow(clippy::type_complexity)]
168pub struct BLECharacteristic {
169    pub(crate) uuid: sys::ble_uuid_any_t,
170    pub(crate) handle: u16,
171    pub(crate) properties: NimbleProperties,
172    value: AttValue,
173    on_read: Option<Box<dyn FnMut(&mut Self, &BLEConnDesc) + Send + Sync>>,
174    on_write: Option<Box<dyn FnMut(&mut OnWriteArgs) + Send + Sync>>,
175    pub(crate) on_notify_tx: Option<Box<dyn FnMut(NotifyTx) + Send + Sync>>,
176    descriptors: Vec<Arc<Mutex<BLEDescriptor>>>,
177    svc_def_descriptors: Vec<sys::ble_gatt_dsc_def>,
178    subscribed_list: Vec<(u16, NimbleSub)>,
179    on_subscribe: Option<Box<dyn FnMut(&Self, &BLEConnDesc, NimbleSub) + Send + Sync>>,
180    #[cfg(cpfd)]
181    pub(crate) cpfd: [sys::ble_gatt_cpfd; 2],
182}
183
184impl BLECharacteristic {
185    pub(crate) fn new(uuid: BleUuid, properties: NimbleProperties) -> Self {
186        Self {
187            uuid: sys::ble_uuid_any_t::from(uuid),
188            handle: NULL_HANDLE,
189            properties,
190            value: AttValue::new(),
191            on_read: None,
192            on_write: None,
193            on_notify_tx: None,
194            descriptors: Vec::new(),
195            svc_def_descriptors: Vec::new(),
196            subscribed_list: Vec::new(),
197            on_subscribe: None,
198            #[cfg(cpfd)]
199            cpfd: [Default::default(); 2],
200        }
201    }
202
203    pub fn uuid(&self) -> BleUuid {
204        BleUuid::from(self.uuid)
205    }
206
207    pub fn set_value(&mut self, value: &[u8]) -> &mut Self {
208        self.value.set_value(value);
209        self
210    }
211
212    #[deprecated(note = "Please use `set_value` + zerocopy::IntoBytes")]
213    pub fn set_from<T: Sized>(&mut self, value: &T) -> &mut Self {
214        #[allow(deprecated)]
215        self.value.set_from(value);
216        self
217    }
218
219    pub fn value_mut(&mut self) -> &mut AttValue {
220        &mut self.value
221    }
222
223    pub fn on_read(
224        &mut self,
225        callback: impl FnMut(&mut Self, &BLEConnDesc) + Send + Sync + 'static,
226    ) -> &mut Self {
227        self.on_read = Some(Box::new(callback));
228        self
229    }
230
231    /// This characteristic is locked while the callback is executing. If you call `.lock()` on this characteristic from inside the callback, it will never execute.
232    pub fn on_write(
233        &mut self,
234        callback: impl FnMut(&mut OnWriteArgs) + Send + Sync + 'static,
235    ) -> &mut Self {
236        self.on_write = Some(Box::new(callback));
237        self
238    }
239
240    pub fn on_notify_tx(
241        &mut self,
242        callback: impl FnMut(NotifyTx) + Send + Sync + 'static,
243    ) -> &mut Self {
244        self.on_notify_tx = Some(Box::new(callback));
245        self
246    }
247
248    pub fn create_descriptor(
249        &mut self,
250        uuid: BleUuid,
251        properties: DescriptorProperties,
252    ) -> Arc<Mutex<BLEDescriptor>> {
253        if uuid == BleUuid::Uuid16(sys::BLE_GATT_DSC_CLT_CFG_UUID16 as _) {
254            panic!("0x2902 descriptors cannot be manually created");
255        }
256
257        let descriptor = Arc::new(Mutex::new(BLEDescriptor::new(uuid, properties)));
258        self.descriptors.push(descriptor.clone());
259        descriptor
260    }
261
262    pub(crate) fn construct_svc_def_descriptors(&mut self) -> *mut sys::ble_gatt_dsc_def {
263        if self.descriptors.is_empty() {
264            return core::ptr::null_mut();
265        }
266        self.svc_def_descriptors.clear();
267
268        for dsc in &mut self.descriptors {
269            let arg = unsafe { Arc::get_mut_unchecked(dsc) } as *mut Mutex<BLEDescriptor>;
270            let dsc = dsc.lock();
271            self.svc_def_descriptors.push(sys::ble_gatt_dsc_def {
272                uuid: unsafe { &dsc.uuid.u },
273                att_flags: dsc.properties.bits(),
274                min_key_size: 0,
275                access_cb: Some(BLEDescriptor::handle_gap_event),
276                arg: arg as _,
277            });
278        }
279        self.svc_def_descriptors
280            .push(sys::ble_gatt_dsc_def::default());
281        self.svc_def_descriptors.as_mut_ptr()
282    }
283
284    pub fn notify_with(&self, value: &[u8], conn_handle: u16) -> Result<(), BLEError> {
285        if let Some((_, flag)) = self.subscribed_list.iter().find(|x| x.0 == conn_handle) {
286            self.send_value(value, conn_handle, *flag)
287        } else {
288            BLEError::convert(sys::BLE_HS_EINVAL)
289        }
290    }
291
292    pub fn notify(&self) {
293        for it in &self.subscribed_list {
294            if let Err(err) = self.send_value(self.value.as_slice(), it.0, it.1) {
295                ::log::warn!("notify error({}): {:?}", it.0, err);
296            }
297        }
298    }
299
300    fn send_value(&self, value: &[u8], conn_handle: u16, flag: NimbleSub) -> Result<(), BLEError> {
301        let mtu = unsafe { sys::ble_att_mtu(conn_handle) - 3 };
302        if mtu == 0 || flag.is_empty() {
303            return BLEError::convert(sys::BLE_HS_EINVAL);
304        }
305        let server = BLEDevice::take().get_server();
306
307        if flag.contains(NimbleSub::INDICATE)
308            && self.properties.contains(NimbleProperties::INDICATE)
309        {
310            if !server.set_indicate_wait(conn_handle) {
311                ::log::error!("prior Indication in progress");
312                return BLEError::convert(sys::BLE_HS_EBUSY);
313            }
314
315            let om = OsMBuf::from_flat(value);
316            let rc = unsafe { sys::ble_gatts_indicate_custom(conn_handle, self.handle, om.0) };
317            if rc != 0 {
318                server.clear_indicate_wait(conn_handle);
319            }
320            BLEError::convert(rc as _)
321        } else if flag.contains(NimbleSub::NOTIFY)
322            && self.properties.contains(NimbleProperties::NOTIFY)
323        {
324            let om = OsMBuf::from_flat(value);
325            ble!(unsafe { sys::ble_gatts_notify_custom(conn_handle, self.handle, om.0) })
326        } else {
327            BLEError::convert(sys::BLE_HS_EINVAL)
328        }
329    }
330
331    #[cfg(cpfd)]
332    /// Set the Characteristic Presentation Format.
333    pub fn cpfd(&mut self, cpfd: Cpfd) {
334        if cpfd.name_space == (sys::BLE_GATT_CHR_NAMESPACE_BT_SIG as _) {
335            debug_assert!(cpfd.description <= (sys::BLE_GATT_CHR_BT_SIG_DESC_EXTERNAL as _));
336        }
337
338        self.cpfd[0].format = cpfd.format.into();
339        self.cpfd[0].exponent = cpfd.exponent;
340        self.cpfd[0].unit = cpfd.unit.into();
341        self.cpfd[0].name_space = cpfd.name_space;
342        self.cpfd[0].description = cpfd.description;
343    }
344
345    #[cfg(not(cpfd))]
346    /// Set the Characteristic Presentation Format.
347    pub fn cpfd(&mut self, cpfd: Cpfd) {
348        let descriptor = Arc::new(Mutex::new(BLEDescriptor::new(
349            BleUuid::Uuid16(0x2904),
350            DescriptorProperties::READ,
351        )));
352        descriptor.lock().set_value(cpfd.as_bytes());
353        self.descriptors.push(descriptor);
354    }
355
356    pub(super) extern "C" fn handle_gap_event(
357        conn_handle: u16,
358        _attr_handle: u16,
359        ctxt: *mut sys::ble_gatt_access_ctxt,
360        arg: *mut c_void,
361    ) -> i32 {
362        let ctxt = unsafe { &*ctxt };
363
364        let mutex = unsafe { voidp_to_ref::<Mutex<Self>>(arg) };
365
366        if crate::utilities::ble_gap_conn_find(conn_handle).is_err() {
367            ::log::warn!("the conn handle does not exist");
368            return sys::BLE_ATT_ERR_UNLIKELY as _;
369        }
370
371        let mut characteristic = mutex.lock();
372        if unsafe {
373            sys::ble_uuid_cmp((*ctxt.__bindgen_anon_1.chr).uuid, &characteristic.uuid.u) != 0
374        } {
375            return sys::BLE_ATT_ERR_UNLIKELY as _;
376        }
377
378        match ctxt.op as _ {
379            sys::BLE_GATT_ACCESS_OP_READ_CHR => {
380                let desc = crate::utilities::ble_gap_conn_find(conn_handle).unwrap();
381
382                unsafe {
383                    if (*(ctxt.om)).om_pkthdr_len > 8
384                        || characteristic.value.len() <= (desc.mtu() - 3) as _
385                    {
386                        let characteristic = UnsafeCell::new(&mut characteristic);
387                        if let Some(callback) = &mut (&mut (*characteristic.get())).on_read {
388                            callback(*characteristic.get(), &desc);
389                        }
390                    }
391                }
392
393                ble_npl_hw_enter_critical();
394                let value = characteristic.value.as_slice();
395                let rc = OsMBuf(ctxt.om).append(value);
396                ble_npl_hw_exit_critical();
397                if rc == 0 {
398                    0
399                } else {
400                    sys::BLE_ATT_ERR_INSUFFICIENT_RES as _
401                }
402            }
403            sys::BLE_GATT_ACCESS_OP_WRITE_CHR => {
404                let om = OsMBuf(ctxt.om);
405                let buf = om.as_flat();
406
407                let mut notify = false;
408
409                unsafe {
410                    let characteristic = UnsafeCell::new(&mut characteristic);
411                    if let Some(callback) = &mut (&mut (*characteristic.get())).on_write {
412                        let desc = crate::utilities::ble_gap_conn_find(conn_handle).unwrap();
413                        let mut arg = OnWriteArgs {
414                            current_data: (&(*characteristic.get())).value.as_slice(),
415                            recv_data: buf.as_slice(),
416                            desc: &desc,
417                            reject: false,
418                            error_code: 0,
419                            notify: false,
420                        };
421                        callback(&mut arg);
422
423                        if arg.reject {
424                            return arg.error_code as _;
425                        }
426                        notify = arg.notify;
427                    }
428                }
429                characteristic.set_value(buf.as_slice());
430                if notify {
431                    characteristic.notify();
432                }
433
434                0
435            }
436            _ => sys::BLE_ATT_ERR_UNLIKELY as _,
437        }
438    }
439
440    pub(super) fn subscribe(&mut self, subscribe: &Subscribe) {
441        let Ok(desc) = crate::utilities::ble_gap_conn_find(subscribe.conn_handle) else {
442            return;
443        };
444
445        let mut sub_val = NimbleSub::empty();
446        if subscribe.cur_notify() > 0 && (self.properties.contains(NimbleProperties::NOTIFY)) {
447            sub_val.insert(NimbleSub::NOTIFY);
448        }
449        if subscribe.cur_indicate() > 0 && (self.properties.contains(NimbleProperties::INDICATE)) {
450            sub_val.insert(NimbleSub::INDICATE);
451        }
452
453        if let Some(idx) = self
454            .subscribed_list
455            .iter()
456            .position(|x| x.0 == subscribe.conn_handle)
457        {
458            if !sub_val.is_empty() {
459                self.subscribed_list[idx].1 = sub_val;
460            } else {
461                self.subscribed_list.swap_remove(idx);
462            }
463        } else if !sub_val.is_empty() {
464            self.subscribed_list.push((subscribe.conn_handle, sub_val));
465        }
466
467        unsafe {
468            let self_ = UnsafeCell::new(self);
469            if let Some(callback) = &mut (*self_.get()).on_subscribe {
470                callback(*self_.get(), &desc, sub_val);
471            }
472        }
473    }
474
475    /// Do not call `lock` on this characteristic inside the callback, use the first input instead.
476    /// In the future, this characteristic could be locked while the callback executes.
477    /// * `callback` - Function to call when a subscription event is recieved, including subscribe and unsubscribe events
478    ///   see [`crate::NimbleSub`] for event type
479    pub fn on_subscribe(
480        &mut self,
481        callback: impl FnMut(&Self, &BLEConnDesc, NimbleSub) + Send + Sync + 'static,
482    ) -> &mut Self {
483        self.on_subscribe = Some(Box::new(callback));
484        self
485    }
486
487    pub fn subscribed_count(&self) -> usize {
488        self.subscribed_list.len()
489    }
490}
491
492impl core::fmt::Debug for BLECharacteristic {
493    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
494        f.debug_struct("BLECharacteristic")
495            .field("uuid", &BleUuid::from(self.uuid))
496            .field("properties", &self.properties)
497            .finish()
498    }
499}
500
501unsafe impl Send for BLECharacteristic {}