static work

This commit is contained in:
nora 2026-06-08 21:22:02 +02:00
parent 8e1d90e0ce
commit 00ec110aac
2 changed files with 160 additions and 45 deletions

View file

@ -1,3 +1,5 @@
use std::ffi::CStr;
struct Bitstream<'a> { struct Bitstream<'a> {
data: &'a [u8], data: &'a [u8],
pos_bit: usize, pos_bit: usize,
@ -9,15 +11,18 @@ impl Bitstream<'_> {
let mut result = 0; let mut result = 0;
let mut read = 0;
while len > 0 { while len > 0 {
if self.data.is_empty() { if self.data.is_empty() {
return Err(Error::Incomplete); return Err(Error::Incomplete);
} }
let to_read_from_current_byte = std::cmp::min(len % 8, 8 - self.pos_bit); let to_read_from_current_byte = std::cmp::min(len % 8, 8 - self.pos_bit);
result <<= to_read_from_current_byte; result |= (((self.data[0] >> self.pos_bit) & ((1 << to_read_from_current_byte) - 1))
result |= as u16)
((self.data[0] >> self.pos_bit) & ((1 << to_read_from_current_byte) - 1)) as u16; << read;
read += to_read_from_current_byte;
len -= to_read_from_current_byte; len -= to_read_from_current_byte;
self.pos_bit += to_read_from_current_byte; self.pos_bit += to_read_from_current_byte;
@ -235,11 +240,78 @@ impl HuffmanTree {
} }
} }
#[derive(Debug)]
pub enum GzipError {
InvalidHeader,
UnexpectedHeaderValue {
value: u8,
expected: u8,
kind: &'static str,
},
Deflate(Error),
}
pub fn inflate_gzip(data: &[u8], out: &mut Vec<u8>) -> Result<(), GzipError> {
if data.len() < 10 {
return Err(GzipError::InvalidHeader);
}
if data[0] != 31 {
return Err(GzipError::UnexpectedHeaderValue {
value: data[0],
expected: 31,
kind: "ID",
});
}
if data[1] != 139 {
return Err(GzipError::UnexpectedHeaderValue {
value: data[1],
expected: 31,
kind: "ID",
});
}
if data[2] != 8 {
return Err(GzipError::UnexpectedHeaderValue {
value: data[2],
expected: 8,
kind: "compression method",
});
}
let flg = data[3];
if flg != 8 && flg != 0 {
return Err(GzipError::UnexpectedHeaderValue {
value: flg,
expected: 8,
kind: "FLG",
});
}
let mut data_start = 10;
if flg & 0b1000 != 0 {
let fname =
CStr::from_bytes_until_nul(&data[10..]).map_err(|_| GzipError::InvalidHeader)?;
data_start += fname.count_bytes() + 1;
}
let blocks = &data.get((data_start)..).ok_or(GzipError::InvalidHeader)?;
let blocks = &blocks
.get(..(blocks.len() - 8))
.ok_or(GzipError::InvalidHeader)?; // crc32 and isize
inflate(blocks, out).map_err(GzipError::Deflate)?;
Ok(())
}
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
ReservedBlockType, ReservedBlockType,
Incomplete, Incomplete,
InvalidCode, InvalidCode,
InvalidDistance,
InvalidLength,
} }
// https://datatracker.ietf.org/doc/html/rfc1951 // https://datatracker.ietf.org/doc/html/rfc1951
@ -347,10 +419,6 @@ pub fn inflate(data: &[u8], out: &mut Vec<u8>) -> Result<(), Error> {
let mut compute_value = let mut compute_value =
|first_code, extra_bits_amount, value_start| -> Result<u16, Error> { |first_code, extra_bits_amount, value_start| -> Result<u16, Error> {
assert_eq!(extra_bits_amount, 3); assert_eq!(extra_bits_amount, 3);
dbg!(data.read_bits(1).unwrap());
dbg!(data.read_bits(1).unwrap());
dbg!(data.read_bits(1).unwrap());
std::process::exit(1);
// TODO: read_bits is broken (i suspect the byte parts are reversed) // TODO: read_bits is broken (i suspect the byte parts are reversed)
@ -399,7 +467,19 @@ pub fn inflate(data: &[u8], out: &mut Vec<u8>) -> Result<(), Error> {
_ => return Err(Error::InvalidCode), _ => return Err(Error::InvalidCode),
}?; }?;
dbg!((length, dist_value, distance)); let base = out
.len()
.checked_sub(distance as usize)
.ok_or(Error::InvalidDistance)?;
let length = length as usize;
let end = base + length;
if end >= out.len() {
return Err(Error::InvalidLength);
}
out.extend_from_within(base..end);
} }
286.. => return Err(Error::InvalidCode), 286.. => return Err(Error::InvalidCode),
} }
@ -416,25 +496,75 @@ pub fn inflate(data: &[u8], out: &mut Vec<u8>) -> Result<(), Error> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::HuffmanTree; mod helpers {
use crate::HuffmanTree;
#[test] #[test]
fn bitstream() { fn bitstream_single() {
let bytes = [0b110_010_01_u8, 0b010_111_01]; let bytes = [0b10010_u8];
let mut stream = super::Bitstream { let mut stream = crate::Bitstream {
data: &bytes, data: &bytes,
pos_bit: 0, pos_bit: 0,
}; };
assert_eq!(stream.read_bits(2).unwrap(), 0b01); assert_eq!(stream.read_bits(1).unwrap(), 0);
assert_eq!(stream.read_bits(3).unwrap(), 0b010); assert_eq!(stream.read_bits(1).unwrap(), 1);
assert_eq!(stream.read_bits(5).unwrap(), 0b11001); assert_eq!(stream.read_bits(1).unwrap(), 0);
assert_eq!(stream.read_bits(3).unwrap(), 0b111); assert_eq!(stream.read_bits(1).unwrap(), 0);
assert_eq!(stream.read_bits(3).unwrap(), 0b010); assert_eq!(stream.read_bits(1).unwrap(), 1);
}
#[test]
fn bitstream_extra_bits() {
let bytes = [0b1110_u8];
let mut stream = crate::Bitstream {
data: &bytes,
pos_bit: 0,
};
assert_eq!(stream.read_bits(4).unwrap(), 0b1110);
}
#[test]
fn bitstream_extra_bits_byte_overlap() {
let bytes = [0b10_000000, 0b000000_11];
let mut stream = crate::Bitstream {
data: &bytes,
pos_bit: 0,
};
stream.read_bits(6).unwrap();
assert_eq!(stream.read_bits(4).unwrap(), 0b1110);
}
#[test]
fn decode() {
let lengths = HuffmanTree::fixed_lit_len_code_lengths();
HuffmanTree::from_lengths(&lengths);
}
} }
#[test] mod decode {
fn decode() { #[test]
let lengths = HuffmanTree::fixed_lit_len_code_lengths(); fn cargo_toml_gzip() {
HuffmanTree::from_lengths(&lengths); let data = [
0x1f, 0x8b, 0x08, 0x08, 0x7f, 0x27, 0xb4, 0x68, 0x00, 0x03, 0x43, 0x61,
0x72, 0x67, 0x6f, 0x2e, 0x74, 0x6f, 0x6d, 0x6c, 0x00, 0x8b, 0x2e, 0x48,
0x4c, 0xce, 0x4e, 0x4c, 0x4f, 0x8d, 0xe5, 0xca, 0x4b, 0xcc, 0x4d, 0x55,
0xb0, 0x55, 0x50, 0xaa, 0x2a, 0x4f, 0x2d, 0x4a, 0xcf, 0xc9, 0x54, 0xe2,
0x2a, 0x4b, 0x2d, 0x2a, 0xce, 0xcc, 0xcf, 0x03, 0x89, 0x19, 0xe8, 0x19,
0xea, 0x19, 0x28, 0x71, 0xa5, 0xa6, 0x64, 0x96, 0x40, 0x45, 0x8c, 0x0c,
0x8c, 0x4c, 0x94, 0xb8, 0xb8, 0xa2, 0x53, 0x52, 0x0b, 0x52, 0xf3, 0x52,
0x52, 0xf3, 0x92, 0x33, 0x53, 0x8b, 0x63, 0xb9, 0x00, 0x5f, 0x81, 0x28,
0x59, 0x4e, 0x00, 0x00, 0x00
];
let mut out = Vec::new();
crate::inflate_gzip(&data, &mut out).unwrap();
let out = String::from_utf8(out).unwrap();
assert_eq!(out, r#"[package]
name = "zwergli"
version = "0.1.0"
edition = "2024"
[dependencies]
"#)
}
} }
} }

View file

@ -1,31 +1,16 @@
use std::ffi::CStr;
fn main() { fn main() {
let gz = std::env::args().nth(1).expect("must provide a cli argument"); let gz = std::env::args()
.nth(1)
.expect("must provide a cli argument");
let gz = std::fs::read(gz).unwrap(); let gz = std::fs::read(gz).unwrap();
assert_eq!(gz[0], 31, "ID"); assert_eq!(gz[0], 31, "ID");
assert_eq!(gz[1], 139, "ID"); assert_eq!(gz[1], 139, "ID");
assert_eq!(gz[2], 8, "compression method"); assert_eq!(gz[2], 8, "compression method");
let flg = gz[3]; let mut out: Vec<u8> = Vec::new();
assert!(flg == 8 || flg == 0); // only FLG.FNAME zwergli::inflate_gzip(&gz, &mut out).unwrap();
let mut data_start = 10;
if flg & 0b1000 != 0 {
let fname = CStr::from_bytes_until_nul(&gz[10..]).unwrap();
dbg!(fname);
data_start += fname.count_bytes() + 1;
}
let blocks = &gz[(data_start)..];
let blocks = &blocks[..(blocks.len() - 8)]; // crc32 and isize
let mut out = Vec::new();
zwergli::inflate(blocks, &mut out);
dbg!(String::from_utf8(out)).ok(); dbg!(String::from_utf8(out)).ok();
} }