blob: c6c4ce2bcb356622e36c7d1a68cce84894bbe2ac [file] [edit]
// Copyright 2026 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.
//! Contains structs and traits that are used to connect and get information for
//! Connected Isochronous Groups and Streams.
use bt_common::core::CodecId;
use bt_common::decodable_enum;
use bt_common::packet_encoding::{Decodable, Encodable};
use bt_common::PeerId;
use futures::future::{ready, Ready};
use std::future::Future;
decodable_enum! {
pub enum Framing<u8, bt_common::packet_encoding::Error, OutOfRange> {
Unframed = 0x00,
Framed = 0x01,
}
}
impl Framing {
pub 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(())
}
}
decodable_enum! {
pub enum Packing<u8, bt_common::packet_encoding::Error, OutOfRange> {
Sequential = 0x00,
Interleaved = 0x01,
}
}
impl Packing {
const BYTE_SIZE: usize = 1;
}
impl Encodable for Packing {
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(())
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct CisId(u8);
impl CisId {
pub 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(())
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct CigId(u8);
impl CigId {
pub 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(())
}
}
/// SDU Interval parameter (in milliseconds)
/// 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 {
pub const BYTE_SIZE: usize = 3;
}
impl TryFrom<std::time::Duration> for SduInterval {
type Error = bt_common::packet_encoding::Error;
fn try_from(value: std::time::Duration) -> Result<Self, Self::Error> {
let Ok(microseconds) = u32::try_from(value.as_micros()) else {
return Err(Self::Error::OutOfRange);
};
if microseconds < 0xFF || microseconds > 0x0FFFFF {
return Err(Self::Error::OutOfRange);
}
Ok(Self(microseconds))
}
}
impl TryFrom<u32> for SduInterval {
type Error = bt_common::packet_encoding::Error;
fn try_from(value: u32) -> Result<Self, Self::Error> {
if value < 0xFF || value > 0x0FFFFF {
return Err(Self::Error::OutOfRange);
}
Ok(Self(value))
}
}
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(())
}
}
/// Max Transport Latency (in milliseconds)
/// 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);
};
Self::try_from(milliseconds)
}
}
impl TryFrom<u16> for MaxTransportLatency {
type Error = bt_common::packet_encoding::Error;
fn try_from(value: u16) -> Result<Self, Self::Error> {
if !(0x0005..=0x0FA0).contains(&value) {
return Err(Self::Error::OutOfRange);
}
Ok(Self(value))
}
}
impl MaxTransportLatency {
pub const BYTE_SIZE: usize = 2;
}
pub struct CigParameters {
pub sdu_interval_central_to_peripheral: SduInterval,
pub sdu_interval_peripheral_to_central: SduInterval,
pub packing: Packing,
pub framing: Framing,
pub max_transport_latency_central_to_peripheral: MaxTransportLatency,
pub max_transport_latency_peripheral_to_central: MaxTransportLatency,
/// The expected or intended peers to join this CIG, if known. This may be
/// used to optimize parameters, but may limit the set of peers that can
/// connect to the group.
pub expected_peers: Vec<PeerId>,
}
/// Data Direction
/// This is always represented in relation to the controller, so:
/// Input is data that goes from the Host to the Controller (to the peer)
/// Output is data that goes from the Controller to the Host (from the peer)
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum DataDirection {
Input,
Output,
}
/// Data Path Id
/// Non-HCI Data Paths are vendor defined
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum DataPathId {
Hci,
Vendor(core::num::NonZeroU8),
}
impl DataPathId {
pub fn hci() -> Self {
Self::Hci
}
pub fn vendor(path_id: core::num::NonZeroU8) -> Self {
Self::Vendor(path_id)
}
}
/// Controller Delay
/// Stored in microseconds, only valid up to 4 seconds.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct ControllerDelay {
microseconds: u32,
}
impl TryFrom<std::time::Duration> for ControllerDelay {
type Error = bt_common::packet_encoding::Error;
fn try_from(duration: std::time::Duration) -> Result<Self, Self::Error> {
if duration > Self::MAX_ALLOWED {
return Err(bt_common::packet_encoding::Error::OutOfRange);
}
Ok(Self { microseconds: duration.as_micros().try_into().unwrap() })
}
}
impl From<ControllerDelay> for std::time::Duration {
fn from(delay: ControllerDelay) -> Self {
Self::from_micros(delay.microseconds.into())
}
}
impl ControllerDelay {
// Maximum allowed delay, from Core Spec Vol 4, Part E 7.8.109
const MAX_ALLOWED: std::time::Duration = std::time::Duration::from_secs(4);
}
#[derive(Debug, Clone, PartialEq)]
pub struct CisRequestedParameters {
pub cis_id: CisId,
pub max_sdu_size_outgoing: u16,
pub max_sdu_size_incoming: u16,
}
#[derive(Debug)]
pub enum ConnectedIsochronousError {
NotSupported,
}
pub trait ConnectedIsochronous: Sized {
type Group: ConnectedIsochronousGroup;
type InitializeCigFut: Future<
Output = std::result::Result<Self::Group, ConnectedIsochronousError>,
>;
/// Initialize an isochronous group with the specified parameters.
/// Dropping a ConnectedIsochronous has no side-effects, notably,
/// initialized groups are not disconnected.
fn initialize(
&self,
group_parameters: CigParameters,
streams_requested: &[CisRequestedParameters],
) -> Self::InitializeCigFut;
}
pub trait ConnectedIsochronousGroup: Sized {
type ConnectedIsochronousStream: ConnectedIsochronousStream;
type CreateCisFut: Future<
Output = std::result::Result<
Vec<Self::ConnectedIsochronousStream>,
ConnectedIsochronousError,
>,
>;
type RemoveGroupFut: Future<Output = ()>;
/// Get the CIG ID that has been assigned to this group. Returns None if the
/// Group has not been created yet.
fn cig_id(&self) -> CigId;
/// Create CISes using existing connections to the listed peers.
/// Returns a set of streams which are established and ready for data.
fn create_streams(&self, peers: &[(CisId, PeerId)]) -> Self::CreateCisFut;
/// Remove this group. All connected streams will be closed and any
/// remaining calls to `create` will fail. This future will complete
/// when the group has been removed and all streams are disconnected.
fn remove(&self) -> Self::RemoveGroupFut;
}
/// Represents a connected isochronous stream.
/// Streams may be readable and writable using platform-specific methods.
pub trait ConnectedIsochronousStream: Sized {
type SetupDataPathFut: Future<Output = std::result::Result<(), ConnectedIsochronousError>>;
/// Get the IDs associated with this stream.
fn id(&self) -> (CigId, CisId);
/// Setup a data path for this stream. This command may be called multiple
/// times for each DataDirection. If it is called multiple times with
/// the same direction, the previous data path will be removed before
/// the new data path is set up.
fn setup_data_path(
&self,
direction: DataDirection,
data_path: DataPathId,
codec: CodecId,
codec_configuration: &[u8],
controller_delay: ControllerDelay,
) -> Self::SetupDataPathFut;
}
pub struct NotSupported;
impl ConnectedIsochronous for NotSupported {
type Group = NotSupported;
type InitializeCigFut = Ready<std::result::Result<Self::Group, ConnectedIsochronousError>>;
fn initialize(
&self,
_group_parameters: CigParameters,
_streams_requested: &[CisRequestedParameters],
) -> Self::InitializeCigFut {
ready(Err(ConnectedIsochronousError::NotSupported))
}
}
impl ConnectedIsochronousGroup for NotSupported {
type ConnectedIsochronousStream = NotSupported;
type CreateCisFut = Ready<
std::result::Result<Vec<Self::ConnectedIsochronousStream>, ConnectedIsochronousError>,
>;
type RemoveGroupFut = Ready<()>;
fn create_streams(&self, _peers: &[(CisId, PeerId)]) -> Self::CreateCisFut {
ready(Err(ConnectedIsochronousError::NotSupported))
}
fn remove(&self) -> Self::RemoveGroupFut {
ready(())
}
fn cig_id(&self) -> CigId {
unreachable!();
}
}
impl ConnectedIsochronousStream for NotSupported {
type SetupDataPathFut = Ready<std::result::Result<(), ConnectedIsochronousError>>;
fn id(&self) -> (CigId, CisId) {
unreachable!();
}
fn setup_data_path(
&self,
_direction: DataDirection,
_data_path: DataPathId,
_codec: CodecId,
_codec_configuration: &[u8],
_controller_delay: ControllerDelay,
) -> Self::SetupDataPathFut {
ready(Err(ConnectedIsochronousError::NotSupported))
}
}