diff --git a/src/lib.rs b/src/lib.rs index 8633c3c..730b3df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +use std::ffi::CStr; + struct Bitstream<'a> { data: &'a [u8], pos_bit: usize, @@ -9,15 +11,18 @@ impl Bitstream<'_> { let mut result = 0; + let mut read = 0; + while len > 0 { if self.data.is_empty() { return Err(Error::Incomplete); } 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)) as u16; + result |= (((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; 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) -> 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)] pub enum Error { ReservedBlockType, Incomplete, InvalidCode, + InvalidDistance, + InvalidLength, } // https://datatracker.ietf.org/doc/html/rfc1951 @@ -347,10 +419,6 @@ pub fn inflate(data: &[u8], out: &mut Vec) -> Result<(), Error> { let mut compute_value = |first_code, extra_bits_amount, value_start| -> Result { 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) @@ -399,7 +467,19 @@ pub fn inflate(data: &[u8], out: &mut Vec) -> Result<(), Error> { _ => 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), } @@ -416,25 +496,75 @@ pub fn inflate(data: &[u8], out: &mut Vec) -> Result<(), Error> { #[cfg(test)] mod tests { - use crate::HuffmanTree; + mod helpers { + use crate::HuffmanTree; - #[test] - fn bitstream() { - let bytes = [0b110_010_01_u8, 0b010_111_01]; - let mut stream = super::Bitstream { - data: &bytes, - pos_bit: 0, - }; - assert_eq!(stream.read_bits(2).unwrap(), 0b01); - assert_eq!(stream.read_bits(3).unwrap(), 0b010); - assert_eq!(stream.read_bits(5).unwrap(), 0b11001); - assert_eq!(stream.read_bits(3).unwrap(), 0b111); - assert_eq!(stream.read_bits(3).unwrap(), 0b010); + #[test] + fn bitstream_single() { + let bytes = [0b10010_u8]; + let mut stream = crate::Bitstream { + data: &bytes, + pos_bit: 0, + }; + assert_eq!(stream.read_bits(1).unwrap(), 0); + assert_eq!(stream.read_bits(1).unwrap(), 1); + assert_eq!(stream.read_bits(1).unwrap(), 0); + assert_eq!(stream.read_bits(1).unwrap(), 0); + 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] - fn decode() { - let lengths = HuffmanTree::fixed_lit_len_code_lengths(); - HuffmanTree::from_lengths(&lengths); + mod decode { + #[test] + fn cargo_toml_gzip() { + 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] +"#) + } } } diff --git a/src/main.rs b/src/main.rs index a518d7f..3823109 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,31 +1,16 @@ -use std::ffi::CStr; - 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(); assert_eq!(gz[0], 31, "ID"); assert_eq!(gz[1], 139, "ID"); assert_eq!(gz[2], 8, "compression method"); - let flg = gz[3]; + let mut out: Vec = Vec::new(); - assert!(flg == 8 || flg == 0); // only FLG.FNAME - - 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); + zwergli::inflate_gzip(&gz, &mut out).unwrap(); dbg!(String::from_utf8(out)).ok(); }