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, Debug, 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::Display for Address {
141    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
142        write!(fmt, "[address ({}) {}]", self.address_type_string(), addr_to_string(self.bytes()))
143    }
144}
145
146#[cfg(target_os = "fuchsia")]
147impl WriteInspect for Address {
148    fn write_inspect<'a>(
149        &self,
150        writer: &fuchsia_inspect::Node,
151        key: impl Into<std::borrow::Cow<'a, str>>,
152    ) {
153        writer.record_string(key, self.to_string());
154    }
155}
156
157pub(crate) mod proptest_util {
158    use super::*;
159    use proptest::prelude::*;
160
161    pub fn any_public_address() -> impl Strategy<Value = Address> {
162        any::<[u8; 6]>().prop_map(Address::Public)
163    }
164
165    pub fn any_random_address() -> impl Strategy<Value = Address> {
166        any::<[u8; 6]>().prop_map(Address::Random)
167    }
168
169    pub fn any_address() -> impl Strategy<Value = Address> {
170        prop_oneof![any_public_address(), any_random_address()]
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::proptest_util::*;
177    use super::*;
178
179    use assert_matches::assert_matches;
180    use proptest::prelude::*;
181
182    proptest! {
183        #[test]
184        fn public_address_str_roundtrip(address in any_public_address()) {
185            let str_rep = addr_to_string(address.bytes());
186            assert_eq!(
187                Address::public_from_str(&str_rep).map_err(|e| e.to_string()),
188                Ok(address),
189            );
190        }
191
192        #[test]
193        fn random_address_str_roundtrip(address in any_random_address()) {
194            let str_rep = addr_to_string(address.bytes());
195            assert_eq!(
196                Address::random_from_str(&str_rep).map_err(|e| e.to_string()),
197                Ok(address),
198            );
199        }
200
201        #[test]
202        fn any_address_fidl_roundtrip(address in any_address()) {
203            let fidl_address: fidl::Address = address.into();
204            assert_eq!(address, Address::from(fidl_address));
205        }
206    }
207
208    #[test]
209    fn address_to_string() {
210        let address = Address::Public([0x01, 0x02, 0x03, 0xDD, 0xEE, 0xFF]);
211        assert_eq!("[address (public) FF:EE:DD:03:02:01]", address.to_string());
212    }
213
214    #[test]
215    fn address_from_string_too_few_octets() {
216        let str_rep = "01:02:03:04:05";
217        let parsed = Address::public_from_str(&str_rep);
218        assert_matches!(parsed, Err(Error::Other(_)));
219    }
220
221    #[test]
222    fn address_from_string_too_many_octets() {
223        let str_rep = "01:02:03:04:05:06:07";
224        let parsed = Address::public_from_str(&str_rep);
225        assert_matches!(parsed, Err(Error::Other(_)));
226    }
227
228    #[test]
229    fn address_from_string_non_hex_chars() {
230        let str_rep = "01:02:03:04:05:0G";
231        let parsed = Address::public_from_str(&str_rep);
232        assert_matches!(parsed, Err(Error::Other(_)));
233    }
234
235    #[test]
236    fn public_address_from_fidl() {
237        let fidl_address =
238            fidl::Address { type_: fidl::AddressType::Public, bytes: [1, 2, 3, 4, 5, 6] };
239        let address: Address = fidl_address.into();
240        assert_eq!(Address::Public([1, 2, 3, 4, 5, 6]), address);
241    }
242
243    #[test]
244    fn random_address_from_fidl() {
245        let fidl_address =
246            fidl::Address { type_: fidl::AddressType::Random, bytes: [1, 2, 3, 4, 5, 6] };
247        let address: Address = fidl_address.into();
248        assert_eq!(Address::Random([1, 2, 3, 4, 5, 6]), address);
249    }
250
251    #[test]
252    fn public_address_into_fidl() {
253        let address = Address::Public([1, 2, 3, 4, 5, 6]);
254        let fidl_address: fidl::Address = address.into();
255        assert_eq!(fidl::AddressType::Public, fidl_address.type_);
256        assert_eq!([1, 2, 3, 4, 5, 6], fidl_address.bytes);
257    }
258
259    #[test]
260    fn random_address_into_fidl() {
261        let address = Address::Random([1, 2, 3, 4, 5, 6]);
262        let fidl_address: fidl::Address = address.into();
263        assert_eq!(fidl::AddressType::Random, fidl_address.type_);
264        assert_eq!([1, 2, 3, 4, 5, 6], fidl_address.bytes);
265    }
266}