package pdf import "core:slice" import "core:testing" // --------------------------------------------------------------------------- // Test-side encoder // // Two pieces, deliberately kept apart. lzw_test_codes runs the textbook LZW // encoder and produces a list of codes, which depends on nothing but the input // bytes. lzw_test_pack turns that list into a bit stream, and *that* is where // the code-width schedule lives. Splitting them means the round-trip tests // exercise the table logic without the packer's width rule being taken on // faith: the hand-computed vectors below pin the widths independently. // --------------------------------------------------------------------------- @(private = "file") Bit_Writer :: struct { bytes: [dynamic]byte, acc: u32, nbits: uint, } @(private = "file") bw_put :: proc(w: ^Bit_Writer, code: u16, width: uint) { w.acc = (w.acc << width) | u32(code) w.nbits += width for w.nbits >= 8 { w.nbits -= 8 append(&w.bytes, u8((w.acc >> w.nbits) & 0xff)) } } @(private = "file") bw_flush :: proc(w: ^Bit_Writer) { if w.nbits > 0 { append(&w.bytes, u8((w.acc << (8 - w.nbits)) & 0xff)) w.nbits = 0 } } // lzw_test_pack packs codes at the widths a decoder will read them at: the // table size is advanced exactly the way the decoder advances it (one entry // per code after the first following a clear). @(private = "file") lzw_test_pack :: proc(codes: []u16, early_change: bool, allocator := context.allocator) -> [dynamic]byte { w := Bit_Writer{bytes = make([dynamic]byte, allocator)} next := LZW_FIRST_CODE have_prev := false for c in codes { bw_put(&w, c, lzw_code_width(next, early_change)) switch c { case LZW_CLEAR: next = LZW_FIRST_CODE have_prev = false case LZW_EOD: // no table change case: if have_prev && next <= LZW_MAX_CODE { next += 1 } have_prev = true } } bw_flush(&w) return w.bytes } // lzw_test_codes is the plain TIFF encoder: it adds a table entry immediately // after emitting a code, which is why its table runs one ahead of a decoder's. @(private = "file") lzw_test_codes :: proc(input: []byte, allocator := context.allocator) -> [dynamic]u16 { codes := make([dynamic]u16, allocator) append(&codes, u16(LZW_CLEAR)) if len(input) == 0 { append(&codes, u16(LZW_EOD)) return codes } dict := make(map[u32]u16, 1024, context.allocator) defer delete(dict) next := u16(LZW_FIRST_CODE) omega := u16(input[0]) for i in 1 ..< len(input) { k := input[i] key := (u32(omega) << 8) | u32(k) if c, found := dict[key]; found { omega = c } else { append(&codes, omega) if int(next) <= LZW_MAX_CODE { dict[key] = next next += 1 } omega = u16(k) } } append(&codes, omega) append(&codes, u16(LZW_EOD)) return codes } @(private = "file") lzw_test_encode :: proc(input: []byte, early_change: bool, allocator := context.allocator) -> [dynamic]byte { codes := lzw_test_codes(input, context.allocator) defer delete(codes) return lzw_test_pack(codes[:], early_change, allocator) } // --------------------------------------------------------------------------- // Published vector: TIFF 6.0 section 13 encodes "-----A---B" as the codes // 256 45 258 258 65 259 66 257, packed at 9 bits into these nine bytes. // --------------------------------------------------------------------------- @(private = "file") TIFF_CODES := [?]u16{256, 45, 258, 258, 65, 259, 66, 257} @(private = "file") TIFF_PACKED := [?]byte{0x80, 0x0b, 0x60, 0x50, 0x22, 0x0c, 0x0c, 0x85, 0x01} @(private = "file") TIFF_PLAIN :: "-----A---B" @(test) test_lzw_tiff_vector :: proc(t: ^testing.T) { for ec in ([?]bool{true, false}) { out, err := lzw_decode(TIFF_PACKED[:], ec) defer delete(out) testing.expect_value(t, err, Error.None) testing.expect_value(t, string(out), TIFF_PLAIN) } } // The packer has to reproduce the spec's bytes exactly, or every round-trip // test below would only be proving the decoder consistent with itself. @(test) test_lzw_packer_matches_tiff_vector :: proc(t: ^testing.T) { packed := lzw_test_pack(TIFF_CODES[:], true) defer delete(packed) testing.expect(t, slice.equal(packed[:], TIFF_PACKED[:]), "packer disagrees with TIFF 6.0 vector") codes := lzw_test_codes(transmute([]byte)string(TIFF_PLAIN)) defer delete(codes) testing.expect(t, slice.equal(codes[:], TIFF_CODES[:]), "encoder disagrees with TIFF 6.0 vector") } // --------------------------------------------------------------------------- // KwKwK: "AAAAAA" encodes to 256 65 258 259 257, where 258 and 259 are both // used by the code that defines them. Packed at 9 bits: 80 10 60 50 38 08. // --------------------------------------------------------------------------- @(test) test_lzw_kwkwk :: proc(t: ^testing.T) { packed := [?]byte{0x80, 0x10, 0x60, 0x50, 0x38, 0x08} out, err := lzw_decode(packed[:]) defer delete(out) testing.expect_value(t, err, Error.None) testing.expect_value(t, string(out), "AAAAAA") } @(test) test_lzw_kwkwk_without_predecessor :: proc(t: ^testing.T) { // 258 immediately after a clear names a string that cannot exist yet. codes := [?]u16{256, 258, 257} packed := lzw_test_pack(codes[:], true) defer delete(packed) out, err := lzw_decode(packed[:]) defer delete(out) testing.expect_value(t, err, Error.Bad_Lzw_Data) testing.expect_value(t, len(out), 0) } // --------------------------------------------------------------------------- // Degenerate and empty streams // --------------------------------------------------------------------------- @(test) test_lzw_empty_input :: proc(t: ^testing.T) { out, err := lzw_decode(nil) defer delete(out) testing.expect_value(t, err, Error.None) testing.expect_value(t, len(out), 0) } @(test) test_lzw_eod_only :: proc(t: ^testing.T) { for ec in ([?]bool{true, false}) { codes := [?]u16{256, 257} packed := lzw_test_pack(codes[:], ec) defer delete(packed) out, err := lzw_decode(packed[:], ec) defer delete(out) // An empty stream is legal; it must not be reported as damaged. testing.expect_value(t, err, Error.None) testing.expect_value(t, len(out), 0) } } @(test) test_lzw_too_short_for_a_code :: proc(t: ^testing.T) { // One byte cannot hold a 9-bit code, so nothing at all comes out. short := [?]byte{0x80} out, err := lzw_decode(short[:]) defer delete(out) testing.expect_value(t, err, Error.Bad_Lzw_Data) testing.expect_value(t, len(out), 0) } @(test) test_lzw_undefined_code :: proc(t: ^testing.T) { // 300 is far beyond the two entries that exist at that point. codes := [?]u16{256, u16('X'), 300, 257} packed := lzw_test_pack(codes[:], true) defer delete(packed) out, err := lzw_decode(packed[:]) defer delete(out) testing.expect_value(t, err, Error.None) testing.expect_value(t, string(out), "X") } @(test) test_lzw_missing_eod :: proc(t: ^testing.T) { // Everything but the trailing EOD; the decoder must stop at end of input // rather than demanding the marker. codes := TIFF_CODES[:len(TIFF_CODES) - 1] packed := lzw_test_pack(codes, true) defer delete(packed) out, err := lzw_decode(packed[:]) defer delete(out) testing.expect_value(t, err, Error.None) testing.expect_value(t, string(out), TIFF_PLAIN) } @(test) test_lzw_truncated_at_every_offset :: proc(t: ^testing.T) { // Truncation drops whole codes off the end, so whatever survives has to be // a prefix of the full output -- and nothing may crash or hang. whole := TIFF_PLAIN for n in 0 ..= len(TIFF_PACKED) { out, err := lzw_decode(TIFF_PACKED[:n]) defer delete(out) got := string(out) testing.expect(t, len(got) <= len(whole), "truncated input produced too much output") testing.expect(t, got == whole[:len(got)], "truncated output is not a prefix of the whole") if len(got) > 0 { testing.expect_value(t, err, Error.None) } } } @(test) test_lzw_clear_mid_stream :: proc(t: ^testing.T) { // A clear resets the table, so the codes after it mean what they would at // the start of a stream. codes := [?]u16{256, u16('a'), u16('b'), 258, 256, u16('a'), u16('b'), 258, 257} packed := lzw_test_pack(codes[:], true) defer delete(packed) out, err := lzw_decode(packed[:]) defer delete(out) testing.expect_value(t, err, Error.None) // 258 is "ab" both times; without the reset the second 258 would still be // "ab" but the entry numbering afterwards would differ. testing.expect_value(t, string(out), "abababab") } // --------------------------------------------------------------------------- // Code width and /EarlyChange // --------------------------------------------------------------------------- @(test) test_lzw_code_width_boundaries :: proc(t: ^testing.T) { Case :: struct { next: int, early: bool, width: uint, } cases := [?]Case{ {258, true, 9}, {510, true, 9}, {511, true, 10}, {512, true, 10}, {1022, true, 10}, {1023, true, 11}, {2046, true, 11}, {2047, true, 12}, {4095, true, 12}, {4096, true, 12}, {258, false, 9}, {511, false, 9}, {512, false, 10}, {1023, false, 10}, {1024, false, 11}, {2047, false, 11}, {2048, false, 12}, {4096, false, 12}, } for c in cases { testing.expect_value(t, lzw_code_width(c.next, c.early), c.width) } } // 254 literal codes leave the table holding exactly 511 entries, which is the // one place /EarlyChange 0 and 1 disagree about how wide the next code is. @(private = "file") lzw_width_boundary_codes :: proc(allocator := context.allocator) -> [dynamic]u16 { codes := make([dynamic]u16, allocator) append(&codes, u16(LZW_CLEAR)) for i in 0 ..< 255 { append(&codes, u16(i)) } append(&codes, u16(LZW_EOD)) return codes } @(test) test_lzw_early_change_boundary :: proc(t: ^testing.T) { codes := lzw_width_boundary_codes() defer delete(codes) expect := make([]byte, 255) defer delete(expect) for i in 0 ..< 255 { expect[i] = u8(i) } for ec in ([?]bool{true, false}) { packed := lzw_test_pack(codes[:], ec) defer delete(packed) out, err := lzw_decode(packed[:], ec) defer delete(out) testing.expect_value(t, err, Error.None) testing.expect(t, slice.equal(out, expect), "width boundary round-trip failed") } // The two settings really do produce different bytes, and reading one with // the other's rule desynchronises rather than silently agreeing. early := lzw_test_pack(codes[:], true) defer delete(early) late := lzw_test_pack(codes[:], false) defer delete(late) testing.expect(t, !slice.equal(early[:], late[:]), "EarlyChange made no difference to the bit stream") wrong, wrong_err := lzw_decode(early[:], false) defer delete(wrong) testing.expect_value(t, wrong_err, Error.None) testing.expect(t, !slice.equal(wrong, expect), "decoding with the wrong EarlyChange should not match") } // --------------------------------------------------------------------------- // Round trips, including a table that fills up // --------------------------------------------------------------------------- @(private = "file") lzw_round_trip :: proc(t: ^testing.T, input: []byte, label: string, loc := #caller_location) { for ec in ([?]bool{true, false}) { packed := lzw_test_encode(input, ec) defer delete(packed) out, err := lzw_decode(packed[:], ec) defer delete(out) testing.expect_value(t, err, Error.None, loc = loc) testing.expectf(t, slice.equal(out, input), "%s round trip failed (early_change=%v)", label, ec, loc = loc) } } @(test) test_lzw_round_trip_small :: proc(t: ^testing.T) { inputs := [?]string{ "-----A---B", "A", "AA", "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "ABABABABABABABABABABABABA", "the quick brown fox jumps over the lazy dog", "\x00\x00\x00\xff\xff\xff\x00\x00\x00\xff\xff\xff", } for s in inputs { lzw_round_trip(t, transmute([]byte)s, s) } } @(test) test_lzw_round_trip_all_byte_values :: proc(t: ^testing.T) { input := make([]byte, 256 * 8) defer delete(input) for i in 0 ..< len(input) { input[i] = u8(i & 0xff) } lzw_round_trip(t, input, "all byte values") } // A stream long enough to exhaust all 3838 table entries without ever sending // a clear: the decoder has to stop growing the table and keep going. @(test) test_lzw_table_overflow_without_clear :: proc(t: ^testing.T) { input := make([]byte, 40000) defer delete(input) state: u32 = 0x1234_5678 for i in 0 ..< len(input) { state = state * 1664525 + 1013904223 input[i] = u8((state >> 19) & 0xff) } codes := lzw_test_codes(input) defer delete(codes) // Confirms the premise: many more codes than the table can hold entries. testing.expect(t, len(codes) > LZW_MAX_CODE + 500, "test input did not overflow the table") lzw_round_trip(t, input, "table overflow") } @(test) test_lzw_long_run_expands :: proc(t: ^testing.T) { // Highly compressible input exercises the growth path of the output // buffer and the long prefix chains that go with it. input := make([]byte, 100_000) defer delete(input) slice.fill(input, u8('Z')) lzw_round_trip(t, input, "long run") } // --------------------------------------------------------------------------- // Nothing may crash, hang or leak on arbitrary bytes // --------------------------------------------------------------------------- @(test) test_lzw_arbitrary_bytes :: proc(t: ^testing.T) { buf := make([]byte, 512) defer delete(buf) state: u32 = 0xdead_beef for _ in 0 ..< 200 { n := (int(state >> 7) % len(buf)) + 1 for i in 0 ..< n { state = state * 1103515245 + 12345 buf[i] = u8(state >> 16) } for ec in ([?]bool{true, false}) { out, err := lzw_decode(buf[:n], ec) defer delete(out) if err != .None { testing.expect_value(t, len(out), 0) } } } } @(test) test_lzw_all_zero_bytes :: proc(t: ^testing.T) { // Nine zero bits is code 0, so this is a run of NUL bytes plus whatever the // growing table makes of it -- a cheap check that a pathological but legal // stream terminates. zeros := make([]byte, 4096) defer delete(zeros) out, err := lzw_decode(zeros) defer delete(out) testing.expect_value(t, err, Error.None) testing.expect(t, len(out) > 0, "a stream of zero bits should decode to something") for b in out { testing.expect_value(t, b, u8(0)) } } @(test) test_lzw_all_ff_bytes :: proc(t: ^testing.T) { ones := make([]byte, 4096) defer delete(ones) slice.fill(ones, u8(0xff)) out, err := lzw_decode(ones) defer delete(out) // 0x1ff is 511, an undefined code straight after the start, so nothing is // decodable -- but the call must still return cleanly. testing.expect_value(t, err, Error.Bad_Lzw_Data) testing.expect_value(t, len(out), 0) } // LZW's expansion is not knowable in advance, so lzw_decode sizes its buffer // from the input length and grows it as it goes. Against a Document arena the // first guess and every superseded growth buffer would be permanent -- an // arena's `delete` is a silent no-op -- and a stream that decodes to nothing // would leave the whole guess behind. apply_filter is where that is prevented, // by decoding into scratch and copying the result across, so what the arena is // charged is the output and nothing else. @(test) test_lzw_growth_stays_out_of_the_document_arena :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) // 0x82 0x00 is the 9-bit code 260, which no table defines at the start of // a stream, so not one byte is decoded -- after 300 KiB have been reserved // for output that will never be written. junk := make([]byte, 100 * 1024) defer delete(junk) junk[0] = 0x82 before := document_memory_used(doc) out, err := apply_filter(doc, "LZWDecode", junk, nil, doc.allocator) testing.expect_value(t, err, Error.Bad_Lzw_Data) testing.expect_value(t, len(out), 0) testing.expectf(t, document_memory_used(doc) == before, "%d bytes stranded in the arena by a stream that decoded to nothing", document_memory_used(doc) - before) // 16 KiB out of a buffer that starts at a few kilobytes: a handful of // regrowths, every one of which would be permanent if it came from here. plain := make([]byte, 16 * 1024) defer delete(plain) for i in 0 ..< len(plain) { plain[i] = byte((i / 64) % 251) } packed := lzw_test_encode(plain, true) defer delete(packed) before = document_memory_used(doc) out2, err2 := apply_filter(doc, "LZWDecode", packed[:], nil, doc.allocator) testing.expect_value(t, err2, Error.None) testing.expect(t, slice.equal(out2, plain), "the stream did not round trip") growth := document_memory_used(doc) - before testing.expectf(t, growth <= len(out2) + FT_ARENA_SLACK, "the arena grew by %d bytes for %d bytes of output", growth, len(out2)) }