blob: fd6014d851206e1574fd49e5b5db961cf0c18348 [file] [log] [blame]
// Copyright 2024 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::packet_encoding::Decodable;
use bt_common::{codable_as_bitmask, decodable_enum};
use thiserror::Error;
use bt_common::core::CodecId;
use bt_common::generic_audio::metadata_ltv::Metadata;
/// Error type
#[derive(Debug, Error)]
pub enum Error {
#[error("Reserved for Future Use: {0}")]
ReservedFutureUse(String),
#[error("Server Only Operation")]
ServerOnlyOperation,
#[error("Service is already published")]
AlreadyPublished,
#[error("Issue publishing service: {0}")]
PublishError(bt_gatt::types::Error),
#[error("Unsupported configuration: {0}")]
Unsupported(String),
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq)]
pub enum ResponseCode {
Success { ase_id: AseId },
UnsupportedOpcode,
InvalidLength,
InvalidAseId { value: u8 },
InvalidAseStateMachineTransition { ase_id: AseId },
InvalidAseDirection { ase_id: AseId },
UnsupportedAudioCapablities { ase_id: AseId },
ConfigurationParameterValue { ase_id: AseId, issue: ResponseIssue, reason: ResponseReason },
Metadata { ase_id: AseId, issue: ResponseIssue, type_value: u8 },
InsufficientResources { ase_id: AseId },
UnspecifiedError { ase_id: AseId },
}
impl ResponseCode {
fn to_code(&self) -> u8 {
match self {
ResponseCode::Success { .. } => 0x00,
ResponseCode::UnsupportedOpcode => 0x01,
ResponseCode::InvalidLength => 0x02,
ResponseCode::InvalidAseId { .. } => 0x03,
ResponseCode::InvalidAseStateMachineTransition { .. } => 0x04,
ResponseCode::InvalidAseDirection { .. } => 0x05,
ResponseCode::UnsupportedAudioCapablities { .. } => 0x06,
ResponseCode::ConfigurationParameterValue {
issue: ResponseIssue::Unsupported, ..
} => 0x07,
ResponseCode::ConfigurationParameterValue {
issue: ResponseIssue::Rejected, ..
} => 0x08,
ResponseCode::ConfigurationParameterValue { issue: ResponseIssue::Invalid, .. } => 0x09,
ResponseCode::Metadata { issue: ResponseIssue::Unsupported, .. } => 0x0A,
ResponseCode::Metadata { issue: ResponseIssue::Rejected, .. } => 0x0B,
ResponseCode::Metadata { issue: ResponseIssue::Invalid, .. } => 0x0C,
ResponseCode::InsufficientResources { .. } => 0x0D,
ResponseCode::UnspecifiedError { .. } => 0x0E,
}
}
fn reason_byte(&self) -> u8 {
match self {
ResponseCode::ConfigurationParameterValue { reason, .. } => (*reason).into(),
ResponseCode::Metadata { type_value, .. } => *type_value,
_ => 0x00,
}
}
fn ase_id_value(&self) -> u8 {
match self {
ResponseCode::UnsupportedOpcode | ResponseCode::InvalidLength => 0x00,
ResponseCode::InvalidAseId { value } => *value,
ResponseCode::Success { ase_id }
| ResponseCode::InvalidAseStateMachineTransition { ase_id }
| ResponseCode::InvalidAseDirection { ase_id }
| ResponseCode::UnsupportedAudioCapablities { ase_id }
| ResponseCode::ConfigurationParameterValue { ase_id, .. }
| ResponseCode::Metadata { ase_id, .. }
| ResponseCode::InsufficientResources { ase_id }
| ResponseCode::UnspecifiedError { ase_id } => (*ase_id).into(),
}
}
pub(crate) fn notify_value(&self) -> Vec<u8> {
[self.ase_id_value(), self.to_code(), self.reason_byte()].into()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ResponseIssue {
Unsupported,
Rejected,
Invalid,
}
decodable_enum! {
#[non_exhaustive]
pub enum ResponseReason<u8, bt_common::packet_encoding::Error, OutOfRange> {
CodecId = 0x01,
CodecSpecificConfiguration = 0x02,
SduInterval = 0x03,
Framing = 0x04,
Phy = 0x05,
MaximumSduSize = 0x06,
RetransmissionNumber = 0x07,
MaxTransportLatency = 0x08,
PresentationDelay = 0x09,
InvalidAseCisMapping = 0x0A,
}
}
decodable_enum! {
#[derive(Default)]
pub enum AseState<u8, bt_common::packet_encoding::Error, OutOfRange> {
#[default]
Idle = 0x00,
CodecConfigured = 0x01,
QosConfigured = 0x02,
Enabling = 0x03,
Streaming = 0x04,
Disabling = 0x05,
Releasing = 0x06,
}
}
/// Audio Stream Endpoint Identifier
/// Exposed by the server in ASE characteristics
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AseId(pub u8);
impl AseId {
const BYTE_SIZE: usize = 1;
}
impl TryFrom<u8> for AseId {
type Error = ResponseCode;
fn try_from(value: u8) -> Result<Self, Self::Error> {
if value == 0 {
return Err(ResponseCode::InvalidAseId { value });
}
Ok(Self(value))
}
}
impl From<AseId> for u8 {
fn from(value: AseId) -> Self {
value.0
}
}
impl Decodable for AseId {
type Error = ResponseCode;
fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.len() < 1 {
return (Err(ResponseCode::InvalidLength), buf.len());
}
(buf[0].try_into(), 1)
}
}
decodable_enum! {
pub enum AseControlPointOpcode<u8, ResponseCode, UnsupportedOpcode> {
ConfigCodec = 0x01,
ConfigQos = 0x02,
Enable = 0x03,
ReceiverStartReady = 0x04,
Disable = 0x05,
ReceiverStopReady = 0x06,
UpdateMetadata = 0x07,
Release = 0x08,
}
}
/// ASE Control Operations. These can be initiated by a server or client.
/// Defined in Table 4.6 of ASCS v1.0
/// Marked non-exaustive as the remaining operations are RFU and new operations
/// could arrive but should be rejected if they are not recognized.
/// Some variants already contain responses as decoding errors are detected,
/// i.e. invalid parameters or metadata, which will be delivered after the
/// operation is complete with the results from the rest of the operation.
#[non_exhaustive]
#[derive(Debug, PartialEq, Clone)]
pub enum AseControlOperation {
ConfigCodec { codec_configurations: Vec<CodecConfiguration>, responses: Vec<ResponseCode> },
ConfigQos { qos_configurations: Vec<QosConfiguration>, responses: Vec<ResponseCode> },
Enable { ases_with_metadata: Vec<AseIdWithMetadata>, responses: Vec<ResponseCode> },
ReceiverStartReady { ases: Vec<AseId> },
// The only possible error here is InvalidLength
Disable { ases: Vec<AseId> },
// The only possible error here is InvalidLength
ReceiverStopReady { ases: Vec<AseId> },
UpdateMetadata { ases_with_metadata: Vec<AseIdWithMetadata>, responses: Vec<ResponseCode> },
// The only possible error here is InvalidLength
Release { ases: Vec<AseId> },
// This is only initiated by the server
Released,
}
impl AseControlOperation {
const MIN_BYTE_SIZE: usize = 3;
fn contains_invalid_length(&self) -> bool {
match self {
Self::ConfigCodec { responses, .. }
| Self::ConfigQos { responses, .. }
| Self::Enable { responses, .. }
| Self::UpdateMetadata { responses, .. } => {
responses.contains(&ResponseCode::InvalidLength)
}
_ => false,
}
}
}
impl TryFrom<AseControlOperation> for u8 {
type Error = Error;
fn try_from(value: AseControlOperation) -> Result<Self, Self::Error> {
match value {
AseControlOperation::ConfigCodec { .. } => Ok(0x01),
AseControlOperation::ConfigQos { .. } => Ok(0x02),
AseControlOperation::Enable { .. } => Ok(0x03),
AseControlOperation::ReceiverStartReady { .. } => Ok(0x04),
AseControlOperation::Disable { .. } => Ok(0x05),
AseControlOperation::ReceiverStopReady { .. } => Ok(0x06),
AseControlOperation::UpdateMetadata { .. } => Ok(0x07),
AseControlOperation::Release { .. } => Ok(0x08),
AseControlOperation::Released => Err(Error::ServerOnlyOperation),
}
}
}
fn partition_results<T, E>(collection: Vec<Result<T, E>>) -> (Vec<T>, Vec<E>) {
let mut oks = Vec::with_capacity(collection.len());
let mut errs = Vec::with_capacity(collection.len());
for item in collection {
match item {
Ok(x) => oks.push(x),
Err(e) => errs.push(e),
}
}
(oks, errs)
}
impl TryFrom<Vec<u8>> for AseControlOperation {
type Error = ResponseCode;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
if value.len() < Self::MIN_BYTE_SIZE {
return Err(ResponseCode::InvalidLength);
}
let operation: AseControlPointOpcode = value[0].try_into()?;
let number_of_ases = value[1] as usize;
if number_of_ases < 1 {
return Err(ResponseCode::InvalidLength);
}
let (op, consumed) = match operation {
AseControlPointOpcode::ConfigCodec => {
let (results, consumed) =
CodecConfiguration::decode_multiple(&value[2..], Some(number_of_ases));
let (codec_configurations, responses) = partition_results(results);
(Self::ConfigCodec { codec_configurations, responses }, consumed)
}
AseControlPointOpcode::ConfigQos => {
let (results, consumed) =
QosConfiguration::decode_multiple(&value[2..], Some(number_of_ases));
let (qos_configurations, responses) = partition_results(results);
(Self::ConfigQos { qos_configurations, responses }, consumed)
}
AseControlPointOpcode::Enable => {
let (results, consumed) =
AseIdWithMetadata::decode_multiple(&value[2..], Some(number_of_ases));
let (ases_with_metadata, responses) = partition_results(results);
(Self::Enable { ases_with_metadata, responses }, consumed)
}
AseControlPointOpcode::ReceiverStartReady => {
// Only InvalidLength is possible
let (results, consumed) = AseId::decode_multiple(&value[2..], Some(number_of_ases));
let ases = results.into_iter().collect::<Result<Vec<_>, ResponseCode>>()?;
(Self::ReceiverStartReady { ases }, consumed)
}
AseControlPointOpcode::Disable => {
// Only InvalidLength is possible
let (results, consumed) = AseId::decode_multiple(&value[2..], Some(number_of_ases));
let ases = results.into_iter().collect::<Result<Vec<_>, ResponseCode>>()?;
(Self::Disable { ases }, consumed)
}
AseControlPointOpcode::ReceiverStopReady => {
// Only InvalidLength is possible
let (results, consumed) = AseId::decode_multiple(&value[2..], Some(number_of_ases));
let ases = results.into_iter().collect::<Result<Vec<_>, ResponseCode>>()?;
(Self::ReceiverStopReady { ases }, consumed)
}
AseControlPointOpcode::UpdateMetadata => {
let (results, consumed) =
AseIdWithMetadata::decode_multiple(&value[2..], Some(number_of_ases));
let (ases_with_metadata, responses) = partition_results(results);
(Self::UpdateMetadata { ases_with_metadata, responses }, consumed)
}
AseControlPointOpcode::Release => {
// Only InvalidLength is possible
let (results, consumed) = AseId::decode_multiple(&value[2..], Some(number_of_ases));
let ases = results.into_iter().collect::<Result<Vec<_>, ResponseCode>>()?;
(Self::Release { ases }, consumed)
}
};
// A client-initiated ASE Control operation shall also be defined as an invalid
// length operation if the total length of all parameters written by the
// client is not equal to the total length of all fixed parameters plus
// the length of any variable length parameters for that operation as
// defined in Section 5.1 through Section 5.8.
if (consumed + 2) != value.len() {
return Err(ResponseCode::InvalidLength);
}
if op.contains_invalid_length() {
return Err(ResponseCode::InvalidLength);
}
Ok(op)
}
}
decodable_enum! {
pub enum TargetLatency<u8, bt_common::packet_encoding::Error, OutOfRange> {
TargetLowLatency = 0x01,
TargetBalanced = 0x02,
TargetHighReliability = 0x03,
}
}
impl TargetLatency {
const BYTE_SIZE: usize = 1;
}
decodable_enum! {
pub enum TargetPhy<u8, bt_common::packet_encoding::Error, OutOfRange> {
Le1MPhy = 0x01,
Le2MPhy = 0x02,
LeCodedPhy = 0x03,
}
}
impl TargetPhy {
const BYTE_SIZE: usize = 1;
}
decodable_enum! {
pub enum Phy<u8, bt_common::packet_encoding::Error, OutOfRange> {
Le1MPhy = 0b0001,
Le2MPhy = 0b0010,
LeCodedPhy = 0b0100,
}
}
codable_as_bitmask!(Phy, u8);
impl Phy {
const BYTE_SIZE: usize = 1;
}
/// Represents Config Codec parameters for a single ASE. See ASCS v1.0.1 Section
/// 5.2.
#[derive(Debug, Clone, PartialEq)]
pub struct CodecConfiguration {
pub ase_id: AseId,
pub target_latency: TargetLatency,
pub target_phy: TargetPhy,
pub codec_id: CodecId,
pub codec_specific_configuration: Vec<u8>,
}
impl CodecConfiguration {
const MIN_BYTE_SIZE: usize =
AseId::BYTE_SIZE + TargetLatency::BYTE_SIZE + TargetPhy::BYTE_SIZE + CodecId::BYTE_SIZE + 1;
}
impl Decodable for CodecConfiguration {
type Error = ResponseCode;
fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.len() < Self::MIN_BYTE_SIZE {
return (Err(ResponseCode::InvalidLength), buf.len());
}
let codec_specific_configuration_len = buf[Self::MIN_BYTE_SIZE - 1] as usize;
let total_len = codec_specific_configuration_len + Self::MIN_BYTE_SIZE;
if buf.len() < total_len {
return (Err(ResponseCode::InvalidLength), buf.len());
}
let try_decode_fn = |buf: &[u8]| {
let ase_id = AseId::try_from(buf[0])?;
let Ok(target_latency) = TargetLatency::try_from(buf[1]) else {
// TODO: unclear what to do if the target latency is out of range.
return Err(ResponseCode::ConfigurationParameterValue {
ase_id,
issue: ResponseIssue::Invalid,
reason: ResponseReason::MaxTransportLatency,
});
};
let Ok(target_phy) = TargetPhy::try_from(buf[2]) else {
return Err(ResponseCode::ConfigurationParameterValue {
ase_id,
issue: ResponseIssue::Unsupported,
reason: ResponseReason::Phy,
});
};
let Ok(codec_id) = CodecId::decode(&buf[3..]).0 else {
return Err(ResponseCode::ConfigurationParameterValue {
ase_id,
issue: ResponseIssue::Invalid,
reason: ResponseReason::CodecId,
});
};
let codec_specific_configuration = Vec::from(
&buf[Self::MIN_BYTE_SIZE..Self::MIN_BYTE_SIZE + codec_specific_configuration_len],
);
Ok(Self { ase_id, target_latency, target_phy, codec_id, codec_specific_configuration })
};
(try_decode_fn(buf), total_len)
}
}
/// Represents Config QoS parameters for a single ASE. See ASCS v1.0.1 Section
/// 5.2.
#[derive(Debug, Clone, PartialEq)]
pub struct QosConfiguration {
pub ase_id: AseId,
pub cig_id: CigId,
pub cis_id: CisId,
pub sdu_interval: SduInterval,
pub framing: Framing,
pub phy: Vec<Phy>,
pub max_sdu: MaxSdu,
pub retransmission_number: u8,
pub max_transport_latency: MaxTransportLatency,
pub presentation_delay: PresentationDelay,
}
impl QosConfiguration {
const BYTE_SIZE: usize = AseId::BYTE_SIZE
+ CigId::BYTE_SIZE
+ CisId::BYTE_SIZE
+ SduInterval::BYTE_SIZE
+ Framing::BYTE_SIZE
+ Phy::BYTE_SIZE
+ MaxSdu::BYTE_SIZE
+ 1 // retransmission_number
+ MaxTransportLatency::BYTE_SIZE
+ PresentationDelay::BYTE_SIZE;
}
impl Decodable for QosConfiguration {
type Error = ResponseCode;
fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.len() < QosConfiguration::BYTE_SIZE {
return (Err(ResponseCode::InvalidLength), buf.len());
}
let try_decode_fn = |buf: &[u8]| {
let ase_id = AseId::try_from(buf[0])?;
let cig_id =
CigId::try_from(buf[1]).map_err(|_e| ResponseCode::UnspecifiedError { ase_id })?;
let cis_id =
CisId::try_from(buf[2]).map_err(|_e| ResponseCode::UnspecifiedError { ase_id })?;
let sdu_interval;
match SduInterval::decode(&buf[3..]) {
(Ok(interval), _) => {
sdu_interval = interval;
}
(Err(bt_common::packet_encoding::Error::BufferTooSmall), _) => {
return Err(ResponseCode::InvalidLength);
}
(Err(bt_common::packet_encoding::Error::OutOfRange), _) => {
return Err(ResponseCode::ConfigurationParameterValue {
ase_id,
issue: ResponseIssue::Invalid,
reason: ResponseReason::SduInterval,
});
}
_ => unreachable!(),
};
let Ok(framing) = Framing::try_from(buf[6]) else {
return Err(ResponseCode::ConfigurationParameterValue {
ase_id,
issue: ResponseIssue::Invalid,
reason: ResponseReason::Framing,
});
};
let phy = Phy::from_bits(buf[7]).collect();
let max_sdu = [buf[8], buf[9]].try_into().map_err(|_e| {
ResponseCode::ConfigurationParameterValue {
issue: ResponseIssue::Invalid,
reason: ResponseReason::MaximumSduSize,
ase_id,
}
})?;
let retransmission_number = buf[10];
let max_transport_latency =
MaxTransportLatency::decode(&buf[11..]).0.map_err(|e| match e {
bt_common::packet_encoding::Error::BufferTooSmall => {
ResponseCode::InvalidLength
}
bt_common::packet_encoding::Error::OutOfRange => {
ResponseCode::ConfigurationParameterValue {
issue: ResponseIssue::Invalid,
reason: ResponseReason::MaxTransportLatency,
ase_id,
}
}
_ => unreachable!(),
})?;
let presentation_delay = PresentationDelay::decode(&buf[13..]).0?;
Ok(Self {
ase_id,
cig_id,
cis_id,
sdu_interval,
framing,
phy,
max_sdu,
retransmission_number,
max_transport_latency,
presentation_delay,
})
};
(try_decode_fn(buf), QosConfiguration::BYTE_SIZE)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct CigId(u8);
impl CigId {
const BYTE_SIZE: usize = 1;
}
impl TryFrom<u8> for CigId {
type Error = bt_common::packet_encoding::Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
if value > 0xEF { Err(Self::Error::OutOfRange) } else { Ok(CigId(value)) }
}
}
impl From<CigId> for u8 {
fn from(value: CigId) -> Self {
value.0
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct CisId(u8);
impl CisId {
const BYTE_SIZE: usize = 1;
}
impl TryFrom<u8> for CisId {
type Error = bt_common::packet_encoding::Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
if value > 0xEF { Err(Self::Error::OutOfRange) } else { Ok(CisId(value)) }
}
}
impl From<CisId> for u8 {
fn from(value: CisId) -> Self {
value.0
}
}
/// SDU Inteval parameter
/// This value is 24 bits long and little-endian on the wire.
/// It is stored native-endian here.
/// Valid range is [0x0000FF, 0x0FFFFF].
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct SduInterval(u32);
impl SduInterval {
const BYTE_SIZE: usize = 3;
}
impl Decodable for SduInterval {
type Error = bt_common::packet_encoding::Error;
fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.len() < Self::BYTE_SIZE {
return (Err(Self::Error::BufferTooSmall), buf.len());
}
let val = u32::from_le_bytes([buf[0], buf[1], buf[2], 0]);
if val < 0xFF || val > 0x0FFFFF {
return (Err(Self::Error::OutOfRange), Self::BYTE_SIZE);
}
(Ok(SduInterval(val)), Self::BYTE_SIZE)
}
}
decodable_enum! {
pub enum Framing<u8, bt_common::packet_encoding::Error, OutOfRange> {
Unframed = 0x00,
Framed = 0x01,
}
}
impl Framing {
const BYTE_SIZE: usize = 1;
}
/// Max SDU parameter value.
/// Valid range is 0x0000-0x0FFF
/// Transmitted in little-endian. Stored here in native-endian.
#[derive(Debug, Clone, PartialEq)]
pub struct MaxSdu(u16);
impl TryFrom<[u8; 2]> for MaxSdu {
type Error = bt_common::packet_encoding::Error;
fn try_from(value: [u8; 2]) -> Result<Self, Self::Error> {
let value = u16::from_le_bytes([value[0], value[1]]);
if value > 0xFFF {
return Err(Self::Error::OutOfRange);
}
Ok(MaxSdu(value))
}
}
impl MaxSdu {
const BYTE_SIZE: usize = 2;
}
/// Max Transport Latency
/// Valid range is [0x0005, 0x0FA0].
/// Transmitted in little-endian, Stored in native-endian.
#[derive(Debug, Clone, PartialEq)]
pub struct MaxTransportLatency(u16);
impl Decodable for MaxTransportLatency {
type Error = bt_common::packet_encoding::Error;
fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.len() < Self::BYTE_SIZE {
return (Err(Self::Error::BufferTooSmall), buf.len());
}
let val = u16::from_le_bytes([buf[0], buf[1]]);
if val < 0x0005 || val > 0x0FA0 {
return (Err(Self::Error::OutOfRange), Self::BYTE_SIZE);
}
(Ok(MaxTransportLatency(val)), Self::BYTE_SIZE)
}
}
impl TryFrom<std::time::Duration> for MaxTransportLatency {
type Error = bt_common::packet_encoding::Error;
fn try_from(value: std::time::Duration) -> Result<Self, Self::Error> {
let Ok(milliseconds) = u16::try_from(value.as_millis()) else {
return Err(Self::Error::OutOfRange);
};
if !(0x0005..=0x0FA0).contains(&milliseconds) {
return Err(Self::Error::OutOfRange);
}
Ok(Self(milliseconds))
}
}
impl MaxTransportLatency {
const BYTE_SIZE: usize = 2;
}
/// Presentation delay parameter value being requested by the client for an ASE.
/// This value is 24 bits long (0x00FFFFFF max)
/// Transmitted in little-endian, Stored in native-endian.
#[derive(Debug, Clone, PartialEq)]
pub struct PresentationDelay {
pub microseconds: u32,
}
impl PresentationDelay {
const BYTE_SIZE: usize = 3;
}
impl Decodable for PresentationDelay {
type Error = ResponseCode;
fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.len() < Self::BYTE_SIZE {
return (Err(ResponseCode::InvalidLength), buf.len());
}
let microseconds = u32::from_le_bytes([buf[0], buf[1], buf[2], 0]);
(Ok(PresentationDelay { microseconds }), Self::BYTE_SIZE)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct AseIdWithMetadata {
pub ase_id: AseId,
pub metadata: Vec<Metadata>,
}
impl AseIdWithMetadata {
const MIN_BYTE_SIZE: usize = 2;
}
impl Decodable for AseIdWithMetadata {
type Error = ResponseCode;
fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.len() < Self::MIN_BYTE_SIZE {
return (Err(ResponseCode::InvalidLength), buf.len());
}
let ase_id = match AseId::try_from(buf[0]) {
Ok(ase_id) => ase_id,
Err(e) => return (Err(e), buf.len()),
};
let metadata_length = buf[1] as usize;
let total_length = Self::MIN_BYTE_SIZE + metadata_length;
if buf.len() < total_length {
return (Err(ResponseCode::InvalidLength), buf.len());
}
use bt_common::core::ltv::Error as LtvError;
use bt_common::core::ltv::LtValue;
let (metadata_results, consumed) = Metadata::decode_all(&buf[2..2 + metadata_length]);
if consumed != metadata_length {
return (Err(ResponseCode::InvalidLength), buf.len());
}
let metadata_result: Result<Vec<Metadata>, LtvError<<Metadata as LtValue>::Type>> =
metadata_results.into_iter().collect();
let Ok(metadata) = metadata_result else {
match metadata_result.unwrap_err() {
LtvError::MissingType => return (Err(ResponseCode::InvalidLength), buf.len()),
LtvError::MissingData(_) => return (Err(ResponseCode::InvalidLength), buf.len()),
LtvError::UnrecognizedType(_, type_value) => {
return (
Err(ResponseCode::Metadata {
ase_id,
issue: ResponseIssue::Unsupported,
type_value,
}),
total_length,
);
}
LtvError::LengthOutOfRange(_, t, _) | LtvError::TypeFailedToDecode(t, _) => {
return (
Err(ResponseCode::Metadata {
ase_id,
issue: ResponseIssue::Invalid,
type_value: t.into(),
}),
total_length,
);
}
}
};
(Ok(Self { ase_id, metadata }), total_length)
}
}
#[cfg(test)]
mod tests {
use super::*;
use bt_common::packet_encoding::Encodable;
use bt_common::core::ltv::LtValue;
use bt_common::generic_audio::{codec_configuration, AudioLocation};
#[test]
fn codec_configuration_roundtrip() {
let codec_specific_configuration = vec![
codec_configuration::CodecConfiguration::SamplingFrequency(
codec_configuration::SamplingFrequency::F48000Hz,
),
codec_configuration::CodecConfiguration::FrameDuration(
codec_configuration::FrameDuration::TenMs,
),
codec_configuration::CodecConfiguration::AudioChannelAllocation(
[AudioLocation::FrontLeft].into_iter().collect(),
),
codec_configuration::CodecConfiguration::CodecFramesPerSdu(1),
];
let codec_config_len =
codec_specific_configuration.iter().fold(0, |a, x| a + x.encoded_len());
let mut vec = Vec::with_capacity(codec_config_len);
vec.resize(codec_config_len, 0);
LtValue::encode_all(codec_specific_configuration.clone().into_iter(), &mut vec[..])
.unwrap();
let _codec_config = CodecConfiguration {
ase_id: AseId(5),
target_latency: TargetLatency::TargetLowLatency,
target_phy: TargetPhy::Le1MPhy,
codec_id: CodecId::Assigned(bt_common::core::CodingFormat::Lc3),
codec_specific_configuration: vec,
};
}
#[test]
fn codec_configuration_decode() {
let encoded = &[
0x04, 0x02, 0x02, 0x06, 0x00, 0x00, 0x00, 0x00, 0x10, 0x02, 0x01, 0x08, 0x02, 0x02,
0x01, 0x05, 0x03, 0x01, 0x00, 0x00, 0x00, 0x03, 0x04, 0x78, 0x00,
];
let (codec_config, size) = CodecConfiguration::decode(&encoded[..]);
let codec_config = codec_config.unwrap();
assert_eq!(size, encoded.len());
assert_eq!(codec_config.ase_id, AseId(4));
assert_eq!(codec_config.target_latency, TargetLatency::TargetBalanced);
assert_eq!(codec_config.target_phy, TargetPhy::Le2MPhy);
assert_eq!(codec_config.codec_id, CodecId::Assigned(bt_common::core::CodingFormat::Lc3));
assert_eq!(codec_config.codec_specific_configuration.len(), 0x10);
}
#[test]
fn ase_control_operation_decode_config_codec() {
let encoded = &[
0x01, 0x01, 0x04, 0x02, 0x02, 0x06, 0x00, 0x00, 0x00, 0x00, 0x10, 0x02, 0x01, 0x08,
0x02, 0x02, 0x01, 0x05, 0x03, 0x01, 0x00, 0x00, 0x00, 0x03, 0x04, 0x78, 0x00,
];
let codec_config = CodecConfiguration::decode(&encoded[2..]).0.unwrap();
let encoded_vec: Vec<u8> = encoded.into_iter().copied().collect();
let operation = AseControlOperation::try_from(encoded_vec);
let Ok(operation) = operation else {
panic!("Expected decode to work correctly, got {operation:?}");
};
assert_eq!(
operation,
AseControlOperation::ConfigCodec {
codec_configurations: vec![codec_config],
responses: Vec::new()
}
);
}
#[test]
fn ase_control_operation_decode_config_codec_some_failures() {
#[rustfmt::skip]
let encoded = &[
0x01, 0x02, // Config Codec, Two ASE_IDs
// First ASE_ID config
0x04, 0x02, 0x02, 0x06, 0x00, 0x00, 0x00, 0x00, 0x10, 0x02, 0x01, 0x08,
0x02, 0x02, 0x01, 0x05, 0x03, 0x01, 0x00, 0x00, 0x00, 0x03, 0x04, 0x78, 0x00,
// Second ASE_ID config, fails based on invalid parameter value
0x05, 0x05, 0x02, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let codec_config = CodecConfiguration::decode(&encoded[2..]).0.unwrap();
let encoded_vec: Vec<u8> = encoded.into_iter().copied().collect();
let operation = AseControlOperation::try_from(encoded_vec);
let Ok(operation) = operation else {
panic!("Expected decode to work correctly, got {operation:?}");
};
match operation {
AseControlOperation::ConfigCodec { codec_configurations, responses } => {
assert_eq!(codec_configurations, vec![codec_config]);
assert_eq!(responses.len(), 1);
assert!(matches!(responses[0], ResponseCode::ConfigurationParameterValue { .. }));
}
x => panic!("Expected ConfigCodec, got {x:?}"),
};
}
#[test]
fn ase_control_operation_decode_invalid_length() {
#[rustfmt::skip]
let encoded = &[
0x01, 0x02, // Config Codec, Two ASE_IDs
// First ASE_ID config
0x04, 0x02, 0x02, 0x06, 0x00, 0x00, 0x00, 0x00, 0x10, 0x02, 0x01, 0x08,
0x02, 0x02, 0x01, 0x05, 0x03, 0x01, 0x00, 0x00, 0x00, 0x03, 0x04, 0x78, 0x00,
// Second ASE_ID config, fails based on invalid length
0x05, 0x05, 0x02, 0x06, 0x00, 0x00, 0x00,
];
let encoded_vec: Vec<u8> = encoded.into_iter().copied().collect();
let operation = AseControlOperation::try_from(encoded_vec);
assert_eq!(operation, Err(ResponseCode::InvalidLength));
}
#[test]
fn qos_configuration_decode() {
let encoded = &[
0x03, // ASE_ID,
0x00, // CIG_ID,
0x00, // CIS_ID,
0x10, 0x27, 0x00, 0x01, 0x02, 0x28, 0x00, 0x02, 0xf4, 0x01, 0x18, 0x00, 0x80,
];
let (qos_config, consumed) = QosConfiguration::decode(&encoded[..]);
let qos_config = qos_config.unwrap();
assert_eq!(consumed, encoded.len());
assert_eq!(qos_config.ase_id, AseId(3));
assert_eq!(qos_config.cig_id, CigId(0));
assert_eq!(qos_config.cis_id, CisId(0));
assert_eq!(qos_config.sdu_interval, SduInterval(10000));
assert_eq!(qos_config.framing, Framing::Framed);
assert_eq!(qos_config.phy, vec![Phy::Le2MPhy]);
assert_eq!(qos_config.max_sdu, MaxSdu(40));
assert_eq!(qos_config.retransmission_number, 2);
assert_eq!(qos_config.max_transport_latency, MaxTransportLatency(500));
assert_eq!(qos_config.presentation_delay, PresentationDelay { microseconds: 8388632 });
}
#[test]
fn aseid_with_metadata_decode() {
let encoded = &[
0x03, // ASE_ID
0x08, // metadata length
0x04, 0x04, 0x65, 0x6E, 0x67, // Language code
0x02, 0x03, 0x61, // Program Info
];
let (ase_with_metadata, consumed) = AseIdWithMetadata::decode(&encoded[..]);
let ase_with_metadata = ase_with_metadata.unwrap();
assert_eq!(consumed, encoded.len());
assert_eq!(ase_with_metadata.ase_id, AseId(3));
assert_eq!(ase_with_metadata.metadata.len(), 2);
}
#[test]
fn ase_control_operation_decode_qos() {
#[rustfmt::skip]
let encoded_qos = &[
0x02, 0x01, // Qos OpCode, 1 number_of_ases
0x03, // ASE_ID,
0x00, // CIG_ID,
0x00, // CIS_ID,
0x10, 0x27, 0x00, // SduInterval
0x01, 0x02, 0x28, 0x00, 0x02, 0xf4,
0x01, 0x18, 0x00, 0x80,
];
assert_eq!(QosConfiguration::BYTE_SIZE, encoded_qos.len() - 2);
let (qos_config, consumed) = QosConfiguration::decode(&encoded_qos[2..]);
assert!(qos_config.is_ok());
assert_eq!(consumed, encoded_qos.len() - 2);
let encoded_qos: Vec<u8> = encoded_qos.into_iter().copied().collect();
let operation = AseControlOperation::try_from(encoded_qos);
let Ok(_operation) = operation else {
panic!("Expected decode to work correctly, for {operation:?}");
};
#[rustfmt::skip]
let encoded_qos_one_fails = &[
0x02, 0x03, // Qos OpCode, 3 number_of_ases
0x03, // ASE_ID,
0x00, // CIG_ID,
0x00, // CIS_ID,
0x10, 0x27, 0x00, // SduInterval
0x01, 0x02, 0x28, 0x00, 0x02, 0xf4,
0x01, 0x18, 0x00, 0x80,
0x00, // ASE_ID, (AseId 0 is not allowed)
0x00, // CIG_ID,
0x00, // CIS_ID,
0x10, 0x27, 0x00, // SduInterval
0x01, 0x02, 0x28, 0x00, 0x02, 0xf4,
0x01, 0x18, 0x00, 0x80,
0x04, // ASE_ID
0x00, // CIG_ID,
0x00, // CIS_ID,
0x10, 0x17, 0x00, // SduInterval
0x01, 0x02, 0x28, 0x00, 0x02, 0xf4,
0x01, 0x18, 0x00, 0x80,
];
let encoded_qos_one_fails: Vec<u8> = encoded_qos_one_fails.into_iter().copied().collect();
match AseControlOperation::try_from(encoded_qos_one_fails) {
Ok(AseControlOperation::ConfigQos { qos_configurations, responses }) => {
assert_eq!(qos_configurations.len(), 2);
assert_eq!(responses.len(), 1);
assert_eq!(responses[0], ResponseCode::InvalidAseId { value: 0x00 });
}
x => panic!("Expected ConfigQos to succeed, got {x:?}"),
};
}
}