#[path = "../tests/common/mod.rs"]
mod common;
use common::*;
use mibz2::bits::Bits;
use mibz2::block::{read_item, Item, BLOCK_MAGIC};
use mibz2::{decode_block, Kernels, Scratch};
use std::path::Path;
use std::process::{Command, Stdio};
fn main() {
let args: Vec<String> = std::env::args().collect();
let out_dir = Path::new(&args[1]);
std::fs::create_dir_all(out_dir).unwrap();
let (mut total_blocks, mut total_bytes, mut failed) = (0usize, 0usize, 0usize);
for path in &args[2..] {
let data = std::fs::read(path).unwrap();
let name = Path::new(path)
.file_stem()
.unwrap()
.to_string_lossy()
.to_string();
let first = (0..data.len() as u64 * 8 - 48).find(|&b| {
let mut br = Bits::at(&data, b);
(br.bits(24) as u64) << 24 | br.bits(24) as u64 == BLOCK_MAGIC
});
let Some(mut bit) = first else {
println!("{name}: no block");
continue;
};
let mut level = 9; let mut blocks: Vec<(u64, u64, u32)> = Vec::new();
let mut want = Vec::new();
let mut scratch: Scratch = Scratch::default();
let mut syms = Vec::new();
let mut streams = 0;
loop {
let mut br = Bits::at(&data, bit);
let item = read_item(&mut br, level, syms);
if br.overran() {
break; }
let end = br.bit_pos();
match item {
Ok(Item::Block(b)) => {
let crc = decode_block(&b, Kernels::detect(), &mut scratch, &mut want)
.unwrap_or_else(|k| panic!("{name}: block at bit {bit}: {k}"));
blocks.push((bit, end, crc));
syms = b.syms;
bit = end;
}
Ok(Item::End(_)) => {
streams += 1;
let at = end.div_ceil(8) as usize;
match data.get(at..at + 4) {
Some([b'B', b'Z', b'h', l @ b'1'..=b'9']) => {
level = (l - b'0') as u32;
bit = (at as u64 + 4) * 8;
syms = Vec::new();
}
None => break,
Some(_) => {
println!(
"{name}: {} bytes after a stream's end, not a stream",
data.len() - at
);
break;
}
}
}
Err((k, _)) => panic!("{name}: at bit {bit}: {k}"),
}
}
let mut w = BitWriter::default();
for &c in b"BZh9" {
w.put(c as u64, 8);
}
for &(s, e, _) in &blocks {
let mut br = Bits::at(&data, s);
let mut left = e - s;
while left > 0 {
let n = left.min(32) as u32;
w.put(br.bits(n) as u64, n);
left -= n as u64;
}
}
w.put(0x1772_4538_5090, 48);
w.put(mibz2::combine(blocks.iter().map(|b| b.2)) as u64, 32);
let stream = w.finish();
let file = out_dir.join(format!("{name}.blocks.bz2"));
std::fs::write(&file, &stream).unwrap();
let mut checks = Vec::new();
checks.push((
"libbzip2".to_string(),
oracle(&stream).map_err(|e| e.to_string()),
));
let lb = Command::new("lbzip2")
.args(["-dc", "-n4"])
.arg(&file)
.stderr(Stdio::inherit())
.output()
.unwrap();
checks.push((
"lbzip2".to_string(),
if lb.status.success() {
Ok(lb.stdout)
} else {
Err("lbzip2 failed".into())
},
));
for c in candidates() {
checks.push((
c.name(),
try_decode(c.as_ref(), &stream).unwrap_or_else(Err),
));
}
let mut ranged = Vec::new();
let r = mibz2::decode_blocks(
&data,
blocks[0].0,
blocks.last().unwrap().1,
level,
Kernels::detect(),
&mut ranged,
);
checks.push((
"mibz2 decode_blocks".to_string(),
r.map(|_| ranged).map_err(|e| e.to_string()),
));
let bad: Vec<&str> = checks
.iter()
.filter(|(_, r)| r.as_ref() != Ok(&want))
.map(|(n, _)| n.as_str())
.collect();
failed += !bad.is_empty() as usize;
total_blocks += blocks.len();
total_bytes += want.len();
println!(
"{name}: {} whole blocks (bits {}..{}), {} streams ended, {} bytes decoded, every block CRC checked: {}",
blocks.len(),
blocks[0].0,
blocks.last().unwrap().1,
streams,
want.len(),
if bad.is_empty() { format!("identical in {}", checks.iter().map(|c| c.0.as_str()).collect::<Vec<_>>().join(", ")) } else { format!("DIFFERENT in {bad:?}") }
);
}
println!("{total_blocks} blocks, {total_bytes} bytes, {failed} windows failed");
std::process::exit(if failed == 0 { 0 } else { 1 });
}