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(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 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, fuchsia_inspect as inspect};
174
175 #[test]
176 fn from_fidl_id_not_present() {
177 let info = HostInfo::try_from(fsys::HostInfo::default());
178 assert!(info.is_err());
179 }
180
181 #[test]
182 fn from_fidl_technology_not_present() {
183 let info = fsys::HostInfo { id: Some(fbt::HostId { value: 1 }), ..Default::default() };
184 let info = HostInfo::try_from(info);
185 assert!(info.is_err());
186 }
187
188 #[test]
189 fn from_fidl_addresses_not_present() {
190 let info = fsys::HostInfo {
191 id: Some(fbt::HostId { value: 1 }),
192 technology: Some(fsys::TechnologyType::LowEnergy),
193 ..Default::default()
194 };
195 let info = HostInfo::try_from(info);
196 assert!(info.is_err());
197 }
198
199 #[test]
200 fn from_fidl_addresses_is_empty() {
201 let info = fsys::HostInfo {
202 id: Some(fbt::HostId { value: 1 }),
203 technology: Some(fsys::TechnologyType::LowEnergy),
204 addresses: Some(vec![]),
205 ..Default::default()
206 };
207 let info = HostInfo::try_from(info);
208 assert!(info.is_err());
209 }
210
211 #[test]
212 fn from_fidl_optional_fields_not_present() {
213 let info = fsys::HostInfo {
214 id: Some(fbt::HostId { value: 1 }),
215 technology: Some(fsys::TechnologyType::LowEnergy),
216 addresses: Some(vec![fbt::Address {
217 type_: fbt::AddressType::Public,
218 bytes: [1, 2, 3, 4, 5, 6],
219 }]),
220 ..Default::default()
221 };
222 let expected = HostInfo {
223 id: HostId(1),
224 technology: fsys::TechnologyType::LowEnergy,
225 addresses: vec![Address::Public([1, 2, 3, 4, 5, 6])],
226 active: false,
227 local_name: None,
228 discoverable: false,
229 discovering: false,
230 };
231
232 let info = HostInfo::try_from(info).expect("expected successful conversion");
233 assert_eq!(expected, info);
234 }
235
236 #[test]
237 fn from_fidl_optional_fields_present() {
238 let info = fsys::HostInfo {
239 id: Some(fbt::HostId { value: 1 }),
240 technology: Some(fsys::TechnologyType::LowEnergy),
241 active: Some(true),
242 local_name: Some("name".to_string()),
243 discoverable: Some(false),
244 discovering: Some(true),
245 addresses: Some(vec![fbt::Address {
246 type_: fbt::AddressType::Public,
247 bytes: [1, 2, 3, 4, 5, 6],
248 }]),
249 ..Default::default()
250 };
251 let expected = HostInfo {
252 id: HostId(1),
253 technology: fsys::TechnologyType::LowEnergy,
254 addresses: vec![Address::Public([1, 2, 3, 4, 5, 6])],
255 active: true,
256 local_name: Some("name".to_string()),
257 discoverable: false,
258 discovering: true,
259 };
260
261 let info = HostInfo::try_from(info).expect("expected successful conversion");
262 assert_eq!(expected, info);
263 }
264
265 #[test]
266 fn to_fidl() {
267 let info = HostInfo {
268 id: HostId(1),
269 technology: fsys::TechnologyType::LowEnergy,
270 addresses: vec![Address::Public([1, 2, 3, 4, 5, 6])],
271 active: false,
272 local_name: Some("name".to_string()),
273 discoverable: false,
274 discovering: false,
275 };
276 let expected = fsys::HostInfo {
277 id: Some(fbt::HostId { value: 1 }),
278 technology: Some(fsys::TechnologyType::LowEnergy),
279 active: Some(false),
280 local_name: Some("name".to_string()),
281 discoverable: Some(false),
282 discovering: Some(false),
283 addresses: Some(vec![fbt::Address {
284 type_: fbt::AddressType::Public,
285 bytes: [1, 2, 3, 4, 5, 6],
286 }]),
287 ..Default::default()
288 };
289
290 assert_eq!(expected, info.into());
291 }
292
293 #[fuchsia::test]
294 async fn inspect() {
295 let inspector = inspect::Inspector::default();
296 let node = inspector.root().create_child("info");
297 let info = HostInfo {
298 id: HostId(1),
299 technology: fsys::TechnologyType::LowEnergy,
300 addresses: vec![Address::Public([1, 2, 3, 4, 5, 6])],
301 active: false,
302 local_name: Some("name".to_string()),
303 discoverable: false,
304 discovering: true,
305 };
306 let mut info = Inspectable::new(info, node);
307 assert_data_tree!(inspector, root: {
308 info: contains {
309 identifier: 1u64,
310 technology: "LowEnergy",
311 active: 0u64,
312 discoverable: 0u64,
313 discovering: 1u64
314 }
315 });
316
317 info.update(HostInfo {
318 id: HostId(1),
319 technology: fsys::TechnologyType::LowEnergy,
320 addresses: vec![Address::Public([1, 2, 3, 4, 5, 6])],
321 active: true,
322 local_name: Some("foo".to_string()),
323 discoverable: true,
324 discovering: true,
325 });
326 assert_data_tree!(inspector, root: {
327 info: contains {
328 identifier: 1u64,
329 technology: "LowEnergy",
330 active: 1u64,
331 discoverable: 1u64,
332 discovering: 1u64
333 }
334 });
335 }
336}