use alloc::vec::Vec;
use crate::error::{Error, Result};
use crate::ext::{HeaderExtension, WORD};
pub const HET_EXT_NOP: u8 = 0;
pub const HET_EXT_AUTH: u8 = 1;
pub const HET_EXT_TIME: u8 = 2;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum LctExtType {
Nop,
Auth,
Time,
Other(u8),
}
impl LctExtType {
pub fn from_het(het: u8) -> Self {
match het {
HET_EXT_NOP => LctExtType::Nop,
HET_EXT_AUTH => LctExtType::Auth,
HET_EXT_TIME => LctExtType::Time,
other => LctExtType::Other(other),
}
}
pub fn het(self) -> u8 {
match self {
LctExtType::Nop => HET_EXT_NOP,
LctExtType::Auth => HET_EXT_AUTH,
LctExtType::Time => HET_EXT_TIME,
LctExtType::Other(v) => v,
}
}
pub fn name(&self) -> &'static str {
match self {
LctExtType::Nop => "EXT_NOP",
LctExtType::Auth => "EXT_AUTH",
LctExtType::Time => "EXT_TIME",
LctExtType::Other(_) => "other",
}
}
}
broadcast_common::impl_spec_display!(LctExtType, Other);
pub const USE_SCT_HIGH: u16 = 0x8000;
pub const USE_SCT_LOW: u16 = 0x4000;
pub const USE_ERT: u16 = 0x2000;
pub const USE_SLC: u16 = 0x1000;
const USE_PI_SPECIFIC_MASK: u16 = 0x00FF;
const USE_RESERVED_MASK: u16 = 0x0F00;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ExtTime {
pub sct_high: Option<u32>,
pub sct_low: Option<u32>,
pub ert: Option<u32>,
pub slc: Option<u32>,
pub pi_specific: u8,
}
impl ExtTime {
pub fn use_field(&self) -> u16 {
let mut u = self.pi_specific as u16;
if self.sct_high.is_some() {
u |= USE_SCT_HIGH;
}
if self.sct_low.is_some() {
u |= USE_SCT_LOW;
}
if self.ert.is_some() {
u |= USE_ERT;
}
if self.slc.is_some() {
u |= USE_SLC;
}
u
}
fn value_count(&self) -> usize {
self.sct_high.is_some() as usize
+ self.sct_low.is_some() as usize
+ self.ert.is_some() as usize
+ self.slc.is_some() as usize
}
pub fn serialized_len(&self) -> usize {
WORD + WORD * self.value_count()
}
pub fn parse(content: &[u8]) -> Result<Self> {
if content.len() < 2 {
return Err(Error::BufferTooShort {
need: 2,
have: content.len(),
what: "EXT_TIME Use field",
});
}
let use_field = u16::from_be_bytes([content[0], content[1]]);
let pi_specific = (use_field & USE_PI_SPECIFIC_MASK) as u8;
if use_field & USE_RESERVED_MASK != 0 {
return Err(Error::InvalidField {
what: "EXT_TIME Use reserved",
reason: "reserved-by-LCT Use bits must be zero",
});
}
if (use_field & USE_SCT_LOW != 0) && (use_field & USE_SCT_HIGH == 0) {
return Err(Error::InvalidField {
what: "EXT_TIME Use",
reason: "SCT-Low set without SCT-High",
});
}
let mut off = 2;
let mut take = |present: bool| -> Result<Option<u32>> {
if !present {
return Ok(None);
}
if content.len() < off + WORD {
return Err(Error::BufferTooShort {
need: off + WORD,
have: content.len(),
what: "EXT_TIME time value",
});
}
let v = u32::from_be_bytes([
content[off],
content[off + 1],
content[off + 2],
content[off + 3],
]);
off += WORD;
Ok(Some(v))
};
let sct_high = take(use_field & USE_SCT_HIGH != 0)?;
let sct_low = take(use_field & USE_SCT_LOW != 0)?;
let ert = take(use_field & USE_ERT != 0)?;
let slc = take(use_field & USE_SLC != 0)?;
Ok(ExtTime {
sct_high,
sct_low,
ert,
slc,
pi_specific,
})
}
pub fn to_content(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(self.serialized_len());
out.extend_from_slice(&self.use_field().to_be_bytes());
for v in [self.sct_high, self.sct_low, self.ert, self.slc]
.into_iter()
.flatten()
{
out.extend_from_slice(&v.to_be_bytes());
}
out
}
pub fn to_extension<'a>(&self, scratch: &'a mut Vec<u8>) -> HeaderExtension<'a> {
*scratch = self.to_content();
HeaderExtension::new(HET_EXT_TIME, scratch)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
use alloc::vec;
#[test]
fn ext_type_round_trip() {
for het in [0u8, 1, 2, 64, 192, 255] {
assert_eq!(LctExtType::from_het(het).het(), het);
}
assert_eq!(LctExtType::from_het(64), LctExtType::Other(64));
assert_eq!(LctExtType::Time.to_string(), "EXT_TIME");
assert_eq!(LctExtType::Other(64).to_string(), "other(0x40)");
}
#[test]
fn ext_time_sct_high_low_round_trip() {
let t = ExtTime {
sct_high: Some(0x1122_3344),
sct_low: Some(0x5566_7788),
ert: None,
slc: None,
pi_specific: 0,
};
assert_eq!(t.use_field(), 0xC000);
let content = t.to_content();
assert_eq!(content.len(), 10);
assert_eq!(&content[0..2], &[0xC0, 0x00]);
assert_eq!(&content[2..6], &[0x11, 0x22, 0x33, 0x44]);
assert_eq!(&content[6..10], &[0x55, 0x66, 0x77, 0x88]);
let re = ExtTime::parse(&content).unwrap();
assert_eq!(re, t);
let mut scratch = vec![];
let ext = t.to_extension(&mut scratch);
assert_eq!(ext.het, 2);
assert_eq!(ext.serialized_len(), 12);
assert_eq!(ext.hel(), 3);
}
#[test]
fn ext_time_all_four_values_in_order() {
let t = ExtTime {
sct_high: Some(1),
sct_low: Some(2),
ert: Some(3),
slc: Some(4),
pi_specific: 0xAB,
};
assert_eq!(t.use_field(), 0xF000 | 0x00AB);
let content = t.to_content();
let re = ExtTime::parse(&content).unwrap();
assert_eq!(re, t);
assert_eq!(&content[2..6], &1u32.to_be_bytes());
assert_eq!(&content[6..10], &2u32.to_be_bytes());
assert_eq!(&content[10..14], &3u32.to_be_bytes());
assert_eq!(&content[14..18], &4u32.to_be_bytes());
}
#[test]
fn ext_time_rejects_sct_low_without_high() {
let content = [0x40u8, 0x00, 0x00, 0x00, 0x00, 0x01];
assert!(matches!(
ExtTime::parse(&content),
Err(Error::InvalidField { .. })
));
}
}