use super::descriptor_body;
use crate::error::{Error, Result};
use dvb_common::{Parse, Serialize};
pub const TAG: u8 = 0x6E;
pub const HEADER_LEN: usize = 2;
pub const INDICATOR_LEN: usize = 2;
pub const TYPE_BYTE_LEN: usize = 1;
pub const REFERENCE_LEN: usize = 7;
const ANNOUNCEMENT_TYPE_MASK: u8 = 0xF0;
const ANNOUNCEMENT_TYPE_SHIFT: u8 = 4;
const RESERVED_BIT_MASK: u8 = 0x08;
const REFERENCE_TYPE_MASK: u8 = 0x07;
pub const ANNOUNCEMENT_TYPE_MAX: u8 = 0x0F;
pub const REFERENCE_TYPE_MAX: u8 = 0x07;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum AnnouncementType {
EmergencyAlarm,
RoadTrafficFlash,
PublicTransportFlash,
WarningMessage,
NewsFlash,
WeatherFlash,
EventAnnouncement,
PersonalCall,
Reserved(u8),
}
impl AnnouncementType {
#[must_use]
pub fn from_u8(v: u8) -> Self {
match v {
0x00 => Self::EmergencyAlarm,
0x01 => Self::RoadTrafficFlash,
0x02 => Self::PublicTransportFlash,
0x03 => Self::WarningMessage,
0x04 => Self::NewsFlash,
0x05 => Self::WeatherFlash,
0x06 => Self::EventAnnouncement,
0x07 => Self::PersonalCall,
v => Self::Reserved(v),
}
}
#[must_use]
pub fn to_u8(self) -> u8 {
match self {
Self::EmergencyAlarm => 0x00,
Self::RoadTrafficFlash => 0x01,
Self::PublicTransportFlash => 0x02,
Self::WarningMessage => 0x03,
Self::NewsFlash => 0x04,
Self::WeatherFlash => 0x05,
Self::EventAnnouncement => 0x06,
Self::PersonalCall => 0x07,
Self::Reserved(v) => v,
}
}
#[must_use]
pub fn name(self) -> &'static str {
match self {
Self::EmergencyAlarm => "Emergency alarm",
Self::RoadTrafficFlash => "Road Traffic flash",
Self::PublicTransportFlash => "Public Transport flash",
Self::WarningMessage => "Warning message",
Self::NewsFlash => "News flash",
Self::WeatherFlash => "Weather flash",
Self::EventAnnouncement => "Event announcement",
Self::PersonalCall => "Personal call",
Self::Reserved(_) => "reserved",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum ReferenceType {
UsualAudioStream,
SeparateAudioStream,
DifferentServiceSameTs,
DifferentServiceDifferentTs,
Reserved(u8),
}
impl ReferenceType {
#[must_use]
pub fn from_u8(v: u8) -> Self {
match v {
0x00 => Self::UsualAudioStream,
0x01 => Self::SeparateAudioStream,
0x02 => Self::DifferentServiceSameTs,
0x03 => Self::DifferentServiceDifferentTs,
v => Self::Reserved(v),
}
}
#[must_use]
pub fn to_u8(self) -> u8 {
match self {
Self::UsualAudioStream => 0x00,
Self::SeparateAudioStream => 0x01,
Self::DifferentServiceSameTs => 0x02,
Self::DifferentServiceDifferentTs => 0x03,
Self::Reserved(v) => v,
}
}
#[must_use]
pub fn name(self) -> &'static str {
match self {
Self::UsualAudioStream => "broadcast in the usual audio stream of the service",
Self::SeparateAudioStream => {
"broadcast in a separate audio stream that is part of the service"
}
Self::DifferentServiceSameTs => {
"broadcast by a different service within the same DVB transport stream"
}
Self::DifferentServiceDifferentTs => {
"broadcast by a different service within a different DVB transport stream"
}
Self::Reserved(_) => "reserved",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct AnnouncementReference {
pub original_network_id: u16,
pub transport_stream_id: u16,
pub service_id: u16,
pub component_tag: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct AnnouncementEntry {
pub announcement_type: AnnouncementType,
pub reference_type: ReferenceType,
pub reference: Option<AnnouncementReference>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct AnnouncementSupportDescriptor {
pub announcement_support_indicator: u16,
pub entries: Vec<AnnouncementEntry>,
}
#[inline]
fn reference_present(reference_type: u8) -> bool {
matches!(reference_type, 0x01..=0x03)
}
impl<'a> Parse<'a> for AnnouncementSupportDescriptor {
type Error = crate::error::Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = descriptor_body(
bytes,
TAG,
"AnnouncementSupportDescriptor",
"unexpected tag for announcement_support_descriptor",
)?;
if body.len() < INDICATOR_LEN {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "body too short (need announcement_support_indicator)",
});
}
let announcement_support_indicator = u16::from_be_bytes([body[0], body[1]]);
let mut entries = Vec::new();
let mut pos = INDICATOR_LEN;
while pos < body.len() {
let flags = body[pos];
pos += TYPE_BYTE_LEN;
let announcement_type = AnnouncementType::from_u8(
(flags & ANNOUNCEMENT_TYPE_MASK) >> ANNOUNCEMENT_TYPE_SHIFT,
);
let reference_type = ReferenceType::from_u8(flags & REFERENCE_TYPE_MASK);
let reference = if reference_present(reference_type.to_u8()) {
if pos + REFERENCE_LEN > body.len() {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "announcement reference block truncated",
});
}
let r = AnnouncementReference {
original_network_id: u16::from_be_bytes([body[pos], body[pos + 1]]),
transport_stream_id: u16::from_be_bytes([body[pos + 2], body[pos + 3]]),
service_id: u16::from_be_bytes([body[pos + 4], body[pos + 5]]),
component_tag: body[pos + 6],
};
pos += REFERENCE_LEN;
Some(r)
} else {
None
};
entries.push(AnnouncementEntry {
announcement_type,
reference_type,
reference,
});
}
Ok(Self {
announcement_support_indicator,
entries,
})
}
}
impl AnnouncementSupportDescriptor {
fn body_len(&self) -> usize {
INDICATOR_LEN
+ self
.entries
.iter()
.map(|e| {
TYPE_BYTE_LEN
+ if reference_present(e.reference_type.to_u8()) {
REFERENCE_LEN
} else {
0
}
})
.sum::<usize>()
}
}
impl Serialize for AnnouncementSupportDescriptor {
type Error = crate::error::Error;
fn serialized_len(&self) -> usize {
HEADER_LEN + self.body_len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
for e in &self.entries {
if e.announcement_type.to_u8() > ANNOUNCEMENT_TYPE_MAX {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "announcement_type exceeds 4 bits",
});
}
if e.reference_type.to_u8() > REFERENCE_TYPE_MAX {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "reference_type exceeds 3 bits",
});
}
if reference_present(e.reference_type.to_u8()) != e.reference.is_some() {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "reference presence does not match reference_type",
});
}
}
let body_len = self.body_len();
if body_len > u8::MAX as usize {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "announcement_support_descriptor body exceeds 255 bytes",
});
}
let len = self.serialized_len();
if buf.len() < len {
return Err(Error::OutputBufferTooSmall {
need: len,
have: buf.len(),
});
}
buf[0] = TAG;
buf[1] = body_len as u8;
buf[HEADER_LEN..HEADER_LEN + INDICATOR_LEN]
.copy_from_slice(&self.announcement_support_indicator.to_be_bytes());
let mut pos = HEADER_LEN + INDICATOR_LEN;
for e in &self.entries {
let flags = ((e.announcement_type.to_u8() << ANNOUNCEMENT_TYPE_SHIFT)
& ANNOUNCEMENT_TYPE_MASK)
| RESERVED_BIT_MASK
| (e.reference_type.to_u8() & REFERENCE_TYPE_MASK);
buf[pos] = flags;
pos += TYPE_BYTE_LEN;
if let Some(r) = &e.reference {
buf[pos..pos + 2].copy_from_slice(&r.original_network_id.to_be_bytes());
buf[pos + 2..pos + 4].copy_from_slice(&r.transport_stream_id.to_be_bytes());
buf[pos + 4..pos + 6].copy_from_slice(&r.service_id.to_be_bytes());
buf[pos + 6] = r.component_tag;
pos += REFERENCE_LEN;
}
}
Ok(len)
}
}
impl<'a> crate::traits::DescriptorDef<'a> for AnnouncementSupportDescriptor {
const TAG: u8 = TAG;
const NAME: &'static str = "ANNOUNCEMENT_SUPPORT";
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_entry_without_reference() {
let bytes = [TAG, 3, 0x00, 0x40, 0x68];
let d = AnnouncementSupportDescriptor::parse(&bytes).unwrap();
assert_eq!(d.announcement_support_indicator, 0x0040);
assert_eq!(d.entries.len(), 1);
assert_eq!(
d.entries[0].announcement_type,
AnnouncementType::EventAnnouncement
);
assert_eq!(d.entries[0].reference_type, ReferenceType::UsualAudioStream);
assert!(d.entries[0].reference.is_none());
}
#[test]
fn parse_entry_with_reference() {
let bytes = [
TAG, 10, 0x00, 0x01,
0x1A, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x09,
];
let d = AnnouncementSupportDescriptor::parse(&bytes).unwrap();
assert_eq!(d.entries.len(), 1);
assert_eq!(
d.entries[0].announcement_type,
AnnouncementType::RoadTrafficFlash
);
assert_eq!(
d.entries[0].reference_type,
ReferenceType::DifferentServiceSameTs
);
let r = d.entries[0].reference.unwrap();
assert_eq!(r.original_network_id, 0xAABB);
assert_eq!(r.transport_stream_id, 0xCCDD);
assert_eq!(r.service_id, 0xEEFF);
assert_eq!(r.component_tag, 0x09);
}
#[test]
fn parse_mixed_entries() {
let bytes = [
TAG, 11, 0x12, 0x34, 0x40, 0x53, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
];
let d = AnnouncementSupportDescriptor::parse(&bytes).unwrap();
assert_eq!(d.entries.len(), 2);
assert!(d.entries[0].reference.is_none());
assert_eq!(
d.entries[1].reference_type,
ReferenceType::DifferentServiceDifferentTs
);
assert!(d.entries[1].reference.is_some());
}
#[test]
fn parse_ignores_reserved_bit() {
let bytes = [TAG, 3, 0x00, 0x00, 0x60];
let d = AnnouncementSupportDescriptor::parse(&bytes).unwrap();
assert_eq!(
d.entries[0].announcement_type,
AnnouncementType::EventAnnouncement
);
assert_eq!(d.entries[0].reference_type, ReferenceType::UsualAudioStream);
}
#[test]
fn parse_rejects_wrong_tag() {
assert!(matches!(
AnnouncementSupportDescriptor::parse(&[0x6F, 2, 0, 0]).unwrap_err(),
Error::InvalidDescriptor { tag: 0x6F, .. }
));
}
#[test]
fn parse_rejects_body_too_short_for_indicator() {
assert!(matches!(
AnnouncementSupportDescriptor::parse(&[TAG, 1, 0]).unwrap_err(),
Error::InvalidDescriptor { tag: TAG, .. }
));
}
#[test]
fn parse_rejects_reference_truncated() {
let bytes = [TAG, 3, 0x00, 0x00, 0x09]; assert!(matches!(
AnnouncementSupportDescriptor::parse(&bytes).unwrap_err(),
Error::InvalidDescriptor { tag: TAG, .. }
));
}
#[test]
fn parse_rejects_buffer_shorter_than_length() {
let bytes = [TAG, 5, 0x00, 0x00];
assert!(matches!(
AnnouncementSupportDescriptor::parse(&bytes).unwrap_err(),
Error::BufferTooShort { .. }
));
}
#[test]
fn serialize_round_trip_mixed() {
let d = AnnouncementSupportDescriptor {
announcement_support_indicator: 0xBEEF,
entries: vec![
AnnouncementEntry {
announcement_type: AnnouncementType::NewsFlash,
reference_type: ReferenceType::UsualAudioStream,
reference: None,
},
AnnouncementEntry {
announcement_type: AnnouncementType::RoadTrafficFlash,
reference_type: ReferenceType::DifferentServiceSameTs,
reference: Some(AnnouncementReference {
original_network_id: 0xAABB,
transport_stream_id: 0xCCDD,
service_id: 0xEEFF,
component_tag: 0x09,
}),
},
],
};
let mut buf = vec![0u8; d.serialized_len()];
d.serialize_into(&mut buf).unwrap();
assert_eq!(AnnouncementSupportDescriptor::parse(&buf).unwrap(), d);
}
#[test]
fn serialize_rejects_too_small_buffer() {
let d = AnnouncementSupportDescriptor {
announcement_support_indicator: 0,
entries: vec![],
};
let mut buf = vec![0u8; 3];
assert!(matches!(
d.serialize_into(&mut buf).unwrap_err(),
Error::OutputBufferTooSmall { .. }
));
}
#[test]
fn serialize_rejects_over_range_announcement_type() {
let d = AnnouncementSupportDescriptor {
announcement_support_indicator: 0,
entries: vec![AnnouncementEntry {
announcement_type: AnnouncementType::Reserved(0x10), reference_type: ReferenceType::UsualAudioStream,
reference: None,
}],
};
let mut buf = vec![0u8; d.serialized_len()];
assert!(matches!(
d.serialize_into(&mut buf).unwrap_err(),
Error::InvalidDescriptor { tag: TAG, .. }
));
}
#[test]
fn serialize_rejects_reference_mismatch() {
let d = AnnouncementSupportDescriptor {
announcement_support_indicator: 0,
entries: vec![AnnouncementEntry {
announcement_type: AnnouncementType::Reserved(0),
reference_type: ReferenceType::DifferentServiceSameTs,
reference: None,
}],
};
let mut buf = vec![0u8; d.serialized_len()];
assert!(matches!(
d.serialize_into(&mut buf).unwrap_err(),
Error::InvalidDescriptor { tag: TAG, .. }
));
}
#[cfg(feature = "serde")]
#[test]
fn serde_round_trip() {
let d = AnnouncementSupportDescriptor {
announcement_support_indicator: 0xBEEF,
entries: vec![AnnouncementEntry {
announcement_type: AnnouncementType::RoadTrafficFlash,
reference_type: ReferenceType::DifferentServiceSameTs,
reference: Some(AnnouncementReference {
original_network_id: 0xAABB,
transport_stream_id: 0xCCDD,
service_id: 0xEEFF,
component_tag: 0x09,
}),
}],
};
let json = serde_json::to_string(&d).unwrap();
let _v: serde_json::Value = serde_json::from_str(&json).unwrap();
}
#[test]
fn announcement_type_full_range_round_trip() {
for b in 0..=0xFF_u8 {
let at = AnnouncementType::from_u8(b);
assert_eq!(at.to_u8(), b, "round-trip failed for byte 0x{b:02X}");
}
}
#[test]
fn reference_type_full_range_round_trip() {
for b in 0..=0xFF_u8 {
let rt = ReferenceType::from_u8(b);
assert_eq!(rt.to_u8(), b, "round-trip failed for byte 0x{b:02X}");
}
}
}