rust/bt-ascs: Initial implementaiton
Audio Stream Control Service crate, including initial decoding of
control point operaitons and initially empty Events from the service.
Test: added unit tests, cargo test
Change-Id: I1945b5e306783a6b514d6da8ba5dda66fcf7b8f0
Reviewed-on: https://bluetooth-review.googlesource.com/c/bluetooth/+/1722
Commit-Queue: Marie Janssen <jamuraa@google.com>
Reviewed-by: Dayeong Lee <dayeonglee@google.com>
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index 650f02c..ddc7870 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -24,6 +24,7 @@
assert_matches = "1.5.0"
bitfield = "0.14.0"
futures = "=0.3.31"
+futures-test = "=0.3.31"
lazy_static = "1.5"
log = { version = "0.4.27", features = [ "kv", "std" ] }
num = { version = "0.4.3", features = ["rand"] }
diff --git a/rust/bt-ascs/Cargo.toml b/rust/bt-ascs/Cargo.toml
new file mode 100644
index 0000000..723aa6f
--- /dev/null
+++ b/rust/bt-ascs/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "bt-ascs"
+version = "0.0.1"
+license.workspace = true
+edition.workspace = true
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+bt-common.workspace = true
+bt-gatt.workspace = true
+thiserror.workspace = true
+futures.workspace = true
+pin-project.workspace = true
+log.workspace = true
+
+[dev-dependencies]
+futures-test.workspace = true
+bt-gatt = { workspace = true, features = ["test-utils"] }
diff --git a/rust/bt-ascs/LICENSE b/rust/bt-ascs/LICENSE
new file mode 120000
index 0000000..30cff74
--- /dev/null
+++ b/rust/bt-ascs/LICENSE
@@ -0,0 +1 @@
+../../LICENSE
\ No newline at end of file
diff --git a/rust/bt-ascs/src/lib.rs b/rust/bt-ascs/src/lib.rs
new file mode 100644
index 0000000..f3f7380
--- /dev/null
+++ b/rust/bt-ascs/src/lib.rs
@@ -0,0 +1,10 @@
+// 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.
+
+pub mod server;
+pub mod types;
+pub use types::Error;
+
+#[cfg(test)]
+pub mod tests;
diff --git a/rust/bt-ascs/src/server.rs b/rust/bt-ascs/src/server.rs
new file mode 100644
index 0000000..12b8274
--- /dev/null
+++ b/rust/bt-ascs/src/server.rs
@@ -0,0 +1,378 @@
+// 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_gatt::Characteristic;
+use bt_gatt::server::{ReadResponder, WriteResponder};
+use bt_gatt::types::{
+ AttributePermissions, CharacteristicProperty, GattError, Handle, SecurityLevels,
+};
+use futures::task::{Poll, Waker};
+use futures::{Future, Stream, stream::FusedStream};
+use pin_project::pin_project;
+use std::collections::HashMap;
+
+use bt_common::{PeerId, Uuid};
+use bt_gatt::server::{LocalService, Server, ServiceDefinition, ServiceId};
+
+use crate::types::*;
+
+#[pin_project(project = LocalServiceProj)]
+enum LocalServiceState<T: bt_gatt::ServerTypes> {
+ NotPublished {
+ waker: Option<Waker>,
+ },
+ Preparing {
+ #[pin]
+ fut: T::LocalServiceFut,
+ },
+ Published {
+ service: T::LocalService,
+ #[pin]
+ events: T::ServiceEventStream,
+ },
+ Terminated,
+}
+
+impl<T: bt_gatt::ServerTypes> Default for LocalServiceState<T> {
+ fn default() -> Self {
+ Self::NotPublished { waker: None }
+ }
+}
+
+impl<T: bt_gatt::ServerTypes> LocalServiceState<T> {
+ fn service(&self) -> Option<&T::LocalService> {
+ let Self::Published { service, .. } = self else {
+ return None;
+ };
+ Some(service)
+ }
+}
+
+impl<T: bt_gatt::ServerTypes> Stream for LocalServiceState<T> {
+ type Item = Result<bt_gatt::server::ServiceEvent<T>, Error>;
+
+ fn poll_next(
+ mut self: std::pin::Pin<&mut Self>,
+ cx: &mut std::task::Context<'_>,
+ ) -> Poll<Option<Self::Item>> {
+ // SAFETY:
+ // - Wakers are Unpin
+ // - We re-pin the structurally pinned futures in Preparing and Published
+ // (service is untouched)
+ // - Terminated is empty
+ loop {
+ match self.as_mut().project() {
+ LocalServiceProj::Terminated => return Poll::Ready(None),
+ LocalServiceProj::NotPublished { .. } => {
+ self.as_mut()
+ .set(LocalServiceState::NotPublished { waker: Some(cx.waker().clone()) });
+ return Poll::Pending;
+ }
+ LocalServiceProj::Preparing { fut } => {
+ let service_result = futures::ready!(fut.poll(cx));
+ let Ok(service) = service_result else {
+ self.as_mut().set(LocalServiceState::Terminated);
+ return Poll::Ready(Some(Err(Error::PublishError(
+ service_result.err().unwrap(),
+ ))));
+ };
+ let events = service.publish();
+ self.as_mut().set(LocalServiceState::Published { service, events });
+ continue;
+ }
+ LocalServiceProj::Published { service: _, events } => {
+ let item = futures::ready!(events.poll_next(cx));
+ let Some(gatt_result) = item else {
+ self.as_mut().set(LocalServiceState::Terminated);
+ return Poll::Ready(Some(Err(Error::PublishError(
+ "GATT server terminated".into(),
+ ))));
+ };
+ let Ok(event) = gatt_result else {
+ self.as_mut().set(LocalServiceState::Terminated);
+ return Poll::Ready(Some(Err(Error::PublishError(
+ gatt_result.err().unwrap(),
+ ))));
+ };
+ return Poll::Ready(Some(Ok(event)));
+ }
+ }
+ }
+ }
+}
+
+impl<T: bt_gatt::ServerTypes> FusedStream for LocalServiceState<T> {
+ fn is_terminated(&self) -> bool {
+ match self {
+ Self::Terminated => true,
+ _ => false,
+ }
+ }
+}
+
+impl<T: bt_gatt::ServerTypes> LocalServiceState<T> {
+ fn is_not_published(&self) -> bool {
+ matches!(self, LocalServiceState::NotPublished { .. })
+ }
+}
+
+#[pin_project]
+pub struct AudioStreamControlServiceServer<T: bt_gatt::ServerTypes> {
+ service_def: ServiceDefinition,
+ #[pin]
+ local_service: LocalServiceState<T>,
+ default_client_endpoints: ClientEndpoints,
+ client_endpoints: HashMap<PeerId, ClientEndpoints>,
+}
+
+const CONTROL_POINT_HANDLE: Handle = Handle(1);
+const BASE_ENDPOINT_HANDLE: Handle = Handle(2);
+
+pub const ASCS_UUID: Uuid = Uuid::from_u16(0x184E);
+
+// As only one ASCS service is allowed on a host, define an arbitrary ServiceId
+// that every AudioStreamControlServiceServer will attempt to use so publishing
+// multiple will fail.
+pub(crate) const ASCS_SERVICE_ID: ServiceId = ServiceId::new(1123901);
+
+impl<T: bt_gatt::ServerTypes> AudioStreamControlServiceServer<T> {
+ pub fn new(source_count: u8, sink_count: u8) -> Self {
+ let default_client_endpoints = ClientEndpoints::new(source_count, sink_count);
+ let mut chars: Vec<Characteristic> = (&default_client_endpoints).into();
+ chars.push(Self::build_control_point());
+ let mut service_def = ServiceDefinition::new(
+ ASCS_SERVICE_ID,
+ ASCS_UUID,
+ bt_gatt::types::ServiceKind::Primary,
+ );
+ for c in chars {
+ service_def.add_characteristic(c).unwrap();
+ }
+ Self {
+ service_def,
+ local_service: Default::default(),
+ default_client_endpoints,
+ client_endpoints: Default::default(),
+ }
+ }
+
+ fn build_control_point() -> Characteristic {
+ let properties = CharacteristicProperty::Write
+ | CharacteristicProperty::WriteWithoutResponse
+ | CharacteristicProperty::Notify;
+ let permissions =
+ AttributePermissions::with_levels(&properties, &SecurityLevels::encryption_required());
+ Characteristic {
+ handle: CONTROL_POINT_HANDLE,
+ uuid: Uuid::from_u16(0x2BC6),
+ properties,
+ permissions,
+ descriptors: Vec::new(),
+ }
+ }
+
+ pub fn publish(&mut self, server: &T::Server) -> Result<(), Error> {
+ if !self.local_service.is_not_published() {
+ return Err(Error::AlreadyPublished);
+ }
+ let LocalServiceState::NotPublished { waker } = std::mem::replace(
+ &mut self.local_service,
+ LocalServiceState::Preparing { fut: server.prepare(self.service_def.clone()) },
+ ) else {
+ unreachable!();
+ };
+ waker.map(Waker::wake);
+ Ok(())
+ }
+
+ pub fn release(&mut self, _id: AseId) -> Result<(), Error> {
+ unimplemented!()
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+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)]
+struct AudioStreamEndpoint {
+ handle: Handle,
+ direction: AudioDirection,
+ ase_id: AseId,
+ state: AseState,
+ // TODO(b/433287917): Add Additional Parameters for other states.
+ // Currently only works for Idle and Releasing states.
+}
+
+impl AudioStreamEndpoint {
+ fn into_char_value(&self) -> Vec<u8> {
+ let mut value = Vec::with_capacity(2);
+ value.push(self.ase_id.into());
+ value.push(self.state.into());
+ // TODO: add the additional_ase_parameters for the other states
+ value
+ }
+}
+
+impl From<&AudioStreamEndpoint> for Characteristic {
+ fn from(value: &AudioStreamEndpoint) -> Self {
+ let properties = CharacteristicProperty::Read | CharacteristicProperty::Notify;
+ let permissions =
+ AttributePermissions::with_levels(&properties, &SecurityLevels::encryption_required());
+ Characteristic {
+ handle: value.handle,
+ uuid: (&value.direction).into(),
+ properties,
+ permissions,
+ descriptors: Vec::new(),
+ }
+ }
+}
+
+struct ClientEndpoints {
+ endpoints: HashMap<AseId, AudioStreamEndpoint>,
+ handles: HashMap<Handle, AseId>,
+}
+
+impl ClientEndpoints {
+ fn new(source_count: u8, sink_count: u8) -> Self {
+ let dir_iter = std::iter::repeat(AudioDirection::Source)
+ .take(source_count as usize)
+ .chain(std::iter::repeat(AudioDirection::Sink).take(sink_count as usize));
+ // AseIds shall not have an id of 0
+ let (endpoints, handles) = (1..)
+ .zip(dir_iter)
+ .map(|(raw_ase_id, direction)| {
+ let handle = Handle(BASE_ENDPOINT_HANDLE.0 + raw_ase_id as u64);
+ let ase_id = AseId(raw_ase_id);
+ (
+ (
+ ase_id,
+ AudioStreamEndpoint { handle, ase_id, direction, state: AseState::Idle },
+ ),
+ (handle, ase_id),
+ )
+ })
+ .unzip();
+ Self { endpoints, handles }
+ }
+
+ fn clone_for_peer(&self, _peer_id: PeerId) -> Self {
+ // TODO: Randomize the ASE_IDs. Handles need to stay the same.
+ Self { endpoints: self.endpoints.clone(), handles: self.handles.clone() }
+ }
+}
+
+impl From<&ClientEndpoints> for Vec<Characteristic> {
+ fn from(value: &ClientEndpoints) -> Self {
+ value.endpoints.values().map(Into::into).collect()
+ }
+}
+
+pub enum ServiceEvent {}
+
+impl<T: bt_gatt::ServerTypes> Stream for AudioStreamControlServiceServer<T> {
+ type Item = Result<ServiceEvent, Error>;
+
+ fn poll_next(
+ self: std::pin::Pin<&mut Self>,
+ cx: &mut std::task::Context<'_>,
+ ) -> std::task::Poll<Option<Self::Item>> {
+ let mut this = self.project();
+ loop {
+ let event = match futures::ready!(this.local_service.as_mut().poll_next(cx)) {
+ None => return Poll::Ready(None),
+ Some(Err(e)) => return Poll::Ready(Some(Err(e))),
+ Some(Ok(event)) => event,
+ };
+ use bt_gatt::server::ServiceEvent::*;
+ let peer_id = event.peer_id();
+ let peer_entry = this
+ .client_endpoints
+ .entry(peer_id)
+ .or_insert_with(|| this.default_client_endpoints.clone_for_peer(peer_id));
+ match event {
+ Read { handle, offset, responder, .. } => {
+ let offset = offset as usize;
+ if handle == CONTROL_POINT_HANDLE {
+ responder.error(GattError::ReadNotPermitted);
+ continue;
+ }
+ let Some(ase_id) = peer_entry.handles.get(&handle) else {
+ responder.error(GattError::InvalidHandle);
+ continue;
+ };
+ let Some(endpoint) = peer_entry.endpoints.get(ase_id) else {
+ responder.error(GattError::UnlikelyError);
+ continue;
+ };
+ let value = endpoint.into_char_value();
+ if offset > value.len() {
+ responder.error(GattError::InvalidOffset);
+ continue;
+ }
+ responder.respond(&value[offset..]);
+ continue;
+ }
+ Write { peer_id, handle, offset, value, responder } => {
+ if handle != CONTROL_POINT_HANDLE {
+ responder.error(GattError::WriteNotPermitted);
+ continue;
+ }
+ if offset != 0 {
+ // Offset write isn't allowed by the service?
+ // TODO: determine if partial writes should be allowed
+ responder.error(GattError::InvalidOffset);
+ continue;
+ }
+ responder.acknowledge();
+ let _op = match AseControlOperation::try_from(value.to_owned()) {
+ Ok(op) => op,
+ Err(e) => {
+ let service_ref = this.local_service.as_ref();
+
+ let value = e.notify_value();
+ service_ref.service().unwrap().notify(
+ &CONTROL_POINT_HANDLE,
+ &value[..],
+ &[peer_id],
+ );
+ continue;
+ }
+ };
+ // TODO: Do the operation here, possibly notifying things
+ continue;
+ }
+ ClientConfiguration { peer_id, handle, notification_type } => {
+ log::info!(
+ "ASCS Got ClientConfig for {peer_id:?}: {handle:?} {notification_type:?}"
+ );
+ }
+ PeerInfo { peer_id, mtu, connected, .. } => {
+ log::info!(
+ "ASCS got PeerInfo {peer_id:?}: mtu {mtu:?}, connected: {connected:?}"
+ );
+ }
+ _ => continue,
+ }
+ }
+ }
+}
+
+impl<T: bt_gatt::ServerTypes> FusedStream for AudioStreamControlServiceServer<T> {
+ fn is_terminated(&self) -> bool {
+ self.local_service.is_terminated()
+ }
+}
diff --git a/rust/bt-ascs/src/tests.rs b/rust/bt-ascs/src/tests.rs
new file mode 100644
index 0000000..3e88f87
--- /dev/null
+++ b/rust/bt-ascs/src/tests.rs
@@ -0,0 +1,143 @@
+// 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::{PeerId, Uuid};
+use bt_gatt::test_utils::{FakeServer, FakeServerEvent, FakeTypes};
+use bt_gatt::types::Handle;
+
+use futures::channel::mpsc::UnboundedReceiver;
+use futures::{Stream, StreamExt};
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+use crate::server::AudioStreamControlServiceServer;
+use crate::server::ServiceEvent;
+use crate::server::{ASCS_SERVICE_ID, ASCS_UUID};
+use crate::*;
+
+#[track_caller]
+fn expect_service_event(events: &mut UnboundedReceiver<FakeServerEvent>) -> FakeServerEvent {
+ match events.poll_next_unpin(&mut futures_test::task::noop_context()) {
+ Poll::Ready(Some(event)) => event,
+ x => panic!("Expected fake server event, got {x:?}"),
+ }
+}
+
+#[test]
+fn publishes() {
+ let mut ascs_server = std::pin::pin!(AudioStreamControlServiceServer::<FakeTypes>::new(1, 1));
+
+ let (count_waker, woken_count) = futures_test::task::new_count_waker();
+
+ // Polling the server before it's published should result in Poll::Pending
+ let poll_result = ascs_server.as_mut().poll_next(&mut Context::from_waker(&count_waker));
+
+ assert!(poll_result.is_pending());
+ assert_eq!(woken_count.get(), 0);
+
+ let (fake_server, mut events) = FakeServer::new();
+
+ let result = ascs_server.publish(&fake_server);
+ assert!(result.is_ok());
+
+ let already_published = ascs_server.publish(&fake_server);
+
+ assert!(already_published.is_err());
+
+ assert_eq!(woken_count.get(), 1);
+
+ let poll_result = ascs_server.poll_next(&mut Context::from_waker(&count_waker));
+
+ match expect_service_event(&mut events) {
+ FakeServerEvent::Published { id: _, definition } => {
+ assert_eq!(definition.uuid(), ASCS_UUID);
+ assert_eq!(
+ definition.characteristics().filter(|c| c.uuid == Uuid::from_u16(0x2BC4)).count(),
+ 1
+ );
+ assert_eq!(
+ definition.characteristics().filter(|c| c.uuid == Uuid::from_u16(0x2BC5)).count(),
+ 1
+ );
+ }
+ x => panic!("Expected published event, got {x:?}"),
+ };
+
+ // Should still be pending, even though we had some work to do.
+ assert!(poll_result.is_pending());
+}
+
+fn published_server() -> (
+ Pin<Box<AudioStreamControlServiceServer<FakeTypes>>>,
+ FakeServer,
+ UnboundedReceiver<FakeServerEvent>,
+) {
+ let mut ascs_server = Box::pin(AudioStreamControlServiceServer::<FakeTypes>::new(1, 1));
+ let (fake_server, events) = FakeServer::new();
+
+ let result = ascs_server.publish(&fake_server);
+ assert!(result.is_ok());
+
+ assert!(ascs_server.poll_next_unpin(&mut futures_test::task::noop_context()).is_pending());
+
+ (ascs_server, fake_server, events)
+}
+
+fn poll_server(
+ server: &mut Pin<Box<AudioStreamControlServiceServer<FakeTypes>>>,
+) -> Poll<Option<core::result::Result<ServiceEvent, Error>>> {
+ server.poll_next_unpin(&mut futures_test::task::noop_context())
+}
+
+// Ignored because we currently do nothing with operations.
+#[ignore]
+#[test]
+fn peers_are_separated() {
+ let (mut ascs_server, fake_server, mut server_events) = published_server();
+
+ // Read the sink uuid
+ fake_server.incoming_read(PeerId(1), ASCS_SERVICE_ID, Handle(2), 0);
+
+ // Poll the ascs server, should not result in an ASCS event
+ assert!(poll_server(&mut ascs_server).is_pending());
+
+ // Should have the response
+ let peer_one_value;
+ match server_events.poll_next_unpin(&mut futures_test::task::noop_context()) {
+ Poll::Ready(Some(FakeServerEvent::ReadResponded { service_id, handle: _, value })) => {
+ assert_eq!(service_id, ASCS_SERVICE_ID);
+ peer_one_value = value.unwrap();
+ }
+ x => panic!("Expected the read to be responded to got {x:?}"),
+ };
+
+ let ase_id = peer_one_value[0];
+
+ // Codec Configure the first peer ase_id
+ fake_server.incoming_write(
+ PeerId(1),
+ ASCS_SERVICE_ID,
+ Handle(1),
+ 0,
+ vec![0x01, ase_id, 0x01, 0x01, 0x06, 0x00],
+ );
+
+ // Still shouldn't have any event
+ assert!(poll_server(&mut ascs_server).is_pending());
+
+ // Read the sink id from another peer
+ fake_server.incoming_read(PeerId(2), ASCS_SERVICE_ID, Handle(2), 0);
+
+ let peer_two_value;
+ match server_events.poll_next_unpin(&mut futures_test::task::noop_context()) {
+ Poll::Ready(Some(FakeServerEvent::ReadResponded { service_id, handle: _, value })) => {
+ assert_eq!(service_id, ASCS_SERVICE_ID);
+ peer_two_value = value.unwrap();
+ }
+ x => panic!("Expected the read to be responded to got {x:?}"),
+ };
+
+ let _ase_id = peer_two_value[0];
+ assert!(peer_one_value[1] != peer_two_value[1]);
+}
diff --git a/rust/bt-ascs/src/types.rs b/rust/bt-ascs/src/types.rs
new file mode 100644
index 0000000..2fb54ad
--- /dev/null
+++ b/rust/bt-ascs/src/types.rs
@@ -0,0 +1,991 @@
+// 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::{AudioLocation, codec_configuration};
+
+ #[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:?}"),
+ };
+ }
+}
diff --git a/rust/bt-bap/src/types.rs b/rust/bt-bap/src/types.rs
index cdb3c7e..a97a657 100644
--- a/rust/bt-bap/src/types.rs
+++ b/rust/bt-bap/src/types.rs
@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use bt_common::core::ltv::LtValue;
use bt_common::core::CodecId;
+use bt_common::core::ltv::LtValue;
use bt_common::generic_audio::codec_configuration::CodecConfiguration;
use bt_common::generic_audio::metadata_ltv::Metadata;
use bt_common::packet_encoding::{Decodable, Encodable, Error as PacketError};
@@ -51,13 +51,13 @@
impl Decodable for BroadcastId {
type Error = PacketError;
- fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.len() != Self::BYTE_SIZE {
- return Err(PacketError::UnexpectedDataLength);
+ return (Err(PacketError::UnexpectedDataLength), buf.len());
}
let padded_bytes = [buf[0], buf[1], buf[2], 0x00];
- Ok((BroadcastId(u32::from_le_bytes(padded_bytes)), Self::BYTE_SIZE))
+ (Ok(BroadcastId(u32::from_le_bytes(padded_bytes))), Self::BYTE_SIZE)
}
}
@@ -96,17 +96,21 @@
impl Decodable for BroadcastAudioAnnouncement {
type Error = PacketError;
- fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.len() < Self::PACKET_SIZE {
- return Err(PacketError::UnexpectedDataLength);
+ return (Err(PacketError::UnexpectedDataLength), buf.len());
}
- let (broadcast_id, _) = BroadcastId::decode(&buf[0..3])?;
- // According to the spec, broadcast audio announcement service data inlcudes
- // broadcast id and any additional service data. We don't store any
- // additional parameters, so for now we just "consume" all of data buffer
- // without doing anything.
- Ok((Self { broadcast_id }, buf.len()))
+ match BroadcastId::decode(&buf[0..3]) {
+ (Ok(broadcast_id), _) => {
+ // According to the spec, broadcast audio announcement service data inlcudes
+ // broadcast id and any additional service data. We don't store any
+ // additional parameters, so for now we just "consume" all of data buffer
+ // without doing anything.
+ (Ok(Self { broadcast_id }), buf.len())
+ }
+ (Err(e), _) => (Err(e), buf.len()),
+ }
}
}
@@ -128,9 +132,9 @@
impl Decodable for BroadcastAudioSourceEndpoint {
type Error = PacketError;
- fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.len() < Self::MIN_PACKET_SIZE {
- return Err(PacketError::UnexpectedDataLength);
+ return (Err(PacketError::UnexpectedDataLength), buf.len());
}
let mut idx = 0 as usize;
@@ -140,20 +144,27 @@
let num_big: usize = buf[idx] as usize;
idx += 1;
if num_big < 1 {
- return Err(PacketError::InvalidParameter(format!(
- "num of subgroups shall be at least 1 got {num_big}"
- )));
+ return (
+ Err(PacketError::InvalidParameter(format!(
+ "num of subgroups shall be at least 1 got {num_big}"
+ ))),
+ buf.len(),
+ );
}
-
let mut big = Vec::new();
while big.len() < num_big {
- let (group, len) = BroadcastIsochronousGroup::decode(&buf[idx..])
- .map_err(|e| PacketError::InvalidParameter(format!("{e}")))?;
- big.push(group);
- idx += len;
+ match BroadcastIsochronousGroup::decode(&buf[idx..]) {
+ (Ok(group), consumed) => {
+ big.push(group);
+ idx += consumed;
+ }
+ (Err(e), _) => {
+ return (Err(PacketError::InvalidParameter(e.to_string())), buf.len());
+ }
+ };
}
- Ok((Self { presentation_delay_ms: presentation_delay, big }, idx))
+ (Ok(Self { presentation_delay_ms: presentation_delay, big }), idx)
}
}
@@ -178,59 +189,83 @@
impl Decodable for BroadcastIsochronousGroup {
type Error = PacketError;
- fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.len() < BroadcastIsochronousGroup::MIN_PACKET_SIZE {
- return Err(PacketError::UnexpectedDataLength);
+ return (Err(PacketError::UnexpectedDataLength), buf.len());
}
let mut idx = 0;
let num_bis = buf[idx] as usize;
idx += 1;
if num_bis < 1 {
- return Err(PacketError::InvalidParameter(format!(
- "num of BIS shall be at least 1 got {num_bis}"
- )));
+ return (
+ Err(PacketError::InvalidParameter(format!(
+ "num of BIS shall be at least 1 got {num_bis}"
+ ))),
+ buf.len(),
+ );
}
- let (codec_id, read_bytes) = CodecId::decode(&buf[idx..])?;
- idx += read_bytes;
+ let mut decode_fn = || {
+ let codec_id;
+ match CodecId::decode(&buf[idx..]) {
+ (Ok(id), consumed) => {
+ codec_id = id;
+ idx += consumed;
+ }
+ (Err(e), _) => {
+ return Err(e);
+ }
+ };
- let codec_config_len = buf[idx] as usize;
- idx += 1;
- if idx + codec_config_len > buf.len() {
- return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+ let codec_config_len = buf[idx] as usize;
+ idx += 1;
+ if idx + codec_config_len > buf.len() {
+ return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+ }
+ let (results, consumed) =
+ CodecConfiguration::decode_all(&buf[idx..idx + codec_config_len]);
+ if consumed != codec_config_len {
+ return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+ }
+
+ let codec_specific_configs = results.into_iter().filter_map(Result::ok).collect();
+ idx += codec_config_len;
+
+ let metadata_len = buf[idx] as usize;
+ idx += 1;
+ if idx + metadata_len > buf.len() {
+ return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+ }
+
+ let (results_metadata, consumed_len) =
+ Metadata::decode_all(&buf[idx..idx + metadata_len]);
+ if consumed_len != metadata_len {
+ return Err(PacketError::UnexpectedDataLength);
+ }
+ // Ignore any undecodable metadata types
+ let metadata = results_metadata.into_iter().filter_map(Result::ok).collect();
+ idx += consumed_len;
+
+ let mut bis = Vec::new();
+ while bis.len() < num_bis {
+ match BroadcastIsochronousStream::decode(&buf[idx..]) {
+ (Ok(stream), consumed) => {
+ bis.push(stream);
+ idx += consumed;
+ }
+ (Err(e), _consumed) => {
+ return Err(PacketError::InvalidParameter(e.to_string()));
+ }
+ }
+ }
+
+ Ok((BroadcastIsochronousGroup { codec_id, codec_specific_configs, metadata, bis }, idx))
+ };
+ match decode_fn() {
+ Ok((obj, consumed)) => (Ok(obj), consumed),
+ Err(e) => (Err(e), buf.len()),
}
- let (results, consumed) = CodecConfiguration::decode_all(&buf[idx..idx + codec_config_len]);
- if consumed != codec_config_len {
- return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
- }
-
- let codec_specific_configs = results.into_iter().filter_map(Result::ok).collect();
- idx += codec_config_len;
-
- let metadata_len = buf[idx] as usize;
- idx += 1;
- if idx + metadata_len > buf.len() {
- return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
- }
-
- let (results_metadata, consumed_len) = Metadata::decode_all(&buf[idx..idx + metadata_len]);
- if consumed_len != metadata_len {
- return Err(PacketError::UnexpectedDataLength);
- }
- // Ignore any undecodable metadata types
- let metadata = results_metadata.into_iter().filter_map(Result::ok).collect();
- idx += consumed_len;
-
- let mut bis = Vec::new();
- while bis.len() < num_bis {
- let (stream, len) = BroadcastIsochronousStream::decode(&buf[idx..])
- .map_err(|e| PacketError::InvalidParameter(format!("{e}")))?;
- bis.push(stream);
- idx += len;
- }
-
- Ok((BroadcastIsochronousGroup { codec_id, codec_specific_configs, metadata, bis }, idx))
}
}
@@ -247,9 +282,9 @@
impl Decodable for BroadcastIsochronousStream {
type Error = PacketError;
- fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.len() < BroadcastIsochronousStream::MIN_PACKET_SIZE {
- return Err(PacketError::UnexpectedDataLength);
+ return (Err(PacketError::UnexpectedDataLength), buf.len());
}
let mut idx = 0;
@@ -262,15 +297,18 @@
let (results, consumed) = CodecConfiguration::decode_all(&buf[idx..idx + codec_config_len]);
if consumed != codec_config_len {
- return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+ return (Err(bt_common::packet_encoding::Error::UnexpectedDataLength), buf.len());
}
let codec_specific_configs = results.into_iter().filter_map(Result::ok).collect();
idx += codec_config_len;
- Ok((
- BroadcastIsochronousStream { bis_index, codec_specific_config: codec_specific_configs },
+ (
+ Ok(BroadcastIsochronousStream {
+ bis_index,
+ codec_specific_config: codec_specific_configs,
+ }),
idx,
- ))
+ )
}
}
@@ -280,8 +318,8 @@
use std::collections::HashSet;
- use bt_common::generic_audio::codec_configuration::{FrameDuration, SamplingFrequency};
use bt_common::generic_audio::AudioLocation;
+ use bt_common::generic_audio::codec_configuration::{FrameDuration, SamplingFrequency};
#[test]
fn broadcast_id() {
@@ -297,8 +335,8 @@
let bytes = vec![0x0C, 0x0B, 0x0A];
assert_eq!(buf, bytes);
- let (got, bytes) = BroadcastId::decode(&bytes).expect("should succeed");
- assert_eq!(got, id);
+ let (got, bytes) = BroadcastId::decode(&bytes);
+ assert_eq!(got, Ok(id));
assert_eq!(bytes, BroadcastId::BYTE_SIZE);
let got = BroadcastId::try_from(u32::from_le_bytes([0x0C, 0x0B, 0x0A, 0x00]))
.expect("should succeed");
@@ -310,15 +348,15 @@
let bytes = vec![0x0C, 0x0B, 0x0A];
let broadcast_id = BroadcastId::try_from(0x000A0B0C).unwrap();
- let (got, consumed) = BroadcastAudioAnnouncement::decode(&bytes).expect("should succeed");
- assert_eq!(got, BroadcastAudioAnnouncement { broadcast_id });
+ let (got, consumed) = BroadcastAudioAnnouncement::decode(&bytes);
+ assert_eq!(got, Ok(BroadcastAudioAnnouncement { broadcast_id }));
assert_eq!(consumed, 3);
let bytes = vec![
0x0C, 0x0B, 0x0A, 0x01, 0x02, 0x03, 0x04, 0x05, /* some other additional data */
];
- let (got, consumed) = BroadcastAudioAnnouncement::decode(&bytes).expect("should succeed");
- assert_eq!(got, BroadcastAudioAnnouncement { broadcast_id });
+ let (got, consumed) = BroadcastAudioAnnouncement::decode(&bytes);
+ assert_eq!(got, Ok(BroadcastAudioAnnouncement { broadcast_id }));
assert_eq!(consumed, 8);
}
@@ -331,11 +369,10 @@
0x05, 0x03, 0x03, 0x00, 0x00, 0x0C, // audio location LTV
];
- let (bis, _read_bytes) =
- BroadcastIsochronousStream::decode(&buf[..]).expect("should not fail");
+ let (bis, _read_bytes) = BroadcastIsochronousStream::decode(&buf[..]);
assert_eq!(
bis,
- BroadcastIsochronousStream {
+ Ok(BroadcastIsochronousStream {
bis_index: 0x01,
codec_specific_config: vec![
CodecConfiguration::SamplingFrequency(SamplingFrequency::F32000Hz),
@@ -346,7 +383,7 @@
AudioLocation::RightSurround
])),
],
- }
+ })
);
}
@@ -361,8 +398,7 @@
0x02, 0x03, 0x02, 0x02, 0x01, // bis index, codec specific config len, frame duration LTV (bis #2)
];
- let (big, _read_bytes) =
- BroadcastIsochronousGroup::decode(&buf[..]).expect("should not fail");
+ let big = BroadcastIsochronousGroup::decode(&buf[..]).0.expect("should not fail");
assert_eq!(
big,
BroadcastIsochronousGroup {
@@ -397,8 +433,7 @@
0x01, 0x03, 0x02, 0x05, 0x08, // bis index, codec specific config len, codec frame blocks LTV (big #2 / bis #2)
];
- let (base, _read_bytes) =
- BroadcastAudioSourceEndpoint::decode(&buf[..]).expect("should not fail");
+ let base = BroadcastAudioSourceEndpoint::decode(&buf[..]).0.expect("should not fail");
assert_eq!(base.presentation_delay_ms, 0x00302010);
assert_eq!(base.big.len(), 2);
assert_eq!(
@@ -449,8 +484,8 @@
0x06, // codec_specific_configuration_length of the 2nd BIS
0x05, 0x03, 0x02, 0x00, 0x00, 0x00, // front right audio channel
];
- let (base, read_bytes) =
- BroadcastAudioSourceEndpoint::decode(&buf[..]).expect("should not fail");
+ let (base, read_bytes) = BroadcastAudioSourceEndpoint::decode(&buf[..]);
+ let base = base.expect("should not fail");
assert_eq!(read_bytes, buf.len());
assert_eq!(base.presentation_delay_ms, 20000);
assert_eq!(base.big.len(), 1);
@@ -473,8 +508,8 @@
0x03, // codec_specific_configuration_length of the 1st BIS
0x02, 0x01, 0x05, // sampling frequency 24 kHz
];
- let (base, read_bytes) =
- BroadcastAudioSourceEndpoint::decode(&buf[..]).expect("should not fail");
+ let (base, read_bytes) = BroadcastAudioSourceEndpoint::decode(&buf[..]);
+ let base = base.expect("should not fail");
assert_eq!(read_bytes, buf.len());
assert_eq!(base.presentation_delay_ms, 20000);
assert_eq!(base.big.len(), 1);
diff --git a/rust/bt-bass/src/client.rs b/rust/bt-bass/src/client.rs
index b48d8ed..f7b3f5b 100644
--- a/rust/bt-bass/src/client.rs
+++ b/rust/bt-bass/src/client.rs
@@ -8,8 +8,8 @@
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
-use futures::stream::{BoxStream, FusedStream, SelectAll, Stream, StreamExt};
use futures::Future;
+use futures::stream::{BoxStream, FusedStream, SelectAll, Stream, StreamExt};
use log::warn;
use parking_lot::Mutex;
@@ -177,8 +177,8 @@
// record.
let mut buf = vec![0; READ_CHARACTERISTIC_BUFFER_SIZE];
match c.read(&mut buf[..]).await {
- Ok(read_bytes) => match BroadcastReceiveState::decode(&buf[0..read_bytes]) {
- Ok((decoded, _decoded_bytes)) => {
+ Ok(read_bytes) => match BroadcastReceiveState::decode(&buf[0..read_bytes]).0 {
+ Ok(decoded) => {
brs_map.insert(*c.handle(), decoded);
continue;
}
@@ -420,15 +420,15 @@
use assert_matches::assert_matches;
use futures::executor::block_on;
- use futures::{pin_mut, FutureExt};
+ use futures::{FutureExt, pin_mut};
- use bt_common::core::AdvertisingSetId;
use bt_common::Uuid;
+ use bt_common::core::AdvertisingSetId;
+ use bt_gatt::Characteristic;
use bt_gatt::test_utils::*;
use bt_gatt::types::{
AttributePermissions, CharacteristicProperties, CharacteristicProperty, Handle,
};
- use bt_gatt::Characteristic;
const RECEIVE_STATE_1_HANDLE: Handle = Handle(1);
const RECEIVE_STATE_2_HANDLE: Handle = Handle(2);
diff --git a/rust/bt-bass/src/client/event.rs b/rust/bt-bass/src/client/event.rs
index ca48fa8..0ff2768 100644
--- a/rust/bt-bass/src/client/event.rs
+++ b/rust/bt-bass/src/client/event.rs
@@ -15,9 +15,9 @@
use bt_gatt::client::CharacteristicNotification;
use bt_gatt::types::Error as BtGattError;
+use crate::client::KnownBroadcastSources;
use crate::client::error::Error;
use crate::client::error::ServiceError;
-use crate::client::KnownBroadcastSources;
use crate::types::*;
#[derive(Clone, Debug, PartialEq)]
@@ -150,7 +150,7 @@
}
Poll::Ready(Some(Ok(notification))) => {
let char_handle = notification.handle;
- let Ok((new_state, _)) =
+ let (Ok(new_state), _) =
BroadcastReceiveState::decode(notification.value.as_slice())
else {
self.event_queue.push_back(Ok(Event::UnknownPacket));
@@ -190,7 +190,7 @@
receive_state.big_encryption,
)));
} else {
- let other_events = Event::from_broadcast_receive_state(receive_state);
+ let other_events = Event::from_broadcast_receive_state(&receive_state);
for e in other_events.into_iter() {
multi_events.push_back(Ok(e));
}
diff --git a/rust/bt-bass/src/types.rs b/rust/bt-bass/src/types.rs
index 0ca916a..22baa25 100644
--- a/rust/bt-bass/src/types.rs
+++ b/rust/bt-bass/src/types.rs
@@ -7,7 +7,7 @@
use bt_common::core::{AddressType, AdvertisingSetId, PaInterval};
use bt_common::generic_audio::metadata_ltv::*;
use bt_common::packet_encoding::{Decodable, Encodable, Error as PacketError};
-use bt_common::{decodable_enum, Uuid};
+use bt_common::{Uuid, decodable_enum};
pub const ADDRESS_BYTE_SIZE: usize = 6;
const NUM_SUBGROUPS_BYTE_SIZE: usize = 1;
@@ -78,12 +78,12 @@
impl Decodable for RemoteScanStoppedOperation {
type Error = PacketError;
- fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
- if buf.len() < ControlPointOpcode::BYTE_SIZE {
- return Err(PacketError::UnexpectedDataLength);
+ fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
+ const BYTE_SIZE: usize = ControlPointOpcode::BYTE_SIZE;
+ if buf.len() < BYTE_SIZE {
+ return (Err(PacketError::BufferTooSmall), buf.len());
}
- let _ = Self::check_opcode(buf[0])?;
- Ok((RemoteScanStoppedOperation, ControlPointOpcode::BYTE_SIZE))
+ (Self::check_opcode(buf[0]).map(|_| RemoteScanStoppedOperation), BYTE_SIZE)
}
}
@@ -116,12 +116,12 @@
impl Decodable for RemoteScanStartedOperation {
type Error = PacketError;
- fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
- if buf.len() < ControlPointOpcode::BYTE_SIZE {
- return Err(PacketError::UnexpectedDataLength);
+ fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
+ const BYTE_SIZE: usize = ControlPointOpcode::BYTE_SIZE;
+ if buf.len() < BYTE_SIZE {
+ return (Err(PacketError::UnexpectedDataLength), buf.len());
}
- let _ = Self::check_opcode(buf[0])?;
- Ok((RemoteScanStartedOperation, ControlPointOpcode::BYTE_SIZE))
+ (Self::check_opcode(buf[0]).map(|_| RemoteScanStartedOperation), BYTE_SIZE)
}
}
@@ -194,43 +194,50 @@
impl Decodable for AddSourceOperation {
type Error = PacketError;
- fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.len() < Self::MIN_PACKET_SIZE {
- return Err(PacketError::UnexpectedDataLength);
+ return (Err(PacketError::UnexpectedDataLength), buf.len());
}
- let _ = Self::check_opcode(buf[0])?;
- let advertiser_address_type = AddressType::try_from(buf[1])?;
- let mut advertiser_address = [0; ADDRESS_BYTE_SIZE];
- advertiser_address.clone_from_slice(&buf[2..8]);
- let advertising_sid = AdvertisingSetId(buf[8]);
- let broadcast_id = BroadcastId::decode(&buf[9..12])?.0;
- let pa_sync = PaSync::try_from(buf[12])?;
- let pa_interval = PaInterval(u16::from_le_bytes(buf[13..15].try_into().unwrap()));
- let num_subgroups = buf[15] as usize;
- let mut subgroups = Vec::new();
+ let decode_fn = || {
+ let _ = Self::check_opcode(buf[0])?;
+ let advertiser_address_type = AddressType::try_from(buf[1])?;
+ let mut advertiser_address = [0; ADDRESS_BYTE_SIZE];
+ advertiser_address.clone_from_slice(&buf[2..8]);
+ let advertising_sid = AdvertisingSetId(buf[8]);
+ let broadcast_id = BroadcastId::decode(&buf[9..12]).0?;
+ let pa_sync = PaSync::try_from(buf[12])?;
+ let pa_interval = PaInterval(u16::from_le_bytes(buf[13..15].try_into().unwrap()));
+ let num_subgroups = buf[15] as usize;
+ let mut subgroups = Vec::new();
- let mut idx: usize = 16;
- for _i in 0..num_subgroups {
- if buf.len() <= idx {
- return Err(PacketError::UnexpectedDataLength);
+ let mut idx: usize = 16;
+ for _i in 0..num_subgroups {
+ if buf.len() <= idx {
+ return Err(PacketError::UnexpectedDataLength);
+ }
+ let (decoded, consumed) = BigSubgroup::decode(&buf[idx..]);
+ subgroups.push(decoded?);
+ idx += consumed;
}
- let decoded = BigSubgroup::decode(&buf[idx..])?;
- subgroups.push(decoded.0);
- idx += decoded.1;
+ Ok((
+ Self {
+ advertiser_address_type,
+ advertiser_address,
+ advertising_sid,
+ broadcast_id,
+ pa_sync,
+ pa_interval,
+ subgroups,
+ },
+ idx,
+ ))
+ };
+
+ match decode_fn() {
+ Ok((result, consumed)) => (Ok(result), consumed),
+ Err(e) => (Err(e), buf.len()),
}
- Ok((
- Self {
- advertiser_address_type,
- advertiser_address,
- advertising_sid,
- broadcast_id,
- pa_sync,
- pa_interval,
- subgroups,
- },
- idx,
- ))
}
}
@@ -303,27 +310,34 @@
type Error = PacketError;
// Min size includes Source_ID, PA_Sync, PA_Interval, and Num_Subgroups params.
- fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.len() < Self::MIN_PACKET_SIZE {
- return Err(PacketError::UnexpectedDataLength);
+ return (Err(PacketError::UnexpectedDataLength), buf.len());
}
- let _ = Self::check_opcode(buf[0])?;
- let source_id = buf[1];
- let pa_sync = PaSync::try_from(buf[2])?;
- let pa_interval = PaInterval(u16::from_le_bytes(buf[3..5].try_into().unwrap()));
- let num_subgroups = buf[5] as usize;
- let mut subgroups = Vec::new();
+ let decode_fn = || {
+ let _ = Self::check_opcode(buf[0])?;
+ let source_id = buf[1];
+ let pa_sync = PaSync::try_from(buf[2])?;
+ let pa_interval = PaInterval(u16::from_le_bytes(buf[3..5].try_into().unwrap()));
+ let num_subgroups = buf[5] as usize;
+ let mut subgroups = Vec::new();
- let mut idx = 6;
- for _i in 0..num_subgroups {
- if buf.len() < idx + BigSubgroup::MIN_PACKET_SIZE {
- return Err(PacketError::UnexpectedDataLength);
+ let mut idx = 6;
+ for _i in 0..num_subgroups {
+ if buf.len() < idx + BigSubgroup::MIN_PACKET_SIZE {
+ return Err(PacketError::UnexpectedDataLength);
+ }
+ let decoded = BigSubgroup::decode(&buf[idx..]);
+ subgroups.push(decoded.0?);
+ idx += decoded.1;
}
- let decoded = BigSubgroup::decode(&buf[idx..])?;
- subgroups.push(decoded.0);
- idx += decoded.1;
+ Ok((Self { source_id, pa_sync, pa_interval, subgroups }, idx))
+ };
+
+ match decode_fn() {
+ Ok((obj, consumed)) => (Ok(obj), consumed),
+ Err(e) => (Err(e), buf.len()),
}
- Ok((Self { source_id, pa_sync, pa_interval, subgroups }, idx))
}
}
@@ -384,15 +398,22 @@
type Error = PacketError;
// Min size includes Source_ID, PA_Sync, PA_Interval, and Num_Subgroups params.
- fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.len() < Self::PACKET_SIZE {
- return Err(PacketError::UnexpectedDataLength);
+ return (Err(PacketError::UnexpectedDataLength), buf.len());
}
- let _ = Self::check_opcode(buf[0])?;
- let source_id = buf[1];
- let mut broadcast_code = [0; Self::BROADCAST_CODE_LEN];
- broadcast_code.copy_from_slice(&buf[2..2 + Self::BROADCAST_CODE_LEN]);
- Ok((Self { source_id, broadcast_code }, Self::PACKET_SIZE))
+ let decode_fn = || {
+ let _ = Self::check_opcode(buf[0])?;
+ let source_id = buf[1];
+ let mut broadcast_code = [0; Self::BROADCAST_CODE_LEN];
+ broadcast_code.copy_from_slice(&buf[2..2 + Self::BROADCAST_CODE_LEN]);
+ Ok((Self { source_id, broadcast_code }, Self::PACKET_SIZE))
+ };
+
+ match decode_fn() {
+ Ok((obj, consumed)) => (Ok(obj), consumed),
+ Err(e) => (Err(e), buf.len()),
+ }
}
}
@@ -437,13 +458,19 @@
type Error = PacketError;
// Min size includes Source_ID, PA_Sync, PA_Interval, and Num_Subgroups params.
- fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.len() < Self::PACKET_SIZE {
- return Err(PacketError::UnexpectedDataLength);
+ return (Err(PacketError::UnexpectedDataLength), buf.len());
}
- let _ = Self::check_opcode(buf[0])?;
- let source_id = buf[1];
- Ok((RemoveSourceOperation(source_id), Self::PACKET_SIZE))
+ let decode_fn = || {
+ let _ = Self::check_opcode(buf[0])?;
+ let source_id = buf[1];
+ Ok((RemoveSourceOperation(source_id), Self::PACKET_SIZE))
+ };
+ match decode_fn() {
+ Ok((obj, consumed)) => (Ok(obj), consumed),
+ Err(e) => (Err(e), buf.len()),
+ }
}
}
@@ -555,27 +582,33 @@
impl Decodable for BigSubgroup {
type Error = PacketError;
- fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.len() < BigSubgroup::MIN_PACKET_SIZE {
- return Err(PacketError::UnexpectedDataLength);
+ return (Err(PacketError::UnexpectedDataLength), buf.len());
}
- let bis_sync = u32::from_le_bytes(buf[0..4].try_into().unwrap());
- let metadata_len = buf[4] as usize;
+ let decode_fn = || {
+ let bis_sync = u32::from_le_bytes(buf[0..4].try_into().unwrap());
+ let metadata_len = buf[4] as usize;
- let mut start_idx = 5;
- if buf.len() < start_idx + metadata_len {
- return Err(PacketError::UnexpectedDataLength);
- }
+ let mut start_idx = 5;
+ if buf.len() < start_idx + metadata_len {
+ return Err(PacketError::UnexpectedDataLength);
+ }
- let (results_metadata, consumed_len) =
- Metadata::decode_all(&buf[start_idx..start_idx + metadata_len]);
- start_idx += consumed_len;
- if start_idx != 5 + metadata_len {
- return Err(PacketError::UnexpectedDataLength);
+ let (results_metadata, consumed_len) =
+ Metadata::decode_all(&buf[start_idx..start_idx + metadata_len]);
+ start_idx += consumed_len;
+ if start_idx != 5 + metadata_len {
+ return Err(PacketError::UnexpectedDataLength);
+ }
+ // Ignore any undecodable metadata types
+ let metadata = results_metadata.into_iter().filter_map(Result::ok).collect();
+ Ok((BigSubgroup { bis_sync: BisSync(bis_sync), metadata }, start_idx))
+ };
+ match decode_fn() {
+ Ok((obj, consumed)) => (Ok(obj), consumed),
+ Err(e) => (Err(e), buf.len()),
}
- // Ignore any undecodable metadata types
- let metadata = results_metadata.into_iter().filter_map(Result::ok).collect();
- Ok((BigSubgroup { bis_sync: BisSync(bis_sync), metadata }, start_idx))
}
}
@@ -647,12 +680,14 @@
impl Decodable for BroadcastReceiveState {
type Error = PacketError;
- fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.len() == 0 {
- return Ok((Self::Empty, 0));
+ return (Ok(Self::Empty), 0);
}
- let res = ReceiveState::decode(&buf[..])?;
- Ok((Self::NonEmpty(res.0), res.1))
+ match ReceiveState::decode(&buf[..]) {
+ (Ok(state), consumed) => (Ok(Self::NonEmpty(state)), consumed),
+ (Err(e), consumed) => (Err(e), consumed),
+ }
}
}
@@ -741,49 +776,63 @@
impl Decodable for ReceiveState {
type Error = PacketError;
- fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.len() < Self::MIN_PACKET_SIZE {
- return Err(PacketError::UnexpectedDataLength);
+ return (Err(PacketError::UnexpectedDataLength), buf.len());
}
- let source_id = buf[0];
- let source_address_type = AddressType::try_from(buf[1])?;
- let mut source_address = [0; ADDRESS_BYTE_SIZE];
- source_address.clone_from_slice(&buf[2..8]);
- let source_adv_sid = AdvertisingSetId(buf[8]);
- let broadcast_id = BroadcastId::decode(&buf[9..12])?.0;
- let pa_sync_state = PaSyncState::try_from(buf[12])?;
+ let decode_fn = || {
+ let source_id = buf[0];
+ let source_address_type = AddressType::try_from(buf[1])?;
+ let mut source_address = [0; ADDRESS_BYTE_SIZE];
+ source_address.clone_from_slice(&buf[2..8]);
+ let source_adv_sid = AdvertisingSetId(buf[8]);
+ let broadcast_id = BroadcastId::decode(&buf[9..12]).0?;
+ let pa_sync_state = PaSyncState::try_from(buf[12])?;
- let decoded = EncryptionStatus::decode(&buf[13..])?;
- let big_encryption = decoded.0;
- let mut idx = 13 + decoded.1;
- if buf.len() <= idx {
- return Err(PacketError::UnexpectedDataLength);
- }
- let num_subgroups = buf[idx] as usize;
- let mut subgroups = Vec::new();
- idx += 1;
- for _i in 0..num_subgroups {
+ let big_encryption;
+ let mut idx = 13;
+ match EncryptionStatus::decode(&buf[13..]) {
+ (Ok(encryption), consumed) => {
+ big_encryption = encryption;
+ idx += consumed;
+ }
+ (Err(e), _) => {
+ return Err(e);
+ }
+ }
if buf.len() <= idx {
return Err(PacketError::UnexpectedDataLength);
}
- let (subgroup, consumed) = BigSubgroup::decode(&buf[idx..])?;
- subgroups.push(subgroup);
- idx += consumed;
+ let num_subgroups = buf[idx] as usize;
+ let mut subgroups = Vec::new();
+ idx += 1;
+ for _i in 0..num_subgroups {
+ if buf.len() <= idx {
+ return Err(PacketError::UnexpectedDataLength);
+ }
+ let (subgroup, consumed) = BigSubgroup::decode(&buf[idx..]);
+ subgroups.push(subgroup?);
+ idx += consumed;
+ }
+ Ok((
+ ReceiveState {
+ source_id,
+ source_address_type,
+ source_address,
+ source_adv_sid,
+ broadcast_id,
+ pa_sync_state,
+ big_encryption,
+ subgroups,
+ },
+ idx,
+ ))
+ };
+ match decode_fn() {
+ Ok((obj, consumed)) => (Ok(obj), consumed),
+ Err(e) => (Err(e), buf.len()),
}
- Ok((
- ReceiveState {
- source_id,
- source_address_type,
- source_address,
- source_adv_sid,
- broadcast_id,
- pa_sync_state,
- big_encryption,
- subgroups,
- },
- idx,
- ))
}
}
@@ -871,21 +920,21 @@
impl Decodable for EncryptionStatus {
type Error = PacketError;
- fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.len() < 1 {
- return Err(PacketError::UnexpectedDataLength);
+ return (Err(PacketError::UnexpectedDataLength), buf.len());
}
match buf[0] {
- 0x00 => Ok((Self::NotEncrypted, 1)),
- 0x01 => Ok((Self::BroadcastCodeRequired, 1)),
- 0x02 => Ok((Self::Decrypting, 1)),
+ 0x00 => (Ok(Self::NotEncrypted), 1),
+ 0x01 => (Ok(Self::BroadcastCodeRequired), 1),
+ 0x02 => (Ok(Self::Decrypting), 1),
0x03 => {
if buf.len() < 17 {
- return Err(PacketError::UnexpectedDataLength);
+ return (Err(PacketError::UnexpectedDataLength), buf.len());
}
- Ok((Self::BadCode(buf[1..17].try_into().unwrap()), 17))
+ (Ok(Self::BadCode(buf[1..17].try_into().unwrap())), 17)
}
- _ => Err(PacketError::OutOfRange),
+ _ => (Err(PacketError::OutOfRange), buf.len()),
}
}
}
@@ -950,9 +999,9 @@
assert_eq!(buf, bytes);
// Decoding not encrypted.
- let decoded = EncryptionStatus::decode(&bytes).expect("should succeed");
- assert_eq!(decoded.0, not_encrypted);
- assert_eq!(decoded.1, 1);
+ let (decoded, len) = EncryptionStatus::decode(&bytes);
+ assert_eq!(decoded, Ok(not_encrypted));
+ assert_eq!(len, 1);
// Encoding bad code status with code.
let bad_code = EncryptionStatus::BadCode([
@@ -969,10 +1018,10 @@
];
assert_eq!(buf, bytes);
- // Deocoding bad code statsu with code.
- let decoded = EncryptionStatus::decode(&bytes).expect("should succeed");
- assert_eq!(decoded.0, bad_code);
- assert_eq!(decoded.1, 17);
+ // Decoding bad code statsu with code.
+ let (decoded, len) = EncryptionStatus::decode(&bytes);
+ assert_eq!(decoded, Ok(bad_code));
+ assert_eq!(len, 17);
}
#[test]
@@ -992,11 +1041,11 @@
// Cannot decode empty buffer.
let buf = vec![];
- let _ = EncryptionStatus::decode(&buf).expect_err("should fail");
+ let _ = EncryptionStatus::decode(&buf).0.expect_err("should fail");
// Bad code status with no code.
let buf = vec![0x03];
- let _ = EncryptionStatus::decode(&buf).expect_err("should fail");
+ let _ = EncryptionStatus::decode(&buf).0.expect_err("should fail");
}
#[test]
@@ -1032,9 +1081,9 @@
assert_eq!(buf, bytes);
// Decoding remote scan stopped.
- let decoded = RemoteScanStoppedOperation::decode(&bytes).expect("should succeed");
- assert_eq!(decoded.0, stopped);
- assert_eq!(decoded.1, 1);
+ let (decoded, len) = RemoteScanStoppedOperation::decode(&bytes);
+ assert_eq!(decoded, Ok(stopped));
+ assert_eq!(len, 1);
}
#[test]
@@ -1049,9 +1098,9 @@
assert_eq!(buf, vec![0x01]);
// Decoding remote scan started.
- let decoded = RemoteScanStartedOperation::decode(&bytes).expect("should succeed");
- assert_eq!(decoded.0, started);
- assert_eq!(decoded.1, 1);
+ let (decoded, len) = RemoteScanStartedOperation::decode(&bytes);
+ assert_eq!(decoded, Ok(started));
+ assert_eq!(len, 1);
}
#[test]
@@ -1077,9 +1126,9 @@
assert_eq!(buf, bytes);
// Decoding operation with no subgroups.
- let decoded = AddSourceOperation::decode(&bytes).expect("should succeed");
- assert_eq!(decoded.0, op);
- assert_eq!(decoded.1, 16);
+ let (decoded, len) = AddSourceOperation::decode(&bytes);
+ assert_eq!(decoded, Ok(op));
+ assert_eq!(len, 16);
}
#[test]
@@ -1111,9 +1160,9 @@
assert_eq!(buf, bytes);
// Decoding operation with subgroups.
- let decoded = AddSourceOperation::decode(&bytes).expect("should succeed");
- assert_eq!(decoded.0, op);
- assert_eq!(decoded.1, 31);
+ let (decoded, len) = AddSourceOperation::decode(&bytes);
+ assert_eq!(decoded, Ok(op));
+ assert_eq!(len, 31);
}
#[test]
@@ -1129,9 +1178,9 @@
assert_eq!(buf, bytes);
// Decoding operation with no subgroups.
- let decoded = ModifySourceOperation::decode(&bytes).expect("should succeed");
- assert_eq!(decoded.0, op);
- assert_eq!(decoded.1, 6);
+ let (decoded, len) = ModifySourceOperation::decode(&bytes);
+ assert_eq!(decoded, Ok(op));
+ assert_eq!(len, 6);
}
#[test]
@@ -1156,9 +1205,9 @@
assert_eq!(buf, bytes);
// Decoding operation with subgroups.
- let decoded = ModifySourceOperation::decode(&bytes).expect("should succeed");
- assert_eq!(decoded.0, op);
- assert_eq!(decoded.1, 21);
+ let (decoded, len) = ModifySourceOperation::decode(&bytes);
+ assert_eq!(decoded, Ok(op));
+ assert_eq!(len, 21);
}
#[test]
@@ -1182,9 +1231,9 @@
assert_eq!(buf, bytes);
// Decoding.
- let decoded = SetBroadcastCodeOperation::decode(&bytes).expect("should succeed");
- assert_eq!(decoded.0, op);
- assert_eq!(decoded.1, 18);
+ let (decoded, len) = SetBroadcastCodeOperation::decode(&bytes);
+ assert_eq!(decoded, Ok(op));
+ assert_eq!(len, 18);
}
#[test]
@@ -1199,9 +1248,9 @@
assert_eq!(buf, bytes);
// Decoding.
- let decoded = RemoveSourceOperation::decode(&bytes).expect("should succeed");
- assert_eq!(decoded.0, op);
- assert_eq!(decoded.1, 2);
+ let (decoded, len) = RemoveSourceOperation::decode(&bytes);
+ assert_eq!(decoded, Ok(op));
+ assert_eq!(len, 2);
}
#[test]
@@ -1233,9 +1282,9 @@
assert_eq!(buf, bytes);
// Decoding.
- let decoded = BroadcastReceiveState::decode(&bytes).expect("should succeed");
- assert_eq!(decoded.0, state);
- assert_eq!(decoded.1, 31);
+ let (decoded, len) = BroadcastReceiveState::decode(&bytes);
+ assert_eq!(decoded, Ok(state));
+ assert_eq!(len, 31);
}
#[test]
@@ -1266,8 +1315,8 @@
assert_eq!(buf, bytes);
// Decoding.
- let decoded = BroadcastReceiveState::decode(&bytes).expect("should succeed");
- assert_eq!(decoded.0, state);
- assert_eq!(decoded.1, 23);
+ let (decoded, len) = BroadcastReceiveState::decode(&bytes);
+ assert_eq!(decoded, Ok(state));
+ assert_eq!(len, 23);
}
}
diff --git a/rust/bt-battery/src/monitor/client.rs b/rust/bt-battery/src/monitor/client.rs
index 516cb72..d1ae91e 100644
--- a/rust/bt-battery/src/monitor/client.rs
+++ b/rust/bt-battery/src/monitor/client.rs
@@ -3,14 +3,14 @@
// found in the LICENSE file.
use bt_common::packet_encoding::Decodable;
+use bt_gatt::GattTypes;
use bt_gatt::client::{CharacteristicNotification, PeerService, ServiceCharacteristic};
use bt_gatt::types::{CharacteristicProperty, Error as GattLibraryError, Handle};
-use bt_gatt::GattTypes;
use futures::stream::{BoxStream, FusedStream, SelectAll, Stream, StreamExt};
use std::task::Poll;
use crate::error::{Error, ServiceError};
-use crate::types::{BatteryLevel, BATTERY_LEVEL_UUID, READ_CHARACTERISTIC_BUFFER_SIZE};
+use crate::types::{BATTERY_LEVEL_UUID, BatteryLevel, READ_CHARACTERISTIC_BUFFER_SIZE};
/// Represents the termination status of a Stream.
#[derive(Clone, Copy, Debug, PartialEq, Default)]
@@ -70,9 +70,8 @@
let result = futures::ready!(self.notification_streams.poll_next_unpin(cx));
match result {
Some(Ok(notification)) => {
- let battery_level_result =
- BatteryLevel::decode(¬ification.value[..]).map(|r| r.0).map_err(Into::into);
- Poll::Ready(Some(battery_level_result))
+ let battery_level_result = BatteryLevel::decode(¬ification.value[..]).0?;
+ Poll::Ready(Some(Ok(battery_level_result)))
}
Some(Err(e)) => {
// GATT Errors are not fatal and will be relayed to the stream.
@@ -151,8 +150,9 @@
let (battery_level, _decoded_bytes) = {
let mut buf = vec![0; READ_CHARACTERISTIC_BUFFER_SIZE];
let read_bytes = primary_battery_level_characteristic.read(&mut buf[..]).await?;
- BatteryLevel::decode(&buf[0..read_bytes])?
+ BatteryLevel::decode(&buf[0..read_bytes])
};
+ let battery_level = battery_level?;
// Subscribe to notifications on the battery level characteristic if it
// is supported.
@@ -199,13 +199,13 @@
pub(crate) mod tests {
use super::*;
- use bt_common::packet_encoding::Error as PacketError;
use bt_common::Uuid;
+ use bt_common::packet_encoding::Error as PacketError;
use bt_gatt::test_utils::{FakeClient, FakePeerService, FakeTypes};
use bt_gatt::types::{
AttributePermissions, Characteristic, CharacteristicProperties, GattError,
};
- use futures::{pin_mut, FutureExt};
+ use futures::{FutureExt, pin_mut};
pub(crate) const BATTERY_LEVEL_HANDLE: Handle = Handle(0x1);
pub(crate) fn fake_battery_service(battery_level: u8) -> FakePeerService {
diff --git a/rust/bt-battery/src/types.rs b/rust/bt-battery/src/types.rs
index b7980c5..ccc4428 100644
--- a/rust/bt-battery/src/types.rs
+++ b/rust/bt-battery/src/types.rs
@@ -2,8 +2,8 @@
// 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, Error as PacketError};
use bt_common::Uuid;
+use bt_common::packet_encoding::{Decodable, Error as PacketError};
/// The UUID of the GATT battery service.
/// Defined in Assigned Numbers Section 3.4.2.
@@ -21,13 +21,13 @@
impl Decodable for BatteryLevel {
type Error = PacketError;
- fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.len() < 1 {
- return Err(PacketError::UnexpectedDataLength);
+ return (Err(PacketError::UnexpectedDataLength), buf.len());
}
let level_percent = buf[0].clamp(0, 100);
- Ok((BatteryLevel(level_percent), 1))
+ (Ok(BatteryLevel(level_percent)), 1)
}
}
@@ -38,31 +38,32 @@
#[test]
fn decode_battery_level_success() {
let buf = [55];
- let (parsed, parsed_size) = BatteryLevel::decode(&buf).expect("valid battery level");
- assert_eq!(parsed, BatteryLevel(55));
+ let (parsed, parsed_size) = BatteryLevel::decode(&buf);
+ assert_eq!(parsed, Ok(BatteryLevel(55)));
assert_eq!(parsed_size, 1);
}
#[test]
fn decode_large_battery_level_clamped() {
let buf = [125]; // Too large, expected to be a percentage value.
- let (parsed, parsed_size) = BatteryLevel::decode(&buf).expect("valid battery level");
- assert_eq!(parsed, BatteryLevel(100));
+ let (parsed, parsed_size) = BatteryLevel::decode(&buf);
+ assert_eq!(parsed, Ok(BatteryLevel(100)));
assert_eq!(parsed_size, 1);
}
#[test]
fn decode_large_buf_success() {
let large_buf = [19, 0]; // Only expect a single u8 for the level.
- let (parsed, parsed_size) = BatteryLevel::decode(&large_buf).expect("valid battery level");
- assert_eq!(parsed, BatteryLevel(19)); // Only the first byte should be read.
+ let (parsed, parsed_size) = BatteryLevel::decode(&large_buf);
+ assert_eq!(parsed, Ok(BatteryLevel(19))); // Only the first byte should be read.
assert_eq!(parsed_size, 1);
}
#[test]
fn decode_invalid_battery_level_buf_is_error() {
let buf = [];
- let result = BatteryLevel::decode(&buf);
+ let (result, parsed_size) = BatteryLevel::decode(&buf);
assert_eq!(result, Err(PacketError::UnexpectedDataLength));
+ assert_eq!(parsed_size, 0);
}
}
diff --git a/rust/bt-broadcast-assistant/src/assistant/event.rs b/rust/bt-broadcast-assistant/src/assistant/event.rs
index e1e444c..7290e47 100644
--- a/rust/bt-broadcast-assistant/src/assistant/event.rs
+++ b/rust/bt-broadcast-assistant/src/assistant/event.rs
@@ -8,14 +8,14 @@
use std::task::Poll;
use bt_bap::types::{BroadcastAudioSourceEndpoint, BroadcastId};
+use bt_common::PeerId;
use bt_common::packet_encoding::Decodable;
use bt_common::packet_encoding::Error as PacketError;
-use bt_common::PeerId;
use bt_gatt::central::{AdvertisingDatum, ScanResult};
use crate::assistant::{
- DiscoveredBroadcastSources, Error, BASIC_AUDIO_ANNOUNCEMENT_SERVICE,
- BROADCAST_AUDIO_ANNOUNCEMENT_SERVICE,
+ BASIC_AUDIO_ANNOUNCEMENT_SERVICE, BROADCAST_AUDIO_ANNOUNCEMENT_SERVICE,
+ DiscoveredBroadcastSources, Error,
};
use crate::types::BroadcastSource;
@@ -59,11 +59,11 @@
continue;
};
if *uuid == BROADCAST_AUDIO_ANNOUNCEMENT_SERVICE {
- let (bid, _) = BroadcastId::decode(data.as_slice())?;
+ let bid = BroadcastId::decode(data.as_slice()).0?;
source.get_or_insert(BroadcastSource::default()).with_broadcast_id(bid);
} else if *uuid == BASIC_AUDIO_ANNOUNCEMENT_SERVICE {
// TODO(dayeonglee): revisit when we implement periodic advertisement.
- let (base, _) = BroadcastAudioSourceEndpoint::decode(data.as_slice())?;
+ let base = BroadcastAudioSourceEndpoint::decode(data.as_slice()).0?;
source.get_or_insert(BroadcastSource::default()).with_endpoint(base);
}
}
diff --git a/rust/bt-common/src/core.rs b/rust/bt-common/src/core.rs
index f7d94e5..db4a1d9 100644
--- a/rust/bt-common/src/core.rs
+++ b/rust/bt-common/src/core.rs
@@ -162,18 +162,18 @@
impl crate::packet_encoding::Decodable for CodecId {
type Error = crate::packet_encoding::Error;
- fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.len() < 5 {
- return Err(crate::packet_encoding::Error::UnexpectedDataLength);
+ return (Err(crate::packet_encoding::Error::UnexpectedDataLength), buf.len());
}
let format = buf[0].into();
if format != CodingFormat::VendorSpecific {
// Maybe don't ignore the company and vendor id, and check if they are wrong.
- return Ok((Self::Assigned(format), 5));
+ return (Ok(Self::Assigned(format)), 5);
}
let company_id = u16::from_le_bytes([buf[1], buf[2]]).into();
let vendor_specific_codec_id = u16::from_le_bytes([buf[3], buf[4]]);
- Ok((Self::VendorSpecific { company_id, vendor_specific_codec_id }, 5))
+ (Ok(Self::VendorSpecific { company_id, vendor_specific_codec_id }), 5)
}
}
@@ -239,34 +239,34 @@
#[test]
fn decode_codec_id() {
let assigned = [0x01, 0x00, 0x00, 0x00, 0x00];
- let (codec_id, _) = CodecId::decode(&assigned[..]).expect("should succeed");
- assert_eq!(codec_id, CodecId::Assigned(CodingFormat::ALawLog));
+ let (codec_id, _) = CodecId::decode(&assigned[..]);
+ assert_eq!(codec_id, Ok(CodecId::Assigned(CodingFormat::ALawLog)));
let vendor_specific = [0xFF, 0x36, 0xFD, 0x11, 0x22];
- let (codec_id, _) = CodecId::decode(&vendor_specific[..]).expect("should succeed");
+ let (codec_id, _) = CodecId::decode(&vendor_specific[..]);
assert_eq!(
codec_id,
- CodecId::VendorSpecific {
+ Ok(CodecId::VendorSpecific {
company_id: (0xFD36 as u16).into(),
vendor_specific_codec_id: 0x2211
- }
+ })
);
}
#[test]
fn encode_codec_id() {
let assigned = [0x01, 0x00, 0x00, 0x00, 0x00];
- let (codec_id, _) = CodecId::decode(&assigned[..]).expect("should succeed");
- assert_eq!(codec_id, CodecId::Assigned(CodingFormat::ALawLog));
+ let (codec_id, _) = CodecId::decode(&assigned[..]);
+ assert_eq!(codec_id, Ok(CodecId::Assigned(CodingFormat::ALawLog)));
let vendor_specific = [0xFF, 0x36, 0xFD, 0x11, 0x22];
- let (codec_id, _) = CodecId::decode(&vendor_specific[..]).expect("should succeed");
+ let (codec_id, _) = CodecId::decode(&vendor_specific[..]);
assert_eq!(
codec_id,
- CodecId::VendorSpecific {
+ Ok(CodecId::VendorSpecific {
company_id: (0xFD36 as u16).into(),
vendor_specific_codec_id: 0x2211
- }
+ })
);
}
}
diff --git a/rust/bt-common/src/core/ltv.rs b/rust/bt-common/src/core/ltv.rs
index 6a1083a..e8c7498 100644
--- a/rust/bt-common/src/core/ltv.rs
+++ b/rust/bt-common/src/core/ltv.rs
@@ -54,21 +54,15 @@
return (results, std::cmp::min(buf.len(), total_consumed));
}
let indicated_len = buf[total_consumed] as usize;
- match Self::decode(&buf[total_consumed..=total_consumed + indicated_len]) {
- Ok((item, consumed)) => {
+ let range_end = std::cmp::min(buf.len() - 1, total_consumed + indicated_len);
+ match Self::decode(&buf[total_consumed..=range_end]) {
+ (Ok(item), consumed) => {
results.push(Ok(item));
total_consumed += consumed;
}
- // If we are missing the type / length completely, or missing some of the data, we
- // can't continue
- Err(e @ Error::MissingType) | Err(e @ Error::MissingData(_)) => {
+ (Err(e), consumed) => {
results.push(Err(e));
- return (results, total_consumed);
- }
- Err(e) => {
- results.push(Err(e));
- // Consume the bytes
- total_consumed += indicated_len + 1;
+ total_consumed += consumed;
}
}
}
@@ -134,28 +128,36 @@
}
}
-impl<T: LtValue> Decodable for T {
+impl<T> Decodable for T
+where
+ T: LtValue,
+{
type Error = Error<T::Type>;
- fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.len() < 2 {
- return Err(Error::MissingType);
+ return (Err(Error::MissingType), buf.len());
}
let indicated_len = buf[0] as usize;
+ let too_short = buf.len() < indicated_len + 1;
+
let Some(ty) = Self::type_from_octet(buf[1]) else {
- return Err(Error::UnrecognizedType(Self::NAME.to_owned(), buf[1]));
+ return (
+ Err(Error::UnrecognizedType(Self::NAME.to_owned(), buf[1])),
+ if too_short { buf.len() } else { indicated_len + 1 },
+ );
};
- if buf.len() < indicated_len + 1 {
- return Err(Error::MissingData(ty));
+ if too_short {
+ return (Err(Error::MissingData(ty)), buf.len());
}
let size_range = Self::length_range_from_type(ty);
let remaining_len = (buf.len() - 1) as u8;
if !size_range.contains(&remaining_len) {
- return Err(Error::LengthOutOfRange(remaining_len, ty, size_range));
+ return (Err(Error::LengthOutOfRange(remaining_len, ty, size_range)), buf.len());
}
match Self::decode_value(&ty, &buf[2..=indicated_len]) {
- Err(e) => Err(Error::TypeFailedToDecode(ty, e)),
- Ok(s) => Ok((s, indicated_len + 1)),
+ Err(e) => (Err(Error::TypeFailedToDecode(ty, e)), indicated_len + 1),
+ Ok(s) => (Ok(s), indicated_len + 1),
}
}
}
@@ -304,6 +306,15 @@
}
#[test]
+ fn decode_wronglength() {
+ let encoded = [0x03, 0x02, 0x10, 0x01, 0x04, 0x03, 0x10, 0x01];
+ let (decoded, consumed) = TestValues::decode_all(&encoded);
+ assert_eq!(consumed, encoded.len());
+ assert_eq!(decoded[0], Ok(TestValues::TwoBytes(4097)));
+ assert_eq!(decoded[1], Err(Error::MissingData(TestType::TwoBytesLittleEndian)));
+ }
+
+ #[test]
fn encode_twobytes() {
let value = TestValues::TwoBytes(0x0A0B);
let mut buf = [0; 4];
diff --git a/rust/bt-common/src/generic_audio/metadata_ltv.rs b/rust/bt-common/src/generic_audio/metadata_ltv.rs
index 5e95215..75b4178 100644
--- a/rust/bt-common/src/generic_audio/metadata_ltv.rs
+++ b/rust/bt-common/src/generic_audio/metadata_ltv.rs
@@ -4,7 +4,7 @@
use crate::core::ltv::LtValue;
use crate::packet_encoding::Error as PacketError;
-use crate::{decodable_enum, CompanyId};
+use crate::{CompanyId, decodable_enum};
use crate::generic_audio::ContextType;
@@ -286,9 +286,9 @@
assert_eq!(buf, bytes);
// Decoding.
- let decoded = Metadata::decode(&buf).expect("should succeed");
- assert_eq!(decoded.0, test);
- assert_eq!(decoded.1, 4);
+ let (decoded, len) = Metadata::decode(&buf);
+ assert_eq!(decoded, Ok(test));
+ assert_eq!(len, 4);
}
#[test]
@@ -304,9 +304,9 @@
assert_eq!(buf, bytes);
// Decoding.
- let decoded = Metadata::decode(&buf).expect("should succeed");
- assert_eq!(decoded.0, test);
- assert_eq!(decoded.1, 4);
+ let (decoded, len) = Metadata::decode(&buf);
+ assert_eq!(decoded, Ok(test));
+ assert_eq!(len, 4);
}
#[test]
@@ -321,9 +321,9 @@
assert_eq!(buf, bytes);
// Decoding.
- let decoded = Metadata::decode(&buf).expect("should succeed");
- assert_eq!(decoded.0, test);
- assert_eq!(decoded.1, 3);
+ let (decoded, len) = Metadata::decode(&buf);
+ assert_eq!(decoded, Ok(test));
+ assert_eq!(len, 3);
}
#[test]
@@ -338,9 +338,9 @@
assert_eq!(buf, bytes);
// Decoding.
- let decoded = Metadata::decode(&buf).expect("should succeed");
- assert_eq!(decoded.0, test);
- assert_eq!(decoded.1, 5);
+ let (decoded, len) = Metadata::decode(&buf);
+ assert_eq!(decoded, Ok(test));
+ assert_eq!(len, 5);
}
#[test]
@@ -355,9 +355,9 @@
assert_eq!(buf, bytes);
// Decoding.
- let decoded = Metadata::decode(&buf).expect("should succeed");
- assert_eq!(decoded.0, test);
- assert_eq!(decoded.1, 4);
+ let (decoded, len) = Metadata::decode(&buf);
+ assert_eq!(decoded, Ok(test));
+ assert_eq!(len, 4);
}
#[test]
@@ -372,9 +372,9 @@
assert_eq!(buf, bytes);
// Decoding.
- let decoded = Metadata::decode(&buf).expect("should succeed");
- assert_eq!(decoded.0, test);
- assert_eq!(decoded.1, 3);
+ let (decoded, len) = Metadata::decode(&buf);
+ assert_eq!(decoded, Ok(test));
+ assert_eq!(len, 3);
}
#[test]
@@ -389,9 +389,9 @@
assert_eq!(buf, bytes);
// Decoding.
- let decoded = Metadata::decode(&buf).expect("should succeed");
- assert_eq!(decoded.0, test);
- assert_eq!(decoded.1, 6);
+ let (decoded, len) = Metadata::decode(&buf);
+ assert_eq!(decoded, Ok(test));
+ assert_eq!(len, 6);
}
#[test]
@@ -406,9 +406,9 @@
assert_eq!(buf, bytes);
// Decoding.
- let decoded = Metadata::decode(&buf).expect("should succeed");
- assert_eq!(decoded.0, test);
- assert_eq!(decoded.1, 3);
+ let (decoded, len) = Metadata::decode(&buf);
+ assert_eq!(decoded, Ok(test));
+ assert_eq!(len, 3);
}
#[test]
@@ -423,9 +423,9 @@
assert_eq!(buf, bytes);
// Decoding.
- let decoded = Metadata::decode(&buf).expect("should succeed");
- assert_eq!(decoded.0, test);
- assert_eq!(decoded.1, 2);
+ let (decoded, len) = Metadata::decode(&buf);
+ assert_eq!(decoded, Ok(test));
+ assert_eq!(len, 2);
}
#[test]
@@ -437,15 +437,15 @@
// Not enough length for Length and Type for decoding.
let buf = vec![0x03];
- let _ = Metadata::decode(&buf).expect_err("should fail");
+ let _ = Metadata::decode(&buf).0.expect_err("should fail");
// Not enough length for Value field for decoding.
let buf = vec![0x02, 0x01, 0x02];
- let _ = Metadata::decode(&buf).expect_err("should fail");
+ let _ = Metadata::decode(&buf).0.expect_err("should fail");
// Buffer length does not match Length value for decoding.
let buf = vec![0x03, 0x03, 0x61];
- let _ = Metadata::decode(&buf).expect_err("should fail");
+ let _ = Metadata::decode(&buf).0.expect_err("should fail");
}
#[test]
diff --git a/rust/bt-common/src/packet_encoding.rs b/rust/bt-common/src/packet_encoding.rs
index 9ff05d4..49f3e63 100644
--- a/rust/bt-common/src/packet_encoding.rs
+++ b/rust/bt-common/src/packet_encoding.rs
@@ -9,34 +9,43 @@
pub trait Decodable: ::core::marker::Sized {
type Error;
- /// Decodes into a new object with the number of bytes that were decoded, or
- /// returns an error.
- fn decode(buf: &[u8]) -> ::core::result::Result<(Self, usize), Self::Error>;
+ /// Decodes into a new object or an error, and the number of bytes that
+ /// the decoding consumed. Should attempt to consume the entire item from
+ /// the buffer in the case of an error. If the item end cannot be determined,
+ /// return an error and consume the entirety of the bufer (`buf.len()`)
+ fn decode(buf: &[u8]) -> (::core::result::Result<Self, Self::Error>, usize);
- /// Tries to decode a collection of a decodable item from a buffer.
- /// If any item fails to decode, fails with an Error and the previously
- /// decoded items.
- /// Will only decode up to max items. If None, will decode the entire
- /// buffer.
+ /// Tries to decode a collection of this object concatenated in a buffer.
+ /// Returns a vector of items (or errors) and the number of bytes consumed to
+ /// decode them.
+ /// Continues to decode items until the buffer is consumed or the max items.
+ /// If None, will decode the entire buffer.
fn decode_multiple(
buf: &[u8],
max: Option<usize>,
- ) -> ::core::result::Result<(Vec<Self>, usize), (Vec<Self>, usize, Self::Error)> {
+ ) -> (Vec<::core::result::Result<Self, Self::Error>>, usize) {
let mut idx = 0;
let mut result = Vec::new();
while idx < buf.len() && Some(result.len()) != max {
- match Self::decode(&buf[idx..]) {
- Err(e) => return Err((result, idx, e)),
- Ok((item, consumed)) => {
- result.push(item);
- idx += consumed;
- }
- }
+ let (one_result, consumed) = Self::decode(&buf[idx..]);
+ result.push(one_result);
+ idx += consumed;
}
- Ok((result, idx))
+ (result, idx)
}
}
+/// A decodable type that has an exact size in bytes.
+pub trait FixedSizeDecodable: ::core::marker::Sized {
+ type Error;
+ const WRONG_SIZE_ERROR: Self::Error;
+ const BYTE_SIZE: usize;
+
+ /// Decodes the value. This function assumes that buf is of at least
+ /// BYTE_SIZE, and assumes that BYTE_SIZE bytes are consumed to decode.
+ fn decode_checked(buf: &[u8]) -> core::result::Result<Self, Self::Error>;
+}
+
/// An encodable type can write itself into a byte buffer.
pub trait Encodable {
type Error;
diff --git a/rust/bt-gatt/src/server.rs b/rust/bt-gatt/src/server.rs
index 8a6f3b3..28efd8e 100644
--- a/rust/bt-gatt/src/server.rs
+++ b/rust/bt-gatt/src/server.rs
@@ -13,7 +13,7 @@
pub struct ServiceId(u64);
impl ServiceId {
- pub fn new(id: u64) -> Self {
+ pub const fn new(id: u64) -> Self {
Self(id)
}
}
diff --git a/rust/bt-gatt/src/test_utils.rs b/rust/bt-gatt/src/test_utils.rs
index 873689f..46aa379 100644
--- a/rust/bt-gatt/src/test_utils.rs
+++ b/rust/bt-gatt/src/test_utils.rs
@@ -3,9 +3,9 @@
// found in the LICENSE file.
use bt_common::core::{Address, AddressType};
-use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
-use futures::future::{ready, Ready};
use futures::Stream;
+use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender, unbounded};
+use futures::future::{Ready, ready};
use parking_lot::Mutex;
use std::collections::HashMap;
use std::sync::Arc;
@@ -18,7 +18,7 @@
use crate::periodic_advertising::{PeriodicAdvertising, SyncReport};
use crate::pii::GetPeerAddr;
use crate::server::{self, LocalService, ReadResponder, ServiceDefinition, WriteResponder};
-use crate::{types::*, GattTypes, ServerTypes};
+use crate::{GattTypes, ServerTypes, types::*};
#[derive(Default)]
struct FakePeerServiceInner {
@@ -362,6 +362,7 @@
}
}
+#[derive(Debug)]
pub enum FakeServerEvent {
ReadResponded {
service_id: server::ServiceId,
diff --git a/rust/bt-gatt/src/types.rs b/rust/bt-gatt/src/types.rs
index aefbb78..c9b72d3 100644
--- a/rust/bt-gatt/src/types.rs
+++ b/rust/bt-gatt/src/types.rs
@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::collections::HashSet;
-
use bt_common::{PeerId, Uuid};
use thiserror::Error;
@@ -264,9 +262,12 @@
impl CharacteristicProperties {
pub fn is_disjoint(&self, other: &Self) -> bool {
- let this_properties: HashSet<_> = HashSet::from_iter(self.0.iter());
- let other_properties: HashSet<_> = HashSet::from_iter(other.0.iter());
- this_properties.is_disjoint(&other_properties)
+ for property in self.0.iter() {
+ if other.0.contains(&property) {
+ return false;
+ }
+ }
+ true
}
pub fn contains(&self, property: CharacteristicProperty) -> bool {
diff --git a/rust/bt-pacs/src/lib.rs b/rust/bt-pacs/src/lib.rs
index 3e34f35..412e574 100644
--- a/rust/bt-pacs/src/lib.rs
+++ b/rust/bt-pacs/src/lib.rs
@@ -37,19 +37,24 @@
impl Decodable for PacRecord {
type Error = bt_common::packet_encoding::Error;
- fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
let mut idx = 0;
- let (codec_id, consumed) = CodecId::decode(&buf[idx..])?;
- idx += consumed;
+ let codec_id = match CodecId::decode(&buf[idx..]) {
+ (Ok(codec_id), consumed) => {
+ idx += consumed;
+ codec_id
+ }
+ (Err(e), _) => return (Err(e), buf.len()),
+ };
let codec_specific_capabilites_length = buf[idx] as usize;
idx += 1;
if idx + codec_specific_capabilites_length > buf.len() {
- return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+ return (Err(bt_common::packet_encoding::Error::UnexpectedDataLength), buf.len());
}
let (results, consumed) =
CodecCapability::decode_all(&buf[idx..idx + codec_specific_capabilites_length]);
if consumed != codec_specific_capabilites_length {
- return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+ return (Err(bt_common::packet_encoding::Error::UnexpectedDataLength), buf.len());
}
let codec_specific_capabilities = results.into_iter().filter_map(Result::ok).collect();
idx += consumed;
@@ -57,16 +62,16 @@
let metadata_length = buf[idx] as usize;
idx += 1;
if idx + metadata_length > buf.len() {
- return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+ return (Err(bt_common::packet_encoding::Error::UnexpectedDataLength), buf.len());
}
let (results, consumed) = Metadata::decode_all(&buf[idx..idx + metadata_length]);
if consumed != metadata_length {
- return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+ return (Err(bt_common::packet_encoding::Error::UnexpectedDataLength), buf.len());
}
let metadata = results.into_iter().filter_map(Result::ok).collect();
idx += consumed;
- Ok((Self { codec_id, codec_specific_capabilities, metadata }, idx))
+ (Ok(Self { codec_id, codec_specific_capabilities, metadata }), idx)
}
}
@@ -124,8 +129,8 @@
let mut next_idx = 1;
let mut capabilities = Vec::with_capacity(num_of_pac_records);
for _ in 0..num_of_pac_records {
- let (cap, consumed) = PacRecord::decode(&value[next_idx..])?;
- capabilities.push(cap);
+ let (cap, consumed) = PacRecord::decode(&value[next_idx..]);
+ capabilities.push(cap?);
next_idx += consumed;
}
Ok(capabilities)
@@ -197,14 +202,14 @@
impl Decodable for AudioLocations {
type Error = bt_common::packet_encoding::Error;
- fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.len() != 4 {
- return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+ return (Err(bt_common::packet_encoding::Error::UnexpectedDataLength), buf.len());
}
let locations =
AudioLocation::from_bits(u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]))
.collect();
- Ok((AudioLocations { locations }, 4))
+ (Ok(AudioLocations { locations }), 4)
}
}
@@ -249,12 +254,12 @@
value: &[u8],
) -> Result<Self, bt_common::packet_encoding::Error> {
let handle = characteristic.handle;
- let (locations, _) = AudioLocations::decode(value)?;
+ let locations = AudioLocations::decode(value).0?;
Ok(Self { handle, locations })
}
fn update(&mut self, new_value: &[u8]) -> Result<&mut Self, bt_common::packet_encoding::Error> {
- self.locations = AudioLocations::decode(new_value)?.0;
+ self.locations = AudioLocations::decode(new_value).0?;
Ok(self)
}
}
@@ -282,12 +287,12 @@
value: &[u8],
) -> Result<Self, bt_common::packet_encoding::Error> {
let handle = characteristic.handle;
- let (locations, _) = AudioLocations::decode(value)?;
+ let locations = AudioLocations::decode(value).0?;
Ok(Self { handle, locations })
}
fn update(&mut self, new_value: &[u8]) -> Result<&mut Self, bt_common::packet_encoding::Error> {
- self.locations = AudioLocations::decode(new_value)?.0;
+ self.locations = AudioLocations::decode(new_value).0?;
Ok(self)
}
}
@@ -301,15 +306,15 @@
impl Decodable for AvailableContexts {
type Error = bt_common::packet_encoding::Error;
- fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> {
+ fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.len() < 2 {
- return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
+ return (Err(bt_common::packet_encoding::Error::UnexpectedDataLength), 2);
}
let encoded = u16::from_le_bytes([buf[0], buf[1]]);
if encoded == 0 {
- Ok((Self::NotAvailable, 2))
+ (Ok(Self::NotAvailable), 2)
} else {
- Ok((Self::Available(ContextType::from_bits(encoded).collect()), 2))
+ (Ok(Self::Available(ContextType::from_bits(encoded).collect())), 2)
}
}
}
@@ -373,8 +378,8 @@
if value.len() < 4 {
return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
}
- let sink = AvailableContexts::decode(&value[0..2])?.0;
- let source = AvailableContexts::decode(&value[2..4])?.0;
+ let sink = AvailableContexts::decode(&value[0..2]).0?;
+ let source = AvailableContexts::decode(&value[2..4]).0?;
Ok(Self { handle, sink, source })
}
@@ -385,8 +390,8 @@
if new_value.len() != 4 {
return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
}
- let sink = AvailableContexts::decode(&new_value[0..2])?.0;
- let source = AvailableContexts::decode(&new_value[2..4])?.0;
+ let sink = AvailableContexts::decode(&new_value[0..2]).0?;
+ let source = AvailableContexts::decode(&new_value[2..4]).0?;
self.sink = sink;
self.source = source;
Ok(self)