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