blob: 0c25fe45126a545e5fb9e5eadb2cb4c865d6960b [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use fidl_fuchsia_bluetooth_bredr as bredr;
use fuchsia_bluetooth::profile::{Attribute, DataElementConversionError};
use fuchsia_bluetooth::types::PeerId;
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use tracing::{debug, info, warn};
use crate::descriptor::{DescriptorList, DescriptorReadError};
use crate::sdp_data;
#[derive(Debug)]
enum HidDataElementError {
// The general error for an inability to convert a data element to the expected native type.
DataElementConversion(DataElementConversionError),
// A more specific error describing a failure to read or parse a HID descriptor.
DescriptorRead(DescriptorReadError),
}
impl From<DataElementConversionError> for HidDataElementError {
fn from(error: DataElementConversionError) -> Self {
Self::DataElementConversion(error)
}
}
impl From<DescriptorReadError> for HidDataElementError {
fn from(error: DescriptorReadError) -> Self {
Self::DescriptorRead(error)
}
}
#[derive(Debug, PartialEq)]
pub enum PeerInfoError {
UnexpectedProtocol {
protocols: Option<Vec<bredr::ProtocolDescriptor>>,
attributes: Vec<bredr::Attribute>,
},
UnparseableSdpRecord {
protocols: Option<Vec<bredr::ProtocolDescriptor>>,
attributes: Vec<bredr::Attribute>,
},
}
#[derive(Clone, Copy, Debug, FromPrimitive)]
enum HidAttribute {
// TODO(https://fxbug.dev/42179386) Parse the rest of the SDP record if necessary.
ParserVersion = 0x201,
DeviceSubclass = 0x202,
CountryCode = 0x203,
VirtualCable = 0x204,
ReconnectInitiate = 0x205,
DescriptorList = 0x206,
// TODO(https://fxbug.dev/42179170) implement HIDLangIDBase
BatteryPower = 0x209,
RemoteWake = 0x20A,
SupervisionTimeout = 0x20C,
NormallyConnectable = 0x20D,
BootDevice = 0x20E,
SsrHostMaxLatency = 0x20F,
SsrHostMinTimeout = 0x210,
}
/// Info parsed from the SDP record for the peer.
#[derive(Debug, Default, PartialEq)]
pub struct PeerInfo {
// TODO(https://fxbug.dev/42179386) Parse the rest of the SDP record if necessary.
pub parser_version: Option<u16>,
pub device_subclass: Option<u8>,
pub country_code: Option<u8>,
pub virtual_cable: Option<bool>,
pub reconnect_initiate: Option<bool>,
pub descriptor_list: Option<DescriptorList>,
//TODO(https://fxbug.dev/42179170) implement HIDLangIDBase
pub battery_power: Option<bool>,
pub remote_wake: Option<bool>,
pub supervision_timeout: Option<u16>,
pub normally_connectable: Option<bool>,
pub boot_device: Option<bool>,
pub ssr_host_max_latency: Option<u16>,
pub ssr_host_min_timeout: Option<u16>,
}
impl PeerInfo {
fn is_complete(&self) -> bool {
let complete =
// TODO(https://fxbug.dev/42179386) Parse the rest of the SDP record if necessary.
self.parser_version.is_some()
&& self.device_subclass.is_some()
&& self.country_code.is_some()
&& self.virtual_cable.is_some()
&& self.reconnect_initiate.is_some()
&& self.descriptor_list.is_some()
// TODO(https://fxbug.dev/42179170) implement HIDLangIDBase
// battery_power is optional
// remote_wake is optional
// supervision_timeout is optional
// normally_connectable is optional
&& self.boot_device.is_some()
// ssr_host_max_latency is optional
// ssr_host_min_timeout is optional
;
complete
}
fn set_attribute(
&mut self,
attribute_id: HidAttribute,
attribute: Attribute,
) -> Result<(), HidDataElementError> {
match attribute_id {
HidAttribute::ParserVersion => {
let value = attribute.element.try_into().map_err(HidDataElementError::from)?;
self.parser_version = Some(value);
}
HidAttribute::DeviceSubclass => {
let value = attribute.element.try_into().map_err(HidDataElementError::from)?;
self.device_subclass = Some(value);
}
HidAttribute::CountryCode => {
let value = attribute.element.try_into().map_err(HidDataElementError::from)?;
self.country_code = Some(value);
}
HidAttribute::VirtualCable => {
let value = attribute.element.try_into().map_err(HidDataElementError::from)?;
self.virtual_cable = Some(value);
}
HidAttribute::ReconnectInitiate => {
let value = attribute.element.try_into().map_err(HidDataElementError::from)?;
self.reconnect_initiate = Some(value);
}
HidAttribute::DescriptorList => {
let value = attribute.element.try_into().map_err(HidDataElementError::from)?;
self.descriptor_list = Some(value);
}
HidAttribute::BatteryPower => {
let value = attribute.element.try_into().map_err(HidDataElementError::from)?;
self.battery_power = Some(value);
}
HidAttribute::RemoteWake => {
let value = attribute.element.try_into().map_err(HidDataElementError::from)?;
self.remote_wake = Some(value);
}
HidAttribute::SupervisionTimeout => {
let value = attribute.element.try_into().map_err(HidDataElementError::from)?;
self.supervision_timeout = Some(value);
}
HidAttribute::NormallyConnectable => {
let value = attribute.element.try_into().map_err(HidDataElementError::from)?;
self.normally_connectable = Some(value);
}
HidAttribute::BootDevice => {
let value = attribute.element.try_into().map_err(HidDataElementError::from)?;
self.boot_device = Some(value);
}
HidAttribute::SsrHostMaxLatency => {
let value = attribute.element.try_into().map_err(HidDataElementError::from)?;
self.ssr_host_max_latency = Some(value);
}
HidAttribute::SsrHostMinTimeout => {
let value = attribute.element.try_into().map_err(HidDataElementError::from)?;
self.ssr_host_min_timeout = Some(value);
}
}
Ok(())
}
fn get_attribute_id_and_set_attribute(&mut self, attribute: Attribute, peer_id: PeerId) {
// TODO(https://fxbug.dev/42179386) Parse the rest of the SDP record if necessary.
let attribute_id = match HidAttribute::from_u16(attribute.id) {
Some(attr) => attr,
None => {
info!(
"Got unknown attribute ID {:} while reading SDP record from peer {:}.",
attribute.id, peer_id
);
return;
}
};
if let Err(err) = self.set_attribute(attribute_id, attribute) {
warn!(
"Unable to read SDP attribute with ID {:?} from peer {:} with error {:?}",
attribute_id, peer_id, err
);
}
}
pub fn new_from_sdp_record(
protocols: &Option<Vec<bredr::ProtocolDescriptor>>,
attributes: &Vec<bredr::Attribute>,
peer_id: PeerId,
) -> Result<Self, PeerInfoError> {
debug!("Parsing SDP record {:?}, {:?} for peer {:}", protocols, attributes, peer_id);
if protocols != &sdp_data::expected_protocols() {
warn!("Got unexpected protocol stack {:?} for peer {:?}", protocols, peer_id);
return Err(PeerInfoError::UnexpectedProtocol {
protocols: protocols.clone(),
attributes: attributes.clone(),
});
}
let mut peer_info: PeerInfo = Default::default();
for attribute in attributes {
peer_info.get_attribute_id_and_set_attribute(attribute.into(), peer_id);
}
if !peer_info.is_complete() {
warn!(
"Got incomplete or unparseable SDP record {:?} parsed as {:?} for peer {:}",
attributes, peer_info, peer_id
);
return Err(PeerInfoError::UnparseableSdpRecord {
protocols: protocols.clone(),
attributes: attributes.clone(),
});
};
debug!(
"Parsed SDP record {:?}, {:?} into {:?} for peer {:}",
protocols, attributes, peer_info, peer_id
);
Ok(peer_info)
}
}
#[cfg(test)]
mod tests {
use super::*;
use sdp_data::test::*;
use sdp_data::*;
#[fuchsia::test]
fn success() {
let peer_info =
PeerInfo::new_from_sdp_record(&expected_protocols(), &attributes(), PeerId(1));
assert_eq!(peer_info, Ok(expected_peer_info()));
}
#[fuchsia::test]
fn success_with_reordered_attributes() {
let mut reordered_attributes = attributes();
reordered_attributes.reverse();
let peer_info =
PeerInfo::new_from_sdp_record(&expected_protocols(), &reordered_attributes, PeerId(1));
assert_eq!(peer_info, Ok(expected_peer_info()));
}
#[fuchsia::test]
fn success_with_missing_optional_attribute() {
let mut expected_peer_info = expected_peer_info();
expected_peer_info.ssr_host_min_timeout = None;
let peer_info = PeerInfo::new_from_sdp_record(
&expected_protocols(),
&attributes_missing_optional(),
PeerId(1),
);
assert_eq!(peer_info, Ok(expected_peer_info));
}
#[fuchsia::test]
fn failure_with_missing_required_attribute() {
let peer_info = PeerInfo::new_from_sdp_record(
&expected_protocols(),
&attributes_missing_required(),
PeerId(1),
);
assert_eq!(
peer_info,
Err(PeerInfoError::UnparseableSdpRecord {
protocols: expected_protocols(),
attributes: attributes_missing_required(),
})
);
}
#[fuchsia::test]
fn failure_with_unexpected_protocols() {
let mut expected_protocols_internal = expected_protocols().unwrap();
let _ = expected_protocols_internal.pop();
let unexpected_protocols = Some(expected_protocols_internal);
let peer_info = PeerInfo::new_from_sdp_record(
&unexpected_protocols,
&attributes_missing_required(),
PeerId(1),
);
assert_eq!(
peer_info,
Err(PeerInfoError::UnexpectedProtocol {
protocols: unexpected_protocols,
attributes: attributes_missing_required(),
})
);
}
}