trouble_host/
lib.rs

1#![no_std]
2#![allow(dead_code)]
3#![allow(unused_variables)]
4#![allow(clippy::needless_lifetimes)]
5#![doc = include_str!(concat!("../", env!("CARGO_PKG_README")))]
6#![warn(missing_docs)]
7
8use core::mem::MaybeUninit;
9
10use advertise::AdvertisementDataError;
11use bt_hci::cmd::status::ReadRssi;
12use bt_hci::cmd::{AsyncCmd, SyncCmd};
13use bt_hci::param::{AddrKind, BdAddr};
14use bt_hci::FromHciBytesError;
15#[cfg(feature = "security")]
16use heapless::Vec;
17use rand_core::{CryptoRng, RngCore};
18
19use crate::att::AttErrorCode;
20use crate::channel_manager::ChannelStorage;
21use crate::connection_manager::ConnectionStorage;
22#[cfg(feature = "security")]
23pub use crate::security_manager::{BondInformation, IdentityResolvingKey, LongTermKey};
24
25/// Number of bonding information stored
26pub(crate) const BI_COUNT: usize = 10; // Should be configurable
27
28mod fmt;
29
30#[cfg(not(any(feature = "central", feature = "peripheral")))]
31compile_error!("Must enable at least one of the `central` or `peripheral` features");
32
33pub mod att;
34#[cfg(feature = "central")]
35pub mod central;
36mod channel_manager;
37mod codec;
38mod command;
39pub mod config;
40mod connection_manager;
41mod cursor;
42#[cfg(feature = "default-packet-pool")]
43mod packet_pool;
44mod pdu;
45#[cfg(feature = "peripheral")]
46pub mod peripheral;
47#[cfg(feature = "security")]
48mod security_manager;
49pub mod types;
50
51#[cfg(feature = "central")]
52use central::*;
53#[cfg(feature = "peripheral")]
54use peripheral::*;
55
56pub mod advertise;
57pub mod connection;
58#[cfg(feature = "gatt")]
59pub mod gap;
60pub mod l2cap;
61#[cfg(feature = "scan")]
62pub mod scan;
63
64#[cfg(test)]
65pub(crate) mod mock_controller;
66
67pub(crate) mod host;
68use host::{AdvHandleState, BleHost, HostMetrics, Runner};
69
70pub mod prelude {
71    //! Convenience include of most commonly used types.
72    pub use bt_hci::controller::ExternalController;
73    pub use bt_hci::param::{AddrKind, BdAddr, LeConnRole as Role, PhyKind, PhyMask};
74    pub use bt_hci::transport::SerialTransport;
75    pub use bt_hci::uuid::*;
76    #[cfg(feature = "derive")]
77    pub use heapless::String as HeaplessString;
78    #[cfg(feature = "derive")]
79    pub use trouble_host_macros::*;
80
81    pub use super::att::AttErrorCode;
82    pub use super::{BleHostError, Controller, Error, Host, HostResources, Packet, PacketPool, Stack};
83    #[cfg(feature = "peripheral")]
84    pub use crate::advertise::*;
85    #[cfg(feature = "gatt")]
86    pub use crate::attribute::*;
87    #[cfg(feature = "gatt")]
88    pub use crate::attribute_server::*;
89    #[cfg(feature = "central")]
90    pub use crate::central::*;
91    pub use crate::connection::*;
92    #[cfg(feature = "gatt")]
93    pub use crate::gap::*;
94    #[cfg(feature = "gatt")]
95    pub use crate::gatt::*;
96    pub use crate::host::{ControlRunner, EventHandler, HostMetrics, Runner, RxRunner, TxRunner};
97    pub use crate::l2cap::*;
98    #[cfg(feature = "default-packet-pool")]
99    pub use crate::packet_pool::DefaultPacketPool;
100    pub use crate::pdu::Sdu;
101    #[cfg(feature = "peripheral")]
102    pub use crate::peripheral::*;
103    #[cfg(feature = "scan")]
104    pub use crate::scan::*;
105    #[cfg(feature = "gatt")]
106    pub use crate::types::gatt_traits::{AsGatt, FixedGattValue, FromGatt};
107    pub use crate::{Address, Identity};
108}
109
110#[cfg(feature = "gatt")]
111pub mod attribute;
112#[cfg(feature = "gatt")]
113mod attribute_server;
114#[cfg(feature = "gatt")]
115pub mod gatt;
116
117/// A BLE address.
118/// Every BLE device is identified by a unique *Bluetooth Device Address*, which is a 48-bit identifier similar to a MAC address. BLE addresses are categorized into two main types: *Public* and *Random*.
119///
120/// A Public Address is globally unique and assigned by the IEEE. It remains constant and is typically used by devices requiring a stable identifier.
121///
122/// A Random Address can be *static* or *dynamic*:
123///
124/// - *Static Random Address*: Remains fixed until the device restarts or resets.
125/// - *Private Random Address*: Changes periodically for privacy purposes. It can be *Resolvable* (can be linked to the original device using an Identity Resolving Key) or *Non-Resolvable* (completely anonymous).
126///
127/// Random addresses enhance privacy by preventing device tracking.
128#[derive(Debug, Clone, Copy, PartialEq)]
129pub struct Address {
130    /// Address type.
131    pub kind: AddrKind,
132    /// Address value.
133    pub addr: BdAddr,
134}
135
136impl Address {
137    /// Create a new random address.
138    pub fn random(val: [u8; 6]) -> Self {
139        Self {
140            kind: AddrKind::RANDOM,
141            addr: BdAddr::new(val),
142        }
143    }
144
145    /// To bytes
146    pub fn to_bytes(&self) -> [u8; 7] {
147        let mut bytes = [0; 7];
148        bytes[0] = self.kind.into_inner();
149        let mut addr_bytes = self.addr.into_inner();
150        addr_bytes.reverse();
151        bytes[1..].copy_from_slice(&addr_bytes);
152        bytes
153    }
154}
155
156impl core::fmt::Display for Address {
157    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
158        let a = self.addr.into_inner();
159        write!(
160            f,
161            "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
162            a[5], a[4], a[3], a[2], a[1], a[0]
163        )
164    }
165}
166
167#[cfg(feature = "defmt")]
168impl defmt::Format for Address {
169    fn format(&self, fmt: defmt::Formatter) {
170        let a = self.addr.into_inner();
171        defmt::write!(
172            fmt,
173            "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
174            a[5],
175            a[4],
176            a[3],
177            a[2],
178            a[1],
179            a[0]
180        )
181    }
182}
183
184/// Identity of a peer device
185///
186/// Sometimes we have to save both the address and the IRK.
187/// Because sometimes the peer uses the static or public address even though the IRK is sent.
188/// In this case, the IRK exists but the used address is not RPA.
189/// Should `Address` be used instead?
190#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
191pub struct Identity {
192    /// Random static or public address
193    pub bd_addr: BdAddr,
194
195    /// Identity Resolving Key
196    #[cfg(feature = "security")]
197    pub irk: Option<IdentityResolvingKey>,
198}
199
200#[cfg(feature = "defmt")]
201impl defmt::Format for Identity {
202    fn format(&self, fmt: defmt::Formatter) {
203        defmt::write!(fmt, "BdAddr({:X}) ", self.bd_addr);
204        #[cfg(feature = "security")]
205        defmt::write!(fmt, "Irk({:X})", self.irk);
206    }
207}
208
209impl Identity {
210    /// Check whether the address matches the identity
211    pub fn match_address(&self, address: &BdAddr) -> bool {
212        if self.bd_addr == *address {
213            return true;
214        }
215        #[cfg(feature = "security")]
216        if let Some(irk) = self.irk {
217            return irk.resolve_address(address);
218        }
219        false
220    }
221
222    /// Check whether the given identity matches current identity
223    pub fn match_identity(&self, identity: &Identity) -> bool {
224        if self.match_address(&identity.bd_addr) {
225            return true;
226        }
227        #[cfg(feature = "security")]
228        if let Some(irk) = identity.irk {
229            if let Some(current_irk) = self.irk {
230                return irk == current_irk;
231            } else {
232                return irk.resolve_address(&self.bd_addr);
233            }
234        }
235        false
236    }
237}
238
239/// Errors returned by the host.
240#[derive(Debug)]
241#[cfg_attr(feature = "defmt", derive(defmt::Format))]
242pub enum BleHostError<E> {
243    /// Error from the controller.
244    Controller(E),
245    /// Error from the host.
246    BleHost(Error),
247}
248
249/// How many bytes of invalid data to capture in the error variants before truncating.
250pub const MAX_INVALID_DATA_LEN: usize = 16;
251
252/// Errors related to Host.
253#[derive(Debug, PartialEq)]
254#[cfg_attr(feature = "defmt", derive(defmt::Format))]
255pub enum Error {
256    /// Error encoding parameters for HCI commands.
257    Hci(bt_hci::param::Error),
258    /// Error decoding responses from HCI commands.
259    HciDecode(FromHciBytesError),
260    /// Error from the Attribute Protocol.
261    Att(AttErrorCode),
262    #[cfg(feature = "security")]
263    /// Error from the security manager
264    Security(crate::security_manager::Reason),
265    /// Insufficient space in the buffer.
266    InsufficientSpace,
267    /// Invalid value.
268    InvalidValue,
269
270    /// Unexpected data length.
271    ///
272    /// This happens if the attribute data length doesn't match the input length size,
273    /// and the attribute is deemed as *not* having variable length due to the characteristic's
274    /// `MAX_SIZE` and `MIN_SIZE` being defined as equal.
275    UnexpectedDataLength {
276        /// Expected length.
277        expected: usize,
278        /// Actual length.
279        actual: usize,
280    },
281
282    /// Error converting from GATT value.
283    CannotConstructGattValue([u8; MAX_INVALID_DATA_LEN]),
284
285    /// Scan config filter accept list is empty.
286    ConfigFilterAcceptListIsEmpty,
287
288    /// Unexpected GATT response.
289    UnexpectedGattResponse,
290
291    /// Received characteristic declaration data shorter than the minimum required length (5 bytes).
292    MalformedCharacteristicDeclaration {
293        /// Expected length.
294        expected: usize,
295        /// Actual length.
296        actual: usize,
297    },
298
299    /// Failed to decode the data structure within a characteristic declaration attribute value.
300    InvalidCharacteristicDeclarationData,
301
302    /// Invalid CCCD handle length.
303    InvalidCccdHandleLength(usize),
304
305    /// Failed to finalize the packet.
306    FailedToFinalize {
307        /// Expected length.
308        expected: usize,
309        /// Actual length.
310        actual: usize,
311    },
312
313    /// Codec error.
314    CodecError(codec::Error),
315
316    /// Extended advertising not supported.
317    ExtendedAdvertisingNotSupported,
318
319    /// Invalid UUID length.
320    InvalidUuidLength(usize),
321
322    /// Error decoding advertisement data.
323    Advertisement(AdvertisementDataError),
324    /// Invalid l2cap channel id provided.
325    InvalidChannelId,
326    /// No l2cap channel available.
327    NoChannelAvailable,
328    /// Resource not found.
329    NotFound,
330    /// Invalid state.
331    InvalidState,
332    /// Out of memory.
333    OutOfMemory,
334    /// Unsupported operation.
335    NotSupported,
336    /// L2cap channel closed.
337    ChannelClosed,
338    /// Operation timed out.
339    Timeout,
340    /// Controller is busy.
341    Busy,
342    /// No send permits available.
343    NoPermits,
344    /// Connection is disconnected.
345    Disconnected,
346    /// Connection limit has been reached.
347    ConnectionLimitReached,
348    /// GATT subscriber limit has been reached.
349    ///
350    /// The limit can be modified using the `gatt-client-notification-max-subscribers-N` features.
351    GattSubscriberLimitReached,
352    /// Other error.
353    Other,
354}
355
356impl<E> From<Error> for BleHostError<E> {
357    fn from(value: Error) -> Self {
358        Self::BleHost(value)
359    }
360}
361
362impl From<FromHciBytesError> for Error {
363    fn from(error: FromHciBytesError) -> Self {
364        Self::HciDecode(error)
365    }
366}
367
368impl From<AttErrorCode> for Error {
369    fn from(error: AttErrorCode) -> Self {
370        Self::Att(error)
371    }
372}
373
374impl<E> From<bt_hci::cmd::Error<E>> for BleHostError<E> {
375    fn from(error: bt_hci::cmd::Error<E>) -> Self {
376        match error {
377            bt_hci::cmd::Error::Hci(p) => Self::BleHost(Error::Hci(p)),
378            bt_hci::cmd::Error::Io(p) => Self::Controller(p),
379        }
380    }
381}
382
383impl<E> From<bt_hci::param::Error> for BleHostError<E> {
384    fn from(error: bt_hci::param::Error) -> Self {
385        Self::BleHost(Error::Hci(error))
386    }
387}
388
389impl From<codec::Error> for Error {
390    fn from(error: codec::Error) -> Self {
391        match error {
392            codec::Error::InsufficientSpace => Error::InsufficientSpace,
393            codec::Error::InvalidValue => Error::CodecError(error),
394        }
395    }
396}
397
398impl<E> From<codec::Error> for BleHostError<E> {
399    fn from(error: codec::Error) -> Self {
400        match error {
401            codec::Error::InsufficientSpace => BleHostError::BleHost(Error::InsufficientSpace),
402            codec::Error::InvalidValue => BleHostError::BleHost(Error::InvalidValue),
403        }
404    }
405}
406
407use bt_hci::cmd::controller_baseband::*;
408use bt_hci::cmd::info::*;
409use bt_hci::cmd::le::*;
410use bt_hci::cmd::link_control::*;
411use bt_hci::controller::{ControllerCmdAsync, ControllerCmdSync};
412
413/// Trait that defines the controller implementation required by the host.
414///
415/// The controller must implement the required commands and events to be able to be used with Trouble.
416pub trait Controller:
417    bt_hci::controller::Controller
418    + embedded_io::ErrorType
419    + ControllerCmdSync<LeReadBufferSize>
420    + ControllerCmdSync<Disconnect>
421    + ControllerCmdSync<SetEventMask>
422    + ControllerCmdSync<SetEventMaskPage2>
423    + ControllerCmdSync<LeSetEventMask>
424    + ControllerCmdSync<LeSetRandomAddr>
425    + ControllerCmdSync<HostBufferSize>
426    + ControllerCmdAsync<LeConnUpdate>
427    + ControllerCmdSync<LeReadFilterAcceptListSize>
428    + ControllerCmdSync<SetControllerToHostFlowControl>
429    + ControllerCmdSync<Reset>
430    + ControllerCmdSync<ReadRssi>
431    + ControllerCmdSync<LeCreateConnCancel>
432    + ControllerCmdSync<LeSetScanEnable>
433    + ControllerCmdSync<LeSetExtScanEnable>
434    + ControllerCmdAsync<LeCreateConn>
435    + ControllerCmdSync<LeClearFilterAcceptList>
436    + ControllerCmdSync<LeAddDeviceToFilterAcceptList>
437    + for<'t> ControllerCmdSync<LeSetAdvEnable>
438    + for<'t> ControllerCmdSync<LeSetExtAdvEnable<'t>>
439    + for<'t> ControllerCmdSync<HostNumberOfCompletedPackets<'t>>
440    + ControllerCmdSync<LeReadBufferSize>
441    + for<'t> ControllerCmdSync<LeSetAdvData>
442    + ControllerCmdSync<LeSetAdvParams>
443    + for<'t> ControllerCmdSync<LeSetAdvEnable>
444    + for<'t> ControllerCmdSync<LeSetScanResponseData>
445    + ControllerCmdSync<LeLongTermKeyRequestReply>
446    + ControllerCmdAsync<LeEnableEncryption>
447    + ControllerCmdSync<ReadBdAddr>
448{
449}
450
451impl<
452        C: bt_hci::controller::Controller
453            + embedded_io::ErrorType
454            + ControllerCmdSync<LeReadBufferSize>
455            + ControllerCmdSync<Disconnect>
456            + ControllerCmdSync<SetEventMask>
457            + ControllerCmdSync<SetEventMaskPage2>
458            + ControllerCmdSync<LeSetEventMask>
459            + ControllerCmdSync<LeSetRandomAddr>
460            + ControllerCmdSync<HostBufferSize>
461            + ControllerCmdAsync<LeConnUpdate>
462            + ControllerCmdSync<LeReadFilterAcceptListSize>
463            + ControllerCmdSync<LeClearFilterAcceptList>
464            + ControllerCmdSync<LeAddDeviceToFilterAcceptList>
465            + ControllerCmdSync<SetControllerToHostFlowControl>
466            + ControllerCmdSync<Reset>
467            + ControllerCmdSync<ReadRssi>
468            + ControllerCmdSync<LeSetScanEnable>
469            + ControllerCmdSync<LeSetExtScanEnable>
470            + ControllerCmdSync<LeCreateConnCancel>
471            + ControllerCmdAsync<LeCreateConn>
472            + for<'t> ControllerCmdSync<LeSetAdvEnable>
473            + for<'t> ControllerCmdSync<LeSetExtAdvEnable<'t>>
474            + for<'t> ControllerCmdSync<HostNumberOfCompletedPackets<'t>>
475            + ControllerCmdSync<LeReadBufferSize>
476            + for<'t> ControllerCmdSync<LeSetAdvData>
477            + ControllerCmdSync<LeSetAdvParams>
478            + for<'t> ControllerCmdSync<LeSetAdvEnable>
479            + for<'t> ControllerCmdSync<LeSetScanResponseData>
480            + ControllerCmdSync<LeLongTermKeyRequestReply>
481            + ControllerCmdAsync<LeEnableEncryption>
482            + ControllerCmdSync<ReadBdAddr>,
483    > Controller for C
484{
485}
486
487/// A Packet is a byte buffer for packet data.
488/// Similar to a `Vec<u8>` it has a length and a capacity.
489pub trait Packet: Sized + AsRef<[u8]> + AsMut<[u8]> {}
490
491/// A Packet Pool that can allocate packets of the desired size.
492///
493/// The MTU is usually related to the MTU of l2cap payloads.
494pub trait PacketPool: 'static {
495    /// Packet type provided by this pool.
496    type Packet: Packet;
497
498    /// The maximum size a packet can have.
499    const MTU: usize;
500
501    /// Allocate a new buffer with space for `MTU` bytes.
502    /// Return `None` when the allocation can't be fulfilled.
503    ///
504    /// This function is called by the L2CAP driver when it needs
505    /// space to receive a packet into.
506    /// It will later call `from_raw_parts` with the buffer and the
507    /// amount of bytes it has received.
508    fn allocate() -> Option<Self::Packet>;
509
510    /// Capacity of this pool in the number of packets.
511    fn capacity() -> usize;
512}
513
514/// HostResources holds the resources used by the host.
515///
516/// The l2cap packet pool is used by the host to handle inbound data, by allocating space for
517/// incoming packets and dispatching to the appropriate connection and channel.
518pub struct HostResources<P: PacketPool, const CONNS: usize, const CHANNELS: usize, const ADV_SETS: usize = 1> {
519    connections: MaybeUninit<[ConnectionStorage<P::Packet>; CONNS]>,
520    channels: MaybeUninit<[ChannelStorage<P::Packet>; CHANNELS]>,
521    advertise_handles: MaybeUninit<[AdvHandleState; ADV_SETS]>,
522}
523
524impl<P: PacketPool, const CONNS: usize, const CHANNELS: usize, const ADV_SETS: usize> Default
525    for HostResources<P, CONNS, CHANNELS, ADV_SETS>
526{
527    fn default() -> Self {
528        Self::new()
529    }
530}
531
532impl<P: PacketPool, const CONNS: usize, const CHANNELS: usize, const ADV_SETS: usize>
533    HostResources<P, CONNS, CHANNELS, ADV_SETS>
534{
535    /// Create a new instance of host resources.
536    pub const fn new() -> Self {
537        Self {
538            connections: MaybeUninit::uninit(),
539            channels: MaybeUninit::uninit(),
540            advertise_handles: MaybeUninit::uninit(),
541        }
542    }
543}
544
545/// Create a new instance of the BLE host using the provided controller implementation and
546/// the resource configuration
547pub fn new<
548    'resources,
549    C: Controller,
550    P: PacketPool,
551    const CONNS: usize,
552    const CHANNELS: usize,
553    const ADV_SETS: usize,
554>(
555    controller: C,
556    resources: &'resources mut HostResources<P, CONNS, CHANNELS, ADV_SETS>,
557) -> Stack<'resources, C, P> {
558    unsafe fn transmute_slice<T>(x: &mut [T]) -> &'static mut [T] {
559        unsafe { core::mem::transmute(x) }
560    }
561
562    // Safety:
563    // - HostResources has the exceeding lifetime as the returned Stack.
564    // - Internal lifetimes are elided (made 'static) to simplify API usage
565    // - This _should_ be OK, because there are no references held to the resources
566    //   when the stack is shut down.
567
568    let connections: &mut [ConnectionStorage<P::Packet>] =
569        &mut *resources.connections.write([const { ConnectionStorage::new() }; CONNS]);
570    let connections: &'resources mut [ConnectionStorage<P::Packet>] = unsafe { transmute_slice(connections) };
571
572    let channels = &mut *resources.channels.write([const { ChannelStorage::new() }; CHANNELS]);
573    let channels: &'static mut [ChannelStorage<P::Packet>] = unsafe { transmute_slice(channels) };
574
575    let advertise_handles = &mut *resources.advertise_handles.write([AdvHandleState::None; ADV_SETS]);
576    let advertise_handles: &'static mut [AdvHandleState] = unsafe { transmute_slice(advertise_handles) };
577    let host: BleHost<'_, C, P> = BleHost::new(controller, connections, channels, advertise_handles);
578
579    Stack { host }
580}
581
582/// Contains the host stack
583pub struct Stack<'stack, C, P: PacketPool> {
584    host: BleHost<'stack, C, P>,
585}
586
587/// Host components.
588#[non_exhaustive]
589pub struct Host<'stack, C, P: PacketPool> {
590    /// Central role
591    #[cfg(feature = "central")]
592    pub central: Central<'stack, C, P>,
593    /// Peripheral role
594    #[cfg(feature = "peripheral")]
595    pub peripheral: Peripheral<'stack, C, P>,
596    /// Host runner
597    pub runner: Runner<'stack, C, P>,
598}
599
600impl<'stack, C: Controller, P: PacketPool> Stack<'stack, C, P> {
601    /// Set the random address used by this host.
602    pub fn set_random_address(mut self, address: Address) -> Self {
603        self.host.address.replace(address);
604        #[cfg(feature = "security")]
605        self.host.connections.security_manager.set_local_address(address);
606        self
607    }
608    /// Set the random generator seed for random generator used by security manager
609    pub fn set_random_generator_seed<RNG: RngCore + CryptoRng>(self, _random_generator: &mut RNG) -> Self {
610        #[cfg(feature = "security")]
611        {
612            let mut random_seed = [0u8; 32];
613            _random_generator.fill_bytes(&mut random_seed);
614            self.host
615                .connections
616                .security_manager
617                .set_random_generator_seed(random_seed);
618        }
619        self
620    }
621
622    /// Build the stack.
623    pub fn build(&'stack self) -> Host<'stack, C, P> {
624        #[cfg(all(feature = "security", not(feature = "dev-disable-csprng-seed-requirement")))]
625        {
626            if !self.host.connections.security_manager.get_random_generator_seeded() {
627                panic!(
628                    "The security manager random number generator has not been seeded from a cryptographically secure random number generator"
629                )
630            }
631        }
632        Host {
633            #[cfg(feature = "central")]
634            central: Central::new(self),
635            #[cfg(feature = "peripheral")]
636            peripheral: Peripheral::new(self),
637            runner: Runner::new(self),
638        }
639    }
640
641    /// Run a HCI command and return the response.
642    pub async fn command<T>(&self, cmd: T) -> Result<T::Return, BleHostError<C::Error>>
643    where
644        T: SyncCmd,
645        C: ControllerCmdSync<T>,
646    {
647        self.host.command(cmd).await
648    }
649
650    /// Run an async HCI command where the response will generate an event later.
651    pub async fn async_command<T>(&self, cmd: T) -> Result<(), BleHostError<C::Error>>
652    where
653        T: AsyncCmd,
654        C: ControllerCmdAsync<T>,
655    {
656        self.host.async_command(cmd).await
657    }
658
659    /// Read current host metrics
660    pub fn metrics<F: FnOnce(&HostMetrics) -> R, R>(&self, f: F) -> R {
661        self.host.metrics(f)
662    }
663
664    /// Log status information of the host
665    pub fn log_status(&self, verbose: bool) {
666        self.host.log_status(verbose);
667    }
668
669    #[cfg(feature = "security")]
670    /// Get bonded devices
671    pub fn add_bond_information(&self, bond_information: BondInformation) -> Result<(), Error> {
672        self.host
673            .connections
674            .security_manager
675            .add_bond_information(bond_information)
676    }
677
678    #[cfg(feature = "security")]
679    /// Remove a bonded device
680    pub fn remove_bond_information(&self, identity: Identity) -> Result<(), Error> {
681        self.host.connections.security_manager.remove_bond_information(identity)
682    }
683
684    #[cfg(feature = "security")]
685    /// Get bonded devices
686    pub fn get_bond_information(&self) -> Vec<BondInformation, BI_COUNT> {
687        self.host.connections.security_manager.get_bond_information()
688    }
689}