use super::*;
impl<'a> ExtensionBodyDef<'a> for TargetRegionName<'a> {
const TAG_EXTENSION: u8 = 0x0A;
const NAME: &'static str = "TARGET_REGION_NAME";
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
pub struct TargetRegionName<'a> {
pub country_code: LangCode,
pub iso_639_language_code: LangCode,
pub regions: Vec<TargetRegionNameEntry<'a>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
pub struct TargetRegionNameEntry<'a> {
pub region_depth: u8,
pub region_name: DvbText<'a>,
pub primary_region_code: u8,
pub secondary_region_code: Option<u8>,
pub tertiary_region_code: Option<u16>,
}
impl<'a> Parse<'a> for TargetRegionName<'a> {
type Error = crate::error::Error;
fn parse(sel: &'a [u8]) -> Result<Self> {
if sel.len() < 2 * ISO_639_LEN {
return Err(Error::BufferTooShort {
need: 2 * ISO_639_LEN,
have: sel.len(),
what: "target_region_name body",
});
}
let country_code = LangCode([sel[0], sel[1], sel[2]]);
let iso_639_language_code = LangCode([sel[3], sel[4], sel[5]]);
let mut regions = Vec::new();
let mut pos = 2 * ISO_639_LEN;
while pos < sel.len() {
let byte = sel[pos];
pos += 1;
let region_depth = byte >> 6;
let name_length = (byte & 0x3F) as usize;
if pos + name_length > sel.len() {
return Err(Error::BufferTooShort {
need: pos + name_length,
have: sel.len(),
what: "target_region_name body",
});
}
let region_name = DvbText::new(&sel[pos..pos + name_length]);
pos += name_length;
if pos >= sel.len() {
return Err(Error::BufferTooShort {
need: pos + 1,
have: sel.len(),
what: "target_region_name body",
});
}
let primary_region_code = sel[pos];
pos += 1;
let secondary_region_code = if region_depth >= 2 {
if pos >= sel.len() {
return Err(Error::BufferTooShort {
need: pos + 1,
have: sel.len(),
what: "target_region_name body",
});
}
let val = sel[pos];
pos += 1;
Some(val)
} else {
None
};
let tertiary_region_code = if region_depth == 3 {
if pos + 1 >= sel.len() {
return Err(Error::BufferTooShort {
need: pos + 2,
have: sel.len(),
what: "target_region_name body",
});
}
let val = u16::from_be_bytes([sel[pos], sel[pos + 1]]);
pos += 2;
Some(val)
} else {
None
};
regions.push(TargetRegionNameEntry {
region_depth,
region_name,
primary_region_code,
secondary_region_code,
tertiary_region_code,
});
}
Ok(TargetRegionName {
country_code,
iso_639_language_code,
regions,
})
}
}
impl Serialize for TargetRegionName<'_> {
type Error = crate::error::Error;
fn serialized_len(&self) -> usize {
2 * ISO_639_LEN
+ self
.regions
.iter()
.map(|r| {
1 + r.region_name.raw().len()
+ 1
+ if r.region_depth >= 2 { 1 } else { 0 }
+ if r.region_depth == 3 { 2 } else { 0 }
})
.sum::<usize>()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len();
if buf.len() < len {
return Err(Error::OutputBufferTooSmall {
need: len,
have: buf.len(),
});
}
buf[..ISO_639_LEN].copy_from_slice(&self.country_code.0);
buf[ISO_639_LEN..2 * ISO_639_LEN].copy_from_slice(&self.iso_639_language_code.0);
let mut pos = 2 * ISO_639_LEN;
for region in &self.regions {
let raw = region.region_name.raw();
buf[pos] = ((region.region_depth & 0x03) << 6) | (raw.len() as u8 & 0x3F);
pos += 1;
buf[pos..pos + raw.len()].copy_from_slice(raw);
pos += raw.len();
buf[pos] = region.primary_region_code;
pos += 1;
if let Some(sec) = region.secondary_region_code {
buf[pos] = sec;
pos += 1;
}
if let Some(tert) = region.tertiary_region_code {
buf[pos..pos + 2].copy_from_slice(&tert.to_be_bytes());
pos += 2;
}
}
Ok(len)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::descriptors::extension::test_support::*;
use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor};
use crate::text::LangCode;
#[test]
fn parse_target_region_name_structured() {
let sel = [b'g', b'b', b'r', b'e', b'n', b'g', 0x42, b'H', b'i', 0x12];
let bytes = wrap(0x0A, &sel);
let d = ExtensionDescriptor::parse(&bytes).unwrap();
match &d.body {
ExtensionBody::TargetRegionName(b) => {
assert_eq!(b.country_code, LangCode(*b"gbr"));
assert_eq!(b.iso_639_language_code, LangCode(*b"eng"));
assert_eq!(b.regions.len(), 1);
assert_eq!(b.regions[0].region_depth, 1);
assert_eq!(b.regions[0].region_name.raw(), b"Hi");
assert_eq!(b.regions[0].primary_region_code, 0x12);
assert_eq!(b.regions[0].secondary_region_code, None);
assert_eq!(b.regions[0].tertiary_region_code, None);
}
other => panic!("expected TargetRegionName, got {other:?}"),
}
round_trip(&d);
}
#[test]
fn target_region_name_tsduck_empty() {
let bytes = from_hex("7f070a666f6f626172");
let d = ExtensionDescriptor::parse(&bytes).unwrap();
match &d.body {
ExtensionBody::TargetRegionName(b) => {
assert_eq!(b.country_code, LangCode(*b"foo"));
assert_eq!(b.iso_639_language_code, LangCode(*b"bar"));
assert!(b.regions.is_empty());
}
other => panic!("expected TargetRegionName, got {other:?}"),
}
let mut buf = vec![0u8; d.serialized_len()];
d.serialize_into(&mut buf).unwrap();
assert_eq!(buf, bytes);
}
#[test]
fn target_region_name_tsduck_full() {
let bytes = from_hex(
"7f260a4142434445464c726567696f6e20666f6f203112cc726567696f6e2062617220323456789a",
);
let d = ExtensionDescriptor::parse(&bytes).unwrap();
match &d.body {
ExtensionBody::TargetRegionName(b) => {
assert_eq!(b.country_code, LangCode(*b"ABC"));
assert_eq!(b.iso_639_language_code, LangCode(*b"DEF"));
assert_eq!(b.regions.len(), 2);
assert_eq!(b.regions[0].region_depth, 1);
assert_eq!(b.regions[0].region_name.raw(), b"region foo 1");
assert_eq!(b.regions[0].primary_region_code, 0x12);
assert_eq!(b.regions[0].secondary_region_code, None);
assert_eq!(b.regions[0].tertiary_region_code, None);
assert_eq!(b.regions[1].region_depth, 3);
assert_eq!(b.regions[1].region_name.raw(), b"region bar 2");
assert_eq!(b.regions[1].primary_region_code, 0x34);
assert_eq!(b.regions[1].secondary_region_code, Some(0x56));
assert_eq!(b.regions[1].tertiary_region_code, Some(0x789A));
}
other => panic!("expected TargetRegionName, got {other:?}"),
}
let mut buf = vec![0u8; d.serialized_len()];
d.serialize_into(&mut buf).unwrap();
assert_eq!(buf, bytes);
}
}