Coverage Report

Created: 2026-08-31 07:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/fax-0.2.7/src/decoder.rs
Line
Count
Source
1
use std::convert::Infallible;
2
use std::io::{self, Bytes, Read};
3
4
use crate::maps::{black, mode, white, Mode, EDFB_HALF, EOL};
5
use crate::{BitReader, ByteReader, Color, Transitions};
6
7
0
fn with_markup<D, R>(decoder: D, reader: &mut R) -> Option<u16>
8
0
where
9
0
    D: Fn(&mut R) -> Option<u16>,
10
{
11
0
    let mut sum: u16 = 0;
12
0
    while let Some(n) = decoder(reader) {
13
        //print!("{} ", n);
14
0
        sum = sum.checked_add(n)?;
15
0
        if n < 64 {
16
            //debug!("= {}", sum);
17
0
            return Some(sum);
18
0
        }
19
    }
20
0
    None
21
0
}
Unexecuted instantiation: fax::decoder::with_markup::<fax::maps::black::decode<fax::ByteReader<std::io::Bytes<std::io::buffered::bufreader::BufReader<std::io::Take<&mut std::io::cursor::Cursor<&[u8]>>>>>>, fax::ByteReader<std::io::Bytes<std::io::buffered::bufreader::BufReader<std::io::Take<&mut std::io::cursor::Cursor<&[u8]>>>>>>
Unexecuted instantiation: fax::decoder::with_markup::<fax::maps::white::decode<fax::ByteReader<std::io::Bytes<std::io::buffered::bufreader::BufReader<std::io::Take<&mut std::io::cursor::Cursor<&[u8]>>>>>>, fax::ByteReader<std::io::Bytes<std::io::buffered::bufreader::BufReader<std::io::Take<&mut std::io::cursor::Cursor<&[u8]>>>>>>
Unexecuted instantiation: fax::decoder::with_markup::<_, _>
22
23
0
fn colored(current: Color, reader: &mut impl BitReader) -> Option<u16> {
24
    //debug!("{:?}", current);
25
0
    match current {
26
0
        Color::Black => with_markup(black::decode, reader),
27
0
        Color::White => with_markup(white::decode, reader),
28
    }
29
0
}
Unexecuted instantiation: fax::decoder::colored::<fax::ByteReader<std::io::Bytes<std::io::buffered::bufreader::BufReader<std::io::Take<&mut std::io::cursor::Cursor<&[u8]>>>>>>
Unexecuted instantiation: fax::decoder::colored::<_>
30
31
/// Turn a list of color changing position into an iterator of pixel colors
32
///
33
/// The width of the line/image has to be given in `width`.
34
/// The iterator will produce exactly that many items.
35
0
pub fn pels(line: &[u16], width: u16) -> impl Iterator<Item = Color> + '_ {
36
    use std::iter::repeat;
37
0
    let mut color = Color::White;
38
0
    let mut last = 0;
39
0
    let pad_color = if line.len() & 1 == 1 { !color } else { color };
40
0
    line.iter()
41
0
        .flat_map(move |&p| {
42
0
            let c = color;
43
0
            color = !color;
44
0
            let n = p.saturating_sub(last);
45
0
            last = p;
46
0
            repeat(c).take(n as usize)
47
0
        })
Unexecuted instantiation: fax::decoder::pels::{closure#0}
Unexecuted instantiation: fax::decoder::pels::{closure#0}
48
0
        .chain(repeat(pad_color))
49
0
        .take(width as usize)
50
0
}
51
52
/// Decode a Group 3 encoded image.
53
///
54
/// The callback `line_cb` is called for each decoded line.
55
/// The argument is the list of positions of color change, starting with white.
56
///
57
/// To obtain an iterator over the pixel colors, the `pels` function is provided.
58
0
pub fn decode_g3(input: impl Iterator<Item = u8>, mut line_cb: impl FnMut(&[u16])) -> Option<()> {
59
0
    let reader = input.map(Result::<u8, Infallible>::Ok);
60
0
    let mut decoder = Group3Decoder::new(reader).ok()?;
61
62
0
    while let Ok(status) = decoder.advance() {
63
        // Always emit the decoded line before checking for end-of-document.
64
        // The last line before the RTC (Return To Control) marker contains
65
        // valid data that should not be dropped.
66
0
        line_cb(decoder.transitions());
67
0
        if status == DecodeStatus::End {
68
0
            return Some(());
69
0
        }
70
    }
71
0
    None
72
0
}
73
74
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
75
pub enum DecodeStatus {
76
    Incomplete,
77
    End,
78
}
79
80
pub struct Group3Decoder<R> {
81
    reader: ByteReader<R>,
82
    current: Vec<u16>,
83
}
84
impl<E: std::fmt::Debug, R: Iterator<Item = Result<u8, E>>> Group3Decoder<R> {
85
0
    pub fn new(reader: R) -> Result<Self, DecodeError<E>> {
86
0
        let mut reader = ByteReader::new(reader).map_err(DecodeError::Reader)?;
87
        // Skip any fill bits (zeros) then consume the initial EOL marker.
88
0
        skip_to_eol(&mut reader).map_err(|_| DecodeError::Invalid)?;
89
90
0
        Ok(Group3Decoder {
91
0
            reader,
92
0
            current: vec![],
93
0
        })
94
0
    }
95
0
    pub fn advance(&mut self) -> Result<DecodeStatus, DecodeError<E>> {
96
0
        self.current.clear();
97
0
        let mut a0: u16 = 0;
98
0
        let mut color = Color::White;
99
        loop {
100
            // Check for EOL before attempting to parse a run-length code.
101
            // This prevents the prefix tree from destructively consuming
102
            // EOL bits that it can't match as a valid code.
103
0
            if is_eol_ahead(&self.reader) {
104
0
                break;
105
0
            }
106
0
            match colored(color, &mut self.reader) {
107
0
                Some(p) => {
108
0
                    a0 = a0.checked_add(p).ok_or(DecodeError::Invalid)?;
109
0
                    self.current.push(a0);
110
0
                    color = !color;
111
                }
112
0
                None => break,
113
            }
114
        }
115
        // Skip any fill bits and consume the EOL.
116
0
        skip_to_eol(&mut self.reader).map_err(|_| DecodeError::Invalid)?;
117
118
        // Check for end-of-document: 6 consecutive EOLs (5 more after the one above).
119
0
        for _ in 0..5 {
120
0
            if is_eol_ahead(&self.reader) {
121
0
                skip_to_eol(&mut self.reader).map_err(|_| DecodeError::Invalid)?;
122
            } else {
123
0
                return Ok(DecodeStatus::Incomplete);
124
            }
125
        }
126
127
0
        Ok(DecodeStatus::End)
128
0
    }
129
0
    pub fn transitions(&self) -> &[u16] {
130
0
        &self.current
131
0
    }
132
}
133
134
/// Check if the next bits form an EOL marker (possibly with fill bits).
135
///
136
/// An EOL is `000000000001` (11 zeros + 1). Fill bits add extra leading
137
/// zeros for byte alignment (up to 7). No valid run-length code has more
138
/// than 7 leading zeros, so 8+ leading zeros guarantees fill + EOL.
139
///
140
/// We peek at 9 bits: if all zero, this is definitely fill+EOL or bare EOL
141
/// (the EOL itself starts with 11 zeros). This handles any fill count
142
/// without exceeding the 16-bit peek window.
143
0
fn is_eol_ahead<E, R: Iterator<Item = Result<u8, E>>>(reader: &ByteReader<R>) -> bool {
144
    // 9 zero bits cannot be the start of any valid run-length code
145
    // (max leading zeros in any code is 7). Must be fill + EOL.
146
    // This also matches bare EOL (000000000001) since its first 9 bits are zero.
147
0
    reader.peek(9) == Some(0)
148
0
}
149
150
/// Skip zero fill bits and consume the EOL marker (000000000001).
151
/// Returns Err if no valid EOL is found.
152
0
fn skip_to_eol<E: std::fmt::Debug, R: Iterator<Item = Result<u8, E>>>(
153
0
    reader: &mut ByteReader<R>,
154
0
) -> Result<(), DecodeError<E>> {
155
    // Skip zero fill bits (used for byte alignment in Group3Options bit 2).
156
0
    while reader.peek(1) == Some(0) {
157
0
        reader.consume(1).map_err(DecodeError::Reader)?;
158
    }
159
    // The next bit should be the '1' that terminates the EOL.
160
0
    if reader.peek(1) == Some(1) {
161
0
        reader.consume(1).map_err(DecodeError::Reader)?;
162
0
        Ok(())
163
    } else {
164
0
        Err(DecodeError::Invalid)
165
    }
166
0
}
167
168
/// Decode a Group 4 Image
169
///
170
/// - `width` is the width of the image.
171
/// - The callback `line_cb` is called for each decoded line.
172
///   The argument is the list of positions of color change, starting with white.
173
///
174
///   If `height` is specified, at most that many lines will be decoded,
175
///   otherwise data is decoded until the end-of-block marker (or end of data).
176
///
177
/// To obtain an iterator over the pixel colors, the `pels` function is provided.
178
0
pub fn decode_g4(
179
0
    input: impl Iterator<Item = u8>,
180
0
    width: u16,
181
0
    height: Option<u16>,
182
0
    mut line_cb: impl FnMut(&[u16]),
183
0
) -> Option<()> {
184
0
    let reader = input.map(Result::<u8, Infallible>::Ok);
185
0
    let mut decoder = Group4Decoder::new(reader, width).ok()?;
186
187
0
    let max_lines = height.unwrap_or(u16::MAX);
188
0
    let mut lines_emitted: u16 = 0;
189
190
0
    while lines_emitted < max_lines {
191
0
        let status = decoder.advance().ok()?;
192
0
        if status == DecodeStatus::End {
193
0
            break;
194
0
        }
195
0
        line_cb(decoder.transition());
196
0
        lines_emitted += 1;
197
    }
198
199
    // Some encoders omit trailing all-white lines before the EOFB,
200
    // expecting the receiver to pad to the known height.
201
    // Empty transitions = all-white line (pels handles this correctly).
202
0
    if let Some(h) = height {
203
0
        while lines_emitted < h {
204
0
            line_cb(&[]);
205
0
            lines_emitted += 1;
206
0
        }
207
0
    }
208
209
0
    Some(())
210
0
}
211
212
#[derive(Debug)]
213
pub enum DecodeError<E> {
214
    Reader(E),
215
    Invalid,
216
    Unsupported,
217
}
218
impl<E> std::fmt::Display for DecodeError<E> {
219
0
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
220
0
        write!(f, "Decode Error")
221
0
    }
Unexecuted instantiation: <fax::decoder::DecodeError<std::io::error::Error> as core::fmt::Display>::fmt
Unexecuted instantiation: <fax::decoder::DecodeError<_> as core::fmt::Display>::fmt
222
}
223
impl<E: std::error::Error> std::error::Error for DecodeError<E> {}
224
225
pub struct Group4Decoder<R> {
226
    reader: ByteReader<R>,
227
    reference: Vec<u16>,
228
    current: Vec<u16>,
229
    width: u16,
230
}
231
impl<E, R: Iterator<Item = Result<u8, E>>> Group4Decoder<R> {
232
0
    pub fn new(reader: R, width: u16) -> Result<Self, E> {
233
        Ok(Group4Decoder {
234
0
            reader: ByteReader::new(reader)?,
235
0
            reference: Vec::new(),
236
0
            current: Vec::new(),
237
0
            width,
238
        })
239
0
    }
Unexecuted instantiation: <fax::decoder::Group4Decoder<std::io::Bytes<std::io::buffered::bufreader::BufReader<std::io::Take<&mut std::io::cursor::Cursor<&[u8]>>>>>>::new
Unexecuted instantiation: <fax::decoder::Group4Decoder<_>>::new
240
    // when Complete::Complete is returned, there is no useful data in .transitions() or .line()
241
0
    pub fn advance(&mut self) -> Result<DecodeStatus, DecodeError<E>> {
242
0
        let mut transitions = Transitions::new(&self.reference);
243
0
        let mut a0 = 0;
244
0
        let mut color = Color::White;
245
0
        let mut start_of_row = true;
246
        //debug!("\n\nline {}", y);
247
248
        loop {
249
            //reader.print_peek();
250
0
            let mode = match mode::decode(&mut self.reader) {
251
0
                Some(mode) => mode,
252
0
                None => return Err(DecodeError::Invalid),
253
            };
254
            //debug!("  {:?}, color={:?}, a0={}", mode, color, a0);
255
256
0
            match mode {
257
                Mode::Pass => {
258
0
                    if start_of_row && color == Color::White {
259
0
                        transitions.pos += 1;
260
0
                    } else {
261
0
                        transitions
262
0
                            .next_color(a0, !color, false)
263
0
                            .ok_or(DecodeError::Invalid)?;
264
                    }
265
                    //debug!("b1={}", b1);
266
0
                    if let Some(b2) = transitions.next() {
267
0
                        //debug!("b2={}", b2);
268
0
                        a0 = b2;
269
0
                    }
270
                }
271
0
                Mode::Vertical(delta) => {
272
0
                    let b1 = transitions
273
0
                        .next_color(a0, !color, start_of_row)
274
0
                        .unwrap_or(self.width);
275
0
                    let a1_i32 = b1 as i32 + delta as i32;
276
0
                    if a1_i32 < 0 || a1_i32 > self.width as i32 {
277
0
                        break;
278
0
                    }
279
0
                    let a1 = a1_i32 as u16;
280
                    //debug!("transition to {:?} at {}", !color, a1);
281
                    // Canonical form: only store transitions strictly less
282
                    // than width. A transition at width is the implicit
283
                    // end-of-line and is not a color change. This matches
284
                    // the encoder's `self.current` representation (see
285
                    // encoder.rs — it only pushes values yielded by pels,
286
                    // which are always in [0, width-1]).
287
0
                    if a1 < self.width {
288
0
                        self.current.push(a1);
289
0
                    }
290
0
                    color = !color;
291
0
                    a0 = a1;
292
0
                    if delta < 0 {
293
0
                        transitions.seek_back(a0);
294
0
                    }
295
                }
296
                Mode::Horizontal => {
297
0
                    let a0a1 = colored(color, &mut self.reader).ok_or(DecodeError::Invalid)?;
298
0
                    let a1a2 = colored(!color, &mut self.reader).ok_or(DecodeError::Invalid)?;
299
0
                    let a1 = a0.checked_add(a0a1).ok_or(DecodeError::Invalid)?;
300
0
                    let a2 = a1.checked_add(a1a2).ok_or(DecodeError::Invalid)?;
301
                    //debug!("a0a1={}, a1a2={}, a1={}, a2={}", a0a1, a1a2, a1, a2);
302
303
                    // Same canonical form rule: never store a transition
304
                    // at width (it's the end-of-line sentinel, not a flip).
305
0
                    if a1 < self.width {
306
0
                        self.current.push(a1);
307
0
                    }
308
0
                    if a2 >= self.width {
309
0
                        break;
310
0
                    }
311
0
                    self.current.push(a2);
312
0
                    a0 = a2;
313
                }
314
                Mode::Extension => {
315
0
                    let _ext = self.reader.peek(3).ok_or(DecodeError::Invalid)?;
316
0
                    let _ = self.reader.consume(3);
317
0
                    return Err(DecodeError::Unsupported);
318
                }
319
0
                Mode::EOF => return Ok(DecodeStatus::End),
320
            }
321
0
            start_of_row = false;
322
323
0
            if a0 >= self.width {
324
0
                break;
325
0
            }
326
        }
327
        //debug!("{:?}", current);
328
329
0
        std::mem::swap(&mut self.reference, &mut self.current);
330
0
        self.current.clear();
331
332
0
        Ok(DecodeStatus::Incomplete)
333
0
    }
Unexecuted instantiation: <fax::decoder::Group4Decoder<std::io::Bytes<std::io::buffered::bufreader::BufReader<std::io::Take<&mut std::io::cursor::Cursor<&[u8]>>>>>>::advance
Unexecuted instantiation: <fax::decoder::Group4Decoder<_>>::advance
334
335
0
    pub fn transition(&self) -> &[u16] {
336
0
        &self.reference
337
0
    }
Unexecuted instantiation: <fax::decoder::Group4Decoder<std::io::Bytes<std::io::buffered::bufreader::BufReader<std::io::Take<&mut std::io::cursor::Cursor<&[u8]>>>>>>::transition
Unexecuted instantiation: <fax::decoder::Group4Decoder<_>>::transition
338
339
0
    pub fn line(&self) -> Line {
340
0
        Line {
341
0
            transitions: &self.reference,
342
0
            width: self.width,
343
0
        }
344
0
    }
345
}
346
347
pub struct Line<'a> {
348
    pub transitions: &'a [u16],
349
    pub width: u16,
350
}
351
impl<'a> Line<'a> {
352
0
    pub fn pels(&self) -> impl Iterator<Item = Color> + 'a {
353
0
        pels(&self.transitions, self.width)
354
0
    }
355
}
356
357
#[cfg(test)]
358
mod tests {
359
    use super::*;
360
361
    /// Fuzz artifact: 5 bytes that triggered checked_add overflow in G4
362
    /// horizontal mode before the fix. The overflow is now caught by
363
    /// checked_add and the decoder recovers, producing partial output.
364
    #[test]
365
    fn g4_fuzz_crash_horizontal_overflow() {
366
        let data: Vec<u8> = vec![0xe8, 0x05, 0x00, 0x00, 0x00];
367
        let mut lines = 0u32;
368
        let result = decode_g4(data.into_iter(), 100, Some(10), |_| {
369
            lines += 1;
370
        });
371
        // Decoder recovers from the overflow and produces some lines.
372
        // The key assertion: no panic. Before the fix this was an
373
        // "attempt to add with overflow" panic.
374
        assert!(
375
            result.is_some(),
376
            "decoder should recover from caught overflow"
377
        );
378
        assert!(lines <= 10, "should not exceed requested height");
379
    }
380
381
    /// Fuzz artifact: 119 bytes that triggered G3 run-length overflow.
382
    /// After the fix, checked_add returns DecodeError::Invalid and the
383
    /// decoder returns None.
384
    #[test]
385
    fn g3_fuzz_crash_run_length_overflow() {
386
        let mut data = vec![
387
            0x10, 0x10, 0x00, 0x04, 0x00, 0x10, 0x00, 0xb3, 0x00, 0x00, 0x10, 0x00, 0xb3, 0x00,
388
            0x10, 0x10,
389
        ];
390
        data.extend_from_slice(&[0xce; 103]);
391
        let result = decode_g3(data.into_iter(), |_| {});
392
        assert_eq!(result, None, "corrupt G3 data should return None");
393
    }
394
395
    /// Width > 32767 used to overflow i16 in vertical mode delta.
396
    /// Now uses i32 — must not panic.
397
    #[test]
398
    fn g4_large_width_no_overflow() {
399
        let data: Vec<u8> = vec![0x00; 512];
400
        let result = decode_g4(data.into_iter(), 40000, Some(1), |_| {});
401
        let _ = result; // must not panic
402
    }
403
404
    /// Zero-width image: degenerate, must not loop forever or panic.
405
    #[test]
406
    fn g4_zero_width_no_panic() {
407
        let data: Vec<u8> = vec![0x00; 64];
408
        let result = decode_g4(data.into_iter(), 0, Some(1), |_| {});
409
        let _ = result; // must not panic
410
    }
411
412
    /// Random bytes fed to G3 decoder — must not panic regardless of content.
413
    #[test]
414
    fn g3_random_bytes_no_panic() {
415
        let data: Vec<u8> = (0..512).map(|i| (i * 37 + 13) as u8).collect();
416
        let result = decode_g3(data.into_iter(), |_| {});
417
        let _ = result; // must not panic
418
    }
419
420
    /// Roundtrip: a line with a color change at width-1 should produce
421
    /// the same pels after encode→decode. Note that transition lists are
422
    /// NOT a canonical representation — e.g., `[3]` and `[3, 4]` both
423
    /// represent "3 white + 1 black" at width=4. We compare pels (the
424
    /// semantic form) rather than transition lists.
425
    #[test]
426
    fn g4_roundtrip_width_boundary_transition() {
427
        let transitions = vec![3u16, 4];
428
        let width = 4u16;
429
        let input_pels: Vec<_> = super::pels(&transitions, width).collect();
430
        let writer = crate::VecWriter::new();
431
        let mut encoder = crate::encoder::Encoder::new(writer);
432
        let _ = encoder.encode_line(input_pels.iter().copied(), width);
433
        let encoded = encoder.finish().unwrap().finish();
434
        let mut decoded = Vec::new();
435
        let _ = decode_g4(encoded.into_iter(), width, Some(1), |line| {
436
            decoded.push(line.to_vec());
437
        });
438
        let decoded_line = decoded.first().expect("decoded one line");
439
        let output_pels: Vec<_> = super::pels(decoded_line, width).collect();
440
        assert_eq!(
441
            input_pels, output_pels,
442
            "pels must roundtrip (decoded transitions: {:?})",
443
            decoded_line
444
        );
445
    }
446
447
    /// Single transition at arbitrary position should roundtrip cleanly.
448
    /// Regression for the 23 "crash" artifacts surfaced by cargo fuzz cmin:
449
    /// the decoder was producing non-canonical transition lists (appending
450
    /// width sentinel), which the fuzz assertion flagged as mismatches.
451
    #[test]
452
    fn g4_roundtrip_canonical_form() {
453
        for &(width, ref transitions) in &[
454
            (10u16, vec![5]),
455
            (2000, vec![10]),
456
            (2000, vec![3, 51]),
457
            (4, vec![3]),
458
            (100, vec![50]),
459
            (100, vec![1]),
460
            (100, vec![99]),
461
        ] {
462
            let input_pels: Vec<_> = super::pels(transitions, width).collect();
463
            let writer = crate::VecWriter::new();
464
            let mut encoder = crate::encoder::Encoder::new(writer);
465
            let _ = encoder.encode_line(input_pels.iter().copied(), width);
466
            let encoded = encoder.finish().unwrap().finish();
467
            let mut decoded = Vec::new();
468
            let _ = decode_g4(encoded.into_iter(), width, Some(1), |line| {
469
                decoded.push(line.to_vec());
470
            });
471
            let decoded_line = decoded.first().expect("decoded one line");
472
            let output_pels: Vec<_> = super::pels(decoded_line, width).collect();
473
            assert_eq!(
474
                input_pels, output_pels,
475
                "pels must roundtrip for width={width} transitions={transitions:?}, \
476
                 got decoded transitions {decoded_line:?}"
477
            );
478
            // Canonical form: decoder must not append the width sentinel.
479
            assert!(
480
                decoded_line.iter().all(|&t| t < width),
481
                "decoder produced non-canonical transition list {decoded_line:?} \
482
                 (contains width={width}); transitions should all be < width"
483
            );
484
        }
485
    }
486
}