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 _le_inspect: Option<LeInspect>,
163 _bredr_inspect: Option<BredrInspect>,
164}
165
166#[cfg(target_os = "fuchsia")]
167impl InspectData<BondingData> for BondingDataInspect {
168 fn new(bd: &BondingData, inspect: inspect::Node) -> BondingDataInspect {
169 BondingDataInspect {
170 _address_type: inspect.create_string("address_type", bd.address.address_type_string()),
171 _le_inspect: bd.le().map(|d| LeInspect::new(d, inspect.create_child("le"))),
172 _bredr_inspect: bd.bredr().map(|d| BredrInspect::new(d, inspect.create_child("bredr"))),
173 _inspect: inspect,
174 }
175 }
176}
177
178#[cfg(target_os = "fuchsia")]
179impl IsInspectable for BondingData {
180 type I = BondingDataInspect;
181}
182
183#[derive(Clone, Debug, PartialEq)]
185pub struct LeBondData {
186 pub connection_parameters: Option<sys::LeConnectionParameters>,
188 pub services: Vec<Uuid>,
190 pub peer_ltk: Option<sys::Ltk>,
197 pub local_ltk: Option<sys::Ltk>,
204 pub irk: Option<sys::PeerKey>,
206 pub csrk: Option<sys::PeerKey>,
208}
209
210#[derive(Clone, Debug, PartialEq)]
212pub struct BredrBondData {
213 pub role_preference: Option<bt::ConnectionRole>,
216 pub services: Vec<Uuid>,
218 pub link_key: Option<sys::PeerKey>,
221}
222
223impl From<sys::LeBondData> for LeBondData {
224 fn from(src: sys::LeBondData) -> Self {
225 Self {
226 connection_parameters: src.connection_parameters,
227 services: src.services.unwrap_or(vec![]).iter().map(Into::into).collect(),
228 peer_ltk: src.peer_ltk,
229 local_ltk: src.local_ltk,
230 irk: src.irk,
231 csrk: src.csrk,
232 }
233 }
234}
235
236impl From<sys::BredrBondData> for BredrBondData {
237 fn from(src: sys::BredrBondData) -> Self {
238 Self {
239 role_preference: src.role_preference,
240 services: src.services.unwrap_or(vec![]).iter().map(Into::into).collect(),
241 link_key: src.link_key,
242 }
243 }
244}
245
246impl From<LeBondData> for sys::LeBondData {
247 fn from(src: LeBondData) -> Self {
248 sys::LeBondData {
249 connection_parameters: src.connection_parameters,
250 services: Some(src.services.into_iter().map(|uuid| uuid.into()).collect()),
251 peer_ltk: src.peer_ltk,
252 local_ltk: src.local_ltk,
253 irk: src.irk,
254 csrk: src.csrk,
255 ..Default::default()
256 }
257 }
258}
259
260impl From<BredrBondData> for sys::BredrBondData {
261 fn from(src: BredrBondData) -> Self {
262 sys::BredrBondData {
263 role_preference: src.role_preference,
264 services: Some(src.services.into_iter().map(|uuid| uuid.into()).collect()),
265 link_key: src.link_key,
266 ..Default::default()
267 }
268 }
269}
270
271#[derive(Clone, Debug, PartialEq)]
273pub struct BondingData {
274 pub identifier: PeerId,
276
277 pub address: Address,
279
280 pub local_address: Address,
282
283 pub name: Option<String>,
285
286 pub data: OneOrBoth<LeBondData, BredrBondData>,
288}
289
290impl BondingData {
291 pub fn le(&self) -> Option<&LeBondData> {
292 self.data.left()
293 }
294 pub fn bredr(&self) -> Option<&BredrBondData> {
295 self.data.right()
296 }
297}
298
299impl TryFrom<sys::BondingData> for BondingData {
300 type Error = Error;
301 fn try_from(fidl: sys::BondingData) -> Result<BondingData, Self::Error> {
302 let fidl_clone = fidl.clone();
303 let data = match (fidl_clone.le_bond, fidl_clone.bredr_bond) {
304 (Some(le_bond), Some(bredr_bond)) => OneOrBoth::Both(le_bond.into(), bredr_bond.into()),
305 (Some(le_bond), None) => OneOrBoth::Left(le_bond.into()),
306 (None, Some(bredr_bond)) => OneOrBoth::Right(bredr_bond.into()),
307 _ => {
308 return Err(Error::missing(format!("bond data: {fidl:?}")));
309 }
310 };
311
312 Ok(BondingData {
313 identifier: fidl
314 .identifier
315 .ok_or_else(|| Error::missing("BondingData identifier"))?
316 .into(),
317 address: fidl.address.ok_or_else(|| Error::missing("BondingData address"))?.into(),
318 local_address: fidl
319 .local_address
320 .ok_or_else(|| Error::missing("BondingData local address"))?
321 .into(),
322 name: fidl.name,
323 data,
324 })
325 }
326}
327
328impl TryFrom<(sys::BondingData, PeerId)> for BondingData {
332 type Error = Error;
333 fn try_from(from: (sys::BondingData, PeerId)) -> Result<BondingData, Self::Error> {
334 let mut bond = from.0;
335 let id = match bond.identifier {
336 Some(id) => id.into(),
337 None => from.1,
338 };
339 bond.identifier = Some(id.into());
340 bond.try_into()
341 }
342}
343
344impl From<BondingData> for sys::BondingData {
345 fn from(bd: BondingData) -> sys::BondingData {
346 let le_bond = bd.le().map(|le| le.clone().into());
347 let bredr_bond = bd.bredr().map(|bredr| bredr.clone().into());
348 sys::BondingData {
349 identifier: Some(bd.identifier.into()),
350 address: Some(bd.address.into()),
351 local_address: Some(bd.local_address.into()),
352 name: bd.name,
353 le_bond,
354 bredr_bond,
355 ..Default::default()
356 }
357 }
358}
359
360#[derive(Clone, Debug, PartialEq)]
362pub struct HostData {
363 pub irk: Option<sys::Key>,
366}
367
368impl From<HostData> for sys::HostData {
369 fn from(src: HostData) -> sys::HostData {
370 sys::HostData { irk: src.irk, ..Default::default() }
371 }
372}
373
374impl From<sys::HostData> for HostData {
375 fn from(src: sys::HostData) -> HostData {
376 HostData { irk: src.irk }
377 }
378}
379
380pub struct Identity {
381 pub host: HostData,
382 pub bonds: Vec<BondingData>,
383}
384
385impl From<Identity> for sys::Identity {
386 fn from(src: Identity) -> sys::Identity {
387 sys::Identity {
388 host: Some(src.host.into()),
389 bonds: Some(src.bonds.into_iter().map(|i| i.into()).collect()),
390 ..Default::default()
391 }
392 }
393}
394
395pub mod proptest_util {
397 use super::*;
398 use crate::types::address::proptest_util::any_address;
399 use proptest::option;
400 use proptest::prelude::*;
401
402 pub fn any_bonding_data() -> impl Strategy<Value = BondingData> {
403 let any_data = prop_oneof![
404 any_le_data().prop_map(OneOrBoth::Left),
405 any_bredr_data().prop_map(OneOrBoth::Right),
406 (any_le_data(), any_bredr_data()).prop_map(|(le, bredr)| OneOrBoth::Both(le, bredr)),
407 ];
408 (any::<u64>(), any_address(), any_address(), option::of("[a-zA-Z][a-zA-Z0-9_]*"), any_data)
409 .prop_map(|(ident, address, local_address, name, data)| {
410 let identifier = PeerId(ident);
411 BondingData { identifier, address, local_address, name, data }
412 })
413 }
414
415 pub(crate) fn any_bredr_data() -> impl Strategy<Value = BredrBondData> {
416 (option::of(any_connection_role()), option::of(any_peer_key())).prop_map(
417 |(role_preference, link_key)| BredrBondData {
418 role_preference,
419 services: vec![],
420 link_key,
421 },
422 )
423 }
424
425 pub(crate) fn any_le_data() -> impl Strategy<Value = LeBondData> {
426 (
427 option::of(any_connection_params()),
428 option::of(any_ltk()),
429 option::of(any_ltk()),
430 option::of(any_peer_key()),
431 option::of(any_peer_key()),
432 )
433 .prop_map(|(connection_parameters, peer_ltk, local_ltk, irk, csrk)| {
434 LeBondData {
435 connection_parameters,
436 services: vec![],
437 peer_ltk,
438 local_ltk,
439 irk,
440 csrk,
441 }
442 })
443 }
444
445 fn any_security_properties() -> impl Strategy<Value = sys::SecurityProperties> {
446 any::<(bool, bool, u8)>().prop_map(
447 |(authenticated, secure_connections, encryption_key_size)| sys::SecurityProperties {
448 authenticated,
449 secure_connections,
450 encryption_key_size,
451 },
452 )
453 }
454
455 fn any_key() -> impl Strategy<Value = sys::Key> {
456 any::<[u8; 16]>().prop_map(|value| sys::Key { value })
457 }
458
459 fn any_peer_key() -> impl Strategy<Value = sys::PeerKey> {
460 (any_security_properties(), any_key())
461 .prop_map(|(security, data)| sys::PeerKey { security, data })
462 }
463
464 fn any_ltk() -> impl Strategy<Value = sys::Ltk> {
465 (any_peer_key(), any::<u16>(), any::<u64>()).prop_map(|(key, ediv, rand)| sys::Ltk {
466 key,
467 ediv,
468 rand,
469 })
470 }
471
472 fn any_connection_params() -> impl Strategy<Value = sys::LeConnectionParameters> {
473 (any::<u16>(), any::<u16>(), any::<u16>()).prop_map(
474 |(connection_interval, connection_latency, supervision_timeout)| {
475 sys::LeConnectionParameters {
476 connection_interval,
477 connection_latency,
478 supervision_timeout,
479 }
480 },
481 )
482 }
483
484 fn any_connection_role() -> impl Strategy<Value = bt::ConnectionRole> {
485 prop_oneof![Just(bt::ConnectionRole::Leader), Just(bt::ConnectionRole::Follower)]
486 }
487}
488
489pub mod example {
490 use super::*;
491
492 pub fn peer_key() -> sys::PeerKey {
493 let data = sys::Key { value: [0; 16] };
494 let security = sys::SecurityProperties {
495 authenticated: false,
496 secure_connections: true,
497 encryption_key_size: 0,
498 };
499 sys::PeerKey { security, data }
500 }
501
502 pub fn bond(host_addr: Address, peer_addr: Address) -> BondingData {
503 let remote_key = example::peer_key();
504 let ltk = sys::Ltk { key: remote_key.clone(), ediv: 1, rand: 2 };
505
506 BondingData {
507 identifier: PeerId(42),
508 address: peer_addr,
509 local_address: host_addr,
510 name: Some("name".into()),
511 data: OneOrBoth::Both(
512 LeBondData {
513 connection_parameters: Some(sys::LeConnectionParameters {
514 connection_interval: 0,
515 connection_latency: 1,
516 supervision_timeout: 2,
517 }),
518 services: vec![],
519 peer_ltk: Some(ltk.clone()),
520 local_ltk: Some(ltk.clone()),
521 irk: Some(remote_key.clone()),
522 csrk: Some(remote_key.clone()),
523 },
524 BredrBondData {
525 role_preference: Some(bt::ConnectionRole::Leader),
526 services: vec![],
527 link_key: Some(remote_key.clone()),
528 },
529 ),
530 }
531 }
532}
533
534#[cfg(test)]
535mod tests {
536 use super::*;
537 use fidl_fuchsia_bluetooth_sys as sys;
538
539 mod from_sys {
541 use super::*;
542 use assert_matches::assert_matches;
543
544 fn default_ltk() -> sys::Ltk {
545 sys::Ltk {
546 key: sys::PeerKey {
547 security: sys::SecurityProperties {
548 authenticated: false,
549 secure_connections: false,
550 encryption_key_size: 16,
551 },
552 data: sys::Key {
553 value: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
554 },
555 },
556 ediv: 0,
557 rand: 0,
558 }
559 }
560
561 fn test_sys_bond(
562 le_bond: &Option<sys::LeBondData>,
563 bredr_bond: &Option<sys::BredrBondData>,
564 ) -> sys::BondingData {
565 sys::BondingData {
566 identifier: Some(bt::PeerId { value: 42 }),
567 address: Some(bt::Address {
568 type_: bt::AddressType::Public,
569 bytes: [0, 0, 0, 0, 0, 0],
570 }),
571 local_address: Some(bt::Address {
572 type_: bt::AddressType::Public,
573 bytes: [0, 0, 0, 0, 0, 0],
574 }),
575 name: Some("name".into()),
576 le_bond: le_bond.clone(),
577 bredr_bond: bredr_bond.clone(),
578 ..Default::default()
579 }
580 }
581
582 #[test]
583 fn id_missing() {
584 let src = sys::BondingData {
585 identifier: None,
586 address: Some(bt::Address {
587 type_: bt::AddressType::Random,
588 bytes: [1, 2, 3, 4, 5, 6],
589 }),
590 le_bond: Some(sys::LeBondData::default()),
591 ..Default::default()
592 };
593 let result = BondingData::try_from(src);
594 assert_matches!(result, Err(Error::MissingRequired(_)));
595 }
596
597 #[test]
598 fn address_missing() {
599 let src = sys::BondingData {
600 identifier: Some(bt::PeerId { value: 1 }),
601 le_bond: Some(sys::LeBondData::default()),
602 bredr_bond: Some(sys::BredrBondData::default()),
603 ..Default::default()
604 };
605 let result = BondingData::try_from(src);
606 assert_matches!(result, Err(Error::MissingRequired(_)));
607 }
608
609 #[test]
610 fn use_address() {
611 let addr = bt::Address { type_: bt::AddressType::Public, bytes: [1, 2, 3, 4, 5, 6] };
612 let src = sys::BondingData {
613 identifier: Some(bt::PeerId { value: 1 }),
614 address: Some(addr.clone()),
615 local_address: Some(bt::Address {
616 type_: bt::AddressType::Public,
617 bytes: [1, 0, 0, 0, 0, 0],
618 }),
619 le_bond: Some(sys::LeBondData::default()),
620 bredr_bond: Some(sys::BredrBondData::default()),
621 ..Default::default()
622 };
623 let result =
624 BondingData::try_from(src).expect("failed to convert from sys.BondingData");
625 assert_eq!(result.address, Address::from(addr));
626 }
627
628 #[test]
629 fn use_peer_and_local_ltk() {
630 let ltk1 = default_ltk();
631 let mut ltk2 = default_ltk();
632 ltk2.key.security.authenticated = true;
633
634 let src = sys::BondingData {
635 identifier: Some(bt::PeerId { value: 1 }),
636 address: Some(bt::Address {
637 type_: bt::AddressType::Public,
638 bytes: [1, 2, 3, 4, 5, 6],
639 }),
640 local_address: Some(bt::Address {
641 type_: bt::AddressType::Public,
642 bytes: [1, 0, 0, 0, 0, 0],
643 }),
644 le_bond: Some(sys::LeBondData {
645 local_ltk: Some(ltk1.clone()),
646 peer_ltk: Some(ltk2.clone()),
647 ..Default::default()
648 }),
649 bredr_bond: Some(sys::BredrBondData::default()),
650 ..Default::default()
651 };
652
653 let result =
654 BondingData::try_from(src).expect("failed to convert from sys.BondingData");
655 let result_le = result.le().expect("expected LE data");
656 assert_eq!(result_le.local_ltk, Some(ltk1));
657 assert_eq!(result_le.peer_ltk, Some(ltk2));
658 }
659
660 #[test]
661 fn rejects_missing_transport_specific() {
662 let le_bond = Some(sys::LeBondData::default());
663 let bredr_bond = Some(sys::BredrBondData::default());
664
665 assert!(BondingData::try_from(test_sys_bond(&le_bond, &None)).is_ok());
667 assert!(BondingData::try_from(test_sys_bond(&None, &bredr_bond)).is_ok());
668 assert!(BondingData::try_from(test_sys_bond(&le_bond, &bredr_bond)).is_ok());
669
670 assert!(BondingData::try_from(test_sys_bond(&None, &None)).is_err());
671 }
672 }
673
674 mod roundtrip {
677 use super::proptest_util::{any_bonding_data, any_bredr_data, any_le_data};
678 use super::*;
679 use proptest::prelude::*;
680
681 proptest! {
682 #[test]
683 fn bredr_data_sys_roundtrip(data in any_bredr_data()) {
684 let sys_bredr_data: sys::BredrBondData = data.clone().into();
685 assert_eq!(data, sys_bredr_data.into());
686 }
687 #[test]
688 fn le_data_sys_roundtrip(data in any_le_data()) {
689 let sys_le_data: sys::LeBondData = data.clone().into();
690 assert_eq!(data, sys_le_data.into());
691 }
692 #[test]
693 fn bonding_data_sys_roundtrip(data in any_bonding_data()) {
694 let peer_id = data.identifier;
695 let sys_bonding_data: sys::BondingData = data.clone().into();
696 let roundtrip_data: BondingData = (sys_bonding_data, peer_id).try_into().expect("bonding data can be converted");
697 assert_eq!(data, roundtrip_data);
698 }
699 }
700 }
701}