/src/image-png/fuzz/fuzz_targets/roundtrip.rs
Line | Count | Source |
1 | | #![no_main] |
2 | | |
3 | | use libfuzzer_sys::fuzz_target; |
4 | | use std::io::Cursor; |
5 | | use png::{Filter, ColorType, BitDepth}; |
6 | | |
7 | | fuzz_target!(|data: (u8, u8, u8, u8, u8, Vec<u8>, Vec<u8>)| { |
8 | | if let Some((raw, encoded)) = encode_png(data.0, data.1, data.2, data.3, data.4, &data.5, &data.6) { |
9 | | let (_info, raw_decoded) = decode_png(&encoded); |
10 | | // raw_decoded can be padded with zeroes at the end, not sure if that's correct |
11 | | let raw_decoded = &raw_decoded[..raw.len()]; |
12 | | assert_eq!(raw, raw_decoded); |
13 | | } |
14 | | }); |
15 | | |
16 | 4.72k | fn encode_png<'a>(width: u8, filter: u8, compression: u8, color_type: u8, raw_bit_depth: u8, raw_palette: &'a [u8], data: &'a [u8]) -> Option<(&'a [u8], Vec<u8>)> { |
17 | | // Convert untyped bytes to the correct types and validate them: |
18 | 4.72k | let width = width as u32; |
19 | 4.72k | if width == 0 { return None }; |
20 | 4.71k | let filter = filter_from_u8(filter); |
21 | 4.71k | let bit_depth = BitDepth::from_u8(raw_bit_depth)?; |
22 | 4.68k | let max_palette_length = 3 * u32::pow(2, raw_bit_depth as u32) as usize; |
23 | 4.68k | let mut palette = raw_palette; |
24 | 4.68k | let color_type = ColorType::from_u8(color_type)?; |
25 | 4.68k | if let ColorType::Indexed = color_type { |
26 | | // when palette is needed, ensure that palette.len() <= 2 ^ bit_depth |
27 | 1.64k | if raw_palette.len() > max_palette_length { |
28 | 1.00k | palette = &raw_palette[..max_palette_length]; |
29 | 1.00k | } |
30 | 3.04k | } |
31 | | // compression |
32 | 4.68k | let compression = match compression { |
33 | 147 | 0 => png::DeflateCompression::NoCompression, |
34 | 3.64k | level @ 1..=9 => png::DeflateCompression::Level(level), |
35 | 893 | 10 => png::DeflateCompression::FdeflateUltraFast, |
36 | 5 | _ => return None, |
37 | | }; |
38 | | |
39 | | // infer the rest of the parameters |
40 | | // raw_row_length_from_width() will add +1 to the row length in bytes |
41 | | // to account for the first bit in the row indicating the filter, so subtract 1 |
42 | 4.67k | let bytes_per_row = raw_row_length_from_width(bit_depth, color_type, width) - 1; |
43 | 4.67k | let height = data.len() / bytes_per_row; |
44 | 4.67k | let total_bytes = bytes_per_row * height; |
45 | 4.67k | let data_to_encode = &data[..total_bytes]; |
46 | | |
47 | | // perform the PNG encoding |
48 | 4.67k | let mut output: Vec<u8> = Vec::new(); |
49 | | { // scoped so that we could return the Vec |
50 | 4.67k | let mut encoder = png::Encoder::new(&mut output, width, height as u32); |
51 | 4.67k | encoder.set_depth(bit_depth); |
52 | 4.67k | encoder.set_color(color_type); |
53 | 4.67k | encoder.set_filter(filter); |
54 | 4.67k | encoder.set_deflate_compression(compression); |
55 | 4.67k | if let ColorType::Indexed = color_type { |
56 | 1.64k | encoder.set_palette(palette) |
57 | 3.03k | } |
58 | | // write_header will return an error given invalid parameters, |
59 | | // such as height 0, or invalid color mode and bit depth combination |
60 | 4.67k | let mut writer = encoder.write_header().ok()?; |
61 | 4.66k | writer.write_image_data(data_to_encode).expect("Encoding failed"); |
62 | | } |
63 | 4.66k | Some((data_to_encode, output)) |
64 | 4.72k | } |
65 | | |
66 | 4.66k | fn decode_png(data: &[u8]) -> (png::OutputInfo, Vec<u8>) { |
67 | 4.66k | let decoder = png::Decoder::new(Cursor::new(data)); |
68 | 4.66k | let mut reader = decoder.read_info().unwrap(); |
69 | | |
70 | 4.66k | let mut img_data = vec![0u8; reader.info().raw_bytes()]; |
71 | | |
72 | 4.66k | let info = reader.next_frame(&mut img_data).unwrap(); |
73 | | |
74 | 4.66k | (info, img_data) |
75 | 4.66k | } |
76 | | |
77 | | /// Filter::from() doesn't cover the Filter::Adaptive variant, so we roll our own |
78 | 4.71k | fn filter_from_u8(input: u8) -> Filter { |
79 | 4.71k | match input { |
80 | 780 | 0 => Filter::NoFilter, |
81 | 628 | 1 => Filter::Sub, |
82 | 210 | 2 => Filter::Up, |
83 | 906 | 3 => Filter::Avg, |
84 | 598 | 4 => Filter::Paeth, |
85 | 1.59k | _ => Filter::Adaptive, |
86 | | } |
87 | 4.71k | } |
88 | | |
89 | | // copied from the `png` codebase because it's pub(crate) |
90 | 4.67k | fn raw_row_length_from_width(depth: BitDepth, color: ColorType, width: u32) -> usize { |
91 | 4.67k | let samples = width as usize * color.samples(); |
92 | 4.67k | 1 + match depth { |
93 | 627 | BitDepth::Sixteen => samples * 2, |
94 | 1.18k | BitDepth::Eight => samples, |
95 | 2.86k | subbyte => { |
96 | 2.86k | let samples_per_byte = 8 / subbyte as usize; |
97 | 2.86k | let whole = samples / samples_per_byte; |
98 | 2.86k | let fract = usize::from(samples % samples_per_byte > 0); |
99 | 2.86k | whole + fract |
100 | | } |
101 | | } |
102 | 4.67k | } |