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