use fidl_fuchsia_bluetooth::{Appearance, DeviceClass};
use fidl_fuchsia_bluetooth_sys as fsys;
use fuchsia_inspect::Node;
use fuchsia_inspect_contrib::log::WriteInspect;
use std::fmt;
use crate::assigned_numbers::find_service_uuid;
use crate::error::Error;
use crate::inspect::*;
use crate::types::{Address, PeerId, Uuid};
#[derive(Clone, Debug, PartialEq)]
pub struct Peer {
pub id: PeerId,
pub address: Address,
pub technology: fsys::TechnologyType,
pub connected: bool,
pub bonded: bool,
pub name: Option<String>,
pub appearance: Option<Appearance>,
pub device_class: Option<DeviceClass>,
pub rssi: Option<i8>,
pub tx_power: Option<i8>,
pub le_services: Vec<Uuid>,
pub bredr_services: Vec<Uuid>,
}
impl Peer {
fn record_inspect(&self, node: &Node) {
node.record_string("peer_id", &self.id.to_string());
node.record_string("technology", &self.technology.debug());
node.record_string("appearance", &self.appearance.debug());
node.record_string("address", &self.address.to_string());
if let Some(rssi) = self.rssi {
node.record_int("rssi", rssi as i64);
}
if let Some(tx_power) = self.tx_power {
node.record_int("tx_power", tx_power as i64);
}
node.record_uint("connected", self.connected.to_property());
node.record_uint("bonded", self.bonded.to_property());
if !self.le_services.is_empty() {
node.record_string("le_services", &self.le_services.to_property());
}
if !self.bredr_services.is_empty() {
node.record_string("bredr_services", &self.bredr_services.to_property());
}
if let Some(name) = &self.name {
node.record_string("name", name);
}
}
}
pub fn peer_audio_stream_id(peer_id: PeerId, uuid: Uuid) -> [u8; 16] {
let mut unique_id = [0; 16];
unique_id[0] = 0x42;
unique_id[1] = 0x54;
let short: u16 = uuid.try_into().expect("UUID should be 16-bit");
unique_id[2..4].copy_from_slice(&(short.to_be_bytes()));
unique_id[8..].copy_from_slice(&(peer_id.0.to_be_bytes()));
unique_id
}
impl ImmutableDataInspect<Peer> for ImmutableDataInspectManager {
fn new(data: &Peer, manager: Node) -> ImmutableDataInspectManager {
data.record_inspect(&manager);
Self { _manager: manager }
}
}
impl IsInspectable for Peer {
type I = ImmutableDataInspectManager;
}
impl WriteInspect for Peer {
fn write_inspect(&self, writer: &Node, key: impl Into<fuchsia_inspect::StringReference>) {
writer.record_child(key, |node| {
self.record_inspect(node);
});
}
}
impl fmt::Display for Peer {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(fmt, "Peer:")?;
writeln!(fmt, "\tId:\t\t{}", self.id)?;
writeln!(fmt, "\tAddress:\t{}", self.address.to_string())?;
writeln!(fmt, "\tTechnology:\t{:?}", self.technology)?;
if let Some(name) = &self.name {
writeln!(fmt, "\tName:\t\t{}", name)?;
}
if let Some(appearance) = &self.appearance {
writeln!(fmt, "\tAppearance:\t{:?}", appearance)?;
}
if let Some(rssi) = &self.rssi {
writeln!(fmt, "\tRSSI:\t\t{}", rssi)?;
}
if let Some(tx_power) = &self.tx_power {
writeln!(fmt, "\tTX Power:\t{}", tx_power)?;
}
writeln!(fmt, "\tConnected:\t{}", self.connected)?;
writeln!(fmt, "\tBonded:\t\t{}", self.bonded)?;
writeln!(fmt, "\tLE Services:\t{:?}", names_from_services(&self.le_services))?;
writeln!(fmt, "\tBR/EDR Serv.:\t{:?}", names_from_services(&self.bredr_services))?;
Ok(())
}
}
fn names_from_services(services: &Vec<Uuid>) -> Vec<String> {
services
.iter()
.map(|uuid| {
let uuid = uuid.to_string();
find_service_uuid(&uuid).map(|an| an.name.into()).unwrap_or(uuid)
})
.collect()
}
impl TryFrom<fsys::Peer> for Peer {
type Error = Error;
fn try_from(src: fsys::Peer) -> Result<Peer, Self::Error> {
Ok(Peer {
id: src.id.ok_or_else(|| Error::missing("sys.Peer.id"))?.into(),
address: src.address.ok_or_else(|| Error::missing("sys.Peer.address"))?.into(),
technology: src.technology.ok_or_else(|| Error::missing("sys.Peer.technology"))?,
connected: src.connected.unwrap_or(false),
bonded: src.bonded.unwrap_or(false),
name: src.name.clone(),
appearance: src.appearance,
device_class: src.device_class,
rssi: src.rssi,
tx_power: src.tx_power,
le_services: src.le_services.unwrap_or(vec![]).iter().map(Uuid::from).collect(),
bredr_services: src.bredr_services.unwrap_or(vec![]).iter().map(Uuid::from).collect(),
})
}
}
impl From<&Peer> for fsys::Peer {
fn from(src: &Peer) -> fsys::Peer {
fsys::Peer {
id: Some(src.id.into()),
address: Some(src.address.into()),
technology: Some(src.technology),
connected: Some(src.connected),
bonded: Some(src.bonded),
name: src.name.clone(),
appearance: src.appearance,
device_class: src.device_class,
rssi: src.rssi,
tx_power: src.tx_power,
services: None,
le_services: Some(src.le_services.iter().map(|uuid| uuid.into()).collect()),
bredr_services: Some(src.bredr_services.iter().map(|uuid| uuid.into()).collect()),
..Default::default()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use fidl_fuchsia_bluetooth as fbt;
use proptest::collection::vec;
use proptest::option;
use proptest::prelude::*;
#[test]
fn try_from_sys_id_not_present() {
let peer = fsys::Peer::default();
let peer = Peer::try_from(peer);
assert!(peer.is_err());
}
#[test]
fn try_from_sys_address_not_present() {
let peer = fsys::Peer { id: Some(fbt::PeerId { value: 1 }), ..Default::default() };
let peer = Peer::try_from(peer);
assert!(peer.is_err());
}
#[test]
fn try_from_sys_technology_not_present() {
let peer = fsys::Peer {
id: Some(fbt::PeerId { value: 1 }),
address: Some(fbt::Address {
type_: fbt::AddressType::Public,
bytes: [1, 2, 3, 4, 5, 6],
}),
..Default::default()
};
let peer = Peer::try_from(peer);
assert!(peer.is_err());
}
fn any_address() -> impl Strategy<Value = Address> {
any::<[u8; 6]>().prop_map(Address::Public)
}
fn any_technology() -> impl Strategy<Value = fsys::TechnologyType> {
prop_oneof![
Just(fsys::TechnologyType::LowEnergy),
Just(fsys::TechnologyType::Classic),
Just(fsys::TechnologyType::DualMode),
]
}
fn any_appearance() -> impl Strategy<Value = Appearance> {
prop_oneof![Just(Appearance::Unknown), Just(Appearance::Computer),]
}
fn any_device_class() -> impl Strategy<Value = DeviceClass> {
any::<u32>().prop_map(|value| DeviceClass { value })
}
fn any_uuids() -> impl Strategy<Value = Vec<Uuid>> {
vec(any::<[u8; 16]>().prop_map(Uuid::from_bytes), 0..3)
}
fn any_peer() -> impl Strategy<Value = Peer> {
(
(
any::<u64>(), any_address(),
any_technology(),
any::<bool>(), any::<bool>(), option::of("[a-zA-Z][a-zA-Z0-9_]*"), option::of(any_appearance()),
option::of(any_device_class()),
option::of(any::<i8>()), option::of(any::<i8>()), ),
any_uuids(), any_uuids(), )
.prop_map(
|(
(
id,
address,
technology,
connected,
bonded,
name,
appearance,
device_class,
rssi,
tx_power,
),
le_services,
bredr_services,
)| {
Peer {
id: PeerId(id),
address,
technology,
connected,
bonded,
name,
appearance,
device_class,
rssi,
tx_power,
le_services,
bredr_services,
}
},
)
}
proptest! {
#[test]
fn peer_sys_roundtrip(peer in any_peer()) {
use std::convert::TryInto;
let sys = fsys::Peer::from(&peer);
assert_eq!(peer, sys.try_into().expect("valid Peer"));
}
}
proptest! {
#[test]
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) {
let peer1 = PeerId(id1);
let peer2 = PeerId(id2);
let service1: Uuid = Uuid::new16(uuid1);
let service2: Uuid = Uuid::new16(uuid2);
if id1 == id2 {
assert_eq!(peer_audio_stream_id(peer1, service1.clone()), peer_audio_stream_id(peer2, service1.clone()));
} else {
assert_ne!(peer_audio_stream_id(peer1, service1.clone()), peer_audio_stream_id(peer2, service1.clone()));
}
if service1 == service2 {
assert_eq!(peer_audio_stream_id(peer1, service1), peer_audio_stream_id(peer1, service2));
} else {
assert_ne!(peer_audio_stream_id(peer1, service1), peer_audio_stream_id(peer1, service2));
}
}
}
}