blob: 324a2b74b5ecd39b06533546c855163c2750998e [file]
// 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, Encodable};
use bt_common::{codable_as_bitmask, decodable_enum, Uuid};
use bt_gatt::types::Handle;
use thiserror::Error;
use crate::client::ClientError;
use crate::server::ServerError;
use bt_common::core::ltv::LtValue;
use bt_common::core::CodecId;
use bt_common::generic_audio::metadata_ltv::Metadata;
use bt_gatt::types::Error as BtGattError;
/// Error type
#[derive(Debug, Error)]
pub enum Error {
#[error("ASCS Server error: {0}")]
Server(#[from] ServerError),
#[error("ASCS Client error: {0}")]
Client(#[from] ClientError),
#[error("GATT operation error: {0}")]
Gatt(#[from] BtGattError),
#[error("Internal error occurred: {0}")]
Internal(String),
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResponseCode {
Success { ase_id: AseId },
UnsupportedOpcode { opcode_byte: u8 },
InvalidLength { opcode_byte: u8 },
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 {
pub(crate) 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,
}
}
pub fn invalid_length() -> Self {
Self::invalid_length_opcode(0)
}
pub fn invalid_length_opcode(opcode_byte: u8) -> Self {
Self::InvalidLength { opcode_byte }
}
pub fn unsupported_opcode(opcode_byte: u8) -> Self {
Self::UnsupportedOpcode { opcode_byte }
}
pub(crate) fn is_invalid_length(&self) -> bool {
matches!(self, Self::InvalidLength { .. })
}
fn reason_byte(&self) -> u8 {
match self {
ResponseCode::ConfigurationParameterValue { reason, .. } => (*reason).into(),
ResponseCode::Metadata { type_value, .. } => *type_value,
_ => 0x00,
}
}
/// Get the ASE_ID value for this ResponseCode. This is included in the
/// control point notification in response to an operation. For
/// UnsupportedOpcode and InvaildLength, the ASE_ID is defined by the
/// spec to be 0x00.
pub(crate) 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 error_notify_value(&self) -> Vec<u8> {
match self {
Self::UnsupportedOpcode { opcode_byte } | Self::InvalidLength { opcode_byte } => {
[*opcode_byte, 0xFFu8].into_iter().chain(self.notify_value()).collect()
}
_ => vec![],
}
}
pub(crate) fn notify_value(&self) -> Vec<u8> {
[self.ase_id_value(), self.to_code(), self.reason_byte()].into()
}
pub fn decode_response(
buf: &[u8],
opcode: u8,
) -> Result<Self, bt_common::packet_encoding::Error> {
if buf.len() < 3 {
return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
}
let ase_id_val = buf[0];
let code = buf[1];
let reason = buf[2];
let ase_id = AseId(ase_id_val);
match code {
0x00 => Ok(Self::Success { ase_id }),
0x01 => {
if ase_id_val != 0 {
return Err(bt_common::packet_encoding::Error::OutOfRange);
}
Ok(Self::UnsupportedOpcode { opcode_byte: opcode })
}
0x02 => {
if ase_id_val != 0 {
return Err(bt_common::packet_encoding::Error::OutOfRange);
}
Ok(Self::InvalidLength { opcode_byte: opcode })
}
0x03 => Ok(Self::InvalidAseId { value: ase_id_val }),
0x04 => Ok(Self::InvalidAseStateMachineTransition { ase_id }),
0x05 => Ok(Self::InvalidAseDirection { ase_id }),
0x06 => Ok(Self::UnsupportedAudioCapablities { ase_id }),
0x07 => {
let reason = ResponseReason::try_from(reason)
.map_err(|_| bt_common::packet_encoding::Error::OutOfRange)?;
Ok(Self::ConfigurationParameterValue {
ase_id,
issue: ResponseIssue::Unsupported,
reason,
})
}
0x08 => {
let reason = ResponseReason::try_from(reason)
.map_err(|_| bt_common::packet_encoding::Error::OutOfRange)?;
Ok(Self::ConfigurationParameterValue {
ase_id,
issue: ResponseIssue::Rejected,
reason,
})
}
0x09 => {
let reason = ResponseReason::try_from(reason)
.map_err(|_| bt_common::packet_encoding::Error::OutOfRange)?;
Ok(Self::ConfigurationParameterValue {
ase_id,
issue: ResponseIssue::Invalid,
reason,
})
}
0x0A => {
Ok(Self::Metadata { ase_id, issue: ResponseIssue::Unsupported, type_value: reason })
}
0x0B => {
Ok(Self::Metadata { ase_id, issue: ResponseIssue::Rejected, type_value: reason })
}
0x0C => {
Ok(Self::Metadata { ase_id, issue: ResponseIssue::Invalid, type_value: reason })
}
0x0D => Ok(Self::InsufficientResources { ase_id }),
0x0E => Ok(Self::UnspecifiedError { ase_id }),
_ => Err(bt_common::packet_encoding::Error::OutOfRange),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
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::invalid_length()), buf.len());
}
(buf[0].try_into(), 1)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum AseAdditionalParameters {
None,
CodecConfigured {
framing: Framing,
preferred_phys: Vec<Phy>,
preferred_retransmission_number: u8,
max_transport_latency: MaxTransportLatency,
presentation_delay_range: PresentationDelayRange,
codec_id: CodecId,
codec_config: Vec<u8>,
},
QosConfigured {
configuration: QosConfiguration,
},
Streaming {
cig_id: CigId,
cis_id: CisId,
metadata: Vec<Metadata>,
qos_configured: Option<QosConfiguration>,
},
}
impl AseAdditionalParameters {
pub fn char_size(&self) -> usize {
match self {
AseAdditionalParameters::None => 0,
AseAdditionalParameters::CodecConfigured { codec_config, .. } => {
23 + codec_config.len()
}
AseAdditionalParameters::QosConfigured { .. } => 15,
AseAdditionalParameters::Streaming { metadata, .. } => {
metadata.iter().fold(3, |total, m| total + m.encoded_len() as usize)
}
}
}
pub fn into_char_value(&self) -> Vec<u8> {
match self {
AseAdditionalParameters::None => Vec::new(),
AseAdditionalParameters::CodecConfigured {
framing,
preferred_phys,
preferred_retransmission_number,
max_transport_latency,
presentation_delay_range,
codec_id,
codec_config,
} => {
let mut value = Vec::with_capacity(self.char_size());
value.resize(self.char_size() - codec_config.len(), 0);
value[0] = (*framing) as u8;
value[1] = Phy::to_bits(preferred_phys.iter());
value[2] = *preferred_retransmission_number;
max_transport_latency.encode(&mut value[3..]).unwrap();
presentation_delay_range.encode(&mut value[5..]).unwrap();
codec_id.encode(&mut value[17..]).unwrap();
value[22] = codec_config.len() as u8;
value.extend(codec_config.clone());
value
}
AseAdditionalParameters::QosConfigured {
configuration:
QosConfiguration {
cig_id,
cis_id,
sdu_interval,
framing,
phy,
max_sdu,
retransmission_number,
max_transport_latency,
presentation_delay,
..
},
} => {
let mut value = Vec::with_capacity(self.char_size());
value.resize(self.char_size(), 0);
cig_id.encode(&mut value[0..]).unwrap();
cis_id.encode(&mut value[1..]).unwrap();
sdu_interval.encode(&mut value[2..]).unwrap();
framing.encode(&mut value[5..]).unwrap();
value[6] = Phy::to_bits(phy.iter());
max_sdu.encode(&mut value[7..]).unwrap();
value[9] = *retransmission_number;
max_transport_latency.encode(&mut value[10..]).unwrap();
presentation_delay.encode(&mut value[12..]).unwrap();
value
}
AseAdditionalParameters::Streaming { cig_id, cis_id, metadata, .. } => {
let mut value = Vec::with_capacity(self.char_size());
value.resize(self.char_size(), 0);
cig_id.encode(&mut value[0..]).unwrap();
cis_id.encode(&mut value[1..]).unwrap();
value[2] = metadata.iter().fold(0usize, |acc, i| acc + i.encoded_len()) as u8;
LtValue::encode_all(metadata.clone().into_iter(), &mut value[3..]).unwrap();
value
}
}
}
pub fn decode(
ase_id: AseId,
state: &AseState,
buf: &[u8],
) -> (Result<Self, ResponseCode>, usize) {
match state {
AseState::Idle | AseState::Releasing => (Ok(Self::None), 0),
AseState::CodecConfigured => {
if buf.len() < 23 {
return (Err(ResponseCode::invalid_length()), buf.len());
}
let Ok(framing) = Framing::try_from(buf[0]) else {
return (Err(ResponseCode::invalid_length()), buf.len());
};
let preferred_phys = Phy::from_bits(buf[1]).collect();
let preferred_retransmission_number = buf[2];
let Ok(max_transport_latency) = MaxTransportLatency::decode(&buf[3..5]).0 else {
return (Err(ResponseCode::invalid_length()), buf.len());
};
let Ok(presentation_delay_range) = PresentationDelayRange::decode(&buf[5..17]).0
else {
return (Err(ResponseCode::invalid_length()), buf.len());
};
let Ok(codec_id) = CodecId::decode(&buf[17..22]).0 else {
return (Err(ResponseCode::invalid_length()), buf.len());
};
let codec_specific_configuration_len = buf[22] as usize;
let total_len = 23 + codec_specific_configuration_len;
if buf.len() < total_len {
return (Err(ResponseCode::invalid_length()), buf.len());
}
let codec_config = buf[23..total_len].to_vec();
(
Ok(Self::CodecConfigured {
framing,
preferred_phys,
preferred_retransmission_number,
max_transport_latency,
presentation_delay_range,
codec_id,
codec_config,
}),
total_len,
)
}
AseState::QosConfigured => {
if buf.len() < 15 {
return (Err(ResponseCode::invalid_length()), buf.len());
}
let mut temp_buf = Vec::with_capacity(16);
temp_buf.push(ase_id.into());
temp_buf.extend_from_slice(&buf[0..15]);
let (config_res, _) = QosConfiguration::decode(&temp_buf);
match config_res {
Ok(configuration) => (Ok(Self::QosConfigured { configuration }), 15),
Err(e) => (Err(e), buf.len()),
}
}
AseState::Enabling | AseState::Streaming | AseState::Disabling => {
if buf.len() < 3 {
return (Err(ResponseCode::invalid_length()), buf.len());
}
let cig_id = match CigId::try_from(buf[0]) {
Ok(id) => id,
Err(_) => return (Err(ResponseCode::invalid_length()), buf.len()),
};
let cis_id = match CisId::try_from(buf[1]) {
Ok(id) => id,
Err(_) => return (Err(ResponseCode::invalid_length()), buf.len()),
};
let metadata_length = buf[2] as usize;
let total_len = 3 + metadata_length;
if buf.len() < total_len {
return (Err(ResponseCode::invalid_length()), buf.len());
}
use bt_common::core::ltv::Error as LtvError;
use bt_common::core::ltv::LtValue;
let (metadata_results, consumed) =
Metadata::decode_all(&buf[3..3 + metadata_length]);
if consumed != metadata_length {
return (Err(ResponseCode::invalid_length()), 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 | LtvError::MissingData(_) => {
return (Err(ResponseCode::invalid_length()), buf.len());
}
LtvError::UnrecognizedType(_, type_value) => {
return (
Err(ResponseCode::Metadata {
ase_id,
issue: ResponseIssue::Unsupported,
type_value,
}),
total_len,
);
}
LtvError::LengthOutOfRange(_, t, _)
| LtvError::TypeFailedToDecode(t, _) => {
return (
Err(ResponseCode::Metadata {
ase_id,
issue: ResponseIssue::Invalid,
type_value: t.into(),
}),
total_len,
);
}
}
};
(Ok(Self::Streaming { cig_id, cis_id, metadata, qos_configured: None }), total_len)
}
}
}
}
impl From<QosConfiguration> for AseAdditionalParameters {
fn from(value: QosConfiguration) -> Self {
Self::QosConfigured { configuration: value }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AudioDirection {
Sink,
Source,
}
impl From<&AudioDirection> for bt_common::Uuid {
fn from(value: &AudioDirection) -> Self {
match value {
AudioDirection::Sink => Uuid::from_u16(0x2BC4),
AudioDirection::Source => Uuid::from_u16(0x2BC5),
}
}
}
#[derive(Debug, Clone)]
pub struct AudioStreamEndpoint {
pub handle: Handle,
pub direction: AudioDirection,
pub ase_id: AseId,
pub state: AseState,
pub additional: AseAdditionalParameters,
}
impl AudioStreamEndpoint {
/// Decodes an `AudioStreamEndpoint` from an ASE characteristic value.
///
/// # Arguments
/// * `handle` - The GATT handle of the ASE characteristic.
/// * `direction` - The direction of the ASE (Sink or Source).
/// * `char_value_buf` - The characteristic value bytes read from the
/// server.
///
/// # Expected `char_value_buf` Layout (ASCS v1.0 Section 4.3):
/// * **Octet 0:** `ASE_ID` (1 octet)
/// * **Octet 1:** `ASE_State` (1 octet)
/// * **Octet 2+:** `Additional_ASE_Parameters` (variable octets, see ASCS
/// v1.0 Table 4.2)
pub fn from_char_value(
handle: Handle,
direction: AudioDirection,
char_value_buf: &[u8],
) -> Result<Self, ResponseCode> {
if char_value_buf.len() < 2 {
return Err(ResponseCode::invalid_length());
}
let ase_id = AseId::try_from(char_value_buf[0])?;
let Ok(state) = AseState::try_from(char_value_buf[1]) else {
return Err(ResponseCode::invalid_length());
};
let (additional_res, _) =
AseAdditionalParameters::decode(ase_id, &state, &char_value_buf[2..]);
let additional = additional_res?;
Ok(Self { handle, direction, ase_id, state, additional })
}
/// Encodes this endpoint's current state into a GATT characteristic value
/// buffer.
pub fn into_char_value(&self) -> Vec<u8> {
let mut buf = vec![0; self.encoded_len()];
self.encode(&mut buf).unwrap();
buf
}
pub fn get_cis(&self) -> Option<(CigId, CisId)> {
match &self.additional {
AseAdditionalParameters::QosConfigured { configuration } => {
Some((configuration.cig_id, configuration.cis_id))
}
AseAdditionalParameters::Streaming { cig_id, cis_id, .. } => Some((*cig_id, *cis_id)),
_ => None,
}
}
}
impl Encodable for AudioStreamEndpoint {
type Error = bt_common::packet_encoding::Error;
fn encoded_len(&self) -> usize {
2 + self.additional.char_size()
}
fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> {
if buf.len() < self.encoded_len() {
return Err(Self::Error::BufferTooSmall);
}
buf[0] = self.ase_id.into();
buf[1] = self.state.into();
let add_bytes = self.additional.into_char_value();
buf[2..2 + add_bytes.len()].copy_from_slice(&add_bytes);
Ok(())
}
}
decodable_enum! {
pub enum AseControlPointOpcode<u8, bt_common::packet_encoding::Error, OutOfRange> {
ConfigCodec = 0x01,
ConfigQos = 0x02,
Enable = 0x03,
ReceiverStartReady = 0x04,
Disable = 0x05,
ReceiverStopReady = 0x06,
UpdateMetadata = 0x07,
Release = 0x08,
}
}
impl AseControlPointOpcode {
pub(crate) fn allowed_in_state(&self, state: &AseState) -> bool {
let allowed_states: &[AseState] = match self {
Self::ConfigCodec { .. } => {
&[AseState::Idle, AseState::CodecConfigured, AseState::QosConfigured]
}
Self::ConfigQos { .. } => &[AseState::CodecConfigured, AseState::QosConfigured],
Self::Enable { .. } => &[AseState::QosConfigured],
Self::ReceiverStartReady { .. } => &[AseState::Enabling],
Self::ReceiverStopReady { .. } => &[AseState::Disabling],
Self::Disable { .. } => &[AseState::Streaming, AseState::Enabling],
Self::UpdateMetadata { .. } => &[AseState::Enabling, AseState::Streaming],
Self::Release { .. } => &[
AseState::Enabling,
AseState::Streaming,
AseState::CodecConfigured,
AseState::QosConfigured,
AseState::Disabling,
],
};
allowed_states.contains(state)
}
}
/// ASE Control Operations. These can be initiated by a server or client.
/// Defined in Table 4.6 of ASCS v1.0
/// Unrecognized Operations result in Error Operations, which translate directly
/// into an Error response.
/// 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.
#[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 { ase_id: AseId },
}
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.into_iter().find(|x| x.is_invalid_length()).is_some()
}
_ => 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::Server(ServerError::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<&AseControlOperation> for AseControlPointOpcode {
type Error = bt_common::packet_encoding::Error;
fn try_from(value: &AseControlOperation) -> Result<Self, Self::Error> {
u8::try_from(value).map_err(|_| Self::Error::OutOfRange)?.try_into()
}
}
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::invalid_length());
}
let operation: AseControlPointOpcode = match value[0].try_into() {
Ok(opcode) => opcode,
Err(_e) => {
return Err(ResponseCode::unsupported_opcode(value[0]));
}
};
let number_of_ases = value[1] as usize;
if number_of_ases < 1 {
return Err(ResponseCode::invalid_length_opcode(value[0]));
}
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 Ok(ases) = results.into_iter().collect::<Result<Vec<_>, ResponseCode>>() else {
return Err(ResponseCode::invalid_length_opcode(value[0]));
};
(Self::ReceiverStartReady { ases }, consumed)
}
AseControlPointOpcode::Disable => {
// Only InvalidLength is possible
let (results, consumed) = AseId::decode_multiple(&value[2..], Some(number_of_ases));
let Ok(ases) = results.into_iter().collect::<Result<Vec<_>, ResponseCode>>() else {
return Err(ResponseCode::invalid_length_opcode(value[0]));
};
(Self::Disable { ases }, consumed)
}
AseControlPointOpcode::ReceiverStopReady => {
// Only InvalidLength is possible
let (results, consumed) = AseId::decode_multiple(&value[2..], Some(number_of_ases));
let Ok(ases) = results.into_iter().collect::<Result<Vec<_>, ResponseCode>>() else {
return Err(ResponseCode::invalid_length_opcode(value[0]));
};
(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 Ok(ases) = results.into_iter().collect::<Result<Vec<_>, ResponseCode>>() else {
return Err(ResponseCode::invalid_length_opcode(value[0]));
};
(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::invalid_length_opcode(value[0]));
}
if op.contains_invalid_length() {
return Err(ResponseCode::invalid_length_opcode(value[0]));
}
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,
}
}
impl From<TargetPhy> for Phy {
fn from(value: TargetPhy) -> Self {
match value {
TargetPhy::Le1MPhy => Self::Le1MPhy,
TargetPhy::Le2MPhy => Self::Le2MPhy,
TargetPhy::LeCodedPhy => Self::LeCodedPhy,
}
}
}
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::invalid_length()), 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::invalid_length()), 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::invalid_length()), 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::invalid_length());
}
(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::invalid_length()
}
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
}
}
impl Encodable for CigId {
type Error = bt_common::packet_encoding::Error;
fn encoded_len(&self) -> core::primitive::usize {
Self::BYTE_SIZE
}
fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
if buf.len() < Self::BYTE_SIZE {
return Err(Self::Error::BufferTooSmall);
}
buf[0] = self.0;
Ok(())
}
}
#[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
}
}
impl Encodable for CisId {
type Error = bt_common::packet_encoding::Error;
fn encoded_len(&self) -> core::primitive::usize {
Self::BYTE_SIZE
}
fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
if buf.len() < Self::BYTE_SIZE {
return Err(Self::Error::BufferTooSmall);
}
buf[0] = self.0;
Ok(())
}
}
/// 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)
}
}
impl Encodable for SduInterval {
type Error = bt_common::packet_encoding::Error;
fn encoded_len(&self) -> core::primitive::usize {
Self::BYTE_SIZE
}
fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
if buf.len() < Self::BYTE_SIZE {
return Err(Self::Error::BufferTooSmall);
}
[buf[0], buf[1], buf[2], _] = self.0.to_le_bytes();
Ok(())
}
}
decodable_enum! {
pub enum Framing<u8, bt_common::packet_encoding::Error, OutOfRange> {
Unframed = 0x00,
Framed = 0x01,
}
}
impl Framing {
const BYTE_SIZE: usize = 1;
}
impl Encodable for Framing {
type Error = bt_common::packet_encoding::Error;
fn encoded_len(&self) -> core::primitive::usize {
Self::BYTE_SIZE
}
fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
if buf.len() < Self::BYTE_SIZE {
return Err(Self::Error::BufferTooSmall);
}
buf[0] = (*self).into();
Ok(())
}
}
/// 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 Encodable for MaxSdu {
type Error = bt_common::packet_encoding::Error;
fn encoded_len(&self) -> core::primitive::usize {
Self::BYTE_SIZE
}
fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
if buf.len() < Self::BYTE_SIZE {
return Err(Self::Error::BufferTooSmall);
}
[buf[0], buf[1]] = self.0.to_le_bytes();
Ok(())
}
}
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, Copy, 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 Encodable for MaxTransportLatency {
type Error = bt_common::packet_encoding::Error;
fn encoded_len(&self) -> core::primitive::usize {
Self::BYTE_SIZE
}
fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
if buf.len() < 2 {
return Err(Self::Error::BufferTooSmall);
}
[buf[0], buf[1]] = self.0.to_le_bytes();
Ok(())
}
}
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, Eq, PartialOrd, Ord)]
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::invalid_length()), buf.len());
}
let microseconds = u32::from_le_bytes([buf[0], buf[1], buf[2], 0]);
(Ok(PresentationDelay { microseconds }), Self::BYTE_SIZE)
}
}
impl Encodable for PresentationDelay {
type Error = bt_common::packet_encoding::Error;
fn encoded_len(&self) -> core::primitive::usize {
Self::BYTE_SIZE
}
fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
if buf.len() < Self::BYTE_SIZE {
return Err(Self::Error::BufferTooSmall);
}
let encoded_le = self.microseconds.to_le_bytes();
[buf[0], buf[1], buf[2]] = [encoded_le[0], encoded_le[1], encoded_le[2]];
Ok(())
}
}
/// Presentation Delay Range. Used to indicate the supported range and
/// preferred range of the Presentation Delay parameter to be requested by the
/// ASCS Client. Prefered Minimum must be above min, and preferred_max must be
/// below max. Either of these being None indicates no preference.
#[derive(Debug, Clone, PartialEq)]
pub struct PresentationDelayRange {
min: PresentationDelay,
max: PresentationDelay,
preferred_min: Option<PresentationDelay>,
preferred_max: Option<PresentationDelay>,
}
impl PresentationDelayRange {
const BYTE_SIZE: usize = PresentationDelay::BYTE_SIZE * 4;
pub fn min(&self) -> &PresentationDelay {
&self.min
}
pub fn max(&self) -> &PresentationDelay {
&self.max
}
pub fn preferred_min(&self) -> Option<&PresentationDelay> {
self.preferred_min.as_ref()
}
pub fn preferred_max(&self) -> Option<&PresentationDelay> {
self.preferred_max.as_ref()
}
/// Make a new delay range with no preference. Returns
/// ResponseCode::InvalidLength if min > max or the value is out of the
/// acceptable range (PresentationDelay is 24 bits)
pub fn build(min_us: u32, max_us: u32) -> Result<Self, ResponseCode> {
if min_us > max_us || min_us > 0x00FFFFFF || max_us > 0x00FFFFFF {
return Err(ResponseCode::invalid_length());
}
Ok(Self {
min: PresentationDelay { microseconds: min_us },
max: PresentationDelay { microseconds: max_us },
preferred_min: None,
preferred_max: None,
})
}
/// Set the preferred range. If the range does not fit into the supported
/// range, returns Err(ResponseCode::InvalidLength).
pub fn with_preferred(
&mut self,
min_us: Option<u32>,
max_us: Option<u32>,
) -> Result<&mut Self, ResponseCode> {
self.preferred_min = match min_us {
Some(min_us)
if min_us < self.min.microseconds || max_us.is_some_and(|max| max < min_us) =>
{
return Err(ResponseCode::invalid_length());
}
Some(min_us) => Some(PresentationDelay { microseconds: min_us }),
None => None,
};
self.preferred_max = match max_us {
Some(max_us)
if max_us > self.max.microseconds || min_us.is_some_and(|min| min > max_us) =>
{
return Err(ResponseCode::invalid_length());
}
Some(max_us) => Some(PresentationDelay { microseconds: max_us }),
None => None,
};
Ok(self)
}
}
impl Encodable for PresentationDelayRange {
type Error = bt_common::packet_encoding::Error;
fn encoded_len(&self) -> core::primitive::usize {
Self::BYTE_SIZE
}
fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
if buf.len() < Self::BYTE_SIZE {
return Err(Self::Error::BufferTooSmall);
}
buf[0..Self::BYTE_SIZE].fill(0);
self.min.encode(&mut buf[0..3])?;
self.max.encode(&mut buf[3..6])?;
if let Some(pref_min) = &self.preferred_min {
pref_min.encode(&mut buf[6..9])?;
}
if let Some(pref_max) = &self.preferred_max {
pref_max.encode(&mut buf[9..12])?;
}
Ok(())
}
}
#[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::invalid_length()), 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::invalid_length()), 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::invalid_length()), 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::invalid_length()), buf.len()),
LtvError::MissingData(_) => {
return (Err(ResponseCode::invalid_length()), 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)
}
}
impl Decodable for PresentationDelayRange {
type Error = ResponseCode;
fn decode(buf: &[u8]) -> (Result<Self, Self::Error>, usize) {
if buf.len() < Self::BYTE_SIZE {
return (Err(ResponseCode::invalid_length()), buf.len());
}
let min = match PresentationDelay::decode(&buf[0..3]).0 {
Ok(min) => min,
Err(e) => return (Err(e), buf.len()),
};
let max = match PresentationDelay::decode(&buf[3..6]).0 {
Ok(max) => max,
Err(e) => return (Err(e), buf.len()),
};
let pref_min_val = u32::from_le_bytes([buf[6], buf[7], buf[8], 0]);
let preferred_min = if pref_min_val == 0 {
None
} else {
Some(PresentationDelay { microseconds: pref_min_val })
};
let pref_max_val = u32::from_le_bytes([buf[9], buf[10], buf[11], 0]);
let preferred_max = if pref_max_val == 0 {
None
} else {
Some(PresentationDelay { microseconds: pref_max_val })
};
(Ok(Self { min, max, preferred_min, preferred_max }), Self::BYTE_SIZE)
}
}
/// Represents an ASE Control Point notification sent by an ASCS Server to a
/// Client.
///
/// See ASCS v1.0 Section 4.2 for the frame structure and notification
/// properties.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ControlPointNotification {
pub opcode: u8,
pub number_of_ases: u8,
pub responses: Vec<ResponseCode>,
}
impl ControlPointNotification {
/// Creates a new, empty `ControlPointNotification` for standard multi-ASE
/// operations.
///
/// The notification starts with a response count of `0`.
///
/// # Arguments
/// * `opcode` - The opcode of the operation being responded to.
pub fn new(opcode: u8) -> Self {
Self { opcode, number_of_ases: 0, responses: Vec::new() }
}
/// Creates a new global error `ControlPointNotification`.
///
/// A response count of `0xFF` is used strictly when the entire control
/// point write is rejected globally before individual ASEs are
/// processed (e.g., unrecognized opcode or invalid packet length). The
/// notification contains a single error response with an
/// ASE ID of `0x00`. See ASCS v1.0 Table 4.7.
///
/// # Arguments
/// * `opcode` - The opcode of the operation being responded to.
/// * `response_code` - Either `0x01` (Unsupported Opcode) or `0x02`
/// (Invalid Length).
pub fn new_error(opcode: u8, response_code: u8) -> Self {
let code = match response_code {
0x01 => ResponseCode::UnsupportedOpcode { opcode_byte: opcode },
0x02 => ResponseCode::InvalidLength { opcode_byte: opcode },
_ => unreachable!(),
};
Self { opcode, number_of_ases: 0xFF, responses: vec![code] }
}
/// Adds a single ASE response code to the notification list.
///
/// If the notification is currently in a global error state (response count
/// `0xFF`), calling this method clears the error and transitions it
/// back to a standard multi-ASE response list.
///
/// # Arguments
/// * `response` - The `ResponseCode` variant to append to the notification.
pub fn add_response(&mut self, response: ResponseCode) {
if self.number_of_ases == 0xFF {
self.number_of_ases = 0;
self.responses.clear();
}
self.responses.push(response);
self.number_of_ases = self.responses.len() as u8;
}
}
impl Encodable for ControlPointNotification {
type Error = bt_common::packet_encoding::Error;
fn encoded_len(&self) -> usize {
2 + self.responses.len() * 3
}
fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> {
if buf.len() < self.encoded_len() {
return Err(Self::Error::BufferTooSmall);
}
buf[0] = self.opcode;
buf[1] = self.number_of_ases;
let mut idx = 2;
for r in &self.responses {
let val = r.notify_value();
buf[idx..idx + 3].copy_from_slice(&val);
idx += 3;
}
Ok(())
}
}
impl Decodable for ControlPointNotification {
type Error = bt_common::packet_encoding::Error;
fn decode(buf: &[u8]) -> (Result<Self, Self::Error>, usize) {
if buf.len() < 2 {
return (Err(Self::Error::UnexpectedDataLength), buf.len());
}
let opcode = buf[0];
let number_of_ases = buf[1];
let responses_buf = &buf[2..];
let mut responses = Vec::new();
if number_of_ases == 0xFF {
if responses_buf.len() < 3 {
return (Err(Self::Error::UnexpectedDataLength), buf.len());
}
let r = match ResponseCode::decode_response(&responses_buf[0..3], opcode) {
Ok(r) => r,
Err(e) => return (Err(e), buf.len()),
};
if r.ase_id_value() != 0 || (r.to_code() != 0x01 && r.to_code() != 0x02) {
return (Err(Self::Error::UnexpectedDataLength), buf.len());
}
responses.push(r);
(Ok(Self { opcode, number_of_ases, responses }), 5)
} else {
let expected_len = number_of_ases as usize * 3;
if responses_buf.len() < expected_len {
return (Err(Self::Error::UnexpectedDataLength), buf.len());
}
for i in 0..(number_of_ases as usize) {
let idx = i * 3;
let r = match ResponseCode::decode_response(&responses_buf[idx..idx + 3], opcode) {
Ok(r) => r,
Err(e) => return (Err(e), buf.len()),
};
responses.push(r);
}
(Ok(Self { opcode, number_of_ases, responses }), 2 + expected_len)
}
}
}
#[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);
#[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:?}");
};
}
#[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);
assert_eq!(
operation,
Ok(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);
match operation {
Ok(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::invalid_length_opcode(0x01)));
}
#[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);
assert!(operation.is_ok());
#[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:?}"),
};
}
#[test]
fn presentation_delay_range() {
// Min must be < than max, and within the right range
assert!(PresentationDelayRange::build(10, 1).is_err());
assert!(PresentationDelayRange::build(1000, 0xC0FFEE00).is_err());
assert!(PresentationDelayRange::build(0xC0FFEE00, 0xC0FFEE01).is_err());
let mut delay_range = PresentationDelayRange::build(1000, 2000).unwrap();
assert_eq!(delay_range.min.microseconds, 1000);
assert_eq!(delay_range.max.microseconds, 2000);
assert!(delay_range.with_preferred(None, None).is_ok());
assert_eq!(delay_range.preferred_min, None);
assert!(delay_range.with_preferred(Some(900), None).is_err());
assert_eq!(delay_range.preferred_min, None);
assert!(delay_range.with_preferred(Some(1500), Some(1001)).is_err());
assert_eq!(delay_range.preferred_min, None);
assert!(delay_range.with_preferred(Some(1500), None).is_ok());
assert_eq!(delay_range.preferred_min, Some(PresentationDelay { microseconds: 1500 }));
assert_eq!(delay_range.preferred_max, None);
assert!(delay_range.with_preferred(None, Some(2100)).is_err());
assert!(delay_range.with_preferred(Some(1801), Some(1800)).is_err());
assert!(delay_range.with_preferred(Some(1500), Some(1800)).is_ok());
assert_eq!(delay_range.preferred_min, Some(PresentationDelay { microseconds: 1500 }));
assert_eq!(delay_range.preferred_max, Some(PresentationDelay { microseconds: 1800 }));
let mut encoded = [0; PresentationDelayRange::BYTE_SIZE];
assert!(delay_range.encode(&mut encoded).is_ok());
#[rustfmt::skip]
let expected: &[u8; 12] = &[
0xE8, 0x03, 0x00, // 1000
0xD0, 0x07, 0x00, // 2000
0xDC, 0x05, 0x00, // 1500
0x08, 0x07, 0x00, // 1800
];
assert_eq!(&encoded, expected);
}
#[test]
fn test_ase_endpoint_roundtrip() {
use bt_gatt::types::Handle;
// Test with AdditionalParameters::None
let endpoint_none = AudioStreamEndpoint {
handle: Handle(1),
direction: AudioDirection::Sink,
ase_id: AseId(1),
state: AseState::Idle,
additional: AseAdditionalParameters::None,
};
let mut buf = vec![0; endpoint_none.encoded_len()];
endpoint_none.encode(&mut buf).unwrap();
assert_eq!(buf.len(), 2);
assert_eq!(buf, vec![1, 0]);
let decoded_endpoint_none =
AudioStreamEndpoint::from_char_value(Handle(1), AudioDirection::Sink, &buf).unwrap();
assert_eq!(decoded_endpoint_none.ase_id, endpoint_none.ase_id);
assert_eq!(decoded_endpoint_none.state, endpoint_none.state);
assert_eq!(decoded_endpoint_none.additional, endpoint_none.additional);
// Test with AdditionalParameters::CodecConfigured
let delay_range = PresentationDelayRange::build(1000, 2000).unwrap();
let endpoint_codec = AudioStreamEndpoint {
handle: Handle(2),
direction: AudioDirection::Source,
ase_id: AseId(2),
state: AseState::CodecConfigured,
additional: AseAdditionalParameters::CodecConfigured {
framing: Framing::Unframed,
preferred_phys: vec![Phy::Le1MPhy].into_iter().collect(),
preferred_retransmission_number: 3,
max_transport_latency: MaxTransportLatency::try_from(
std::time::Duration::from_millis(10),
)
.unwrap(),
presentation_delay_range: delay_range,
codec_id: CodecId::Assigned(bt_common::core::CodingFormat::Lc3),
codec_config: vec![1, 2, 3],
},
};
let mut buf2 = vec![0; endpoint_codec.encoded_len()];
endpoint_codec.encode(&mut buf2).unwrap();
let decoded_endpoint_codec =
AudioStreamEndpoint::from_char_value(Handle(2), AudioDirection::Source, &buf2).unwrap();
assert_eq!(decoded_endpoint_codec.ase_id, endpoint_codec.ase_id);
assert_eq!(decoded_endpoint_codec.state, endpoint_codec.state);
assert_eq!(decoded_endpoint_codec.additional, endpoint_codec.additional);
}
#[test]
fn control_point_notification_roundtrip() {
// 1. Test Standard Multi-ASE notification roundtrip
let mut notification = ControlPointNotification::new(0x01); // Opcode ConfigCodec
notification.add_response(ResponseCode::Success { ase_id: AseId(1) });
notification.add_response(ResponseCode::InvalidAseId { value: 2 });
let mut buf = vec![0; notification.encoded_len()];
assert!(notification.encode(&mut buf).is_ok());
#[rustfmt::skip]
let expected_bytes = &[
0x01, // Opcode
0x02, // Number of ASEs
0x01, 0x00, 0x00, // Response 1: ASE 1, Success, None
0x02, 0x03, 0x00, // Response 2: ASE 2, Invalid ASE ID, None
];
assert_eq!(buf, expected_bytes);
let (decoded, consumed) = ControlPointNotification::decode(&buf);
assert_eq!(consumed, buf.len());
assert_eq!(decoded.expect("should succeed"), notification);
// 2. Test Global Error Notification (0xFF) roundtrip
let err_notification = ControlPointNotification::new_error(0x01, 0x02); // Invalid Length error
let mut err_buf = vec![0; err_notification.encoded_len()];
assert!(err_notification.encode(&mut err_buf).is_ok());
#[rustfmt::skip]
let expected_err_bytes = &[
0x01, // Opcode
0xFF, // Number of ASEs: Global Error
0x00, 0x02, 0x00, // ASE 0, Invalid Length, None
];
assert_eq!(err_buf, expected_err_bytes);
let (decoded_err, consumed_err) = ControlPointNotification::decode(&err_buf);
assert_eq!(consumed_err, err_buf.len());
assert_eq!(decoded_err.expect("should succeed"), err_notification);
// 3. Test Validation constraint fails on malformed 0xFF notification
// Case A: number_of_ases = 0xFF, but ase_id is not 0x00 (e.g., AseId 1)
#[rustfmt::skip]
let malformed_bytes_ase = &[
0x01, // Opcode
0xFF, // Global Error
0x01, 0x02, 0x00, // ASE 1, Invalid Length, None (ase_id must be 0x00)
];
let (res_malformed_ase, _) = ControlPointNotification::decode(malformed_bytes_ase);
assert_eq!(res_malformed_ase, Err(bt_common::packet_encoding::Error::OutOfRange));
// Case B: number_of_ases = 0xFF, but response_code is not 0x01 or 0x02 (e.g.
// Success 0x00)
#[rustfmt::skip]
let malformed_bytes_code = &[
0x01, // Opcode
0xFF, // Global Error
0x00, 0x00, 0x00, // ASE 0, Success, None (response code must be 0x01 or 0x02)
];
let (res_malformed_code, _) = ControlPointNotification::decode(malformed_bytes_code);
assert_eq!(
res_malformed_code,
Err(bt_common::packet_encoding::Error::UnexpectedDataLength)
);
}
}