use crate::error::Result;
macro_rules! declare_commands {
(
$lt:lifetime;
$( $variant:ident = $ct:literal => $($path:ident)::+ $(<$plt:lifetime>)? ),+ $(,)?
) => {
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[non_exhaustive]
pub enum AnyCommand<$lt> {
$(
#[allow(missing_docs)]
$variant($($path)::+ $(<$plt>)?),
)+
Unknown {
command_type: u8,
body: &$lt [u8],
},
}
$(
impl<$lt> From<$($path)::+ $(<$plt>)?> for AnyCommand<$lt> {
fn from(c: $($path)::+ $(<$plt>)?) -> Self {
Self::$variant(c)
}
}
)+
impl<$lt> AnyCommand<$lt> {
pub const DISPATCHED_TYPES: &'static [u8] = &[$($ct),+];
#[must_use]
pub fn name(&self) -> &'static str {
match self {
$(
Self::$variant(_) =>
<$($path)::+ as crate::traits::CommandDef>::NAME,
)+
Self::Unknown { .. } => "UNKNOWN",
}
}
#[must_use]
pub fn command_type(&self) -> u8 {
match self {
$(
Self::$variant(_) =>
<$($path)::+ as crate::traits::CommandDef>::COMMAND_TYPE,
)+
Self::Unknown { command_type, .. } => *command_type,
}
}
pub fn dispatch(command_type: u8, body: &$lt [u8]) -> Result<Self> {
use dvb_common::Parse;
match command_type {
$(
$ct => <$($path)::+>::parse(body).map(Self::$variant),
)+
_ => Ok(Self::Unknown { command_type, body }),
}
}
#[must_use]
pub fn body_len(&self) -> usize {
use dvb_common::Serialize;
match self {
$(
Self::$variant(c) => c.serialized_len(),
)+
Self::Unknown { body, .. } => body.len(),
}
}
pub fn serialize_body_into(&self, buf: &mut [u8]) -> Result<usize> {
use dvb_common::Serialize;
match self {
$(
Self::$variant(c) => c.serialize_into(buf),
)+
Self::Unknown { body, .. } => {
if buf.len() < body.len() {
return Err(crate::error::Error::OutputBufferTooSmall {
need: body.len(),
have: buf.len(),
});
}
buf[..body.len()].copy_from_slice(body);
Ok(body.len())
}
}
}
}
#[cfg(test)]
mod macro_drift {
#[test]
fn command_type_literals_match_command_def() {
use crate::traits::CommandDef;
$(
assert_eq!(
$ct,
<$($path)::+ as CommandDef>::COMMAND_TYPE,
concat!("command_type literal drift for ", stringify!($variant)),
);
assert!(
!<$($path)::+ as CommandDef>::NAME.is_empty(),
concat!("empty NAME for ", stringify!($variant)),
);
)+
}
}
};
}
declare_commands! {'a;
SpliceNull = 0x00 => crate::commands::splice_null::SpliceNull,
SpliceSchedule = 0x04 => crate::commands::splice_schedule::SpliceSchedule,
SpliceInsert = 0x05 => crate::commands::splice_insert::SpliceInsert,
TimeSignal = 0x06 => crate::commands::time_signal::TimeSignal,
BandwidthReservation = 0x07 => crate::commands::bandwidth_reservation::BandwidthReservation,
PrivateCommand = 0xFF => crate::commands::private_command::PrivateCommand<'a>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unknown_command_type_round_trips_body() {
let body = [0xDE, 0xAD, 0xBE, 0xEF];
let cmd = AnyCommand::dispatch(0x03, &body).unwrap();
assert!(matches!(
cmd,
AnyCommand::Unknown {
command_type: 0x03,
..
}
));
assert_eq!(cmd.body_len(), 4);
assert_eq!(cmd.command_type(), 0x03);
assert_eq!(cmd.name(), "UNKNOWN");
let mut buf = vec![0u8; cmd.body_len()];
cmd.serialize_body_into(&mut buf).unwrap();
assert_eq!(buf, body);
}
#[test]
fn dispatch_splice_null() {
let cmd = AnyCommand::dispatch(0x00, &[]).unwrap();
assert_eq!(cmd.name(), "SPLICE_NULL");
assert_eq!(cmd.command_type(), 0x00);
assert_eq!(cmd.body_len(), 0);
}
}