1use 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#[derive(Clone, Debug, PartialEq)]
17pub struct HostInfo {
18 pub id: HostId,
20
21 pub technology: fsys::TechnologyType,
23
24 pub addresses: Vec<Address>,
27
28 pub active: bool,
31
32 pub local_name: Option<String>,
35
36 pub discoverable: bool,
39
40 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)] 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
159pub 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}