1#[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#[derive(Clone, Debug, PartialEq)]
189pub struct LeBondData {
190 pub connection_parameters: Option<sys::LeConnectionParameters>,
192 pub services: Vec<Uuid>,
194 pub peer_ltk: Option<sys::Ltk>,
201 pub local_ltk: Option<sys::Ltk>,
208 pub irk: Option<sys::PeerKey>,
210 pub csrk: Option<sys::PeerKey>,
212}
213
214#[derive(Clone, Debug, PartialEq)]
216pub struct BredrBondData {
217 pub role_preference: Option<bt::ConnectionRole>,
220 pub services: Vec<Uuid>,
222 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#[derive(Clone, Debug, PartialEq)]
277pub struct BondingData {
278 pub identifier: PeerId,
280
281 pub address: Address,
283
284 pub local_address: Address,
286
287 pub name: Option<String>,
289
290 pub device_class: Option<bt::DeviceClass>,
292
293 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
336impl 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#[derive(Clone, Debug, PartialEq)]
371pub struct HostData {
372 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
404pub 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 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 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 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}