use crate::cursor::ManifestCursor;
use crate::drop_record::{parse_drop_record, DropRecord, DROP_RECORD_LEN};
use crate::error::CoreError;
use crate::slab::{parse_slab_header, SlabHeader};
#[derive(Debug, Clone)]
pub struct SlabView<'a> {
bytes: &'a [u8],
header: SlabHeader,
drop_records: Vec<DropRecord>,
solid_window_start: usize,
}
impl SlabView<'_> {
#[must_use]
pub const fn header(&self) -> SlabHeader {
self.header
}
#[must_use]
pub fn drop_records(&self) -> &[DropRecord] {
&self.drop_records
}
#[must_use]
pub const fn solid_window_offset(&self) -> usize {
self.solid_window_start
}
#[must_use]
pub fn find_record(&self, drop_id: &[u8; 32]) -> Option<&DropRecord> {
self.drop_records
.iter()
.find(|r| r.drop_id.as_bytes() == drop_id)
}
#[must_use]
pub fn plaintext_for(&self, drop_id: &[u8; 32]) -> Option<Result<Vec<u8>, CoreError>> {
self.plaintext_for_with_dict_lookup(drop_id, &|_| None)
}
#[must_use]
pub fn plaintext_for_with_dict_lookup(
&self,
drop_id: &[u8; 32],
dict_lookup: &dyn Fn(u8) -> Option<Vec<u8>>,
) -> Option<Result<Vec<u8>, CoreError>> {
let record = self.find_record(drop_id)?;
if record.representation.aead != 0x00 {
return Some(Err(CoreError::UnsupportedFeature {
feature: format!(
"drop aead 0x{:02X} (only plaintext/0x00 supported in v0.1)",
record.representation.aead
),
}));
}
if record.solid_window_index != 0 {
return Some(Err(CoreError::UnsupportedFeature {
feature: format!(
"solid_window_index {} (only single-window slabs supported in v0.1)",
record.solid_window_index
),
}));
}
let offset = usize::try_from(record.offset_in_window).ok()?;
let len = usize::try_from(record.len_in_window).ok()?;
let start = self.solid_window_start.checked_add(offset)?;
let end = start.checked_add(len)?;
if end > self.bytes.len() {
return Some(Err(CoreError::Corrupt {
reason: format!(
"drop range [{start}..{end}] extends past slab length {}",
self.bytes.len()
),
}));
}
let raw = &self.bytes[start..end];
if record.dict_id == crate::drop_record::NO_DICT {
Some(crate::codec::decompress(
record.representation.codec,
raw,
record.plaintext_len,
))
} else {
let Some(dict_bytes) = dict_lookup(record.dict_id) else {
return Some(Err(CoreError::Corrupt {
reason: format!(
"drop references dict_id 0x{:02X} but no dictionary_section provided",
record.dict_id
),
}));
};
Some(crate::codec::zstd_dict::decompress_with_dict(
raw,
record.plaintext_len,
&dict_bytes,
))
}
}
}
pub fn parse_slab(bytes: &[u8]) -> Result<SlabView<'_>, CoreError> {
let mut cursor = ManifestCursor::new(bytes);
let header = parse_slab_header(&mut cursor)?;
let total_length = usize::try_from(header.total_length).map_err(|_| CoreError::Corrupt {
reason: format!("slab total_length {} exceeds usize", header.total_length),
})?;
if total_length != bytes.len() {
return Err(CoreError::Corrupt {
reason: format!(
"slab total_length {total_length} does not match buffer length {}",
bytes.len()
),
});
}
let mut drop_records: Vec<DropRecord> = Vec::new();
let mut window_len_sum: u64 = 0;
loop {
let cursor_pos = u64::try_from(cursor.position()).map_err(|_| CoreError::Corrupt {
reason: format!("slab cursor position {} exceeds u64", cursor.position()),
})?;
let remaining_after_cursor =
header
.total_length
.checked_sub(cursor_pos)
.ok_or_else(|| CoreError::Corrupt {
reason: format!(
"slab cursor position {cursor_pos} past total_length {}",
header.total_length
),
})?;
if remaining_after_cursor == window_len_sum {
break;
}
if remaining_after_cursor < window_len_sum {
return Err(CoreError::Corrupt {
reason: format!(
"slab drop records overran solid window: cursor_pos={cursor_pos}, window_sum={window_len_sum}, total_length={}",
header.total_length
),
});
}
let trailing = remaining_after_cursor - window_len_sum;
if trailing < u64::try_from(DROP_RECORD_LEN).unwrap_or(u64::MAX) {
return Err(CoreError::Corrupt {
reason: format!(
"slab has {trailing} trailing bytes that are neither a full drop record nor accounted for by the solid window"
),
});
}
let record = parse_drop_record(&mut cursor, &header)?;
window_len_sum = window_len_sum
.checked_add(u64::from(record.len_in_window))
.ok_or_else(|| CoreError::Corrupt {
reason: format!(
"slab drop len_in_window sum overflow at record {}",
drop_records.len()
),
})?;
drop_records.push(record);
}
let solid_window_start = cursor.position();
Ok(SlabView {
bytes,
header,
drop_records,
solid_window_start,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::slab::SLAB_HEADER_LEN;
use limnifs_format::DropId;
fn make_slab(drops: &[(&[u8; 32], &[u8])]) -> Vec<u8> {
let mut drop_records = Vec::new();
let mut solid_window = Vec::new();
for (id, plaintext) in drops {
let plaintext_len = u32::try_from(plaintext.len()).unwrap();
let offset_in_window = u32::try_from(solid_window.len()).unwrap();
drop_records.extend_from_slice(*id);
drop_records.extend_from_slice(&plaintext_len.to_le_bytes());
drop_records.extend_from_slice(&[0x00, 0x00, 0x00]); drop_records.push(0x00); drop_records.extend_from_slice(&offset_in_window.to_le_bytes());
drop_records.extend_from_slice(&plaintext_len.to_le_bytes());
drop_records.push(crate::drop_record::NO_DICT); solid_window.extend_from_slice(plaintext);
}
let slab_content = [&drop_records[..], &solid_window[..]].concat();
let total_length = u64::try_from(SLAB_HEADER_LEN + slab_content.len()).unwrap();
let mut bytes = Vec::with_capacity(usize::try_from(total_length).expect("fits usize"));
bytes.extend_from_slice(b"LIM1");
bytes.extend_from_slice(&1u16.to_le_bytes());
bytes.extend_from_slice(&0u64.to_le_bytes()); bytes.extend_from_slice(&[0u8; 32]); bytes.extend_from_slice(&total_length.to_le_bytes());
bytes.push(0x00); bytes.push(0x00); bytes.extend_from_slice(&slab_content);
bytes
}
#[test]
fn parses_empty_slab() {
let bytes = make_slab(&[]);
let view = parse_slab(&bytes).expect("empty slab parses");
assert_eq!(view.drop_records().len(), 0);
}
#[test]
fn parses_single_drop() {
let id = [0xAA; 32];
let plaintext = b"hello world";
let bytes = make_slab(&[(&id, plaintext)]);
let view = parse_slab(&bytes).expect("single-drop slab parses");
assert_eq!(view.drop_records().len(), 1);
let got = view
.plaintext_for(&id)
.expect("drop present")
.expect("store codec ok");
assert_eq!(got, plaintext);
}
#[test]
fn parses_multiple_drops() {
let id1 = [0x11; 32];
let id2 = [0x22; 32];
let id3 = [0x33; 32];
let p1 = b"first drop plaintext";
let p2 = b"second";
let p3 = b"third drop is longer than the others combined";
let bytes = make_slab(&[(&id1, p1), (&id2, p2), (&id3, p3)]);
let view = parse_slab(&bytes).expect("multi-drop slab parses");
assert_eq!(view.drop_records().len(), 3);
assert_eq!(
view.plaintext_for(&id1)
.expect("drop 1 present")
.expect("store codec ok"),
p1
);
assert_eq!(
view.plaintext_for(&id2)
.expect("drop 2 present")
.expect("store codec ok"),
p2
);
assert_eq!(
view.plaintext_for(&id3)
.expect("drop 3 present")
.expect("store codec ok"),
p3
);
}
#[test]
fn missing_drop_returns_none() {
let id = [0xAA; 32];
let bytes = make_slab(&[(&id, b"data")]);
let view = parse_slab(&bytes).expect("slab parses");
let missing = DropId::from_bytes([0xBB; 32]);
assert!(view.plaintext_for(missing.as_bytes()).is_none());
}
#[test]
fn rejects_buffer_length_mismatch() {
let id = [0xAA; 32];
let mut bytes = make_slab(&[(&id, b"data")]);
bytes.truncate(bytes.len() - 1);
match parse_slab(&bytes) {
Err(CoreError::Corrupt { reason }) => {
assert!(
reason.contains("does not match buffer length"),
"got: {reason}"
);
}
other => panic!("expected Corrupt, got {other:?}"),
}
}
#[test]
fn slab_from_writer_round_trips() {
let id1 = [0x11; 32];
let id2 = [0x22; 32];
let p1 = vec![0xAB; 4096];
let p2 = vec![0xCD; 1024];
let bytes = make_slab(&[(&id1, &p1), (&id2, &p2)]);
let view = parse_slab(&bytes).expect("writer-style slab parses");
assert_eq!(view.drop_records().len(), 2);
assert_eq!(
view.plaintext_for(&id1)
.expect("drop 1 present")
.expect("ok"),
&p1[..]
);
assert_eq!(
view.plaintext_for(&id2)
.expect("drop 2 present")
.expect("ok"),
&p2[..]
);
}
}