const fn make_crc32_table() -> [u32; 256] {
let mut table = [0u32; 256];
let mut i = 0;
while i < 256 {
let mut c = i as u32;
let mut k = 0;
while k < 8 {
c = if c & 1 != 0 { 0xEDB88320 ^ (c >> 1) } else { c >> 1 };
k += 1;
}
table[i] = c;
i += 1;
}
table
}
static CRC32_TABLE: [u32; 256] = make_crc32_table();
pub fn crc32(data: &[u8]) -> u32 {
let mut c = 0xFFFF_FFFFu32;
for &b in data {
c = CRC32_TABLE[((c ^ b as u32) & 0xFF) as usize] ^ (c >> 8);
}
c ^ 0xFFFF_FFFF
}
const fn make_crc64_table() -> [u64; 256] {
let mut table = [0u64; 256];
let mut i = 0;
while i < 256 {
let mut c = i as u64;
let mut k = 0;
while k < 8 {
c = if c & 1 != 0 { 0xC96C5795D7870F42 ^ (c >> 1) } else { c >> 1 };
k += 1;
}
table[i] = c;
i += 1;
}
table
}
static CRC64_TABLE: [u64; 256] = make_crc64_table();
pub fn crc64(data: &[u8]) -> u64 {
let mut c = 0xFFFF_FFFF_FFFF_FFFFu64;
for &b in data {
c = CRC64_TABLE[((c ^ b as u64) & 0xFF) as usize] ^ (c >> 8);
}
c ^ 0xFFFF_FFFF_FFFF_FFFF
}
fn varint(mut v: u64) -> Vec<u8> {
let mut out = Vec::new();
loop {
let mut b = (v & 0x7F) as u8;
v >>= 7;
if v != 0 {
b |= 0x80;
}
out.push(b);
if v == 0 {
break;
}
}
out
}
fn read_varint(data: &[u8], pos: &mut usize) -> Option<u64> {
let mut v: u64 = 0;
let mut shift = 0;
loop {
let b = *data.get(*pos)?;
*pos += 1;
v |= ((b & 0x7F) as u64) << shift;
if b & 0x80 == 0 {
break;
}
shift += 7;
if shift > 63 {
return None;
}
}
Some(v)
}
pub fn encode_stored(data: &[u8]) -> Vec<u8> {
let mut out: Vec<u8> = Vec::new();
out.extend_from_slice(b"\xFD7zXZ\x00");
let flags = [0x00u8, 0x04];
out.extend_from_slice(&flags);
out.extend_from_slice(&crc32(&flags).to_le_bytes());
let mut lzma2: Vec<u8> = Vec::new();
let mut first = true;
for chunk in data.chunks(65536) {
if chunk.is_empty() {
break;
}
lzma2.push(if first { 0x01 } else { 0x02 });
first = false;
let sz = (chunk.len() - 1) as u16;
lzma2.extend_from_slice(&sz.to_be_bytes());
lzma2.extend_from_slice(chunk);
}
lzma2.push(0x00); let check = crc64(data);
let mut hdr_body: Vec<u8> = Vec::new();
hdr_body.push(0x00); hdr_body.push(0x21); hdr_body.push(0x01); hdr_body.push(0x00); let mut total = 1 + hdr_body.len() + 4;
let pad_hdr = (4 - (total % 4)) % 4;
total += pad_hdr;
let mut block_header: Vec<u8> = Vec::new();
block_header.push((total / 4 - 1) as u8);
block_header.extend_from_slice(&hdr_body);
block_header.extend(std::iter::repeat(0u8).take(pad_hdr));
let hcrc = crc32(&block_header);
block_header.extend_from_slice(&hcrc.to_le_bytes());
let block_start = out.len();
out.extend_from_slice(&block_header);
out.extend_from_slice(&lzma2);
let pad_len = (4 - ((block_header.len() + lzma2.len()) % 4)) % 4;
out.extend(std::iter::repeat(0u8).take(pad_len));
out.extend_from_slice(&check.to_le_bytes());
let unpadded = (block_header.len() + lzma2.len() + 8) as u64; let _ = block_start;
let mut index: Vec<u8> = Vec::new();
index.push(0x00);
index.extend_from_slice(&varint(1));
index.extend_from_slice(&varint(unpadded));
index.extend_from_slice(&varint(data.len() as u64));
while index.len() % 4 != 0 {
index.push(0x00);
}
let icrc = crc32(&index);
index.extend_from_slice(&icrc.to_le_bytes());
let backward_size = (index.len() / 4 - 1) as u32;
out.extend_from_slice(&index);
let mut foot_tail: Vec<u8> = Vec::new();
foot_tail.extend_from_slice(&backward_size.to_le_bytes());
foot_tail.extend_from_slice(&flags);
let fcrc = crc32(&foot_tail);
out.extend_from_slice(&fcrc.to_le_bytes());
out.extend_from_slice(&foot_tail);
out.extend_from_slice(b"YZ");
out
}
pub fn decode_stored(stream: &[u8]) -> Result<Vec<u8>, String> {
if stream.len() < 12 || &stream[0..6] != b"\xFD7zXZ\x00" {
return Err("not an xz stream".into());
}
let check_type = stream[7] & 0x0F;
if check_type != 0x04 {
return Err(format!("unsupported check type {:#x} (need CRC64)", check_type));
}
let hdr_size = ((stream[12] as usize) + 1) * 4;
let mut pos = 12 + hdr_size;
let mut out: Vec<u8> = Vec::new();
loop {
let ctrl = *stream.get(pos).ok_or("truncated LZMA2")?;
pos += 1;
if ctrl == 0x00 {
break;
}
if ctrl == 0x01 || ctrl == 0x02 {
let hi = *stream.get(pos).ok_or("truncated chunk")? as usize;
let lo = *stream.get(pos + 1).ok_or("truncated chunk")? as usize;
pos += 2;
let n = (hi << 8 | lo) + 1;
let end = pos + n;
if end > stream.len() {
return Err("chunk overruns stream".into());
}
out.extend_from_slice(&stream[pos..end]);
pos = end;
} else {
return Err(format!("unsupported LZMA2 chunk control {:#x} (compressed chunks need the xz tool)", ctrl));
}
}
let pad = (4 - ((hdr_size + (pos - 12 - hdr_size)) % 4)) % 4;
let _ = pad;
let block_data_len = pos - (12 + hdr_size);
let pad_len = (4 - ((hdr_size + block_data_len) % 4)) % 4;
let check_pos = pos + pad_len;
if check_pos + 8 <= stream.len() {
let stored = u64::from_le_bytes(stream[check_pos..check_pos + 8].try_into().unwrap());
if stored != crc64(&out) {
return Err("block check (CRC64) mismatch".into());
}
}
Ok(out)
}
pub fn unseal(stream: &[u8]) -> Result<Vec<u8>, String> {
match decode_stored(stream) {
Ok(v) => Ok(v),
Err(_) => {
use std::io::Write;
let mut child = std::process::Command::new("xz")
.args(["-d", "-c"])
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.spawn()
.map_err(|e| format!("xz tool unavailable: {}", e))?;
let mut stdin = child.stdin.take().unwrap();
let bytes = stream.to_vec();
let writer = std::thread::spawn(move || {
use std::io::Write;
let _ = stdin.write_all(&bytes);
let _ = stdin.flush();
});
let output = child.wait_with_output().map_err(|e| e.to_string())?;
let _ = writer.join();
if output.status.success() {
Ok(output.stdout)
} else {
Err("xz tool failed to decompress stream".into())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn crc_vectors() {
assert_eq!(crc32(b"123456789"), 0xCBF43926);
assert_eq!(crc64(b"123456789"), 0x995DC9BBDF1939FA);
}
#[test]
fn stored_roundtrip() {
for payload in [&b""[..], b"hello phext", &[7u8; 200_000]] {
let sealed = encode_stored(payload);
assert_eq!(decode_stored(&sealed).unwrap(), payload.to_vec());
}
}
}