1use fidl_fuchsia_bluetooth::{Appearance, DeviceClass};
6use fidl_fuchsia_bluetooth_sys as fsys;
7#[cfg(target_os = "fuchsia")]
8use fuchsia_inspect::Node;
9#[cfg(target_os = "fuchsia")]
10use fuchsia_inspect_contrib::log::WriteInspect;
11use std::fmt;
12
13use crate::assigned_numbers::find_service_uuid;
14use crate::error::Error;
15#[cfg(target_os = "fuchsia")]
16use crate::inspect::*;
17use crate::types::{Address, PeerId, Uuid};
18
19#[derive(Clone, Debug, PartialEq)]
20pub struct Peer {
21 pub id: PeerId,
23
24 pub address: Address,
30
31 pub technology: fsys::TechnologyType,
33
34 pub connected: bool,
36
37 pub bonded: bool,
39
40 pub name: Option<String>,
42
43 pub appearance: Option<Appearance>,
46
47 pub device_class: Option<DeviceClass>,
49
50 pub rssi: Option<i8>,
52
53 pub tx_power: Option<i8>,
55
56 pub le_services: Vec<Uuid>,
58
59 pub bredr_services: Vec<Uuid>,
61}
62
63#[cfg(target_os = "fuchsia")]
64impl Peer {
65 fn record_inspect(&self, node: &Node) {
67 node.record_string("peer_id", &self.id.to_string());
68 node.record_string("technology", &self.technology.debug());
69 node.record_string("appearance", &self.appearance.debug());
70 node.record_string("address", &self.address.to_string());
71 if let Some(rssi) = self.rssi {
72 node.record_int("rssi", rssi as i64);
73 }
74 if let Some(tx_power) = self.tx_power {
75 node.record_int("tx_power", tx_power as i64);
76 }
77 node.record_uint("connected", self.connected.to_property());
78 node.record_uint("bonded", self.bonded.to_property());
79 if !self.le_services.is_empty() {
80 node.record_string("le_services", &self.le_services.to_property());
81 }
82 if !self.bredr_services.is_empty() {
83 node.record_string("bredr_services", &self.bredr_services.to_property());
84 }
85 if let Some(name) = &self.name {
86 node.record_string("name", name);
87 }
88 }
89}
90
91pub fn peer_audio_stream_id(peer_id: PeerId, uuid: Uuid) -> [u8; 16] {
106 let mut unique_id = [0; 16];
107 unique_id[0] = 0x42;
108 unique_id[1] = 0x54;
109 let short: u16 = uuid.try_into().expect("UUID should be 16-bit");
110 unique_id[2..4].copy_from_slice(&(short.to_be_bytes()));
111 unique_id[8..].copy_from_slice(&(peer_id.0.to_be_bytes()));
112 unique_id
113}
114
115pub fn audio_stream_id_to_peer(audio_id: &[u8; 16]) -> Result<(PeerId, Uuid), Error> {
122 if audio_id[0] != 0x42 || audio_id[1] != 0x54 {
123 return Err(Error::other("Not a Bluetooth audio device"));
124 }
125 let uuid = Uuid::new16(u16::from_be_bytes(audio_id[2..4].try_into().unwrap()));
126 let peer_id = PeerId(u64::from_be_bytes(audio_id[8..16].try_into().unwrap()));
127 Ok((peer_id, uuid))
128}
129
130#[cfg(target_os = "fuchsia")]
131impl ImmutableDataInspect<Peer> for ImmutableDataInspectManager {
132 fn new(data: &Peer, manager: Node) -> ImmutableDataInspectManager {
133 data.record_inspect(&manager);
134 Self { _manager: manager }
135 }
136}
137
138#[cfg(target_os = "fuchsia")]
139impl IsInspectable for Peer {
140 type I = ImmutableDataInspectManager;
141}
142
143#[cfg(target_os = "fuchsia")]
144impl WriteInspect for Peer {
145 fn write_inspect<'a>(&self, writer: &Node, key: impl Into<std::borrow::Cow<'a, str>>) {
146 writer.record_child(key, |node| {
147 self.record_inspect(node);
148 });
149 }
150}
151
152impl fmt::Display for Peer {
153 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
154 writeln!(fmt, "Peer:")?;
155 writeln!(fmt, "\tId:\t\t{}", self.id)?;
156 writeln!(fmt, "\tAddress:\t{}", self.address)?;
157 writeln!(fmt, "\tTechnology:\t{:?}", self.technology)?;
158 if let Some(name) = &self.name {
159 writeln!(fmt, "\tName:\t\t{}", name)?;
160 }
161 if let Some(appearance) = &self.appearance {
162 writeln!(fmt, "\tAppearance:\t{:?}", appearance)?;
163 }
164 if let Some(rssi) = &self.rssi {
165 writeln!(fmt, "\tRSSI:\t\t{}", rssi)?;
166 }
167 if let Some(tx_power) = &self.tx_power {
168 writeln!(fmt, "\tTX Power:\t{}", tx_power)?;
169 }
170 writeln!(fmt, "\tConnected:\t{}", self.connected)?;
171 writeln!(fmt, "\tBonded:\t\t{}", self.bonded)?;
172 writeln!(fmt, "\tLE Services:\t{:?}", names_from_services(&self.le_services))?;
173 writeln!(fmt, "\tBR/EDR Serv.:\t{:?}", names_from_services(&self.bredr_services))?;
174 Ok(())
175 }
176}
177
178fn names_from_services(services: &Vec<Uuid>) -> Vec<String> {
179 services
180 .iter()
181 .map(|uuid| {
182 let uuid = uuid.to_string();
183 find_service_uuid(&uuid).map(|an| an.name.into()).unwrap_or(uuid)
184 })
185 .collect()
186}
187
188impl TryFrom<fsys::Peer> for Peer {
189 type Error = Error;
190 fn try_from(src: fsys::Peer) -> Result<Peer, Self::Error> {
191 Ok(Peer {
192 id: src.id.ok_or_else(|| Error::missing("sys.Peer.id"))?.into(),
193 address: src.address.ok_or_else(|| Error::missing("sys.Peer.address"))?.into(),
194 technology: src.technology.ok_or_else(|| Error::missing("sys.Peer.technology"))?,
195 connected: src.connected.unwrap_or(false),
196 bonded: src.bonded.unwrap_or(false),
197 name: src.name.clone(),
198 appearance: src.appearance,
199 device_class: src.device_class,
200 rssi: src.rssi,
201 tx_power: src.tx_power,
202 le_services: src.le_services.unwrap_or(vec![]).iter().map(Uuid::from).collect(),
203 bredr_services: src.bredr_services.unwrap_or(vec![]).iter().map(Uuid::from).collect(),
204 })
205 }
206}
207
208impl From<&Peer> for fsys::Peer {
209 fn from(src: &Peer) -> fsys::Peer {
210 fsys::Peer {
211 id: Some(src.id.into()),
212 address: Some(src.address.into()),
213 technology: Some(src.technology),
214 connected: Some(src.connected),
215 bonded: Some(src.bonded),
216 name: src.name.clone(),
217 appearance: src.appearance,
218 device_class: src.device_class,
219 rssi: src.rssi,
220 tx_power: src.tx_power,
221 services: None,
222 le_services: Some(src.le_services.iter().map(|uuid| uuid.into()).collect()),
223 bredr_services: Some(src.bredr_services.iter().map(|uuid| uuid.into()).collect()),
224 ..Default::default()
225 }
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232
233 use fidl_fuchsia_bluetooth as fbt;
234 use proptest::collection::vec;
235 use proptest::option;
236 use proptest::prelude::*;
237
238 #[test]
239 fn try_from_sys_id_not_present() {
240 let peer = fsys::Peer::default();
241 let peer = Peer::try_from(peer);
242 assert!(peer.is_err());
243 }
244
245 #[test]
246 fn try_from_sys_address_not_present() {
247 let peer = fsys::Peer { id: Some(fbt::PeerId { value: 1 }), ..Default::default() };
248 let peer = Peer::try_from(peer);
249 assert!(peer.is_err());
250 }
251
252 #[test]
253 fn try_from_sys_technology_not_present() {
254 let peer = fsys::Peer {
255 id: Some(fbt::PeerId { value: 1 }),
256 address: Some(fbt::Address {
257 type_: fbt::AddressType::Public,
258 bytes: [1, 2, 3, 4, 5, 6],
259 }),
260 ..Default::default()
261 };
262 let peer = Peer::try_from(peer);
263 assert!(peer.is_err());
264 }
265
266 fn any_address() -> impl Strategy<Value = Address> {
267 any::<[u8; 6]>().prop_map(Address::Public)
268 }
269
270 fn any_technology() -> impl Strategy<Value = fsys::TechnologyType> {
271 prop_oneof![
272 Just(fsys::TechnologyType::LowEnergy),
273 Just(fsys::TechnologyType::Classic),
274 Just(fsys::TechnologyType::DualMode),
275 ]
276 }
277
278 fn any_appearance() -> impl Strategy<Value = Appearance> {
279 prop_oneof![Just(Appearance::Unknown), Just(Appearance::Computer),]
280 }
281
282 fn any_device_class() -> impl Strategy<Value = DeviceClass> {
283 any::<u32>().prop_map(|value| DeviceClass { value })
284 }
285
286 fn any_uuids() -> impl Strategy<Value = Vec<Uuid>> {
287 vec(any::<[u8; 16]>().prop_map(Uuid::from_bytes), 0..3)
288 }
289
290 fn any_peer() -> impl Strategy<Value = Peer> {
291 (
292 (
294 any::<u64>(), any_address(),
296 any_technology(),
297 any::<bool>(), any::<bool>(), option::of("[a-zA-Z][a-zA-Z0-9_]*"), option::of(any_appearance()),
301 option::of(any_device_class()),
302 option::of(any::<i8>()), option::of(any::<i8>()), ),
305 any_uuids(), any_uuids(), )
308 .prop_map(
309 |(
310 (
311 id,
312 address,
313 technology,
314 connected,
315 bonded,
316 name,
317 appearance,
318 device_class,
319 rssi,
320 tx_power,
321 ),
322 le_services,
323 bredr_services,
324 )| {
325 Peer {
326 id: PeerId(id),
327 address,
328 technology,
329 connected,
330 bonded,
331 name,
332 appearance,
333 device_class,
334 rssi,
335 tx_power,
336 le_services,
337 bredr_services,
338 }
339 },
340 )
341 }
342
343 proptest! {
344 #[test]
345 fn peer_sys_roundtrip(peer in any_peer()) {
346 use std::convert::TryInto;
347
348 let sys = fsys::Peer::from(&peer);
349 assert_eq!(peer, sys.try_into().expect("valid Peer"));
350 }
351 }
352
353 proptest! {
354 #[test]
355 fn peer_audio_stream_id_generation(id1 in prop::num::u64::ANY, id2 in prop::num::u64::ANY, uuid1 in prop::num::u16::ANY, uuid2 in prop::num::u16::ANY) {
356 let peer1 = PeerId(id1);
357 let peer2 = PeerId(id2);
358 let service1: Uuid = Uuid::new16(uuid1);
359 let service2: Uuid = Uuid::new16(uuid2);
360
361 if id1 == id2 {
362 assert_eq!(peer_audio_stream_id(peer1, service1.clone()), peer_audio_stream_id(peer2, service1.clone()));
363 } else {
364 assert_ne!(peer_audio_stream_id(peer1, service1.clone()), peer_audio_stream_id(peer2, service1.clone()));
365 }
366
367 if service1 == service2 {
368 assert_eq!(peer_audio_stream_id(peer1, service1), peer_audio_stream_id(peer1, service2));
369 } else {
370 assert_ne!(peer_audio_stream_id(peer1, service1), peer_audio_stream_id(peer1, service2));
371 }
372 }
373 }
374
375 #[test]
376 fn peer_from_audio_stream_id() {
377 use fidl_fuchsia_bluetooth_bredr::ServiceClassProfileIdentifier;
378
379 let a2dp_device = [
380 0x42, 0x54, 0x11, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
381 0x07, 0x08,
382 ];
383 let (peer_id, uuid) = audio_stream_id_to_peer(&a2dp_device).expect("should be converted");
384 assert_eq!(peer_id.0, 0x0102030405060708);
385 assert_eq!(uuid, ServiceClassProfileIdentifier::AudioSource.into());
386
387 let non_bluetooth_device = [0x0A; 16];
388 let _ = audio_stream_id_to_peer(&non_bluetooth_device)
389 .expect_err("should have failed to convert");
390 }
391}