fuchsia_bluetooth/types/
address.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 fidl_fuchsia_bluetooth as fidl;
6#[cfg(target_os = "fuchsia")]
7use fuchsia_inspect_contrib::log::WriteInspect;
8use std::fmt;
9
10use crate::error::Error;
11
12/// A Bluetooth device address can either be public or private. The controller device address used
13/// in BR/EDR (aka BD_ADDR) and LE have the "public" address type. A private address is one that is
14/// randomly generated by the controller or the host and can only be used in LE. The identity
15/// address can be random (often "static random") but is not typically considered private.
16///
17/// Some controller procedures depend on knowledge of whether an address is public (i.e. the BD_ADDR
18/// assigned to the controller) or randomly assigned by the host. This enum type represents that
19/// information.
20///
21/// This type represents a Bluetooth device address. `Address` can be converted to/from a FIDL
22/// Bluetooth device address type.
23#[derive(Copy, Clone, Eq, Hash, PartialEq)]
24pub enum Address {
25    Public(AddressBytes),
26    Random(AddressBytes),
27}
28
29const NUM_ADDRESS_BYTES: usize = 6;
30pub type AddressBytes = [u8; NUM_ADDRESS_BYTES];
31
32fn addr_to_string(bytes: &AddressBytes) -> String {
33    format!(
34        "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
35        bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0]
36    )
37}
38
39/// Combines a list of addresses with the provided `separator`.
40pub fn addresses_to_custom_string(addresses: &Vec<Address>, separator: &str) -> String {
41    addresses.iter().map(|addr| format!("{}", addr)).collect::<Vec<String>>().join(separator)
42}
43
44impl Address {
45    fn to_fidl(&self) -> fidl::Address {
46        match self {
47            Address::Public(b) => {
48                fidl::Address { type_: fidl::AddressType::Public, bytes: b.clone() }
49            }
50            Address::Random(b) => {
51                fidl::Address { type_: fidl::AddressType::Random, bytes: b.clone() }
52            }
53        }
54    }
55    pub fn public_from_str(s: &str) -> Result<Address, Error> {
56        Ok(Address::Public(le_bytes_from_be_str(s)?))
57    }
58
59    pub fn random_from_str(s: &str) -> Result<Address, Error> {
60        Ok(Address::Random(le_bytes_from_be_str(s)?))
61    }
62
63    pub fn address_type_string(&self) -> String {
64        match self {
65            Address::Public(_) => "public".to_string(),
66            Address::Random(_) => "random".to_string(),
67        }
68    }
69
70    pub fn as_hex_string(&self) -> String {
71        addr_to_string(self.bytes())
72    }
73
74    pub fn bytes(&self) -> &AddressBytes {
75        match self {
76            Address::Public(b) => b,
77            Address::Random(b) => b,
78        }
79    }
80}
81
82/// Address is comparable with a string-based representation of the address,
83/// i.e. Address::Public([6, 5, 4, 3, 2, 1]) == "01:02:03:04:05:06"
84impl PartialEq<str> for Address {
85    fn eq(&self, other: &str) -> bool {
86        match le_bytes_from_be_str(other) {
87            Err(_) => false,
88            Ok(bytes) => &bytes == self.bytes(),
89        }
90    }
91}
92
93/// Read a string of bytes in BigEndian Hex and produce a slice in LittleEndian
94/// E.g. 0x010203040506 becomes [6,5,4,3,2,1]
95fn le_bytes_from_be_str(s: &str) -> Result<AddressBytes, Error> {
96    let mut bytes = [0; 6];
97    let mut insert_cursor = 6; // Start back and work forward
98
99    for octet in s.split(|c| c == ':') {
100        if insert_cursor == 0 {
101            return Err(Error::other("too many octets in Address string"));
102        }
103        bytes[insert_cursor - 1] = u8::from_str_radix(octet, 16).map_err(Error::external)?;
104        insert_cursor -= 1;
105    }
106
107    if insert_cursor != 0 {
108        return Err(Error::other("too few octets in Address string"));
109    }
110    Ok(bytes)
111}
112
113impl From<&fidl::Address> for Address {
114    fn from(src: &fidl::Address) -> Address {
115        match src.type_ {
116            fidl::AddressType::Public => Address::Public(src.bytes.clone()),
117            fidl::AddressType::Random => Address::Random(src.bytes.clone()),
118        }
119    }
120}
121
122impl From<fidl::Address> for Address {
123    fn from(src: fidl::Address) -> Address {
124        Address::from(&src)
125    }
126}
127
128impl Into<fidl::Address> for Address {
129    fn into(self) -> fidl::Address {
130        self.to_fidl()
131    }
132}
133
134impl Into<fidl::Address> for &Address {
135    fn into(self) -> fidl::Address {
136        self.to_fidl()
137    }
138}
139
140impl fmt::Debug for Address {
141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        let formatted_bytes = self
143            .bytes()
144            .iter()
145            .map(|&b| format!("0x{:02X}", b))
146            .collect::<Vec<String>>()
147            .join(", ");
148
149        let addr_type = match self {
150            Self::Public(_) => "Public",
151            Self::Random(_) => "Random",
152        };
153        f.debug_tuple(&addr_type).field(&format_args!("[{}]", formatted_bytes)).finish()
154    }
155}
156
157impl fmt::Display for Address {
158    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
159        write!(fmt, "[address ({}) {}]", self.address_type_string(), addr_to_string(self.bytes()))
160    }
161}
162
163#[cfg(target_os = "fuchsia")]
164impl WriteInspect for Address {
165    fn write_inspect<'a>(
166        &self,
167        writer: &fuchsia_inspect::Node,
168        key: impl Into<std::borrow::Cow<'a, str>>,
169    ) {
170        writer.record_string(key, self.to_string());
171    }
172}
173
174pub(crate) mod proptest_util {
175    use super::*;
176    use proptest::prelude::*;
177
178    pub fn any_public_address() -> impl Strategy<Value = Address> {
179        any::<[u8; 6]>().prop_map(Address::Public)
180    }
181
182    pub fn any_random_address() -> impl Strategy<Value = Address> {
183        any::<[u8; 6]>().prop_map(Address::Random)
184    }
185
186    pub fn any_address() -> impl Strategy<Value = Address> {
187        prop_oneof![any_public_address(), any_random_address()]
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::proptest_util::*;
194    use super::*;
195
196    use assert_matches::assert_matches;
197    use proptest::prelude::*;
198
199    proptest! {
200        #[test]
201        fn public_address_str_roundtrip(address in any_public_address()) {
202            let str_rep = addr_to_string(address.bytes());
203            assert_eq!(
204                Address::public_from_str(&str_rep).map_err(|e| e.to_string()),
205                Ok(address),
206            );
207        }
208
209        #[test]
210        fn random_address_str_roundtrip(address in any_random_address()) {
211            let str_rep = addr_to_string(address.bytes());
212            assert_eq!(
213                Address::random_from_str(&str_rep).map_err(|e| e.to_string()),
214                Ok(address),
215            );
216        }
217
218        #[test]
219        fn any_address_fidl_roundtrip(address in any_address()) {
220            let fidl_address: fidl::Address = address.into();
221            assert_eq!(address, Address::from(fidl_address));
222        }
223    }
224
225    #[test]
226    fn address_debug_string() {
227        let address = Address::Public([9, 0x02, 0x12, 0xDD, 20, 0xFF]);
228        let debug = format!("{address:?}");
229        assert_eq!(debug, "Public([0x09, 0x02, 0x12, 0xDD, 0x14, 0xFF])".to_string());
230    }
231
232    #[test]
233    fn address_to_string() {
234        let address = Address::Public([0x01, 0x02, 0x03, 0xDD, 0xEE, 0xFF]);
235        assert_eq!("[address (public) FF:EE:DD:03:02:01]", address.to_string());
236    }
237
238    #[test]
239    fn address_from_string_too_few_octets() {
240        let str_rep = "01:02:03:04:05";
241        let parsed = Address::public_from_str(&str_rep);
242        assert_matches!(parsed, Err(Error::Other(_)));
243    }
244
245    #[test]
246    fn address_from_string_too_many_octets() {
247        let str_rep = "01:02:03:04:05:06:07";
248        let parsed = Address::public_from_str(&str_rep);
249        assert_matches!(parsed, Err(Error::Other(_)));
250    }
251
252    #[test]
253    fn address_from_string_non_hex_chars() {
254        let str_rep = "01:02:03:04:05:0G";
255        let parsed = Address::public_from_str(&str_rep);
256        assert_matches!(parsed, Err(Error::Other(_)));
257    }
258
259    #[test]
260    fn public_address_from_fidl() {
261        let fidl_address =
262            fidl::Address { type_: fidl::AddressType::Public, bytes: [1, 2, 3, 4, 5, 6] };
263        let address: Address = fidl_address.into();
264        assert_eq!(Address::Public([1, 2, 3, 4, 5, 6]), address);
265    }
266
267    #[test]
268    fn random_address_from_fidl() {
269        let fidl_address =
270            fidl::Address { type_: fidl::AddressType::Random, bytes: [1, 2, 3, 4, 5, 6] };
271        let address: Address = fidl_address.into();
272        assert_eq!(Address::Random([1, 2, 3, 4, 5, 6]), address);
273    }
274
275    #[test]
276    fn public_address_into_fidl() {
277        let address = Address::Public([1, 2, 3, 4, 5, 6]);
278        let fidl_address: fidl::Address = address.into();
279        assert_eq!(fidl::AddressType::Public, fidl_address.type_);
280        assert_eq!([1, 2, 3, 4, 5, 6], fidl_address.bytes);
281    }
282
283    #[test]
284    fn random_address_into_fidl() {
285        let address = Address::Random([1, 2, 3, 4, 5, 6]);
286        let fidl_address: fidl::Address = address.into();
287        assert_eq!(fidl::AddressType::Random, fidl_address.type_);
288        assert_eq!([1, 2, 3, 4, 5, 6], fidl_address.bytes);
289    }
290}