use crate::error::Result;
macro_rules! declare_operations {
(
$lt:lifetime;
$( $variant:ident = $oid:literal => $ty:ty ),+ $(,)?
) => {
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum AnyOperation<$lt> {
$(
#[allow(missing_docs)]
$variant($ty),
)+
Unknown {
op_id: u16,
body: &$lt [u8],
},
}
$(
impl<$lt> From<$ty> for AnyOperation<$lt> {
fn from(c: $ty) -> Self {
Self::$variant(c)
}
}
)+
impl<$lt> AnyOperation<$lt> {
pub const DISPATCHED_OP_IDS: &'static [u16] = &[$($oid),+];
#[must_use]
pub fn name(&self) -> &'static str {
match self {
$(
Self::$variant(_) =>
<$ty as crate::traits::OperationDef>::NAME,
)+
Self::Unknown { .. } => "UNKNOWN",
}
}
#[must_use]
pub fn op_id(&self) -> u16 {
match self {
$(
Self::$variant(_) =>
<$ty as crate::traits::OperationDef>::OP_ID,
)+
Self::Unknown { op_id, .. } => *op_id,
}
}
pub fn dispatch(op_id: u16, body: &$lt [u8]) -> Result<Self> {
use dvb_common::Parse;
match op_id {
$(
$oid => <$ty>::parse(body).map(Self::$variant),
)+
_ => Ok(Self::Unknown { op_id, 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())
}
}
}
}
};
}
declare_operations! {'a;
SpliceRequest = 0x0101 => crate::operations::splice_request::SpliceRequest,
SpliceNullRequest = 0x0102 => crate::operations::splice_null_request::SpliceNullRequest,
StartScheduleDownload = 0x0103 => crate::operations::start_schedule_download::StartScheduleDownload,
TimeSignalRequest = 0x0104 => crate::operations::time_signal_request::TimeSignalRequest,
TransmitSchedule = 0x0105 => crate::operations::transmit_schedule::TransmitSchedule,
ComponentModeDpi = 0x0106 => crate::operations::component_mode_dpi::ComponentModeDpi,
EncryptedDpi = 0x0107 => crate::operations::encrypted_dpi::EncryptedDpi,
InsertDescriptor = 0x0108 => crate::operations::insert_descriptor::InsertDescriptor<'a>,
InsertDtmfDescriptor = 0x0109 => crate::operations::insert_dtmf_descriptor::InsertDtmfDescriptor<'a>,
InsertAvailDescriptor = 0x010A => crate::operations::insert_avail_descriptor::InsertAvailDescriptor,
InsertSegmentationDescriptor = 0x010B => crate::operations::insert_segmentation_descriptor::InsertSegmentationDescriptor<'a>,
ProprietaryCommand = 0x010C => crate::operations::proprietary_command::ProprietaryCommand<'a>,
ScheduleComponentMode = 0x010D => crate::operations::schedule_component_mode::ScheduleComponentMode,
ScheduleDefinition = 0x010E => crate::operations::schedule_definition::ScheduleDefinition,
InsertTier = 0x010F => crate::operations::insert_tier::InsertTier,
InsertTimeDescriptor = 0x0110 => crate::operations::insert_time_descriptor::InsertTimeDescriptor,
InsertAudioDescriptor = 0x0111 => crate::operations::insert_audio_descriptor::InsertAudioDescriptor<'a>,
InsertAudioProvisioning = 0x0112 => crate::operations::insert_audio_provisioning::InsertAudioProvisioning<'a>,
InsertAlternateBreakDuration = 0x0113 => crate::operations::insert_alternate_break_duration::InsertAlternateBreakDuration<'a>,
DeleteControlWord = 0x0300 => crate::operations::control_word::DeleteControlWord,
UpdateControlWord = 0x0301 => crate::operations::control_word::UpdateControlWord,
InjectSectionData = 0x0100 => crate::operations::inject_section_data::InjectSectionData<'a>,
}
#[cfg(test)]
mod drift_tests {
use crate::traits::OperationDef;
#[test]
fn op_id_drift() {
assert_eq!(
0x0101,
crate::operations::splice_request::SpliceRequest::OP_ID
);
assert_eq!(
0x0102,
crate::operations::splice_null_request::SpliceNullRequest::OP_ID
);
assert_eq!(
0x0103,
crate::operations::start_schedule_download::StartScheduleDownload::OP_ID
);
assert_eq!(
0x0104,
crate::operations::time_signal_request::TimeSignalRequest::OP_ID
);
assert_eq!(
0x0105,
crate::operations::transmit_schedule::TransmitSchedule::OP_ID
);
assert_eq!(
0x0106,
crate::operations::component_mode_dpi::ComponentModeDpi::OP_ID
);
assert_eq!(
0x0107,
crate::operations::encrypted_dpi::EncryptedDpi::OP_ID
);
assert_eq!(
0x0108,
crate::operations::insert_descriptor::InsertDescriptor::OP_ID
);
assert_eq!(
0x0109,
crate::operations::insert_dtmf_descriptor::InsertDtmfDescriptor::OP_ID
);
assert_eq!(
0x010A,
crate::operations::insert_avail_descriptor::InsertAvailDescriptor::OP_ID
);
assert_eq!(
0x010B,
crate::operations::insert_segmentation_descriptor::InsertSegmentationDescriptor::OP_ID
);
assert_eq!(
0x010C,
crate::operations::proprietary_command::ProprietaryCommand::OP_ID
);
assert_eq!(
0x010D,
crate::operations::schedule_component_mode::ScheduleComponentMode::OP_ID
);
assert_eq!(
0x010E,
crate::operations::schedule_definition::ScheduleDefinition::OP_ID
);
assert_eq!(0x010F, crate::operations::insert_tier::InsertTier::OP_ID);
assert_eq!(
0x0110,
crate::operations::insert_time_descriptor::InsertTimeDescriptor::OP_ID
);
assert_eq!(
0x0111,
crate::operations::insert_audio_descriptor::InsertAudioDescriptor::OP_ID
);
assert_eq!(
0x0112,
crate::operations::insert_audio_provisioning::InsertAudioProvisioning::OP_ID
);
assert_eq!(
0x0113,
crate::operations::insert_alternate_break_duration::InsertAlternateBreakDuration::OP_ID
);
assert_eq!(
0x0300,
crate::operations::control_word::DeleteControlWord::OP_ID
);
assert_eq!(
0x0301,
crate::operations::control_word::UpdateControlWord::OP_ID
);
assert_eq!(
0x0100,
crate::operations::inject_section_data::InjectSectionData::OP_ID
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::operations::splice_request::{SpliceInsertType, SpliceRequest};
use alloc::vec;
use dvb_common::Serialize;
#[test]
fn unknown_op_id_round_trips_body() {
let body = [0xDE, 0xAD, 0xBE, 0xEF];
let op = AnyOperation::dispatch(0x1234, &body).unwrap();
assert!(matches!(op, AnyOperation::Unknown { op_id: 0x1234, .. }));
assert_eq!(op.body_len(), 4);
assert_eq!(op.op_id(), 0x1234);
assert_eq!(op.name(), "UNKNOWN");
let mut buf = vec![0u8; op.body_len()];
op.serialize_body_into(&mut buf).unwrap();
assert_eq!(buf, body);
}
#[test]
fn dispatch_splice_request() {
let body = SpliceRequest {
splice_insert_type: SpliceInsertType::SpliceStartNormal,
splice_event_id: 0x42,
unique_program_id: 1,
pre_roll_time: 5000,
break_duration: 300,
avail_num: 0,
avails_expected: 0,
auto_return_flag: 1,
not_an_entry_flag: 0,
}
.to_bytes();
let op = AnyOperation::dispatch(0x0101, &body).unwrap();
assert_eq!(op.name(), "SPLICE_REQUEST");
assert_eq!(op.op_id(), 0x0101);
}
#[test]
fn dispatched_op_ids_count() {
assert!(AnyOperation::DISPATCHED_OP_IDS.contains(&0x0100));
assert!(AnyOperation::DISPATCHED_OP_IDS.contains(&0x0101));
assert!(AnyOperation::DISPATCHED_OP_IDS.contains(&0x0301));
}
}