blob: b77a577226cd705c96bd14eae44bbf40b41c74e8 [file] [log] [blame]
// Copyright 2023 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 bt_common::core::ltv::LtValue;
use bt_common::core::CodecId;
use bt_common::generic_audio::codec_capabilities::CodecCapability;
use bt_common::generic_audio::metadata_ltv::Metadata;
use bt_common::generic_audio::{AudioLocation, ContextType};
use bt_common::packet_encoding::{Decodable, Encodable};
use bt_common::Uuid;
use bt_gatt::{client::FromCharacteristic, Characteristic};
use std::collections::HashSet;
pub mod debug;
pub mod server;
/// UUID from Assigned Numbers section 3.4.
pub const PACS_UUID: Uuid = Uuid::from_u16(0x1850);
/// A Published Audio Capability (PAC) record.
/// Published Audio Capabilities represent the capabilities of a given peer to
/// transmit or receive Audio capabilities, exposed in PAC records, represent
/// the server audio capabilities independent of available resources at any
/// given time. Audio capabilities do not distinguish between unicast
/// Audio Streams or broadcast Audio Streams.
#[derive(Debug, Clone, PartialEq)]
pub struct PacRecord {
pub codec_id: CodecId,
pub codec_specific_capabilities: Vec<CodecCapability>,
pub metadata: Vec<Metadata>,
}
impl Decodable for PacRecord {
type Error = bt_common::packet_encoding::Error;
fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
let mut idx = 0;
let (codec_id, consumed) = CodecId::decode(&buf[idx..])?;
idx += consumed;
let codec_specific_capabilites_length = buf[idx] as usize;
idx += 1;
if idx + codec_specific_capabilites_length > buf.len() {
return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
}
let (results, consumed) =
CodecCapability::decode_all(&buf[idx..idx + codec_specific_capabilites_length]);
if consumed != codec_specific_capabilites_length {
return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
}
let codec_specific_capabilities = results.into_iter().filter_map(Result::ok).collect();
idx += consumed;
let metadata_length = buf[idx] as usize;
idx += 1;
if idx + metadata_length > buf.len() {
return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
}
let (results, consumed) = Metadata::decode_all(&buf[idx..idx + metadata_length]);
if consumed != metadata_length {
return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
}
let metadata = results.into_iter().filter_map(Result::ok).collect();
idx += consumed;
Ok((Self { codec_id, codec_specific_capabilities, metadata }, idx))
}
}
impl Encodable for PacRecord {
type Error = bt_common::packet_encoding::Error;
fn encoded_len(&self) -> core::primitive::usize {
5usize
+ self.codec_specific_capabilities.iter().fold(0, |a, x| a + x.encoded_len())
+ self.metadata.iter().fold(0, |a, x| a + x.encoded_len())
+ 2
}
fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
if buf.len() < self.encoded_len() {
return Err(Self::Error::BufferTooSmall);
}
self.codec_id.encode(&mut buf[0..]).unwrap();
let codec_capabilities_len =
self.codec_specific_capabilities.iter().fold(0, |a, x| a + x.encoded_len());
buf[5] = codec_capabilities_len as u8;
LtValue::encode_all(self.codec_specific_capabilities.clone().into_iter(), &mut buf[6..])?;
let metadata_len = self.metadata.iter().fold(0, |a, x| a + x.encoded_len());
buf[6 + codec_capabilities_len] = metadata_len as u8;
if metadata_len != 0 {
LtValue::encode_all(
self.metadata.clone().into_iter(),
&mut buf[6 + codec_capabilities_len + 1..],
)?;
}
Ok(())
}
}
fn pac_records_into_char_value(records: &Vec<PacRecord>) -> Vec<u8> {
let mut val = Vec::new();
val.push(records.len() as u8);
let mut idx = 1;
for record in records {
let record_len = record.encoded_len();
val.resize(val.len() + record_len, 0);
record.encode(&mut val[idx..]).unwrap();
idx += record_len;
}
val
}
fn pac_records_from_bytes(
value: &[u8],
) -> Result<Vec<PacRecord>, bt_common::packet_encoding::Error> {
if value.len() < 1 {
return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
}
let num_of_pac_records = value[0] as usize;
let mut next_idx = 1;
let mut capabilities = Vec::with_capacity(num_of_pac_records);
for _ in 0..num_of_pac_records {
let (cap, consumed) = PacRecord::decode(&value[next_idx..])?;
capabilities.push(cap);
next_idx += consumed;
}
Ok(capabilities)
}
/// One Sink Published Audio Capability Characteristic, or Sink PAC, exposed on
/// a service. More than one Sink PAC can exist on a given PACS service. If
/// multiple are exposed, they are returned separately and can be notified by
/// the server separately.
#[derive(Debug, PartialEq, Clone)]
pub struct SinkPac {
pub handle: bt_gatt::types::Handle,
pub capabilities: Vec<PacRecord>,
}
impl FromCharacteristic for SinkPac {
/// UUID from Assigned Numbers section 3.8.
const UUID: Uuid = Uuid::from_u16(0x2BC9);
fn from_chr(
characteristic: Characteristic,
value: &[u8],
) -> Result<Self, bt_common::packet_encoding::Error> {
let handle = characteristic.handle;
let capabilities = pac_records_from_bytes(value)?;
Ok(Self { handle, capabilities })
}
fn update(&mut self, new_value: &[u8]) -> Result<&mut Self, bt_common::packet_encoding::Error> {
self.capabilities = pac_records_from_bytes(new_value)?;
Ok(self)
}
}
/// One Sink Published Audio Capability Characteristic, or Sink PAC, exposed on
/// a service. More than one Sink PAC can exist on a given PACS service. If
/// multiple are exposed, they are returned separately and can be notified by
/// the server separately.
#[derive(Debug, PartialEq, Clone)]
pub struct SourcePac {
pub handle: bt_gatt::types::Handle,
pub capabilities: Vec<PacRecord>,
}
impl FromCharacteristic for SourcePac {
/// UUID from Assigned Numbers section 3.8.
const UUID: Uuid = Uuid::from_u16(0x2BCB);
fn from_chr(
characteristic: Characteristic,
value: &[u8],
) -> Result<Self, bt_common::packet_encoding::Error> {
let handle = characteristic.handle;
let capabilities = pac_records_from_bytes(value)?;
Ok(Self { handle, capabilities })
}
fn update(&mut self, new_value: &[u8]) -> Result<&mut Self, bt_common::packet_encoding::Error> {
self.capabilities = pac_records_from_bytes(new_value)?;
Ok(self)
}
}
#[derive(Debug, PartialEq, Clone, Default)]
pub struct AudioLocations {
pub locations: HashSet<AudioLocation>,
}
impl Decodable for AudioLocations {
type Error = bt_common::packet_encoding::Error;
fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
if buf.len() != 4 {
return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
}
let locations =
AudioLocation::from_bits(u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]))
.collect();
Ok((AudioLocations { locations }, 4))
}
}
impl Encodable for AudioLocations {
type Error = bt_common::packet_encoding::Error;
fn encoded_len(&self) -> core::primitive::usize {
4
}
fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
if buf.len() < 4 {
return Err(Self::Error::BufferTooSmall);
}
[buf[0], buf[1], buf[2], buf[3]] =
AudioLocation::to_bits(self.locations.iter()).to_le_bytes();
Ok(())
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct SourceAudioLocations {
pub handle: bt_gatt::types::Handle,
pub locations: AudioLocations,
}
impl SourceAudioLocations {
fn into_char_value(&self) -> Vec<u8> {
let mut val = Vec::with_capacity(self.locations.encoded_len());
val.resize(self.locations.encoded_len(), 0);
self.locations.encode(&mut val[..]).unwrap();
val
}
}
impl FromCharacteristic for SourceAudioLocations {
/// UUID from Assigned Numbers section 3.8.
const UUID: Uuid = Uuid::from_u16(0x2BCC);
fn from_chr(
characteristic: Characteristic,
value: &[u8],
) -> Result<Self, bt_common::packet_encoding::Error> {
let handle = characteristic.handle;
let (locations, _) = AudioLocations::decode(value)?;
Ok(Self { handle, locations })
}
fn update(&mut self, new_value: &[u8]) -> Result<&mut Self, bt_common::packet_encoding::Error> {
self.locations = AudioLocations::decode(new_value)?.0;
Ok(self)
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct SinkAudioLocations {
pub handle: bt_gatt::types::Handle,
pub locations: AudioLocations,
}
impl SinkAudioLocations {
fn into_char_value(&self) -> Vec<u8> {
let mut val = Vec::with_capacity(self.locations.encoded_len());
val.resize(self.locations.encoded_len(), 0);
self.locations.encode(&mut val[..]).unwrap();
val
}
}
impl FromCharacteristic for SinkAudioLocations {
const UUID: Uuid = Uuid::from_u16(0x2BCA);
fn from_chr(
characteristic: Characteristic,
value: &[u8],
) -> Result<Self, bt_common::packet_encoding::Error> {
let handle = characteristic.handle;
let (locations, _) = AudioLocations::decode(value)?;
Ok(Self { handle, locations })
}
fn update(&mut self, new_value: &[u8]) -> Result<&mut Self, bt_common::packet_encoding::Error> {
self.locations = AudioLocations::decode(new_value)?.0;
Ok(self)
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum AvailableContexts {
NotAvailable,
Available(HashSet<ContextType>),
}
impl Decodable for AvailableContexts {
type Error = bt_common::packet_encoding::Error;
fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
if buf.len() < 2 {
return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
}
let encoded = u16::from_le_bytes([buf[0], buf[1]]);
if encoded == 0 {
Ok((Self::NotAvailable, 2))
} else {
Ok((Self::Available(ContextType::from_bits(encoded).collect()), 2))
}
}
}
impl Encodable for AvailableContexts {
type Error = bt_common::packet_encoding::Error;
fn encoded_len(&self) -> core::primitive::usize {
2
}
fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
if buf.len() < 2 {
return Err(Self::Error::BufferTooSmall);
}
match self {
AvailableContexts::NotAvailable => [buf[0], buf[1]] = [0x00, 0x00],
AvailableContexts::Available(set) => {
[buf[0], buf[1]] = ContextType::to_bits(set.iter()).to_le_bytes()
}
}
Ok(())
}
}
impl From<&HashSet<ContextType>> for AvailableContexts {
fn from(value: &HashSet<ContextType>) -> Self {
if value.is_empty() {
return AvailableContexts::NotAvailable;
}
AvailableContexts::Available(value.clone())
}
}
#[derive(Debug, Clone)]
pub struct AvailableAudioContexts {
pub handle: bt_gatt::types::Handle,
pub sink: AvailableContexts,
pub source: AvailableContexts,
}
impl AvailableAudioContexts {
fn into_char_value(&self) -> Vec<u8> {
let mut val = Vec::with_capacity(self.sink.encoded_len() + self.source.encoded_len());
val.resize(self.sink.encoded_len() + self.source.encoded_len(), 0);
self.sink.encode(&mut val[0..]).unwrap();
self.source.encode(&mut val[2..]).unwrap();
val
}
}
impl FromCharacteristic for AvailableAudioContexts {
/// UUID from Assigned Numbers section 3.8.
const UUID: Uuid = Uuid::from_u16(0x2BCD);
fn from_chr(
characteristic: Characteristic,
value: &[u8],
) -> core::result::Result<Self, bt_common::packet_encoding::Error> {
let handle = characteristic.handle;
if value.len() < 4 {
return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
}
let sink = AvailableContexts::decode(&value[0..2])?.0;
let source = AvailableContexts::decode(&value[2..4])?.0;
Ok(Self { handle, sink, source })
}
fn update(
&mut self,
new_value: &[u8],
) -> core::result::Result<&mut Self, bt_common::packet_encoding::Error> {
if new_value.len() != 4 {
return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
}
let sink = AvailableContexts::decode(&new_value[0..2])?.0;
let source = AvailableContexts::decode(&new_value[2..4])?.0;
self.sink = sink;
self.source = source;
Ok(self)
}
}
#[derive(Debug, Clone)]
pub struct SupportedAudioContexts {
pub handle: bt_gatt::types::Handle,
pub sink: HashSet<ContextType>,
pub source: HashSet<ContextType>,
}
impl SupportedAudioContexts {
fn into_char_value(&self) -> Vec<u8> {
let mut val = Vec::with_capacity(self.encoded_len());
val.resize(self.encoded_len(), 0);
self.encode(&mut val[0..]).unwrap();
val
}
}
impl Encodable for SupportedAudioContexts {
type Error = bt_common::packet_encoding::Error;
fn encoded_len(&self) -> core::primitive::usize {
4
}
fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
if buf.len() < 4 {
return Err(Self::Error::BufferTooSmall);
}
[buf[0], buf[1]] = ContextType::to_bits(self.sink.iter()).to_le_bytes();
[buf[2], buf[3]] = ContextType::to_bits(self.source.iter()).to_le_bytes();
Ok(())
}
}
impl FromCharacteristic for SupportedAudioContexts {
const UUID: Uuid = Uuid::from_u16(0x2BCE);
fn from_chr(
characteristic: Characteristic,
value: &[u8],
) -> core::result::Result<Self, bt_common::packet_encoding::Error> {
let handle = characteristic.handle;
if value.len() < 4 {
return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
}
let sink = ContextType::from_bits(u16::from_le_bytes([value[0], value[1]])).collect();
let source = ContextType::from_bits(u16::from_le_bytes([value[2], value[3]])).collect();
Ok(Self { handle, sink, source })
}
fn update(
&mut self,
new_value: &[u8],
) -> core::result::Result<&mut Self, bt_common::packet_encoding::Error> {
if new_value.len() < 4 {
return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
}
self.sink =
ContextType::from_bits(u16::from_le_bytes([new_value[0], new_value[1]])).collect();
self.source =
ContextType::from_bits(u16::from_le_bytes([new_value[2], new_value[3]])).collect();
Ok(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use bt_common::{
generic_audio::codec_capabilities::{CodecCapabilityType, SamplingFrequency},
Uuid,
};
use bt_gatt::{
types::{AttributePermissions, Handle},
Characteristic,
};
use pretty_assertions::assert_eq;
const SINGLE_PAC_SIMPLE: [u8; 12] = [
0x01, // Num of records; 1
0x06, // CodecID: codec LC3,
0x00, 0x00, 0x00, 0x00, // CodecID: company and specific id (zero)
0x04, // Len of Codec capabilities
0x03, 0x01, // lt: supported_sampling_frequencies
0xC0, 0x02, // Supported: 44.1kHz, 48kHz, 96kHz
0x00, // Len of metadata
];
const MULTIPLE_PAC_COMPLEX: [u8; 29] = [
0x02, // Num of records; 2
0x05, // CodecID: codec MSBC,
0x00, 0x00, 0x00, 0x00, // CodecID: company and specific id (zero)
0x0A, // Len of Codec capabilities (10)
0x03, 0x01, // lt: supported_sampling_frequencies
0xC0, 0x02, // Supported: 44.1kHz, 48kHz, 96kHz
0x05, 0x04, // lt: Octets per codec frame
0x11, 0x00, // Minimum: 9
0x00, 0x10, // Maximum: 4096
0x04, // Len of metadata: 4
0x03, 0x01, 0x03, 0x00, // PreferredAudioContexts
0xFF, // CodecId: Vendor specific
0xE0, 0x00, // Google
0x01, 0x10, // ID 4097
0x00, // Len of codec capabilities (none)
0x00, // Len of metadata (none)
];
#[track_caller]
fn assert_has_frequencies(
cap: &bt_common::generic_audio::codec_capabilities::CodecCapability,
freqs: &[SamplingFrequency],
) {
let CodecCapability::SupportedSamplingFrequencies(set) = cap else {
unreachable!();
};
for freq in freqs {
assert!(set.contains(freq));
}
}
#[test]
fn simple_sink_pac() {
let pac = SinkPac::from_chr(
Characteristic {
handle: Handle(1),
uuid: Uuid::from_u16(0x2BC9),
properties: bt_gatt::types::CharacteristicProperties(vec![
bt_gatt::types::CharacteristicProperty::Read,
]),
permissions: AttributePermissions::default(),
descriptors: vec![],
},
&SINGLE_PAC_SIMPLE,
)
.expect("should decode correctly");
assert_eq!(pac.capabilities.len(), 1);
let cap = &pac.capabilities[0];
assert_eq!(cap.codec_id, CodecId::Assigned(bt_common::core::CodingFormat::Lc3));
let freq_cap = cap
.codec_specific_capabilities
.iter()
.find(|c| c.into_type() == CodecCapabilityType::SupportedSamplingFrequencies)
.unwrap();
assert_has_frequencies(
freq_cap,
&[
SamplingFrequency::F44100Hz,
SamplingFrequency::F48000Hz,
SamplingFrequency::F96000Hz,
],
);
}
#[test]
fn simple_source_pac() {
let pac = SinkPac::from_chr(
Characteristic {
handle: Handle(1),
uuid: Uuid::from_u16(0x2BC9),
properties: bt_gatt::types::CharacteristicProperties(vec![
bt_gatt::types::CharacteristicProperty::Read,
]),
permissions: AttributePermissions::default(),
descriptors: vec![],
},
&SINGLE_PAC_SIMPLE,
)
.expect("should decode correctly");
assert_eq!(pac.capabilities.len(), 1);
let cap = &pac.capabilities[0];
assert_eq!(cap.codec_id, CodecId::Assigned(bt_common::core::CodingFormat::Lc3));
let freq_cap = cap
.codec_specific_capabilities
.iter()
.find(|c| c.into_type() == CodecCapabilityType::SupportedSamplingFrequencies)
.unwrap();
assert_has_frequencies(
freq_cap,
&[
SamplingFrequency::F44100Hz,
SamplingFrequency::F48000Hz,
SamplingFrequency::F96000Hz,
],
);
}
#[test]
fn complex_sink_pac() {
let pac = SinkPac::from_chr(
Characteristic {
handle: Handle(1),
uuid: Uuid::from_u16(0x2BC9),
properties: bt_gatt::types::CharacteristicProperties(vec![
bt_gatt::types::CharacteristicProperty::Read,
]),
permissions: AttributePermissions::default(),
descriptors: vec![],
},
&MULTIPLE_PAC_COMPLEX,
)
.expect("should decode correctly");
assert_eq!(pac.capabilities.len(), 2);
let first = &pac.capabilities[0];
assert_eq!(first.codec_id, CodecId::Assigned(bt_common::core::CodingFormat::Msbc));
let freq_cap = first
.codec_specific_capabilities
.iter()
.find(|c| c.into_type() == CodecCapabilityType::SupportedSamplingFrequencies)
.unwrap();
assert_has_frequencies(
freq_cap,
&[
SamplingFrequency::F44100Hz,
SamplingFrequency::F48000Hz,
SamplingFrequency::F96000Hz,
],
);
let metadata = &first.metadata;
assert_eq!(metadata.len(), 1);
let Metadata::PreferredAudioContexts(p) = &metadata[0] else {
panic!("expected PreferredAudioContexts, got {:?}", metadata[0]);
};
assert_eq!(p.len(), 2);
let second = &pac.capabilities[1];
assert_eq!(
second.codec_id,
CodecId::VendorSpecific {
company_id: 0x00E0.into(),
vendor_specific_codec_id: 0x1001_u16,
}
);
assert_eq!(second.codec_specific_capabilities.len(), 0);
}
#[test]
fn available_contexts_no_sink() {
let available = AvailableAudioContexts::from_chr(
Characteristic {
handle: Handle(1),
uuid: Uuid::from_u16(0x28CD),
properties: bt_gatt::types::CharacteristicProperties(vec![
bt_gatt::types::CharacteristicProperty::Read,
]),
permissions: AttributePermissions::default(),
descriptors: vec![],
},
&[0x00, 0x00, 0x02, 0x04],
)
.expect("should decode correctly");
assert_eq!(available.handle, Handle(1));
assert_eq!(available.sink, AvailableContexts::NotAvailable);
let AvailableContexts::Available(a) = available.source else {
panic!("Source should be available");
};
assert_eq!(a, [ContextType::Conversational, ContextType::Alerts].into_iter().collect());
}
#[test]
fn available_contexts_wrong_size() {
let chr = Characteristic {
handle: Handle(1),
uuid: Uuid::from_u16(0x28CD),
properties: bt_gatt::types::CharacteristicProperties(vec![
bt_gatt::types::CharacteristicProperty::Read,
]),
permissions: AttributePermissions::default(),
descriptors: vec![],
};
let _ = AvailableAudioContexts::from_chr(chr.clone(), &[0x00, 0x00, 0x02])
.expect_err("should not decode with too short");
let available =
AvailableAudioContexts::from_chr(chr.clone(), &[0x00, 0x00, 0x02, 0x04, 0xCA, 0xFE])
.expect("should attempt to decode with too long");
assert_eq!(available.sink, AvailableContexts::NotAvailable);
let AvailableContexts::Available(a) = available.source else {
panic!("Source should be available");
};
assert_eq!(a, [ContextType::Conversational, ContextType::Alerts].into_iter().collect());
}
#[test]
fn supported_contexts() {
let chr = Characteristic {
handle: Handle(1),
uuid: Uuid::from_u16(0x28CE),
properties: bt_gatt::types::CharacteristicProperties(vec![
bt_gatt::types::CharacteristicProperty::Read,
]),
permissions: AttributePermissions::default(),
descriptors: vec![],
};
let supported =
SupportedAudioContexts::from_chr(chr.clone(), &[0x00, 0x00, 0x00, 0x00]).unwrap();
assert_eq!(supported.sink.len(), 0);
assert_eq!(supported.source.len(), 0);
let supported =
SupportedAudioContexts::from_chr(chr.clone(), &[0x08, 0x06, 0x06, 0x03]).unwrap();
assert_eq!(supported.sink.len(), 3);
assert_eq!(supported.source.len(), 4);
assert_eq!(
supported.source,
[
ContextType::Media,
ContextType::Conversational,
ContextType::Ringtone,
ContextType::Notifications
]
.into_iter()
.collect()
);
}
}