fuchsia_bluetooth/types/
host_info.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
5use fidl_fuchsia_bluetooth_sys as fsys;
6#[cfg(target_os = "fuchsia")]
7use fuchsia_inspect::{self as inspect, Property};
8use std::fmt;
9
10use crate::error::Error;
11#[cfg(target_os = "fuchsia")]
12use crate::inspect::{DebugExt, InspectData, Inspectable, IsInspectable, ToProperty};
13use crate::types::{addresses_to_custom_string, Address, HostId};
14
15/// `HostInfo` contains informational parameters and state for a bt-host device.
16#[derive(Clone, Debug, PartialEq)]
17pub struct HostInfo {
18    /// Uniquely identifies a host on the current system.
19    pub id: HostId,
20
21    /// The Bluetooth technologies that are supported by this adapter.
22    pub technology: fsys::TechnologyType,
23
24    /// The known Classic and LE addresses associated with this Host.
25    /// This is guaranteed to be nonempty. The Public Address is always first.
26    pub addresses: Vec<Address>,
27
28    /// Indicates whether or not this is the active host. The system has one active host which
29    /// handles all Bluetooth procedures.
30    pub active: bool,
31
32    /// The local name of this host. This is the name that is visible to other devices when this
33    /// host is in the discoverable mode. Not present if the local device name is unknown.
34    pub local_name: Option<String>,
35
36    /// Whether or not the local adapter is currently discoverable over BR/EDR and
37    /// LE physical channels.
38    pub discoverable: bool,
39
40    /// Whether or not device discovery is currently being performed.
41    pub discovering: bool,
42}
43
44impl TryFrom<&fsys::HostInfo> for HostInfo {
45    type Error = Error;
46    fn try_from(src: &fsys::HostInfo) -> Result<HostInfo, Self::Error> {
47        let addresses =
48            src.addresses.as_ref().ok_or_else(|| Error::missing("HostInfo.addresses"))?;
49        if addresses.is_empty() {
50            return Err(Error::conversion("HostInfo.addresses must be nonempty"));
51        }
52        let addresses = addresses.iter().map(Into::into).collect();
53        Ok(HostInfo {
54            id: HostId::from(src.id.ok_or_else(|| Error::missing("HostInfo.id"))?),
55            technology: src.technology.ok_or_else(|| Error::missing("HostInfo.technology"))?,
56            addresses,
57            active: src.active.unwrap_or(false),
58            local_name: src.local_name.clone(),
59            discoverable: src.discoverable.unwrap_or(false),
60            discovering: src.discovering.unwrap_or(false),
61        })
62    }
63}
64
65impl TryFrom<fsys::HostInfo> for HostInfo {
66    type Error = Error;
67    fn try_from(src: fsys::HostInfo) -> Result<HostInfo, Self::Error> {
68        HostInfo::try_from(&src)
69    }
70}
71
72impl From<&HostInfo> for fsys::HostInfo {
73    fn from(src: &HostInfo) -> fsys::HostInfo {
74        fsys::HostInfo {
75            id: Some(src.id.into()),
76            technology: Some(src.technology),
77            active: Some(src.active),
78            local_name: src.local_name.clone(),
79            discoverable: Some(src.discoverable),
80            discovering: Some(src.discovering),
81            addresses: Some(src.addresses.iter().map(Into::into).collect()),
82            ..Default::default()
83        }
84    }
85}
86
87impl From<HostInfo> for fsys::HostInfo {
88    fn from(src: HostInfo) -> fsys::HostInfo {
89        fsys::HostInfo::from(&src)
90    }
91}
92
93impl fmt::Display for HostInfo {
94    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
95        writeln!(fmt, "HostInfo:")?;
96        writeln!(fmt, "\tidentifier:\t{}", self.id)?;
97        writeln!(fmt, "\taddresses:\t{}", addresses_to_custom_string(&self.addresses, "\n\t\t\t"))?;
98        writeln!(fmt, "\tactive:\t{}", self.active)?;
99        writeln!(fmt, "\ttechnology:\t{:?}", self.technology)?;
100        #[allow(clippy::or_fun_call)] // TODO(https://fxbug.dev/379717320)
101        writeln!(
102            fmt,
103            "\tlocal name:\t{}",
104            self.local_name.as_ref().unwrap_or(&"(unknown)".to_string())
105        )?;
106        writeln!(fmt, "\tdiscoverable:\t{}", self.discoverable)?;
107        writeln!(fmt, "\tdiscovering:\t{}", self.discovering)
108    }
109}
110
111#[cfg(target_os = "fuchsia")]
112impl Inspectable<HostInfo> {
113    pub fn update(&mut self, info: HostInfo) {
114        self.inspect.update(&info);
115        self.inner = info;
116    }
117}
118
119#[cfg(target_os = "fuchsia")]
120pub struct HostInfoInspect {
121    _inspect: inspect::Node,
122    identifier: inspect::UintProperty,
123    technology: inspect::StringProperty,
124    active: inspect::UintProperty,
125    discoverable: inspect::UintProperty,
126    discovering: inspect::UintProperty,
127}
128
129#[cfg(target_os = "fuchsia")]
130impl HostInfoInspect {
131    fn update(&mut self, info: &HostInfo) {
132        self.identifier.set(info.id.0);
133        self.technology.set(&info.technology.debug());
134        self.active.set(info.active.to_property());
135        self.discoverable.set(info.discoverable.to_property());
136        self.discovering.set(info.discovering.to_property());
137    }
138}
139
140#[cfg(target_os = "fuchsia")]
141impl IsInspectable for HostInfo {
142    type I = HostInfoInspect;
143}
144
145#[cfg(target_os = "fuchsia")]
146impl InspectData<HostInfo> for HostInfoInspect {
147    fn new(info: &HostInfo, inspect: inspect::Node) -> HostInfoInspect {
148        HostInfoInspect {
149            identifier: inspect.create_uint("identifier", info.id.0),
150            technology: inspect.create_string("technology", info.technology.debug()),
151            active: inspect.create_uint("active", info.active.to_property()),
152            discoverable: inspect.create_uint("discoverable", info.discoverable.to_property()),
153            discovering: inspect.create_uint("discovering", info.discovering.to_property()),
154            _inspect: inspect,
155        }
156    }
157}
158
159/// Example Bluetooth host for testing.
160pub fn example_host(id: HostId, active: bool, discoverable: bool) -> fsys::HostInfo {
161    fsys::HostInfo {
162        id: Some(id.into()),
163        technology: Some(fsys::TechnologyType::LowEnergy),
164        active: Some(active),
165        local_name: Some("fuchsia123".to_string()),
166        discoverable: Some(discoverable),
167        discovering: Some(true),
168        addresses: Some(vec![Address::Public([1, 2, 3, 4, 5, 6]).into()]),
169        ..Default::default()
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176
177    use diagnostics_assertions::assert_data_tree;
178    use {fidl_fuchsia_bluetooth as fbt, fuchsia_inspect as inspect};
179
180    #[test]
181    fn from_fidl_id_not_present() {
182        let info = HostInfo::try_from(fsys::HostInfo::default());
183        assert!(info.is_err());
184    }
185
186    #[test]
187    fn from_fidl_technology_not_present() {
188        let info = fsys::HostInfo { id: Some(fbt::HostId { value: 1 }), ..Default::default() };
189        let info = HostInfo::try_from(info);
190        assert!(info.is_err());
191    }
192
193    #[test]
194    fn from_fidl_addresses_not_present() {
195        let info = fsys::HostInfo {
196            id: Some(fbt::HostId { value: 1 }),
197            technology: Some(fsys::TechnologyType::LowEnergy),
198            ..Default::default()
199        };
200        let info = HostInfo::try_from(info);
201        assert!(info.is_err());
202    }
203
204    #[test]
205    fn from_fidl_addresses_is_empty() {
206        let info = fsys::HostInfo {
207            id: Some(fbt::HostId { value: 1 }),
208            technology: Some(fsys::TechnologyType::LowEnergy),
209            addresses: Some(vec![]),
210            ..Default::default()
211        };
212        let info = HostInfo::try_from(info);
213        assert!(info.is_err());
214    }
215
216    #[test]
217    fn from_fidl_optional_fields_not_present() {
218        let info = fsys::HostInfo {
219            id: Some(fbt::HostId { value: 1 }),
220            technology: Some(fsys::TechnologyType::LowEnergy),
221            addresses: Some(vec![fbt::Address {
222                type_: fbt::AddressType::Public,
223                bytes: [1, 2, 3, 4, 5, 6],
224            }]),
225            ..Default::default()
226        };
227        let expected = HostInfo {
228            id: HostId(1),
229            technology: fsys::TechnologyType::LowEnergy,
230            addresses: vec![Address::Public([1, 2, 3, 4, 5, 6])],
231            active: false,
232            local_name: None,
233            discoverable: false,
234            discovering: false,
235        };
236
237        let info = HostInfo::try_from(info).expect("expected successful conversion");
238        assert_eq!(expected, info);
239    }
240
241    #[test]
242    fn from_fidl_optional_fields_present() {
243        let info = fsys::HostInfo {
244            id: Some(fbt::HostId { value: 1 }),
245            technology: Some(fsys::TechnologyType::LowEnergy),
246            active: Some(true),
247            local_name: Some("name".to_string()),
248            discoverable: Some(false),
249            discovering: Some(true),
250            addresses: Some(vec![fbt::Address {
251                type_: fbt::AddressType::Public,
252                bytes: [1, 2, 3, 4, 5, 6],
253            }]),
254            ..Default::default()
255        };
256        let expected = HostInfo {
257            id: HostId(1),
258            technology: fsys::TechnologyType::LowEnergy,
259            addresses: vec![Address::Public([1, 2, 3, 4, 5, 6])],
260            active: true,
261            local_name: Some("name".to_string()),
262            discoverable: false,
263            discovering: true,
264        };
265
266        let info = HostInfo::try_from(info).expect("expected successful conversion");
267        assert_eq!(expected, info);
268    }
269
270    #[test]
271    fn to_fidl() {
272        let info = HostInfo {
273            id: HostId(1),
274            technology: fsys::TechnologyType::LowEnergy,
275            addresses: vec![Address::Public([1, 2, 3, 4, 5, 6])],
276            active: false,
277            local_name: Some("name".to_string()),
278            discoverable: false,
279            discovering: false,
280        };
281        let expected = fsys::HostInfo {
282            id: Some(fbt::HostId { value: 1 }),
283            technology: Some(fsys::TechnologyType::LowEnergy),
284            active: Some(false),
285            local_name: Some("name".to_string()),
286            discoverable: Some(false),
287            discovering: Some(false),
288            addresses: Some(vec![fbt::Address {
289                type_: fbt::AddressType::Public,
290                bytes: [1, 2, 3, 4, 5, 6],
291            }]),
292            ..Default::default()
293        };
294
295        assert_eq!(expected, info.into());
296    }
297
298    #[test]
299    fn inspect() {
300        let inspector = inspect::Inspector::default();
301        let node = inspector.root().create_child("info");
302        let info = HostInfo {
303            id: HostId(1),
304            technology: fsys::TechnologyType::LowEnergy,
305            addresses: vec![Address::Public([1, 2, 3, 4, 5, 6])],
306            active: false,
307            local_name: Some("name".to_string()),
308            discoverable: false,
309            discovering: true,
310        };
311        let mut info = Inspectable::new(info, node);
312        assert_data_tree!(inspector, root: {
313            info: contains {
314                identifier: 1u64,
315                technology: "LowEnergy",
316                active: 0u64,
317                discoverable: 0u64,
318                discovering: 1u64
319            }
320        });
321
322        info.update(HostInfo {
323            id: HostId(1),
324            technology: fsys::TechnologyType::LowEnergy,
325            addresses: vec![Address::Public([1, 2, 3, 4, 5, 6])],
326            active: true,
327            local_name: Some("foo".to_string()),
328            discoverable: true,
329            discovering: true,
330        });
331        assert_data_tree!(inspector, root: {
332            info: contains {
333                identifier: 1u64,
334                technology: "LowEnergy",
335                active: 1u64,
336                discoverable: 1u64,
337                discovering: 1u64
338            }
339        });
340    }
341}