blob: 04ef0d0508eadc65a772048abdda448172b96ade [file]
// Copyright 2026 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use core::num::NonZeroU8;
use bt_common::packet_encoding::{Decodable, Encodable, Error as PacketError};
use bt_common::{decodable_enum, Uuid};
/// 16-bit UUID value for the Coordinated Set Identification Service and its
/// characteristics.
pub const COORDINATED_SET_IDENTIFICATION_SERVICE_UUID: Uuid = Uuid::from_u16(0x1846);
pub const SET_IDENTITY_RESOLVING_KEY_UUID: Uuid = Uuid::from_u16(0x2B84);
pub const COORDINATED_SET_SIZE_UUID: Uuid = Uuid::from_u16(0x2B85);
pub const SET_MEMBER_LOCK_UUID: Uuid = Uuid::from_u16(0x2B86);
pub const SET_MEMBER_RANK_UUID: Uuid = Uuid::from_u16(0x2B87);
// TODO(b/534436497): Add Coordinated Set Name characteristic (CSIS v1.1 Section
// 5.5).
// TODO(b/534436497): Add CsisApplicationError enum.
decodable_enum! {
/// The type of the Set Identity Resolving Key (SIRK).
/// See CSIS v1.1 Section 5.1.1, Table 5.2.
pub enum SirkType<u8, bt_common::packet_encoding::Error, OutOfRange> {
Encrypted = 0x00,
Plaintext = 0x01,
}
}
/// The Set Identity Resolving Key (SIRK) characteristic exposes the key
/// associated with the Coordinated Set (1 octet Type + 16 octets Value).
/// See CSIS v1.1 Section 5.1.
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct SetIdentityResolvingKey {
sirk_type: SirkType,
value: [u8; 16],
}
impl SetIdentityResolvingKey {
pub const BYTE_SIZE: usize = 17;
pub fn new(sirk_type: SirkType, value: [u8; 16]) -> Self {
Self { sirk_type, value }
}
pub fn sirk_type(&self) -> SirkType {
self.sirk_type
}
pub fn value(&self) -> &[u8; 16] {
&self.value
}
}
/// Omits the key.
impl std::fmt::Debug for SetIdentityResolvingKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SetIdentityResolvingKey")
.field("sirk_type", &self.sirk_type)
.finish_non_exhaustive()
}
}
/// Omits the key.
impl std::fmt::Display for SetIdentityResolvingKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?} SIRK", self.sirk_type)
}
}
impl Decodable for SetIdentityResolvingKey {
type Error = PacketError;
fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.len() < Self::BYTE_SIZE {
return (Err(PacketError::UnexpectedDataLength), buf.len());
}
let sirk_type = match SirkType::try_from(buf[0]) {
Ok(t) => t,
Err(e) => return (Err(e), Self::BYTE_SIZE),
};
let mut value = [0; 16];
value.copy_from_slice(&buf[1..17]);
(Ok(Self { sirk_type, value }), Self::BYTE_SIZE)
}
}
impl Encodable for SetIdentityResolvingKey {
type Error = PacketError;
fn encoded_len(&self) -> usize {
Self::BYTE_SIZE
}
fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> {
if buf.len() < Self::BYTE_SIZE {
return Err(PacketError::BufferTooSmall);
}
buf[0] = self.sirk_type.into();
buf[1..17].copy_from_slice(&self.value);
Ok(())
}
}
/// The Set Member Rank characteristic exposes a numeric value that is unique
/// within a Coordinated Set (0x01 to set size). See CSIS v1.1 Section 5.4.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SetMemberRank(NonZeroU8);
impl SetMemberRank {
pub const BYTE_SIZE: usize = 1;
pub fn new(rank: NonZeroU8) -> Self {
Self(rank)
}
pub fn value(&self) -> NonZeroU8 {
self.0
}
}
impl Decodable for SetMemberRank {
type Error = PacketError;
fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.is_empty() {
return (Err(PacketError::UnexpectedDataLength), 0);
}
let val = match NonZeroU8::new(buf[0]) {
Some(v) => v,
None => {
return (
Err(PacketError::InvalidParameter("SetMemberRank cannot be 0".to_string())),
1,
)
}
};
(Ok(Self(val)), Self::BYTE_SIZE)
}
}
impl Encodable for SetMemberRank {
type Error = PacketError;
fn encoded_len(&self) -> usize {
Self::BYTE_SIZE
}
fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> {
if buf.is_empty() {
return Err(PacketError::BufferTooSmall);
}
buf[0] = self.0.get();
Ok(())
}
}
/// The Coordinated Set Size characteristic exposes the number of devices
/// comprising the Coordinated Set (0x01 to 0xFF). See CSIS v1.1 Section 5.2.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CoordinatedSetSize(NonZeroU8);
impl CoordinatedSetSize {
pub const BYTE_SIZE: usize = 1;
pub fn new(size: NonZeroU8) -> Self {
Self(size)
}
pub fn value(&self) -> NonZeroU8 {
self.0
}
}
impl Decodable for CoordinatedSetSize {
type Error = PacketError;
fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.is_empty() {
return (Err(PacketError::UnexpectedDataLength), 0);
}
let val = match NonZeroU8::new(buf[0]) {
Some(v) => v,
None => {
return (
Err(PacketError::InvalidParameter(
"CoordinatedSetSize cannot be 0".to_string(),
)),
1,
);
}
};
(Ok(Self(val)), Self::BYTE_SIZE)
}
}
impl Encodable for CoordinatedSetSize {
type Error = PacketError;
fn encoded_len(&self) -> usize {
Self::BYTE_SIZE
}
fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> {
if buf.is_empty() {
return Err(PacketError::BufferTooSmall);
}
buf[0] = self.0.get();
Ok(())
}
}
decodable_enum! {
/// The Set Member Lock characteristic value. All other values are RFU.
/// See CSIS v1.1 Section 5.3, Table 5.4.
pub enum SetMemberLock<u8, bt_common::packet_encoding::Error, OutOfRange> {
Unlocked = 0x01,
Locked = 0x02,
}
}
impl SetMemberLock {
pub const BYTE_SIZE: usize = 1;
}
impl Decodable for SetMemberLock {
type Error = PacketError;
fn decode(buf: &[u8]) -> (core::result::Result<Self, Self::Error>, usize) {
if buf.is_empty() {
return (Err(PacketError::UnexpectedDataLength), 0);
}
let lock = match Self::try_from(buf[0]) {
Ok(val) => val,
Err(e) => return (Err(e), Self::BYTE_SIZE),
};
(Ok(lock), Self::BYTE_SIZE)
}
}
impl Encodable for SetMemberLock {
type Error = PacketError;
fn encoded_len(&self) -> usize {
Self::BYTE_SIZE
}
fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> {
if buf.is_empty() {
return Err(PacketError::BufferTooSmall);
}
buf[0] = (*self).into();
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
#[test]
fn sirk_decoding() {
let mut buf = [0; 17];
buf[0] = 0x01; // Plaintext
buf[1..17].copy_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
let (res, consumed) = SetIdentityResolvingKey::decode(&buf);
assert_eq!(consumed, 17);
let sirk = res.unwrap();
assert_eq!(sirk.sirk_type(), SirkType::Plaintext);
assert_eq!(sirk.value(), &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
}
#[test]
fn sirk_encoding() {
let sirk = SetIdentityResolvingKey::new(SirkType::Encrypted, [1; 16]);
let mut buf = [0; 17];
sirk.encode(&mut buf).unwrap();
assert_eq!(buf[0], 0x00);
assert_eq!(buf[1..17], [1; 16]);
}
#[test]
fn set_member_rank_decoding() {
let mut buf = [0; 1];
buf[0] = 0x05;
let (res, consumed) = SetMemberRank::decode(&buf);
assert_eq!(consumed, 1);
assert_eq!(res.unwrap().value().get(), 5);
buf[0] = 0x00; // Invalid
let (res, _) = SetMemberRank::decode(&buf);
assert_matches!(res, Err(PacketError::InvalidParameter(_)));
}
#[test]
fn set_member_rank_encoding() {
let rank = SetMemberRank::new(NonZeroU8::new(5).unwrap());
assert_eq!(rank.encoded_len(), 1);
let mut buf = [0; 1];
rank.encode(&mut buf).unwrap();
assert_eq!(buf[0], 0x05);
let mut empty_buf = [];
assert_matches!(rank.encode(&mut empty_buf), Err(PacketError::BufferTooSmall));
}
#[test]
fn coordinated_set_size_decoding() {
let mut buf = [0; 1];
buf[0] = 0x02;
let (res, consumed) = CoordinatedSetSize::decode(&buf);
assert_eq!(consumed, 1);
assert_eq!(res.unwrap().value().get(), 2);
buf[0] = 0x00; // Invalid
let (res, _) = CoordinatedSetSize::decode(&buf);
assert_matches!(res, Err(PacketError::InvalidParameter(_)));
}
#[test]
fn coordinated_set_size_encoding() {
let size = CoordinatedSetSize::new(NonZeroU8::new(2).unwrap());
assert_eq!(size.encoded_len(), 1);
let mut buf = [0; 1];
size.encode(&mut buf).unwrap();
assert_eq!(buf[0], 0x02);
let mut empty_buf = [];
assert_matches!(size.encode(&mut empty_buf), Err(PacketError::BufferTooSmall));
}
#[test]
fn set_member_lock_decoding() {
let (res, consumed) = SetMemberLock::decode(&[0x01]);
assert_eq!(consumed, 1);
assert_eq!(res.unwrap(), SetMemberLock::Unlocked);
let (res, consumed) = SetMemberLock::decode(&[0x02]);
assert_eq!(consumed, 1);
assert_eq!(res.unwrap(), SetMemberLock::Locked);
// 0x00 and anything above 0x02 are RFU per CSIS v1.1 Section 5.3.
let (res, _) = SetMemberLock::decode(&[0x00]);
assert_matches!(res, Err(_));
let (res, _) = SetMemberLock::decode(&[0x03]);
assert_matches!(res, Err(_));
let (res, consumed) = SetMemberLock::decode(&[]);
assert_eq!(consumed, 0);
assert_matches!(res, Err(PacketError::UnexpectedDataLength));
}
#[test]
fn set_member_lock_encoding() {
let lock = SetMemberLock::Locked;
assert_eq!(lock.encoded_len(), 1);
let mut buf = [0; 1];
lock.encode(&mut buf).unwrap();
assert_eq!(buf[0], 0x02);
SetMemberLock::Unlocked.encode(&mut buf).unwrap();
assert_eq!(buf[0], 0x01);
let mut empty_buf = [];
assert_matches!(lock.encode(&mut empty_buf), Err(PacketError::BufferTooSmall));
}
}