esp32_nimble/server/
ble_server.rs

1use crate::{
2    BLECharacteristic, BLEConnDesc, BLEDevice, BLEError, BLEService, NimbleProperties, NotifyTx,
3    ble,
4    utilities::{BleUuid, ble_gap_conn_find, extend_lifetime_mut, mutex::Mutex},
5};
6use alloc::{boxed::Box, sync::Arc, vec::Vec};
7use core::{cell::UnsafeCell, ffi::c_void};
8use esp_idf_svc::sys as esp_idf_sys;
9
10const BLE_HS_CONN_HANDLE_NONE: u16 = esp_idf_sys::BLE_HS_CONN_HANDLE_NONE as _;
11const MAX_CONNECTIONS: usize = esp_idf_sys::CONFIG_BT_NIMBLE_MAX_CONNECTIONS as _;
12
13#[allow(clippy::type_complexity)]
14pub struct BLEServer {
15    pub(crate) started: bool,
16    advertise_on_disconnect: bool,
17    services: Vec<Arc<Mutex<BLEService>>>,
18    notify_characteristic: Vec<&'static mut BLECharacteristic>,
19    connections: heapless::Vec<u16, MAX_CONNECTIONS>,
20    indicate_wait: [u16; MAX_CONNECTIONS],
21
22    on_connect: Option<Box<dyn FnMut(&mut Self, &BLEConnDesc) + Send + Sync>>,
23    on_disconnect: Option<Box<dyn FnMut(&BLEConnDesc, Result<(), BLEError>) + Send + Sync>>,
24    on_passkey_request: Option<Box<dyn Fn() -> u32 + Send + Sync>>,
25    on_confirm_pin: Option<Box<dyn Fn(u32) -> bool + Send + Sync>>,
26    on_authentication_complete:
27        Option<Box<dyn Fn(&mut Self, &BLEConnDesc, Result<(), BLEError>) + Send + Sync>>,
28}
29
30impl BLEServer {
31    pub(crate) fn new() -> Self {
32        Self {
33            started: false,
34            advertise_on_disconnect: true,
35            services: Vec::new(),
36            notify_characteristic: Vec::new(),
37            connections: heapless::Vec::new(),
38            indicate_wait: [BLE_HS_CONN_HANDLE_NONE; MAX_CONNECTIONS],
39            on_connect: None,
40            on_disconnect: None,
41            on_passkey_request: None,
42            on_confirm_pin: None,
43            on_authentication_complete: None,
44        }
45    }
46
47    pub fn on_connect(
48        &mut self,
49        callback: impl FnMut(&mut Self, &BLEConnDesc) + Send + Sync + 'static,
50    ) -> &mut Self {
51        self.on_connect = Some(Box::new(callback));
52        self
53    }
54
55    /// Handle a client disconnection.
56    /// * callback first parameter: A reference to a `esp_idf_sys::ble_gap_conn_desc` instance with information about the peer connection parameters.
57    /// * callback second parameter: The reason code for the disconnection.
58    pub fn on_disconnect(
59        &mut self,
60        callback: impl FnMut(&BLEConnDesc, Result<(), BLEError>) + Send + Sync + 'static,
61    ) -> &mut Self {
62        self.on_disconnect = Some(Box::new(callback));
63        self
64    }
65
66    /// Set a callback fn for generating a passkey if required by the connection
67    /// * The passkey will always be exactly 6 digits. Setting the passkey to 1234
68    ///   will require the user to provide '001234'
69    /// * a static passkey can also be set by [`crate::BLESecurity::set_passkey`]
70    pub fn on_passkey_request(
71        &mut self,
72        callback: impl Fn() -> u32 + Send + Sync + 'static,
73    ) -> &mut Self {
74        if cfg!(debug_assertions) {
75            self.on_passkey_request = Some(Box::new(move || {
76                let passkey = callback();
77                debug_assert!(
78                    passkey <= 999999,
79                    "passkey must be between 000000..=999999 inclusive"
80                );
81                passkey
82            }));
83        } else {
84            self.on_passkey_request = Some(Box::new(callback));
85        }
86
87        self
88    }
89
90    pub fn on_confirm_pin(
91        &mut self,
92        callback: impl Fn(u32) -> bool + Send + Sync + 'static,
93    ) -> &mut Self {
94        self.on_confirm_pin = Some(Box::new(callback));
95        self
96    }
97
98    /// The callback function is called when the pairing procedure is complete.
99    /// * callback first parameter: A reference to a `BLEConnDesc` instance.
100    /// * callback second parameter: Indicates the result of the encryption state change attempt;
101    ///   o 0: the encrypted state was successfully updated;
102    ///   o BLE host error code: the encryption state change attempt failed for the specified reason.
103    pub fn on_authentication_complete(
104        &mut self,
105        callback: impl Fn(&mut Self, &BLEConnDesc, Result<(), BLEError>) + Send + Sync + 'static,
106    ) -> &mut Self {
107        self.on_authentication_complete = Some(Box::new(callback));
108        self
109    }
110
111    pub fn start(&mut self) -> Result<(), BLEError> {
112        if self.started {
113            return Ok(());
114        }
115
116        unsafe {
117            esp_idf_sys::ble_gatts_reset();
118            esp_idf_sys::ble_svc_gap_init();
119            esp_idf_sys::ble_svc_gatt_init();
120        }
121
122        for svc in &mut self.services {
123            svc.lock().start()?;
124        }
125
126        unsafe {
127            ble!(esp_idf_sys::ble_gatts_start())?;
128
129            for svc in &self.services {
130                let mut svc = svc.lock();
131                ble!(esp_idf_sys::ble_gatts_find_svc(
132                    &svc.uuid.u,
133                    &mut svc.handle
134                ))?;
135
136                for chr in &svc.characteristics {
137                    let mut chr = chr.lock();
138                    if chr
139                        .properties
140                        .intersects(NimbleProperties::INDICATE | NimbleProperties::NOTIFY)
141                    {
142                        let chr = &mut *chr;
143                        self.notify_characteristic.push(extend_lifetime_mut(chr));
144                    }
145                }
146            }
147        }
148
149        self.started = true;
150
151        Ok(())
152    }
153
154    /// Disconnect the specified client.
155    pub fn disconnect(&mut self, conn_id: u16) -> Result<(), BLEError> {
156        self.disconnect_with_reason(
157            conn_id,
158            esp_idf_sys::ble_error_codes_BLE_ERR_REM_USER_CONN_TERM as _,
159        )
160    }
161
162    /// Disconnect the specified client with optional reason.
163    pub fn disconnect_with_reason(&mut self, conn_id: u16, reason: u8) -> Result<(), BLEError> {
164        unsafe { ble!(esp_idf_sys::ble_gap_terminate(conn_id, reason)) }
165    }
166
167    /// Prints dump of local GATT database.
168    /// This is useful to log local state of database in human readable form.
169    pub fn ble_gatts_show_local(&self) {
170        unsafe {
171            esp_idf_sys::ble_gatts_show_local();
172        }
173    }
174
175    pub fn connected_count(&self) -> usize {
176        self.connections.len()
177    }
178
179    pub fn connections(&self) -> impl Iterator<Item = BLEConnDesc> + '_ {
180        self.connections
181            .iter()
182            .filter_map(|x| ble_gap_conn_find(*x).ok())
183    }
184
185    pub fn create_service(&mut self, uuid: BleUuid) -> Arc<Mutex<BLEService>> {
186        let service = Arc::new(Mutex::new(BLEService::new(uuid)));
187        self.services.push(service.clone());
188        service
189    }
190
191    /// Get the service object for the UUID.
192    pub async fn get_service(&self, uuid: BleUuid) -> Option<&Arc<Mutex<BLEService>>> {
193        self.services
194            .iter()
195            .find(|x| unsafe { x.raw() }.uuid() == uuid)
196    }
197
198    /// Set the server to automatically start advertising when a client disconnects.
199    pub fn advertise_on_disconnect(&mut self, value: bool) -> &mut Self {
200        self.advertise_on_disconnect = value;
201        self
202    }
203
204    /// Request an Update the connection parameters:
205    /// Can only be used after a connection has been established.
206    ///
207    /// * `conn_handle`: The connection handle of the peer to send the request to.
208    /// * `min_interval`: The minimum connection interval in 1.25ms units.
209    /// * `max_interval`: The maximum connection interval in 1.25ms units.
210    /// * `latency`: The number of packets allowed to skip (extends max interval).
211    /// * `timeout`: The timeout time in 10ms units before disconnecting.
212    pub fn update_conn_params(
213        &mut self,
214        conn_handle: u16,
215        min_interval: u16,
216        max_interval: u16,
217        latency: u16,
218        timeout: u16,
219    ) -> Result<(), BLEError> {
220        let params = esp_idf_sys::ble_gap_upd_params {
221            itvl_min: min_interval,
222            itvl_max: max_interval,
223            latency,
224            supervision_timeout: timeout,
225            min_ce_len: esp_idf_sys::BLE_GAP_INITIAL_CONN_MIN_CE_LEN as _,
226            max_ce_len: esp_idf_sys::BLE_GAP_INITIAL_CONN_MAX_CE_LEN as _,
227        };
228
229        unsafe { ble!(esp_idf_sys::ble_gap_update_params(conn_handle, &params)) }
230    }
231
232    pub(crate) fn reset(&mut self) {
233        self.advertise_on_disconnect = true;
234        self.services.clear();
235        self.notify_characteristic.clear();
236        self.connections.clear();
237        self.on_connect = None;
238        self.on_disconnect = None;
239        self.on_passkey_request = None;
240        self.on_confirm_pin = None;
241        self.on_authentication_complete = None;
242    }
243
244    pub(crate) extern "C" fn handle_gap_event(
245        _event: *mut esp_idf_sys::ble_gap_event,
246        _arg: *mut c_void,
247    ) -> i32 {
248        let event = unsafe { &*_event };
249        let server = BLEDevice::take().get_server();
250
251        match event.type_ as _ {
252            esp_idf_sys::BLE_GAP_EVENT_CONNECT => {
253                let connect = unsafe { &event.__bindgen_anon_1.connect };
254                if connect.status == 0 {
255                    if !server.connections.contains(&connect.conn_handle)
256                        && server.connections.push(connect.conn_handle).is_err()
257                    {
258                        ::log::warn!(
259                            "BLE connection table full; drop conn_handle={}",
260                            connect.conn_handle
261                        );
262                        return esp_idf_sys::BLE_ATT_ERR_INSUFFICIENT_RES as _;
263                    }
264
265                    if let Ok(desc) = ble_gap_conn_find(connect.conn_handle) {
266                        let server = UnsafeCell::new(server);
267                        unsafe {
268                            if let Some(callback) = (*server.get()).on_connect.as_mut() {
269                                callback(*server.get(), &desc);
270                            }
271                        }
272                    }
273                }
274            }
275            esp_idf_sys::BLE_GAP_EVENT_DISCONNECT => {
276                let disconnect = unsafe { &event.__bindgen_anon_1.disconnect };
277                if let Some(idx) = server
278                    .connections
279                    .iter()
280                    .position(|x| *x == disconnect.conn.conn_handle)
281                {
282                    server.connections.swap_remove(idx);
283                }
284
285                if let Some(callback) = server.on_disconnect.as_mut() {
286                    callback(
287                        &BLEConnDesc(disconnect.conn),
288                        BLEError::convert(disconnect.reason as _),
289                    );
290                }
291
292                #[cfg(not(esp_idf_bt_nimble_ext_adv))]
293                if server.advertise_on_disconnect
294                    && let Err(err) = BLEDevice::take().get_advertising().lock().start()
295                {
296                    ::log::warn!("can't start advertising: {err:?}");
297                }
298            }
299            esp_idf_sys::BLE_GAP_EVENT_SUBSCRIBE => {
300                let subscribe = unsafe { &event.__bindgen_anon_1.subscribe };
301                if let Some(chr) = server
302                    .notify_characteristic
303                    .iter_mut()
304                    .find(|x| x.handle == subscribe.attr_handle)
305                {
306                    if chr.properties.intersects(
307                        NimbleProperties::READ_AUTHEN
308                            | NimbleProperties::READ_AUTHOR
309                            | NimbleProperties::READ_ENC,
310                    ) && let Ok(desc) = ble_gap_conn_find(subscribe.conn_handle)
311                        && !desc.encrypted()
312                    {
313                        let rc = unsafe {
314                            esp_idf_sys::ble_gap_security_initiate(subscribe.conn_handle)
315                        };
316                        if rc != 0 {
317                            ::log::error!("ble_gap_security_initiate: rc={rc}");
318                        }
319                    }
320
321                    chr.subscribe(subscribe);
322                }
323            }
324            esp_idf_sys::BLE_GAP_EVENT_MTU => {
325                let mtu = unsafe { &event.__bindgen_anon_1.mtu };
326                ::log::info!(
327                    "mtu update event; conn_handle={} mtu={}",
328                    mtu.conn_handle,
329                    mtu.value
330                );
331            }
332            esp_idf_sys::BLE_GAP_EVENT_NOTIFY_TX => {
333                let notify_tx = unsafe { &event.__bindgen_anon_1.notify_tx };
334                if let Some(chr) = server
335                    .notify_characteristic
336                    .iter_mut()
337                    .find(|x| x.handle == notify_tx.attr_handle)
338                {
339                    if notify_tx.indication() > 0 {
340                        if notify_tx.status == 0 {
341                            return 0;
342                        }
343
344                        BLEDevice::take()
345                            .get_server()
346                            .clear_indicate_wait(notify_tx.conn_handle);
347                    }
348
349                    if let Some(callback) = &mut chr.on_notify_tx {
350                        callback(NotifyTx { notify_tx });
351                    }
352                }
353            }
354            #[cfg(not(esp_idf_bt_nimble_ext_adv))]
355            esp_idf_sys::BLE_GAP_EVENT_ADV_COMPLETE => {
356                return crate::BLEAdvertising::handle_gap_event(_event, _arg);
357            }
358            #[cfg(esp_idf_bt_nimble_ext_adv)]
359            esp_idf_sys::BLE_GAP_EVENT_ADV_COMPLETE | esp_idf_sys::BLE_GAP_EVENT_SCAN_REQ_RCVD => {
360                return crate::BLEExtAdvertising::handle_gap_event(_event, _arg);
361            }
362            esp_idf_sys::BLE_GAP_EVENT_CONN_UPDATE => {
363                ::log::debug!("Connection parameters updated.");
364            }
365            esp_idf_sys::BLE_GAP_EVENT_CONN_UPDATE_REQ => {}
366            esp_idf_sys::BLE_GAP_EVENT_REPEAT_PAIRING => {
367                let repeat_pairing = unsafe { &event.__bindgen_anon_1.repeat_pairing };
368
369                // Delete the old bond
370                let Ok(desc) = crate::utilities::ble_gap_conn_find(repeat_pairing.conn_handle)
371                else {
372                    return esp_idf_sys::BLE_GAP_REPEAT_PAIRING_IGNORE as _;
373                };
374                unsafe {
375                    esp_idf_sys::ble_store_util_delete_peer(&desc.0.peer_id_addr);
376                }
377
378                // Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should
379                // continue with the pairing operation.
380                return esp_idf_sys::BLE_GAP_REPEAT_PAIRING_RETRY as _;
381            }
382            esp_idf_sys::BLE_GAP_EVENT_ENC_CHANGE => {
383                let enc_change = unsafe { &event.__bindgen_anon_1.enc_change };
384                let Ok(desk) = ble_gap_conn_find(enc_change.conn_handle) else {
385                    return esp_idf_sys::BLE_ATT_ERR_INVALID_HANDLE as _;
386                };
387
388                let server = UnsafeCell::new(server);
389                unsafe {
390                    if let Some(callback) = &(*server.get()).on_authentication_complete {
391                        callback(
392                            *server.get(),
393                            &desk,
394                            BLEError::convert(enc_change.status as _),
395                        );
396                    }
397                }
398            }
399            esp_idf_sys::BLE_GAP_EVENT_PASSKEY_ACTION => {
400                let passkey = unsafe { &event.__bindgen_anon_1.passkey };
401                let mut pkey = esp_idf_sys::ble_sm_io {
402                    action: passkey.params.action,
403                    ..Default::default()
404                };
405                match passkey.params.action as _ {
406                    esp_idf_sys::BLE_SM_IOACT_DISP => {
407                        pkey.__bindgen_anon_1.passkey =
408                            if let Some(callback) = &server.on_passkey_request {
409                                callback()
410                            } else {
411                                BLEDevice::take().security().get_passkey()
412                            };
413
414                        let rc = unsafe {
415                            esp_idf_sys::ble_sm_inject_io(passkey.conn_handle, &mut pkey)
416                        };
417                        ::log::debug!("BLE_SM_IOACT_DISP; ble_sm_inject_io result: {rc}");
418                    }
419                    esp_idf_sys::BLE_SM_IOACT_NUMCMP => {
420                        if let Some(callback) = &server.on_confirm_pin {
421                            pkey.__bindgen_anon_1.numcmp_accept =
422                                callback(passkey.params.numcmp) as _;
423                        } else {
424                            ::log::warn!("on_passkey_request is not setted");
425                        }
426                        let rc = unsafe {
427                            esp_idf_sys::ble_sm_inject_io(passkey.conn_handle, &mut pkey)
428                        };
429                        ::log::debug!("BLE_SM_IOACT_NUMCMP; ble_sm_inject_io result: {rc}");
430                    }
431                    esp_idf_sys::BLE_SM_IOACT_INPUT => {
432                        if let Some(callback) = &server.on_passkey_request {
433                            pkey.__bindgen_anon_1.passkey = callback();
434                        } else {
435                            ::log::warn!("on_passkey_request is not setted");
436                        }
437                        let rc = unsafe {
438                            esp_idf_sys::ble_sm_inject_io(passkey.conn_handle, &mut pkey)
439                        };
440                        ::log::debug!("BLE_SM_IOACT_INPUT; ble_sm_inject_io result: {rc}");
441                    }
442                    esp_idf_sys::BLE_SM_IOACT_NONE => {
443                        ::log::debug!("BLE_SM_IOACT_NONE; No passkey action required");
444                    }
445                    action => {
446                        todo!("implementation required: {}", action);
447                    }
448                }
449            }
450            #[cfg(not(all(
451                esp_idf_version_major = "5",
452                any(esp_idf_version_minor = "1", esp_idf_version_minor = "2"),
453            )))]
454            esp_idf_sys::BLE_GAP_EVENT_DATA_LEN_CHG => {
455                let data_len = unsafe { &event.__bindgen_anon_1.data_len_chg };
456                ::log::debug!(
457                    "==========
458data length changed:
459conn_handle={},
460tx_octets={},
461tx_time={},
462rx_octets={},
463rx_time={}
464==========",
465                    data_len.conn_handle,
466                    data_len.max_tx_octets,
467                    data_len.max_tx_time,
468                    data_len.max_rx_octets,
469                    data_len.max_rx_time
470                );
471            }
472            esp_idf_sys::BLE_GAP_EVENT_IDENTITY_RESOLVED
473            | esp_idf_sys::BLE_GAP_EVENT_PHY_UPDATE_COMPLETE => {}
474            _ => {
475                ::log::warn!("unhandled event: {}", event.type_);
476            }
477        }
478
479        0
480    }
481
482    pub(super) fn set_indicate_wait(&self, conn_handle: u16) -> bool {
483        !self.indicate_wait.contains(&conn_handle)
484    }
485
486    pub(super) fn clear_indicate_wait(&mut self, conn_handle: u16) {
487        if let Some(it) = self.indicate_wait.iter_mut().find(|x| **x == conn_handle) {
488            *it = BLE_HS_CONN_HANDLE_NONE;
489        }
490    }
491}