extern crate alloc;
use alloc::vec::Vec;
use broadcast_common::{Parse, Serialize};
use crate::ber::{ber_length_size, decode_ber_length, encode_ber_length};
use crate::error::{Error, Result};
use crate::types::{UlBytes, parse_uid_batch, serialize_uid_batch, ul_bytes_from_prefix};
const PARTITION_KEY_PREFIX: [u8; 7] = [0x06, 0x0E, 0x2B, 0x34, 0x02, 0x05, 0x01];
const PARTITION_KEY_MID: [u8; 4] = [0x0D, 0x01, 0x02, 0x01];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum PartitionKind {
Header,
Body,
Footer,
}
impl PartitionKind {
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::Header => "Header Partition",
Self::Body => "Body Partition",
Self::Footer => "Footer Partition",
}
}
fn from_byte(b: u8) -> Result<Self> {
match b {
0x02 => Ok(Self::Header),
0x03 => Ok(Self::Body),
0x04 => Ok(Self::Footer),
other => Err(Error::UnknownPartitionKind { byte: other }),
}
}
fn to_byte(self) -> u8 {
match self {
Self::Header => 0x02,
Self::Body => 0x03,
Self::Footer => 0x04,
}
}
}
broadcast_common::impl_spec_display!(PartitionKind);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum PartitionStatus {
OpenIncomplete,
ClosedIncomplete,
OpenComplete,
ClosedComplete,
}
impl PartitionStatus {
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::OpenIncomplete => "open and incomplete",
Self::ClosedIncomplete => "closed and incomplete",
Self::OpenComplete => "open and complete",
Self::ClosedComplete => "closed and complete",
}
}
#[must_use]
pub fn is_open(&self) -> bool {
matches!(self, Self::OpenIncomplete | Self::OpenComplete)
}
fn from_byte(b: u8) -> Result<Self> {
match b {
0x01 => Ok(Self::OpenIncomplete),
0x02 => Ok(Self::ClosedIncomplete),
0x03 => Ok(Self::OpenComplete),
0x04 => Ok(Self::ClosedComplete),
other => Err(Error::UnknownPartitionStatus { byte: other }),
}
}
fn to_byte(self) -> u8 {
match self {
Self::OpenIncomplete => 0x01,
Self::ClosedIncomplete => 0x02,
Self::OpenComplete => 0x03,
Self::ClosedComplete => 0x04,
}
}
}
broadcast_common::impl_spec_display!(PartitionStatus);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PartitionPack {
pub kind: PartitionKind,
pub status: PartitionStatus,
pub major_version: u16,
pub minor_version: u16,
pub kag_size: u32,
pub this_partition: u64,
pub previous_partition: u64,
pub footer_partition: u64,
pub header_byte_count: u64,
pub index_byte_count: u64,
pub index_sid: u32,
pub body_offset: u64,
pub body_sid: u32,
pub operational_pattern: UlBytes,
pub essence_containers: Vec<UlBytes>,
}
impl PartitionPack {
#[must_use]
pub fn key(kind: PartitionKind, status: PartitionStatus) -> UlBytes {
let mut key = [0u8; 16];
key[0..7].copy_from_slice(&PARTITION_KEY_PREFIX);
key[7] = 0x01;
key[8..12].copy_from_slice(&PARTITION_KEY_MID);
key[12] = 0x01; key[13] = kind.to_byte();
key[14] = status.to_byte();
key[15] = 0x00;
key
}
#[must_use]
pub fn is_partition_key(key: &UlBytes) -> bool {
key[0..7] == PARTITION_KEY_PREFIX
&& key[8..12] == PARTITION_KEY_MID
&& key[12] == 0x01
&& PartitionKind::from_byte(key[13]).is_ok()
}
fn parse_key(key: &UlBytes) -> Result<(PartitionKind, PartitionStatus)> {
if key[0..7] != PARTITION_KEY_PREFIX || key[8..12] != PARTITION_KEY_MID || key[12] != 0x01 {
return Err(Error::KeyPrefixMismatch {
what: "Partition Pack (Table 4)",
});
}
let kind = PartitionKind::from_byte(key[13])?;
let status = PartitionStatus::from_byte(key[14])?;
if kind == PartitionKind::Footer && status.is_open() {
return Err(Error::OpenFooterPartition { byte: key[14] });
}
Ok((kind, status))
}
}
impl<'a> Parse<'a> for PartitionPack {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
if bytes.len() < 16 {
return Err(Error::BufferTooShort {
need: 16,
have: bytes.len(),
what: "Partition Pack key",
});
}
let key: UlBytes = ul_bytes_from_prefix(bytes);
let (kind, status) = Self::parse_key(&key)?;
let (len, len_size) = decode_ber_length(&bytes[16..])?;
let value_start = 16 + len_size;
let len = len as usize;
let value_end = value_start.checked_add(len).ok_or(Error::BufferTooShort {
need: usize::MAX,
have: bytes.len(),
what: "Partition Pack value (length overflow)",
})?;
if bytes.len() < value_end {
return Err(Error::BufferTooShort {
need: value_end,
have: bytes.len(),
what: "Partition Pack value",
});
}
let v = &bytes[value_start..value_end];
const FIXED_FIELDS_LEN: usize = 2 + 2 + 4 + 8 + 8 + 8 + 8 + 8 + 4 + 8 + 4 + 16;
if v.len() < FIXED_FIELDS_LEN {
return Err(Error::BufferTooShort {
need: FIXED_FIELDS_LEN,
have: v.len(),
what: "Partition Pack fixed fields",
});
}
let u16_at = |o: usize| u16::from_be_bytes([v[o], v[o + 1]]);
let u32_at = |o: usize| u32::from_be_bytes([v[o], v[o + 1], v[o + 2], v[o + 3]]);
let u64_at = |o: usize| {
u64::from_be_bytes([
v[o],
v[o + 1],
v[o + 2],
v[o + 3],
v[o + 4],
v[o + 5],
v[o + 6],
v[o + 7],
])
};
let major_version = u16_at(0);
let minor_version = u16_at(2);
let kag_size = u32_at(4);
let this_partition = u64_at(8);
let previous_partition = u64_at(16);
let footer_partition = u64_at(24);
let header_byte_count = u64_at(32);
let index_byte_count = u64_at(40);
let index_sid = u32_at(48);
let body_offset = u64_at(52);
let body_sid = u32_at(60);
let operational_pattern: UlBytes = ul_bytes_from_prefix(&v[64..]);
let essence_containers = parse_uid_batch(&v[80..])?;
Ok(PartitionPack {
kind,
status,
major_version,
minor_version,
kag_size,
this_partition,
previous_partition,
footer_partition,
header_byte_count,
index_byte_count,
index_sid,
body_offset,
body_sid,
operational_pattern,
essence_containers,
})
}
}
impl Serialize for PartitionPack {
type Error = Error;
fn serialized_len(&self) -> usize {
let value_len =
2 + 2 + 4 + 8 + 8 + 8 + 8 + 8 + 4 + 8 + 4 + 16 + 8 + self.essence_containers.len() * 16;
16 + ber_length_size(value_len as u64) + value_len
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
if self.kind == PartitionKind::Footer && self.status.is_open() {
return Err(Error::OpenFooterPartition {
byte: self.status.to_byte(),
});
}
let total = self.serialized_len();
if buf.len() < total {
return Err(Error::BufferTooShort {
need: total,
have: buf.len(),
what: "Partition Pack",
});
}
buf[0..16].copy_from_slice(&Self::key(self.kind, self.status));
let batch = serialize_uid_batch(&self.essence_containers);
let value_len = 2 + 2 + 4 + 8 + 8 + 8 + 8 + 8 + 4 + 8 + 4 + 16 + batch.len();
let len_size = encode_ber_length(value_len as u64, &mut buf[16..])?;
let mut pos = 16 + len_size;
buf[pos..pos + 2].copy_from_slice(&self.major_version.to_be_bytes());
pos += 2;
buf[pos..pos + 2].copy_from_slice(&self.minor_version.to_be_bytes());
pos += 2;
buf[pos..pos + 4].copy_from_slice(&self.kag_size.to_be_bytes());
pos += 4;
buf[pos..pos + 8].copy_from_slice(&self.this_partition.to_be_bytes());
pos += 8;
buf[pos..pos + 8].copy_from_slice(&self.previous_partition.to_be_bytes());
pos += 8;
buf[pos..pos + 8].copy_from_slice(&self.footer_partition.to_be_bytes());
pos += 8;
buf[pos..pos + 8].copy_from_slice(&self.header_byte_count.to_be_bytes());
pos += 8;
buf[pos..pos + 8].copy_from_slice(&self.index_byte_count.to_be_bytes());
pos += 8;
buf[pos..pos + 4].copy_from_slice(&self.index_sid.to_be_bytes());
pos += 4;
buf[pos..pos + 8].copy_from_slice(&self.body_offset.to_be_bytes());
pos += 8;
buf[pos..pos + 4].copy_from_slice(&self.body_sid.to_be_bytes());
pos += 4;
buf[pos..pos + 16].copy_from_slice(&self.operational_pattern);
pos += 16;
buf[pos..pos + batch.len()].copy_from_slice(&batch);
pos += batch.len();
Ok(pos)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample(kind: PartitionKind, status: PartitionStatus) -> PartitionPack {
PartitionPack {
kind,
status,
major_version: 1,
minor_version: 3,
kag_size: 512,
this_partition: 0,
previous_partition: 0,
footer_partition: 12345,
header_byte_count: 1000,
index_byte_count: 0,
index_sid: 0,
body_offset: 0,
body_sid: 1,
operational_pattern: [0xAA; 16],
essence_containers: alloc::vec![[0xBBu8; 16]],
}
}
#[test]
fn header_partition_round_trip() {
let pp = sample(PartitionKind::Header, PartitionStatus::ClosedComplete);
let mut buf = alloc::vec![0u8; pp.serialized_len()];
pp.serialize_into(&mut buf).unwrap();
let parsed = PartitionPack::parse(&buf).unwrap();
assert_eq!(parsed, pp);
}
#[test]
fn body_partition_round_trip_empty_essence_containers() {
let mut pp = sample(PartitionKind::Body, PartitionStatus::OpenIncomplete);
pp.essence_containers.clear();
let mut buf = alloc::vec![0u8; pp.serialized_len()];
pp.serialize_into(&mut buf).unwrap();
let parsed = PartitionPack::parse(&buf).unwrap();
assert_eq!(parsed, pp);
assert!(parsed.essence_containers.is_empty());
}
#[test]
fn footer_partition_cannot_be_open() {
let pp = sample(PartitionKind::Footer, PartitionStatus::OpenComplete);
let mut buf = alloc::vec![0u8; pp.serialized_len()];
assert!(matches!(
pp.serialize_into(&mut buf),
Err(Error::OpenFooterPartition { .. })
));
}
#[test]
fn parse_rejects_open_footer_key() {
let key = PartitionPack::key(PartitionKind::Footer, PartitionStatus::OpenComplete);
let mut bytes = alloc::vec::Vec::new();
bytes.extend_from_slice(&key);
bytes.push(0); assert!(matches!(
PartitionPack::parse(&bytes),
Err(Error::OpenFooterPartition { .. })
));
}
#[test]
fn unknown_kind_byte_rejected() {
assert!(matches!(
PartitionKind::from_byte(0xFF),
Err(Error::UnknownPartitionKind { byte: 0xFF })
));
}
}