package pdf import "core:mem/virtual" import "core:slice" import "core:testing" // The predictor vectors below are hand-computed from RFC 2083 section 6 and // TIFF 6.0 section 14 and are written out byte by byte in the test that uses // them, so a change of behaviour has to be argued with rather than re-blessed. // The compressed vectors come from zlib via CPython, and the LZW one is the // published TIFF 6.0 example. // --------------------------------------------------------------------------- // Vectors // --------------------------------------------------------------------------- @(private = "file") V_CHAIN_PLAIN :: "Hello, filter chain!" // zlib.compress(V_CHAIN_PLAIN) @(private = "file") v_chain_flate := [?]byte{ 0x78, 0xda, 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0xd7, 0x51, 0x48, 0xcb, 0xcc, 0x29, 0x49, 0x2d, 0x52, 0x48, 0xce, 0x48, 0xcc, 0xcc, 0x53, 0x04, 0x00, 0x4a, 0x94, 0x07, 0x0b, } // the same bytes in ASCII85: "GhVa[c,n)Z;+SmS.7l*h8BscMbYnWE8sBPg~>" @(private = "file") v_chain_a85 := [?]byte{ 0x47, 0x68, 0x56, 0x61, 0x5b, 0x63, 0x2c, 0x6e, 0x29, 0x5a, 0x3b, 0x2b, 0x53, 0x6d, 0x53, 0x2e, 0x37, 0x6c, 0x2a, 0x68, 0x38, 0x42, 0x73, 0x63, 0x4d, 0x62, 0x59, 0x6e, 0x57, 0x45, 0x38, 0x73, 0x42, 0x50, 0x67, 0x7e, 0x3e, } // The shape an xref stream has: three four-byte rows, PNG Up-filtered, then // deflated. The encoded rows are {2,1,0,0,0}, {2,0,0,0,1}, {2,0,0,0,1} and // they reconstruct to V_PRED_PAYLOAD. @(private = "file") v_pred_flate := [?]byte{ 0x78, 0xda, 0x63, 0x62, 0x64, 0x60, 0x60, 0x60, 0x02, 0x62, 0x46, 0x30, 0x01, 0x00, 0x00, 0x60, 0x00, 0x0a, } @(private = "file") v_pred_payload := [?]byte{1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 2} // TIFF 6.0 section 13: "-----A---B" packed at 9 bits. @(private = "file") v_lzw_tiff := [?]byte{0x80, 0x0b, 0x60, 0x50, 0x22, 0x0c, 0x0c, 0x85, 0x01} @(private = "file") V_LZW_PLAIN :: "-----A---B" // --------------------------------------------------------------------------- // Fixtures // // A Document built by hand: filters need nothing but the arena and the repair // list, and going through open_bytes would drag in the whole file structure. // --------------------------------------------------------------------------- // Package-private rather than file-private: the codec test files measure how // much of this arena a decode costs, and that rule belongs to apply_filter // rather than to any one codec, so they should not each grow a Document of // their own. @(private) filter_test_doc :: proc() -> ^Document { doc := new(Document) doc.backing = context.allocator if err := virtual.arena_init_growing(&doc.arena); err != nil { free(doc) return nil } doc.allocator = virtual.arena_allocator(&doc.arena) doc.repairs = make([dynamic]Repair, doc.allocator) return doc } @(private) filter_test_doc_free :: proc(doc: ^Document) { if doc == nil { return } virtual.arena_destroy(&doc.arena) free(doc, doc.backing) } @(private = "file") ft_names :: proc(doc: ^Document, names: []string) -> ^Array { a := new_array(doc.allocator) for n in names { array_push(a, Name(n)) } return a } // ft_parms builds a parameter dictionary from alternating key/value pairs, // where every value is an integer -- which is all the predictor and // /EarlyChange need. @(private = "file") ft_parms :: proc(doc: ^Document, keys: []string, values: []i64) -> ^Dict { d := new_dict(doc.allocator) for k, i in keys { if i < len(values) { dict_set(d, k, values[i]) } } return d } @(private = "file") ft_stream :: proc(doc: ^Document, raw: []byte, filter: Object, parms: Object, filter_key := "Filter", parms_key := "DecodeParms") -> ^Stream { d := new_dict(doc.allocator) if filter != nil { dict_set(d, filter_key, filter) } if parms != nil { dict_set(d, parms_key, parms) } return new_stream(d, raw, doc.allocator) } @(private = "file") ft_repairs :: proc(doc: ^Document, kind: Repair_Kind) -> int { n := 0 for r in doc.repairs { if r.kind == kind { n += 1 } } return n } // --------------------------------------------------------------------------- // Filter names // --------------------------------------------------------------------------- @(test) test_filter_kind_accepts_full_names_and_abbreviations :: proc(t: ^testing.T) { Case :: struct { name: string, kind: Filter_Kind, } cases := [?]Case{ {"ASCIIHexDecode", .Ascii_Hex}, {"AHx", .Ascii_Hex}, {"ASCII85Decode", .Ascii85}, {"A85", .Ascii85}, {"LZWDecode", .Lzw}, {"LZW", .Lzw}, {"FlateDecode", .Flate}, {"Fl", .Flate}, {"RunLengthDecode", .Run_Length}, {"RL", .Run_Length}, {"CCITTFaxDecode", .Ccitt}, {"CCF", .Ccitt}, {"DCTDecode", .Dct}, {"DCT", .Dct}, {"JPXDecode", .Jpx}, {"JBIG2Decode", .Jbig2}, {"Crypt", .Crypt}, {"", .Unknown}, {"Flate", .Unknown}, {"flatedecode", .Unknown}, {"NotAFilter", .Unknown}, } for c in cases { testing.expectf(t, filter_kind(c.name) == c.kind, "filter_kind(%q) = %v, want %v", c.name, filter_kind(c.name), c.kind) } // The abbreviation is always reported back in full, so the image layer has // one spelling to compare against. testing.expect_value(t, filter_name(filter_kind("DCT"), "DCT"), "DCTDecode") testing.expect_value(t, filter_name(.Unknown, "Whatever"), "Whatever") } // --------------------------------------------------------------------------- // PNG predictors -- RFC 2083, 6.2 to 6.6 // --------------------------------------------------------------------------- // One row per filter type, each hand-reconstructed above: // // None 10 20 30 // Sub 1 1+2=3 3+3=6 // Up 5+1 5+3 5+6 // Average 1+(0+6)/2 1+(4+8)/2 1+(7+11)/2 // Paeth 1+b(4) 2+b(7) 3+b(10) @(test) test_predictor_png_every_filter :: proc(t: ^testing.T) { data := [?]byte{ 0, 10, 20, 30, 1, 1, 2, 3, 2, 5, 5, 5, 3, 1, 1, 1, 4, 1, 2, 3, } want := [?]byte{ 10, 20, 30, 1, 3, 6, 6, 8, 11, 4, 7, 10, 5, 9, 13, } out, err := apply_predictor(data[:], 12, 1, 8, 3) defer delete(out) testing.expect_value(t, err, Error.None) testing.expectf(t, slice.equal(out, want[:]), "got %v, want %v", out, want) } // Every /Predictor from 10 to 15 decodes identically: the value records the // widest filter the encoder was allowed to pick, and the row tag is what // actually selects one. @(test) test_predictor_png_values_10_to_15_agree :: proc(t: ^testing.T) { data := [?]byte{1, 1, 2, 3, 2, 5, 5, 5} want := [?]byte{1, 3, 6, 6, 8, 11} for p in 10 ..= 15 { out, err := apply_predictor(data[:], p, 1, 8, 3) defer delete(out) testing.expectf(t, err == .None, "predictor %d: %s", p, error_string(err)) testing.expectf(t, slice.equal(out, want[:]), "predictor %d: got %v", p, out) } } // On the first row the filters read zeroes for the line above, so Up is a // no-op, Average halves the left neighbour and Paeth degenerates to Sub. @(test) test_predictor_png_first_row_reads_zeroes :: proc(t: ^testing.T) { Case :: struct { tag: byte, want: [2]byte, } cases := [?]Case{ {0, {5, 3}}, {1, {5, 8}}, {2, {5, 3}}, {3, {5, 5}}, {4, {5, 8}}, } for c in cases { data := [?]byte{c.tag, 5, 3} want := c.want out, err := apply_predictor(data[:], 15, 1, 8, 2) defer delete(out) testing.expectf(t, err == .None, "tag %d: %s", c.tag, error_string(err)) testing.expectf(t, slice.equal(out, want[:]), "tag %d: got %v, want %v", c.tag, out, want) } } // bpp is the byte distance to the pixel on the left, so Sub over three // 8-bit colours adds the value three bytes back, not one. @(test) test_predictor_png_multibyte_pixel :: proc(t: ^testing.T) { data := [?]byte{1, 10, 20, 30, 1, 2, 3} want := [?]byte{10, 20, 30, 11, 22, 33} out, err := apply_predictor(data[:], 12, 3, 8, 2) defer delete(out) testing.expect_value(t, err, Error.None) testing.expectf(t, slice.equal(out, want[:]), "got %v, want %v", out, want) } // bpp has a floor of 1: four 1-bit colours are half a byte per pixel, and Sub // still reaches one byte back. @(test) test_predictor_png_sub_byte_pixel :: proc(t: ^testing.T) { data := [?]byte{1, 0x11, 0x22} want := [?]byte{0x11, 0x33} out, err := apply_predictor(data[:], 12, 4, 1, 4) defer delete(out) testing.expect_value(t, err, Error.None) testing.expectf(t, slice.equal(out, want[:]), "got %v, want %v", out, want) } // Arithmetic is modulo 256 in both directions (RFC 2083, 6.2). @(test) test_predictor_png_wraps_at_256 :: proc(t: ^testing.T) { data := [?]byte{1, 200, 100, 2, 200} // 200; 200+100 = 300 mod 256 = 44; then Up: 200+200 = 400 mod 256 = 144. want := [?]byte{200, 44, 144, 44} out, err := apply_predictor(data[:], 12, 1, 8, 2) defer delete(out) testing.expect_value(t, err, Error.None) testing.expectf(t, slice.equal(out, want[:]), "got %v, want %v", out, want) } // A tag no encoder emits: one byte lost from the front of the payload shifts // every tag, and reading the row unfiltered keeps the rest of the stream in // step instead of losing all of it. The row after it still decodes against // the row the unknown tag produced. @(test) test_predictor_png_unknown_row_tag_is_none :: proc(t: ^testing.T) { data := [?]byte{9, 7, 7, 2, 1, 2} want := [?]byte{7, 7, 8, 9} out, err := apply_predictor(data[:], 12, 1, 8, 2) defer delete(out) testing.expect_value(t, err, Error.None) testing.expectf(t, slice.equal(out, want[:]), "got %v, want %v", out, want) } @(test) test_predictor_paeth_matches_the_reference :: proc(t: ^testing.T) { reference :: proc(a, b, c: int) -> int { p := a + b - c pa, pb, pc := abs(p - a), abs(p - b), abs(p - c) if pa <= pb && pa <= pc { return a } else if pb <= pc { return b } return c } for a := 0; a < 256; a += 7 { for b := 0; b < 256; b += 5 { for c := 0; c < 256; c += 3 { testing.expectf(t, predictor_paeth(a, b, c) == reference(a, b, c), "paeth(%d,%d,%d) = %d, want %d", a, b, c, predictor_paeth(a, b, c), reference(a, b, c)) } } } } // --------------------------------------------------------------------------- // TIFF predictor -- TIFF 6.0, section 14 // --------------------------------------------------------------------------- @(test) test_predictor_tiff_8_bit :: proc(t: ^testing.T) { data := [?]byte{10, 5, 5, 1, 1, 1} want := [?]byte{10, 15, 20, 1, 2, 3} out, err := apply_predictor(data[:], 2, 1, 8, 3) defer delete(out) testing.expect_value(t, err, Error.None) testing.expectf(t, slice.equal(out, want[:]), "got %v, want %v", out, want) } // Differencing is per colour, so with three colours each component is the // difference from the same component of the pixel to its left. @(test) test_predictor_tiff_8_bit_three_colours :: proc(t: ^testing.T) { data := [?]byte{10, 20, 30, 1, 2, 3} want := [?]byte{10, 20, 30, 11, 22, 33} out, err := apply_predictor(data[:], 2, 3, 8, 2) defer delete(out) testing.expect_value(t, err, Error.None) testing.expectf(t, slice.equal(out, want[:]), "got %v, want %v", out, want) } // Four 4-bit components in two bytes: 1 2 3 4 accumulates to 1 3 6 10. @(test) test_predictor_tiff_4_bit :: proc(t: ^testing.T) { data := [?]byte{0x12, 0x34} want := [?]byte{0x13, 0x6a} out, err := apply_predictor(data[:], 2, 1, 4, 4) defer delete(out) testing.expect_value(t, err, Error.None) testing.expectf(t, slice.equal(out, want[:]), "got %x, want %x", out, want) } // The same two bytes read as two pixels of two 4-bit colours: component 2 adds // component 0 and component 3 adds component 1. @(test) test_predictor_tiff_4_bit_two_colours :: proc(t: ^testing.T) { data := [?]byte{0x12, 0x34} want := [?]byte{0x12, 0x46} out, err := apply_predictor(data[:], 2, 2, 4, 2) defer delete(out) testing.expect_value(t, err, Error.None) testing.expectf(t, slice.equal(out, want[:]), "got %x, want %x", out, want) } // Four 2-bit components in one byte: 1 2 3 1 accumulates to 1 3 2 3, each // sum taken modulo 4. @(test) test_predictor_tiff_2_bit :: proc(t: ^testing.T) { data := [?]byte{0b01_10_11_01} want := [?]byte{0b01_11_10_11} out, err := apply_predictor(data[:], 2, 1, 2, 4) defer delete(out) testing.expect_value(t, err, Error.None) testing.expectf(t, slice.equal(out, want[:]), "got %b, want %b", out, want) } // 1-bit components add modulo 2, which is an exclusive or along the row. @(test) test_predictor_tiff_1_bit :: proc(t: ^testing.T) { data := [?]byte{0xaa} want := [?]byte{0xcc} out, err := apply_predictor(data[:], 2, 1, 1, 8) defer delete(out) testing.expect_value(t, err, Error.None) testing.expectf(t, slice.equal(out, want[:]), "got %x, want %x", out, want) } // 16-bit components are big-endian and wrap at 65536, not at 256. @(test) test_predictor_tiff_16_bit :: proc(t: ^testing.T) { Case :: struct { data: [4]byte, want: [4]byte, } cases := [?]Case{ {{0x00, 0x10, 0x00, 0x20}, {0x00, 0x10, 0x00, 0x30}}, {{0xff, 0xff, 0x00, 0x02}, {0xff, 0xff, 0x00, 0x01}}, } for c in cases { input := c.data want := c.want out, err := apply_predictor(input[:], 2, 1, 16, 2) defer delete(out) testing.expect_value(t, err, Error.None) testing.expectf(t, slice.equal(out, want[:]), "got %x, want %x", out, want) } } // The padding bits at the end of a row are not components and must be left // alone: 3 columns of 4 bits occupy a byte and a half. @(test) test_predictor_tiff_ignores_row_padding :: proc(t: ^testing.T) { data := [?]byte{0x12, 0x3f} // components 1, 2, 3 -> 1, 3, 6; the low nibble of byte 1 is padding. want := [?]byte{0x13, 0x6f} out, err := apply_predictor(data[:], 2, 1, 4, 3) defer delete(out) testing.expect_value(t, err, Error.None) testing.expectf(t, slice.equal(out, want[:]), "got %x, want %x", out, want) } // --------------------------------------------------------------------------- // Predictor edge cases // --------------------------------------------------------------------------- // A payload that stops mid-row is padded, not dropped. A truncated xref stream // still describes the objects it got to. @(test) test_predictor_truncated_final_row_is_padded :: proc(t: ^testing.T) { png := [?]byte{0, 10, 20, 30, 2, 1} png_want := [?]byte{10, 20, 30, 11, 20, 30} out, err := apply_predictor(png[:], 12, 1, 8, 3) defer delete(out) testing.expect_value(t, err, Error.None) testing.expectf(t, slice.equal(out, png_want[:]), "png: got %v, want %v", out, png_want) tiff := [?]byte{10, 5, 5, 1, 1} tiff_want := [?]byte{10, 15, 20, 1, 2, 2} out2, err2 := apply_predictor(tiff[:], 2, 1, 8, 3) defer delete(out2) testing.expect_value(t, err2, Error.None) testing.expectf(t, slice.equal(out2, tiff_want[:]), "tiff: got %v, want %v", out2, tiff_want) } // A row consisting of nothing but its filter tag still produces a row. @(test) test_predictor_final_row_is_only_a_tag :: proc(t: ^testing.T) { data := [?]byte{0, 10, 20, 1} want := [?]byte{10, 20, 0, 0} out, err := apply_predictor(data[:], 12, 1, 8, 2) defer delete(out) testing.expect_value(t, err, Error.None) testing.expectf(t, slice.equal(out, want[:]), "got %v, want %v", out, want) } @(test) test_predictor_none_copies :: proc(t: ^testing.T) { data := [?]byte{1, 2, 3, 4} for p in 0 ..= 1 { out, err := apply_predictor(data[:], p, 1, 8, 4) defer delete(out) testing.expectf(t, err == .None, "predictor %d: %s", p, error_string(err)) testing.expectf(t, slice.equal(out, data[:]), "predictor %d changed the data", p) testing.expectf(t, raw_data(out) != raw_data(data[:]), "predictor %d aliased its input", p) } } @(test) test_predictor_empty_input :: proc(t: ^testing.T) { for p in ([?]int{1, 2, 12}) { out, err := apply_predictor(nil, p, 1, 8, 4) defer delete(out) testing.expectf(t, err == .None, "predictor %d: %s", p, error_string(err)) testing.expectf(t, len(out) == 0, "predictor %d invented %d bytes", p, len(out)) } } @(test) test_predictor_rejects_absurd_parameters :: proc(t: ^testing.T) { Case :: struct { label: string, predictor, colors, bpc, columns: int, } cases := [?]Case{ {"predictor 3 is not defined", 3, 1, 8, 4}, {"predictor 9 is not defined", 9, 1, 8, 4}, {"predictor 16 is past PNG", 16, 1, 8, 4}, {"negative predictor", -1, 1, 8, 4}, {"zero colours", 12, 0, 8, 4}, {"negative colours", 12, -3, 8, 4}, {"more colours than DeviceN", 12, 33, 8, 4}, {"3 bits per component", 12, 1, 3, 4}, {"zero bits per component", 12, 1, 0, 4}, {"32 bits per component", 12, 1, 32, 4}, {"negative bits per component", 12, 1, -8, 4}, {"zero columns", 12, 1, 8, 0}, {"negative columns", 12, 1, 8, -4}, {"absurd columns", 12, 1, 8, 1 << 30}, {"absurd row", 2, 32, 16, 1 << 20}, } data := [?]byte{0, 1, 2, 3, 4, 5, 6, 7} for c in cases { out, err := apply_predictor(data[:], c.predictor, c.colors, c.bpc, c.columns) defer delete(out) testing.expectf(t, err == .Bad_Predictor, "%s: got %s", c.label, error_string(err)) testing.expectf(t, out == nil, "%s: returned %d bytes as well", c.label, len(out)) } } // Padding the final row is the only way the output can exceed the input, so // the ceiling on an allocation the file controls is one row -- and a row is // itself capped, which is what MAX_PREDICTOR_ROW_BYTES is for. @(test) test_predictor_output_is_bounded_by_one_row :: proc(t: ^testing.T) { Case :: struct { predictor, colors, bpc, columns: int, row_bytes: int, } cases := [?]Case{ {15, 32, 16, 1 << 15, 32 * 16 * (1 << 15) / 8}, {12, 1, 8, 1 << 20, 1 << 20}, {2, 4, 8, 1000, 4000}, {12, 1, 1, 7, 1}, } data := make([]byte, 4096) defer delete(data) for c in cases { out, err := apply_predictor(data, c.predictor, c.colors, c.bpc, c.columns) defer delete(out) testing.expectf(t, err == .None, "%v: %s", c, error_string(err)) testing.expectf(t, len(out) <= len(data) + c.row_bytes, "%v: %d bytes out of %d in, more than one row of slack", c, len(out), len(data)) } } // --------------------------------------------------------------------------- // apply_filter // --------------------------------------------------------------------------- @(test) test_apply_filter_byte_filters :: proc(t: ^testing.T) { Case :: struct { name: string, raw: string, want: string, } cases := [?]Case{ {"ASCIIHexDecode", "48656C6C6F>", "Hello"}, {"AHx", "48656C6C6F>", "Hello"}, {"ASCII85Decode", "87cURD]j7BEbo7~>", "Hello world"}, {"A85", "87cURD]j7BEbo7~>", "Hello world"}, {"RunLengthDecode", "\x02abc\xfeZ\x80", "abcZZZ"}, {"RL", "\x02abc\xfeZ\x80", "abcZZZ"}, } for c in cases { out, err := apply_filter(nil, c.name, transmute([]byte)c.raw, nil) defer delete(out) testing.expectf(t, err == .None, "%s: %s", c.name, error_string(err)) testing.expectf(t, string(out) == c.want, "%s: got %q, want %q", c.name, string(out), c.want) } } @(test) test_apply_filter_flate :: proc(t: ^testing.T) { out, err := apply_filter(nil, "Fl", v_chain_flate[:], nil) defer delete(out) testing.expect_value(t, err, Error.None) testing.expect_value(t, string(out), V_CHAIN_PLAIN) } @(test) test_apply_filter_lzw_honours_early_change :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) for value in ([?]i64{0, 1}) { parms := ft_parms(doc, {"EarlyChange"}, {value}) out, err := apply_filter(doc, "LZW", v_lzw_tiff[:], parms) defer delete(out) testing.expectf(t, err == .None, "EarlyChange %d: %s", value, error_string(err)) testing.expectf(t, string(out) == V_LZW_PLAIN, "EarlyChange %d: got %q", value, string(out)) } // No parameters at all means /EarlyChange 1. out, err := apply_filter(nil, "LZWDecode", v_lzw_tiff[:], nil) defer delete(out) testing.expect_value(t, err, Error.None) testing.expect_value(t, string(out), V_LZW_PLAIN) } @(test) test_apply_filter_runs_the_predictor :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) parms := ft_parms(doc, {"Predictor", "Columns"}, {12, 4}) out, err := apply_filter(doc, "FlateDecode", v_pred_flate[:], parms) defer delete(out) testing.expect_value(t, err, Error.None) testing.expectf(t, slice.equal(out, v_pred_payload[:]), "got %v, want %v", out, v_pred_payload) } // LZW with the TIFF predictor, end to end: "-----A---B" read as two rows of // five 8-bit components, each accumulating along its row modulo 256. @(test) test_stream_data_lzw_with_the_tiff_predictor :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) parms := ft_parms(doc, {"Predictor", "Colors", "BitsPerComponent", "Columns"}, {2, 1, 8, 5}) s := ft_stream(doc, v_lzw_tiff[:], Name("LZWDecode"), parms) got, err := stream_data(doc, s) want := [?]byte{45, 90, 135, 180, 225, 65, 110, 155, 200, 10} testing.expect_value(t, err, Error.None) testing.expectf(t, slice.equal(got, want[:]), "got %v, want %v", got, want) } // A predictor whose parameters cannot describe any image fails the filter // rather than handing back bytes that only look like samples. @(test) test_apply_filter_bad_predictor_fails :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) parms := ft_parms(doc, {"Predictor", "Columns"}, {7, 4}) out, err := apply_filter(doc, "FlateDecode", v_pred_flate[:], parms) defer delete(out) testing.expect_value(t, err, Error.Bad_Predictor) testing.expect(t, out == nil, "bytes returned alongside a predictor failure") } // The pass-through errors hand the input back untouched, which is what lets // stream_image_data give a JPX codestream to the image layer. @(test) test_apply_filter_pass_through_errors :: proc(t: ^testing.T) { Case :: struct { name: string, err: Error, } cases := [?]Case{ {"DCTDecode", .Unsupported_Image}, {"DCT", .Unsupported_Image}, {"JBIG2Decode", .Unsupported_Image}, {"JPXDecode", .Unsupported_Jpx}, {"NoSuchThing", .Unknown_Filter}, {"", .Unknown_Filter}, } data := [?]byte{1, 2, 3, 4} for c in cases { out, err := apply_filter(nil, c.name, data[:], nil) testing.expectf(t, err == c.err, "%s: got %s", c.name, error_string(err)) testing.expectf(t, raw_data(out) == raw_data(data[:]), "%s: did not hand back the input", c.name) } } // /Crypt is a marker: the bytes were decrypted at parse time. @(test) test_apply_filter_crypt_is_identity :: proc(t: ^testing.T) { data := [?]byte{9, 8, 7} out, err := apply_filter(nil, "Crypt", data[:], nil) defer delete(out) testing.expect_value(t, err, Error.None) testing.expectf(t, slice.equal(out, data[:]), "got %v", out) testing.expect(t, raw_data(out) != raw_data(data[:]), "Crypt aliased its input") } @(test) test_apply_filter_ccitt_decodes_rows :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) // Group 4, one row of 8 columns, all white: horizontal mode (001), a white // run of 8 (10011) and a black run of 0 (0000110111), zero padded. encoded := [?]byte{0b0011_0011, 0b0000_1101, 0b1100_0000} // With /BlackIs1 false -- the default -- a white pixel is a 1 bit. parms := ft_parms(doc, {"K", "Columns", "Rows"}, {-1, 8, 1}) out, err := apply_filter(doc, "CCF", encoded[:], parms) defer delete(out) testing.expectf(t, err == .None, "%s", error_string(err)) testing.expectf(t, len(out) == 1 && out[0] == 0xff, "got %x", out) // /BlackIs1 exchanges them. inverted := ft_parms(doc, {"K", "Columns", "Rows"}, {-1, 8, 1}) dict_set(inverted, "BlackIs1", true) out2, err2 := apply_filter(doc, "CCITTFaxDecode", encoded[:], inverted) defer delete(out2) testing.expectf(t, err2 == .None, "%s", error_string(err2)) testing.expectf(t, len(out2) == 1 && out2[0] == 0x00, "got %x", out2) } // --------------------------------------------------------------------------- // stream_data: the chain // --------------------------------------------------------------------------- @(test) test_stream_data_two_filter_chain :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) s := ft_stream(doc, v_chain_a85[:], ft_names(doc, {"ASCII85Decode", "FlateDecode"}), nil) got, err := stream_data(doc, s) testing.expect_value(t, err, Error.None) testing.expect_value(t, string(got), V_CHAIN_PLAIN) testing.expect_value(t, s.state, Stream_Decode_State.Decoded) testing.expect_value(t, len(doc.repairs), 0) } @(test) test_stream_data_abbreviated_names :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) s := ft_stream(doc, v_chain_a85[:], ft_names(doc, {"A85", "Fl"}), nil) got, err := stream_data(doc, s) testing.expect_value(t, err, Error.None) testing.expect_value(t, string(got), V_CHAIN_PLAIN) } // Inline images abbreviate the keys as well as the filter names // (ISO 32000-2, table 91). @(test) test_stream_data_inline_image_keys :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) parms := ft_parms(doc, {"Predictor", "Columns"}, {12, 4}) s := ft_stream(doc, v_pred_flate[:], Name("Fl"), parms, "F", "DP") got, err := stream_data(doc, s) testing.expect_value(t, err, Error.None) testing.expectf(t, slice.equal(got, v_pred_payload[:]), "got %v", got) } // A single name is as legal as a one-element array. @(test) test_stream_data_single_name_filter :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) s := ft_stream(doc, v_chain_flate[:], Name("FlateDecode"), nil) got, err := stream_data(doc, s) testing.expect_value(t, err, Error.None) testing.expect_value(t, string(got), V_CHAIN_PLAIN) } // /DecodeParms is positional, and a null stands for "this filter has none". @(test) test_stream_data_decode_parms_array_with_a_null :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) parms := new_array(doc.allocator) array_push(parms, nil) array_push(parms, ft_parms(doc, {"Predictor", "Columns"}, {12, 4})) // ASCII85 armour over the flate stream that needs the predictor. armoured := [?]byte{ 0x47, 0x68, 0x51, 0x59, 0x3a, 0x41, 0x37, 0x2f, 0x65, 0x51, 0x21, 0x62, 0x2b, 0x56, 0x51, 0x21, 0x3c, 0x3c, 0x2b, 0x2d, 0x21, 0x22, 0x26, 0x7e, 0x3e, } s := ft_stream(doc, armoured[:], ft_names(doc, {"A85", "Fl"}), parms) got, err := stream_data(doc, s) testing.expect_value(t, err, Error.None) testing.expectf(t, slice.equal(got, v_pred_payload[:]), "got %v, want %v", got, v_pred_payload) testing.expect_value(t, len(doc.repairs), 0) } // A short /DecodeParms array is padded with nulls rather than treated as a // disagreement worth failing over. @(test) test_stream_data_short_decode_parms_array :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) parms := new_array(doc.allocator) array_push(parms, nil) s := ft_stream(doc, v_chain_a85[:], ft_names(doc, {"A85", "Fl"}), parms) got, err := stream_data(doc, s) testing.expect_value(t, err, Error.None) testing.expect_value(t, string(got), V_CHAIN_PLAIN) } // A bare dictionary against a chain is malformed, but it can only have been // meant for the filter that reads parameters -- here the flate, not the // ASCII85 armour in front of it. @(test) test_stream_data_single_parms_dict_for_a_chain :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) parms := ft_parms(doc, {"Predictor", "Columns"}, {12, 4}) armoured := [?]byte{ 0x47, 0x68, 0x51, 0x59, 0x3a, 0x41, 0x37, 0x2f, 0x65, 0x51, 0x21, 0x62, 0x2b, 0x56, 0x51, 0x21, 0x3c, 0x3c, 0x2b, 0x2d, 0x21, 0x22, 0x26, 0x7e, 0x3e, } s := ft_stream(doc, armoured[:], ft_names(doc, {"A85", "Fl"}), parms) got, err := stream_data(doc, s) testing.expect_value(t, err, Error.None) testing.expectf(t, slice.equal(got, v_pred_payload[:]), "got %v, want %v", got, v_pred_payload) } // A stray null inside /Filter loses that entry, not the stream. @(test) test_stream_data_null_inside_the_filter_array :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) filters := new_array(doc.allocator) array_push(filters, nil) array_push(filters, Name("FlateDecode")) s := ft_stream(doc, v_chain_flate[:], filters, nil) got, err := stream_data(doc, s) testing.expect_value(t, err, Error.None) testing.expect_value(t, string(got), V_CHAIN_PLAIN) } // --------------------------------------------------------------------------- // stream_data: degrading // --------------------------------------------------------------------------- @(test) test_stream_data_unknown_filter_degrades :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) s := ft_stream(doc, v_chain_flate[:], Name("SuperZipDecode"), nil) got, err := stream_data(doc, s) testing.expect_value(t, err, Error.None) testing.expect(t, got == nil, "an unknown filter produced bytes") testing.expect_value(t, s.state, Stream_Decode_State.Failed) testing.expect_value(t, ft_repairs(doc, .Bad_Filter_Data), 1) testing.expect(t, doc.is_damaged, "an unknown filter did not mark the document damaged") // And the failure is cached too: asking again is free and silent. again, err2 := stream_data(doc, s) testing.expect_value(t, err2, Error.None) testing.expect(t, again == nil, "second call produced bytes") testing.expect_value(t, ft_repairs(doc, .Bad_Filter_Data), 1) } // An unknown filter anywhere in the chain empties the stream: the bytes that // did decode are still encoded as far as the next stage is concerned, and // handing a content parser half-encoded data is worse than handing it nothing. @(test) test_stream_data_unknown_filter_late_in_the_chain :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) s := ft_stream(doc, v_chain_a85[:], ft_names(doc, {"A85", "SuperZipDecode"}), nil) got, err := stream_data(doc, s) testing.expect_value(t, err, Error.None) testing.expect(t, got == nil, "produced bytes past an unknown filter") testing.expect_value(t, ft_repairs(doc, .Bad_Filter_Data), 1) } // JPX and JBIG2 belong to the image layer. Reaching them through stream_data // is a caller mistake, not a document defect worth an error. @(test) test_stream_data_image_filters_degrade :: proc(t: ^testing.T) { for name in ([?]string{"DCTDecode", "JPXDecode", "JBIG2Decode"}) { doc := filter_test_doc() defer filter_test_doc_free(doc) s := ft_stream(doc, v_chain_flate[:], Name(name), nil) got, err := stream_data(doc, s) testing.expectf(t, err == .None, "%s: %s", name, error_string(err)) testing.expectf(t, got == nil, "%s: produced bytes", name) testing.expectf(t, ft_repairs(doc, .Bad_Filter_Data) == 1, "%s: no repair recorded", name) } } @(test) test_stream_data_broken_flate_degrades :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) junk := [?]byte{0xff, 0xff, 0xff, 0xff} s := ft_stream(doc, junk[:], Name("FlateDecode"), nil) got, err := stream_data(doc, s) testing.expect_value(t, err, Error.None) testing.expect(t, got == nil, "produced bytes from junk") testing.expect_value(t, ft_repairs(doc, .Bad_Filter_Data), 1) } // A stream truncated part-way keeps what inflated. Half a content stream draws // half a page, which beats a blank one. @(test) test_stream_data_truncated_flate_keeps_what_it_got :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) s := ft_stream(doc, v_chain_flate[:len(v_chain_flate) - 6], Name("FlateDecode"), nil) got, err := stream_data(doc, s) testing.expect_value(t, err, Error.None) testing.expect(t, len(got) > 0, "a truncated stream decoded to nothing") plain := V_CHAIN_PLAIN testing.expectf(t, len(got) <= len(plain) && plain[:len(got)] == string(got), "recovered prefix disagrees: %q", string(got)) } // A chain this long is not a document. @(test) test_stream_data_absurdly_long_chain :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) filters := new_array(doc.allocator) for _ in 0 ..< MAX_FILTER_CHAIN + 1 { array_push(filters, Name("FlateDecode")) } s := ft_stream(doc, v_chain_flate[:], filters, nil) got, err := stream_data(doc, s) testing.expect_value(t, err, Error.None) testing.expect(t, got == nil, "a 17-filter chain produced bytes") testing.expect_value(t, ft_repairs(doc, .Bad_Filter_Data), 1) } // --------------------------------------------------------------------------- // stream_data: caching and borrowing // --------------------------------------------------------------------------- @(test) test_stream_data_caches_the_decode :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) s := ft_stream(doc, v_chain_a85[:], ft_names(doc, {"A85", "Fl"}), nil) first, err1 := stream_data(doc, s) second, err2 := stream_data(doc, s) testing.expect_value(t, err1, Error.None) testing.expect_value(t, err2, Error.None) testing.expect_value(t, string(first), V_CHAIN_PLAIN) // Same pointer, not merely the same bytes: a second decode would land // somewhere else in the arena. testing.expect(t, raw_data(first) == raw_data(second), "the decode was not cached") testing.expect(t, raw_data(s.decoded) == raw_data(first), "the cache holds something else") } // A stream with no filters is its own decoded form; copying every content // stream into the arena would double the memory a document costs for nothing. @(test) test_stream_data_without_filters_borrows_the_raw_bytes :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) raw := [?]byte{'B', 'T', ' ', 'E', 'T'} s := ft_stream(doc, raw[:], nil, nil) got, err := stream_data(doc, s) testing.expect_value(t, err, Error.None) testing.expect(t, raw_data(got) == raw_data(raw[:]), "the raw bytes were copied") testing.expect_value(t, s.state, Stream_Decode_State.Decoded) } // An empty payload is empty whatever the chain claims. Running flate over it // would manufacture a repair for a stream that is legitimately empty. @(test) test_stream_data_empty_payload_is_not_a_repair :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) s := ft_stream(doc, nil, Name("FlateDecode"), nil) got, err := stream_data(doc, s) testing.expect_value(t, err, Error.None) testing.expect_value(t, len(got), 0) testing.expect_value(t, s.state, Stream_Decode_State.Decoded) testing.expect_value(t, len(doc.repairs), 0) } @(test) test_stream_data_nil_arguments :: proc(t: ^testing.T) { got, err := stream_data(nil, nil) testing.expect_value(t, err, Error.Invalid_Argument) testing.expect(t, got == nil, "bytes from a nil stream") // A nil Document is legal: the standalone object parser has none. The // decoded bytes then come from the context allocator and are the caller's. d := new_dict(context.allocator) dict_set(d, "Filter", Name("FlateDecode")) defer { delete(d^) free(d) } s := new_stream(d, v_chain_flate[:], context.allocator) defer free(s) got2, err2 := stream_data(nil, s) defer delete(got2) testing.expect_value(t, err2, Error.None) testing.expect_value(t, string(got2), V_CHAIN_PLAIN) testing.expect(t, stream_data_raw(nil, nil) == nil, "raw bytes from a nil stream") testing.expect(t, raw_data(stream_data_raw(nil, s)) == raw_data(v_chain_flate[:]), "stream_data_raw did not hand back the stored bytes") } // --------------------------------------------------------------------------- // stream_image_data // --------------------------------------------------------------------------- @(test) test_stream_image_data_stops_at_the_image_filter :: proc(t: ^testing.T) { names := [?]string{"DCTDecode", "DCT", "JPXDecode", "JBIG2Decode", "CCITTFaxDecode", "CCF"} for name in names { doc := filter_test_doc() defer filter_test_doc_free(doc) payload := [?]byte{0xff, 0xd8, 0xff, 0xe0} s := ft_stream(doc, payload[:], Name(name), nil) data, filter, parms, err := stream_image_data(doc, s) defer delete(data) testing.expectf(t, err == .None, "%s: %s", name, error_string(err)) testing.expectf(t, slice.equal(data, payload[:]), "%s: payload changed", name) testing.expectf(t, filter == filter_name(filter_kind(name), name), "%s: reported %q", name, filter) testing.expectf(t, parms == nil, "%s: invented parameters", name) // Caller-owned, so never the stream's own bytes. testing.expectf(t, raw_data(data) != raw_data(payload[:]), "%s: aliased the raw bytes", name) } } // The armour comes off; the image codec's own bytes are handed over intact. @(test) test_stream_image_data_applies_the_byte_filters_first :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) // ASCII85 for the four bytes ff d8 ff e0. armoured := [?]byte{'s', '4', 'I', 'A', '0', '~', '>'} s := ft_stream(doc, armoured[:], ft_names(doc, {"A85", "DCTDecode"}), nil) data, filter, _, err := stream_image_data(doc, s) defer delete(data) testing.expect_value(t, err, Error.None) testing.expect_value(t, filter, "DCTDecode") want := [?]byte{0xff, 0xd8, 0xff, 0xe0} testing.expectf(t, slice.equal(data, want[:]), "got %x, want %x", data, want) } // The parameters that go with the image filter are handed over, since the // image layer is the one that has to act on them. @(test) test_stream_image_data_returns_the_image_parameters :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) parms := ft_parms(doc, {"K", "Columns", "Rows"}, {-1, 16, 4}) payload := [?]byte{0x00, 0x01} s := ft_stream(doc, payload[:], Name("CCF"), parms) data, filter, got_parms, err := stream_image_data(doc, s) defer delete(data) testing.expect_value(t, err, Error.None) testing.expect_value(t, filter, "CCITTFaxDecode") testing.expect(t, got_parms == parms, "the parameter dictionary did not come back") p := filter_ccitt_params(doc, got_parms) testing.expect_value(t, p.k, -1) testing.expect_value(t, p.columns, 16) testing.expect_value(t, p.rows, 4) testing.expect_value(t, p.end_of_block, true) } // No image filter at all: the samples are already bytes. @(test) test_stream_image_data_without_an_image_filter :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) s := ft_stream(doc, v_chain_flate[:], Name("FlateDecode"), nil) data, filter, parms, err := stream_image_data(doc, s) defer delete(data) testing.expect_value(t, err, Error.None) testing.expect_value(t, filter, "") testing.expect(t, parms == nil, "invented parameters") testing.expect_value(t, string(data), V_CHAIN_PLAIN) } // An unfiltered image: still caller-owned memory, so the caller can free it // without knowing how it got there. @(test) test_stream_image_data_unfiltered :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) payload := [?]byte{1, 2, 3, 4, 5} s := ft_stream(doc, payload[:], nil, nil) data, filter, _, err := stream_image_data(doc, s) defer delete(data) testing.expect_value(t, err, Error.None) testing.expect_value(t, filter, "") testing.expectf(t, slice.equal(data, payload[:]), "got %v", data) testing.expect(t, raw_data(data) != raw_data(payload[:]), "aliased the raw bytes") } @(test) test_stream_image_data_broken_chain :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) s := ft_stream(doc, v_chain_flate[:], ft_names(doc, {"SuperZipDecode", "DCTDecode"}), nil) data, filter, _, err := stream_image_data(doc, s) defer delete(data) testing.expect_value(t, err, Error.Unknown_Filter) testing.expect(t, data == nil, "bytes past an unknown filter") testing.expect_value(t, filter, "") testing.expect_value(t, ft_repairs(doc, .Bad_Filter_Data), 1) } @(test) test_stream_image_data_nil_stream :: proc(t: ^testing.T) { data, filter, parms, err := stream_image_data(nil, nil) testing.expect_value(t, err, Error.Invalid_Argument) testing.expect(t, data == nil && parms == nil && filter == "", "something came back") } // --------------------------------------------------------------------------- // Malformed input // --------------------------------------------------------------------------- // Every filter, over payloads of every shape, with parameters that make no // sense: nothing here may panic, hang or leak. @(test) test_filters_never_crash_on_nonsense :: proc(t: ^testing.T) { names := [?]string{ "FlateDecode", "LZWDecode", "ASCII85Decode", "ASCIIHexDecode", "RunLengthDecode", "CCITTFaxDecode", "DCTDecode", "JPXDecode", "JBIG2Decode", "Crypt", "Nonsense", "", } payloads := [?][]byte{ nil, {0x00}, {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, {'~', '>'}, {'>', '>', '>'}, {0x78, 0xda}, v_chain_flate[:], v_chain_a85[:], v_lzw_tiff[:], } parm_sets := [?][2]i64{{1, 1}, {2, 0}, {12, 4}, {15, 1 << 40}, {-1, -1}, {99, 7}} doc := filter_test_doc() defer filter_test_doc_free(doc) for name in names { for payload in payloads { for set in parm_sets { parms := ft_parms(doc, {"Predictor", "Columns", "Colors", "BitsPerComponent", "K", "Rows"}, {set[0], set[1], set[0], set[1], set[0], set[1]}) s := ft_stream(doc, payload, Name(name), parms) data, derr := stream_data(doc, s) testing.expectf(t, derr == .None || derr == .Invalid_Argument, "%s: stream_data returned %s", name, error_string(derr)) _ = data s2 := ft_stream(doc, payload, Name(name), parms) img, _, _, _ := stream_image_data(doc, s2) delete(img) } } } } // A /Filter that is not a name or an array of names is not a filter. @(test) test_stream_data_nonsense_filter_entry :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) raw := [?]byte{'x', 'y', 'z'} entries := [?]Object{i64(42), f64(1.5), true, String(transmute([]byte)string("Fl"))} for entry in entries { s := ft_stream(doc, raw[:], entry, nil) got, err := stream_data(doc, s) testing.expectf(t, err == .None, "%v: %s", entry, error_string(err)) testing.expectf(t, slice.equal(got, raw[:]), "%v: data changed", entry) } } // --------------------------------------------------------------------------- // Allocator discipline // // Two rules, both invisible from the outside and both easy to break again. // A filter chain must free its intermediates -- with no Document there is // nobody else to. And a codec's speculation must not reach the Document arena, // where `delete` is a silent no-op and nothing is reclaimed before `close`. // The tests below are the only thing standing between either rule and a // regression, so they measure rather than inspect: the tracking allocator the // test runner installs catches the first, `document_memory_used` the second. // --------------------------------------------------------------------------- // "Hi" run through ASCIIHexDecode twice and three times over. Decoding applies // the chain in the order /Filter lists it (ISO 32000-2, 7.4.1), so each pass // peels one layer of hex off and every pass but the last leaves a buffer // behind that only `stream_data` can free. @(private = "file") V_HEX_TWICE :: "34383639" @(private = "file") V_HEX_THRICE :: "3334333833363339" @(test) test_stream_data_nil_document_frees_the_intermediates :: proc(t: ^testing.T) { cases := [?]string{V_HEX_TWICE, V_HEX_THRICE} for raw, i in cases { depth := i + 2 // A nil Document means no arena, so the chain runs on the context // allocator and every buffer it produces is somebody's to free. d := new_dict(context.allocator) a := new_array(context.allocator) for _ in 0 ..< depth { array_push(a, Name("ASCIIHexDecode")) } dict_set(d, "Filter", a) s := new_stream(d, transmute([]byte)raw, context.allocator) got, err := stream_data(nil, s) testing.expectf(t, err == .None, "%d filters: %s", depth, error_string(err)) testing.expectf(t, string(got) == "Hi", "%d filters: got %q", depth, string(got)) testing.expectf(t, s.state == .Decoded, "%d filters: state %v", depth, s.state) // Only the last stage is reachable here; if the earlier ones were not // freed as the chain advanced, nothing can free them now and the // tracking allocator reports the leak. delete(got) delete(a.items) free(a) delete(d^) free(d) free(s) } } // The same chain over a real Document: the intermediates must not merely be // freed, they must never have been taken from the arena in the first place, // because an arena cannot give them back. @(test) test_stream_data_leaves_only_the_last_stage_in_the_arena :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) // /ASCII85Decode over /FlateDecode: the armour comes off into a buffer // that is 28 bytes of deflate, which inflates into the 20 bytes that are // kept. Only the 20 may be charged to the arena. s := ft_stream(doc, v_chain_a85[:], ft_names(doc, {"ASCII85Decode", "FlateDecode"}), nil) before := document_memory_used(doc) got, err := stream_data(doc, s) testing.expect_value(t, err, Error.None) testing.expect_value(t, string(got), V_CHAIN_PLAIN) growth := document_memory_used(doc) - before testing.expectf(t, growth <= len(got) + FT_ARENA_SLACK, "the arena grew by %d bytes for a %d byte stream", growth, len(got)) // Decoding twice must not decode twice, let alone allocate twice. settled := document_memory_used(doc) again, err2 := stream_data(doc, s) testing.expect_value(t, err2, Error.None) testing.expect(t, raw_data(again) == raw_data(got), "the cache was not used") testing.expect_value(t, document_memory_used(doc), settled) } // An arena rounds each allocation up to its alignment, so an exact byte count // would be a test of core:mem/virtual rather than of this file. The point is // that growth tracks the output and not the number of buffers it took to // produce it, and one alignment quantum of slack does not blunt that. @(private) FT_ARENA_SLACK :: 64 // apply_filter is where a codec's guesswork stops. Every rejected attempt and // every superseded growth buffer belongs to the scratch allocator; exactly one // buffer, at its final length, is charged to the caller's. @(test) test_apply_filter_charges_the_arena_for_one_buffer :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) before := document_memory_used(doc) out, err := apply_filter(doc, "FlateDecode", v_chain_flate[:], nil, doc.allocator) testing.expect_value(t, err, Error.None) testing.expect_value(t, string(out), V_CHAIN_PLAIN) growth := document_memory_used(doc) - before testing.expectf(t, growth <= len(out) + FT_ARENA_SLACK, "the arena grew by %d bytes for %d bytes of output", growth, len(out)) } // A filter that fails hands back nil or the input untouched, and either way // must not have spent a byte of the arena on the attempt. @(test) test_apply_filter_failure_costs_the_arena_nothing :: proc(t: ^testing.T) { broken := [?]byte{0x78, 0x9c, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07} cases := [?]struct{name: string, data: []byte, want: Error}{ {"FlateDecode", broken[:], .Bad_Flate_Data}, {"DCTDecode", broken[:], .Unsupported_Image}, {"JPXDecode", broken[:], .Unsupported_Jpx}, {"NoSuchThing", broken[:], .Unknown_Filter}, } for c in cases { doc := filter_test_doc() defer filter_test_doc_free(doc) before := document_memory_used(doc) out, err := apply_filter(doc, c.name, c.data, nil, doc.allocator) testing.expectf(t, err == c.want, "%s: %s", c.name, error_string(err)) testing.expectf(t, document_memory_used(doc) == before, "%s: %d bytes stranded in the arena by a filter that returned an error", c.name, document_memory_used(doc) - before) // The pass-through errors hand back the caller's own bytes, which // means the result is never something to free. if err != .Bad_Flate_Data { testing.expectf(t, raw_data(out) == raw_data(c.data), "%s: the input was copied on an error path", c.name) } } } // The chain runs on scratch even when it is one filter long, so a filter that // fails half way through a chain leaves nothing behind either. @(test) test_stream_data_failed_chain_costs_the_arena_nothing :: proc(t: ^testing.T) { doc := filter_test_doc() defer filter_test_doc_free(doc) // A85 decodes to eight bytes that JPXDecode will refuse, and refusing is // what ends the chain. s := ft_stream(doc, v_chain_a85[:], ft_names(doc, {"ASCII85Decode", "JPXDecode"}), nil) before := document_memory_used(doc) got, err := stream_data(doc, s) testing.expect_value(t, err, Error.None) testing.expect_value(t, len(got), 0) testing.expect_value(t, s.state, Stream_Decode_State.Failed) testing.expectf(t, ft_repairs(doc, .Bad_Filter_Data) == 1, "no repair was recorded") growth := document_memory_used(doc) - before // The repair itself is charged to the arena, which is the only thing that // should be. testing.expectf(t, growth <= size_of(Repair) * 8 + FT_ARENA_SLACK, "the arena grew by %d bytes for a chain that decoded to nothing", growth) }