use crate::descriptors::{descriptor_body, write_descriptor_header};
use crate::error::{Error, Result};
use alloc::vec::Vec;
use broadcast_common::{Parse, Serialize};
pub const TAG: u8 = 0x0D;
const HEADER_LEN: usize = 2;
const ENTRY_LEN: usize = 4;
const DII_IDENTIFICATION_MASK: u16 = 0x7FFF;
const RESERVED_BIT: u16 = 0x8000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct DiiLocation {
pub reserved_future_use: bool,
pub dii_identification: u16,
pub association_tag: u16,
}
impl DiiLocation {
#[must_use]
pub const fn new(dii_identification: u16, association_tag: u16) -> Self {
Self {
reserved_future_use: true,
dii_identification,
association_tag,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct DiiLocationDescriptor {
pub transport_protocol_label: u8,
pub locations: Vec<DiiLocation>,
}
impl<'a> Parse<'a> for DiiLocationDescriptor {
type Error = crate::error::Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = descriptor_body(
bytes,
TAG,
"DiiLocationDescriptor",
"unexpected tag for DII_location_descriptor",
)?;
let (&transport_protocol_label, rest) =
body.split_first().ok_or(Error::InvalidDescriptor {
tag: TAG,
reason: "DII_location_descriptor missing transport_protocol_label",
})?;
if rest.len() % ENTRY_LEN != 0 {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "DII_location_descriptor entry loop is not a multiple of 4 bytes",
});
}
let locations = rest
.chunks_exact(ENTRY_LEN)
.map(|c| {
let id = u16::from_be_bytes([c[0], c[1]]);
DiiLocation {
reserved_future_use: id & RESERVED_BIT != 0,
dii_identification: id & DII_IDENTIFICATION_MASK,
association_tag: u16::from_be_bytes([c[2], c[3]]),
}
})
.collect();
Ok(Self {
transport_protocol_label,
locations,
})
}
}
impl Serialize for DiiLocationDescriptor {
type Error = crate::error::Error;
fn serialized_len(&self) -> usize {
HEADER_LEN + 1 + self.locations.len() * ENTRY_LEN
}
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(),
});
}
write_descriptor_header(buf, TAG, len - HEADER_LEN)?;
buf[2] = self.transport_protocol_label;
let mut pos = 3;
for l in &self.locations {
let id = broadcast_common::len::fit_mask(
l.dii_identification,
DII_IDENTIFICATION_MASK,
"DII_identification",
)?;
let word = id | (u16::from(l.reserved_future_use) * RESERVED_BIT);
buf[pos..pos + 2].copy_from_slice(&word.to_be_bytes());
buf[pos + 2..pos + ENTRY_LEN].copy_from_slice(&l.association_tag.to_be_bytes());
pos += ENTRY_LEN;
}
Ok(len)
}
}
impl crate::traits::DescriptorDef<'_> for DiiLocationDescriptor {
const TAG: u8 = TAG;
const NAME: &'static str = "DII_LOCATION";
}
#[cfg(test)]
mod tests {
use super::*;
const BYTES: [u8; 11] = [TAG, 9, 0x02, 0x81, 0x23, 0x04, 0x56, 0x7F, 0xFF, 0x00, 0x01];
#[test]
fn parse_fields() {
let d = DiiLocationDescriptor::parse(&BYTES).unwrap();
assert_eq!(d.transport_protocol_label, 2);
assert_eq!(d.locations.len(), 2);
assert!(d.locations[0].reserved_future_use);
assert_eq!(d.locations[0].dii_identification, 0x0123);
assert_eq!(d.locations[0].association_tag, 0x0456);
assert!(!d.locations[1].reserved_future_use);
assert_eq!(d.locations[1].dii_identification, 0x7FFF);
}
#[test]
fn round_trip_byte_identical_both_reserved_values() {
let d = DiiLocationDescriptor::parse(&BYTES).unwrap();
let mut out = vec![0u8; d.serialized_len()];
d.serialize_into(&mut out).unwrap();
assert_eq!(out, BYTES);
assert_eq!(DiiLocationDescriptor::parse(&out).unwrap(), d);
}
#[test]
fn builder_default_reserved_and_errors() {
let d = DiiLocationDescriptor {
transport_protocol_label: 0,
locations: vec![DiiLocation::new(1, 2)],
};
let mut out = vec![0u8; d.serialized_len()];
d.serialize_into(&mut out).unwrap();
assert_eq!(out, [TAG, 5, 0, 0x80, 0x01, 0x00, 0x02]);
assert!(DiiLocationDescriptor::parse(&[TAG, 0]).is_err());
assert!(DiiLocationDescriptor::parse(&[TAG, 4, 0, 1, 2, 3]).is_err());
let bad = DiiLocationDescriptor {
transport_protocol_label: 0,
locations: vec![DiiLocation::new(0x8000, 0)],
};
let mut out = vec![0u8; bad.serialized_len()];
assert!(bad.serialize_into(&mut out).is_err());
}
}