Coverage Report

Created: 2025-10-13 07:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}