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