trouble_host/
connection.rs

1//! BLE connection.
2
3use bt_hci::cmd::le::{LeConnUpdate, LeReadLocalSupportedFeatures, LeReadPhy, LeSetDataLength, LeSetPhy};
4use bt_hci::cmd::status::ReadRssi;
5use bt_hci::controller::{ControllerCmdAsync, ControllerCmdSync};
6use bt_hci::param::{
7    AddrKind, AllPhys, BdAddr, ConnHandle, DisconnectReason, LeConnRole, PhyKind, PhyMask, PhyOptions, Status,
8};
9#[cfg(feature = "connection-params-update")]
10use bt_hci::{
11    cmd::le::{LeRemoteConnectionParameterRequestNegativeReply, LeRemoteConnectionParameterRequestReply},
12    param::RemoteConnectionParamsRejectReason,
13};
14#[cfg(feature = "gatt")]
15use embassy_sync::blocking_mutex::raw::RawMutex;
16use embassy_time::Duration;
17
18use crate::connection_manager::ConnectionManager;
19#[cfg(feature = "connection-metrics")]
20pub use crate::connection_manager::Metrics as ConnectionMetrics;
21use crate::pdu::Pdu;
22#[cfg(feature = "gatt")]
23use crate::prelude::{AttributeServer, GattConnection};
24#[cfg(feature = "security")]
25use crate::security_manager::{BondInformation, PassKey};
26#[cfg(feature = "connection-params-update")]
27use crate::types::l2cap::{ConnParamUpdateReq, ConnParamUpdateRes};
28use crate::{bt_hci_duration, BleHostError, Error, Identity, PacketPool, Stack};
29
30/// Security level of a connection
31///
32/// This describes the various security levels that are supported.
33///
34#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
35#[cfg_attr(feature = "defmt", derive(defmt::Format))]
36pub enum SecurityLevel {
37    /// No encryption and no authentication. All connections start on this security level.
38    NoEncryption,
39    /// Encrypted but not authenticated communication. Does not provide MITM protection.
40    Encrypted,
41    /// Encrypted and authenticated security level. MITM protected.
42    EncryptedAuthenticated,
43}
44
45impl SecurityLevel {
46    /// Check if the security level is encrypted.
47    pub fn encrypted(&self) -> bool {
48        !matches!(self, SecurityLevel::NoEncryption)
49    }
50
51    /// Check if the security level is authenticated.
52    pub fn authenticated(&self) -> bool {
53        matches!(self, SecurityLevel::EncryptedAuthenticated)
54    }
55}
56
57/// Connection configuration.
58pub struct ConnectConfig<'d> {
59    /// Scan configuration to use while connecting.
60    pub scan_config: ScanConfig<'d>,
61    /// Parameters to use for the connection.
62    pub connect_params: ConnectParams,
63}
64
65/// Scan/connect configuration.
66pub struct ScanConfig<'d> {
67    /// Active scanning.
68    pub active: bool,
69    /// List of addresses to accept.
70    pub filter_accept_list: &'d [(AddrKind, &'d BdAddr)],
71    /// PHYs to scan on.
72    pub phys: PhySet,
73    /// Scan interval.
74    pub interval: Duration,
75    /// Scan window.
76    pub window: Duration,
77    /// Scan timeout.
78    pub timeout: Duration,
79}
80
81impl Default for ScanConfig<'_> {
82    fn default() -> Self {
83        Self {
84            active: true,
85            filter_accept_list: &[],
86            phys: PhySet::M1,
87            interval: Duration::from_secs(1),
88            window: Duration::from_secs(1),
89            timeout: Duration::from_secs(0),
90        }
91    }
92}
93
94/// PHYs to scan on.
95#[cfg_attr(feature = "defmt", derive(defmt::Format))]
96#[derive(Eq, PartialEq, Copy, Clone)]
97#[repr(u8)]
98pub enum PhySet {
99    /// 1Mbps phy
100    M1 = 1,
101    /// 2Mbps phy
102    M2 = 2,
103    /// 1Mbps + 2Mbps phys
104    M1M2 = 3,
105    /// Coded phy (125kbps, S=8)
106    Coded = 4,
107    /// 1Mbps and Coded phys
108    M1Coded = 5,
109    /// 2Mbps and Coded phys
110    M2Coded = 6,
111    /// 1Mbps, 2Mbps and Coded phys
112    M1M2Coded = 7,
113}
114
115/// Connection parameters.
116#[derive(Debug, Clone, PartialEq)]
117#[cfg_attr(feature = "defmt", derive(defmt::Format))]
118pub struct ConnectParams {
119    /// Minimum connection interval.
120    pub min_connection_interval: Duration,
121    /// Maximum connection interval.
122    pub max_connection_interval: Duration,
123    /// Maximum slave latency.
124    pub max_latency: u16,
125    /// Event length.
126    pub min_event_length: Duration,
127    /// Event length.
128    pub max_event_length: Duration,
129    /// Supervision timeout.
130    pub supervision_timeout: Duration,
131}
132
133/// A connection event.
134#[derive(Debug)]
135#[cfg_attr(feature = "defmt", derive(defmt::Format))]
136pub enum ConnectionEvent {
137    /// Connection disconnected.
138    Disconnected {
139        /// The reason (status code) for the disconnect.
140        reason: Status,
141    },
142    /// The phy settings was updated for this connection.
143    PhyUpdated {
144        /// The TX phy.
145        tx_phy: PhyKind,
146        /// The RX phy.
147        rx_phy: PhyKind,
148    },
149    /// The phy settings was updated for this connection.
150    ConnectionParamsUpdated {
151        /// Connection interval.
152        conn_interval: Duration,
153        /// Peripheral latency.
154        peripheral_latency: u16,
155        /// Supervision timeout.
156        supervision_timeout: Duration,
157    },
158    /// The data length was changed for this connection.
159    DataLengthUpdated {
160        /// Max TX octets.
161        max_tx_octets: u16,
162        /// Max TX time.
163        max_tx_time: u16,
164        /// Max RX octets.
165        max_rx_octets: u16,
166        /// Max RX time.
167        max_rx_time: u16,
168    },
169    /// A request to change the connection parameters.
170    ///
171    /// If connection parameter update procedure is supported, the [`Connection::accept_connection_params()`] should be called
172    /// to respond to the request.
173    RequestConnectionParams {
174        /// Minimum connection interval.
175        min_connection_interval: Duration,
176        /// Maximum connection interval.
177        max_connection_interval: Duration,
178        /// Maximum slave latency.
179        max_latency: u16,
180        /// Supervision timeout.
181        supervision_timeout: Duration,
182    },
183    #[cfg(feature = "security")]
184    /// Request to display a pass key
185    PassKeyDisplay(PassKey),
186    #[cfg(feature = "security")]
187    /// Request to display and confirm a pass key
188    PassKeyConfirm(PassKey),
189    #[cfg(feature = "security")]
190    /// Request to make the user input the pass key
191    PassKeyInput,
192    #[cfg(feature = "security")]
193    /// Pairing completed
194    PairingComplete {
195        /// Security level of this pairing
196        security_level: SecurityLevel,
197        /// Bond information if the devices create a bond with this pairing.
198        bond: Option<BondInformation>,
199    },
200    #[cfg(feature = "security")]
201    /// Pairing completed
202    PairingFailed(Error),
203}
204
205impl Default for ConnectParams {
206    fn default() -> Self {
207        Self {
208            min_connection_interval: Duration::from_millis(80),
209            max_connection_interval: Duration::from_millis(80),
210            max_latency: 0,
211            min_event_length: Duration::from_secs(0),
212            max_event_length: Duration::from_secs(0),
213            supervision_timeout: Duration::from_secs(8),
214        }
215    }
216}
217
218/// Handle to a BLE connection.
219///
220/// When the last reference to a connection is dropped, the connection is automatically disconnected.
221pub struct Connection<'stack, P: PacketPool> {
222    index: u8,
223    manager: &'stack ConnectionManager<'stack, P>,
224}
225
226impl<P: PacketPool> Clone for Connection<'_, P> {
227    fn clone(&self) -> Self {
228        self.manager.inc_ref(self.index);
229        Connection::new(self.index, self.manager)
230    }
231}
232
233impl<P: PacketPool> Drop for Connection<'_, P> {
234    fn drop(&mut self) {
235        self.manager.dec_ref(self.index);
236    }
237}
238
239impl<'stack, P: PacketPool> Connection<'stack, P> {
240    pub(crate) fn new(index: u8, manager: &'stack ConnectionManager<'stack, P>) -> Self {
241        Self { index, manager }
242    }
243
244    pub(crate) fn set_att_mtu(&self, mtu: u16) {
245        self.manager.set_att_mtu(self.index, mtu);
246    }
247
248    pub(crate) fn get_att_mtu(&self) -> u16 {
249        self.manager.get_att_mtu(self.index)
250    }
251
252    pub(crate) async fn send(&self, pdu: Pdu<P::Packet>) {
253        self.manager.send(self.index, pdu).await
254    }
255
256    pub(crate) fn try_send(&self, pdu: Pdu<P::Packet>) -> Result<(), Error> {
257        self.manager.try_send(self.index, pdu)
258    }
259
260    pub(crate) async fn post_event(&self, event: ConnectionEvent) {
261        self.manager.post_event(self.index, event).await
262    }
263
264    /// Wait for next connection event.
265    pub async fn next(&self) -> ConnectionEvent {
266        self.manager.next(self.index).await
267    }
268
269    #[cfg(feature = "gatt")]
270    pub(crate) async fn next_gatt(&self) -> Pdu<P::Packet> {
271        self.manager.next_gatt(self.index).await
272    }
273
274    /// Check if still connected
275    pub fn is_connected(&self) -> bool {
276        self.manager.is_connected(self.index)
277    }
278
279    /// Connection handle of this connection.
280    pub fn handle(&self) -> ConnHandle {
281        self.manager.handle(self.index)
282    }
283
284    /// Expose the att_mtu.
285    pub fn att_mtu(&self) -> u16 {
286        self.get_att_mtu()
287    }
288
289    /// The connection role for this connection.
290    pub fn role(&self) -> LeConnRole {
291        self.manager.role(self.index)
292    }
293
294    /// The peer address for this connection.
295    pub fn peer_address(&self) -> BdAddr {
296        self.manager.peer_address(self.index)
297    }
298
299    /// The peer identity key for this connection.
300    pub fn peer_identity(&self) -> Identity {
301        self.manager.peer_identity(self.index)
302    }
303    /// Request a certain security level
304    ///
305    /// For a peripheral this may cause the peripheral to send a security request. For a central
306    /// this may cause the central to send a pairing request.
307    ///
308    /// If the link is already encrypted then this will always generate an error.
309    ///
310    pub fn request_security(&self) -> Result<(), Error> {
311        self.manager.request_security(self.index)
312    }
313
314    /// Get the encrypted state of the connection
315    pub fn security_level(&self) -> Result<SecurityLevel, Error> {
316        self.manager.get_security_level(self.index)
317    }
318
319    /// Get whether the connection is set as bondable or not.
320    ///
321    /// This is only relevant before pairing has started.
322    pub fn bondable(&self) -> Result<bool, Error> {
323        self.manager.get_bondable(self.index)
324    }
325
326    /// Set whether the connection is bondable or not.
327    ///
328    /// By default a connection is **not** bondable.
329    ///
330    /// This must be set before pairing is initiated. Once the pairing procedure has started
331    /// this field is ignored.
332    ///
333    /// If both peripheral and central are bondable then the [`ConnectionEvent::PairingComplete`]
334    /// event contains the bond information for the pairing. This bond information should be stored
335    /// in non-volatile memory and restored on reboot using [`Stack::add_bond_information()`].
336    ///
337    /// If any party in a pairing is not bondable the [`ConnectionEvent::PairingComplete`] contains
338    /// a `None` entry for the `bond` member.
339    ///
340    pub fn set_bondable(&self, bondable: bool) -> Result<(), Error> {
341        self.manager.set_bondable(self.index, bondable)
342    }
343
344    /// Confirm that the displayed pass key matches the one displayed on the other party
345    pub fn pass_key_confirm(&self) -> Result<(), Error> {
346        self.manager.pass_key_confirm(self.index, true)
347    }
348
349    /// The displayed pass key does not match the one displayed on the other party
350    pub fn pass_key_cancel(&self) -> Result<(), Error> {
351        self.manager.pass_key_confirm(self.index, false)
352    }
353
354    /// Input the pairing pass key
355    pub fn pass_key_input(&self, pass_key: u32) -> Result<(), Error> {
356        self.manager.pass_key_input(self.index, pass_key)
357    }
358
359    /// Request connection to be disconnected.
360    pub fn disconnect(&self) {
361        self.manager
362            .request_disconnect(self.index, DisconnectReason::RemoteUserTerminatedConn);
363    }
364
365    /// Read metrics for this connection
366    #[cfg(feature = "connection-metrics")]
367    pub fn metrics<F: FnOnce(&ConnectionMetrics) -> R, R>(&self, f: F) -> R {
368        self.manager.metrics(self.index, f)
369    }
370
371    /// The RSSI value for this connection.
372    pub async fn rssi<T>(&self, stack: &Stack<'_, T, P>) -> Result<i8, BleHostError<T::Error>>
373    where
374        T: ControllerCmdSync<ReadRssi>,
375    {
376        let handle = self.handle();
377        let ret = stack.host.command(ReadRssi::new(handle)).await?;
378        Ok(ret.rssi)
379    }
380
381    /// Update phy for this connection.
382    ///
383    /// This updates both TX and RX phy of the connection. For more fine grained control,
384    /// use the LeSetPhy HCI command directly.
385    pub async fn set_phy<T>(&self, stack: &Stack<'_, T, P>, phy: PhyKind) -> Result<(), BleHostError<T::Error>>
386    where
387        T: ControllerCmdAsync<LeSetPhy>,
388    {
389        let all_phys = AllPhys::new()
390            .set_has_no_rx_phy_preference(false)
391            .set_has_no_tx_phy_preference(false);
392        let mut mask = PhyMask::new()
393            .set_le_coded_preferred(false)
394            .set_le_1m_preferred(false)
395            .set_le_2m_preferred(false);
396        let mut options = PhyOptions::default();
397        match phy {
398            PhyKind::Le2M => {
399                mask = mask.set_le_2m_preferred(true);
400            }
401            PhyKind::Le1M => {
402                mask = mask.set_le_1m_preferred(true);
403            }
404            PhyKind::LeCoded => {
405                mask = mask.set_le_coded_preferred(true);
406                options = PhyOptions::S8CodingPreferred;
407            }
408            PhyKind::LeCodedS2 => {
409                mask = mask.set_le_coded_preferred(true);
410                options = PhyOptions::S2CodingPreferred;
411            }
412        }
413        stack
414            .host
415            .async_command(LeSetPhy::new(self.handle(), all_phys, mask, mask, options))
416            .await?;
417        Ok(())
418    }
419
420    /// Read the current phy used for the connection.
421    pub async fn read_phy<T>(&self, stack: &Stack<'_, T, P>) -> Result<(PhyKind, PhyKind), BleHostError<T::Error>>
422    where
423        T: ControllerCmdSync<LeReadPhy>,
424    {
425        let res = stack.host.command(LeReadPhy::new(self.handle())).await?;
426        Ok((res.tx_phy, res.rx_phy))
427    }
428
429    /// Update data length for this connection.
430    pub async fn update_data_length<T>(
431        &self,
432        stack: &Stack<'_, T, P>,
433        length: u16,
434        time_us: u16,
435    ) -> Result<(), BleHostError<T::Error>>
436    where
437        T: ControllerCmdSync<LeSetDataLength> + ControllerCmdSync<LeReadLocalSupportedFeatures>,
438    {
439        let handle = self.handle();
440        // First, check the local supported features to ensure that the connection update is supported.
441        let features = stack.host.command(LeReadLocalSupportedFeatures::new()).await?;
442        if length <= 27 || features.supports_le_data_packet_length_extension() {
443            match stack.host.command(LeSetDataLength::new(handle, length, time_us)).await {
444                Ok(_) => Ok(()),
445                Err(BleHostError::BleHost(crate::Error::Hci(bt_hci::param::Error::UNKNOWN_CONN_IDENTIFIER))) => {
446                    Err(crate::Error::Disconnected.into())
447                }
448                Err(e) => Err(e),
449            }
450        } else {
451            Err(BleHostError::BleHost(Error::InvalidValue))
452        }
453    }
454
455    /// Update connection parameters for this connection.
456    pub async fn update_connection_params<T>(
457        &self,
458        stack: &Stack<'_, T, P>,
459        params: &ConnectParams,
460    ) -> Result<(), BleHostError<T::Error>>
461    where
462        T: ControllerCmdAsync<LeConnUpdate> + ControllerCmdSync<LeReadLocalSupportedFeatures>,
463    {
464        let handle = self.handle();
465        // First, check the local supported features to ensure that the connection update is supported.
466        let features = stack.host.command(LeReadLocalSupportedFeatures::new()).await?;
467        if features.supports_conn_parameters_request_procedure() || self.role() == LeConnRole::Central {
468            match stack.host.async_command(into_le_conn_update(handle, params)).await {
469                Ok(_) => return Ok(()),
470                Err(BleHostError::BleHost(crate::Error::Hci(bt_hci::param::Error::UNKNOWN_CONN_IDENTIFIER))) => {
471                    return Err(crate::Error::Disconnected.into());
472                }
473                Err(BleHostError::BleHost(crate::Error::Hci(bt_hci::param::Error::UNSUPPORTED_REMOTE_FEATURE))) => {
474                    // We tried to send the request as a periperhal but the remote central does not support procedure.
475                    // Use the L2CAP signaling method below instead.
476                    // This code path should never be reached when acting as a central. If a bugged controller implementation
477                    // returns this error code we transmit an invalid L2CAP signal which then is rejected by the remote.
478                }
479                Err(e) => return Err(e),
480            }
481        }
482
483        #[cfg(feature = "connection-params-update")]
484        {
485            // Use L2CAP signaling to update connection parameters
486            info!(
487                "Connection parameters request procedure not supported, use l2cap connection parameter update req instead"
488            );
489            let interval_min: bt_hci::param::Duration<1_250> = bt_hci_duration(params.min_connection_interval);
490            let interval_max: bt_hci::param::Duration<1_250> = bt_hci_duration(params.max_connection_interval);
491            let timeout: bt_hci::param::Duration<10_000> = bt_hci_duration(params.supervision_timeout);
492            let param = ConnParamUpdateReq {
493                interval_min: interval_min.as_u16(),
494                interval_max: interval_max.as_u16(),
495                latency: params.max_latency,
496                timeout: timeout.as_u16(),
497            };
498            stack.host.send_conn_param_update_req(handle, &param).await?;
499        }
500        Ok(())
501    }
502
503    #[cfg(feature = "connection-params-update")]
504    /// Respond to updated parameters.
505    ///
506    /// This should only be called if a request to update the connection parameters was received.
507    pub async fn accept_connection_params<T>(
508        &self,
509        stack: &Stack<'_, T, P>,
510        params: &ConnectParams,
511    ) -> Result<(), BleHostError<T::Error>>
512    where
513        T: ControllerCmdAsync<LeConnUpdate>
514            + ControllerCmdSync<LeReadLocalSupportedFeatures>
515            + ControllerCmdAsync<LeRemoteConnectionParameterRequestReply>
516            + ControllerCmdAsync<LeRemoteConnectionParameterRequestNegativeReply>,
517    {
518        let handle = self.handle();
519        if self.role() == LeConnRole::Central {
520            let features = stack.host.command(LeReadLocalSupportedFeatures::new()).await?;
521            match stack.host.async_command(into_le_conn_update(handle, params)).await {
522                Ok(_) => {
523                    if features.supports_conn_parameters_request_procedure() {
524                        let interval_min: bt_hci::param::Duration<1_250> =
525                            bt_hci_duration(params.min_connection_interval);
526                        let interval_max: bt_hci::param::Duration<1_250> =
527                            bt_hci_duration(params.max_connection_interval);
528                        let timeout: bt_hci::param::Duration<10_000> = bt_hci_duration(params.supervision_timeout);
529                        if let Err(e) = stack
530                            .host
531                            .async_command(LeRemoteConnectionParameterRequestReply::new(
532                                handle,
533                                interval_min,
534                                interval_max,
535                                params.max_latency,
536                                timeout,
537                                bt_hci_duration(params.min_event_length),
538                                bt_hci_duration(params.max_event_length),
539                            ))
540                            .await
541                        {
542                            return Err(e);
543                        }
544                    } else {
545                        // Use L2CAP signaling to update connection parameters
546                        info!(
547                            "Connection parameters request procedure not supported, using l2cap connection parameter update res instead"
548                        );
549                        let param = ConnParamUpdateRes { result: 0 };
550                        stack.host.send_conn_param_update_res(handle, &param).await?;
551                    }
552                    Ok(())
553                }
554                Err(BleHostError::BleHost(crate::Error::Hci(bt_hci::param::Error::UNKNOWN_CONN_IDENTIFIER))) => {
555                    Err(crate::Error::Disconnected.into())
556                }
557                Err(e) => {
558                    info!("Connection parameters request procedure failed");
559                    if features.supports_conn_parameters_request_procedure() {
560                        stack
561                            .host
562                            .async_command(LeRemoteConnectionParameterRequestNegativeReply::new(
563                                handle,
564                                RemoteConnectionParamsRejectReason::UnacceptableConnParameters,
565                            ))
566                            .await?;
567                    } else {
568                        let param = ConnParamUpdateRes { result: 1 };
569                        stack.host.send_conn_param_update_res(handle, &param).await?;
570                    }
571                    Err(e)
572                }
573            }
574        } else {
575            Err(crate::Error::NotSupported.into())
576        }
577    }
578
579    /// Transform BLE connection into a `GattConnection`
580    #[cfg(feature = "gatt")]
581    pub fn with_attribute_server<
582        'values,
583        'server,
584        M: RawMutex,
585        const ATT_MAX: usize,
586        const CCCD_MAX: usize,
587        const CONN_MAX: usize,
588    >(
589        self,
590        server: &'server AttributeServer<'values, M, P, ATT_MAX, CCCD_MAX, CONN_MAX>,
591    ) -> Result<GattConnection<'stack, 'server, P>, Error> {
592        GattConnection::try_new(self, server)
593    }
594}
595
596fn into_le_conn_update(handle: ConnHandle, params: &ConnectParams) -> LeConnUpdate {
597    LeConnUpdate::new(
598        handle,
599        bt_hci_duration(params.min_connection_interval),
600        bt_hci_duration(params.max_connection_interval),
601        params.max_latency,
602        bt_hci_duration(params.supervision_timeout),
603        bt_hci_duration(params.min_event_length),
604        bt_hci_duration(params.max_event_length),
605    )
606}