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, 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
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::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}