1use 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#[derive(Clone, PartialEq)]
216pub struct LeBondData {
217 pub connection_parameters: Option<sys::LeConnectionParameters>,
219 pub services: Vec<Uuid>,
221 pub peer_ltk: Option<sys::Ltk>,
228 pub local_ltk: Option<sys::Ltk>,
235 pub irk: Option<sys::PeerKey>,
237 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#[derive(Clone, PartialEq)]
256pub struct BredrBondData {
257 pub role_preference: Option<bt::ConnectionRole>,
260 pub services: Vec<Uuid>,
262 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#[derive(Clone, Debug, PartialEq)]
327pub struct BondingData {
328 pub identifier: PeerId,
330
331 pub address: Address,
333
334 pub local_address: Address,
336
337 pub name: Option<String>,
339
340 pub device_class: Option<bt::DeviceClass>,
342
343 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
388impl 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#[derive(Clone, PartialEq)]
423pub struct HostData {
424 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
464pub 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 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 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 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}