use crate::error::{Error, Result};
use alloc::vec::Vec;
use broadcast_common::clock33::{self, pack_marked, unpack_marked};
use broadcast_common::len::need_out;
use broadcast_common::{Parse, Serialize};
const HEADER_LEN: usize = 2;
const METRIC_HEADER_LEN: usize = 5;
const DTS_FIELD_LEN: usize = 5;
const DTS_PREFIX: u8 = 0b0010;
const DTS_BITS: u32 = 33;
const WHAT: &str = "QualityAccessUnit";
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct QualitySample<'a> {
pub media_dts: u64,
#[cfg_attr(feature = "serde", serde(borrow))]
pub quality_metric_sample: &'a [u8],
}
impl<'a> QualitySample<'a> {
#[must_use]
pub const fn new(media_dts: u64, quality_metric_sample: &'a [u8]) -> Self {
Self {
media_dts,
quality_metric_sample,
}
}
#[must_use]
pub fn value_u64(&self) -> Option<u64> {
const U64_BYTES: usize = 8;
let (high, low) = self
.quality_metric_sample
.split_at(self.quality_metric_sample.len().saturating_sub(U64_BYTES));
if high.iter().any(|b| *b != 0) {
return None;
}
Some(low.iter().fold(0u64, |v, b| (v << 8) | u64::from(*b)))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct QualityMetric<'a> {
pub metric_code: u32,
#[cfg_attr(feature = "serde", serde(borrow))]
pub samples: Vec<QualitySample<'a>>,
}
impl<'a> QualityMetric<'a> {
#[must_use]
pub fn new(metric_code: u32, samples: Vec<QualitySample<'a>>) -> Self {
Self {
metric_code,
samples,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct QualityAccessUnit<'a> {
pub field_size_bytes: u8,
#[cfg_attr(feature = "serde", serde(borrow))]
pub metrics: Vec<QualityMetric<'a>>,
}
impl<'a> QualityAccessUnit<'a> {
#[must_use]
pub fn new(field_size_bytes: u8, metrics: Vec<QualityMetric<'a>>) -> Self {
Self {
field_size_bytes,
metrics,
}
}
fn sample_len(&self) -> usize {
DTS_FIELD_LEN + usize::from(self.field_size_bytes)
}
}
fn short(need: usize, have: usize) -> Error {
Error::BufferTooShort(broadcast_common::error::BufferTooShort {
need,
have,
what: WHAT,
})
}
impl<'a> Parse<'a> for QualityAccessUnit<'a> {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let (header, mut rest) = bytes
.split_first_chunk::<HEADER_LEN>()
.ok_or_else(|| short(HEADER_LEN, bytes.len()))?;
let field_size_bytes = header[0];
let metric_count = header[1];
let sample_len = DTS_FIELD_LEN + usize::from(field_size_bytes);
let mut metrics = Vec::new();
for _ in 0..metric_count {
let (mh, tail) = rest
.split_first_chunk::<METRIC_HEADER_LEN>()
.ok_or_else(|| short(bytes.len() + METRIC_HEADER_LEN, bytes.len()))?;
let metric_code = u32::from_be_bytes([mh[0], mh[1], mh[2], mh[3]]);
let sample_count = usize::from(mh[4]);
rest = tail;
let mut samples = Vec::new();
for _ in 0..sample_count {
if rest.len() < sample_len {
return Err(short(bytes.len() + sample_len - rest.len(), bytes.len()));
}
let (sample, tail) = rest.split_at(sample_len);
let (dts, value) = sample.split_at(DTS_FIELD_LEN);
let mut field = [0u8; DTS_FIELD_LEN];
field.copy_from_slice(dts);
let marked = unpack_marked(&field);
if marked.prefix != DTS_PREFIX {
return Err(Error::ReservedBits(broadcast_common::error::ReservedBits {
field: "media_DTS prefix",
expected: u64::from(DTS_PREFIX),
found: u64::from(marked.prefix),
}));
}
if marked.markers != clock33::MARKERS_ALL_SET {
return Err(Error::ReservedBits(broadcast_common::error::ReservedBits {
field: "media_DTS marker_bits",
expected: u64::from(clock33::MARKERS_ALL_SET),
found: u64::from(marked.markers),
}));
}
samples.push(QualitySample {
media_dts: marked.timestamp,
quality_metric_sample: value,
});
rest = tail;
}
metrics.push(QualityMetric {
metric_code,
samples,
});
}
if !rest.is_empty() {
return Err(Error::ValueOutOfRange {
field: "Quality_Access_Unit",
reason: "bytes remain after the last metric (Table 2-164 defines none)",
});
}
Ok(Self {
field_size_bytes,
metrics,
})
}
}
impl Serialize for QualityAccessUnit<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
HEADER_LEN
+ self
.metrics
.iter()
.map(|m| METRIC_HEADER_LEN + m.samples.len() * self.sample_len())
.sum::<usize>()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len();
need_out(buf, len)?;
buf[0] = self.field_size_bytes;
buf[1] = broadcast_common::len::fit_u8(self.metrics.len(), "metric_count")?;
let mut pos = HEADER_LEN;
for m in &self.metrics {
buf[pos..pos + 4].copy_from_slice(&m.metric_code.to_be_bytes());
buf[pos + 4] = broadcast_common::len::fit_u8(m.samples.len(), "sample_count")?;
pos += METRIC_HEADER_LEN;
for s in &m.samples {
if s.quality_metric_sample.len() != usize::from(self.field_size_bytes) {
return Err(Error::ValueOutOfRange {
field: "quality_metric_sample",
reason: "length differs from field_size_bytes",
});
}
let dts = broadcast_common::len::fit_bits(s.media_dts, DTS_BITS, "media_DTS")?;
buf[pos..pos + DTS_FIELD_LEN].copy_from_slice(&pack_marked(dts, DTS_PREFIX));
pos += DTS_FIELD_LEN;
buf[pos..pos + s.quality_metric_sample.len()]
.copy_from_slice(s.quality_metric_sample);
pos += s.quality_metric_sample.len();
}
}
Ok(len)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dts_bytes_are_marker_coded() {
assert_eq!(
pack_marked(0x1_2345_6789, DTS_PREFIX),
[0x29, 0x8D, 0x15, 0xCF, 0x13]
);
}
#[test]
fn parse_and_round_trip() {
let b = [
0x02, 0x02, 0x00, 0x00, 0x00, 0x01, 0x02, 0x29, 0x8D, 0x15, 0xCF, 0x13, 0x01, 0x02, 0x21, 0x00, 0x01, 0x00, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, ];
let d = QualityAccessUnit::parse(&b).unwrap();
assert_eq!(d.field_size_bytes, 2);
assert_eq!(d.metrics.len(), 2);
assert_eq!(d.metrics[0].metric_code, 1);
assert_eq!(d.metrics[0].samples[0].media_dts, 0x1_2345_6789);
assert_eq!(d.metrics[0].samples[0].value_u64(), Some(0x0102));
assert_eq!(d.metrics[0].samples[1].media_dts, 0);
assert_eq!(d.metrics[0].samples[1].value_u64(), Some(0));
assert!(d.metrics[1].samples.is_empty());
let mut out = vec![0u8; d.serialized_len()];
d.serialize_into(&mut out).unwrap();
assert_eq!(out, b);
}
#[test]
fn wide_samples_and_value_u64() {
let wide = [0u8, 0, 0, 0, 0, 0, 0, 0, 0xAB];
assert_eq!(QualitySample::new(0, &wide).value_u64(), Some(0xAB));
let too_wide = [1u8, 0, 0, 0, 0, 0, 0, 0, 0];
assert_eq!(QualitySample::new(0, &too_wide).value_u64(), None);
let unit = QualityAccessUnit::new(
9,
vec![QualityMetric::new(7, vec![QualitySample::new(5, &wide)])],
);
let mut out = vec![0u8; unit.serialized_len()];
unit.serialize_into(&mut out).unwrap();
assert_eq!(QualityAccessUnit::parse(&out).unwrap(), unit);
}
#[test]
fn malformed_is_rejected() {
assert!(matches!(
QualityAccessUnit::parse(&[]),
Err(Error::BufferTooShort(..))
));
assert!(matches!(
&(QualityAccessUnit::parse(&[0x01, 0x01])),
Err(crate::error::Error::BufferTooShort(..))
));
assert!(matches!(
&(QualityAccessUnit::parse(&[0x01, 0x01, 0, 0, 0, 1, 1, 0x21, 0, 1])),
Err(crate::error::Error::BufferTooShort(..))
));
let bad = [
0x01, 0x01, 0, 0, 0, 1, 1, 0x31, 0x00, 0x01, 0x00, 0x01, 0x07,
];
assert_eq!(
QualityAccessUnit::parse(&bad),
Err(Error::ReservedBits(broadcast_common::error::ReservedBits {
field: "media_DTS prefix",
expected: 0b0010,
found: 3,
}))
);
let bad = [
0x01, 0x01, 0, 0, 0, 1, 1, 0x20, 0x00, 0x01, 0x00, 0x01, 0x07,
];
assert_eq!(
QualityAccessUnit::parse(&bad),
Err(Error::ReservedBits(broadcast_common::error::ReservedBits {
field: "media_DTS marker_bits",
expected: 0b111,
found: 0b011,
}))
);
let ok = [0x01, 0x00, 0xFF];
assert!(matches!(
QualityAccessUnit::parse(&ok),
Err(Error::ValueOutOfRange { .. })
));
let unit = QualityAccessUnit::new(
2,
vec![QualityMetric::new(1, vec![QualitySample::new(0, &[1])])],
);
assert!(matches!(
&(unit.serialize_into(&mut [0u8; 32])),
Err(crate::error::Error::ValueOutOfRange { .. })
));
let unit = QualityAccessUnit::new(
1,
vec![QualityMetric::new(
1,
vec![QualitySample::new(1 << 33, &[1])],
)],
);
assert!(matches!(
&(unit.serialize_into(&mut [0u8; 32])),
Err(crate::error::Error::FieldOverflow(_))
));
}
}