#[cfg(not(feature = "std"))]
use alloc::{vec, vec::Vec};
use crate::error::BzzError;
use crate::zp_impl::ZpDecoder;
const CTX_COUNT: usize = 300;
const FREQ_SLOTS: usize = 4;
const LEVEL_CTXIDS: usize = 3;
pub fn bzz_decode(data: &[u8]) -> Result<Vec<u8>, BzzError> {
let mut zp = ZpDecoder::new(data)?;
let mut output = Vec::new();
let mut block_ctx = [0u8; CTX_COUNT];
loop {
let block_size = decode_raw_bits(&mut zp, 24);
if block_size == 0 {
break;
}
let block = decode_one_block(&mut zp, &mut block_ctx, block_size as usize)?;
output.extend_from_slice(&block);
}
Ok(output)
}
pub fn decode(data: &[u8]) -> Result<Vec<u8>, BzzError> {
bzz_decode(data)
}
fn decode_raw_bits(zp: &mut ZpDecoder, bit_count: u32) -> u32 {
let limit = 1u32 << bit_count;
let mut n = 1u32;
while n < limit {
let bit = zp.decode_passthrough() as u32;
n = (n << 1) | bit;
}
n - limit
}
fn decode_context_bits(zp: &mut ZpDecoder, ctx: &mut [u8], ctx_base: usize, bit_count: u32) -> u32 {
let subtree_offset = ctx_base.wrapping_sub(1);
let limit = 1u32 << bit_count;
let mut n = 1u32;
while n < limit {
let bit = zp.decode_bit(&mut ctx[subtree_offset + n as usize]) as u32;
n = (n << 1) | bit;
}
n - limit
}
fn decode_one_block(
zp: &mut ZpDecoder,
ctx: &mut [u8; CTX_COUNT],
block_size: usize,
) -> Result<Vec<u8>, BzzError> {
let mut freq_shift: u32 = 0;
if zp.decode_passthrough() {
freq_shift += 1;
if zp.decode_passthrough() {
freq_shift += 1;
}
}
let mut mtf_order: [u8; 256] = core::array::from_fn(|i| i as u8);
let mut freq_counts = [0u32; FREQ_SLOTS];
let mut freq_add: u32 = 4;
let mut last_mtf_pos: u32 = 3;
let mut marker_at: Option<usize> = None;
let mut bwt_data = vec![0u8; block_size];
for (sym_idx, output_byte) in bwt_data.iter_mut().enumerate() {
let ctx_id = (last_mtf_pos.min(LEVEL_CTXIDS as u32 - 1)) as usize;
let mtf_position;
let mut ctx_offset: usize = 0;
if zp.decode_bit(&mut ctx[ctx_offset + ctx_id]) {
mtf_position = 0;
} else {
ctx_offset += LEVEL_CTXIDS;
if zp.decode_bit(&mut ctx[ctx_offset + ctx_id]) {
mtf_position = 1;
} else {
ctx_offset += LEVEL_CTXIDS;
if zp.decode_bit(&mut ctx[ctx_offset]) {
mtf_position = 2 + decode_context_bits(zp, ctx, ctx_offset + 1, 1);
} else {
ctx_offset += 2;
if zp.decode_bit(&mut ctx[ctx_offset]) {
mtf_position = 4 + decode_context_bits(zp, ctx, ctx_offset + 1, 2);
} else {
ctx_offset += 4;
if zp.decode_bit(&mut ctx[ctx_offset]) {
mtf_position = 8 + decode_context_bits(zp, ctx, ctx_offset + 1, 3);
} else {
ctx_offset += 8;
if zp.decode_bit(&mut ctx[ctx_offset]) {
mtf_position = 16 + decode_context_bits(zp, ctx, ctx_offset + 1, 4);
} else {
ctx_offset += 16;
if zp.decode_bit(&mut ctx[ctx_offset]) {
mtf_position =
32 + decode_context_bits(zp, ctx, ctx_offset + 1, 5);
} else {
ctx_offset += 32;
if zp.decode_bit(&mut ctx[ctx_offset]) {
mtf_position =
64 + decode_context_bits(zp, ctx, ctx_offset + 1, 6);
} else {
ctx_offset += 64;
if zp.decode_bit(&mut ctx[ctx_offset]) {
mtf_position = 128
+ decode_context_bits(zp, ctx, ctx_offset + 1, 7);
} else {
mtf_position = 256;
}
}
}
}
}
}
}
}
}
last_mtf_pos = mtf_position;
if mtf_position == 256 {
*output_byte = 0;
marker_at = Some(sym_idx);
} else {
let sym = *mtf_order
.get(mtf_position as usize)
.ok_or(BzzError::InvalidBlockSize)?;
*output_byte = sym;
freq_add = freq_add.wrapping_add(freq_add >> freq_shift);
if freq_add > 0x1000_0000 {
freq_add >>= 24;
for f in freq_counts.iter_mut() {
*f >>= 24;
}
}
let mut combined_freq = freq_add;
if (mtf_position as usize) < FREQ_SLOTS {
combined_freq = combined_freq.saturating_add(freq_counts[mtf_position as usize]);
}
let mut insert_at = mtf_position as usize;
while insert_at >= FREQ_SLOTS {
*mtf_order
.get_mut(insert_at)
.ok_or(BzzError::InvalidBlockSize)? = *mtf_order
.get(insert_at - 1)
.ok_or(BzzError::InvalidBlockSize)?;
insert_at -= 1;
}
while insert_at > 0 {
let prev_freq = *freq_counts
.get(insert_at - 1)
.ok_or(BzzError::InvalidBlockSize)?;
if combined_freq >= prev_freq {
*mtf_order
.get_mut(insert_at)
.ok_or(BzzError::InvalidBlockSize)? = *mtf_order
.get(insert_at - 1)
.ok_or(BzzError::InvalidBlockSize)?;
*freq_counts
.get_mut(insert_at)
.ok_or(BzzError::InvalidBlockSize)? = prev_freq;
insert_at -= 1;
} else {
break;
}
}
*mtf_order
.get_mut(insert_at)
.ok_or(BzzError::InvalidBlockSize)? = sym;
if let Some(fc) = freq_counts.get_mut(insert_at) {
*fc = combined_freq;
}
}
}
let marker_pos = marker_at.ok_or(BzzError::MissingMarker)?;
inverse_bwt(&bwt_data, marker_pos)
}
fn inverse_bwt(bwt_data: &[u8], marker_pos: usize) -> Result<Vec<u8>, BzzError> {
let total = bwt_data.len();
if total == 0 {
return Ok(Vec::new());
}
let mut byte_count = [0u32; 256];
let mut rank = vec![0u32; total];
for i in 0..total {
if i == marker_pos {
continue;
}
let byte_val = bwt_data[i] as usize;
rank[i] = ((byte_val as u32) << 24) | (byte_count[byte_val] & 0x00ff_ffff);
byte_count[byte_val] += 1;
}
let mut sorted_start = [0u32; 256];
let mut running = 1u32; for (byte_val, count) in byte_count.iter().enumerate() {
sorted_start[byte_val] = running;
running += count;
}
let output_len = total - 1;
let mut output = vec![0u8; output_len];
let mut follow = 0usize; let mut remaining = output_len;
while remaining > 0 {
let encoded = rank[follow];
let byte_val = (encoded >> 24) as u8;
let occurrence = encoded & 0x00ff_ffff;
remaining -= 1;
output[remaining] = byte_val;
follow = (sorted_start[byte_val as usize] + occurrence) as usize;
}
Ok(output)
}
#[cfg(test)]
mod tests {
use super::*;
fn golden_bzz_path() -> std::path::PathBuf {
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/golden/bzz")
}
#[test]
fn empty_input_returns_error() {
let result = bzz_decode(&[]);
assert!(
result.is_err(),
"expected error for empty input, got {:?}",
result
);
}
#[test]
fn single_byte_does_not_panic() {
let result = bzz_decode(&[0x00]);
assert!(result.is_err(), "expected error for 1-byte input");
}
#[test]
fn bzz_decode_known_short() {
let compressed =
std::fs::read(golden_bzz_path().join("test_short.bzz")).expect("test fixture missing");
let expected =
std::fs::read(golden_bzz_path().join("test_short.txt")).expect("test fixture missing");
let decoded = bzz_decode(&compressed).expect("bzz_decode failed");
assert_eq!(
decoded, expected,
"decoded output does not match expected for test_short"
);
}
#[test]
fn bzz_decode_known_long() {
let compressed =
std::fs::read(golden_bzz_path().join("test_long.bzz")).expect("test fixture missing");
let expected =
std::fs::read(golden_bzz_path().join("test_long.txt")).expect("test fixture missing");
let decoded = bzz_decode(&compressed).expect("bzz_decode failed");
assert_eq!(
decoded, expected,
"decoded output does not match for test_long"
);
}
#[test]
fn bzz_decode_known_1byte() {
let compressed =
std::fs::read(golden_bzz_path().join("test_1byte.bzz")).expect("test fixture missing");
let expected =
std::fs::read(golden_bzz_path().join("test_1byte.txt")).expect("test fixture missing");
let decoded = bzz_decode(&compressed).expect("bzz_decode failed");
assert_eq!(
decoded, expected,
"decoded output does not match for test_1byte"
);
}
#[test]
fn bzz_decode_real_dirm_chunk() {
let compressed = std::fs::read(golden_bzz_path().join("navm_fgbz_dirm.bzz"))
.expect("test fixture missing");
let expected = std::fs::read(golden_bzz_path().join("navm_fgbz_dirm.bin"))
.expect("test fixture missing");
let decoded = bzz_decode(&compressed).expect("bzz_decode failed on real DIRM chunk");
assert!(
!decoded.is_empty(),
"decoded DIRM chunk should not be empty"
);
assert_eq!(
decoded, expected,
"decoded DIRM chunk does not match expected"
);
}
#[test]
fn zp_tables_spot_check() {
use crate::zp_impl::tables::{LPS_NEXT, MPS_NEXT, PROB, THRESHOLD};
assert_eq!(PROB[0], 0x8000, "P[0] should be 0x8000");
assert_eq!(PROB[250], 0x481a, "P[250] should be 0x481a");
assert_eq!(MPS_NEXT[0], 84, "UP[0] should be 84");
assert_eq!(LPS_NEXT[0], 145, "DN[0] should be 145");
assert_eq!(THRESHOLD[83], 0, "M[83] should be 0");
assert_eq!(THRESHOLD[250], 0, "M[250] should be 0");
}
}