use super::*;
impl<'a> ExtensionBodyDef<'a> for TargetRegion {
const TAG_EXTENSION: u8 = 0x09;
const NAME: &'static str = "TARGET_REGION";
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TargetRegion {
pub country_code: LangCode,
pub regions: Vec<TargetRegionEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TargetRegionEntry {
pub country_code: Option<LangCode>,
pub region_codes: RegionCodes,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum RegionCodes {
None,
Primary {
primary_region_code: u8,
},
PrimarySecondary {
primary_region_code: u8,
secondary_region_code: u8,
},
Full {
primary_region_code: u8,
secondary_region_code: u8,
tertiary_region_code: u16,
},
}
impl<'a> Parse<'a> for TargetRegion {
type Error = crate::error::Error;
fn parse(sel: &'a [u8]) -> Result<Self> {
if sel.len() < ISO_639_LEN {
return Err(Error::BufferTooShort {
need: ISO_639_LEN,
have: sel.len(),
what: "target_region body",
});
}
let country_code = LangCode([sel[0], sel[1], sel[2]]);
let regions = parse_region_entries(sel, ISO_639_LEN)?;
Ok(TargetRegion {
country_code,
regions,
})
}
}
pub(crate) fn parse_region_entries(sel: &[u8], start: usize) -> Result<Vec<TargetRegionEntry>> {
let mut regions = Vec::new();
let mut pos = start;
while pos < sel.len() {
let flags = sel[pos];
pos += 1;
let country_code_flag = (flags >> 2) & 1;
let region_depth = flags & 0x03;
let country_code = if country_code_flag == 1 {
if pos + ISO_639_LEN > sel.len() {
return Err(Error::BufferTooShort {
need: pos + ISO_639_LEN,
have: sel.len(),
what: "target_region body",
});
}
let cc = LangCode([sel[pos], sel[pos + 1], sel[pos + 2]]);
pos += ISO_639_LEN;
Some(cc)
} else {
None
};
let region_codes = match region_depth {
0 => RegionCodes::None,
1 => {
if pos >= sel.len() {
return Err(Error::BufferTooShort {
need: pos + 1,
have: sel.len(),
what: "target_region body",
});
}
let primary = sel[pos];
pos += 1;
RegionCodes::Primary {
primary_region_code: primary,
}
}
2 => {
if pos + 1 >= sel.len() {
return Err(Error::BufferTooShort {
need: pos + 2,
have: sel.len(),
what: "target_region body",
});
}
let primary = sel[pos];
let secondary = sel[pos + 1];
pos += 2;
RegionCodes::PrimarySecondary {
primary_region_code: primary,
secondary_region_code: secondary,
}
}
3 => {
if pos + 3 >= sel.len() {
return Err(Error::BufferTooShort {
need: pos + 4,
have: sel.len(),
what: "target_region body",
});
}
let primary = sel[pos];
let secondary = sel[pos + 1];
let tertiary = u16::from_be_bytes([sel[pos + 2], sel[pos + 3]]);
pos += 4;
RegionCodes::Full {
primary_region_code: primary,
secondary_region_code: secondary,
tertiary_region_code: tertiary,
}
}
_ => return Err(invalid("target_region: invalid region_depth")),
};
regions.push(TargetRegionEntry {
country_code,
region_codes,
});
}
Ok(regions)
}
pub(crate) fn region_entries_serialized_len(entries: &[TargetRegionEntry]) -> usize {
entries
.iter()
.map(|r| {
1 + if r.country_code.is_some() {
ISO_639_LEN
} else {
0
} + match &r.region_codes {
RegionCodes::None => 0,
RegionCodes::Primary { .. } => 1,
RegionCodes::PrimarySecondary { .. } => 2,
RegionCodes::Full { .. } => 4,
}
})
.sum()
}
pub(crate) fn write_region_entries(
entries: &[TargetRegionEntry],
buf: &mut [u8],
mut pos: usize,
) -> usize {
let start = pos;
for region in entries {
let depth = match ®ion.region_codes {
RegionCodes::None => 0u8,
RegionCodes::Primary { .. } => 1,
RegionCodes::PrimarySecondary { .. } => 2,
RegionCodes::Full { .. } => 3,
};
buf[pos] = 0xF8 | ((region.country_code.is_some() as u8) << 2) | depth;
pos += 1;
if let Some(cc) = ®ion.country_code {
buf[pos..pos + ISO_639_LEN].copy_from_slice(&cc.0);
pos += ISO_639_LEN;
}
match ®ion.region_codes {
RegionCodes::None => {}
RegionCodes::Primary {
primary_region_code,
} => {
buf[pos] = *primary_region_code;
pos += 1;
}
RegionCodes::PrimarySecondary {
primary_region_code,
secondary_region_code,
} => {
buf[pos] = *primary_region_code;
buf[pos + 1] = *secondary_region_code;
pos += 2;
}
RegionCodes::Full {
primary_region_code,
secondary_region_code,
tertiary_region_code,
} => {
buf[pos] = *primary_region_code;
buf[pos + 1] = *secondary_region_code;
buf[pos + 2..pos + 4].copy_from_slice(&tertiary_region_code.to_be_bytes());
pos += 4;
}
}
}
pos - start
}
impl Serialize for TargetRegion {
type Error = crate::error::Error;
fn serialized_len(&self) -> usize {
ISO_639_LEN + region_entries_serialized_len(&self.regions)
}
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);
write_region_entries(&self.regions, buf, ISO_639_LEN);
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_structured() {
let sel = [b'g', b'b', b'r', 0xF9, 0x12];
let bytes = wrap(0x09, &sel);
let d = ExtensionDescriptor::parse(&bytes).unwrap();
match &d.body {
ExtensionBody::TargetRegion(b) => {
assert_eq!(b.country_code, LangCode(*b"gbr"));
assert_eq!(b.regions.len(), 1);
assert_eq!(b.regions[0].country_code, None);
assert_eq!(
b.regions[0].region_codes,
RegionCodes::Primary {
primary_region_code: 0x12
}
);
}
other => panic!("expected TargetRegion, got {other:?}"),
}
round_trip(&d);
}
#[test]
fn target_region_tsduck_empty() {
let bytes = from_hex("7f0409666f6f");
let d = ExtensionDescriptor::parse(&bytes).unwrap();
match &d.body {
ExtensionBody::TargetRegion(b) => {
assert_eq!(b.country_code, LangCode(*b"foo"));
assert!(b.regions.is_empty());
}
other => panic!("expected TargetRegion, got {other:?}"),
}
let mut buf = vec![0u8; d.serialized_len()];
d.serialize_into(&mut buf).unwrap();
assert_eq!(buf, bytes);
}
#[test]
fn target_region_tsduck_full() {
let bytes = from_hex("7f1509626172f8fd666f6f12fa3456ff616263789abcde");
let d = ExtensionDescriptor::parse(&bytes).unwrap();
match &d.body {
ExtensionBody::TargetRegion(b) => {
assert_eq!(b.country_code, LangCode(*b"bar"));
assert_eq!(b.regions.len(), 4);
assert_eq!(b.regions[0].country_code, None);
assert_eq!(b.regions[0].region_codes, RegionCodes::None);
assert_eq!(b.regions[1].country_code, Some(LangCode(*b"foo")));
assert_eq!(
b.regions[1].region_codes,
RegionCodes::Primary {
primary_region_code: 0x12
}
);
assert_eq!(b.regions[2].country_code, None);
assert_eq!(
b.regions[2].region_codes,
RegionCodes::PrimarySecondary {
primary_region_code: 0x34,
secondary_region_code: 0x56,
}
);
assert_eq!(b.regions[3].country_code, Some(LangCode(*b"abc")));
assert_eq!(
b.regions[3].region_codes,
RegionCodes::Full {
primary_region_code: 0x78,
secondary_region_code: 0x9A,
tertiary_region_code: 0xBCDE,
}
);
}
other => panic!("expected TargetRegion, got {other:?}"),
}
let mut buf = vec![0u8; d.serialized_len()];
d.serialize_into(&mut buf).unwrap();
assert_eq!(buf, bytes);
}
}