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