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_default(),
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_default()
107 .into_iter()
108 .map(|uuid| Uuid::from(uuid))
109 .collect(),
110 service_data: src
111 .service_data
112 .unwrap_or_default()
113 .into_iter()
114 .map(|data| data.into())
115 .collect(),
116 manufacturer_data: src
117 .manufacturer_data
118 .unwrap_or_default()
119 .into_iter()
120 .map(|data| data.into())
121 .collect(),
122 uris: src.uris.unwrap_or_default(),
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_default()
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_default()
150 .into_iter()
151 .map(ServiceData::try_from)
152 .collect::<Result<Vec<_>, Error>>()?,
153 manufacturer_data: src
154 .manufacturer_specific_data
155 .unwrap_or_default()
156 .into_iter()
157 .map(|data| data.into())
158 .collect(),
159 uris: src.uris.unwrap_or_default(),
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;
236 use fidl_fuchsia_bluetooth_le as fble;
237
238 #[test]
239 fn advertising_data_from_fidl_empty() {
240 let empty_data = fble::AdvertisingData::default();
241 let expected = AdvertisingData {
242 name: None,
243 tx_power_level: None,
244 appearance: None,
245 service_uuids: vec![],
246 service_data: vec![],
247 manufacturer_data: vec![],
248 uris: vec![],
249 };
250 let data = AdvertisingData::from(empty_data);
251 assert_eq!(expected, data);
252 }
253
254 #[test]
255 fn advertising_data_from_fidl() {
256 let uuid = fbt::Uuid {
257 value: [
258 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x0d, 0x18,
259 0x00, 0x00,
260 ],
261 };
262 let data = fble::AdvertisingData {
263 name: Some("hello".to_string()),
264 tx_power_level: Some(-10),
265 appearance: Some(fbt::Appearance::Watch),
266 service_uuids: Some(vec![uuid.clone()]),
267 service_data: Some(vec![fble::ServiceData { uuid: uuid.clone(), data: vec![1, 2, 3] }]),
268 manufacturer_data: Some(vec![fble::ManufacturerData {
269 company_id: 1,
270 data: vec![3, 4, 5],
271 }]),
272 uris: Some(vec!["some/uri".to_string()]),
273 ..Default::default()
274 };
275 let expected = AdvertisingData {
276 name: Some("hello".to_string()),
277 tx_power_level: Some(-10),
278 appearance: Some(fbt::Appearance::Watch),
279 service_uuids: vec![Uuid::new16(0x180d)],
280 service_data: vec![ServiceData { uuid: Uuid::new16(0x180d), data: vec![1, 2, 3] }],
281 manufacturer_data: vec![ManufacturerData { company_id: 1, data: vec![3, 4, 5] }],
282 uris: vec!["some/uri".to_string()],
283 };
284 let data = AdvertisingData::from(data);
285 assert_eq!(expected, data);
286 }
287
288 #[test]
289 fn advertising_data_from_deprecated_fidl_malformed_appearance() {
290 let data = fble::AdvertisingDataDeprecated {
291 name: None,
292 tx_power_level: None,
293 appearance: Some(Box::new(fbt::UInt16 { value: 1 })), service_uuids: None,
295 service_data: None,
296 manufacturer_specific_data: None,
297 solicited_service_uuids: None,
298 uris: None,
299 };
300 let data = AdvertisingData::try_from(data);
301 assert!(data.is_err());
302 }
303
304 #[test]
305 fn advertising_data_from_deprecated_fidl_malformed_service_uuid() {
306 let data = fble::AdvertisingDataDeprecated {
307 name: None,
308 tx_power_level: None,
309 appearance: None,
310 service_uuids: Some(vec!["💩".to_string()]),
311 service_data: None,
312 manufacturer_specific_data: None,
313 solicited_service_uuids: None,
314 uris: None,
315 };
316 let data = AdvertisingData::try_from(data);
317 assert!(data.is_err());
318 }
319
320 #[test]
321 fn advertising_data_from_deprecated_fidl_malformed_service_data() {
322 let data = fble::AdvertisingDataDeprecated {
323 name: None,
324 tx_power_level: None,
325 appearance: None,
326 service_uuids: None,
327 service_data: Some(vec![fble::ServiceDataEntry {
328 uuid: "💩".to_string(),
329 data: vec![1, 2],
330 }]),
331 manufacturer_specific_data: None,
332 solicited_service_uuids: None,
333 uris: None,
334 };
335 let data = AdvertisingData::try_from(data);
336 assert!(data.is_err());
337 }
338
339 #[test]
340 fn advertising_data_from_deprecated_fidl() {
341 let data = fble::AdvertisingDataDeprecated {
342 name: Some("hello".to_string()),
343 tx_power_level: Some(Box::new(fbt::Int8 { value: -10 })),
344 appearance: Some(Box::new(fbt::UInt16 { value: 64 })), service_uuids: Some(vec!["0000180d-0000-1000-8000-00805f9b34fb".to_string()]),
346 service_data: Some(vec![fble::ServiceDataEntry {
347 uuid: "0000180d-0000-1000-8000-00805f9b34fb".to_string(),
348 data: vec![1, 2],
349 }]),
350 manufacturer_specific_data: Some(vec![fble::ManufacturerSpecificDataEntry {
351 company_id: 1,
352 data: vec![1],
353 }]),
354 solicited_service_uuids: None,
355 uris: Some(vec!["some/uri".to_string()]),
356 };
357 let expected = AdvertisingData {
358 name: Some("hello".to_string()),
359 tx_power_level: Some(-10),
360 appearance: Some(fbt::Appearance::Phone),
361 service_uuids: vec![Uuid::new16(0x180d)],
362 service_data: vec![ServiceData { uuid: Uuid::new16(0x180d), data: vec![1, 2] }],
363 manufacturer_data: vec![ManufacturerData { company_id: 1, data: vec![1] }],
364 uris: vec!["some/uri".to_string()],
365 };
366 let data = AdvertisingData::try_from(data).expect("expected successful conversion");
367 assert_eq!(expected, data);
368 }
369
370 #[test]
371 fn peer_from_fidl_no_id() {
372 let peer = fble::Peer {
373 id: None, ..Default::default()
375 };
376 let peer = Peer::try_from(peer);
377 assert!(peer.is_err());
378 }
379
380 #[test]
381 fn peer_from_fidl_mandatory_fields_only() {
382 let peer = fble::Peer { id: Some(fbt::PeerId { value: 1 }), ..Default::default() };
383 let expected = Peer {
384 id: PeerId(1),
385 name: None,
386 connectable: false,
387 rssi: None,
388 advertising_data: None,
389 };
390 let peer = Peer::try_from(peer).expect("expected successful conversion");
391 assert_eq!(expected, peer);
392 }
393
394 #[test]
395 fn peer_from_fidl() {
396 let peer = fble::Peer {
397 id: Some(fbt::PeerId { value: 1 }),
398 connectable: Some(true),
399 rssi: Some(-10),
400 advertising_data: Some(fble::AdvertisingData {
401 name: Some("hello".to_string()),
402 tx_power_level: Some(-10),
403 appearance: Some(fbt::Appearance::Watch),
404 ..Default::default()
405 }),
406 ..Default::default()
407 };
408 let expected = Peer {
409 id: PeerId(1),
410 name: None,
411 connectable: true,
412 rssi: Some(-10),
413 advertising_data: Some(AdvertisingData {
414 name: Some("hello".to_string()),
415 tx_power_level: Some(-10),
416 appearance: Some(fbt::Appearance::Watch),
417 service_uuids: vec![],
418 service_data: vec![],
419 manufacturer_data: vec![],
420 uris: vec![],
421 }),
422 };
423 let peer = Peer::try_from(peer).expect("expected successful conversion");
424 assert_eq!(expected, peer);
425 }
426}