blob: ca765f509c19e9b162a04884794ffaa0d2128e59 [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 bitflags::bitflags;
use bt_common::packet_encoding::{Decodable, Encodable, Error as PacketError};
use bt_common::{decodable_enum, Uuid};
/// 16-bit UUID for the Media Control Service (MCS).
/// Defined in MCS v1.0.1 Section 2
pub(crate) const MEDIA_CONTROL_SERVICE_UUID: Uuid = Uuid::from_u16(0x1848);
/// 16-bit UUID for the Generic Media Control Service (GMCS).
/// Defined in MCS v1.0.1 Section 2
pub(crate) const GENERIC_MEDIA_CONTROL_SERVICE_UUID: Uuid = Uuid::from_u16(0x1849);
// ============================================================================
// Characteristic UUIDs (Mandatory, defined in MCS v1.0.1 Section 3)
// ============================================================================
/// Media Player Name characteristic UUID.
pub(crate) const MEDIA_PLAYER_NAME_UUID: Uuid = Uuid::from_u16(0x2B93);
/// Track Changed characteristic UUID.
pub(crate) const TRACK_CHANGED_UUID: Uuid = Uuid::from_u16(0x2B96);
/// Track Title characteristic UUID.
pub(crate) const TRACK_TITLE_UUID: Uuid = Uuid::from_u16(0x2B97);
/// Track Duration characteristic UUID.
pub(crate) const TRACK_DURATION_UUID: Uuid = Uuid::from_u16(0x2B98);
/// Track Position characteristic UUID.
pub(crate) const TRACK_POSITION_UUID: Uuid = Uuid::from_u16(0x2B99);
/// Playback Speed characteristic UUID.
pub(crate) const PLAYBACK_SPEED_UUID: Uuid = Uuid::from_u16(0x2B9A);
/// Seeking Speed characteristic UUID.
pub(crate) const SEEKING_SPEED_UUID: Uuid = Uuid::from_u16(0x2B9B);
/// Playing Order characteristic UUID.
pub(crate) const PLAYING_ORDER_UUID: Uuid = Uuid::from_u16(0x2BA1);
/// Playing Orders Supported characteristic UUID.
pub(crate) const PLAYING_ORDERS_SUPPORTED_UUID: Uuid = Uuid::from_u16(0x2BA2);
/// Media State characteristic UUID.
pub(crate) const MEDIA_STATE_UUID: Uuid = Uuid::from_u16(0x2BA3);
/// Media Control Point characteristic UUID.
pub(crate) const MEDIA_CONTROL_POINT_UUID: Uuid = Uuid::from_u16(0x2BA4);
/// Media Control Point Opcodes Supported characteristic UUID.
pub(crate) const MEDIA_CONTROL_POINT_OPCODES_SUPPORTED_UUID: Uuid = Uuid::from_u16(0x2BA5);
/// Content Control ID (CCID) characteristic UUID.
pub(crate) const CONTENT_CONTROL_ID_UUID: Uuid = Uuid::from_u16(0x2BA8);
decodable_enum! {
/// State of the media player.
pub enum MediaState<u8, PacketError, OutOfRange> {
/// Media player is inactive.
Inactive = 0x00,
/// Media player is currently playing.
Playing = 0x01,
/// Media player is paused.
Paused = 0x02,
/// Media player is fast forwarding or rewinding.
Seeking = 0x03,
}
}
impl Encodable for MediaState {
type Error = PacketError;
fn encoded_len(&self) -> usize {
1
}
fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> {
if buf.is_empty() {
return Err(PacketError::BufferTooSmall);
}
buf[0] = (*self).into();
Ok(())
}
}
impl Decodable for MediaState {
type Error = PacketError;
fn decode(buf: &[u8]) -> (Result<Self, Self::Error>, usize) {
if buf.is_empty() {
return (Err(PacketError::UnexpectedDataLength), 0);
}
(Self::try_from(buf[0]), 1)
}
}
decodable_enum! {
/// Result code returned in a Media Control Point notification.
pub enum ControlPointResultCode<u8, PacketError, OutOfRange> {
/// The procedure completed successfully.
Success = 0x01,
/// The opcode is not supported by the media player.
OpcodeNotSupported = 0x02,
/// The media player is inactive and cannot perform the operation.
MediaPlayerInactive = 0x03,
/// The command cannot be completed.
CommandCannotBeCompleted = 0x04,
}
}
impl Encodable for ControlPointResultCode {
type Error = PacketError;
fn encoded_len(&self) -> usize {
1
}
fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> {
if buf.is_empty() {
return Err(PacketError::BufferTooSmall);
}
buf[0] = (*self).into();
Ok(())
}
}
impl Decodable for ControlPointResultCode {
type Error = PacketError;
fn decode(buf: &[u8]) -> (Result<Self, Self::Error>, usize) {
if buf.is_empty() {
return (Err(PacketError::UnexpectedDataLength), 0);
}
(Self::try_from(buf[0]), 1)
}
}
decodable_enum! {
/// Playing order mode for media playback.
pub enum PlayingOrder<u8, PacketError, OutOfRange> {
/// Play a single track once, then stop.
SingleOnce = 0x01,
/// Play a single track repeatedly.
SingleRepeat = 0x02,
/// Play all tracks in normal sequence once, then stop.
Normal = 0x03,
/// Play all tracks in normal sequence repeatedly.
NormalRepeat = 0x04,
/// Play all tracks in shuffled order once, then stop.
ShuffleOnce = 0x05,
/// Play all tracks in shuffled order repeatedly.
ShuffleRepeat = 0x06,
}
}
impl Encodable for PlayingOrder {
type Error = PacketError;
fn encoded_len(&self) -> usize {
1
}
fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> {
if buf.is_empty() {
return Err(PacketError::BufferTooSmall);
}
buf[0] = (*self).into();
Ok(())
}
}
impl Decodable for PlayingOrder {
type Error = PacketError;
fn decode(buf: &[u8]) -> (Result<Self, Self::Error>, usize) {
if buf.is_empty() {
return (Err(PacketError::UnexpectedDataLength), 0);
}
(Self::try_from(buf[0]), 1)
}
}
bitflags! {
/// Bitmask representing supported playing orders (MCS v1.0.1 Section 3.10).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct SupportedPlayingOrders: u16 {
const SINGLE_ONCE = 0x0001;
const SINGLE_REPEAT = 0x0002;
const NORMAL = 0x0004;
const NORMAL_REPEAT = 0x0008;
const SHUFFLE_ONCE = 0x0010;
const SHUFFLE_REPEAT = 0x0020;
}
}
impl From<PlayingOrder> for SupportedPlayingOrders {
fn from(order: PlayingOrder) -> Self {
match order {
PlayingOrder::SingleOnce => Self::SINGLE_ONCE,
PlayingOrder::SingleRepeat => Self::SINGLE_REPEAT,
PlayingOrder::Normal => Self::NORMAL,
PlayingOrder::NormalRepeat => Self::NORMAL_REPEAT,
PlayingOrder::ShuffleOnce => Self::SHUFFLE_ONCE,
PlayingOrder::ShuffleRepeat => Self::SHUFFLE_REPEAT,
}
}
}
impl Encodable for SupportedPlayingOrders {
type Error = PacketError;
fn encoded_len(&self) -> usize {
2
}
fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> {
if buf.len() < 2 {
return Err(PacketError::BufferTooSmall);
}
buf[..2].copy_from_slice(&self.bits().to_le_bytes());
Ok(())
}
}
impl Decodable for SupportedPlayingOrders {
type Error = PacketError;
fn decode(buf: &[u8]) -> (Result<Self, Self::Error>, usize) {
if buf.len() < 2 {
return (Err(PacketError::UnexpectedDataLength), 0);
}
let raw = u16::from_le_bytes([buf[0], buf[1]]);
(Ok(Self::from_bits_truncate(raw)), 2)
}
}
/// Media Control Point Opcode.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MediaControlOpcode {
/// Start playing current track.
Play,
/// Pause playback.
Pause,
/// Fast rewind.
FastRewind,
/// Fast forward.
FastForward,
/// Stop playback.
Stop,
/// Move playback position relative to current position in seconds.
MoveRelative(i32),
/// Jump to previous segment.
PreviousSegment,
/// Jump to next segment.
NextSegment,
/// Jump to first segment.
FirstSegment,
/// Jump to last segment.
LastSegment,
/// Jump to specified segment number.
GotoSegment(i32),
/// Jump to previous track.
PreviousTrack,
/// Jump to next track.
NextTrack,
/// Jump to first track.
FirstTrack,
/// Jump to last track.
LastTrack,
/// Jump to specified track number.
GotoTrack(i32),
/// Jump to previous group.
PreviousGroup,
/// Jump to next group.
NextGroup,
/// Jump to first group.
FirstGroup,
/// Jump to last group.
LastGroup,
/// Jump to specified group number.
GotoGroup(i32),
}
impl MediaControlOpcode {
/// Returns the raw 1-byte opcode value.
pub fn raw_opcode(&self) -> u8 {
match self {
Self::Play => 0x01,
Self::Pause => 0x02,
Self::FastRewind => 0x03,
Self::FastForward => 0x04,
Self::Stop => 0x05,
Self::MoveRelative(_) => 0x20,
Self::PreviousSegment => 0x21,
Self::NextSegment => 0x22,
Self::FirstSegment => 0x23,
Self::LastSegment => 0x24,
Self::GotoSegment(_) => 0x25,
Self::PreviousTrack => 0x30,
Self::NextTrack => 0x31,
Self::FirstTrack => 0x32,
Self::LastTrack => 0x33,
Self::GotoTrack(_) => 0x34,
Self::PreviousGroup => 0x40,
Self::NextGroup => 0x41,
Self::FirstGroup => 0x42,
Self::LastGroup => 0x43,
Self::GotoGroup(_) => 0x44,
}
}
}
impl Encodable for MediaControlOpcode {
type Error = PacketError;
fn encoded_len(&self) -> usize {
match self {
Self::MoveRelative(_)
| Self::GotoSegment(_)
| Self::GotoTrack(_)
| Self::GotoGroup(_) => 5,
_ => 1,
}
}
fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> {
if buf.len() < self.encoded_len() {
return Err(PacketError::BufferTooSmall);
}
buf[0] = self.raw_opcode();
match self {
Self::MoveRelative(param)
| Self::GotoSegment(param)
| Self::GotoTrack(param)
| Self::GotoGroup(param) => {
buf[1..5].copy_from_slice(&param.to_le_bytes());
}
_ => {}
}
Ok(())
}
}
impl Decodable for MediaControlOpcode {
type Error = PacketError;
fn decode(buf: &[u8]) -> (Result<Self, Self::Error>, usize) {
if buf.is_empty() {
return (Err(PacketError::UnexpectedDataLength), 0);
}
let opcode = buf[0];
match opcode {
0x01 => (Ok(Self::Play), 1),
0x02 => (Ok(Self::Pause), 1),
0x03 => (Ok(Self::FastRewind), 1),
0x04 => (Ok(Self::FastForward), 1),
0x05 => (Ok(Self::Stop), 1),
0x20 => {
if buf.len() < 5 {
(Err(PacketError::UnexpectedDataLength), buf.len())
} else {
let param = i32::from_le_bytes([buf[1], buf[2], buf[3], buf[4]]);
(Ok(Self::MoveRelative(param)), 5)
}
}
0x21 => (Ok(Self::PreviousSegment), 1),
0x22 => (Ok(Self::NextSegment), 1),
0x23 => (Ok(Self::FirstSegment), 1),
0x24 => (Ok(Self::LastSegment), 1),
0x25 => {
if buf.len() < 5 {
(Err(PacketError::UnexpectedDataLength), buf.len())
} else {
let param = i32::from_le_bytes([buf[1], buf[2], buf[3], buf[4]]);
(Ok(Self::GotoSegment(param)), 5)
}
}
0x30 => (Ok(Self::PreviousTrack), 1),
0x31 => (Ok(Self::NextTrack), 1),
0x32 => (Ok(Self::FirstTrack), 1),
0x33 => (Ok(Self::LastTrack), 1),
0x34 => {
if buf.len() < 5 {
(Err(PacketError::UnexpectedDataLength), buf.len())
} else {
let param = i32::from_le_bytes([buf[1], buf[2], buf[3], buf[4]]);
(Ok(Self::GotoTrack(param)), 5)
}
}
0x40 => (Ok(Self::PreviousGroup), 1),
0x41 => (Ok(Self::NextGroup), 1),
0x42 => (Ok(Self::FirstGroup), 1),
0x43 => (Ok(Self::LastGroup), 1),
0x44 => {
if buf.len() < 5 {
(Err(PacketError::UnexpectedDataLength), buf.len())
} else {
let param = i32::from_le_bytes([buf[1], buf[2], buf[3], buf[4]]);
(Ok(Self::GotoGroup(param)), 5)
}
}
_ => (Err(PacketError::OutOfRange), 1),
}
}
}
bitflags! {
/// Bitmask representing supported Media Control Point opcodes (MCS v1.0.1 Section 3.13).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct SupportedOpcodes: u32 {
const PLAY = 0x00000001;
const PAUSE = 0x00000002;
const FAST_REWIND = 0x00000004;
const FAST_FORWARD = 0x00000008;
const STOP = 0x00000010;
const MOVE_RELATIVE = 0x00000020;
const PREVIOUS_SEGMENT = 0x00000040;
const NEXT_SEGMENT = 0x00000080;
const FIRST_SEGMENT = 0x00000100;
const LAST_SEGMENT = 0x00000200;
const GOTO_SEGMENT = 0x00000400;
const PREVIOUS_TRACK = 0x00000800;
const NEXT_TRACK = 0x00001000;
const FIRST_TRACK = 0x00002000;
const LAST_TRACK = 0x00004000;
const GOTO_TRACK = 0x00008000;
const PREVIOUS_GROUP = 0x00010000;
const NEXT_GROUP = 0x00020000;
const FIRST_GROUP = 0x00040000;
const LAST_GROUP = 0x00080000;
const GOTO_GROUP = 0x00100000;
}
}
impl From<&MediaControlOpcode> for SupportedOpcodes {
fn from(opcode: &MediaControlOpcode) -> Self {
match opcode {
MediaControlOpcode::Play => Self::PLAY,
MediaControlOpcode::Pause => Self::PAUSE,
MediaControlOpcode::FastRewind => Self::FAST_REWIND,
MediaControlOpcode::FastForward => Self::FAST_FORWARD,
MediaControlOpcode::Stop => Self::STOP,
MediaControlOpcode::MoveRelative(_) => Self::MOVE_RELATIVE,
MediaControlOpcode::PreviousSegment => Self::PREVIOUS_SEGMENT,
MediaControlOpcode::NextSegment => Self::NEXT_SEGMENT,
MediaControlOpcode::FirstSegment => Self::FIRST_SEGMENT,
MediaControlOpcode::LastSegment => Self::LAST_SEGMENT,
MediaControlOpcode::GotoSegment(_) => Self::GOTO_SEGMENT,
MediaControlOpcode::PreviousTrack => Self::PREVIOUS_TRACK,
MediaControlOpcode::NextTrack => Self::NEXT_TRACK,
MediaControlOpcode::FirstTrack => Self::FIRST_TRACK,
MediaControlOpcode::LastTrack => Self::LAST_TRACK,
MediaControlOpcode::GotoTrack(_) => Self::GOTO_TRACK,
MediaControlOpcode::PreviousGroup => Self::PREVIOUS_GROUP,
MediaControlOpcode::NextGroup => Self::NEXT_GROUP,
MediaControlOpcode::FirstGroup => Self::FIRST_GROUP,
MediaControlOpcode::LastGroup => Self::LAST_GROUP,
MediaControlOpcode::GotoGroup(_) => Self::GOTO_GROUP,
}
}
}
impl From<MediaControlOpcode> for SupportedOpcodes {
fn from(opcode: MediaControlOpcode) -> Self {
Self::from(&opcode)
}
}
impl Encodable for SupportedOpcodes {
type Error = PacketError;
fn encoded_len(&self) -> usize {
4
}
fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> {
if buf.len() < 4 {
return Err(PacketError::BufferTooSmall);
}
buf[..4].copy_from_slice(&self.bits().to_le_bytes());
Ok(())
}
}
impl Decodable for SupportedOpcodes {
type Error = PacketError;
fn decode(buf: &[u8]) -> (Result<Self, Self::Error>, usize) {
if buf.len() < 4 {
return (Err(PacketError::UnexpectedDataLength), 0);
}
let raw = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
(Ok(Self::from_bits_truncate(raw)), 4)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_media_state_encode_decode() {
for &state in MediaState::VARIANTS {
let mut buf = [0u8; 1];
assert!(state.encode(&mut buf).is_ok());
let (decoded, consumed) = MediaState::decode(&buf);
assert_eq!(consumed, 1);
assert_eq!(decoded.unwrap(), state);
}
// Test out of range
let (err, consumed) = MediaState::decode(&[0xFF]);
assert_eq!(consumed, 1);
assert_eq!(err.unwrap_err(), PacketError::OutOfRange);
// Test empty buffer
let (err, consumed) = MediaState::decode(&[]);
assert_eq!(consumed, 0);
assert_eq!(err.unwrap_err(), PacketError::UnexpectedDataLength);
}
#[test]
fn test_control_point_result_code_encode_decode() {
for &code in ControlPointResultCode::VARIANTS {
let mut buf = [0u8; 1];
assert!(code.encode(&mut buf).is_ok());
let (decoded, consumed) = ControlPointResultCode::decode(&buf);
assert_eq!(consumed, 1);
assert_eq!(decoded.unwrap(), code);
}
// Test out of range
let (err, consumed) = ControlPointResultCode::decode(&[0x00]);
assert_eq!(consumed, 1);
assert_eq!(err.unwrap_err(), PacketError::OutOfRange);
}
#[test]
fn test_playing_order_and_supported_bitmask() {
for &order in PlayingOrder::VARIANTS {
let mut buf = [0u8; 1];
assert!(order.encode(&mut buf).is_ok());
let (decoded, consumed) = PlayingOrder::decode(&buf);
assert_eq!(consumed, 1);
assert_eq!(decoded.unwrap(), order);
}
let supported = SupportedPlayingOrders::SINGLE_ONCE
| SupportedPlayingOrders::NORMAL
| SupportedPlayingOrders::SHUFFLE_REPEAT;
assert!(supported.contains(PlayingOrder::SingleOnce.into()));
assert!(supported.contains(PlayingOrder::Normal.into()));
assert!(supported.contains(PlayingOrder::ShuffleRepeat.into()));
assert!(!supported.contains(PlayingOrder::SingleRepeat.into()));
assert!(!supported.contains(PlayingOrder::NormalRepeat.into()));
assert!(!supported.contains(PlayingOrder::ShuffleOnce.into()));
let mut buf = [0u8; 2];
assert!(supported.encode(&mut buf).is_ok());
let (decoded, consumed) = SupportedPlayingOrders::decode(&buf);
assert_eq!(consumed, 2);
assert_eq!(decoded.unwrap(), supported);
}
#[test]
fn test_media_control_opcode_all_non_parameterized() {
let opcodes = [
(MediaControlOpcode::Play, 0x01),
(MediaControlOpcode::Pause, 0x02),
(MediaControlOpcode::FastRewind, 0x03),
(MediaControlOpcode::FastForward, 0x04),
(MediaControlOpcode::Stop, 0x05),
(MediaControlOpcode::PreviousSegment, 0x21),
(MediaControlOpcode::NextSegment, 0x22),
(MediaControlOpcode::FirstSegment, 0x23),
(MediaControlOpcode::LastSegment, 0x24),
(MediaControlOpcode::PreviousTrack, 0x30),
(MediaControlOpcode::NextTrack, 0x31),
(MediaControlOpcode::FirstTrack, 0x32),
(MediaControlOpcode::LastTrack, 0x33),
(MediaControlOpcode::PreviousGroup, 0x40),
(MediaControlOpcode::NextGroup, 0x41),
(MediaControlOpcode::FirstGroup, 0x42),
(MediaControlOpcode::LastGroup, 0x43),
];
for (opcode, raw_byte) in opcodes {
assert_eq!(opcode.raw_opcode(), raw_byte);
assert_eq!(opcode.encoded_len(), 1);
let mut buf = [0u8; 1];
assert!(opcode.encode(&mut buf).is_ok());
assert_eq!(buf[0], raw_byte);
let (decoded, consumed) = MediaControlOpcode::decode(&buf);
assert_eq!(consumed, 1);
assert_eq!(decoded.unwrap(), opcode);
}
}
#[test]
fn test_media_control_opcode_parameterized() {
let test_cases = [
(MediaControlOpcode::MoveRelative(10), 0x20, 10i32),
(MediaControlOpcode::MoveRelative(-15), 0x20, -15i32),
(MediaControlOpcode::GotoSegment(3), 0x25, 3i32),
(MediaControlOpcode::GotoTrack(42), 0x34, 42i32),
(MediaControlOpcode::GotoGroup(1), 0x44, 1i32),
];
for (opcode, raw_byte, param) in test_cases {
assert_eq!(opcode.raw_opcode(), raw_byte);
assert_eq!(opcode.encoded_len(), 5);
let mut buf = [0u8; 5];
assert!(opcode.encode(&mut buf).is_ok());
assert_eq!(buf[0], raw_byte);
assert_eq!(&buf[1..5], &param.to_le_bytes());
let (decoded, consumed) = MediaControlOpcode::decode(&buf);
assert_eq!(consumed, 5);
assert_eq!(decoded.unwrap(), opcode);
}
}
#[test]
fn test_media_control_opcode_truncated_buffer() {
// Opcode 0x20 (MoveRelative) requires 5 bytes, only 3 provided
let truncated = [0x20, 0x01, 0x02];
let (err, consumed) = MediaControlOpcode::decode(&truncated);
assert_eq!(consumed, 3);
assert_eq!(err.unwrap_err(), PacketError::UnexpectedDataLength);
}
#[test]
fn test_supported_opcodes_bitmask() {
let supported = SupportedOpcodes::PLAY
| SupportedOpcodes::PAUSE
| SupportedOpcodes::MOVE_RELATIVE
| SupportedOpcodes::NEXT_TRACK;
assert!(supported.contains(MediaControlOpcode::Play.into()));
assert!(supported.contains(MediaControlOpcode::Pause.into()));
assert!(supported.contains(MediaControlOpcode::MoveRelative(30).into()));
assert!(supported.contains(MediaControlOpcode::NextTrack.into()));
assert!(!supported.contains(MediaControlOpcode::PreviousTrack.into()));
assert!(!supported.contains(MediaControlOpcode::FastForward.into()));
assert!(!supported.contains(MediaControlOpcode::GotoTrack(1).into()));
let mut buf = [0u8; 4];
assert!(supported.encode(&mut buf).is_ok());
let (decoded, consumed) = SupportedOpcodes::decode(&buf);
assert_eq!(consumed, 4);
assert_eq!(decoded.unwrap(), supported);
}
}