fuchsia_bluetooth/types/
id.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
5use anyhow::{format_err, Error};
6use fidl_fuchsia_bluetooth as fidl;
7#[cfg(target_os = "fuchsia")]
8use fuchsia_inspect_contrib::log::WriteInspect;
9use std::fmt;
10use std::str::FromStr;
11
12/// Valid id strings have only Hex characters (0-9, a-f) and are 16 chars long
13/// to match the 64 bit representation of a PeerId.
14fn parse_hex_identifier(hex_repr: &str) -> Result<u64, Error> {
15    if hex_repr.len() > 16 {
16        return Err(format_err!("Id string representation is longer than 16 characters"));
17    }
18    match u64::from_str_radix(hex_repr, 16) {
19        Ok(id) => Ok(id),
20        Err(_) => Err(format_err!("Id string representation is not valid hexadecimal")),
21    }
22}
23
24/// A Fuchsia-generated unique Identifier for a remote Peer that has been observed by the system
25/// Identifiers are currently guaranteed to be stable for the duration of system uptime, including
26/// if the peer is lost and then re-observed. Identifiers are *not* guaranteed to be stable between
27/// reboots.
28#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
29pub struct PeerId(pub u64);
30
31impl PeerId {
32    pub fn random() -> PeerId {
33        PeerId(rand::random())
34    }
35}
36
37impl fmt::Display for PeerId {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        // Zero-Pad the output string to be 16 characters to maintain formatting consistency.
40        write!(f, "{:016x}", self.0)
41    }
42}
43
44impl FromStr for PeerId {
45    type Err = anyhow::Error;
46
47    fn from_str(src: &str) -> Result<Self, Self::Err> {
48        parse_hex_identifier(src).map(|n| PeerId(n))
49    }
50}
51
52impl From<fidl::PeerId> for PeerId {
53    fn from(src: fidl::PeerId) -> PeerId {
54        PeerId(src.value)
55    }
56}
57
58impl Into<fidl::PeerId> for PeerId {
59    fn into(self) -> fidl::PeerId {
60        fidl::PeerId { value: self.0 }
61    }
62}
63
64#[cfg(target_os = "fuchsia")]
65impl WriteInspect for PeerId {
66    fn write_inspect<'a>(
67        &self,
68        writer: &fuchsia_inspect::Node,
69        key: impl Into<std::borrow::Cow<'a, str>>,
70    ) {
71        writer.record_string(key, self.to_string());
72    }
73}
74
75/// A Bluetooth Host Adapter id. Uniquely identifiers a bluetooth Host on this system.
76/// `HostId` can be converted to/from a FIDL Bluetooth HostId type.
77#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
78pub struct HostId(pub u64);
79
80impl fmt::Display for HostId {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        // Zero-Pad the output string to be 16 characters to maintain formatting consistency.
83        write!(f, "{:016x}", self.0)
84    }
85}
86
87impl FromStr for HostId {
88    type Err = anyhow::Error;
89
90    /// Valid HostId strings have only Hex characters (0-9, a-f) and are 16 chars long
91    /// to match the 64 bit representation of a HostId.
92    fn from_str(src: &str) -> Result<HostId, Error> {
93        parse_hex_identifier(&src).map(|r| HostId(r))
94    }
95}
96
97impl From<fidl::HostId> for HostId {
98    fn from(src: fidl::HostId) -> HostId {
99        HostId(src.value)
100    }
101}
102
103impl Into<fidl::HostId> for HostId {
104    fn into(self) -> fidl::HostId {
105        fidl::HostId { value: self.0 }
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112    use proptest::prelude::*;
113
114    #[test]
115    fn peerid_to_string() {
116        let testcases = vec![
117            // Lowest possible id.
118            (PeerId(0), "0000000000000000"),
119            // Normal case with padding.
120            (PeerId(1234567890), "00000000499602d2"),
121            // Normal case with padding.
122            (PeerId(123123777771778888), "01b56c6c6d7db348"),
123            // Normal case without padding.
124            (PeerId(2000037777717788818), "1bc18fc31e3b0092"),
125            // u64 max test.
126            (PeerId(u64::MAX), "ffffffffffffffff"),
127        ];
128
129        for (id, expected) in testcases {
130            assert_eq!(expected, id.to_string());
131        }
132    }
133
134    #[test]
135    fn id_to_string() {
136        let testcases = vec![
137            // Lowest possible id.
138            (HostId(0), "0000000000000000"),
139            // Normal case with padding.
140            (HostId(1234567890), "00000000499602d2"),
141            // Normal case with padding.
142            (HostId(123123777771778888), "01b56c6c6d7db348"),
143            // Normal case without padding.
144            (HostId(2000037777717788818), "1bc18fc31e3b0092"),
145            // u64 max test.
146            (HostId(u64::MAX), "ffffffffffffffff"),
147        ];
148
149        for (id, expected) in testcases {
150            assert_eq!(expected, id.to_string());
151        }
152    }
153
154    #[test]
155    fn peerid_from_string() {
156        let testcases = vec![
157            // Largest valid id.
158            ("ffffffffffffffff", Ok(PeerId(18446744073709551615))),
159            // Smallest valid id.
160            ("0000000000000000", Ok(PeerId(0))),
161            // BT stack wont produce IDs that aren't 16 characters long, but the conversion
162            // can handle smaller string ids.
163            // In the reverse direction, the string will be padded to 16 characters.
164            ("10", Ok(PeerId(16))),
165            // Normal case.
166            ("fe12ffdda3b89002", Ok(PeerId(18307976762614124546))),
167            // String with invalid hex chars (i.e not 0-9, A-F).
168            ("klinvalidstr", Err(())),
169            // String that is too long to be a PeerId (> 16 chars).
170            ("90000111122223333", Err(())),
171        ];
172
173        for (input, expected) in testcases {
174            assert_eq!(expected, input.parse::<PeerId>().map_err(|_| ()))
175        }
176    }
177
178    #[test]
179    fn id_from_string() {
180        let testcases = vec![
181            // Largest valid id.
182            ("ffffffffffffffff", Ok(HostId(18446744073709551615))),
183            // Smallest valid id.
184            ("0000000000000000", Ok(HostId(0))),
185            // BT stack wont produce IDs that aren't 16 characters long, but the conversion
186            // can handle smaller string ids.
187            // In the reverse direction, the string will be padded to 16 characters.
188            ("10", Ok(HostId(16))),
189            // Normal case.
190            ("fe12ffdda3b89002", Ok(HostId(18307976762614124546))),
191            // String with invalid hex chars (i.e not 0-9, A-F).
192            ("klinvalidstr", Err(())),
193            // String that is too long to be a Id (> 16 chars).
194            ("90000111122223333", Err(())),
195        ];
196
197        for (input, expected) in testcases {
198            assert_eq!(expected, input.parse::<HostId>().map_err(|_| ()))
199        }
200    }
201
202    proptest! {
203        #[test]
204        fn peerid_string_roundtrip(n in prop::num::u64::ANY) {
205            let peer_id = PeerId(n);
206            assert_eq!(Ok(peer_id), peer_id.to_string().parse::<PeerId>().map_err(|_| ()));
207        }
208
209        #[test]
210        fn peerid_fidl_roundtrip(n in prop::num::u64::ANY) {
211            let peer_id = PeerId(n);
212            let fidl_id: fidl::PeerId = peer_id.into();
213            assert_eq!(peer_id, PeerId::from(fidl_id));
214        }
215
216        #[test]
217        fn peerid_into_fidl(n in prop::num::u64::ANY) {
218            let peer_id = PeerId(n);
219            let fidl_p_id: fidl::PeerId = peer_id.into();
220            assert_eq!(n, fidl_p_id.value);
221        }
222
223        #[test]
224        fn id_into_fidl(n in prop::num::u64::ANY) {
225            let id = HostId(n);
226            let fidl_id: fidl::HostId = id.into();
227            assert_eq!(n, fidl_id.value);
228        }
229
230        #[test]
231        fn peer_id_from_fidl(n in prop::num::u64::ANY) {
232            let fidl_p_id = fidl::PeerId { value: n };
233            let peer_id: PeerId = fidl_p_id.into();
234            assert_eq!(PeerId(n), peer_id);
235        }
236
237        #[test]
238        fn id_from_fidl(n in prop::num::u64::ANY) {
239            let fidl_id = fidl::HostId { value: n };
240            let id: HostId = fidl_id.into();
241            assert_eq!(HostId(n), id);
242        }
243    }
244}