Coverage Report

Created: 2024-06-18 07:39

/src/image-png/fuzz/fuzz_targets/roundtrip.rs
Line
Count
Source (jump to first uncovered line)
1
#![no_main]
2
3
use png::{FilterType, ColorType, BitDepth};
4
#[macro_use] extern crate libfuzzer_sys;
5
extern crate png;
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
0
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
0
    // Convert untyped bytes to the correct types and validate them:
18
0
    let width = width as u32;
19
0
    if width == 0 { return None };
20
0
    let filter = FilterType::from_u8(filter)?;
21
0
    let bit_depth = BitDepth::from_u8(raw_bit_depth)?;
22
0
    let max_palette_length = 3 * u32::pow(2, raw_bit_depth as u32) as usize;
23
0
    let mut palette = raw_palette;
24
0
    let color_type = ColorType::from_u8(color_type)?;
25
0
    if let ColorType::Indexed = color_type {
26
        // when palette is needed, ensure that palette.len() <= 2 ^ bit_depth
27
0
        if raw_palette.len() > max_palette_length {
28
0
            palette = &raw_palette[..max_palette_length];
29
0
        }
30
0
    }
31
    // compression
32
0
    let compression = match compression {
33
0
        0 => png::Compression::Default,
34
0
        1 => png::Compression::Fast,
35
0
        2 => png::Compression::Best,
36
0
        3 => png::Compression::Huffman,
37
0
        4 => png::Compression::Rle,
38
0
        _ => return None,
39
    };
40
41
    // infer the rest of the parameters
42
    // raw_row_length_from_width() will add +1 to the row length in bytes
43
    // to account for the first bit in the row indicating the filter, so subtract 1
44
0
    let bytes_per_row = raw_row_length_from_width(bit_depth, color_type, width) - 1;
45
0
    let height = data.len() / bytes_per_row;
46
0
    let total_bytes = bytes_per_row * height;
47
0
    let data_to_encode = &data[..total_bytes];
48
0
49
0
    // perform the PNG encoding
50
0
    let mut output: Vec<u8> = Vec::new();
51
0
    { // scoped so that we could return the Vec
52
0
        let mut encoder = png::Encoder::new(&mut output, width, height as u32);
53
0
        encoder.set_depth(bit_depth);
54
0
        encoder.set_color(color_type);
55
0
        encoder.set_filter(filter);
56
0
        encoder.set_compression(compression);
57
0
        if let ColorType::Indexed = color_type {
58
0
            encoder.set_palette(palette)
59
0
        }
60
        // write_header will return an error given invalid parameters,
61
        // such as height 0, or invalid color mode and bit depth combination
62
0
        let mut writer = encoder.write_header().ok()?;
63
0
        writer.write_image_data(data_to_encode).expect("Encoding failed");
64
0
    }
65
0
    Some((data_to_encode, output))
66
0
}
67
68
0
fn decode_png(data: &[u8]) -> (png::OutputInfo, Vec<u8>) {
69
0
    let decoder = png::Decoder::new(data);
70
0
    let  mut reader = decoder.read_info().unwrap();
71
0
72
0
    let mut img_data = vec![0u8; reader.info().raw_bytes()];
73
0
74
0
    let info = reader.next_frame(&mut img_data).unwrap();
75
0
76
0
    (info, img_data)
77
0
}
78
79
// copied from the `png` codebase because it's pub(crate)
80
0
fn raw_row_length_from_width(depth: BitDepth, color: ColorType, width: u32) -> usize {
81
0
    let samples = width as usize * color.samples();
82
0
    1 + match depth {
83
0
        BitDepth::Sixteen => samples * 2,
84
0
        BitDepth::Eight => samples,
85
0
        subbyte => {
86
0
            let samples_per_byte = 8 / subbyte as usize;
87
0
            let whole = samples / samples_per_byte;
88
0
            let fract = usize::from(samples % samples_per_byte > 0);
89
0
            whole + fract
90
        }
91
    }
92
0
}