blob: cd49de07ec1c867d399605195d2b04625331b73d [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(0x2BBA);
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 (MCS v1.0.1 Section 3.15, Table 3.3).
pub enum PlayingOrder<u8, PacketError, OutOfRange> {
/// A single track is played once; there is no next track.
SingleOnce = 0x01,
/// A single track is played repeatedly; the next track is the current track.
SingleRepeat = 0x02,
/// The tracks within a group are played once in track order.
InOrderOnce = 0x03,
/// The tracks within a group are played in track order repeatedly.
InOrderRepeat = 0x04,
/// The tracks within a group are played once only from the oldest first.
OldestOnce = 0x05,
/// The tracks within a group are played from the oldest first repeatedly.
OldestRepeat = 0x06,
/// The tracks within a group are played once only from the newest first.
NewestOnce = 0x07,
/// The tracks within a group are played from the newest first repeatedly.
NewestRepeat = 0x08,
/// The tracks within a group are played in random order once.
ShuffleOnce = 0x09,
/// The tracks within a group are played in random order repeatedly.
ShuffleRepeat = 0x0A,
}
}
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.16, Table 3.4).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SupportedPlayingOrders: u16 {
/// A single track is played once; there is no next track.
const SINGLE_ONCE = 0x0001;
/// A single track is played repeatedly; the next track is the current track.
const SINGLE_REPEAT = 0x0002;
/// The tracks within a group are played once in track order.
const IN_ORDER_ONCE = 0x0004;
/// The tracks within a group are played in track order repeatedly.
const IN_ORDER_REPEAT = 0x0008;
/// The tracks within a group are played once only from the oldest first.
const OLDEST_ONCE = 0x0010;
/// The tracks within a group are played from the oldest first repeatedly.
const OLDEST_REPEAT = 0x0020;
/// The tracks within a group are played once only from the newest first.
const NEWEST_ONCE = 0x0040;
/// The tracks within a group are played from the newest first repeatedly.
const NEWEST_REPEAT = 0x0080;
/// The tracks within a group are played in random order once.
const SHUFFLE_ONCE = 0x0100;
/// The tracks within a group are played in random order repeatedly.
const SHUFFLE_REPEAT = 0x0200;
}
}
impl From<PlayingOrder> for SupportedPlayingOrders {
fn from(order: PlayingOrder) -> Self {
match order {
PlayingOrder::SingleOnce => Self::SINGLE_ONCE,
PlayingOrder::SingleRepeat => Self::SINGLE_REPEAT,
PlayingOrder::InOrderOnce => Self::IN_ORDER_ONCE,
PlayingOrder::InOrderRepeat => Self::IN_ORDER_REPEAT,
PlayingOrder::OldestOnce => Self::OLDEST_ONCE,
PlayingOrder::OldestRepeat => Self::OLDEST_REPEAT,
PlayingOrder::NewestOnce => Self::NEWEST_ONCE,
PlayingOrder::NewestRepeat => Self::NEWEST_REPEAT,
PlayingOrder::ShuffleOnce => Self::SHUFFLE_ONCE,
PlayingOrder::ShuffleRepeat => Self::SHUFFLE_REPEAT,
}
}
}
impl From<&PlayingOrder> for SupportedPlayingOrders {
fn from(order: &PlayingOrder) -> Self {
Self::from(*order)
}
}
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 (MCS v1.0.1 Section 3.18, Table 3.6).
#[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 by offset.
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 defined in MCS v1.0.1 Table 3.6.
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(_) => 0x10,
Self::PreviousSegment => 0x20,
Self::NextSegment => 0x21,
Self::FirstSegment => 0x22,
Self::LastSegment => 0x23,
Self::GotoSegment(_) => 0x24,
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),
0x10 => {
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)
}
}
0x20 => (Ok(Self::PreviousSegment), 1),
0x21 => (Ok(Self::NextSegment), 1),
0x22 => (Ok(Self::FirstSegment), 1),
0x23 => (Ok(Self::LastSegment), 1),
0x24 => {
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.19, Table 3.10).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
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 media_state_encode_decode_success() {
let states = [
(MediaState::Inactive, 0x00),
(MediaState::Playing, 0x01),
(MediaState::Paused, 0x02),
(MediaState::Seeking, 0x03),
];
for (state, raw_byte) in states {
assert_eq!(u8::from(state), raw_byte);
assert_eq!(state.encoded_len(), 1);
let mut buf = [0u8; 1];
assert!(state.encode(&mut buf).is_ok());
assert_eq!(buf[0], raw_byte);
let (decoded, consumed) = MediaState::decode(&buf);
assert_eq!(consumed, 1);
assert_eq!(decoded.unwrap(), state);
}
}
#[test]
fn media_state_decode_error() {
// Test out of range
for &invalid in &[0x04, 0x05, 0xFF] {
let (err, consumed) = MediaState::decode(&[invalid]);
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 buffer too small for encode
let mut empty_buf = [0u8; 0];
assert_eq!(
MediaState::Playing.encode(&mut empty_buf).unwrap_err(),
PacketError::BufferTooSmall
);
}
#[test]
fn control_point_result_code_encode_decode_success() {
let codes = [
(ControlPointResultCode::Success, 0x01),
(ControlPointResultCode::OpcodeNotSupported, 0x02),
(ControlPointResultCode::MediaPlayerInactive, 0x03),
(ControlPointResultCode::CommandCannotBeCompleted, 0x04),
];
for (code, raw_byte) in codes {
assert_eq!(u8::from(code), raw_byte);
assert_eq!(code.encoded_len(), 1);
let mut buf = [0u8; 1];
assert!(code.encode(&mut buf).is_ok());
assert_eq!(buf[0], raw_byte);
let (decoded, consumed) = ControlPointResultCode::decode(&buf);
assert_eq!(consumed, 1);
assert_eq!(decoded.unwrap(), code);
}
}
#[test]
fn control_point_result_code_decode_error() {
// Test out of range
for &invalid in &[0x00, 0x05, 0x06, 0xFF] {
let (err, consumed) = ControlPointResultCode::decode(&[invalid]);
assert_eq!(consumed, 1);
assert_eq!(err.unwrap_err(), PacketError::OutOfRange);
}
// Test empty buffer
let (err, consumed) = ControlPointResultCode::decode(&[]);
assert_eq!(consumed, 0);
assert_eq!(err.unwrap_err(), PacketError::UnexpectedDataLength);
// Test buffer too small for encode
let mut empty_buf = [0u8; 0];
assert_eq!(
ControlPointResultCode::Success.encode(&mut empty_buf).unwrap_err(),
PacketError::BufferTooSmall
);
}
#[test]
fn playing_order_encode_decode_success() {
let orders = [
(PlayingOrder::SingleOnce, 0x01, SupportedPlayingOrders::SINGLE_ONCE),
(PlayingOrder::SingleRepeat, 0x02, SupportedPlayingOrders::SINGLE_REPEAT),
(PlayingOrder::InOrderOnce, 0x03, SupportedPlayingOrders::IN_ORDER_ONCE),
(PlayingOrder::InOrderRepeat, 0x04, SupportedPlayingOrders::IN_ORDER_REPEAT),
(PlayingOrder::OldestOnce, 0x05, SupportedPlayingOrders::OLDEST_ONCE),
(PlayingOrder::OldestRepeat, 0x06, SupportedPlayingOrders::OLDEST_REPEAT),
(PlayingOrder::NewestOnce, 0x07, SupportedPlayingOrders::NEWEST_ONCE),
(PlayingOrder::NewestRepeat, 0x08, SupportedPlayingOrders::NEWEST_REPEAT),
(PlayingOrder::ShuffleOnce, 0x09, SupportedPlayingOrders::SHUFFLE_ONCE),
(PlayingOrder::ShuffleRepeat, 0x0A, SupportedPlayingOrders::SHUFFLE_REPEAT),
];
for (order, raw_byte, flag) in orders {
assert_eq!(u8::from(order), raw_byte);
assert_eq!(order.encoded_len(), 1);
let mut buf = [0u8; 1];
assert!(order.encode(&mut buf).is_ok());
assert_eq!(buf[0], raw_byte);
let (decoded, consumed) = PlayingOrder::decode(&buf);
assert_eq!(consumed, 1);
assert_eq!(decoded.unwrap(), order);
let converted: SupportedPlayingOrders = order.into();
assert_eq!(converted, flag);
let converted_ref: SupportedPlayingOrders = (&order).into();
assert_eq!(converted_ref, flag);
assert!(SupportedPlayingOrders::all().contains(order.into()));
}
}
#[test]
fn playing_order_decode_error() {
// Test out of range
for &invalid in &[0x00, 0x0B, 0x10, 0xFF] {
let (err, consumed) = PlayingOrder::decode(&[invalid]);
assert_eq!(consumed, 1);
assert_eq!(err.unwrap_err(), PacketError::OutOfRange);
}
// Test empty buffer
let (err, consumed) = PlayingOrder::decode(&[]);
assert_eq!(consumed, 0);
assert_eq!(err.unwrap_err(), PacketError::UnexpectedDataLength);
// Test buffer too small for encode
let mut empty_buf = [0u8; 0];
assert_eq!(
PlayingOrder::InOrderOnce.encode(&mut empty_buf).unwrap_err(),
PacketError::BufferTooSmall
);
}
#[test]
fn supported_playing_orders_bitmask_success() {
assert_eq!(SupportedPlayingOrders::all().bits(), 0x03FF);
let supported = SupportedPlayingOrders::SINGLE_ONCE
| SupportedPlayingOrders::IN_ORDER_ONCE
| SupportedPlayingOrders::OLDEST_ONCE
| SupportedPlayingOrders::NEWEST_REPEAT
| SupportedPlayingOrders::SHUFFLE_REPEAT;
assert!(supported.contains(PlayingOrder::SingleOnce.into()));
assert!(supported.contains(PlayingOrder::InOrderOnce.into()));
assert!(supported.contains(PlayingOrder::OldestOnce.into()));
assert!(supported.contains(PlayingOrder::NewestRepeat.into()));
assert!(supported.contains(PlayingOrder::ShuffleRepeat.into()));
assert!(!supported.contains(PlayingOrder::SingleRepeat.into()));
assert!(!supported.contains(PlayingOrder::InOrderRepeat.into()));
assert!(!supported.contains(PlayingOrder::OldestRepeat.into()));
assert!(!supported.contains(PlayingOrder::NewestOnce.into()));
assert!(!supported.contains(PlayingOrder::ShuffleOnce.into()));
// Encode and decode SupportedPlayingOrders
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 supported_playing_orders_error() {
let supported = SupportedPlayingOrders::SINGLE_ONCE;
// Buffer too small for SupportedPlayingOrders encode/decode
assert_eq!(supported.encode(&mut [0u8; 1]).unwrap_err(), PacketError::BufferTooSmall);
let (err, consumed) = SupportedPlayingOrders::decode(&[0x01]);
assert_eq!(consumed, 0);
assert_eq!(err.unwrap_err(), PacketError::UnexpectedDataLength);
}
#[test]
fn 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, 0x20),
(MediaControlOpcode::NextSegment, 0x21),
(MediaControlOpcode::FirstSegment, 0x22),
(MediaControlOpcode::LastSegment, 0x23),
(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);
// Buffer too small for encode
let mut empty = [0u8; 0];
assert_eq!(opcode.encode(&mut empty).unwrap_err(), PacketError::BufferTooSmall);
}
}
#[test]
fn media_control_opcode_parameterized() {
let test_cases = [
(MediaControlOpcode::MoveRelative(10), 0x10, 10i32),
(MediaControlOpcode::MoveRelative(-15), 0x10, -15i32),
(MediaControlOpcode::MoveRelative(0), 0x10, 0i32),
(MediaControlOpcode::GotoSegment(3), 0x24, 3i32),
(MediaControlOpcode::GotoSegment(-1), 0x24, -1i32),
(MediaControlOpcode::GotoSegment(0), 0x24, 0i32),
(MediaControlOpcode::GotoTrack(42), 0x34, 42i32),
(MediaControlOpcode::GotoTrack(-5), 0x34, -5i32),
(MediaControlOpcode::GotoTrack(0), 0x34, 0i32),
(MediaControlOpcode::GotoGroup(1), 0x44, 1i32),
(MediaControlOpcode::GotoGroup(-2), 0x44, -2i32),
(MediaControlOpcode::GotoGroup(0), 0x44, 0i32),
];
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);
// Buffer too small for encode
let mut small = [0u8; 4];
assert_eq!(opcode.encode(&mut small).unwrap_err(), PacketError::BufferTooSmall);
}
}
#[test]
fn media_control_opcode_truncated_and_invalid_buffers() {
// Opcode 0x10 (MoveRelative) requires 5 bytes
for len in 1..5 {
let buf = vec![0x10, 0x01, 0x02, 0x03];
let (err, consumed) = MediaControlOpcode::decode(&buf[..len]);
assert_eq!(consumed, len);
assert_eq!(err.unwrap_err(), PacketError::UnexpectedDataLength);
}
// Opcode 0x24 (GotoSegment) requires 5 bytes
let (err, consumed) = MediaControlOpcode::decode(&[0x24, 0x01]);
assert_eq!(consumed, 2);
assert_eq!(err.unwrap_err(), PacketError::UnexpectedDataLength);
// Opcode 0x34 (GotoTrack) requires 5 bytes
let (err, consumed) = MediaControlOpcode::decode(&[0x34, 0x01, 0x02, 0x03]);
assert_eq!(consumed, 4);
assert_eq!(err.unwrap_err(), PacketError::UnexpectedDataLength);
// Opcode 0x44 (GotoGroup) requires 5 bytes
let (err, consumed) = MediaControlOpcode::decode(&[0x44]);
assert_eq!(consumed, 1);
assert_eq!(err.unwrap_err(), PacketError::UnexpectedDataLength);
// Invalid / RFU opcodes
for &invalid in &[0x00, 0x06, 0x0F, 0x11, 0x1F, 0x25, 0x2F, 0x35, 0x3F, 0x45, 0xFF] {
let (err, consumed) = MediaControlOpcode::decode(&[invalid]);
assert_eq!(consumed, 1);
assert_eq!(err.unwrap_err(), PacketError::OutOfRange);
}
// Empty buffer
let (err, consumed) = MediaControlOpcode::decode(&[]);
assert_eq!(consumed, 0);
assert_eq!(err.unwrap_err(), PacketError::UnexpectedDataLength);
}
#[test]
fn supported_opcodes_bitmask_success() {
assert_eq!(SupportedOpcodes::all().bits(), 0x001F_FFFF);
let opcode_flags = [
(MediaControlOpcode::Play, SupportedOpcodes::PLAY, 0x00000001),
(MediaControlOpcode::Pause, SupportedOpcodes::PAUSE, 0x00000002),
(MediaControlOpcode::FastRewind, SupportedOpcodes::FAST_REWIND, 0x00000004),
(MediaControlOpcode::FastForward, SupportedOpcodes::FAST_FORWARD, 0x00000008),
(MediaControlOpcode::Stop, SupportedOpcodes::STOP, 0x00000010),
(MediaControlOpcode::MoveRelative(0), SupportedOpcodes::MOVE_RELATIVE, 0x00000020),
(MediaControlOpcode::PreviousSegment, SupportedOpcodes::PREVIOUS_SEGMENT, 0x00000040),
(MediaControlOpcode::NextSegment, SupportedOpcodes::NEXT_SEGMENT, 0x00000080),
(MediaControlOpcode::FirstSegment, SupportedOpcodes::FIRST_SEGMENT, 0x00000100),
(MediaControlOpcode::LastSegment, SupportedOpcodes::LAST_SEGMENT, 0x00000200),
(MediaControlOpcode::GotoSegment(0), SupportedOpcodes::GOTO_SEGMENT, 0x00000400),
(MediaControlOpcode::PreviousTrack, SupportedOpcodes::PREVIOUS_TRACK, 0x00000800),
(MediaControlOpcode::NextTrack, SupportedOpcodes::NEXT_TRACK, 0x00001000),
(MediaControlOpcode::FirstTrack, SupportedOpcodes::FIRST_TRACK, 0x00002000),
(MediaControlOpcode::LastTrack, SupportedOpcodes::LAST_TRACK, 0x00004000),
(MediaControlOpcode::GotoTrack(0), SupportedOpcodes::GOTO_TRACK, 0x00008000),
(MediaControlOpcode::PreviousGroup, SupportedOpcodes::PREVIOUS_GROUP, 0x00010000),
(MediaControlOpcode::NextGroup, SupportedOpcodes::NEXT_GROUP, 0x00020000),
(MediaControlOpcode::FirstGroup, SupportedOpcodes::FIRST_GROUP, 0x00040000),
(MediaControlOpcode::LastGroup, SupportedOpcodes::LAST_GROUP, 0x00080000),
(MediaControlOpcode::GotoGroup(0), SupportedOpcodes::GOTO_GROUP, 0x00100000),
];
for (opcode, flag, bits) in opcode_flags {
assert_eq!(flag.bits(), bits);
let converted: SupportedOpcodes = opcode.into();
assert_eq!(converted, flag);
let converted_ref: SupportedOpcodes = (&opcode).into();
assert_eq!(converted_ref, flag);
assert!(SupportedOpcodes::all().contains(flag));
}
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);
}
#[test]
fn supported_opcodes_error() {
let supported = SupportedOpcodes::PLAY;
// Buffer too small for SupportedOpcodes encode/decode
assert_eq!(supported.encode(&mut [0u8; 3]).unwrap_err(), PacketError::BufferTooSmall);
let (err, consumed) = SupportedOpcodes::decode(&[0x01, 0x02, 0x03]);
assert_eq!(consumed, 0);
assert_eq!(err.unwrap_err(), PacketError::UnexpectedDataLength);
}
}