use crate::check::Check;
use crate::error::{Error, Result};
use crate::io::{Reader, Writer};
pub const LTBL_HEADER_LEN: usize = 16;
pub const LTBL_ENTRY_SIZE_V1: u32 = 28;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct LayerEntry {
pub data_size: u32,
pub first_lrov: u32,
pub first_layr: u32,
pub additional_sector_count: u32,
pub data_offset: u64,
pub sector_id: u32,
}
impl LayerEntry {
pub fn is_empty(&self) -> bool {
self.data_size == 0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LayerTable {
pub table_version: u32,
pub layer_count: u32,
pub entry_size: u32,
pub entry_count: u32,
pub entries: Vec<LayerEntry>,
first: Vec<u32>,
}
impl LayerTable {
pub fn parse(payload: &[u8]) -> Result<LayerTable> {
let mut r = Reader::checked(payload, Check::LtblEntrySize);
let table_version = r.u32()?;
if table_version != 1 {
return Err(Error::new(
Check::LtblVersion,
format!("unsupported table_version {table_version}"),
));
}
let layer_count = r.u32()?;
let entry_size = r.u32()?;
let entry_count = r.u32()?;
if entry_size < LTBL_ENTRY_SIZE_V1 {
return Err(Error::new(
Check::LtblEntrySize,
format!("entry_size {entry_size} is below the {LTBL_ENTRY_SIZE_V1} byte v1 layout"),
));
}
let table_len = (entry_count as usize)
.checked_mul(entry_size as usize)
.ok_or_else(|| Error::new(Check::LtblEntryCount, "the layer table length overflows"))?;
if table_len != r.remaining() {
return Err(Error::new(
Check::LtblEntryCount,
format!(
"entry_count {entry_count} of {entry_size} bytes needs {table_len} bytes, {} remain",
r.remaining()
),
));
}
let mut entries = Vec::with_capacity(entry_count as usize);
for _ in 0..entry_count {
entries.push(LayerEntry {
data_size: r.u32()?,
first_lrov: r.u32()?,
first_layr: r.u32()?,
additional_sector_count: r.u32()?,
data_offset: r.u64()?,
sector_id: r.u32()?,
});
r.skip(entry_size as usize - LTBL_ENTRY_SIZE_V1 as usize)?;
}
let first = layer_index(&entries, layer_count)?;
Ok(LayerTable {
table_version,
layer_count,
entry_size,
entry_count,
entries,
first,
})
}
pub fn new(entries: Vec<LayerEntry>, layer_count: u32) -> Result<LayerTable> {
let first = layer_index(&entries, layer_count)?;
Ok(LayerTable {
table_version: 1,
layer_count,
entry_size: LTBL_ENTRY_SIZE_V1,
entry_count: entries.len() as u32,
entries,
first,
})
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut w = Writer::with_capacity(
LTBL_HEADER_LEN + self.entries.len() * LTBL_ENTRY_SIZE_V1 as usize,
);
w.u32(self.table_version);
w.u32(self.layer_count);
w.u32(LTBL_ENTRY_SIZE_V1);
w.u32(self.entries.len() as u32);
for entry in &self.entries {
w.u32(entry.data_size);
w.u32(entry.first_lrov);
w.u32(entry.first_layr);
w.u32(entry.additional_sector_count);
w.u64(entry.data_offset);
w.u32(entry.sector_id);
}
w.into_vec()
}
pub fn layer_entries(&self, layer: u32) -> &[LayerEntry] {
let Some(&start) = self.first.get(layer as usize) else {
return &[];
};
let start = start as usize;
let count = 1 + self.entries[start].additional_sector_count as usize;
&self.entries[start..start + count]
}
pub fn first_entry(&self, layer: u32) -> Option<&LayerEntry> {
self.layer_entries(layer).first()
}
pub fn entry(&self, layer: u32, sector_id: u32) -> Option<&LayerEntry> {
self.layer_entries(layer)
.iter()
.find(|entry| entry.sector_id == sector_id)
}
pub fn is_multi_sector(&self) -> bool {
(0..self.layer_count).any(|layer| {
self.layer_entries(layer)
.iter()
.filter(|entry| !entry.is_empty())
.count()
> 1
})
}
}
fn layer_index(entries: &[LayerEntry], layer_count: u32) -> Result<Vec<u32>> {
let mut first = Vec::with_capacity(layer_count as usize);
let mut cursor = 0usize;
for layer in 0..layer_count {
if cursor >= entries.len() {
return Err(Error::new(
Check::LtblLayerIndexRange,
format!(
"layer {layer} has no first entry: {} of the {layer_count} layers account \
for every one of the {} entries",
first.len(),
entries.len()
),
));
}
let count = 1 + entries[cursor].additional_sector_count as usize;
if entries.len() - cursor < count {
return Err(Error::new(
Check::LtblEntryCount,
format!(
"layer {layer} declares {count} entries, but only {} remain after {}",
entries.len() - cursor,
cursor
),
));
}
for (k, entry) in entries[cursor..cursor + count].iter().enumerate().skip(1) {
if entry.additional_sector_count != 0 {
return Err(Error::new(
Check::LtblEntryCount,
format!(
"layer {layer}'s entry {k} carries additional_sector_count {}, which only \
a layer's first entry may",
entry.additional_sector_count
),
));
}
}
first.push(cursor as u32);
cursor += count;
}
if cursor != entries.len() {
return Err(Error::new(
Check::LtblEntryCount,
format!(
"{layer_count} layers account for {cursor} entries, but the table declares {}",
entries.len()
),
));
}
for layer in 0..layer_count {
let start = first[layer as usize] as usize;
let count = 1 + entries[start].additional_sector_count as usize;
for pair in entries[start..start + count].windows(2) {
if pair[1].sector_id == pair[0].sector_id {
return Err(Error::new(
Check::LtblSectorIdUnique,
format!("layer {layer} names sector {} twice", pair[1].sector_id),
));
}
if pair[1].sector_id < pair[0].sector_id {
return Err(Error::new(
Check::LtblSectorIdsAscending,
format!(
"layer {layer} names sector {} after sector {}",
pair[1].sector_id, pair[0].sector_id
),
));
}
}
}
for layer in 0..layer_count {
let start = first[layer as usize] as usize;
if entries[start].sector_id != 0 {
return Err(Error::new(
Check::LtblFirstEntryIsSectorZero,
format!(
"layer {layer}'s first entry names sector {}, not the primary sector 0",
entries[start].sector_id
),
));
}
}
Ok(first)
}
#[cfg(test)]
mod tests {
use super::*;
fn entry(
sector_id: u32,
first_layr: u32,
data_offset: u64,
data_size: u32,
additional: u32,
first_lrov: u32,
) -> LayerEntry {
LayerEntry {
data_size,
first_lrov,
first_layr,
additional_sector_count: additional,
data_offset,
sector_id,
}
}
fn table() -> LayerTable {
LayerTable::new(
vec![
entry(0, 3, 0, 5, 1, 0),
entry(1, 4, 0, 7, 0, 6),
entry(0, 3, 5, 0, 0, 0),
],
2,
)
.unwrap()
}
#[test]
fn a_table_round_trips_and_indexes_its_layers() {
let table = table();
assert_eq!(table.table_version, 1);
assert_eq!(table.layer_count, 2);
assert_eq!(table.entry_size, 28);
assert_eq!(table.entry_count, 3);
assert_eq!(table.layer_entries(0).len(), 2);
assert_eq!(table.layer_entries(1).len(), 1);
assert_eq!(table.layer_entries(2).len(), 0, "no such layer");
assert_eq!(table.first_entry(1).unwrap().data_size, 0);
assert_eq!(table.entry(0, 1).unwrap().first_lrov, 6);
assert_eq!(table.entry(0, 2), None);
assert!(table.is_multi_sector(), "layer 0 carries two sectors");
let bytes = table.to_bytes();
assert_eq!(bytes.len(), LTBL_HEADER_LEN + 3 * 28);
let parsed = LayerTable::parse(&bytes).unwrap();
assert_eq!(parsed, table);
}
#[test]
fn a_single_sector_file_is_not_multi_sector() {
let table =
LayerTable::new(vec![entry(0, 3, 0, 5, 0, 0), entry(0, 3, 5, 0, 0, 0)], 2).unwrap();
assert!(!table.is_multi_sector());
let table =
LayerTable::new(vec![entry(0, 3, 0, 5, 1, 0), entry(4, 4, 0, 0, 0, 7)], 1).unwrap();
assert!(!table.is_multi_sector(), "sector 4 holds no bytes");
}
#[test]
fn the_entry_accounting_is_enforced() {
let rejects = |entries: Vec<LayerEntry>, layers: u32, check: Check| {
assert_eq!(LayerTable::new(entries, layers).unwrap_err().check(), check);
};
rejects(vec![entry(0, 3, 0, 5, 1, 0)], 1, Check::LtblEntryCount);
rejects(
vec![entry(0, 3, 0, 5, 1, 0), entry(1, 4, 0, 7, 1, 0)],
1,
Check::LtblEntryCount,
);
rejects(
vec![entry(0, 3, 0, 5, 0, 0), entry(0, 3, 5, 1, 0, 0)],
1,
Check::LtblEntryCount,
);
rejects(vec![entry(0, 3, 0, 5, 0, 0)], 2, Check::LtblLayerIndexRange);
rejects(
vec![entry(3, 3, 0, 5, 0, 0)],
1,
Check::LtblFirstEntryIsSectorZero,
);
rejects(
vec![
entry(0, 3, 0, 5, 2, 0),
entry(2, 3, 5, 1, 0, 0),
entry(1, 3, 6, 1, 0, 0),
],
1,
Check::LtblSectorIdsAscending,
);
rejects(
vec![
entry(0, 3, 0, 5, 2, 0),
entry(1, 3, 5, 1, 0, 0),
entry(1, 3, 6, 1, 0, 0),
],
1,
Check::LtblSectorIdUnique,
);
}
#[test]
fn a_table_whose_bytes_disagree_with_its_header_is_rejected() {
let bytes = table().to_bytes();
let mut short = bytes.clone();
short[12..16].copy_from_slice(&2u32.to_le_bytes());
assert_eq!(
LayerTable::parse(&short).unwrap_err().check(),
Check::LtblEntryCount
);
let mut over = bytes.clone();
over[12..16].copy_from_slice(&9u32.to_le_bytes());
assert_eq!(
LayerTable::parse(&over).unwrap_err().check(),
Check::LtblEntryCount
);
let mut long = bytes.clone();
long[16 + 12..16 + 16].copy_from_slice(&9u32.to_le_bytes());
assert_eq!(
LayerTable::parse(&long).unwrap_err().check(),
Check::LtblEntryCount
);
}
#[test]
fn a_bad_version_entry_size_or_stride_is_rejected() {
let mut bytes = table().to_bytes();
let mut version = bytes.clone();
version[0..4].copy_from_slice(&2u32.to_le_bytes());
assert_eq!(
LayerTable::parse(&version).unwrap_err().check(),
Check::LtblVersion
);
let mut small = bytes.clone();
small[8..12].copy_from_slice(&27u32.to_le_bytes());
assert_eq!(
LayerTable::parse(&small).unwrap_err().check(),
Check::LtblEntrySize
);
bytes[12..16].copy_from_slice(&9u32.to_le_bytes());
assert_eq!(
LayerTable::parse(&bytes).unwrap_err().check(),
Check::LtblEntryCount
);
}
#[test]
fn a_wider_entry_strides_past_unknown_fields() {
let mut w = Writer::new();
w.u32(1);
w.u32(1);
w.u32(32);
w.u32(2);
for (sector, offset, size, additional) in [(0u32, 0u64, 5u32, 1u32), (2, 5, 7, 0)] {
w.u32(size);
w.u32(0);
w.u32(3);
w.u32(additional);
w.u64(offset);
w.u32(sector);
w.u32(0xDEAD_BEEF); }
let parsed = LayerTable::parse(&w.into_vec()).unwrap();
assert_eq!(parsed.entry_size, 32);
assert_eq!(parsed.layer_entries(0).len(), 2);
assert_eq!(parsed.entry(0, 2).unwrap().data_offset, 5);
}
}