Skip to main content

fuchsia_bluetooth/types/
bonding_data.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use fidl_fuchsia_bluetooth as bt;
6use fidl_fuchsia_bluetooth_sys as sys;
7#[cfg(target_os = "fuchsia")]
8use fuchsia_inspect as inspect;
9use std::fmt;
10
11use crate::error::Error;
12#[cfg(target_os = "fuchsia")]
13use crate::inspect::{InspectData, IsInspectable, ToProperty};
14use crate::types::uuid::Uuid;
15use crate::types::{Address, OneOrBoth, PeerId};
16
17#[derive(Debug)]
18#[cfg(target_os = "fuchsia")]
19struct LeInspect {
20    _inspect: inspect::Node,
21    _services: inspect::StringProperty,
22    _connection_interval: Option<inspect::UintProperty>,
23    _connection_latency: Option<inspect::UintProperty>,
24    _supervision_timeout: Option<inspect::UintProperty>,
25    _peer_ltk_authenticated: inspect::UintProperty,
26    _peer_ltk_secure_connections: inspect::UintProperty,
27    _peer_ltk_encryption_key_size: Option<inspect::UintProperty>,
28    _local_ltk_authenticated: inspect::UintProperty,
29    _local_ltk_secure_connections: inspect::UintProperty,
30    _local_ltk_encryption_key_size: Option<inspect::UintProperty>,
31    _irk_authenticated: inspect::UintProperty,
32    _irk_secure_connections: inspect::UintProperty,
33    _irk_encryption_key_size: Option<inspect::UintProperty>,
34    _csrk_authenticated: inspect::UintProperty,
35    _csrk_secure_connections: inspect::UintProperty,
36    _csrk_encryption_key_size: Option<inspect::UintProperty>,
37}
38
39#[cfg(target_os = "fuchsia")]
40impl LeInspect {
41    fn new(d: &LeBondData, inspect: inspect::Node) -> LeInspect {
42        LeInspect {
43            _services: inspect.create_string("services", d.services.to_property()),
44
45            _connection_interval: d
46                .connection_parameters
47                .as_ref()
48                .map(|p| inspect.create_uint("connection_interval", p.connection_interval as u64)),
49            _connection_latency: d
50                .connection_parameters
51                .as_ref()
52                .map(|p| inspect.create_uint("connection_latency", p.connection_latency as u64)),
53            _supervision_timeout: d
54                .connection_parameters
55                .as_ref()
56                .map(|p| inspect.create_uint("supervision_timeout", p.supervision_timeout as u64)),
57
58            _peer_ltk_authenticated: inspect.create_uint(
59                "peer_ltk_authenticated",
60                d.peer_ltk.as_ref().map(|ltk| ltk.key.security.authenticated).to_property(),
61            ),
62            _peer_ltk_secure_connections: inspect.create_uint(
63                "peer_ltk_secure_connections",
64                d.peer_ltk.as_ref().map(|ltk| ltk.key.security.secure_connections).to_property(),
65            ),
66            _peer_ltk_encryption_key_size: d.peer_ltk.as_ref().map(|ltk| {
67                inspect.create_uint(
68                    "peer_ltk_encryption_key_size",
69                    ltk.key.security.encryption_key_size as u64,
70                )
71            }),
72
73            _local_ltk_authenticated: inspect.create_uint(
74                "local_ltk_authenticated",
75                d.local_ltk.as_ref().map(|ltk| ltk.key.security.authenticated).to_property(),
76            ),
77            _local_ltk_secure_connections: inspect.create_uint(
78                "local_ltk_secure_connections",
79                d.local_ltk.as_ref().map(|ltk| ltk.key.security.secure_connections).to_property(),
80            ),
81            _local_ltk_encryption_key_size: d.local_ltk.as_ref().map(|ltk| {
82                inspect.create_uint(
83                    "local_ltk_encryption_key_size",
84                    ltk.key.security.encryption_key_size as u64,
85                )
86            }),
87
88            _irk_authenticated: inspect.create_uint(
89                "irk_authenticated",
90                d.irk.as_ref().map(|k| k.security.authenticated).to_property(),
91            ),
92            _irk_secure_connections: inspect.create_uint(
93                "irk_secure_connections",
94                d.irk.as_ref().map(|k| k.security.secure_connections).to_property(),
95            ),
96            _irk_encryption_key_size: d.irk.as_ref().map(|irk| {
97                inspect
98                    .create_uint("irk_encryption_key_size", irk.security.encryption_key_size as u64)
99            }),
100            _csrk_authenticated: inspect.create_uint(
101                "csrk_authenticated",
102                d.csrk.as_ref().map(|k| k.security.authenticated).to_property(),
103            ),
104            _csrk_secure_connections: inspect.create_uint(
105                "csrk_secure_connections",
106                d.csrk.as_ref().map(|k| k.security.secure_connections).to_property(),
107            ),
108            _csrk_encryption_key_size: d.csrk.as_ref().map(|k| {
109                inspect
110                    .create_uint("csrk_encryption_key_size", k.security.encryption_key_size as u64)
111            }),
112
113            _inspect: inspect,
114        }
115    }
116}
117
118#[derive(Debug)]
119#[cfg(target_os = "fuchsia")]
120struct BredrInspect {
121    _inspect: inspect::Node,
122    _role_preference: Option<inspect::StringProperty>,
123    _services: inspect::StringProperty,
124    _lk_authenticated: inspect::UintProperty,
125    _lk_secure_connections: inspect::UintProperty,
126    _lk_encryption_key_size: Option<inspect::UintProperty>,
127}
128
129#[cfg(target_os = "fuchsia")]
130impl BredrInspect {
131    fn new(d: &BredrBondData, inspect: inspect::Node) -> BredrInspect {
132        BredrInspect {
133            _role_preference: d.role_preference.as_ref().map(|role| {
134                inspect.create_string(
135                    "role_preference",
136                    match role {
137                        bt::ConnectionRole::Leader => "Leader",
138                        bt::ConnectionRole::Follower => "Follower",
139                    },
140                )
141            }),
142            _services: inspect.create_string("services", d.services.to_property()),
143            _lk_authenticated: inspect.create_uint(
144                "authenticated",
145                d.link_key.as_ref().map(|ltk| ltk.security.authenticated).to_property(),
146            ),
147            _lk_secure_connections: inspect.create_uint(
148                "secure_connections",
149                d.link_key.as_ref().map(|ltk| ltk.security.secure_connections).to_property(),
150            ),
151            _lk_encryption_key_size: d.link_key.as_ref().map(|ltk| {
152                inspect.create_uint("encryption_key_size", ltk.security.encryption_key_size as u64)
153            }),
154            _inspect: inspect,
155        }
156    }
157}
158
159#[derive(Debug)]
160#[cfg(target_os = "fuchsia")]
161pub struct BondingDataInspect {
162    _inspect: inspect::Node,
163    _address_type: inspect::StringProperty,
164    _device_class: Option<inspect::UintProperty>,
165    _le_inspect: Option<LeInspect>,
166    _bredr_inspect: Option<BredrInspect>,
167}
168
169#[cfg(target_os = "fuchsia")]
170impl InspectData<BondingData> for BondingDataInspect {
171    fn new(bd: &BondingData, inspect: inspect::Node) -> BondingDataInspect {
172        BondingDataInspect {
173            _address_type: inspect.create_string("address_type", bd.address.address_type_string()),
174            _device_class: bd
175                .device_class
176                .map(|d| inspect.create_uint("device_class", d.value.into())),
177            _le_inspect: bd.le().map(|d| LeInspect::new(d, inspect.create_child("le"))),
178            _bredr_inspect: bd.bredr().map(|d| BredrInspect::new(d, inspect.create_child("bredr"))),
179            _inspect: inspect,
180        }
181    }
182}
183
184#[cfg(target_os = "fuchsia")]
185impl IsInspectable for BondingData {
186    type I = BondingDataInspect;
187}
188
189const REDACTED_KEY: &str = "<REDACTED>";
190
191struct RedactedPeerKey<'a>(&'a sys::PeerKey);
192
193impl fmt::Debug for RedactedPeerKey<'_> {
194    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195        f.debug_struct("PeerKey")
196            .field("security", &self.0.security)
197            .field("data", &REDACTED_KEY)
198            .finish()
199    }
200}
201
202struct RedactedLtk<'a>(&'a sys::Ltk);
203
204impl fmt::Debug for RedactedLtk<'_> {
205    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206        f.debug_struct("Ltk")
207            .field("key", &RedactedPeerKey(&self.0.key))
208            .field("ediv", &self.0.ediv)
209            .field("rand", &self.0.rand)
210            .finish()
211    }
212}
213
214/// Bluetooth Low Energy specific bonding data
215#[derive(Clone, PartialEq)]
216pub struct LeBondData {
217    /// The peer's preferred connection parameters, if known.
218    pub connection_parameters: Option<sys::LeConnectionParameters>,
219    /// Known GATT service UUIDs.
220    pub services: Vec<Uuid>,
221    /// LE long-term key generated and distributed by the peer device. This key is used when the
222    /// peer is the follower (i.e. the peer is in the LE peripheral role).
223    ///
224    /// Note: In LE legacy pairing, both sides are allowed to generate and distribute a link key.
225    /// In Secure Connections pairing, both sides generate the same LTK and hence the `peer_ltk` and
226    /// `local_ltk` values are identical.
227    pub peer_ltk: Option<sys::Ltk>,
228    /// LE long-term key generated and distributed by the local bt-host. This key is used when the
229    /// peer is the leader (i.e. the peer in the LE central role).
230    ///
231    /// Note: In LE legacy pairing, both sides are allowed to generate and distribute a link key.
232    /// In Secure Connections pairing, both sides generate the same LTK and hence the `peer_ltk` and
233    /// `local_ltk` values are identical.
234    pub local_ltk: Option<sys::Ltk>,
235    /// Identity Resolving RemoteKey used to generate and resolve random addresses.
236    pub irk: Option<sys::PeerKey>,
237    /// Connection Signature Resolving RemoteKey used for data signing without encryption.
238    pub csrk: Option<sys::PeerKey>,
239}
240
241impl fmt::Debug for LeBondData {
242    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243        f.debug_struct("LeBondData")
244            .field("connection_parameters", &self.connection_parameters)
245            .field("services", &self.services)
246            .field("peer_ltk", &self.peer_ltk.as_ref().map(RedactedLtk))
247            .field("local_ltk", &self.local_ltk.as_ref().map(RedactedLtk))
248            .field("irk", &self.irk.as_ref().map(RedactedPeerKey))
249            .field("csrk", &self.csrk.as_ref().map(RedactedPeerKey))
250            .finish()
251    }
252}
253
254/// Bluetooth BR/EDR (Classic) specific bonding data
255#[derive(Clone, PartialEq)]
256pub struct BredrBondData {
257    /// True if the peer prefers to lead the piconet. This is determined by role switch procedures.
258    /// Paging and connecting from a peer does not automatically set this flag.
259    pub role_preference: Option<bt::ConnectionRole>,
260    /// Known service UUIDs obtained from EIR data or SDP.
261    pub services: Vec<Uuid>,
262    /// The semi-permanent BR/EDR key. Present if link was paired with Secure Simple Pairing or
263    /// stronger.
264    pub link_key: Option<sys::PeerKey>,
265}
266
267impl fmt::Debug for BredrBondData {
268    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
269        f.debug_struct("BredrBondData")
270            .field("role_preference", &self.role_preference)
271            .field("services", &self.services)
272            .field("link_key", &self.link_key.as_ref().map(RedactedPeerKey))
273            .finish()
274    }
275}
276
277impl From<sys::LeBondData> for LeBondData {
278    fn from(src: sys::LeBondData) -> Self {
279        Self {
280            connection_parameters: src.connection_parameters,
281            services: src.services.unwrap_or_default().iter().map(Into::into).collect(),
282            peer_ltk: src.peer_ltk,
283            local_ltk: src.local_ltk,
284            irk: src.irk,
285            csrk: src.csrk,
286        }
287    }
288}
289
290impl From<sys::BredrBondData> for BredrBondData {
291    fn from(src: sys::BredrBondData) -> Self {
292        Self {
293            role_preference: src.role_preference,
294            services: src.services.unwrap_or_default().iter().map(Into::into).collect(),
295            link_key: src.link_key,
296        }
297    }
298}
299
300impl From<LeBondData> for sys::LeBondData {
301    fn from(src: LeBondData) -> Self {
302        sys::LeBondData {
303            connection_parameters: src.connection_parameters,
304            services: Some(src.services.into_iter().map(|uuid| uuid.into()).collect()),
305            peer_ltk: src.peer_ltk,
306            local_ltk: src.local_ltk,
307            irk: src.irk,
308            csrk: src.csrk,
309            ..Default::default()
310        }
311    }
312}
313
314impl From<BredrBondData> for sys::BredrBondData {
315    fn from(src: BredrBondData) -> Self {
316        sys::BredrBondData {
317            role_preference: src.role_preference,
318            services: Some(src.services.into_iter().map(|uuid| uuid.into()).collect()),
319            link_key: src.link_key,
320            ..Default::default()
321        }
322    }
323}
324
325/// Data required to store a bond between a Peer and the system, so the bond can be restored later
326#[derive(Clone, Debug, PartialEq)]
327pub struct BondingData {
328    /// The persisted unique identifier for this peer.
329    pub identifier: PeerId,
330
331    /// The identity address of the peer.
332    pub address: Address,
333
334    /// The local bt-host identity address that this bond is associated with.
335    pub local_address: Address,
336
337    /// The device name obtained using general discovery and name discovery procedures.
338    pub name: Option<String>,
339
340    /// The device class of the peer, if known.
341    pub device_class: Option<bt::DeviceClass>,
342
343    /// Valid Bonding Data must include at least one of LeBondData or BredrBondData.
344    pub data: OneOrBoth<LeBondData, BredrBondData>,
345}
346
347impl BondingData {
348    pub fn le(&self) -> Option<&LeBondData> {
349        self.data.left()
350    }
351    pub fn bredr(&self) -> Option<&BredrBondData> {
352        self.data.right()
353    }
354}
355
356impl TryFrom<sys::BondingData> for BondingData {
357    type Error = Error;
358    fn try_from(fidl: sys::BondingData) -> Result<BondingData, Self::Error> {
359        let data = match (fidl.le_bond, fidl.bredr_bond) {
360            (Some(le_bond), Some(bredr_bond)) => OneOrBoth::Both(le_bond.into(), bredr_bond.into()),
361            (Some(le_bond), None) => OneOrBoth::Left(le_bond.into()),
362            (None, Some(bredr_bond)) => OneOrBoth::Right(bredr_bond.into()),
363            (None, None) => {
364                return Err(Error::missing(format!(
365                    "bond transport data (le_bond or bredr_bond) for peer {:?}",
366                    fidl.identifier.map(PeerId::from)
367                )));
368            }
369        };
370
371        Ok(BondingData {
372            identifier: fidl
373                .identifier
374                .ok_or_else(|| Error::missing("BondingData identifier"))?
375                .into(),
376            address: fidl.address.ok_or_else(|| Error::missing("BondingData address"))?.into(),
377            local_address: fidl
378                .local_address
379                .ok_or_else(|| Error::missing("BondingData local address"))?
380                .into(),
381            name: fidl.name,
382            device_class: fidl.device_class,
383            data,
384        })
385    }
386}
387
388/// To convert an external BondingData to an internal Fuchsia bonding data, we must provide a
389/// fuchsia PeerId to be used if the external source is missing one (for instance, it is being
390/// migrated from a previous, non-Fuchsia system)
391impl TryFrom<(sys::BondingData, PeerId)> for BondingData {
392    type Error = Error;
393    fn try_from(from: (sys::BondingData, PeerId)) -> Result<BondingData, Self::Error> {
394        let mut bond = from.0;
395        let id = match bond.identifier {
396            Some(id) => id.into(),
397            None => from.1,
398        };
399        bond.identifier = Some(id.into());
400        bond.try_into()
401    }
402}
403
404impl From<BondingData> for sys::BondingData {
405    fn from(bd: BondingData) -> sys::BondingData {
406        let le_bond = bd.le().map(|le| le.clone().into());
407        let bredr_bond = bd.bredr().map(|bredr| bredr.clone().into());
408        sys::BondingData {
409            identifier: Some(bd.identifier.into()),
410            address: Some(bd.address.into()),
411            local_address: Some(bd.local_address.into()),
412            name: bd.name,
413            device_class: bd.device_class,
414            le_bond,
415            bredr_bond,
416            ..Default::default()
417        }
418    }
419}
420
421/// Persisted data for a local bt-host.
422#[derive(Clone, PartialEq)]
423pub struct HostData {
424    /// A local IRK that is distributed to peers and used to generate RPAs when in LE peripheral
425    /// mode.
426    pub irk: Option<sys::Key>,
427}
428
429impl fmt::Debug for HostData {
430    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
431        f.debug_struct("HostData")
432            .field("irk", &self.irk.as_ref().map(|_: &sys::Key| REDACTED_KEY))
433            .finish()
434    }
435}
436
437impl From<HostData> for sys::HostData {
438    fn from(src: HostData) -> sys::HostData {
439        sys::HostData { irk: src.irk, ..Default::default() }
440    }
441}
442
443impl From<sys::HostData> for HostData {
444    fn from(src: sys::HostData) -> HostData {
445        HostData { irk: src.irk }
446    }
447}
448
449pub struct Identity {
450    pub host: HostData,
451    pub bonds: Vec<BondingData>,
452}
453
454impl From<Identity> for sys::Identity {
455    fn from(src: Identity) -> sys::Identity {
456        sys::Identity {
457            host: Some(src.host.into()),
458            bonds: Some(src.bonds.into_iter().map(|i| i.into()).collect()),
459            ..Default::default()
460        }
461    }
462}
463
464/// This module defines a BondingData test strategy generator for use with proptest.
465pub mod proptest_util {
466    use super::*;
467    use crate::types::address::proptest_util::any_address;
468    use proptest::option;
469    use proptest::prelude::*;
470
471    fn any_device_class() -> impl Strategy<Value = bt::DeviceClass> {
472        any::<u32>().prop_map(|value| bt::DeviceClass { value })
473    }
474
475    pub fn any_bonding_data() -> impl Strategy<Value = BondingData> {
476        let any_data = prop_oneof![
477            any_le_data().prop_map(OneOrBoth::Left),
478            any_bredr_data().prop_map(OneOrBoth::Right),
479            (any_le_data(), any_bredr_data()).prop_map(|(le, bredr)| OneOrBoth::Both(le, bredr)),
480        ];
481        (
482            any::<u64>(),
483            any_address(),
484            any_address(),
485            option::of("[a-zA-Z][a-zA-Z0-9_]*"),
486            any_data,
487            option::of(any_device_class()),
488        )
489            .prop_map(|(ident, address, local_address, name, data, device_class)| {
490                let identifier = PeerId(ident);
491                BondingData { identifier, address, local_address, name, device_class, data }
492            })
493    }
494
495    pub(crate) fn any_bredr_data() -> impl Strategy<Value = BredrBondData> {
496        (option::of(any_connection_role()), option::of(any_peer_key())).prop_map(
497            |(role_preference, link_key)| BredrBondData {
498                role_preference,
499                services: vec![],
500                link_key,
501            },
502        )
503    }
504
505    pub(crate) fn any_le_data() -> impl Strategy<Value = LeBondData> {
506        (
507            option::of(any_connection_params()),
508            option::of(any_ltk()),
509            option::of(any_ltk()),
510            option::of(any_peer_key()),
511            option::of(any_peer_key()),
512        )
513            .prop_map(|(connection_parameters, peer_ltk, local_ltk, irk, csrk)| {
514                LeBondData {
515                    connection_parameters,
516                    services: vec![],
517                    peer_ltk,
518                    local_ltk,
519                    irk,
520                    csrk,
521                }
522            })
523    }
524
525    fn any_security_properties() -> impl Strategy<Value = sys::SecurityProperties> {
526        any::<(bool, bool, u8)>().prop_map(
527            |(authenticated, secure_connections, encryption_key_size)| sys::SecurityProperties {
528                authenticated,
529                secure_connections,
530                encryption_key_size,
531            },
532        )
533    }
534
535    fn any_key() -> impl Strategy<Value = sys::Key> {
536        any::<[u8; 16]>().prop_map(|value| sys::Key { value })
537    }
538
539    fn any_peer_key() -> impl Strategy<Value = sys::PeerKey> {
540        (any_security_properties(), any_key())
541            .prop_map(|(security, data)| sys::PeerKey { security, data })
542    }
543
544    fn any_ltk() -> impl Strategy<Value = sys::Ltk> {
545        (any_peer_key(), any::<u16>(), any::<u64>()).prop_map(|(key, ediv, rand)| sys::Ltk {
546            key,
547            ediv,
548            rand,
549        })
550    }
551
552    fn any_connection_params() -> impl Strategy<Value = sys::LeConnectionParameters> {
553        (any::<u16>(), any::<u16>(), any::<u16>()).prop_map(
554            |(connection_interval, connection_latency, supervision_timeout)| {
555                sys::LeConnectionParameters {
556                    connection_interval,
557                    connection_latency,
558                    supervision_timeout,
559                }
560            },
561        )
562    }
563
564    fn any_connection_role() -> impl Strategy<Value = bt::ConnectionRole> {
565        prop_oneof![Just(bt::ConnectionRole::Leader), Just(bt::ConnectionRole::Follower)]
566    }
567}
568
569pub mod example {
570    use super::*;
571
572    pub fn peer_key() -> sys::PeerKey {
573        let data = sys::Key { value: [0; 16] };
574        let security = sys::SecurityProperties {
575            authenticated: false,
576            secure_connections: true,
577            encryption_key_size: 0,
578        };
579        sys::PeerKey { security, data }
580    }
581
582    pub fn bond(host_addr: Address, peer_addr: Address) -> BondingData {
583        let remote_key = example::peer_key();
584        let ltk = sys::Ltk { key: remote_key.clone(), ediv: 1, rand: 2 };
585
586        BondingData {
587            identifier: PeerId(42),
588            address: peer_addr,
589            local_address: host_addr,
590            name: Some("name".into()),
591            device_class: Some(bt::DeviceClass { value: 0 }),
592            data: OneOrBoth::Both(
593                LeBondData {
594                    connection_parameters: Some(sys::LeConnectionParameters {
595                        connection_interval: 0,
596                        connection_latency: 1,
597                        supervision_timeout: 2,
598                    }),
599                    services: vec![],
600                    peer_ltk: Some(ltk.clone()),
601                    local_ltk: Some(ltk.clone()),
602                    irk: Some(remote_key.clone()),
603                    csrk: Some(remote_key.clone()),
604                },
605                BredrBondData {
606                    role_preference: Some(bt::ConnectionRole::Leader),
607                    services: vec![],
608                    link_key: Some(remote_key.clone()),
609                },
610            ),
611        }
612    }
613}
614
615#[cfg(test)]
616mod tests {
617    use super::*;
618    use fidl_fuchsia_bluetooth_sys as sys;
619
620    // Tests for conversions from fuchsia.bluetooth.sys API
621    mod from_sys {
622        use super::*;
623        use assert_matches::assert_matches;
624
625        fn default_ltk() -> sys::Ltk {
626            sys::Ltk {
627                key: sys::PeerKey {
628                    security: sys::SecurityProperties {
629                        authenticated: false,
630                        secure_connections: false,
631                        encryption_key_size: 16,
632                    },
633                    data: sys::Key {
634                        value: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
635                    },
636                },
637                ediv: 0,
638                rand: 0,
639            }
640        }
641
642        fn test_sys_bond(
643            le_bond: &Option<sys::LeBondData>,
644            bredr_bond: &Option<sys::BredrBondData>,
645        ) -> sys::BondingData {
646            sys::BondingData {
647                identifier: Some(bt::PeerId { value: 42 }),
648                address: Some(bt::Address {
649                    type_: bt::AddressType::Public,
650                    bytes: [0, 0, 0, 0, 0, 0],
651                }),
652                local_address: Some(bt::Address {
653                    type_: bt::AddressType::Public,
654                    bytes: [0, 0, 0, 0, 0, 0],
655                }),
656                name: Some("name".into()),
657                device_class: Some(bt::DeviceClass { value: 1000 }),
658                le_bond: le_bond.clone(),
659                bredr_bond: bredr_bond.clone(),
660                ..Default::default()
661            }
662        }
663
664        #[test]
665        fn id_missing() {
666            let src = sys::BondingData {
667                identifier: None,
668                address: Some(bt::Address {
669                    type_: bt::AddressType::Random,
670                    bytes: [1, 2, 3, 4, 5, 6],
671                }),
672                le_bond: Some(sys::LeBondData::default()),
673                ..Default::default()
674            };
675            let result = BondingData::try_from(src);
676            assert_matches!(result, Err(Error::MissingRequired(_)));
677        }
678
679        #[test]
680        fn address_missing() {
681            let src = sys::BondingData {
682                identifier: Some(bt::PeerId { value: 1 }),
683                le_bond: Some(sys::LeBondData::default()),
684                bredr_bond: Some(sys::BredrBondData::default()),
685                ..Default::default()
686            };
687            let result = BondingData::try_from(src);
688            assert_matches!(result, Err(Error::MissingRequired(_)));
689        }
690
691        #[test]
692        fn use_address() {
693            let addr = bt::Address { type_: bt::AddressType::Public, bytes: [1, 2, 3, 4, 5, 6] };
694            let src = sys::BondingData {
695                identifier: Some(bt::PeerId { value: 1 }),
696                address: Some(addr.clone()),
697                local_address: Some(bt::Address {
698                    type_: bt::AddressType::Public,
699                    bytes: [1, 0, 0, 0, 0, 0],
700                }),
701                le_bond: Some(sys::LeBondData::default()),
702                bredr_bond: Some(sys::BredrBondData::default()),
703                ..Default::default()
704            };
705            let result =
706                BondingData::try_from(src).expect("failed to convert from sys.BondingData");
707            assert_eq!(result.address, Address::from(addr));
708        }
709
710        #[test]
711        fn use_peer_and_local_ltk() {
712            let ltk1 = default_ltk();
713            let mut ltk2 = default_ltk();
714            ltk2.key.security.authenticated = true;
715
716            let src = sys::BondingData {
717                identifier: Some(bt::PeerId { value: 1 }),
718                address: Some(bt::Address {
719                    type_: bt::AddressType::Public,
720                    bytes: [1, 2, 3, 4, 5, 6],
721                }),
722                local_address: Some(bt::Address {
723                    type_: bt::AddressType::Public,
724                    bytes: [1, 0, 0, 0, 0, 0],
725                }),
726                le_bond: Some(sys::LeBondData {
727                    local_ltk: Some(ltk1.clone()),
728                    peer_ltk: Some(ltk2.clone()),
729                    ..Default::default()
730                }),
731                bredr_bond: Some(sys::BredrBondData::default()),
732                ..Default::default()
733            };
734
735            let result =
736                BondingData::try_from(src).expect("failed to convert from sys.BondingData");
737            let result_le = result.le().expect("expected LE data");
738            assert_eq!(result_le.local_ltk, Some(ltk1));
739            assert_eq!(result_le.peer_ltk, Some(ltk2));
740        }
741
742        #[test]
743        fn rejects_missing_transport_specific() {
744            let le_bond = Some(sys::LeBondData::default());
745            let bredr_bond = Some(sys::BredrBondData::default());
746
747            // Valid combinations of bonding data
748            assert!(BondingData::try_from(test_sys_bond(&le_bond, &None)).is_ok());
749            assert!(BondingData::try_from(test_sys_bond(&None, &bredr_bond)).is_ok());
750            assert!(BondingData::try_from(test_sys_bond(&le_bond, &bredr_bond)).is_ok());
751
752            assert!(BondingData::try_from(test_sys_bond(&None, &None)).is_err());
753        }
754
755        #[test]
756        fn debug_and_try_from_redact_secret_keys() {
757            let bond = example::bond(Address::Public([1; 6]), Address::Public([2; 6]));
758            let bond_dbg = format!("{bond:?}");
759            assert_eq!(bond_dbg.matches(REDACTED_KEY).count(), 5);
760            assert!(!bond_dbg.contains(&format!("{:?}", [0u8; 16])));
761
762            let host_data = HostData { irk: Some(sys::Key { value: [0xAA; 16] }) };
763            let host_dbg = format!("{host_data:?}");
764            assert_eq!(host_dbg.matches(REDACTED_KEY).count(), 1);
765            assert!(!host_dbg.contains(&format!("{:?}", [0xAAu8; 16])));
766
767            let missing_transport = test_sys_bond(&None, &None);
768            let err_msg = BondingData::try_from(missing_transport).unwrap_err().to_string();
769            assert!(err_msg.contains(&format!("{:?}", Some(PeerId(42)))));
770            assert!(!err_msg.contains("BondingData {"));
771        }
772
773        #[test]
774        fn fidl_key_debug_wraps_value_with_sensitive_decorator() {
775            let key = sys::Key { value: [0xAB; 16] };
776            let expected_field = format!("value: SENSITIVE{{{:?}}}", key.value);
777            assert!(format!("{key:?}").contains(&expected_field));
778            assert!(format!("{key:#?}").contains(&expected_field));
779        }
780    }
781
782    // The test cases below use proptest to exercise round-trip conversions between FIDL and the
783    // library type across several permutations.
784    mod roundtrip {
785        use super::proptest_util::{any_bonding_data, any_bredr_data, any_le_data};
786        use super::*;
787        use proptest::prelude::*;
788
789        proptest! {
790            #[test]
791            fn bredr_data_sys_roundtrip(data in any_bredr_data()) {
792                let sys_bredr_data: sys::BredrBondData = data.clone().into();
793                assert_eq!(data, sys_bredr_data.into());
794            }
795            #[test]
796            fn le_data_sys_roundtrip(data in any_le_data()) {
797                let sys_le_data: sys::LeBondData = data.clone().into();
798                assert_eq!(data, sys_le_data.into());
799            }
800            #[test]
801            fn bonding_data_sys_roundtrip(data in any_bonding_data()) {
802                let peer_id = data.identifier;
803                let sys_bonding_data: sys::BondingData = data.clone().into();
804                let roundtrip_data: BondingData = (sys_bonding_data, peer_id).try_into().expect("bonding data can be converted");
805                assert_eq!(data, roundtrip_data);
806            }
807        }
808    }
809}