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::{Address, HostId, addresses_to_custom_string};
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_default(),
58 local_name: src.local_name.clone(),
59 discoverable: src.discoverable.unwrap_or_default(),
60 discovering: src.discovering.unwrap_or_default(),
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 writeln!(fmt, "\tlocal name:\t{}", self.local_name.as_deref().unwrap_or("(unknown)"))?;
101 writeln!(fmt, "\tdiscoverable:\t{}", self.discoverable)?;
102 writeln!(fmt, "\tdiscovering:\t{}", self.discovering)
103 }
104}
105
106#[cfg(target_os = "fuchsia")]
107impl Inspectable<HostInfo> {
108 pub fn update(&mut self, info: HostInfo) {
109 self.inspect.update(&info);
110 self.inner = info;
111 }
112}
113
114#[cfg(target_os = "fuchsia")]
115pub struct HostInfoInspect {
116 _inspect: inspect::Node,
117 identifier: inspect::UintProperty,
118 technology: inspect::StringProperty,
119 active: inspect::UintProperty,
120 discoverable: inspect::UintProperty,
121 discovering: inspect::UintProperty,
122}
123
124#[cfg(target_os = "fuchsia")]
125impl HostInfoInspect {
126 fn update(&mut self, info: &HostInfo) {
127 self.identifier.set(info.id.0);
128 self.technology.set(&info.technology.debug());
129 self.active.set(info.active.to_property());
130 self.discoverable.set(info.discoverable.to_property());
131 self.discovering.set(info.discovering.to_property());
132 }
133}
134
135#[cfg(target_os = "fuchsia")]
136impl IsInspectable for HostInfo {
137 type I = HostInfoInspect;
138}
139
140#[cfg(target_os = "fuchsia")]
141impl InspectData<HostInfo> for HostInfoInspect {
142 fn new(info: &HostInfo, inspect: inspect::Node) -> HostInfoInspect {
143 HostInfoInspect {
144 identifier: inspect.create_uint("identifier", info.id.0),
145 technology: inspect.create_string("technology", info.technology.debug()),
146 active: inspect.create_uint("active", info.active.to_property()),
147 discoverable: inspect.create_uint("discoverable", info.discoverable.to_property()),
148 discovering: inspect.create_uint("discovering", info.discovering.to_property()),
149 _inspect: inspect,
150 }
151 }
152}
153
154pub fn example_host(id: HostId, active: bool, discoverable: bool) -> fsys::HostInfo {
156 fsys::HostInfo {
157 id: Some(id.into()),
158 technology: Some(fsys::TechnologyType::LowEnergy),
159 active: Some(active),
160 local_name: Some("fuchsia123".to_string()),
161 discoverable: Some(discoverable),
162 discovering: Some(true),
163 addresses: Some(vec![Address::Public([1, 2, 3, 4, 5, 6]).into()]),
164 ..Default::default()
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 use diagnostics_assertions::assert_data_tree;
173 use fidl_fuchsia_bluetooth as fbt;
174 use fuchsia_inspect as inspect;
175
176 #[test]
177 fn from_fidl_id_not_present() {
178 let info = HostInfo::try_from(fsys::HostInfo::default());
179 assert!(info.is_err());
180 }
181
182 #[test]
183 fn from_fidl_technology_not_present() {
184 let info = fsys::HostInfo { id: Some(fbt::HostId { value: 1 }), ..Default::default() };
185 let info = HostInfo::try_from(info);
186 assert!(info.is_err());
187 }
188
189 #[test]
190 fn from_fidl_addresses_not_present() {
191 let info = fsys::HostInfo {
192 id: Some(fbt::HostId { value: 1 }),
193 technology: Some(fsys::TechnologyType::LowEnergy),
194 ..Default::default()
195 };
196 let info = HostInfo::try_from(info);
197 assert!(info.is_err());
198 }
199
200 #[test]
201 fn from_fidl_addresses_is_empty() {
202 let info = fsys::HostInfo {
203 id: Some(fbt::HostId { value: 1 }),
204 technology: Some(fsys::TechnologyType::LowEnergy),
205 addresses: Some(vec![]),
206 ..Default::default()
207 };
208 let info = HostInfo::try_from(info);
209 assert!(info.is_err());
210 }
211
212 #[test]
213 fn from_fidl_optional_fields_not_present() {
214 let info = fsys::HostInfo {
215 id: Some(fbt::HostId { value: 1 }),
216 technology: Some(fsys::TechnologyType::LowEnergy),
217 addresses: Some(vec![fbt::Address {
218 type_: fbt::AddressType::Public,
219 bytes: [1, 2, 3, 4, 5, 6],
220 }]),
221 ..Default::default()
222 };
223 let expected = HostInfo {
224 id: HostId(1),
225 technology: fsys::TechnologyType::LowEnergy,
226 addresses: vec![Address::Public([1, 2, 3, 4, 5, 6])],
227 active: false,
228 local_name: None,
229 discoverable: false,
230 discovering: false,
231 };
232
233 let info = HostInfo::try_from(info).expect("expected successful conversion");
234 assert_eq!(expected, info);
235 }
236
237 #[test]
238 fn from_fidl_optional_fields_present() {
239 let info = fsys::HostInfo {
240 id: Some(fbt::HostId { value: 1 }),
241 technology: Some(fsys::TechnologyType::LowEnergy),
242 active: Some(true),
243 local_name: Some("name".to_string()),
244 discoverable: Some(false),
245 discovering: Some(true),
246 addresses: Some(vec![fbt::Address {
247 type_: fbt::AddressType::Public,
248 bytes: [1, 2, 3, 4, 5, 6],
249 }]),
250 ..Default::default()
251 };
252 let expected = HostInfo {
253 id: HostId(1),
254 technology: fsys::TechnologyType::LowEnergy,
255 addresses: vec![Address::Public([1, 2, 3, 4, 5, 6])],
256 active: true,
257 local_name: Some("name".to_string()),
258 discoverable: false,
259 discovering: true,
260 };
261
262 let info = HostInfo::try_from(info).expect("expected successful conversion");
263 assert_eq!(expected, info);
264 }
265
266 #[test]
267 fn to_fidl() {
268 let info = HostInfo {
269 id: HostId(1),
270 technology: fsys::TechnologyType::LowEnergy,
271 addresses: vec![Address::Public([1, 2, 3, 4, 5, 6])],
272 active: false,
273 local_name: Some("name".to_string()),
274 discoverable: false,
275 discovering: false,
276 };
277 let expected = fsys::HostInfo {
278 id: Some(fbt::HostId { value: 1 }),
279 technology: Some(fsys::TechnologyType::LowEnergy),
280 active: Some(false),
281 local_name: Some("name".to_string()),
282 discoverable: Some(false),
283 discovering: Some(false),
284 addresses: Some(vec![fbt::Address {
285 type_: fbt::AddressType::Public,
286 bytes: [1, 2, 3, 4, 5, 6],
287 }]),
288 ..Default::default()
289 };
290
291 assert_eq!(expected, info.into());
292 }
293
294 #[fuchsia::test]
295 async fn inspect() {
296 let inspector = inspect::Inspector::default();
297 let node = inspector.root().create_child("info");
298 let info = HostInfo {
299 id: HostId(1),
300 technology: fsys::TechnologyType::LowEnergy,
301 addresses: vec![Address::Public([1, 2, 3, 4, 5, 6])],
302 active: false,
303 local_name: Some("name".to_string()),
304 discoverable: false,
305 discovering: true,
306 };
307 let mut info = Inspectable::new(info, node);
308 assert_data_tree!(inspector, root: {
309 info: contains {
310 identifier: 1u64,
311 technology: "LowEnergy",
312 active: 0u64,
313 discoverable: 0u64,
314 discovering: 1u64
315 }
316 });
317
318 info.update(HostInfo {
319 id: HostId(1),
320 technology: fsys::TechnologyType::LowEnergy,
321 addresses: vec![Address::Public([1, 2, 3, 4, 5, 6])],
322 active: true,
323 local_name: Some("foo".to_string()),
324 discoverable: true,
325 discovering: true,
326 });
327 assert_data_tree!(inspector, root: {
328 info: contains {
329 identifier: 1u64,
330 technology: "LowEnergy",
331 active: 1u64,
332 discoverable: 1u64,
333 discovering: 1u64
334 }
335 });
336 }
337}