blob: a96ee7d10ab06bf2abea70b578ba25801f79c65a [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 SetMemberLock enum and 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(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SetIdentityResolvingKey {
pub sirk_type: SirkType,
pub value: [u8; 16],
}
impl SetIdentityResolvingKey {
pub const BYTE_SIZE: usize = 17;
}
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(pub NonZeroU8);
impl SetMemberRank {
pub const BYTE_SIZE: usize = 1;
}
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(pub NonZeroU8);
impl CoordinatedSetSize {
pub const BYTE_SIZE: usize = 1;
}
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(())
}
}
#[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 { sirk_type: SirkType::Encrypted, value: [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().0.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(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().0.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(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));
}
}