1use 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#[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
39pub 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
82impl 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
93fn le_bytes_from_be_str(s: &str) -> Result<AddressBytes, Error> {
96 let mut bytes = [0; 6];
97 let mut insert_cursor = 6; 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}