use crate::TsError;
#[derive(Debug, Clone)]
pub struct Descriptor<'a> {
pub tag: u8,
pub data: &'a [u8],
pub body: DescriptorBody<'a>,
}
#[derive(Debug, Clone)]
pub enum DescriptorBody<'a> {
VideoStream(VideoStreamDescriptor),
AudioStream(AudioStreamDescriptor),
Hierarchy(HierarchyDescriptor),
Registration {
format_identifier: [u8; 4],
additional_identification_info: &'a [u8],
},
DataStreamAlignment(DataStreamAlignmentDescriptor),
TargetBackgroundGrid(TargetBackgroundGridDescriptor),
VideoWindow(VideoWindowDescriptor),
Ca(CaDescriptor<'a>),
Iso639Language(Vec<Iso639Language>),
SystemClock(SystemClockDescriptor),
MultiplexBufferUtilization(MultiplexBufferUtilizationDescriptor),
Copyright(CopyrightDescriptor<'a>),
MaximumBitrate(MaximumBitrateDescriptor),
SmoothingBuffer(SmoothingBufferDescriptor),
Std(StdDescriptor),
Ibp(IbpDescriptor),
AvcVideo(AvcVideoDescriptor),
HevcVideo(HevcVideoDescriptor),
Service(ServiceDescriptor<'a>),
ShortEvent(ShortEventDescriptor<'a>),
Raw,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ServiceDescriptor<'a> {
pub service_type: u8,
pub service_provider_name: &'a [u8],
pub service_name: &'a [u8],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ShortEventDescriptor<'a> {
pub language_code: [u8; 3],
pub event_name: &'a [u8],
pub text: &'a [u8],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VideoStreamDescriptor {
pub multiple_frame_rate_flag: bool,
pub frame_rate_code: u8,
pub mpeg_1_only_flag: bool,
pub constrained_parameter_flag: bool,
pub still_picture_flag: bool,
pub profile_and_level_indication: Option<u8>,
pub chroma_format: Option<u8>,
pub frame_rate_extension_flag: Option<bool>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AudioStreamDescriptor {
pub free_format_flag: bool,
pub id: bool,
pub layer: u8,
pub variable_rate_audio_indicator: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HierarchyDescriptor {
pub hierarchy_type: HierarchyType,
pub hierarchy_layer_index: u8,
pub hierarchy_embedded_layer_index: u8,
pub hierarchy_channel: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HierarchyType {
Mpeg2SpatialScalability,
Mpeg2SnrScalability,
Mpeg2TemporalScalability,
Mpeg2DataPartitioning,
Mpeg2AudioExtension,
PrivateStream,
Mpeg2MultiviewProfile,
BaseLayer,
Reserved(u8),
Unknown(u8),
}
impl HierarchyType {
pub fn from_nibble(value: u8) -> Self {
match value & 0x0F {
1 => HierarchyType::Mpeg2SpatialScalability,
2 => HierarchyType::Mpeg2SnrScalability,
3 => HierarchyType::Mpeg2TemporalScalability,
4 => HierarchyType::Mpeg2DataPartitioning,
5 => HierarchyType::Mpeg2AudioExtension,
6 => HierarchyType::PrivateStream,
7 => HierarchyType::Mpeg2MultiviewProfile,
15 => HierarchyType::BaseLayer,
other => HierarchyType::Reserved(other),
}
}
pub fn as_nibble(self) -> u8 {
match self {
HierarchyType::Mpeg2SpatialScalability => 1,
HierarchyType::Mpeg2SnrScalability => 2,
HierarchyType::Mpeg2TemporalScalability => 3,
HierarchyType::Mpeg2DataPartitioning => 4,
HierarchyType::Mpeg2AudioExtension => 5,
HierarchyType::PrivateStream => 6,
HierarchyType::Mpeg2MultiviewProfile => 7,
HierarchyType::BaseLayer => 15,
HierarchyType::Reserved(v) => v & 0x0F,
HierarchyType::Unknown(v) => v,
}
}
pub fn is_base_layer(self) -> bool {
matches!(self, HierarchyType::BaseLayer)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CaDescriptor<'a> {
pub ca_system_id: u16,
pub ca_pid: u16,
pub private_data: &'a [u8],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Iso639Language {
pub language: [u8; 3],
pub audio_type: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AvcVideoDescriptor {
pub profile_idc: u8,
pub constraint_set_and_compat: u8,
pub level_idc: u8,
pub avc_still_present: bool,
pub avc_24_hour_picture_flag: bool,
pub frame_packing_sei_not_present_flag: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DataStreamAlignmentDescriptor {
pub alignment_type: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TargetBackgroundGridDescriptor {
pub horizontal_size: u16,
pub vertical_size: u16,
pub aspect_ratio_information: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VideoWindowDescriptor {
pub horizontal_offset: u16,
pub vertical_offset: u16,
pub window_priority: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SystemClockDescriptor {
pub external_clock_reference: bool,
pub clock_accuracy_integer: u8,
pub clock_accuracy_exponent: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CopyrightDescriptor<'a> {
pub copyright_identifier: u32,
pub additional_copyright_info: &'a [u8],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MaximumBitrateDescriptor {
pub maximum_bitrate: u32,
}
impl MaximumBitrateDescriptor {
pub fn bits_per_second(self) -> u64 {
(self.maximum_bitrate as u64) * 400
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SmoothingBufferDescriptor {
pub sb_leak_rate: u32,
pub sb_size: u32,
}
impl SmoothingBufferDescriptor {
pub fn leak_rate_bits_per_second(self) -> u64 {
(self.sb_leak_rate as u64) * 400
}
pub fn leak_rate_bytes_per_second(self) -> u64 {
(self.sb_leak_rate as u64) * 50
}
pub fn buffer_size_bytes(self) -> u64 {
self.sb_size as u64
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StdDescriptor {
pub leak_valid_flag: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MultiplexBufferUtilizationDescriptor {
pub bound_valid_flag: bool,
pub ltw_offset_lower_bound: u16,
pub ltw_offset_upper_bound: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IbpDescriptor {
pub closed_gop_flag: bool,
pub identical_gop_flag: bool,
pub max_gop_length: u16,
}
impl IbpDescriptor {
pub fn is_well_formed(self) -> bool {
self.max_gop_length != 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HevcVideoDescriptor {
pub profile_space: u8,
pub tier_flag: bool,
pub profile_idc: u8,
pub profile_compatibility_indication: u32,
pub progressive_source_flag: bool,
pub interlaced_source_flag: bool,
pub non_packed_constraint_flag: bool,
pub frame_only_constraint_flag: bool,
pub level_idc: u8,
pub temporal_layer_subset_flag: bool,
pub hevc_still_present_flag: bool,
pub hevc_24hr_picture_present_flag: bool,
pub temporal_id_min: Option<u8>,
pub temporal_id_max: Option<u8>,
}
pub fn iter_descriptors(block: &[u8]) -> DescriptorIter<'_> {
DescriptorIter { rest: block }
}
#[derive(Debug)]
pub struct DescriptorIter<'a> {
rest: &'a [u8],
}
impl<'a> Iterator for DescriptorIter<'a> {
type Item = Result<Descriptor<'a>, TsError>;
fn next(&mut self) -> Option<Self::Item> {
if self.rest.is_empty() {
return None;
}
if self.rest.len() < 2 {
let have = self.rest.len();
self.rest = &[][..];
return Some(Err(TsError::Truncated {
what: "descriptor header",
have,
need: 2,
}));
}
let tag = self.rest[0];
let len = self.rest[1] as usize;
if self.rest.len() < 2 + len {
let have = self.rest.len() - 2;
self.rest = &[][..];
return Some(Err(TsError::Truncated {
what: "descriptor body",
have,
need: len,
}));
}
let data = &self.rest[2..2 + len];
self.rest = &self.rest[2 + len..];
Some(Ok(Descriptor {
tag,
data,
body: decode_body(tag, data),
}))
}
}
pub fn parse_descriptors(block: &[u8]) -> Result<Vec<Descriptor<'_>>, TsError> {
iter_descriptors(block).collect()
}
fn decode_body<'a>(tag: u8, data: &'a [u8]) -> DescriptorBody<'a> {
match tag {
0x02 => decode_video_stream(data).unwrap_or(DescriptorBody::Raw),
0x03 => decode_audio_stream(data).unwrap_or(DescriptorBody::Raw),
0x04 => decode_hierarchy(data).unwrap_or(DescriptorBody::Raw),
0x05 => decode_registration(data).unwrap_or(DescriptorBody::Raw),
0x06 => decode_data_stream_alignment(data).unwrap_or(DescriptorBody::Raw),
0x07 => decode_target_background_grid(data).unwrap_or(DescriptorBody::Raw),
0x08 => decode_video_window(data).unwrap_or(DescriptorBody::Raw),
0x09 => decode_ca(data).unwrap_or(DescriptorBody::Raw),
0x0A => decode_iso639_language(data).unwrap_or(DescriptorBody::Raw),
0x0B => decode_system_clock(data).unwrap_or(DescriptorBody::Raw),
0x0C => decode_multiplex_buffer_utilization(data).unwrap_or(DescriptorBody::Raw),
0x0D => decode_copyright(data).unwrap_or(DescriptorBody::Raw),
0x0E => decode_maximum_bitrate(data).unwrap_or(DescriptorBody::Raw),
0x10 => decode_smoothing_buffer(data).unwrap_or(DescriptorBody::Raw),
0x11 => decode_std(data).unwrap_or(DescriptorBody::Raw),
0x12 => decode_ibp(data).unwrap_or(DescriptorBody::Raw),
0x28 => decode_avc_video(data).unwrap_or(DescriptorBody::Raw),
0x38 => decode_hevc_video(data).unwrap_or(DescriptorBody::Raw),
0x48 => decode_service(data).unwrap_or(DescriptorBody::Raw),
0x4D => decode_short_event(data).unwrap_or(DescriptorBody::Raw),
_ => DescriptorBody::Raw,
}
}
fn decode_service(data: &[u8]) -> Option<DescriptorBody<'_>> {
if data.len() < 2 {
return None;
}
let service_type = data[0];
let provider_len = data[1] as usize;
let provider_start = 2usize;
let provider_end = provider_start.checked_add(provider_len)?;
if provider_end >= data.len() {
return None;
}
let service_provider_name = &data[provider_start..provider_end];
let name_len = data[provider_end] as usize;
let name_start = provider_end + 1;
let name_end = name_start.checked_add(name_len)?;
if name_end > data.len() {
return None;
}
let service_name = &data[name_start..name_end];
Some(DescriptorBody::Service(ServiceDescriptor {
service_type,
service_provider_name,
service_name,
}))
}
fn decode_short_event(data: &[u8]) -> Option<DescriptorBody<'_>> {
if data.len() < 4 {
return None;
}
let language_code = [data[0], data[1], data[2]];
let name_len = data[3] as usize;
let name_start = 4usize;
let name_end = name_start.checked_add(name_len)?;
if name_end >= data.len() {
return None;
}
let event_name = &data[name_start..name_end];
let text_len = data[name_end] as usize;
let text_start = name_end + 1;
let text_end = text_start.checked_add(text_len)?;
if text_end > data.len() {
return None;
}
let text = &data[text_start..text_end];
Some(DescriptorBody::ShortEvent(ShortEventDescriptor {
language_code,
event_name,
text,
}))
}
fn decode_video_stream(data: &[u8]) -> Option<DescriptorBody<'_>> {
if data.is_empty() {
return None;
}
let b0 = data[0];
let multiple_frame_rate_flag = (b0 & 0b1000_0000) != 0;
let frame_rate_code = (b0 >> 3) & 0b0000_1111;
let mpeg_1_only_flag = (b0 & 0b0000_0100) != 0;
let constrained_parameter_flag = (b0 & 0b0000_0010) != 0;
let still_picture_flag = (b0 & 0b0000_0001) != 0;
let (profile_and_level_indication, chroma_format, frame_rate_extension_flag) =
if !mpeg_1_only_flag && data.len() >= 3 {
let pli = data[1];
let b2 = data[2];
let chroma = (b2 >> 6) & 0b11;
let fre = (b2 & 0b0010_0000) != 0;
(Some(pli), Some(chroma), Some(fre))
} else {
(None, None, None)
};
Some(DescriptorBody::VideoStream(VideoStreamDescriptor {
multiple_frame_rate_flag,
frame_rate_code,
mpeg_1_only_flag,
constrained_parameter_flag,
still_picture_flag,
profile_and_level_indication,
chroma_format,
frame_rate_extension_flag,
}))
}
fn decode_audio_stream(data: &[u8]) -> Option<DescriptorBody<'_>> {
if data.is_empty() {
return None;
}
let b0 = data[0];
let free_format_flag = (b0 & 0b1000_0000) != 0;
let id = (b0 & 0b0100_0000) != 0;
let layer = (b0 >> 4) & 0b0000_0011;
let variable_rate_audio_indicator = (b0 & 0b0000_1000) != 0;
Some(DescriptorBody::AudioStream(AudioStreamDescriptor {
free_format_flag,
id,
layer,
variable_rate_audio_indicator,
}))
}
fn decode_hierarchy(data: &[u8]) -> Option<DescriptorBody<'_>> {
if data.len() < 4 {
return None;
}
let hierarchy_type = HierarchyType::from_nibble(data[0]);
let hierarchy_layer_index = data[1] & 0b0011_1111;
let hierarchy_embedded_layer_index = data[2] & 0b0011_1111;
let hierarchy_channel = data[3] & 0b0011_1111;
Some(DescriptorBody::Hierarchy(HierarchyDescriptor {
hierarchy_type,
hierarchy_layer_index,
hierarchy_embedded_layer_index,
hierarchy_channel,
}))
}
fn decode_registration(data: &[u8]) -> Option<DescriptorBody<'_>> {
if data.len() < 4 {
return None;
}
let mut format_identifier = [0u8; 4];
format_identifier.copy_from_slice(&data[..4]);
Some(DescriptorBody::Registration {
format_identifier,
additional_identification_info: &data[4..],
})
}
fn decode_ca(data: &[u8]) -> Option<DescriptorBody<'_>> {
if data.len() < 4 {
return None;
}
let ca_system_id = u16::from_be_bytes([data[0], data[1]]);
let ca_pid = (((data[2] & 0b0001_1111) as u16) << 8) | (data[3] as u16);
Some(DescriptorBody::Ca(CaDescriptor {
ca_system_id,
ca_pid,
private_data: &data[4..],
}))
}
fn decode_iso639_language(data: &[u8]) -> Option<DescriptorBody<'_>> {
if data.len() % 4 != 0 {
return None;
}
let mut langs = Vec::with_capacity(data.len() / 4);
for chunk in data.chunks_exact(4) {
let mut language = [0u8; 3];
language.copy_from_slice(&chunk[..3]);
langs.push(Iso639Language {
language,
audio_type: chunk[3],
});
}
Some(DescriptorBody::Iso639Language(langs))
}
fn decode_data_stream_alignment(data: &[u8]) -> Option<DescriptorBody<'_>> {
if data.is_empty() {
return None;
}
Some(DescriptorBody::DataStreamAlignment(
DataStreamAlignmentDescriptor {
alignment_type: data[0],
},
))
}
fn decode_target_background_grid(data: &[u8]) -> Option<DescriptorBody<'_>> {
if data.len() < 4 {
return None;
}
let horizontal_size = ((data[0] as u16) << 6) | ((data[1] >> 2) as u16);
let vertical_size = (((data[1] & 0b0000_0011) as u16) << 12)
| ((data[2] as u16) << 4)
| ((data[3] >> 4) as u16);
let aspect_ratio_information = data[3] & 0b0000_1111;
Some(DescriptorBody::TargetBackgroundGrid(
TargetBackgroundGridDescriptor {
horizontal_size,
vertical_size,
aspect_ratio_information,
},
))
}
fn decode_video_window(data: &[u8]) -> Option<DescriptorBody<'_>> {
if data.len() < 4 {
return None;
}
let horizontal_offset = ((data[0] as u16) << 6) | ((data[1] >> 2) as u16);
let vertical_offset = (((data[1] & 0b0000_0011) as u16) << 12)
| ((data[2] as u16) << 4)
| ((data[3] >> 4) as u16);
let window_priority = data[3] & 0b0000_1111;
Some(DescriptorBody::VideoWindow(VideoWindowDescriptor {
horizontal_offset,
vertical_offset,
window_priority,
}))
}
fn decode_system_clock(data: &[u8]) -> Option<DescriptorBody<'_>> {
if data.len() < 2 {
return None;
}
let b0 = data[0];
let b1 = data[1];
let external_clock_reference = (b0 & 0b1000_0000) != 0;
let clock_accuracy_integer = b0 & 0b0011_1111;
let clock_accuracy_exponent = (b1 >> 5) & 0b0000_0111;
Some(DescriptorBody::SystemClock(SystemClockDescriptor {
external_clock_reference,
clock_accuracy_integer,
clock_accuracy_exponent,
}))
}
fn decode_multiplex_buffer_utilization(data: &[u8]) -> Option<DescriptorBody<'_>> {
if data.len() < 4 {
return None;
}
let bound_valid_flag = (data[0] & 0b1000_0000) != 0;
let ltw_offset_lower_bound = (((data[0] & 0b0111_1111) as u16) << 8) | (data[1] as u16);
let ltw_offset_upper_bound = (((data[2] & 0b0111_1111) as u16) << 8) | (data[3] as u16);
Some(DescriptorBody::MultiplexBufferUtilization(
MultiplexBufferUtilizationDescriptor {
bound_valid_flag,
ltw_offset_lower_bound,
ltw_offset_upper_bound,
},
))
}
fn decode_copyright(data: &[u8]) -> Option<DescriptorBody<'_>> {
if data.len() < 4 {
return None;
}
let copyright_identifier = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
Some(DescriptorBody::Copyright(CopyrightDescriptor {
copyright_identifier,
additional_copyright_info: &data[4..],
}))
}
fn decode_maximum_bitrate(data: &[u8]) -> Option<DescriptorBody<'_>> {
if data.len() < 3 {
return None;
}
let maximum_bitrate =
(((data[0] & 0b0011_1111) as u32) << 16) | ((data[1] as u32) << 8) | (data[2] as u32);
Some(DescriptorBody::MaximumBitrate(MaximumBitrateDescriptor {
maximum_bitrate,
}))
}
fn decode_smoothing_buffer(data: &[u8]) -> Option<DescriptorBody<'_>> {
if data.len() < 6 {
return None;
}
let sb_leak_rate =
(((data[0] & 0b0011_1111) as u32) << 16) | ((data[1] as u32) << 8) | (data[2] as u32);
let sb_size =
(((data[3] & 0b0011_1111) as u32) << 16) | ((data[4] as u32) << 8) | (data[5] as u32);
Some(DescriptorBody::SmoothingBuffer(SmoothingBufferDescriptor {
sb_leak_rate,
sb_size,
}))
}
fn decode_std(data: &[u8]) -> Option<DescriptorBody<'_>> {
if data.is_empty() {
return None;
}
Some(DescriptorBody::Std(StdDescriptor {
leak_valid_flag: (data[0] & 0b0000_0001) != 0,
}))
}
fn decode_ibp(data: &[u8]) -> Option<DescriptorBody<'_>> {
if data.len() < 2 {
return None;
}
let closed_gop_flag = (data[0] & 0b1000_0000) != 0;
let identical_gop_flag = (data[0] & 0b0100_0000) != 0;
let max_gop_length = (((data[0] & 0b0011_1111) as u16) << 8) | (data[1] as u16);
Some(DescriptorBody::Ibp(IbpDescriptor {
closed_gop_flag,
identical_gop_flag,
max_gop_length,
}))
}
fn decode_avc_video(data: &[u8]) -> Option<DescriptorBody<'_>> {
if data.len() < 4 {
return None;
}
let profile_idc = data[0];
let constraint_set_and_compat = data[1];
let level_idc = data[2];
let flags = data[3];
let avc_still_present = (flags & 0b1000_0000) != 0;
let avc_24_hour_picture_flag = (flags & 0b0100_0000) != 0;
let frame_packing_sei_not_present_flag = (flags & 0b0010_0000) != 0;
Some(DescriptorBody::AvcVideo(AvcVideoDescriptor {
profile_idc,
constraint_set_and_compat,
level_idc,
avc_still_present,
avc_24_hour_picture_flag,
frame_packing_sei_not_present_flag,
}))
}
fn decode_hevc_video(data: &[u8]) -> Option<DescriptorBody<'_>> {
if data.len() < 13 {
return None;
}
let b0 = data[0];
let profile_space = (b0 >> 6) & 0b11;
let tier_flag = (b0 & 0b0010_0000) != 0;
let profile_idc = b0 & 0b0001_1111;
let profile_compatibility_indication = u32::from_be_bytes([data[1], data[2], data[3], data[4]]);
let b5 = data[5];
let progressive_source_flag = (b5 & 0b1000_0000) != 0;
let interlaced_source_flag = (b5 & 0b0100_0000) != 0;
let non_packed_constraint_flag = (b5 & 0b0010_0000) != 0;
let frame_only_constraint_flag = (b5 & 0b0001_0000) != 0;
let level_idc = data[12];
let (temporal_layer_subset_flag, hevc_still_present_flag, hevc_24hr_picture_present_flag) =
if data.len() >= 14 {
let b13 = data[13];
(
(b13 & 0b1000_0000) != 0,
(b13 & 0b0100_0000) != 0,
(b13 & 0b0010_0000) != 0,
)
} else {
(false, false, false)
};
let (temporal_id_min, temporal_id_max) = if temporal_layer_subset_flag && data.len() >= 16 {
let lo = (data[14] >> 5) & 0b111;
let hi = (data[15] >> 5) & 0b111;
(Some(lo), Some(hi))
} else {
(None, None)
};
Some(DescriptorBody::HevcVideo(HevcVideoDescriptor {
profile_space,
tier_flag,
profile_idc,
profile_compatibility_indication,
progressive_source_flag,
interlaced_source_flag,
non_packed_constraint_flag,
frame_only_constraint_flag,
level_idc,
temporal_layer_subset_flag,
hevc_still_present_flag,
hevc_24hr_picture_present_flag,
temporal_id_min,
temporal_id_max,
}))
}
#[cfg(test)]
mod tests {
use super::*;
fn tlv(tag: u8, body: &[u8]) -> Vec<u8> {
let mut v = vec![tag, body.len() as u8];
v.extend_from_slice(body);
v
}
#[test]
fn iter_empty_block_yields_none() {
let mut it = iter_descriptors(&[]);
assert!(it.next().is_none());
}
#[test]
fn unknown_tag_passes_through_as_raw() {
let block = tlv(0x42, &[0xAA, 0xBB, 0xCC]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
assert_eq!(d.tag, 0x42);
assert_eq!(d.data, &[0xAA, 0xBB, 0xCC]);
assert!(matches!(d.body, DescriptorBody::Raw));
}
#[test]
fn two_descriptors_back_to_back() {
let mut block = Vec::new();
block.extend_from_slice(&tlv(0x05, b"HDMV"));
block.extend_from_slice(&tlv(0x09, &[0x09, 0x00, 0xE5, 0x00, 0xAB, 0xCD]));
let v: Vec<_> = iter_descriptors(&block).collect();
assert_eq!(v.len(), 2);
let d0 = v[0].as_ref().unwrap();
assert_eq!(d0.tag, 0x05);
match &d0.body {
DescriptorBody::Registration {
format_identifier,
additional_identification_info,
} => {
assert_eq!(format_identifier, b"HDMV");
assert!(additional_identification_info.is_empty());
}
other => panic!("expected Registration, got {other:?}"),
}
let d1 = v[1].as_ref().unwrap();
assert_eq!(d1.tag, 0x09);
match &d1.body {
DescriptorBody::Ca(ca) => {
assert_eq!(ca.ca_system_id, 0x0900);
assert_eq!(ca.ca_pid, 0x0500);
assert_eq!(ca.private_data, &[0xAB, 0xCD]);
}
other => panic!("expected Ca, got {other:?}"),
}
}
#[test]
fn truncated_header_surfaces_error_then_stops() {
let block = [0x05u8]; let mut it = iter_descriptors(&block);
let err = it.next().expect("one error").unwrap_err();
assert!(matches!(err, TsError::Truncated { .. }));
assert!(it.next().is_none());
}
#[test]
fn truncated_body_surfaces_error_then_stops() {
let mut block = vec![0x05u8, 0x08]; block.extend_from_slice(&[0xAA, 0xBB]); let mut it = iter_descriptors(&block);
let err = it.next().expect("one error").unwrap_err();
assert!(matches!(err, TsError::Truncated { .. }));
assert!(it.next().is_none());
}
#[test]
fn registration_descriptor_decodes_hdmv_with_trailer() {
let block = tlv(0x05, b"HDMV\x88\x99");
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::Registration {
format_identifier,
additional_identification_info,
} => {
assert_eq!(format_identifier, b"HDMV");
assert_eq!(additional_identification_info, &[0x88, 0x99]);
}
other => panic!("expected Registration, got {other:?}"),
}
}
#[test]
fn iso639_language_descriptor_two_entries() {
let body = [b'e', b'n', b'g', 0x00, b'j', b'p', b'n', 0x02];
let block = tlv(0x0A, &body);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::Iso639Language(langs) => {
assert_eq!(langs.len(), 2);
assert_eq!(&langs[0].language, b"eng");
assert_eq!(langs[0].audio_type, 0);
assert_eq!(&langs[1].language, b"jpn");
assert_eq!(langs[1].audio_type, 2);
}
other => panic!("expected Iso639Language, got {other:?}"),
}
}
#[test]
fn iso639_language_bad_length_falls_back_to_raw() {
let body = [b'e', b'n', b'g', 0x00, 0xFF];
let block = tlv(0x0A, &body);
let d = iter_descriptors(&block).next().unwrap().unwrap();
assert!(matches!(d.body, DescriptorBody::Raw));
assert_eq!(d.data, &body);
}
#[test]
fn video_stream_descriptor_mpeg2_three_bytes() {
let body = [4u8 << 3, 0x44u8, 0b01 << 6];
let block = tlv(0x02, &body);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::VideoStream(v) => {
assert!(!v.multiple_frame_rate_flag);
assert_eq!(v.frame_rate_code, 4);
assert!(!v.mpeg_1_only_flag);
assert_eq!(v.profile_and_level_indication, Some(0x44));
assert_eq!(v.chroma_format, Some(0b01));
assert_eq!(v.frame_rate_extension_flag, Some(false));
}
other => panic!("expected VideoStream, got {other:?}"),
}
}
#[test]
fn video_stream_descriptor_mpeg1_one_byte() {
let body = [(3 << 3) | 0b0000_0101];
let block = tlv(0x02, &body);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::VideoStream(v) => {
assert!(v.mpeg_1_only_flag);
assert_eq!(v.frame_rate_code, 3);
assert!(v.still_picture_flag);
assert!(v.profile_and_level_indication.is_none());
assert!(v.chroma_format.is_none());
assert!(v.frame_rate_extension_flag.is_none());
}
other => panic!("expected VideoStream, got {other:?}"),
}
}
#[test]
fn audio_stream_descriptor_layer_ii() {
let body = [(1u8 << 6) | (0b10 << 4) | (1 << 3)];
let block = tlv(0x03, &body);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::AudioStream(a) => {
assert!(!a.free_format_flag);
assert!(a.id);
assert_eq!(a.layer, 0b10);
assert!(a.variable_rate_audio_indicator);
}
other => panic!("expected AudioStream, got {other:?}"),
}
}
#[test]
fn ca_descriptor_min_no_private_data() {
let body = [0x4A, 0xAA, 0xE1 , 0x23];
let block = tlv(0x09, &body);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::Ca(ca) => {
assert_eq!(ca.ca_system_id, 0x4AAA);
assert_eq!(ca.ca_pid, 0x0123);
assert!(ca.private_data.is_empty());
}
other => panic!("expected Ca, got {other:?}"),
}
}
#[test]
fn avc_video_descriptor_typical_values() {
let body = [100, 0x00, 41, 0x00];
let block = tlv(0x28, &body);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::AvcVideo(a) => {
assert_eq!(a.profile_idc, 100);
assert_eq!(a.level_idc, 41);
assert!(!a.avc_still_present);
assert!(!a.frame_packing_sei_not_present_flag);
}
other => panic!("expected AvcVideo, got {other:?}"),
}
}
#[test]
fn hevc_video_descriptor_min_13_bytes() {
let b0 = (1u8 << 5) | 2;
let compat: u32 = 0x6000_0000;
let b5: u8 = 0b1011_0000;
let level_idc: u8 = 153;
let body = vec![
b0,
(compat >> 24) as u8,
(compat >> 16) as u8,
(compat >> 8) as u8,
compat as u8,
b5,
0,
0,
0,
0,
0,
0,
level_idc,
];
let block = tlv(0x38, &body);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::HevcVideo(h) => {
assert_eq!(h.profile_space, 0);
assert!(h.tier_flag);
assert_eq!(h.profile_idc, 2);
assert_eq!(h.profile_compatibility_indication, compat);
assert!(h.progressive_source_flag);
assert!(!h.interlaced_source_flag);
assert!(h.non_packed_constraint_flag);
assert!(h.frame_only_constraint_flag);
assert_eq!(h.level_idc, level_idc);
assert!(!h.temporal_layer_subset_flag);
assert!(h.temporal_id_min.is_none());
assert!(h.temporal_id_max.is_none());
}
other => panic!("expected HevcVideo, got {other:?}"),
}
}
#[test]
fn hevc_video_descriptor_with_temporal_layer_subset() {
let mut body = vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0, 0, 0, 0, 0, 0, 120];
body.push(0b1010_0000);
body.push(1 << 5);
body.push(4 << 5);
let block = tlv(0x38, &body);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::HevcVideo(h) => {
assert!(h.temporal_layer_subset_flag);
assert!(h.hevc_24hr_picture_present_flag);
assert_eq!(h.temporal_id_min, Some(1));
assert_eq!(h.temporal_id_max, Some(4));
}
other => panic!("expected HevcVideo, got {other:?}"),
}
}
#[test]
fn data_stream_alignment_video_access_unit() {
let block = tlv(0x06, &[0x02]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::DataStreamAlignment(a) => {
assert_eq!(a.alignment_type, 0x02);
}
other => panic!("expected DataStreamAlignment, got {other:?}"),
}
}
#[test]
fn data_stream_alignment_empty_body_falls_back_to_raw() {
let block = tlv(0x06, &[]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
assert!(matches!(d.body, DescriptorBody::Raw));
}
#[test]
fn system_clock_descriptor_external_ref_off() {
let b0 = 0b0100_1010u8;
let b1 = 0b0110_0000u8;
let block = tlv(0x0B, &[b0, b1]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::SystemClock(s) => {
assert!(!s.external_clock_reference);
assert_eq!(s.clock_accuracy_integer, 10);
assert_eq!(s.clock_accuracy_exponent, 3);
}
other => panic!("expected SystemClock, got {other:?}"),
}
}
#[test]
fn system_clock_descriptor_external_ref_on() {
let block = tlv(0x0B, &[0b1000_0000, 0b0000_0000]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::SystemClock(s) => {
assert!(s.external_clock_reference);
assert_eq!(s.clock_accuracy_integer, 0);
assert_eq!(s.clock_accuracy_exponent, 0);
}
other => panic!("expected SystemClock, got {other:?}"),
}
}
#[test]
fn maximum_bitrate_descriptor_typical() {
let b0 = 0b1100_0000 | 0b0001_0010; let block = tlv(0x0E, &[b0, 0x34, 0x56]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::MaximumBitrate(m) => {
assert_eq!(m.maximum_bitrate, 0x12_3456);
assert_eq!(m.bits_per_second(), 0x12_3456u64 * 400);
}
other => panic!("expected MaximumBitrate, got {other:?}"),
}
}
#[test]
fn maximum_bitrate_descriptor_zero_bitrate() {
let block = tlv(0x0E, &[0b1100_0000, 0x00, 0x00]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::MaximumBitrate(m) => {
assert_eq!(m.maximum_bitrate, 0);
assert_eq!(m.bits_per_second(), 0);
}
other => panic!("expected MaximumBitrate, got {other:?}"),
}
}
#[test]
fn maximum_bitrate_descriptor_short_body_falls_back_to_raw() {
let block = tlv(0x0E, &[0xC0, 0x00]); let d = iter_descriptors(&block).next().unwrap().unwrap();
assert!(matches!(d.body, DescriptorBody::Raw));
}
#[test]
fn std_descriptor_leak_valid_set() {
let block = tlv(0x11, &[0b1111_1111]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::Std(s) => assert!(s.leak_valid_flag),
other => panic!("expected Std, got {other:?}"),
}
}
#[test]
fn std_descriptor_leak_valid_unset() {
let block = tlv(0x11, &[0b1111_1110]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::Std(s) => assert!(!s.leak_valid_flag),
other => panic!("expected Std, got {other:?}"),
}
}
#[test]
fn smoothing_buffer_descriptor_typical_values() {
let leak: u32 = 0x09_C400;
let size: u32 = 0x00_C000;
let body = [
0b1100_0000 | ((leak >> 16) as u8 & 0b0011_1111),
(leak >> 8) as u8,
leak as u8,
0b1100_0000 | ((size >> 16) as u8 & 0b0011_1111),
(size >> 8) as u8,
size as u8,
];
let block = tlv(0x10, &body);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::SmoothingBuffer(sb) => {
assert_eq!(sb.sb_leak_rate, leak);
assert_eq!(sb.sb_size, size);
assert_eq!(sb.leak_rate_bits_per_second(), 256_000_000);
assert_eq!(sb.leak_rate_bytes_per_second(), 32_000_000);
assert_eq!(sb.buffer_size_bytes(), 49_152);
}
other => panic!("expected SmoothingBuffer, got {other:?}"),
}
}
#[test]
fn smoothing_buffer_descriptor_zero_fields() {
let block = tlv(0x10, &[0u8; 6]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::SmoothingBuffer(sb) => {
assert_eq!(sb.sb_leak_rate, 0);
assert_eq!(sb.sb_size, 0);
assert_eq!(sb.leak_rate_bits_per_second(), 0);
assert_eq!(sb.buffer_size_bytes(), 0);
}
other => panic!("expected SmoothingBuffer, got {other:?}"),
}
}
#[test]
fn smoothing_buffer_descriptor_max_22bit_fields() {
let max22: u32 = 0x003F_FFFF;
let body = [0xFFu8, 0xFFu8, 0xFFu8, 0xFFu8, 0xFFu8, 0xFFu8];
let block = tlv(0x10, &body);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::SmoothingBuffer(sb) => {
assert_eq!(sb.sb_leak_rate, max22);
assert_eq!(sb.sb_size, max22);
assert_eq!(sb.leak_rate_bits_per_second(), (max22 as u64) * 400);
assert_eq!(sb.buffer_size_bytes(), max22 as u64);
}
other => panic!("expected SmoothingBuffer, got {other:?}"),
}
}
#[test]
fn smoothing_buffer_descriptor_short_body_falls_back_to_raw() {
let block = tlv(0x10, &[0xFF, 0xFF, 0xFF, 0xFF, 0xFF]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
assert!(matches!(d.body, DescriptorBody::Raw));
assert_eq!(d.data.len(), 5);
}
#[test]
fn smoothing_buffer_descriptor_lives_in_pmt_program_info() {
let mut block = Vec::new();
block.extend_from_slice(&tlv(0x0A, &[b'f', b'r', b'a', 0x00]));
let leak: u32 = 0x0001_0000; let size: u32 = 0x0000_4000; let sb_body = [
((leak >> 16) as u8) & 0b0011_1111,
(leak >> 8) as u8,
leak as u8,
((size >> 16) as u8) & 0b0011_1111,
(size >> 8) as u8,
size as u8,
];
block.extend_from_slice(&tlv(0x10, &sb_body));
block.extend_from_slice(&tlv(0x11, &[0b1111_1111]));
let v = parse_descriptors(&block).unwrap();
assert_eq!(v.len(), 3);
assert_eq!(v[0].tag, 0x0A);
assert_eq!(v[1].tag, 0x10);
match &v[1].body {
DescriptorBody::SmoothingBuffer(sb) => {
assert_eq!(sb.sb_leak_rate, leak);
assert_eq!(sb.sb_size, size);
}
other => panic!("expected SmoothingBuffer, got {other:?}"),
}
assert_eq!(v[2].tag, 0x11);
match &v[2].body {
DescriptorBody::Std(s) => assert!(s.leak_valid_flag),
other => panic!("expected Std, got {other:?}"),
}
}
#[test]
fn copyright_descriptor_identifier_only_no_trailer() {
let body = [0x49, 0x53, 0x42, 0x43];
let block = tlv(0x0D, &body);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::Copyright(c) => {
assert_eq!(c.copyright_identifier, 0x4953_4243);
assert!(c.additional_copyright_info.is_empty());
}
other => panic!("expected Copyright, got {other:?}"),
}
}
#[test]
fn copyright_descriptor_with_trailing_info() {
let body = [0x00, 0x00, 0x00, 0x01, 0xDE, 0xAD, 0xBE, 0xEF, 0x42];
let block = tlv(0x0D, &body);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::Copyright(c) => {
assert_eq!(c.copyright_identifier, 0x0000_0001);
assert_eq!(c.additional_copyright_info, &[0xDE, 0xAD, 0xBE, 0xEF, 0x42]);
}
other => panic!("expected Copyright, got {other:?}"),
}
}
#[test]
fn copyright_descriptor_short_body_falls_back_to_raw() {
let block = tlv(0x0D, &[0xAA, 0xBB, 0xCC]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
assert!(matches!(d.body, DescriptorBody::Raw));
assert_eq!(d.data, &[0xAA, 0xBB, 0xCC]);
}
#[test]
fn copyright_descriptor_zero_body_falls_back_to_raw() {
let block = tlv(0x0D, &[]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
assert!(matches!(d.body, DescriptorBody::Raw));
assert!(d.data.is_empty());
}
#[test]
fn copyright_descriptor_in_pmt_program_info_alongside_other_tags() {
let mut block = Vec::new();
block.extend_from_slice(&tlv(0x0A, &[b'e', b'n', b'g', 0x00]));
let cid: u32 = 0x4953_524E; let trailer = [0x12, 0x34];
let mut copy_body = Vec::new();
copy_body.extend_from_slice(&cid.to_be_bytes());
copy_body.extend_from_slice(&trailer);
block.extend_from_slice(&tlv(0x0D, ©_body));
let leak: u32 = 0x0001_0000;
let size: u32 = 0x0000_4000;
let sb_body = [
((leak >> 16) as u8) & 0b0011_1111,
(leak >> 8) as u8,
leak as u8,
((size >> 16) as u8) & 0b0011_1111,
(size >> 8) as u8,
size as u8,
];
block.extend_from_slice(&tlv(0x10, &sb_body));
let v = parse_descriptors(&block).unwrap();
assert_eq!(v.len(), 3);
assert_eq!(v[0].tag, 0x0A);
assert_eq!(v[1].tag, 0x0D);
match &v[1].body {
DescriptorBody::Copyright(c) => {
assert_eq!(c.copyright_identifier, cid);
assert_eq!(c.additional_copyright_info, &trailer);
}
other => panic!("expected Copyright, got {other:?}"),
}
assert_eq!(v[2].tag, 0x10);
assert!(matches!(v[2].body, DescriptorBody::SmoothingBuffer(_)));
}
#[test]
fn hierarchy_descriptor_spatial_scalability_layer() {
let body = [
0b1111_0001, 0b1100_0000 | 5, 0b1100_0000 | 15, 0b1100_0000, ];
let block = tlv(0x04, &body);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::Hierarchy(h) => {
assert_eq!(h.hierarchy_type, HierarchyType::Mpeg2SpatialScalability);
assert_eq!(h.hierarchy_type.as_nibble(), 1);
assert!(!h.hierarchy_type.is_base_layer());
assert_eq!(h.hierarchy_layer_index, 5);
assert_eq!(h.hierarchy_embedded_layer_index, 15);
assert_eq!(h.hierarchy_channel, 0);
}
other => panic!("expected Hierarchy, got {other:?}"),
}
}
#[test]
fn hierarchy_descriptor_base_layer_marker() {
let body = [
0b0000_1111, 0b0000_0001, 0b0011_1111, 0b0000_0010, ];
let block = tlv(0x04, &body);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::Hierarchy(h) => {
assert_eq!(h.hierarchy_type, HierarchyType::BaseLayer);
assert!(h.hierarchy_type.is_base_layer());
assert_eq!(h.hierarchy_layer_index, 1);
assert_eq!(h.hierarchy_embedded_layer_index, 0x3F);
assert_eq!(h.hierarchy_channel, 2);
}
other => panic!("expected Hierarchy, got {other:?}"),
}
}
#[test]
fn hierarchy_descriptor_all_typed_variants_round_trip() {
let mapped = [
(1u8, HierarchyType::Mpeg2SpatialScalability),
(2, HierarchyType::Mpeg2SnrScalability),
(3, HierarchyType::Mpeg2TemporalScalability),
(4, HierarchyType::Mpeg2DataPartitioning),
(5, HierarchyType::Mpeg2AudioExtension),
(6, HierarchyType::PrivateStream),
(7, HierarchyType::Mpeg2MultiviewProfile),
(15, HierarchyType::BaseLayer),
];
for (raw, expected) in mapped {
let decoded = HierarchyType::from_nibble(raw);
assert_eq!(decoded, expected, "wire nibble {raw}");
assert_eq!(decoded.as_nibble(), raw, "round-trip for {raw}");
}
assert!(HierarchyType::BaseLayer.is_base_layer());
for (_, t) in mapped[..7].iter().copied() {
assert!(!t.is_base_layer());
}
}
#[test]
fn hierarchy_descriptor_reserved_values_preserve_raw_nibble() {
for raw in [0u8, 8, 9, 10, 11, 12, 13, 14] {
let decoded = HierarchyType::from_nibble(raw);
assert_eq!(decoded, HierarchyType::Reserved(raw));
assert_eq!(decoded.as_nibble(), raw);
assert!(!decoded.is_base_layer());
}
assert_eq!(HierarchyType::Unknown(0xAB).as_nibble(), 0xAB);
}
#[test]
fn hierarchy_descriptor_high_nibble_ignored_in_type_byte() {
let body = [0b1010_0011, 0, 0, 0]; let block = tlv(0x04, &body);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::Hierarchy(h) => {
assert_eq!(h.hierarchy_type, HierarchyType::Mpeg2TemporalScalability);
}
other => panic!("expected Hierarchy, got {other:?}"),
}
}
#[test]
fn hierarchy_descriptor_short_body_falls_back_to_raw() {
let block = tlv(0x04, &[0x0F, 0xC0, 0xC0]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
assert!(matches!(d.body, DescriptorBody::Raw));
assert_eq!(d.data, &[0x0F, 0xC0, 0xC0]);
}
#[test]
fn hierarchy_descriptor_max_6bit_index_fields() {
let body = [
0b0000_0010, 0xFF, 0xFF, 0xFF, ];
let block = tlv(0x04, &body);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::Hierarchy(h) => {
assert_eq!(h.hierarchy_type, HierarchyType::Mpeg2SnrScalability);
assert_eq!(h.hierarchy_layer_index, 0x3F);
assert_eq!(h.hierarchy_embedded_layer_index, 0x3F);
assert_eq!(h.hierarchy_channel, 0x3F);
}
other => panic!("expected Hierarchy, got {other:?}"),
}
}
#[test]
fn hierarchy_descriptor_in_pmt_es_info_alongside_other_tags() {
let mut block = Vec::new();
block.extend_from_slice(&tlv(0x0A, &[b'e', b'n', b'g', 0x00]));
let h_body = [
0b0000_0001, 0b0000_0010, 0b0000_0000, 0b0000_0001, ];
block.extend_from_slice(&tlv(0x04, &h_body));
block.extend_from_slice(&tlv(0x0E, &[0b1100_0000, 0x00, 0x64]));
let v = parse_descriptors(&block).unwrap();
assert_eq!(v.len(), 3);
assert_eq!(v[0].tag, 0x0A);
assert_eq!(v[1].tag, 0x04);
match &v[1].body {
DescriptorBody::Hierarchy(h) => {
assert_eq!(h.hierarchy_type, HierarchyType::Mpeg2SpatialScalability);
assert_eq!(h.hierarchy_layer_index, 2);
assert_eq!(h.hierarchy_embedded_layer_index, 0);
assert_eq!(h.hierarchy_channel, 1);
}
other => panic!("expected Hierarchy, got {other:?}"),
}
assert_eq!(v[2].tag, 0x0E);
assert!(matches!(v[2].body, DescriptorBody::MaximumBitrate(_)));
}
#[test]
fn multiplex_buffer_utilization_descriptor_bounds_valid() {
let lower: u16 = 0x1234; let upper: u16 = 0x5678; let body = [
0x80 | ((lower >> 8) as u8 & 0x7F),
lower as u8,
(upper >> 8) as u8 & 0x7F,
upper as u8,
];
let block = tlv(0x0C, &body);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::MultiplexBufferUtilization(m) => {
assert!(m.bound_valid_flag);
assert_eq!(m.ltw_offset_lower_bound, lower);
assert_eq!(m.ltw_offset_upper_bound, upper);
}
other => panic!("expected MultiplexBufferUtilization, got {other:?}"),
}
}
#[test]
fn multiplex_buffer_utilization_descriptor_bounds_invalid() {
let body = [0x7F, 0xFF, 0x7F, 0xFF];
let block = tlv(0x0C, &body);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::MultiplexBufferUtilization(m) => {
assert!(!m.bound_valid_flag);
assert_eq!(m.ltw_offset_lower_bound, 0x7FFF);
assert_eq!(m.ltw_offset_upper_bound, 0x7FFF);
}
other => panic!("expected MultiplexBufferUtilization, got {other:?}"),
}
}
#[test]
fn multiplex_buffer_utilization_descriptor_short_body_falls_back_to_raw() {
let block = tlv(0x0C, &[0xA0, 0x00, 0x00]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
assert!(matches!(d.body, DescriptorBody::Raw));
assert_eq!(d.data, &[0xA0, 0x00, 0x00]);
}
#[test]
fn multiplex_buffer_utilization_descriptor_reserved_bit_ignored_on_read() {
let lower = 0x0000u16;
let upper = 0x0001u16; let with_reserved_zero = [0x80, 0x00, (upper >> 8) as u8, upper as u8];
let with_reserved_one = [0x80, 0x00, ((upper >> 8) as u8) | 0x80, upper as u8];
let block0 = tlv(0x0C, &with_reserved_zero);
let block1 = tlv(0x0C, &with_reserved_one);
let d0 = iter_descriptors(&block0).next().unwrap().unwrap();
let d1 = iter_descriptors(&block1).next().unwrap().unwrap();
let unwrap = |b: &DescriptorBody<'_>| match b {
DescriptorBody::MultiplexBufferUtilization(m) => *m,
other => panic!("expected MultiplexBufferUtilization, got {other:?}"),
};
let m0 = unwrap(&d0.body);
let m1 = unwrap(&d1.body);
assert_eq!(m0.ltw_offset_lower_bound, lower);
assert_eq!(m0.ltw_offset_upper_bound, upper);
assert_eq!(m0, m1);
}
#[test]
fn multiplex_buffer_utilization_descriptor_max_15bit_bounds() {
let body = [0xFF, 0xFF, 0xFF, 0xFF];
let block = tlv(0x0C, &body);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::MultiplexBufferUtilization(m) => {
assert!(m.bound_valid_flag);
assert_eq!(m.ltw_offset_lower_bound, 0x7FFF);
assert_eq!(m.ltw_offset_upper_bound, 0x7FFF);
}
other => panic!("expected MultiplexBufferUtilization, got {other:?}"),
}
}
#[test]
fn multiplex_buffer_utilization_descriptor_in_es_info_list() {
let mut block = Vec::new();
block.extend_from_slice(&tlv(0x0A, &[b'e', b'n', b'g', 0x00]));
block.extend_from_slice(&tlv(0x10, &[0x01, 0x02, 0x03, 0x04, 0x05, 0x06]));
block.extend_from_slice(&tlv(0x0C, &[0x81, 0x00, 0x02, 0x00]));
let v = parse_descriptors(&block).unwrap();
assert_eq!(v.len(), 3);
assert_eq!(v[0].tag, 0x0A);
assert_eq!(v[1].tag, 0x10);
assert_eq!(v[2].tag, 0x0C);
match &v[2].body {
DescriptorBody::MultiplexBufferUtilization(m) => {
assert!(m.bound_valid_flag);
assert_eq!(m.ltw_offset_lower_bound, 0x0100);
assert_eq!(m.ltw_offset_upper_bound, 0x0200);
}
other => panic!("expected MultiplexBufferUtilization, got {other:?}"),
}
}
#[test]
fn parse_descriptors_helper_collects_all() {
let mut block = Vec::new();
block.extend_from_slice(&tlv(0x05, b"HDMV"));
block.extend_from_slice(&tlv(0x0A, &[b'e', b'n', b'g', 0x00]));
block.extend_from_slice(&tlv(0xFF, &[]));
let v = parse_descriptors(&block).unwrap();
assert_eq!(v.len(), 3);
assert_eq!(v[0].tag, 0x05);
assert_eq!(v[1].tag, 0x0A);
assert_eq!(v[2].tag, 0xFF);
assert!(matches!(v[2].body, DescriptorBody::Raw));
assert!(v[2].data.is_empty());
}
#[test]
fn ibp_descriptor_all_flags_set_max_length() {
let block = tlv(0x12, &[0xFF, 0xFF]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
assert_eq!(d.tag, 0x12);
match &d.body {
DescriptorBody::Ibp(ibp) => {
assert!(ibp.closed_gop_flag);
assert!(ibp.identical_gop_flag);
assert_eq!(ibp.max_gop_length, 0x3FFF);
assert!(ibp.is_well_formed());
}
other => panic!("expected Ibp, got {other:?}"),
}
}
#[test]
fn ibp_descriptor_flags_clear_typical_length() {
let block = tlv(0x12, &[0x00, 0x0F]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::Ibp(ibp) => {
assert!(!ibp.closed_gop_flag);
assert!(!ibp.identical_gop_flag);
assert_eq!(ibp.max_gop_length, 15);
assert!(ibp.is_well_formed());
}
other => panic!("expected Ibp, got {other:?}"),
}
}
#[test]
fn ibp_descriptor_closed_only_mid_length() {
let block = tlv(0x12, &[0x81, 0x2C]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::Ibp(ibp) => {
assert!(ibp.closed_gop_flag);
assert!(!ibp.identical_gop_flag);
assert_eq!(ibp.max_gop_length, 0x012C);
}
other => panic!("expected Ibp, got {other:?}"),
}
}
#[test]
fn ibp_descriptor_identical_only_mid_length() {
let block = tlv(0x12, &[0x40, 0x2A]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::Ibp(ibp) => {
assert!(!ibp.closed_gop_flag);
assert!(ibp.identical_gop_flag);
assert_eq!(ibp.max_gop_length, 42);
}
other => panic!("expected Ibp, got {other:?}"),
}
}
#[test]
fn ibp_descriptor_forbidden_zero_max_gop_length_flagged() {
let block = tlv(0x12, &[0x00, 0x00]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::Ibp(ibp) => {
assert_eq!(ibp.max_gop_length, 0);
assert!(!ibp.is_well_formed());
}
other => panic!("expected Ibp, got {other:?}"),
}
}
#[test]
fn ibp_descriptor_truncated_falls_back_to_raw() {
let block = tlv(0x12, &[0xFF]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
assert_eq!(d.tag, 0x12);
assert!(matches!(d.body, DescriptorBody::Raw));
assert_eq!(d.data, &[0xFF]);
}
#[test]
fn ibp_descriptor_in_es_info_list_with_neighbours() {
let mut block = Vec::new();
block.extend_from_slice(&tlv(0x0A, &[b'e', b'n', b'g', 0x00]));
block.extend_from_slice(&tlv(0x12, &[0x80, 0x12]));
block.extend_from_slice(&tlv(0x11, &[0x01]));
let v = parse_descriptors(&block).unwrap();
assert_eq!(v.len(), 3);
assert_eq!(v[0].tag, 0x0A);
assert_eq!(v[1].tag, 0x12);
assert_eq!(v[2].tag, 0x11);
match &v[1].body {
DescriptorBody::Ibp(ibp) => {
assert!(ibp.closed_gop_flag);
assert!(!ibp.identical_gop_flag);
assert_eq!(ibp.max_gop_length, 18);
assert!(ibp.is_well_formed());
}
other => panic!("expected Ibp, got {other:?}"),
}
}
#[test]
fn target_background_grid_descriptor_typical_sd() {
let block = tlv(0x07, &[0x0B, 0x40, 0x24, 0x03]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
assert_eq!(d.tag, 0x07);
match &d.body {
DescriptorBody::TargetBackgroundGrid(g) => {
assert_eq!(g.horizontal_size, 720);
assert_eq!(g.vertical_size, 576);
assert_eq!(g.aspect_ratio_information, 3);
}
other => panic!("expected TargetBackgroundGrid, got {other:?}"),
}
}
#[test]
fn target_background_grid_descriptor_max_fields() {
let block = tlv(0x07, &[0xFF, 0xFF, 0xFF, 0xFF]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::TargetBackgroundGrid(g) => {
assert_eq!(g.horizontal_size, 0x3FFF);
assert_eq!(g.vertical_size, 0x3FFF);
assert_eq!(g.aspect_ratio_information, 0xF);
}
other => panic!("expected TargetBackgroundGrid, got {other:?}"),
}
}
#[test]
fn target_background_grid_descriptor_all_zero() {
let block = tlv(0x07, &[0x00, 0x00, 0x00, 0x00]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::TargetBackgroundGrid(g) => {
assert_eq!(g.horizontal_size, 0);
assert_eq!(g.vertical_size, 0);
assert_eq!(g.aspect_ratio_information, 0);
}
other => panic!("expected TargetBackgroundGrid, got {other:?}"),
}
}
#[test]
fn target_background_grid_descriptor_short_body_falls_back_to_raw() {
let block = tlv(0x07, &[0x0B, 0x40, 0x24]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
assert_eq!(d.tag, 0x07);
assert!(matches!(d.body, DescriptorBody::Raw));
assert_eq!(d.data, &[0x0B, 0x40, 0x24]);
}
#[test]
fn target_background_grid_descriptor_in_es_info_list_with_neighbours() {
let mut block = Vec::new();
block.extend_from_slice(&tlv(0x0A, &[b'e', b'n', b'g', 0x00]));
block.extend_from_slice(&tlv(0x07, &[0x1E, 0x00, 0x43, 0x81]));
block.extend_from_slice(&tlv(0x11, &[0x01]));
let v = parse_descriptors(&block).unwrap();
assert_eq!(v.len(), 3);
assert_eq!(v[0].tag, 0x0A);
assert_eq!(v[1].tag, 0x07);
assert_eq!(v[2].tag, 0x11);
match &v[1].body {
DescriptorBody::TargetBackgroundGrid(g) => {
assert_eq!(g.horizontal_size, 1920);
assert_eq!(g.vertical_size, 1080);
assert_eq!(g.aspect_ratio_information, 1);
}
other => panic!("expected TargetBackgroundGrid, got {other:?}"),
}
}
#[test]
fn video_window_descriptor_typical_offsets() {
let block = tlv(0x08, &[0x05, 0x00, 0x0F, 0x05]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
assert_eq!(d.tag, 0x08);
match &d.body {
DescriptorBody::VideoWindow(w) => {
assert_eq!(w.horizontal_offset, 320);
assert_eq!(w.vertical_offset, 240);
assert_eq!(w.window_priority, 5);
}
other => panic!("expected VideoWindow, got {other:?}"),
}
}
#[test]
fn video_window_descriptor_max_fields() {
let block = tlv(0x08, &[0xFF, 0xFF, 0xFF, 0xFF]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::VideoWindow(w) => {
assert_eq!(w.horizontal_offset, 0x3FFF);
assert_eq!(w.vertical_offset, 0x3FFF);
assert_eq!(w.window_priority, 0xF);
}
other => panic!("expected VideoWindow, got {other:?}"),
}
}
#[test]
fn video_window_descriptor_all_zero() {
let block = tlv(0x08, &[0x00, 0x00, 0x00, 0x00]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match &d.body {
DescriptorBody::VideoWindow(w) => {
assert_eq!(w.horizontal_offset, 0);
assert_eq!(w.vertical_offset, 0);
assert_eq!(w.window_priority, 0);
}
other => panic!("expected VideoWindow, got {other:?}"),
}
}
#[test]
fn video_window_descriptor_short_body_falls_back_to_raw() {
let block = tlv(0x08, &[0x05, 0x00, 0x0F]);
let d = iter_descriptors(&block).next().unwrap().unwrap();
assert_eq!(d.tag, 0x08);
assert!(matches!(d.body, DescriptorBody::Raw));
assert_eq!(d.data, &[0x05, 0x00, 0x0F]);
}
#[test]
fn video_window_descriptor_pairs_with_grid_in_es_info_list() {
let mut block = Vec::new();
block.extend_from_slice(&tlv(0x07, &[0x1E, 0x00, 0x43, 0x81]));
block.extend_from_slice(&tlv(0x08, &[0x01, 0x90, 0x03, 0x27]));
let v = parse_descriptors(&block).unwrap();
assert_eq!(v.len(), 2);
assert_eq!(v[0].tag, 0x07);
assert_eq!(v[1].tag, 0x08);
match &v[0].body {
DescriptorBody::TargetBackgroundGrid(g) => {
assert_eq!(g.horizontal_size, 1920);
assert_eq!(g.vertical_size, 1080);
}
other => panic!("expected TargetBackgroundGrid, got {other:?}"),
}
match &v[1].body {
DescriptorBody::VideoWindow(w) => {
assert_eq!(w.horizontal_offset, 100);
assert_eq!(w.vertical_offset, 50);
assert_eq!(w.window_priority, 7);
}
other => panic!("expected VideoWindow, got {other:?}"),
}
}
#[test]
fn service_descriptor_decodes_names_and_type() {
let mut body = vec![0x01];
body.push(4);
body.extend_from_slice(b"Prov");
body.push(9);
body.extend_from_slice(b"Channel 1");
let block = tlv(0x48, &body);
let d = iter_descriptors(&block).next().unwrap().unwrap();
assert_eq!(d.tag, 0x48);
match d.body {
DescriptorBody::Service(s) => {
assert_eq!(s.service_type, 0x01);
assert_eq!(s.service_provider_name, b"Prov");
assert_eq!(s.service_name, b"Channel 1");
}
other => panic!("expected Service, got {other:?}"),
}
}
#[test]
fn service_descriptor_empty_provider_name() {
let mut body = vec![0x11]; body.push(0);
body.push(3);
body.extend_from_slice(b"HD1");
let block = tlv(0x48, &body);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match d.body {
DescriptorBody::Service(s) => {
assert_eq!(s.service_type, 0x11);
assert!(s.service_provider_name.is_empty());
assert_eq!(s.service_name, b"HD1");
}
other => panic!("expected Service, got {other:?}"),
}
}
#[test]
fn service_descriptor_truncated_falls_back_to_raw() {
let block = tlv(0x48, &[0x01, 0x09, b'X', b'Y']);
let d = iter_descriptors(&block).next().unwrap().unwrap();
assert_eq!(d.tag, 0x48);
assert!(matches!(d.body, DescriptorBody::Raw));
assert_eq!(d.data, &[0x01, 0x09, b'X', b'Y']);
}
#[test]
fn short_event_descriptor_decodes_name_and_text() {
let mut body = Vec::new();
body.extend_from_slice(b"eng");
body.push(4);
body.extend_from_slice(b"News");
body.push(14);
body.extend_from_slice(b"Daily bulletin");
let block = tlv(0x4D, &body);
let d = iter_descriptors(&block).next().unwrap().unwrap();
assert_eq!(d.tag, 0x4D);
match d.body {
DescriptorBody::ShortEvent(s) => {
assert_eq!(&s.language_code, b"eng");
assert_eq!(s.event_name, b"News");
assert_eq!(s.text, b"Daily bulletin");
}
other => panic!("expected ShortEvent, got {other:?}"),
}
}
#[test]
fn short_event_descriptor_empty_text() {
let mut body = Vec::new();
body.extend_from_slice(b"fre");
body.push(5);
body.extend_from_slice(b"Match");
body.push(0);
let block = tlv(0x4D, &body);
let d = iter_descriptors(&block).next().unwrap().unwrap();
match d.body {
DescriptorBody::ShortEvent(s) => {
assert_eq!(&s.language_code, b"fre");
assert_eq!(s.event_name, b"Match");
assert!(s.text.is_empty());
}
other => panic!("expected ShortEvent, got {other:?}"),
}
}
#[test]
fn short_event_descriptor_truncated_falls_back_to_raw() {
let block = tlv(0x4D, &[b'e', b'n', b'g', 0x09, b'X', b'Y']);
let d = iter_descriptors(&block).next().unwrap().unwrap();
assert_eq!(d.tag, 0x4D);
assert!(matches!(d.body, DescriptorBody::Raw));
}
}