diff --git a/fuzz/fuzz_targets/common.rs b/fuzz/fuzz_targets/common.rs index b097619..e1d1aee 100644 --- a/fuzz/fuzz_targets/common.rs +++ b/fuzz/fuzz_targets/common.rs @@ -11,9 +11,9 @@ pub struct FuzzInput { impl std::fmt::Debug for FuzzInput { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("FuzzInput") - .field("data_length", &self.data.len()) + f.debug_struct("FuzzInput") .field("codec", &self.codec) + .field("data", &HexSlice(&self.data)) .finish() } } @@ -117,3 +117,26 @@ impl From for BoxedCppCodec { } } } + +pub struct HexSlice<'a>(pub &'a [u32]); + +impl<'a> std::fmt::Debug for HexSlice<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + const MAX: usize = 20; + + let total = self.0.len(); + let shown = total.min(MAX); + + let mut list = f.debug_list(); + + for v in &self.0[..shown] { + list.entry(&format_args!("{:#010x}", v)); + } + + if total > MAX { + list.entry(&format_args!(".. out of {} total", total)); + } + + list.finish() + } +} diff --git a/fuzz/fuzz_targets/rust_compress_oracle.rs b/fuzz/fuzz_targets/rust_compress_oracle.rs index 6ed1f1b..6895173 100644 --- a/fuzz/fuzz_targets/rust_compress_oracle.rs +++ b/fuzz/fuzz_targets/rust_compress_oracle.rs @@ -13,11 +13,6 @@ fuzz_target!(|data: FuzzInput| { return; } - // TODO: Behaviour differs - if data.codec == RustCodec::VariableByte { - return; - } - // TODO: To make the encoder not crash -> Skip inputs smaller than block size let block_size = match data.codec { RustCodec::FastPFOR256 => 256, @@ -44,7 +39,7 @@ fuzz_target!(|data: FuzzInput| { .expect("Rust compression failed"); // Compress with C++ implementation - let cpp_result = match data.codec { + let compressed_oracle_from_cpp = match data.codec { RustCodec::FastPFOR256 => { let mut cpp_codec = cpp::FastPFor256Codec::new(); cpp_codec @@ -58,7 +53,7 @@ fuzz_target!(|data: FuzzInput| { .expect("C++ compression failed") } RustCodec::VariableByte => { - let mut cpp_codec = cpp::VByteCodec::new(); + let mut cpp_codec = cpp::MaskedVByteCodec::new(); cpp_codec .compress_to_slice(input, &mut cpp_compressed) .expect("C++ compression failed") @@ -74,13 +69,13 @@ fuzz_target!(|data: FuzzInput| { // Compare compressed outputs assert_eq!( rust_result.len(), - cpp_result.len(), + compressed_oracle_from_cpp.len(), "Compressed length mismatch: Rust={}, C++={}", rust_result.len(), - cpp_result.len() + compressed_oracle_from_cpp.len() ); - for (i, (&rust_val, &cpp_val)) in rust_result.iter().zip(cpp_result.iter()).enumerate() { + for (i, (&rust_val, &cpp_val)) in rust_result.iter().zip(compressed_oracle_from_cpp.iter()).enumerate() { assert_eq!( rust_val, cpp_val, "Compressed data mismatch at position {}: Rust={}, C++={}", diff --git a/fuzz/fuzz_targets/rust_decompress_oracle.rs b/fuzz/fuzz_targets/rust_decompress_oracle.rs index 1ccf20f..a7311a7 100644 --- a/fuzz/fuzz_targets/rust_decompress_oracle.rs +++ b/fuzz/fuzz_targets/rust_decompress_oracle.rs @@ -1,6 +1,6 @@ #![no_main] -use fastpfor::{CodecToSlice, cpp, rust}; +use fastpfor::{cpp, rust, CodecToSlice}; use libfuzzer_sys::fuzz_target; mod common; use common::*; @@ -13,11 +13,6 @@ fuzz_target!(|data: FuzzInput| { return; } - // TODO: Behaviour differs - if data.codec == RustCodec::VariableByte { - return; - } - // TODO: To make the decoder not crash -> Skip inputs smaller than block size let block_size = match data.codec { RustCodec::FastPFOR256 => 256, @@ -35,7 +30,7 @@ fuzz_target!(|data: FuzzInput| { // First, compress with C++ implementation to get valid compressed data let mut cpp_compressed = vec![0u32; input.len() * 2 + 1024]; - let compressed_data = match data.codec { + let compressed_oracle_from_cpp = match data.codec { RustCodec::FastPFOR256 => { let mut cpp_codec = cpp::FastPFor256Codec::new(); cpp_codec @@ -49,7 +44,7 @@ fuzz_target!(|data: FuzzInput| { .expect("C++ compression failed") } RustCodec::VariableByte => { - let mut cpp_codec = cpp::VByteCodec::new(); + let mut cpp_codec = cpp::MaskedVByteCodec::new(); cpp_codec .compress_to_slice(input, &mut cpp_compressed) .expect("C++ compression failed") @@ -66,7 +61,7 @@ fuzz_target!(|data: FuzzInput| { let mut rust_decompressed = vec![0u32; input.len()]; let mut rust_codec = rust::Codec::from(data.codec); let rust_result = rust_codec - .decompress_to_slice(compressed_data, &mut rust_decompressed) + .decompress_to_slice(compressed_oracle_from_cpp, &mut rust_decompressed) .expect("Rust decompression failed"); // Compare decompressed outputs diff --git a/src/rust/integer_compression/helpers.rs b/src/rust/integer_compression/helpers.rs index a4881bb..9efee81 100644 --- a/src/rust/integer_compression/helpers.rs +++ b/src/rust/integer_compression/helpers.rs @@ -35,15 +35,3 @@ fn bitlen(x: u64) -> i32 { } 64 - clz(x) as i32 } - -/// Extracts 7 bits from `val` at the position specified by `i` (0-indexed, each position is 7 bits). -/// The result is masked to ensure only 7 bits are returned. -pub fn extract7bits(i: i32, val: i64) -> u8 { - ((val >> (7 * i)) & ((1 << 7) - 1)) as u8 -} - -/// Extracts 7 bits from `val` at the position specified by `i` without masking. -/// Caller must ensure proper masking if needed. -pub fn extract_7bits_maskless(i: i32, val: i64) -> u8 { - (val >> (7 * i)) as u8 -} diff --git a/src/rust/integer_compression/variable_byte.rs b/src/rust/integer_compression/variable_byte.rs index da3db14..1bd4a37 100644 --- a/src/rust/integer_compression/variable_byte.rs +++ b/src/rust/integer_compression/variable_byte.rs @@ -1,15 +1,25 @@ use std::io::Cursor; -use bytes::{Buf as _, BufMut as _, BytesMut}; - use crate::rust::cursor::IncrementCursor; -use crate::rust::integer_compression::helpers::{extract7bits, extract_7bits_maskless}; use crate::rust::{FastPForError, FastPForResult, Integer, Skippable}; /// Variable-byte encoding codec for integer compression. #[derive(Debug)] pub struct VariableByte; +// Helper functions with const generics for extracting 7-bit chunks +impl VariableByte { + /// Extract 7 bits from position i (with masking) + const fn extract_7bits(val: u32) -> u8 { + ((val >> (7 * I)) & ((1 << 7) - 1)) as u8 + } + + /// Extract 7 bits from position i (without masking, for last byte) + const fn extract_7bits_maskless(val: u32) -> u8 { + (val >> (7 * I)) as u8 + } +} + // Implemented for consistency with other codecs impl VariableByte { /// Creates a new instance @@ -38,40 +48,54 @@ impl Skippable for VariableByte { // Return early if there is no data to compress return Ok(()); } - let mut buf = BytesMut::with_capacity(input_length as usize * 8); + + // Get byte view of the output buffer + let output_start = output_offset.position() as usize; + let output_bytes: &mut [u8] = unsafe { + std::slice::from_raw_parts_mut( + output[output_start..].as_mut_ptr().cast::(), + (output.len() - output_start) * 4, + ) + }; + + let mut byte_pos = 0; for k in input_offset.position()..(input_offset.position() + u64::from(input_length)) { - let val = i64::from(input[k as usize]); + let val = input[k as usize]; if val < (1 << 7) { - buf.put_u8((val | (1 << 7)) as u8); + output_bytes[byte_pos] = Self::extract_7bits::<0>(val); + byte_pos += 1; } else if val < (1 << 14) { - buf.put_u8(extract7bits(0, val)); - buf.put_u8(extract_7bits_maskless(1, val) | (1 << 7)); + output_bytes[byte_pos] = Self::extract_7bits::<0>(val) | (1 << 7); + output_bytes[byte_pos + 1] = Self::extract_7bits_maskless::<1>(val); + byte_pos += 2; } else if val < (1 << 21) { - buf.put_u8(extract7bits(0, val)); - buf.put_u8(extract7bits(1, val)); - buf.put_u8(extract_7bits_maskless(2, val) | (1 << 7)); + output_bytes[byte_pos] = Self::extract_7bits::<0>(val) | (1 << 7); + output_bytes[byte_pos + 1] = Self::extract_7bits::<1>(val) | (1 << 7); + output_bytes[byte_pos + 2] = Self::extract_7bits_maskless::<2>(val); + byte_pos += 3; } else if val < (1 << 28) { - buf.put_u8(extract7bits(0, val)); - buf.put_u8(extract7bits(1, val)); - buf.put_u8(extract7bits(2, val)); - buf.put_u8(extract_7bits_maskless(3, val) | (1 << 7)); + output_bytes[byte_pos] = Self::extract_7bits::<0>(val) | (1 << 7); + output_bytes[byte_pos + 1] = Self::extract_7bits::<1>(val) | (1 << 7); + output_bytes[byte_pos + 2] = Self::extract_7bits::<2>(val) | (1 << 7); + output_bytes[byte_pos + 3] = Self::extract_7bits_maskless::<3>(val); + byte_pos += 4; } else { - buf.put_u8(extract7bits(0, val)); - buf.put_u8(extract7bits(1, val)); - buf.put_u8(extract7bits(2, val)); - buf.put_u8(extract7bits(3, val)); - buf.put_u8(extract_7bits_maskless(4, val) | (1 << 7)); + output_bytes[byte_pos] = Self::extract_7bits::<0>(val) | (1 << 7); + output_bytes[byte_pos + 1] = Self::extract_7bits::<1>(val) | (1 << 7); + output_bytes[byte_pos + 2] = Self::extract_7bits::<2>(val) | (1 << 7); + output_bytes[byte_pos + 3] = Self::extract_7bits::<3>(val) | (1 << 7); + output_bytes[byte_pos + 4] = Self::extract_7bits_maskless::<4>(val); + byte_pos += 5; } } - while buf.len() % 4 != 0 { - buf.put_u8(0); - } - let length = buf.len(); - let output_position = output_offset.position() as usize; - for it in output.iter_mut().skip(output_position).take(length / 4) { - *it = buf.get_u32_le(); + + // Pad to 4-byte alignment with 0xFF + while byte_pos % 4 != 0 { + output_bytes[byte_pos] = 0xFF; + byte_pos += 1; } - output_offset.add(length as u32 / 4); + + output_offset.add(byte_pos as u32 / 4); input_offset.add(input_length); Ok(()) @@ -111,32 +135,68 @@ impl Integer for VariableByte { output: &mut [u32], output_offset: &mut Cursor, ) -> FastPForResult<()> { - let mut s = 0; - let mut val = 0; - let mut p = input_offset.position() as u32; - let final_p = input_offset.position() as u32 + input_length; - let mut tmp_outpos = output_offset.position(); - let mut shift = 0; - let mut v = 0; + if input_length == 0 { + return Ok(()); + } - while p < final_p { - val = input[p as usize]; - let c = val >> s; - s += 8; - p += s >> 5; - s &= 31; - shift = shift.min(31); // for safety - v += (c & 127) << shift; - if (c & 128) == 128 { - output[tmp_outpos as usize] = v; - tmp_outpos += 1; - v = 0; - shift = 0; - } else { + // Convert u32 array to byte view + let byte_length = (input_length as usize) * 4; + let input_start = input_offset.position() as usize; + + // Create a byte slice view of the input + let input_bytes: &[u8] = unsafe { + std::slice::from_raw_parts(input[input_start..].as_ptr().cast::(), byte_length) + }; + + let mut byte_pos = 0; + let mut tmp_outpos = output_offset.position() as usize; + + // Fast path: process while we have at least 10 bytes remaining + while byte_pos + 10 <= byte_length { + let mut v: u32 = 0; + let mut bytes_read = 0; + + // Decode up to 5 bytes for a u32 value + for i in 0..5 { + let c = input_bytes[byte_pos + i]; + + if i < 4 { + // For bytes 0-3, use 7 bits each + v |= u32::from(c & 0x7F) << (i * 7); + if c < 128 { + bytes_read = i + 1; + break; + } + } else { + // For byte 4, use only 4 bits (total: 7*4 + 4 = 32 bits) + v |= u32::from(c & 0x0F) << 28; + bytes_read = 5; + } + } + + byte_pos += bytes_read; + output[tmp_outpos] = v; + tmp_outpos += 1; + } + + // Slow path: process remaining bytes + while byte_pos < byte_length { + let mut shift = 0; + let mut v: u32 = 0; + while byte_pos < byte_length { + let c = input_bytes[byte_pos]; + byte_pos += 1; + v += u32::from(c & 127) << shift; + if c < 128 { + output[tmp_outpos] = v; + tmp_outpos += 1; + break; + } shift += 7; } } - output_offset.set_position(tmp_outpos); + + output_offset.set_position(tmp_outpos as u64); input_offset.add(input_length); Ok(()) @@ -158,41 +218,41 @@ impl Integer for VariableByte { } let mut out_pos_tmp = output_offset.position(); for k in input_offset.position() as u32..(input_offset.position() as u32 + input_length) { - let val = i64::from(input[k as usize]); + let val = input[k as usize]; if val < (1 << 7) { - output[out_pos_tmp as usize] = (val | (1 << 7)) as i8; + output[out_pos_tmp as usize] = Self::extract_7bits::<0>(val) as i8; out_pos_tmp += 1; } else if val < (1 << 14) { - output[out_pos_tmp as usize] = extract7bits(0, val) as i8; + output[out_pos_tmp as usize] = (Self::extract_7bits::<0>(val) | (1 << 7)) as i8; out_pos_tmp += 1; - output[out_pos_tmp as usize] = (extract_7bits_maskless(1, val) | (1 << 7)) as i8; + output[out_pos_tmp as usize] = Self::extract_7bits_maskless::<1>(val) as i8; out_pos_tmp += 1; } else if val < (1 << 21) { - output[out_pos_tmp as usize] = extract7bits(0, val) as i8; + output[out_pos_tmp as usize] = (Self::extract_7bits::<0>(val) | (1 << 7)) as i8; out_pos_tmp += 1; - output[out_pos_tmp as usize] = extract7bits(1, val) as i8; + output[out_pos_tmp as usize] = (Self::extract_7bits::<1>(val) | (1 << 7)) as i8; out_pos_tmp += 1; - output[out_pos_tmp as usize] = (extract_7bits_maskless(2, val) | (1 << 7)) as i8; + output[out_pos_tmp as usize] = Self::extract_7bits_maskless::<2>(val) as i8; out_pos_tmp += 1; } else if val < (1 << 28) { - output[out_pos_tmp as usize] = extract7bits(0, val) as i8; + output[out_pos_tmp as usize] = (Self::extract_7bits::<0>(val) | (1 << 7)) as i8; out_pos_tmp += 1; - output[out_pos_tmp as usize] = extract7bits(1, val) as i8; + output[out_pos_tmp as usize] = (Self::extract_7bits::<1>(val) | (1 << 7)) as i8; out_pos_tmp += 1; - output[out_pos_tmp as usize] = extract7bits(2, val) as i8; + output[out_pos_tmp as usize] = (Self::extract_7bits::<2>(val) | (1 << 7)) as i8; out_pos_tmp += 1; - output[out_pos_tmp as usize] = (extract_7bits_maskless(3, val) | (1 << 7)) as i8; + output[out_pos_tmp as usize] = Self::extract_7bits_maskless::<3>(val) as i8; out_pos_tmp += 1; } else { - output[out_pos_tmp as usize] = extract7bits(0, val) as i8; + output[out_pos_tmp as usize] = (Self::extract_7bits::<0>(val) | (1 << 7)) as i8; out_pos_tmp += 1; - output[out_pos_tmp as usize] = extract7bits(1, val) as i8; + output[out_pos_tmp as usize] = (Self::extract_7bits::<1>(val) | (1 << 7)) as i8; out_pos_tmp += 1; - output[out_pos_tmp as usize] = extract7bits(2, val) as i8; + output[out_pos_tmp as usize] = (Self::extract_7bits::<2>(val) | (1 << 7)) as i8; out_pos_tmp += 1; - output[out_pos_tmp as usize] = extract7bits(3, val) as i8; + output[out_pos_tmp as usize] = (Self::extract_7bits::<3>(val) | (1 << 7)) as i8; out_pos_tmp += 1; - output[out_pos_tmp as usize] = (extract_7bits_maskless(4, val) | (1 << 7)) as i8; + output[out_pos_tmp as usize] = Self::extract_7bits_maskless::<4>(val) as i8; out_pos_tmp += 1; } } @@ -211,29 +271,45 @@ impl Integer for VariableByte { let mut p = input_offset.position() as u32; let final_p = input_offset.position() as u32 + input_length; let mut tmp_outpos = output_offset.position(); - let mut v = 0; + while p < final_p { - v = i32::from(input[p as usize]); - if input[p as usize] < 0 { + let mut v = i32::from(input[p as usize] & 0x7F); + if input[p as usize] >= 0 { + // High bit is NOT set, this is the last byte p += 1; + output[tmp_outpos as usize] = v as u32; + tmp_outpos += 1; continue; } - v |= i32::from(input[p as usize + 1]) << 7; - if input[p as usize + 1] < 0 { + + v |= i32::from(input[p as usize + 1] & 0x7F) << 7; + if input[p as usize + 1] >= 0 { + // High bit is NOT set, this is the last byte p += 2; + output[tmp_outpos as usize] = v as u32; + tmp_outpos += 1; continue; } - v |= i32::from(input[p as usize + 2]) << 14; - if input[p as usize + 2] < 0 { + + v |= i32::from(input[p as usize + 2] & 0x7F) << 14; + if input[p as usize + 2] >= 0 { + // High bit is NOT set, this is the last byte p += 3; + output[tmp_outpos as usize] = v as u32; + tmp_outpos += 1; continue; } - v |= i32::from(input[p as usize + 3]) << 21; - if input[p as usize + 3] < 0 { + + v |= i32::from(input[p as usize + 3] & 0x7F) << 21; + if input[p as usize + 3] >= 0 { + // High bit is NOT set, this is the last byte p += 4; + output[tmp_outpos as usize] = v as u32; + tmp_outpos += 1; continue; } - v |= i32::from(input[p as usize + 4]) << 28; + + v |= i32::from(input[p as usize + 4] & 0x0F) << 28; p += 5; output[tmp_outpos as usize] = v as u32; tmp_outpos += 1; @@ -248,53 +324,223 @@ impl Integer for VariableByte { mod tests { use super::*; - #[test] - fn test_empty_int_array() { - let input: Vec = vec![]; - let mut output: Vec = vec![]; + fn verify_u32_roundtrip(input: &[u32]) { let mut vb = VariableByte::new(); + let mut encoded: Vec = vec![0; input.len() * 2]; + let mut input_offset = Cursor::new(0); + let mut output_offset = Cursor::new(0); + vb.compress( - &input, - 0, - &mut Cursor::new(0), - &mut output, - &mut Cursor::new(0), + input, + input.len() as u32, + &mut input_offset, + &mut encoded, + &mut output_offset, ) .expect("Failed to compress"); - let mut answer: Vec = vec![]; + + let encoded_len = output_offset.position() as u32; + let mut decoded: Vec = vec![0; input.len()]; + let mut input_offset = Cursor::new(0); + let mut output_offset = Cursor::new(0); + vb.uncompress( - &output, - output.len() as u32, - &mut Cursor::new(0), - &mut answer, - &mut Cursor::new(0), + &encoded, + encoded_len, + &mut input_offset, + &mut decoded, + &mut output_offset, ) .expect("Failed to uncompress"); - assert_eq!(input, answer); + + assert_eq!( + input.len(), + output_offset.position() as usize, + "Decoded length mismatch" + ); + assert_eq!(input, &decoded[..input.len()], "Decoded data mismatch"); } - #[test] - fn test_empty_byte_array() { - let input: Vec = vec![]; - let mut output: Vec = vec![]; + fn verify_i8_roundtrip(input: &[u32]) { let mut vb = VariableByte::new(); + let mut encoded: Vec = vec![0; input.len() * 10]; + let mut input_offset = Cursor::new(0); + let mut output_offset = Cursor::new(0); + vb.compress( - &input, - 0, - &mut Cursor::new(0), - &mut output, - &mut Cursor::new(0), + input, + input.len() as u32, + &mut input_offset, + &mut encoded, + &mut output_offset, ) .expect("Failed to compress"); - let mut answer: Vec = vec![]; + + let encoded_len = (output_offset.position() - input.len() as u64) as u32; + let mut decoded: Vec = vec![0; input.len()]; + let mut input_offset = Cursor::new(0); + let mut output_offset = Cursor::new(0); + vb.uncompress( - &output, - output.len() as u32, - &mut Cursor::new(0), - &mut answer, - &mut Cursor::new(0), + &encoded, + encoded_len, + &mut input_offset, + &mut decoded, + &mut output_offset, ) .expect("Failed to uncompress"); - assert_eq!(input, answer); + + assert_eq!( + input.len(), + output_offset.position() as usize, + "Decoded length mismatch" + ); + assert_eq!(input, &decoded[..input.len()], "Decoded data mismatch"); + } + + #[test] + fn test_empty_int_array() { + verify_u32_roundtrip(&[]); + } + + #[test] + fn test_empty_byte_array() { + verify_i8_roundtrip(&[]); + } + + #[test] + fn test_single_small_value() { + verify_u32_roundtrip(&[5]); + verify_i8_roundtrip(&[5]); + } + + #[test] + fn test_single_large_value() { + verify_u32_roundtrip(&[10_878_508]); + verify_i8_roundtrip(&[10_878_508]); + } + + #[test] + fn test_boundary_values_7bit() { + verify_u32_roundtrip(&[0, 127]); + verify_i8_roundtrip(&[0, 127]); + } + + #[test] + fn test_boundary_values_14bit() { + verify_u32_roundtrip(&[128, 16383]); + verify_i8_roundtrip(&[128, 16383]); + } + + #[test] + fn test_boundary_values_21bit() { + verify_u32_roundtrip(&[16384, 2_097_151]); + verify_i8_roundtrip(&[16384, 2_097_151]); + } + + #[test] + fn test_boundary_values_28bit() { + verify_u32_roundtrip(&[2_097_152, 268_435_455]); + verify_i8_roundtrip(&[2_097_152, 268_435_455]); + } + + #[test] + fn test_boundary_values_32bit() { + verify_u32_roundtrip(&[268_435_456, u32::MAX]); + verify_i8_roundtrip(&[268_435_456, u32::MAX]); + } + + #[test] + fn test_increasing_sequence() { + let input: Vec = (0..1000).collect(); + verify_u32_roundtrip(&input); + verify_i8_roundtrip(&input); + } + + #[test] + fn test_max_and_min() { + verify_u32_roundtrip(&[0, u32::MAX]); + verify_i8_roundtrip(&[0, u32::MAX]); + } + + #[test] + fn test_powers_of_two() { + let input: Vec = (0..31).map(|i| 1u32 << i).collect(); + verify_u32_roundtrip(&input); + verify_i8_roundtrip(&input); + } + + #[test] + fn test_mixed_sizes() { + let input = vec![ + 5, // 1 byte + 200, // 2 bytes + 20_000, // 3 bytes + 2_000_000, // 4 bytes + 200_000_000, // 5 bytes + ]; + verify_u32_roundtrip(&input); + verify_i8_roundtrip(&input); + } + + #[test] + fn test_all_same_value() { + let input = vec![42; 100]; + verify_u32_roundtrip(&input); + verify_i8_roundtrip(&input); + } + + #[test] + fn test_alternating_small_large() { + let mut input = Vec::new(); + for i in 0..50 { + if i % 2 == 0 { + input.push(1); + } else { + input.push(u32::MAX); + } + } + verify_u32_roundtrip(&input); + verify_i8_roundtrip(&input); + } + + #[test] + fn test_random_numbers_small() { + use std::collections::hash_map::RandomState; + use std::hash::{BuildHasher, Hasher}; + + let seed = RandomState::new().build_hasher().finish(); + let mut rng = seed; + let mut input = Vec::new(); + + for _ in 0..1000 { + rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1); + input.push((rng % u64::from(u32::MAX)) as u32); + } + + verify_u32_roundtrip(&input); + verify_i8_roundtrip(&input); + } + + #[test] + fn test_fuzz_case_regression() { + // Regression test from fuzzing: input [0x00a6002c] + let input = vec![0x00a6002c]; + verify_u32_roundtrip(&input); + verify_i8_roundtrip(&input); + } + + #[test] + fn test_sequential_values() { + let input: Vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + verify_u32_roundtrip(&input); + verify_i8_roundtrip(&input); + } + + #[test] + fn test_sparse_values() { + let input = vec![0, 1000000, 2000000, 3000000, 4000000]; + verify_u32_roundtrip(&input); + verify_i8_roundtrip(&input); } } diff --git a/tests/basic_tests.rs b/tests/basic_tests.rs index dc9f5ad..a991e4a 100644 --- a/tests/basic_tests.rs +++ b/tests/basic_tests.rs @@ -76,22 +76,24 @@ fn test_varying_length() { let mut data_copy = data.clone(); data_copy.resize(l, 0); let mut output_compress = vec![0; data_copy.len() * 4]; + let mut output_offset = Cursor::new(0); codec .compress( &data_copy, data_copy.len() as u32, &mut Cursor::new(0), &mut output_compress, - &mut Cursor::new(0), + &mut output_offset, ) .unwrap_or_else(|e| { panic!("Failed to compress with {}: {e:?}", codec.name()); }); + let compressed_len = output_offset.position() as u32; let mut answer = vec![0; l + 1024]; codec .uncompress( &output_compress, - output_compress.len() as u32, + compressed_len, &mut Cursor::new(0), &mut answer, &mut Cursor::new(0),