1use fidl_fuchsia_bluetooth::Appearance;
22use fidl_fuchsia_bluetooth_le as fidl;
23use std::fmt;
24use std::str::FromStr;
25
26use crate::error::Error;
27use crate::types::id::PeerId;
28use crate::types::uuid::Uuid;
29
30#[derive(Clone, Debug)]
31pub struct RemoteDevice {
32 pub identifier: String,
33 pub connectable: bool,
34 pub rssi: Option<i8>,
35 pub advertising_data: Option<AdvertisingData>,
36}
37
38#[derive(Clone, Debug, PartialEq)]
39pub struct Peer {
40 pub id: PeerId,
41 pub name: Option<String>,
42 pub connectable: bool,
43 pub rssi: Option<i8>,
44 pub advertising_data: Option<AdvertisingData>,
45}
46
47#[derive(Clone, Debug, PartialEq)]
48pub struct AdvertisingData {
49 pub name: Option<String>,
50 pub tx_power_level: Option<i8>,
51 pub appearance: Option<Appearance>,
52 pub service_uuids: Vec<Uuid>,
53 pub service_data: Vec<ServiceData>,
54 pub manufacturer_data: Vec<ManufacturerData>,
55 pub uris: Vec<String>,
56}
57
58#[derive(Clone, Debug, PartialEq)]
59pub struct ServiceData {
60 pub uuid: Uuid,
61 pub data: Vec<u8>,
62}
63
64#[derive(Clone, Debug, PartialEq)]
65pub struct ManufacturerData {
66 pub company_id: u16,
67 pub data: Vec<u8>,
68}
69
70impl TryFrom<fidl::RemoteDevice> for RemoteDevice {
71 type Error = Error;
72 fn try_from(src: fidl::RemoteDevice) -> Result<RemoteDevice, Self::Error> {
73 Ok(RemoteDevice {
74 identifier: src.identifier,
75 connectable: src.connectable,
76 rssi: src.rssi.map(|v| v.value),
77 advertising_data: match src.advertising_data {
78 Some(a) => Some(AdvertisingData::try_from(*a)?),
79 None => None,
80 },
81 })
82 }
83}
84
85impl TryFrom<fidl::Peer> for Peer {
86 type Error = Error;
87 fn try_from(src: fidl::Peer) -> Result<Peer, Error> {
88 Ok(Peer {
89 id: src.id.map(PeerId::from).ok_or_else(|| Error::missing("le.Peer.id"))?,
90 name: src.name,
91 connectable: src.connectable.unwrap_or(false),
92 rssi: src.rssi,
93 advertising_data: src.advertising_data.map(|ad| ad.into()),
94 })
95 }
96}
97
98impl From<fidl::AdvertisingData> for AdvertisingData {
99 fn from(src: fidl::AdvertisingData) -> AdvertisingData {
100 AdvertisingData {
101 name: src.name,
102 tx_power_level: src.tx_power_level,
103 appearance: src.appearance,
104 service_uuids: src
105 .service_uuids
106 .unwrap_or(vec![])
107 .into_iter()
108 .map(|uuid| Uuid::from(uuid))
109 .collect(),
110 service_data: src
111 .service_data
112 .unwrap_or(vec![])
113 .into_iter()
114 .map(|data| data.into())
115 .collect(),
116 manufacturer_data: src
117 .manufacturer_data
118 .unwrap_or(vec![])
119 .into_iter()
120 .map(|data| data.into())
121 .collect(),
122 uris: src.uris.unwrap_or(vec![]),
123 }
124 }
125}
126
127impl TryFrom<fidl::AdvertisingDataDeprecated> for AdvertisingData {
128 type Error = Error;
129 fn try_from(src: fidl::AdvertisingDataDeprecated) -> Result<AdvertisingData, Self::Error> {
130 Ok(AdvertisingData {
131 name: src.name,
132 tx_power_level: src.tx_power_level.map(|v| v.value),
133 appearance: src
134 .appearance
135 .map(|v| {
136 Appearance::from_primitive(v.value).ok_or_else(|| {
137 Error::conversion("invalid AdvertisingDataDeprecated.appearance")
138 })
139 })
140 .map_or(Ok(None), |v| v.map(Some))?,
141 service_uuids: src
142 .service_uuids
143 .unwrap_or(vec![])
144 .into_iter()
145 .map(|uuid| Uuid::from_str(&uuid).map_err(|e| e.into()))
146 .collect::<Result<Vec<Uuid>, Error>>()?,
147 service_data: src
148 .service_data
149 .unwrap_or(vec![])
150 .into_iter()
151 .map(ServiceData::try_from)
152 .collect::<Result<Vec<_>, Error>>()?,
153 manufacturer_data: src
154 .manufacturer_specific_data
155 .unwrap_or(vec![])
156 .into_iter()
157 .map(|data| data.into())
158 .collect(),
159 uris: src.uris.unwrap_or(vec![]),
160 })
161 }
162}
163
164impl TryFrom<fidl::ServiceDataEntry> for ServiceData {
165 type Error = Error;
166 fn try_from(src: fidl::ServiceDataEntry) -> Result<ServiceData, Self::Error> {
167 Ok(ServiceData { uuid: Uuid::from_str(&src.uuid)?, data: src.data })
168 }
169}
170
171impl From<fidl::ServiceData> for ServiceData {
172 fn from(src: fidl::ServiceData) -> ServiceData {
173 ServiceData { uuid: Uuid::from(src.uuid), data: src.data }
174 }
175}
176
177impl From<fidl::ManufacturerSpecificDataEntry> for ManufacturerData {
178 fn from(src: fidl::ManufacturerSpecificDataEntry) -> ManufacturerData {
179 ManufacturerData { company_id: src.company_id, data: src.data }
180 }
181}
182
183impl From<fidl::ManufacturerData> for ManufacturerData {
184 fn from(src: fidl::ManufacturerData) -> ManufacturerData {
185 ManufacturerData { company_id: src.company_id, data: src.data }
186 }
187}
188
189impl fmt::Display for RemoteDevice {
190 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191 let connectable = if self.connectable { "connectable" } else { "non-connectable" };
192
193 write!(f, "[device ({}) ", connectable)?;
194
195 if let Some(rssi) = &self.rssi {
196 write!(f, "rssi: {}, ", rssi)?;
197 }
198
199 if let Some(ad) = &self.advertising_data {
200 if let Some(name) = &ad.name {
201 write!(f, "{}, ", name)?;
202 }
203 for m in &ad.manufacturer_data {
204 write!(f, "(mfct data: {:#06x} - {:x?}), ", m.company_id, m.data)?;
205 }
206 }
207
208 write!(f, "id: {}]", self.identifier)
209 }
210}
211
212impl fmt::Display for Peer {
213 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214 let connectable = if self.connectable { "connectable" } else { "non-connectable" };
215
216 write!(f, "[peer ({}) ", connectable)?;
217
218 if let Some(rssi) = &self.rssi {
219 write!(f, "rssi: {}, ", rssi)?;
220 }
221
222 if let Some(ad) = &self.advertising_data {
223 if let Some(name) = &ad.name {
224 write!(f, "{}, ", name)?;
225 }
226 }
227
228 write!(f, "id: {}]", self.id)
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235 use {fidl_fuchsia_bluetooth as fbt, fidl_fuchsia_bluetooth_le as fble};
236
237 #[test]
238 fn advertising_data_from_fidl_empty() {
239 let empty_data = fble::AdvertisingData::default();
240 let expected = AdvertisingData {
241 name: None,
242 tx_power_level: None,
243 appearance: None,
244 service_uuids: vec![],
245 service_data: vec![],
246 manufacturer_data: vec![],
247 uris: vec![],
248 };
249 let data = AdvertisingData::from(empty_data);
250 assert_eq!(expected, data);
251 }
252
253 #[test]
254 fn advertising_data_from_fidl() {
255 let uuid = fbt::Uuid {
256 value: [
257 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x0d, 0x18,
258 0x00, 0x00,
259 ],
260 };
261 let data = fble::AdvertisingData {
262 name: Some("hello".to_string()),
263 tx_power_level: Some(-10),
264 appearance: Some(fbt::Appearance::Watch),
265 service_uuids: Some(vec![uuid.clone()]),
266 service_data: Some(vec![fble::ServiceData { uuid: uuid.clone(), data: vec![1, 2, 3] }]),
267 manufacturer_data: Some(vec![fble::ManufacturerData {
268 company_id: 1,
269 data: vec![3, 4, 5],
270 }]),
271 uris: Some(vec!["some/uri".to_string()]),
272 ..Default::default()
273 };
274 let expected = AdvertisingData {
275 name: Some("hello".to_string()),
276 tx_power_level: Some(-10),
277 appearance: Some(fbt::Appearance::Watch),
278 service_uuids: vec![Uuid::new16(0x180d)],
279 service_data: vec![ServiceData { uuid: Uuid::new16(0x180d), data: vec![1, 2, 3] }],
280 manufacturer_data: vec![ManufacturerData { company_id: 1, data: vec![3, 4, 5] }],
281 uris: vec!["some/uri".to_string()],
282 };
283 let data = AdvertisingData::from(data);
284 assert_eq!(expected, data);
285 }
286
287 #[test]
288 fn advertising_data_from_deprecated_fidl_malformed_appearance() {
289 let data = fble::AdvertisingDataDeprecated {
290 name: None,
291 tx_power_level: None,
292 appearance: Some(Box::new(fbt::UInt16 { value: 1 })), service_uuids: None,
294 service_data: None,
295 manufacturer_specific_data: None,
296 solicited_service_uuids: None,
297 uris: None,
298 };
299 let data = AdvertisingData::try_from(data);
300 assert!(data.is_err());
301 }
302
303 #[test]
304 fn advertising_data_from_deprecated_fidl_malformed_service_uuid() {
305 let data = fble::AdvertisingDataDeprecated {
306 name: None,
307 tx_power_level: None,
308 appearance: None,
309 service_uuids: Some(vec!["💩".to_string()]),
310 service_data: None,
311 manufacturer_specific_data: None,
312 solicited_service_uuids: None,
313 uris: None,
314 };
315 let data = AdvertisingData::try_from(data);
316 assert!(data.is_err());
317 }
318
319 #[test]
320 fn advertising_data_from_deprecated_fidl_malformed_service_data() {
321 let data = fble::AdvertisingDataDeprecated {
322 name: None,
323 tx_power_level: None,
324 appearance: None,
325 service_uuids: None,
326 service_data: Some(vec![fble::ServiceDataEntry {
327 uuid: "💩".to_string(),
328 data: vec![1, 2],
329 }]),
330 manufacturer_specific_data: None,
331 solicited_service_uuids: None,
332 uris: None,
333 };
334 let data = AdvertisingData::try_from(data);
335 assert!(data.is_err());
336 }
337
338 #[test]
339 fn advertising_data_from_deprecated_fidl() {
340 let data = fble::AdvertisingDataDeprecated {
341 name: Some("hello".to_string()),
342 tx_power_level: Some(Box::new(fbt::Int8 { value: -10 })),
343 appearance: Some(Box::new(fbt::UInt16 { value: 64 })), service_uuids: Some(vec!["0000180d-0000-1000-8000-00805f9b34fb".to_string()]),
345 service_data: Some(vec![fble::ServiceDataEntry {
346 uuid: "0000180d-0000-1000-8000-00805f9b34fb".to_string(),
347 data: vec![1, 2],
348 }]),
349 manufacturer_specific_data: Some(vec![fble::ManufacturerSpecificDataEntry {
350 company_id: 1,
351 data: vec![1],
352 }]),
353 solicited_service_uuids: None,
354 uris: Some(vec!["some/uri".to_string()]),
355 };
356 let expected = AdvertisingData {
357 name: Some("hello".to_string()),
358 tx_power_level: Some(-10),
359 appearance: Some(fbt::Appearance::Phone),
360 service_uuids: vec![Uuid::new16(0x180d)],
361 service_data: vec![ServiceData { uuid: Uuid::new16(0x180d), data: vec![1, 2] }],
362 manufacturer_data: vec![ManufacturerData { company_id: 1, data: vec![1] }],
363 uris: vec!["some/uri".to_string()],
364 };
365 let data = AdvertisingData::try_from(data).expect("expected successful conversion");
366 assert_eq!(expected, data);
367 }
368
369 #[test]
370 fn peer_from_fidl_no_id() {
371 let peer = fble::Peer {
372 id: None, ..Default::default()
374 };
375 let peer = Peer::try_from(peer);
376 assert!(peer.is_err());
377 }
378
379 #[test]
380 fn peer_from_fidl_mandatory_fields_only() {
381 let peer = fble::Peer { id: Some(fbt::PeerId { value: 1 }), ..Default::default() };
382 let expected = Peer {
383 id: PeerId(1),
384 name: None,
385 connectable: false,
386 rssi: None,
387 advertising_data: None,
388 };
389 let peer = Peer::try_from(peer).expect("expected successful conversion");
390 assert_eq!(expected, peer);
391 }
392
393 #[test]
394 fn peer_from_fidl() {
395 let peer = fble::Peer {
396 id: Some(fbt::PeerId { value: 1 }),
397 connectable: Some(true),
398 rssi: Some(-10),
399 advertising_data: Some(fble::AdvertisingData {
400 name: Some("hello".to_string()),
401 tx_power_level: Some(-10),
402 appearance: Some(fbt::Appearance::Watch),
403 ..Default::default()
404 }),
405 ..Default::default()
406 };
407 let expected = Peer {
408 id: PeerId(1),
409 name: None,
410 connectable: true,
411 rssi: Some(-10),
412 advertising_data: Some(AdvertisingData {
413 name: Some("hello".to_string()),
414 tx_power_level: Some(-10),
415 appearance: Some(fbt::Appearance::Watch),
416 service_uuids: vec![],
417 service_data: vec![],
418 manufacturer_data: vec![],
419 uris: vec![],
420 }),
421 };
422 let peer = Peer::try_from(peer).expect("expected successful conversion");
423 assert_eq!(expected, peer);
424 }
425}