fuchsia_bluetooth/types/
le.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
5//! This module declares native Rust encodings equivalent to FIDL structs for the
6//! Bluetooth LowEnergy interfaces. These structs use standard Rust primitives
7//! rather than the default mapping from FIDL, and derive the `Clone` trait for a
8//! more ergonomic api than those exposed in the `fidl_fuchsia_bluetooth_le`
9//! crate.
10//!
11//! These types also implement the `From` trait, so usage when receiving a fidl
12//! struct is simply a case of calling `.into(...)` (`From` implies `Into`):
13//!
14//! ```ignore
15//!   fn use_peer(fidl_peer: fidl_fuchsia_bluetooth_le::RemoteDevice) {
16//!      let peer: Le::RemoteDevice = fidl_peer.into();
17//!      ...
18//!   }
19//! ```
20
21use 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 })), // fbt::Appearance does not declare this entry
293            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 })), // "Phone"
344            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, // Omitted
373            ..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}