1use 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#[derive(Clone, Debug, PartialEq)]
190pub struct LeBondData {
191 pub connection_parameters: Option<sys::LeConnectionParameters>,
193 pub services: Vec<Uuid>,
195 pub peer_ltk: Option<sys::Ltk>,
202 pub local_ltk: Option<sys::Ltk>,
209 pub irk: Option<sys::PeerKey>,
211 pub csrk: Option<sys::PeerKey>,
213}
214
215#[derive(Clone, Debug, PartialEq)]
217pub struct BredrBondData {
218 pub role_preference: Option<bt::ConnectionRole>,
221 pub services: Vec<Uuid>,
223 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#[derive(Clone, Debug, PartialEq)]
278pub struct BondingData {
279 pub identifier: PeerId,
281
282 pub address: Address,
284
285 pub local_address: Address,
287
288 pub name: Option<String>,
290
291 pub device_class: Option<bt::DeviceClass>,
293
294 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
337impl 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#[derive(Clone, Debug, PartialEq)]
372pub struct HostData {
373 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
405pub 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 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 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 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}