use std::sync::atomic::{AtomicU64, Ordering};
use crate::codec::{self, CodecTunables};
use crate::error::CoreError;
static FRAMES_DECODED: AtomicU64 = AtomicU64::new(0);
#[must_use]
pub fn frames_decoded() -> u64 {
FRAMES_DECODED.load(Ordering::Relaxed)
}
pub(crate) fn count_frame_decode() {
FRAMES_DECODED.fetch_add(1, Ordering::Relaxed);
}
#[must_use]
pub fn frame_key(drop_id: &[u8; 32], frame_index: u32) -> [u8; 32] {
let mut buf = [0u8; 36];
buf[..32].copy_from_slice(drop_id);
buf[32..36].copy_from_slice(&frame_index.to_le_bytes());
crate::merkle::hash_section(&buf)
}
pub const SEEKABLE_MAGIC: [u8; 4] = *b"LMSK";
pub const SEEKABLE_VERSION: u16 = 1;
const FOOTER_TAIL_LEN: usize = 10;
pub const SEEKABLE_FRAME_SIZE: usize = 256 * 1024;
pub const SEEKABLE_EMISSION_THRESHOLD: usize = 1024 * 1024;
pub const DROP_FLAG_SEEKABLE: u8 = 0x01;
#[must_use]
pub fn is_seekable_codec(codec_id: u8) -> bool {
matches!(
codec_id,
codec::CODEC_LZ4
| codec::CODEC_LZ4_HC
| codec::CODEC_ZSTD
| codec::CODEC_XZ
| codec::CODEC_BROTLI
| codec::CODEC_DEFLATE
| codec::CODEC_SNAPPY
| codec::CODEC_BZIP2
| codec::CODEC_PPMD
| codec::CODEC_PPMD8
| codec::CODEC_LIBDEFLATE
| codec::CODEC_DEFLATE64
)
}
pub fn encode_seekable(
codec_id: u8,
plaintext: &[u8],
tunables: &CodecTunables,
) -> Result<Vec<u8>, CoreError> {
let chunks: Vec<&[u8]> = if plaintext.is_empty() {
vec![&[][..]]
} else {
plaintext.chunks(SEEKABLE_FRAME_SIZE).collect()
};
let frame_count = chunks.len();
let mut body = Vec::with_capacity(plaintext.len() / 2 + frame_count * 8);
let mut comp_lens = Vec::with_capacity(frame_count);
for frame in &chunks {
let compressed = codec::compress_with_tunables(codec_id, frame, tunables)?;
comp_lens.push(
u32::try_from(compressed.len()).map_err(|_| CoreError::Corrupt {
reason: "seekable frame compressed length exceeds u32".into(),
})?,
);
body.extend_from_slice(&compressed);
}
let mut out = body;
for (frame, comp_len) in chunks.iter().zip(&comp_lens) {
out.extend_from_slice(
&u32::try_from(frame.len())
.expect("frame plaintext fits u32")
.to_le_bytes(),
);
out.extend_from_slice(&comp_len.to_le_bytes());
}
out.extend_from_slice(&SEEKABLE_MAGIC);
out.extend_from_slice(&SEEKABLE_VERSION.to_le_bytes());
out.extend_from_slice(
&u32::try_from(frame_count)
.expect("frame count fits u32")
.to_le_bytes(),
);
Ok(out)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SeekFooter {
pub uncomp_lens: Vec<u32>,
pub comp_lens: Vec<u32>,
starts: Vec<u64>,
total_uncomp: u64,
}
impl SeekFooter {
#[must_use]
pub fn uncomp_offset(&self, i: usize) -> u64 {
self.starts[i.min(self.starts.len() - 1)]
}
#[must_use]
pub const fn total_uncomp(&self) -> u64 {
self.total_uncomp
}
#[must_use]
pub fn total_comp(&self) -> u64 {
self.comp_lens.iter().map(|&l| u64::from(l)).sum()
}
#[must_use]
pub fn frame_containing(&self, off: u64) -> usize {
self.starts.partition_point(|&s| s <= off).saturating_sub(1)
}
#[must_use]
pub fn compressed_offset_of(&self, i: usize) -> usize {
self.comp_lens[..i].iter().map(|&l| l as usize).sum()
}
}
pub(crate) fn parse_footer(container: &[u8]) -> Result<SeekFooter, CoreError> {
let corrupt = |reason: String| CoreError::Corrupt {
reason: format!("seekable container: {reason}"),
};
if container.len() < 8 + FOOTER_TAIL_LEN {
return Err(corrupt(format!(
"length {} too small for one frame entry + footer",
container.len()
)));
}
let tail = &container[container.len() - FOOTER_TAIL_LEN..];
if tail[..4] != SEEKABLE_MAGIC {
return Err(corrupt("footer magic is not LMSK".into()));
}
let version = u16::from_le_bytes([tail[4], tail[5]]);
if version != SEEKABLE_VERSION {
return Err(corrupt(format!(
"footer version {version} (supported: {SEEKABLE_VERSION})"
)));
}
let frame_count = u32::from_le_bytes([tail[6], tail[7], tail[8], tail[9]]) as usize;
let Some(table_start) = (container.len() - FOOTER_TAIL_LEN).checked_sub(frame_count * 8) else {
return Err(corrupt(format!(
"frame_count {frame_count} overruns container length {}",
container.len()
)));
};
let mut uncomp_lens = Vec::with_capacity(frame_count);
let mut comp_lens = Vec::with_capacity(frame_count);
for i in 0..frame_count {
let e = table_start + i * 8;
uncomp_lens.push(u32::from_le_bytes([
container[e],
container[e + 1],
container[e + 2],
container[e + 3],
]));
comp_lens.push(u32::from_le_bytes([
container[e + 4],
container[e + 5],
container[e + 6],
container[e + 7],
]));
}
let mut starts = Vec::with_capacity(uncomp_lens.len() + 1);
let mut acc = 0u64;
for &l in &uncomp_lens {
starts.push(acc);
acc += u64::from(l);
}
starts.push(acc);
let total_uncomp = acc;
let footer = SeekFooter {
uncomp_lens,
comp_lens,
starts,
total_uncomp,
};
let expected_len = frame_count * 8 + FOOTER_TAIL_LEN;
let body_len = table_start;
if footer.total_comp() != body_len as u64 {
return Err(corrupt(format!(
"frame lengths sum to {} compressed bytes but container has {}",
footer.total_comp(),
body_len
)));
}
if footer.uncomp_lens.iter().any(|&l| l == 0) && footer.uncomp_lens.len() > 1 {
return Err(corrupt("zero-length frame in multi-frame container".into()));
}
Ok(footer)
}
pub fn decode_seekable(
codec_id: u8,
container: &[u8],
expected_len: u32,
) -> Result<Vec<u8>, CoreError> {
let footer = parse_footer(container)?;
if footer.total_uncomp() != u64::from(expected_len) {
return Err(CoreError::Corrupt {
reason: format!(
"seekable container: frames cover {} plaintext bytes, drop record says {expected_len}",
footer.total_uncomp()
),
});
}
let mut out = Vec::with_capacity(expected_len as usize);
let mut pos = 0usize;
for (i, &comp_len) in footer.comp_lens.iter().enumerate() {
let frame = &container[pos..pos + comp_len as usize];
FRAMES_DECODED.fetch_add(1, Ordering::Relaxed);
out.extend_from_slice(&codec::decompress(codec_id, frame, footer.uncomp_lens[i])?);
pos += comp_len as usize;
}
Ok(out)
}
pub fn decode_seekable_range(
codec_id: u8,
container: &[u8],
off: u64,
len: usize,
) -> Result<Vec<u8>, CoreError> {
let footer = parse_footer(container)?;
let total = footer.total_uncomp();
if off > total || off + len as u64 > total {
return Err(CoreError::Corrupt {
reason: format!(
"seekable range [{off}, {}) outside plaintext length {total}",
off + len as u64
),
});
}
let first = footer.frame_containing(off);
let mut out = Vec::with_capacity(len);
let mut comp_pos = footer.compressed_offset_of(first);
let mut cum = footer.uncomp_offset(first);
for i in first..footer.uncomp_lens.len() {
let uncomp_len = footer.uncomp_lens[i];
let comp_len = footer.comp_lens[i] as usize;
let frame = &container[comp_pos..comp_pos + comp_len];
FRAMES_DECODED.fetch_add(1, Ordering::Relaxed);
let decoded = codec::decompress(codec_id, frame, uncomp_len)?;
let slice_from = off.saturating_sub(cum) as usize;
let slice_to = ((off + len as u64) - cum).min(u64::from(uncomp_len)) as usize;
out.extend_from_slice(&decoded[slice_from..slice_to]);
if cum + u64::from(uncomp_len) >= off + len as u64 {
break;
}
comp_pos += comp_len;
cum += u64::from(uncomp_len);
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
fn tunables() -> CodecTunables {
CodecTunables::default()
}
fn payload(len: usize) -> Vec<u8> {
let mut state = 0x0123_4567_89AB_CDEFu64;
(0..len)
.map(|_| {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
(state >> 56) as u8
})
.collect()
}
#[test]
fn round_trip_full() {
let pt = payload(700 * 1024); let container = encode_seekable(codec::CODEC_LZ4, &pt, &tunables()).expect("encode");
let decoded =
decode_seekable(codec::CODEC_LZ4, &container, pt.len() as u32).expect("decode");
assert_eq!(decoded, pt);
let footer = parse_footer(&container).expect("footer");
assert_eq!(footer.uncomp_lens.len(), 3);
assert_eq!(footer.uncomp_lens[0] as usize, SEEKABLE_FRAME_SIZE);
}
#[test]
fn range_reads_match_payload() {
let pt = payload(600 * 1024);
let container = encode_seekable(codec::CODEC_ZSTD, &pt, &tunables()).expect("encode");
for (off, len) in [
(0usize, 8usize),
(100 * 1024, 4096),
(250 * 1024, 300 * 1024), (599 * 1024, 1024),
] {
let got = decode_seekable_range(codec::CODEC_ZSTD, &container, off as u64, len)
.expect("range");
assert_eq!(got, pt[off..off + len], "off={off} len={len}");
}
}
#[test]
fn bad_magic_rejected() {
let pt = payload(10);
let mut container = encode_seekable(codec::CODEC_LZ4, &pt, &tunables()).expect("encode");
let last = container.len() - 1;
container[last] ^= 0xFF;
assert!(decode_seekable(codec::CODEC_LZ4, &container, 10).is_err());
}
#[test]
fn length_mismatch_rejected() {
let pt = payload(10);
let container = encode_seekable(codec::CODEC_LZ4, &pt, &tunables()).expect("encode");
assert!(decode_seekable(codec::CODEC_LZ4, &container, 11).is_err());
}
#[test]
fn seekable_codec_classification() {
assert!(is_seekable_codec(codec::CODEC_LZ4));
assert!(is_seekable_codec(codec::CODEC_ZSTD));
assert!(is_seekable_codec(codec::CODEC_BROTLI));
assert!(!is_seekable_codec(codec::CODEC_STORE));
assert!(!is_seekable_codec(codec::CODEC_FLAC));
assert!(!is_seekable_codec(codec::CODEC_RICEPP));
assert!(!is_seekable_codec(codec::CODEC_FSST_BROTLI));
}
}