diff --git a/src/api.rs b/src/api.rs index 544a4a0..4a80a3a 100644 --- a/src/api.rs +++ b/src/api.rs @@ -379,126 +379,3 @@ pub fn assert_no_unknown_objects(file: &DwgFile, version: Version) -> Result<()> } Ok(()) } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn parsemode_default_is_best_effort() { - assert_eq!(ParseMode::default(), ParseMode::BestEffort); - } - - #[test] - fn decoded_complete_has_no_warnings() { - let d = Decoded::complete(42); - assert!(d.complete); - assert!(d.diagnostics.warnings.is_empty()); - } - - #[test] - fn decoded_partial_preserves_diagnostics() { - let mut diag = Diagnostics::default(); - diag.warn("test", "something went off"); - let d = Decoded::partial("value", diag); - assert!(!d.complete); - assert_eq!(d.diagnostics.warnings.len(), 1); - } - - #[test] - fn decoded_map_preserves_diagnostics() { - let mut diag = Diagnostics::default(); - diag.warn("a", "b"); - let d = Decoded::partial(10_i32, diag); - let mapped = d.map(|n| n * 2); - assert_eq!(mapped.value, 20); - assert_eq!(mapped.diagnostics.warnings.len(), 1); - assert!(!mapped.complete); - } - - #[test] - fn diagnostics_is_clean_on_default() { - let d = Diagnostics::default(); - assert!(d.is_clean()); - } - - #[test] - fn diagnostics_warn_mutates() { - let mut d = Diagnostics::default(); - d.warn("x", "y"); - assert!(!d.is_clean()); - assert_eq!(d.warnings[0].code, "x"); - assert_eq!(d.warnings[0].message, "y"); - assert_eq!(d.warnings[0].bit_offset, None); - } - - #[test] - fn diagnostics_warn_at_carries_offset() { - let mut d = Diagnostics::default(); - d.warn_at("truncated", 1234, "section ran short"); - assert_eq!(d.warnings[0].bit_offset, Some(1234)); - } - - #[test] - fn confidence_zero_attempts_is_one() { - let d = Diagnostics::default(); - assert_eq!(d.confidence(0), 1.0); - } - - #[test] - fn confidence_all_clean_is_one() { - let d = Diagnostics::default(); - assert_eq!(d.confidence(100), 1.0); - } - - #[test] - fn confidence_counts_all_failure_modes() { - let mut d = Diagnostics { - skipped_records: 10, - failed_streams: 5, - partial_fields: 3, - ..Default::default() - }; - d.warn("a", "b"); - d.warn("c", "d"); - // 20 bad out of 100 → confidence 0.8 - assert_eq!(d.confidence(100), 0.8); - } - - #[test] - fn decoded_complete_initializes_new_fields() { - let d = Decoded::complete(7_u8); - assert_eq!(d.consumed_bits, 0); - assert_eq!(d.stopped_at_field, None); - } - - #[test] - fn decoded_partial_initializes_new_fields() { - let d = Decoded::partial(7_u8, Diagnostics::default()); - assert_eq!(d.consumed_bits, 0); - assert_eq!(d.stopped_at_field, None); - } - - #[test] - fn decoded_partial_at_captures_marker() { - let mut diag = Diagnostics::default(); - diag.warn("truncated_field", "ran out of bits in flags"); - let d = Decoded::partial_at(0_u32, diag, 1_234, "flags"); - assert!(!d.complete); - assert_eq!(d.consumed_bits, 1_234); - assert_eq!(d.stopped_at_field, Some("flags")); - assert_eq!(d.diagnostics.warnings[0].code, "truncated_field"); - } - - #[test] - fn decoded_map_preserves_partial_at_markers() { - let mut diag = Diagnostics::default(); - diag.warn("x", "y"); - let d = Decoded::partial_at(10_i32, diag, 64, "thickness"); - let mapped = d.map(|n| n.to_string()); - assert_eq!(mapped.value, "10"); - assert_eq!(mapped.consumed_bits, 64); - assert_eq!(mapped.stopped_at_field, Some("thickness")); - assert!(!mapped.complete); - } -} diff --git a/src/bitcursor.rs b/src/bitcursor.rs index 8bc0e90..1f98fd8 100644 --- a/src/bitcursor.rs +++ b/src/bitcursor.rs @@ -479,264 +479,3 @@ impl Handle { matches!(self.code, 0x6 | 0x8 | 0xA | 0xC) } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::bitwriter::BitWriter; - - #[test] - fn read_handle_accepts_a_full_eight_byte_counter() { - let mut w = BitWriter::new(); - w.write_handle(4, u64::MAX); - let bytes = w.into_bytes(); - let h = BitCursor::new(&bytes).read_handle().unwrap(); - assert_eq!(h.code, 4); - assert_eq!(h.counter, 8); - assert_eq!(h.value, u64::MAX); - } - - #[test] - fn read_handle_rejects_a_counter_above_eight() { - // Code 2, counter 11 — the signature the sample_AC1032.dwg - // phantom records carried (#44/#51). Eleven value bytes cannot - // fit a u64, so the cursor is provably not on a handle field. - let mut w = BitWriter::new(); - w.write_rc(0x2B); - for b in 0..11u8 { - w.write_rc(b); - } - let bytes = w.into_bytes(); - let err = BitCursor::new(&bytes).read_handle().unwrap_err(); - match err { - Error::HandleCounterTooLarge { code, counter } => { - assert_eq!(code, 2); - assert_eq!(counter, 11); - } - other => panic!("expected HandleCounterTooLarge, got {other:?}"), - } - } - - /// Helper: pack a sequence of `(value, bit_count)` pairs MSB-first into a - /// byte vector. Pads the final byte with zeros. Used to construct bit - /// streams for spec example tests without pen-and-paper arithmetic. - fn pack_bits(fields: &[(u64, u32)]) -> Vec { - let mut acc: u128 = 0; - let mut nbits: u32 = 0; - for &(v, k) in fields { - assert!(k <= 64); - assert!(nbits + k <= 128, "test helper overflow"); - acc = (acc << k) | (v as u128); - nbits += k; - } - // pad to byte boundary - let pad = (8 - (nbits % 8)) % 8; - acc <<= pad as u128; - let total = nbits + pad; - let mut out = Vec::with_capacity((total / 8) as usize); - let mut remaining = total; - while remaining > 0 { - remaining -= 8; - out.push(((acc >> remaining) & 0xFF) as u8); - } - out - } - - // Spec §2.2 — BITSHORT worked example: - // Stream: 00 00000001 00000001 10 11 01 00001111 10 - // 5 shorts: 257, 0, 256, 15, 0 - #[test] - fn spec_2_2_bitshort_example() { - let stream = pack_bits(&[ - (0b00, 2), // tag 00 → 16-bit LE - (0b0000_0001, 8), // LSB - (0b0000_0001, 8), // MSB → 257 - (0b10, 2), // → 0 - (0b11, 2), // → 256 - (0b01, 2), // tag 01 → 8-bit - (0b0000_1111, 8), // = 15 - (0b10, 2), // → 0 - ]); - let mut c = BitCursor::new(&stream); - assert_eq!(c.read_bs().unwrap(), 257); - assert_eq!(c.read_bs().unwrap(), 0); - assert_eq!(c.read_bs().unwrap(), 256); - assert_eq!(c.read_bs().unwrap(), 15); - assert_eq!(c.read_bs().unwrap(), 0); - } - - // Spec §2.3 — BITLONG worked example: - // Stream: 00 00000001 00000001 00000000 00000000 10 01 00001111 10 - // 4 longs: 257, 0, 15, 0 - #[test] - fn spec_2_3_bitlong_example() { - let stream = pack_bits(&[ - (0b00, 2), // tag 00 → 32-bit LE - (0x01, 8), // byte 0 (LSB) - (0x01, 8), // byte 1 - (0x00, 8), // byte 2 - (0x00, 8), // byte 3 (MSB) → 257 - (0b10, 2), // → 0 - (0b01, 2), // tag 01 → 8-bit - (0b0000_1111, 8), // = 15 - (0b10, 2), // → 0 - ]); - let mut c = BitCursor::new(&stream); - assert_eq!(c.read_bl().unwrap(), 257); - assert_eq!(c.read_bl().unwrap(), 0); - assert_eq!(c.read_bl().unwrap(), 15); - assert_eq!(c.read_bl().unwrap(), 0); - } - - // Spec §2.5 — BITDOUBLE special cases + one full double. - #[test] - fn spec_2_5_bitdouble_specials() { - // Prefix 01 10 00 then 8 LE bytes of 2.0. - let bytes_2p0 = 2.0f64.to_le_bytes(); - let mut fields: Vec<(u64, u32)> = vec![ - (0b01, 2), // → 1.0 - (0b10, 2), // → 0.0 - (0b00, 2), // → read 8 raw LE bytes - ]; - for b in bytes_2p0 { - fields.push((b as u64, 8)); - } - let stream = pack_bits(&fields); - let mut c = BitCursor::new(&stream); - assert_eq!(c.read_bd().unwrap(), 1.0); - assert_eq!(c.read_bd().unwrap(), 0.0); - assert_eq!(c.read_bd().unwrap(), 2.0); - } - - // Spec §2.5 — DD (bitdouble with default) examples. - #[test] - fn spec_2_5_bitdouble_with_default_variants() { - let default = 12.5f64; - let patch4 = f64::from_le_bytes([1, 2, 3, 4, 0, 0, 0x29, 0x40]); - let patch6 = f64::from_le_bytes([9, 8, 7, 6, 5, 4, 0x29, 0x40]); - let full = -42.25f64; - - let fields: Vec<(u64, u32)> = vec![ - (0b00, 2), - (0b01, 2), - (1, 8), - (2, 8), - (3, 8), - (4, 8), - (0b10, 2), - (5, 8), - (4, 8), - (9, 8), - (8, 8), - (7, 8), - (6, 8), - ]; - - let stream = pack_bits(&fields); - let mut c = BitCursor::new(&stream); - assert_eq!(c.read_dd(default).unwrap(), default); - assert_eq!(c.read_dd(default).unwrap(), patch4); - assert_eq!(c.read_dd(default).unwrap(), patch6); - - let mut full_fields: Vec<(u64, u32)> = vec![(0b11, 2)]; - for b in full.to_le_bytes() { - full_fields.push((b as u64, 8)); - } - let stream = pack_bits(&full_fields); - let mut c = BitCursor::new(&stream); - assert_eq!(c.read_dd(default).unwrap(), full); - } - - // §2.0 — single bits and 2-bit codes - #[test] - fn single_bits() { - let bytes = [0b1010_0000]; - let mut c = BitCursor::new(&bytes); - assert!(c.read_b().unwrap()); - assert!(!c.read_b().unwrap()); - assert!(c.read_b().unwrap()); - assert!(!c.read_b().unwrap()); - } - - #[test] - fn two_bit_codes() { - // 11 01 10 00 → 3, 1, 2, 0 - let bytes = [0b1101_1000]; - let mut c = BitCursor::new(&bytes); - assert_eq!(c.read_bb().unwrap(), 3); - assert_eq!(c.read_bb().unwrap(), 1); - assert_eq!(c.read_bb().unwrap(), 2); - assert_eq!(c.read_bb().unwrap(), 0); - } - - // §2.1 — 3B - #[test] - fn read_3b_is_three_raw_bits() { - for value in 0u8..=7 { - let bytes = [value << 5]; - assert_eq!( - BitCursor::new(&bytes).read_3b().unwrap(), - value, - "value={value}" - ); - } - // Three bits, always — the next read starts at bit 3. - let bytes = [0b0011_1111]; - let mut c = BitCursor::new(&bytes); - assert_eq!(c.read_3b().unwrap(), 0b001); - assert_eq!(c.position_bits(), 3); - } - - // Byte alignment between objects, per §2.14. - #[test] - fn align_to_byte_drops_partial() { - let bytes = [0b1010_0000, 0xAB]; - let mut c = BitCursor::new(&bytes); - // Consume 3 bits; cursor at bit 3. - let _ = c.read_bits(3).unwrap(); - c.align_to_byte(); - assert_eq!(c.read_rc().unwrap(), 0xAB); - } - - #[test] - fn remaining_bits_tracks_position() { - let bytes = [0xFF, 0xFF]; - let mut c = BitCursor::new(&bytes); - assert_eq!(c.remaining_bits(), 16); - let _ = c.read_bits(5).unwrap(); - assert_eq!(c.remaining_bits(), 11); - } - - #[test] - fn exhaustion_returns_error() { - let bytes = [0xFF]; - let mut c = BitCursor::new(&bytes); - let _ = c.read_bits(8).unwrap(); - assert!(matches!(c.read_b(), Err(Error::BitsExhausted { .. }))); - } - - // §2.13 — Handle reference - // - // Spec example: LAYER 0 has handle 0x0F stored as 5.1.0F - // → CODE=5 COUNTER=1 HANDLE=0x0F - // → bitstream: 0101 0001 00001111 - #[test] - fn handle_layer_0_example() { - let bytes = [0b0101_0001, 0b0000_1111]; - let mut c = BitCursor::new(&bytes); - let h = c.read_handle().unwrap(); - assert_eq!(h.code, 5); - assert_eq!(h.counter, 1); - assert_eq!(h.value, 0x0F); - assert!(h.is_absolute()); - assert!(!h.is_offset()); - } - - #[test] - fn raw_types_are_byte_aligned_le() { - let bytes = [0x01, 0x00, 0x02, 0x00, 0x00, 0x00]; - let mut c = BitCursor::new(&bytes); - assert_eq!(c.read_rs().unwrap(), 1); - assert_eq!(c.read_rl().unwrap(), 2); - } -} diff --git a/src/cipher.rs b/src/cipher.rs index 0ac301b..493665c 100644 --- a/src/cipher.rs +++ b/src/cipher.rs @@ -82,54 +82,3 @@ pub fn xor_in_place(block: &mut [u8]) { pub fn section_page_mask(offset: u32) -> u32 { 0x4164_536B ^ offset } - -#[cfg(test)] -mod tests { - use super::*; - - /// The first 16 bytes of the sequence are published on page 24 of - /// spec v5.4.1. Match them exactly. - #[test] - fn matches_spec_published_prefix() { - let m = magic_sequence(); - let expected: [u8; 16] = [ - 0x29, 0x23, 0xBE, 0x84, 0xE1, 0x6C, 0xD6, 0xAE, 0x52, 0x90, 0x49, 0xF1, 0xF1, 0xBB, - 0xE9, 0xEB, - ]; - assert_eq!(&m[..16], &expected); - } - - /// The full 0x6C-byte sequence from the spec. - #[test] - fn matches_spec_full_sequence() { - let m = magic_sequence(); - let expected: [u8; 108] = [ - 0x29, 0x23, 0xBE, 0x84, 0xE1, 0x6C, 0xD6, 0xAE, 0x52, 0x90, 0x49, 0xF1, 0xF1, 0xBB, - 0xE9, 0xEB, 0xB3, 0xA6, 0xDB, 0x3C, 0x87, 0x0C, 0x3E, 0x99, 0x24, 0x5E, 0x0D, 0x1C, - 0x06, 0xB7, 0x47, 0xDE, 0xB3, 0x12, 0x4D, 0xC8, 0x43, 0xBB, 0x8B, 0xA6, 0x1F, 0x03, - 0x5A, 0x7D, 0x09, 0x38, 0x25, 0x1F, 0x5D, 0xD4, 0xCB, 0xFC, 0x96, 0xF5, 0x45, 0x3B, - 0x13, 0x0D, 0x89, 0x0A, 0x1C, 0xDB, 0xAE, 0x32, 0x20, 0x9A, 0x50, 0xEE, 0x40, 0x78, - 0x36, 0xFD, 0x12, 0x49, 0x32, 0xF6, 0x9E, 0x7D, 0x49, 0xDC, 0xAD, 0x4F, 0x14, 0xF2, - 0x44, 0x40, 0x66, 0xD0, 0x6B, 0xC4, 0x30, 0xB7, 0x32, 0x3B, 0xA1, 0x22, 0xF6, 0x22, - 0x91, 0x9D, 0xE1, 0x8B, 0x1F, 0xDA, 0xB0, 0xCA, 0x99, 0x02, - ]; - assert_eq!(m, expected); - } - - /// XOR is its own inverse: encrypting then decrypting restores input. - #[test] - fn xor_is_involution() { - let original: Vec = (0u8..(MAGIC_LEN as u8)).collect(); - let mut buf = original.clone(); - xor_in_place(&mut buf); - assert_ne!(buf, original); - xor_in_place(&mut buf); - assert_eq!(buf, original); - } - - #[test] - fn section_page_mask_changes_with_offset() { - assert_ne!(section_page_mask(0x100), section_page_mask(0x200)); - assert_eq!(section_page_mask(0) ^ 0x4164_536B, 0); - } -} diff --git a/src/crc.rs b/src/crc.rs index cf71ed6..62b75eb 100644 --- a/src/crc.rs +++ b/src/crc.rs @@ -304,125 +304,3 @@ pub fn page_checksums(header_zeroed: &[u8], payload: &[u8]) -> (u32, u32) { let payload_cs = section_page_checksum(header_cs, payload); (header_cs, payload_cs) } - -#[cfg(test)] -mod tests { - use super::*; - - /// A CRC table has a trivially-testable invariant: index 0 must be 0 - /// (the CRC of an empty byte at state 0 stays 0). - #[test] - fn crc8_table_index_0_is_zero() { - assert_eq!(CRC8_TABLE[0], 0x0000); - } - - /// Self-consistency: running the table through a known byte sequence - /// and comparing against a direct polynomial evaluation of the same - /// reflected CRC-16 family (poly 0xA001 = reflect of 0x8005). - #[test] - fn crc8_matches_reflected_poly_a001() { - // Bitwise-compute the CRC for "123456789" (classic CRC test vector). - let msg = b"123456789"; - let mut bit_crc: u16 = 0; - for &b in msg { - bit_crc ^= b as u16; - for _ in 0..8 { - if bit_crc & 1 != 0 { - bit_crc = (bit_crc >> 1) ^ 0xA001; - } else { - bit_crc >>= 1; - } - } - } - let tbl_crc = crc8(0, msg); - assert_eq!(tbl_crc, bit_crc); - } - - #[test] - fn crc32_ieee_test_vector() { - // "123456789" under IEEE 802.3 CRC-32 is 0xCBF43926. - assert_eq!(crc32(0, b"123456789"), 0xCBF4_3926); - } - - #[test] - fn r13_locator_seeds_known_table() { - assert_eq!(r13_locator_seed(3), Some(0xA598)); - assert_eq!(r13_locator_seed(6), Some(0x8461)); - assert_eq!(r13_locator_seed(99), None); - } - - #[test] - fn section_page_checksum_empty_is_seed() { - assert_eq!(section_page_checksum(0, &[]), 0); - assert_eq!(section_page_checksum(0xDEAD_BEEF, &[]), 0xDEAD_BEEF); - } - - #[test] - fn section_page_checksum_is_deterministic() { - let data = b"The DWG file format version 2004 compression is a variation on LZ77."; - let a = section_page_checksum(0, data); - let b = section_page_checksum(0, data); - assert_eq!(a, b); - // Non-zero for non-empty input. - assert_ne!(a, 0); - } - - // -------- L12-02: writer helpers -------- - - #[test] - fn embed_crc8_round_trips() { - // Build a section with a 2-byte CRC slot at offset 4, then - // verify that reading the slot + recomputing the CRC over the - // zeroed payload reproduces the embedded value. - let mut buf = vec![0xAA, 0xBB, 0xCC, 0xDD, 0xFF, 0xFF, 0x01, 0x02, 0x03]; - let written = embed_crc8(&mut buf, 4, 0).expect("slot fits"); - // Read back the embedded LE pair. - let embedded = u16::from_le_bytes([buf[4], buf[5]]); - assert_eq!(embedded, written); - // Reconstruct zero-filled buffer and re-verify the CRC. - let mut verify = buf.clone(); - verify[4] = 0; - verify[5] = 0; - assert_eq!(crc8(0, &verify), written); - } - - #[test] - fn embed_crc8_rejects_too_small_buffer() { - let mut buf = vec![0u8; 3]; - assert!(embed_crc8(&mut buf, 2, 0).is_none()); - // Exactly fits at offset 1. - let mut buf2 = vec![0u8; 3]; - assert!(embed_crc8(&mut buf2, 1, 0).is_some()); - } - - #[test] - fn embed_crc32_round_trips_with_zeroed_slot() { - let mut buf = vec![0x01, 0x02, 0x03, 0x04, 0xFF, 0xFF, 0xFF, 0xFF, 0x05]; - let written = embed_crc32(&mut buf, 4, 0).expect("slot fits"); - let embedded = u32::from_le_bytes(buf[4..8].try_into().unwrap()); - assert_eq!(embedded, written); - let mut verify = buf.clone(); - verify[4..8].fill(0); - assert_eq!(crc32(0, &verify), written); - } - - #[test] - fn embed_crc32_rejects_too_small_buffer() { - let mut buf = vec![0u8; 5]; - assert!(embed_crc32(&mut buf, 3, 0).is_none()); // only 2 bytes fit past 3 - let mut buf2 = vec![0u8; 5]; - assert!(embed_crc32(&mut buf2, 1, 0).is_some()); // 4 bytes fit at [1..5] - } - - #[test] - fn page_checksums_chains_header_into_payload() { - // The payload checksum seeds from the header checksum, so - // changing the header changes the payload output even if - // payload bytes are identical. - let payload = b"identical payload"; - let (h1, p1) = page_checksums(&[0u8; 16], payload); - let (h2, p2) = page_checksums(&[1u8; 16], payload); - assert_ne!(h1, h2); - assert_ne!(p1, p2); - } -} diff --git a/src/header.rs b/src/header.rs index 4297230..ce799b4 100644 --- a/src/header.rs +++ b/src/header.rs @@ -270,26 +270,3 @@ impl R2004Header { }) } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn common_header_truncated() { - let bytes = b"AC1015"; // just the magic, nothing else - assert!(matches!( - CommonHeader::parse(bytes), - Err(Error::Truncated { .. }) - )); - } - - #[test] - fn locator_record_round_trip() { - let raw = [0x01u8, 0x78, 0x56, 0x34, 0x12, 0x34, 0x12, 0x00, 0x00]; - let r = LocatorRecord::parse(&raw).unwrap(); - assert_eq!(r.number, 1); - assert_eq!(r.seeker, 0x1234_5678); - assert_eq!(r.size, 0x0000_1234); - } -} diff --git a/src/limits.rs b/src/limits.rs index aee7b9d..70afc71 100644 --- a/src/limits.rs +++ b/src/limits.rs @@ -147,44 +147,6 @@ impl ParseLimits { } } -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn paranoid_is_strictly_lower_than_safe() { - let safe = ParseLimits::safe(); - let paranoid = ParseLimits::paranoid(); - assert!(paranoid.max_handle_entries < safe.max_handle_entries); - assert!(paranoid.max_retained_dispatch_errors < safe.max_retained_dispatch_errors); - assert!(paranoid.max_xdata_iterations < safe.max_xdata_iterations); - assert!(paranoid.max_class_entries < safe.max_class_entries); - assert!(paranoid.max_lwpolyline_count < safe.max_lwpolyline_count); - assert!(paranoid.max_image_clip_verts < safe.max_image_clip_verts); - assert!(paranoid.max_leader_points < safe.max_leader_points); - assert!(paranoid.max_spline_count < safe.max_spline_count); - } - - #[test] - fn permissive_is_strictly_higher_than_safe() { - let safe = ParseLimits::safe(); - let permissive = ParseLimits::permissive(); - assert!(permissive.max_handle_entries > safe.max_handle_entries); - assert!(permissive.max_retained_dispatch_errors > safe.max_retained_dispatch_errors); - assert!(permissive.max_xdata_iterations > safe.max_xdata_iterations); - assert!(permissive.max_class_entries > safe.max_class_entries); - assert!(permissive.max_lwpolyline_count > safe.max_lwpolyline_count); - assert!(permissive.max_image_clip_verts > safe.max_image_clip_verts); - assert!(permissive.max_leader_points > safe.max_leader_points); - assert!(permissive.max_spline_count > safe.max_spline_count); - } - - #[test] - fn default_matches_safe() { - assert_eq!(ParseLimits::default(), ParseLimits::safe()); - } -} - /// Caller-configurable safety limits for graph iteration — the /// handle-driven walks that resolve owner chains, reactor back-refs, /// and block expansions. @@ -263,34 +225,6 @@ impl WalkerLimits { } } -#[cfg(test)] -mod walker_tests { - use super::*; - - #[test] - fn walker_paranoid_strictly_lower() { - let s = WalkerLimits::safe(); - let p = WalkerLimits::paranoid(); - assert!(p.max_handles < s.max_handles); - assert!(p.max_scan_bytes < s.max_scan_bytes); - assert!(p.max_block_nesting < s.max_block_nesting); - } - - #[test] - fn walker_permissive_strictly_higher() { - let s = WalkerLimits::safe(); - let p = WalkerLimits::permissive(); - assert!(p.max_handles > s.max_handles); - assert!(p.max_scan_bytes > s.max_scan_bytes); - assert!(p.max_block_nesting > s.max_block_nesting); - } - - #[test] - fn walker_default_matches_safe() { - assert_eq!(WalkerLimits::default(), WalkerLimits::safe()); - } -} - /// Top-level safety profile applied at [`crate::DwgFile::open_with_limits`]. /// /// Bundles the three load-bearing caps: @@ -365,34 +299,3 @@ impl OpenLimits { } } } - -#[cfg(test)] -mod open_limits_tests { - use super::*; - - #[test] - fn open_paranoid_strictly_lower_than_safe() { - let s = OpenLimits::safe(); - let p = OpenLimits::paranoid(); - assert!(p.max_file_bytes < s.max_file_bytes); - assert!(p.max_section_bytes < s.max_section_bytes); - // Inner profile caps follow ParseLimits + WalkerLimits invariants. - assert!(p.parse.max_handle_entries < s.parse.max_handle_entries); - assert!(p.walker.max_handles < s.walker.max_handles); - } - - #[test] - fn open_permissive_strictly_higher_than_safe() { - let s = OpenLimits::safe(); - let p = OpenLimits::permissive(); - assert!(p.max_file_bytes > s.max_file_bytes); - assert!(p.max_section_bytes > s.max_section_bytes); - assert!(p.parse.max_handle_entries > s.parse.max_handle_entries); - assert!(p.walker.max_handles > s.walker.max_handles); - } - - #[test] - fn open_default_matches_safe() { - assert_eq!(OpenLimits::default(), OpenLimits::safe()); - } -} diff --git a/src/lz77.rs b/src/lz77.rs index 8863c3c..a262144 100644 --- a/src/lz77.rs +++ b/src/lz77.rs @@ -379,238 +379,3 @@ fn read_long_compression_offset(r: &mut Lz77Reader<'_>) -> Result { } } } - -#[cfg(test)] -mod tests { - use super::*; - - /// Literal-only stream: literal length of 5 (encoded as 0x02 → +3), - /// 5 literal bytes "Hello", then terminator 0x11. - #[test] - fn literal_only_stream() { - let stream = [0x02, b'H', b'e', b'l', b'l', b'o', 0x11]; - let out = decompress(&stream, None).unwrap(); - assert_eq!(&out, b"Hello"); - } - - /// Initial literal length of 0 via high-nibble peekback: first byte - /// is 0x11 (opcode = terminator). Output is empty. - #[test] - fn empty_stream_via_immediate_terminator() { - let stream = [0x11]; - let out = decompress(&stream, None).unwrap(); - assert!(out.is_empty()); - } - - /// Simple back-reference with opcode 0x22 (compBytes = 4, class - /// 0x21-0x3F which gets +1 offset adjustment): - /// Literal "ABCDEFG" (len 7 = 0x04 + 3), copy 4 bytes at effective - /// offset 3 (= base 2 + 1), then 2 literal bytes "XY", terminator. - /// - /// Copy starts at position 7-3 = 4, copies out[4..4+4] = "EFGE" - /// (the 4th byte wraps since we're reading what we just wrote). - #[test] - fn back_reference_with_rle_wrap() { - // Two-byte offset: base=2, litCount=2 - // first = (2 << 2) | 2 = 0x0A - // second = 0 - // Class 0x21-0x3F adds +1 → effective offset = 3. - let stream = [ - 0x04, b'A', b'B', b'C', b'D', b'E', b'F', b'G', // literal run - 0x22, 0x0A, 0x00, // opcode 0x22 → copy 4 at offset 3, litCount 2 - b'X', b'Y', // 2 literal bytes - 0x11, // terminator - ]; - let out = decompress(&stream, None).unwrap(); - assert_eq!(&out, b"ABCDEFGEFGEXY"); - } - - /// Literal length extended via 0x00 run: `0x00 0x00 0x02` means - /// `0x0F + 0xFF + 0x02 + 3 = 275` bytes of literal. - #[test] - fn extended_literal_length() { - const N: usize = 275; - let mut stream = vec![0x00u8, 0x00, 0x02]; - for i in 0..N { - stream.push((i & 0xFF) as u8); - } - stream.push(0x11); - let out = decompress(&stream, None).unwrap(); - assert_eq!(out.len(), N); - for (i, &b) in out.iter().enumerate() { - assert_eq!(b, (i & 0xFF) as u8); - } - } - - /// Opcode 0x40-0xFF encoding: litCount in bits 0-1 of opcode1. Offset - /// is computed as `((op1 >> 2) & 3) | (op2 << 2) + 1` per the ACadSharp - /// reference (the spec's "+0" appears to be a typo). - /// - /// opcode1 = 0x41 = 0b0100_0001: - /// compBytes = ((0x41 & 0xF0) >> 4) - 1 = 3. - /// litCount = 0x41 & 0x03 = 1. - /// opcode2 = 0x01: - /// compOffset = ((0x41 >> 2) & 3) | (0x01 << 2) + 1 - /// = 0 | 4 + 1 = 5. - /// - /// Literal "ABCDEFG" (len 7), copy 3 at offset 5 → out[2..5] = "CDE", - /// then 1 literal "Z", then terminator. Total: "ABCDEFGCDEZ". - #[test] - fn opcode_0x40_family() { - let stream = [ - 0x04, b'A', b'B', b'C', b'D', b'E', b'F', b'G', // 7 literals - 0x41, 0x01, // compBytes=3, offset=5, litCount=1 - b'Z', 0x11, - ]; - let out = decompress(&stream, None).unwrap(); - assert_eq!(&out, b"ABCDEFGCDEZ"); - } - - /// Truncated input should error cleanly, not panic. - #[test] - fn truncated_stream_errors() { - let stream = [0x04, b'A', b'B']; // says 7 literals, only has 2 - let e = decompress(&stream, None).unwrap_err(); - assert!(matches!(e, Error::Lz77Truncated)); - } - - /// Reserved opcode range 0x00-0x0F is not valid AFTER the initial - /// literal length. Construct: 0 literal length (via peekback of 0x11 - /// is too trivial; use 0 via direct). We need a stream that reaches - /// the main loop with opcode < 0x10. - /// - /// Easiest: 1-byte literal (0x0F? but that's a valid literal length) - /// → use 0x01 = length 4, 4 literals, then opcode 0x05 (invalid). - #[test] - fn reserved_opcode_errors() { - let stream = [0x01, b'A', b'B', b'C', b'D', 0x05]; - let e = decompress(&stream, None).unwrap_err(); - assert!(matches!(e, Error::Lz77InvalidOpcode { opcode: 0x05, .. })); - } - - // ================================================================ - // Decompression-bomb defense regression tests. - // ================================================================ - - /// A literal run that asks for more bytes than the configured - /// `max_output_bytes` must error before any allocation. - #[test] - fn output_limit_rejects_oversize_literal_run() { - let limits = DecompressLimits { - max_output_bytes: 8, - max_backref_len: 8, - }; - // Literal length 8 → 0x05 + 3 = 8; 8 bytes of literal. - let mut stream = vec![0x05]; - stream.extend_from_slice(b"AAAAAAAA"); - stream.push(0x11); - // 8 bytes fits exactly; must succeed. - let out = decompress_with_limits(&stream, None, limits).unwrap(); - assert_eq!(out.len(), 8); - - // Nudge the literal length up one byte (0x06 + 3 = 9) and - // expect the limit to fire. - let mut bomb = vec![0x06]; - bomb.extend_from_slice(b"AAAAAAAAA"); - bomb.push(0x11); - let err = decompress_with_limits(&bomb, None, limits).unwrap_err(); - assert!(matches!(err, Error::Lz77OutputLimitExceeded { limit: 8 })); - } - - /// A back-reference copy whose length alone would exceed the - /// configured limit must error. - #[test] - fn output_limit_rejects_oversize_backref() { - // Build a stream: literal "AB" (len=2 → 0x00+3 via extension), - // then a 0x20 opcode that claims comp_bytes = 64 and offset = 2. - // Skip 0x20: uses the long-compression-offset extension. - // - // Simpler: use opcode 0x21 (class 0x21-0x3F) with a small cb. - // cb = (0x21 & 0x1F) + 2 = 1 + 2 = 3. Not useful for a big copy. - // - // Use opcode 0x20 which extends comp_bytes via the long form. - // cb = long_extension + 0x21. - // To hit cb = 100, long_extension = 79, encoded as single byte 0x4F. - // - // Layout: [literal_len=0x02 "ABC", terminator would be 0x11, but we - // need the main-loop opcode]. Easier: use the 0x40-0xFF family - // where cb = (op1>>4 - 1). E.g., op1=0xC1 → cb = (0xC >> 0) - 1 - // = 0xB = 11. Still small. Combined with class 0x20 is fastest. - let mut stream = vec![ - 0x01, b'A', b'B', b'C', b'D', // 4 literal bytes - 0x20, // class 0x20 → cb = long_extension + 0x21 - 0x4F, // long_extension first byte = 0x4F (79). cb = 79 + 0x21 = 112. - 0x00, 0x00, // two-byte offset, small value → offset = 0 + 1 = 1, lit=0 - 0x11, // terminator (won't be reached) - ]; - // Force literal-len-extension for the lit=0 case to avoid the - // reserved-opcode path; not needed since our terminator 0x11 is - // NOT in 0x00..=0x0F. - // (literal length 0 via peekback: the first post-opcode byte is - // the lit_count from the two-byte-offset, which is 0, then we - // read a real literal length. Use 0x11 as the peeked byte to - // terminate via the opcode path.) - stream.push(0x11); - - let limits = DecompressLimits { - max_output_bytes: 1024 * 1024, - max_backref_len: 50, // less than the 112-byte copy above - }; - let err = decompress_with_limits(&stream, None, limits).unwrap_err(); - assert!( - matches!( - err, - Error::Lz77BackrefTooLong { - length: 112, - limit: 50 - } - ), - "err={err:?}" - ); - } - - /// `expected_size` must be clamped to the output limit so a - /// caller-claimed huge size cannot trigger a giant up-front - /// `Vec::with_capacity` allocation. - #[test] - fn expected_size_is_clamped_to_output_limit() { - let limits = DecompressLimits { - max_output_bytes: 16, - max_backref_len: 16, - }; - // Decompressing the empty-terminator stream with a huge - // expected_size must not allocate 1 TiB. This test does not - // observe memory directly — it asserts behavior (no error, - // empty output) that proves the function didn't try to - // allocate more than the limit. - let stream = [0x11u8]; - let out = decompress_with_limits(&stream, Some(1usize << 40), limits).unwrap(); - assert!(out.is_empty()); - } - - /// The default `DecompressLimits` profile ships with a 256 MiB - /// output cap. This pins the contract so a future config tweak - /// has to consciously lower/raise it rather than drift unnoticed. - #[test] - fn default_limits_cap_output_at_256_mib() { - let d = DecompressLimits::default(); - assert_eq!(d.max_output_bytes, 256 * 1024 * 1024); - assert_eq!(d.max_backref_len, 1024 * 1024); - } - - /// Compression-bomb regression: a small input that falsely claims - /// a huge `expected_size` must not allocate anywhere near that - /// size. Caller-supplied hint gets clamped to the output limit, - /// and the decoded output is small and accurate — no panic, no - /// OOM, no over-read. - #[test] - fn small_input_with_huge_expected_size_stays_bounded() { - // 6-byte input decompresses to a 5-byte literal "HELLO". - // expected_size claims 1 TiB — must be clamped by the default - // DecompressLimits (256 MiB) and allocate accordingly. - let stream = [0x02, b'H', b'E', b'L', b'L', b'O', 0x11]; - let huge = 1usize << 40; - let out = decompress(&stream, Some(huge)).unwrap(); - assert_eq!(&out, b"HELLO"); - } -} diff --git a/src/metadata.rs b/src/metadata.rs index 63a2628..5cc85e6 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -515,50 +515,3 @@ impl<'a> ByteCursor<'a> { Ok(s) } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn summary_info_empty_strings() { - // 8 strings × 2 bytes (length=0) + 8+8+8 date bytes + 2 byte prop count - let mut data = vec![0u8; 2 * 8 + 24 + 2]; - // all zeros means empty strings and zero properties - let si = SummaryInfo::parse(&data).unwrap(); - assert_eq!(si.title, ""); - assert_eq!(si.properties.len(), 0); - // silence unused mut warning - data[0] = 0; - } - - #[test] - fn summary_info_with_title() { - // title = "X" (1 byte + NUL = 2 bytes with len=2) - let mut data = Vec::new(); - data.extend_from_slice(&2u16.to_le_bytes()); - data.extend_from_slice(b"X\0"); - for _ in 0..7 { - data.extend_from_slice(&0u16.to_le_bytes()); - } - data.extend_from_slice(&[0u8; 24]); - data.extend_from_slice(&0u16.to_le_bytes()); - let si = SummaryInfo::parse(&data).unwrap(); - assert_eq!(si.title, "X"); - } - - #[test] - fn preview_rejects_bad_sentinel() { - let bad = [0u8; 32]; - assert!(Preview::parse(&bad).is_err()); - } - - #[test] - fn preview_empty_after_sentinel_errors_cleanly() { - let mut data = Vec::new(); - data.extend_from_slice(&Preview::START_SENTINEL); - // too short, overall_size + image count require 5 more bytes - let out = Preview::parse(&data); - assert!(out.is_err()); - } -} diff --git a/src/object_type.rs b/src/object_type.rs index a5229ee..b5e5695 100644 --- a/src/object_type.rs +++ b/src/object_type.rs @@ -433,53 +433,3 @@ impl fmt::Display for ObjectType { } } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn fixed_codes_decode() { - assert_eq!(ObjectType::from_code(0x13), ObjectType::Line); - assert_eq!(ObjectType::from_code(0x12), ObjectType::Circle); - assert_eq!(ObjectType::from_code(0x11), ObjectType::Arc); - assert_eq!(ObjectType::from_code(0x4D), ObjectType::LwPolyline); - assert_eq!(ObjectType::from_code(0x4E), ObjectType::Hatch); - } - - #[test] - fn proxy_codes_decode() { - assert_eq!(ObjectType::from_code(0x1F2), ObjectType::AcadProxyEntity); - assert_eq!(ObjectType::from_code(0x1F3), ObjectType::AcadProxyObject); - } - - #[test] - fn dynamic_codes_decode() { - assert_eq!(ObjectType::from_code(500), ObjectType::Custom(500)); - assert_eq!(ObjectType::from_code(12345), ObjectType::Custom(12345)); - } - - #[test] - fn entity_classification() { - assert!(ObjectType::Line.is_entity()); - assert!(ObjectType::Circle.is_entity()); - assert!(ObjectType::LwPolyline.is_entity()); - assert!(!ObjectType::Dictionary.is_entity()); - assert!(!ObjectType::LayerControl.is_entity()); - } - - #[test] - fn control_classification() { - assert!(ObjectType::LayerControl.is_control()); - assert!(ObjectType::BlockControl.is_control()); - assert!(!ObjectType::Layer.is_control()); - assert!(!ObjectType::Line.is_control()); - } - - #[test] - fn short_label_stable() { - assert_eq!(ObjectType::Line.short_label(), "LINE"); - assert_eq!(ObjectType::LwPolyline.short_label(), "LWPOLYLINE"); - assert_eq!(ObjectType::MText.short_label(), "MTEXT"); - } -} diff --git a/src/r2007.rs b/src/r2007.rs index 7728cc3..692c827 100644 --- a/src/r2007.rs +++ b/src/r2007.rs @@ -749,132 +749,3 @@ pub fn read_section( out.truncate(total); Ok(out) } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn system_page_size_matches_the_spec_pseudo_code() { - // §5.3.1's floor. - assert_eq!(system_page_size(0), 0x400); - assert_eq!(system_page_size(304), 0x400); - // The section map of every corpus R2007 file: 2132 decompressed - // bytes, which the writer put in a 0x1200-byte page. - assert_eq!(system_page_size(2132), 0x1200); - } - - #[test] - fn deinterleave_gathers_each_codewords_message_bytes() { - // Two interleaved codewords, message size 3: codeword 0 owns the - // even positions, codeword 1 the odd ones. - let mut page = vec![0u8; 2 * RS_CODEWORD]; - for i in 0..RS_CODEWORD { - page[i * 2] = i as u8; - page[i * 2 + 1] = 0x80u8.wrapping_add(i as u8); - } - let out = deinterleave(&page, 2, 3).unwrap(); - assert_eq!(out, vec![0, 1, 2, 0x80, 0x81, 0x82]); - } - - #[test] - fn deinterleave_refuses_a_short_page() { - let err = deinterleave(&[0u8; 10], 1, RS_SYSTEM_MESSAGE).unwrap_err(); - assert!(matches!(err, Error::Truncated { .. })); - } - - #[test] - fn page_map_accumulates_offsets_in_map_order() { - // Three pages of 0x400 / 0x200 / 0x100 bytes with ids 22, 23, 3 — - // the shape every corpus file starts with. - let mut data = Vec::new(); - for (size, id) in [(0x400i64, 22i64), (0x200, 23), (0x100, 3)] { - data.extend_from_slice(&size.to_le_bytes()); - data.extend_from_slice(&id.to_le_bytes()); - } - let map = PageMap::parse(&data).unwrap(); - assert_eq!(map.len(), 3); - assert_eq!(map.file_offset_of(22), Some(PAGE_BASE)); - assert_eq!(map.file_offset_of(23), Some(PAGE_BASE + 0x400)); - assert_eq!(map.file_offset_of(3), Some(PAGE_BASE + 0x600)); - assert_eq!(map.file_offset_of(99), None); - } - - #[test] - fn page_map_keys_on_the_absolute_id_so_free_pages_still_resolve() { - let mut data = Vec::new(); - data.extend_from_slice(&0x400i64.to_le_bytes()); - data.extend_from_slice(&(-7i64).to_le_bytes()); - let map = PageMap::parse(&data).unwrap(); - assert_eq!(map.file_offset_of(7), Some(PAGE_BASE)); - } - - /// Build one section-map record by hand and read it back. The name - /// length counts the two-byte terminator, which is the detail that - /// decides where the page list starts. - #[test] - fn section_map_name_length_counts_the_terminator() { - let name = "AcDb:Handles"; - let mut data = Vec::new(); - let mut header = [0u64; 8]; - header[0] = 591; // data size - header[1] = 63488; // max size - header[2] = 0; // encryption - header[3] = 0x3F6E_0450; // hash code - header[4] = (name.len() as u64 + 1) * 2; // name length, with terminator - header[6] = 4; // encoding - header[7] = 1; // one page - for v in header { - data.extend_from_slice(&v.to_le_bytes()); - } - for unit in name.encode_utf16() { - data.extend_from_slice(&unit.to_le_bytes()); - } - data.extend_from_slice(&0u16.to_le_bytes()); - for v in [0u64, 768, 14, 591, 566, 0x0A92_5B96, 0] { - data.extend_from_slice(&v.to_le_bytes()); - } - let map = SectionMap::parse(&data).unwrap(); - assert_eq!(map.sections.len(), 1); - let s = &map.sections[0]; - assert_eq!(s.name, "AcDb:Handles"); - assert_eq!(s.hash_code, 0x3F6E_0450); - assert_eq!(s.encoding, 4); - assert_eq!(s.pages.len(), 1); - assert_eq!(s.pages[0].id, 14); - assert_eq!(s.pages[0].compressed, 566); - assert_eq!(s.pages[0].uncompressed, 591); - } - - #[test] - fn section_map_drops_the_trailing_unnamed_record() { - let mut data = vec![0u8; 0x40]; - // A zero header: no name, no pages. SectionsAmount counts it, the - // parsed list does not. - LittleEndian::write_u64(&mut data[0..8], 0); - let map = SectionMap::parse(&data).unwrap(); - assert!(map.sections.is_empty()); - } - - #[test] - fn file_header_parse_refuses_a_short_buffer() { - let err = FileHeader::parse(&[0u8; 0x100]).unwrap_err(); - assert!(matches!(err, Error::Truncated { .. })); - } - - #[test] - fn encrypted_section_is_refused_rather_than_mis_decoded() { - let desc = SectionDescriptor { - name: "AcDb:AcDbObjects".into(), - data_size: 16, - max_size: 0xF800, - encryption: 1, - hash_code: 0x674C_05A9, - encoding: 4, - pages: Vec::new(), - }; - let err = - read_section(&[], &PageMap::default(), &desc, DecompressLimits::default()).unwrap_err(); - assert!(matches!(err, Error::SectionEncrypted { .. })); - } -} diff --git a/src/r21_lz.rs b/src/r21_lz.rs index 17f2c98..5b8d4b8 100644 --- a/src/r21_lz.rs +++ b/src/r21_lz.rs @@ -362,109 +362,3 @@ pub fn decompress(src: &[u8], expected: usize, limits: DecompressLimits) -> Resu } Ok(out) } - -#[cfg(test)] -mod tests { - use super::*; - - /// The first sixteen bytes of `line_2007.dwg`'s Reed-Solomon-decoded - /// file-header payload, decompressed by hand from the spec: - /// - /// - `20 00 00 02` — leading opcode `0x20` (high nibble 2), so the - /// literal length is `src[3] & 7` = 2. - /// - literal `00 70` → permuted to `70 00` (header size `0x70`). - /// - opcode `0x70` → high nibble 7: length 7, offset 1 → seven zero - /// bytes, completing the `u64` `0x70`. - /// - `0x02 & 7` = 2 → literal `01 03` → permuted to `03 01`. - /// - opcode `0x58` → length 5, offset 9 → five zero bytes. - /// - /// Result: `70 00 00 00 00 00 00 00` (header size 112) followed by - /// `00 03 01 00 00 00 00 00` (file size 0x10300 = 66304, which is - /// `line_2007.dwg`'s exact byte count). - const HEADER_PREFIX: &[u8] = &[ - 0x20, 0x00, 0x00, 0x02, 0x00, 0x70, 0x70, 0x02, 0x01, 0x03, 0x58, 0x00, - ]; - - #[test] - fn header_prefix_decodes_to_the_two_documented_u64_fields() { - let out = decompress(HEADER_PREFIX, 16, DecompressLimits::default()).unwrap(); - assert_eq!(out.len(), 16); - assert_eq!(u64::from_le_bytes(out[0..8].try_into().unwrap()), 0x70); - assert_eq!(u64::from_le_bytes(out[8..16].try_into().unwrap()), 66304); - } - - #[test] - fn two_byte_literal_block_is_reversed() { - let mut out = Vec::new(); - emit_permuted(&[0xAA, 0xBB], 0, 2, &mut out).unwrap(); - assert_eq!(out, vec![0xBB, 0xAA]); - } - - #[test] - fn four_and_eight_byte_literal_blocks_are_straight() { - let src: Vec = (0..8).collect(); - let mut four = Vec::new(); - emit_permuted(&src, 0, 4, &mut four).unwrap(); - assert_eq!(four, vec![0, 1, 2, 3]); - let mut eight = Vec::new(); - emit_permuted(&src, 0, 8, &mut eight).unwrap(); - assert_eq!(eight, vec![0, 1, 2, 3, 4, 5, 6, 7]); - } - - #[test] - fn sixteen_byte_literal_block_swaps_its_halves() { - let src: Vec = (0..16).collect(); - let mut out = Vec::new(); - emit_permuted(&src, 0, 16, &mut out).unwrap(); - assert_eq!( - out, - vec![8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7] - ); - } - - /// The `AcDb:AppInfoHistory` measurement from the module docs: an - /// 11-byte literal whose `2 [9]` block must come out reversed for the - /// UTF-16 name to be well formed. - #[test] - fn eleven_byte_literal_reverses_only_its_two_byte_block() { - let src: Vec = (0..11).collect(); - let mut out = Vec::new(); - emit_permuted(&src, 0, 11, &mut out).unwrap(); - assert_eq!(out, vec![10, 9, 1, 2, 3, 4, 5, 6, 7, 8, 0]); - } - - #[test] - fn thirty_two_byte_block_swaps_its_halves_and_recurses() { - let src: Vec = (0..32).collect(); - let mut out = Vec::new(); - emit_permuted(&src, 0, 32, &mut out).unwrap(); - let expect: Vec = (24..32).chain(16..24).chain(8..16).chain(0..8).collect(); - assert_eq!(out, expect); - } - - #[test] - fn truncated_stream_errors_rather_than_panicking() { - let err = - decompress(&[0x20, 0x00, 0x00, 0x07], 64, DecompressLimits::default()).unwrap_err(); - assert!(matches!(err, Error::Lz77Truncated)); - } - - #[test] - fn output_cap_is_enforced() { - let limits = DecompressLimits { - max_output_bytes: 8, - ..DecompressLimits::default() - }; - let err = decompress(HEADER_PREFIX, 16, limits).unwrap_err(); - assert!(matches!(err, Error::Lz77OutputLimitExceeded { limit: 8 })); - } - - #[test] - fn empty_input_yields_empty_output() { - assert!( - decompress(&[], 0, DecompressLimits::default()) - .unwrap() - .is_empty() - ); - } -} diff --git a/src/reed_solomon.rs b/src/reed_solomon.rs index 986db19..8aa1d72 100644 --- a/src/reed_solomon.rs +++ b/src/reed_solomon.rs @@ -323,41 +323,3 @@ fn solve_magnitudes(syn: &[u8], positions: &[usize], f: &Field) -> Result Result> { // just to get a checksum; mirrors the shape of the public API. #[allow(unused_imports)] pub(crate) use crc::section_page_checksum as _unused_export; - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn page_map_empty_input() { - let pages = decode_page_map_entries(&[]).unwrap(); - assert!(pages.is_empty()); - } - - #[test] - fn page_map_single_positive_entry() { - // number = 1, size = 0x7400 - let mut data = Vec::new(); - data.extend_from_slice(&1i32.to_le_bytes()); - data.extend_from_slice(&0x7400u32.to_le_bytes()); - let pages = decode_page_map_entries(&data).unwrap(); - assert_eq!(pages.len(), 1); - assert_eq!(pages[0].number, 1); - assert_eq!(pages[0].size, 0x7400); - assert_eq!(pages[0].file_offset, 0x100); - assert!(!pages[0].is_gap); - } - - #[test] - fn page_map_gap_entry_consumes_four_trailing_u32s() { - // number = -2 (gap), size = 0x1000, then parent=9 left=10 right=11 end=0 - let mut data = Vec::new(); - data.extend_from_slice(&(-2i32).to_le_bytes()); - data.extend_from_slice(&0x1000u32.to_le_bytes()); - data.extend_from_slice(&9u32.to_le_bytes()); - data.extend_from_slice(&10u32.to_le_bytes()); - data.extend_from_slice(&11u32.to_le_bytes()); - data.extend_from_slice(&0u32.to_le_bytes()); - // then a valid positive entry to verify cursor advanced right - data.extend_from_slice(&3i32.to_le_bytes()); - data.extend_from_slice(&0x200u32.to_le_bytes()); - let pages = decode_page_map_entries(&data).unwrap(); - assert_eq!(pages.len(), 2); - assert_eq!(pages[0].number, -2); - assert!(pages[0].is_gap); - assert_eq!(pages[0].size, 0x1000); - assert_eq!(pages[1].number, 3); - assert_eq!(pages[1].file_offset, 0x100 + 0x1000); - } - - #[test] - fn section_info_empty_descriptions() { - // Header with NumDescriptions = 0 and 4 more u32 padding. - let mut data = Vec::new(); - data.extend_from_slice(&0u32.to_le_bytes()); - data.extend_from_slice(&2u32.to_le_bytes()); - data.extend_from_slice(&0x7400u32.to_le_bytes()); - data.extend_from_slice(&0u32.to_le_bytes()); - data.extend_from_slice(&0u32.to_le_bytes()); - let descs = decode_section_descriptions(&data).unwrap(); - assert!(descs.is_empty()); - } -}