Skip to main content

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_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 })), // fbt::Appearance does not declare this entry
294            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 })), // "Phone"
345            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, // Omitted
374            ..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}