Skip to main content

fax/
decoder.rs

1use std::convert::Infallible;
2use std::io::{self, Bytes, Read};
3
4use crate::maps::{black, mode, white, Mode, EDFB_HALF, EOL};
5use crate::{BitReader, ByteReader, Color, Transitions};
6
7fn with_markup<D, R>(decoder: D, reader: &mut R) -> Option<u16>
8where
9    D: Fn(&mut R) -> Option<u16>,
10{
11    let mut sum: u16 = 0;
12    while let Some(n) = decoder(reader) {
13        //print!("{} ", n);
14        sum = sum.checked_add(n)?;
15        if n < 64 {
16            //debug!("= {}", sum);
17            return Some(sum);
18        }
19    }
20    None
21}
22
23fn colored(current: Color, reader: &mut impl BitReader) -> Option<u16> {
24    //debug!("{:?}", current);
25    match current {
26        Color::Black => with_markup(black::decode, reader),
27        Color::White => with_markup(white::decode, reader),
28    }
29}
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.
35pub fn pels(line: &[u16], width: u16) -> impl Iterator<Item = Color> + '_ {
36    use std::iter::repeat;
37    let mut color = Color::White;
38    let mut last = 0;
39    let pad_color = if line.len() & 1 == 1 { !color } else { color };
40    line.iter()
41        .flat_map(move |&p| {
42            let c = color;
43            color = !color;
44            let n = p.saturating_sub(last);
45            last = p;
46            repeat(c).take(n as usize)
47        })
48        .chain(repeat(pad_color))
49        .take(width as usize)
50}
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.
58pub fn decode_g3(input: impl Iterator<Item = u8>, mut line_cb: impl FnMut(&[u16])) -> Option<()> {
59    let reader = input.map(Result::<u8, Infallible>::Ok);
60    let mut decoder = Group3Decoder::new(reader).ok()?;
61
62    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        line_cb(decoder.transitions());
67        if status == DecodeStatus::End {
68            return Some(());
69        }
70    }
71    None
72}
73
74#[derive(PartialEq, Eq, Debug, Copy, Clone)]
75pub enum DecodeStatus {
76    Incomplete,
77    End,
78}
79
80pub struct Group3Decoder<R> {
81    reader: ByteReader<R>,
82    current: Vec<u16>,
83}
84impl<E: std::fmt::Debug, R: Iterator<Item = Result<u8, E>>> Group3Decoder<R> {
85    pub fn new(reader: R) -> Result<Self, DecodeError<E>> {
86        let mut reader = ByteReader::new(reader).map_err(DecodeError::Reader)?;
87        // Skip any fill bits (zeros) then consume the initial EOL marker.
88        skip_to_eol(&mut reader).map_err(|_| DecodeError::Invalid)?;
89
90        Ok(Group3Decoder {
91            reader,
92            current: vec![],
93        })
94    }
95    pub fn advance(&mut self) -> Result<DecodeStatus, DecodeError<E>> {
96        self.current.clear();
97        let mut a0: u16 = 0;
98        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            if is_eol_ahead(&self.reader) {
104                break;
105            }
106            match colored(color, &mut self.reader) {
107                Some(p) => {
108                    a0 = a0.checked_add(p).ok_or(DecodeError::Invalid)?;
109                    self.current.push(a0);
110                    color = !color;
111                }
112                None => break,
113            }
114        }
115        // Skip any fill bits and consume the EOL.
116        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        for _ in 0..5 {
120            if is_eol_ahead(&self.reader) {
121                skip_to_eol(&mut self.reader).map_err(|_| DecodeError::Invalid)?;
122            } else {
123                return Ok(DecodeStatus::Incomplete);
124            }
125        }
126
127        Ok(DecodeStatus::End)
128    }
129    pub fn transitions(&self) -> &[u16] {
130        &self.current
131    }
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.
143fn 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    reader.peek(9) == Some(0)
148}
149
150/// Skip zero fill bits and consume the EOL marker (000000000001).
151/// Returns Err if no valid EOL is found.
152fn skip_to_eol<E: std::fmt::Debug, R: Iterator<Item = Result<u8, E>>>(
153    reader: &mut ByteReader<R>,
154) -> Result<(), DecodeError<E>> {
155    // Skip zero fill bits (used for byte alignment in Group3Options bit 2).
156    while reader.peek(1) == Some(0) {
157        reader.consume(1).map_err(DecodeError::Reader)?;
158    }
159    // The next bit should be the '1' that terminates the EOL.
160    if reader.peek(1) == Some(1) {
161        reader.consume(1).map_err(DecodeError::Reader)?;
162        Ok(())
163    } else {
164        Err(DecodeError::Invalid)
165    }
166}
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.
178pub fn decode_g4(
179    input: impl Iterator<Item = u8>,
180    width: u16,
181    height: Option<u16>,
182    mut line_cb: impl FnMut(&[u16]),
183) -> Option<()> {
184    let reader = input.map(Result::<u8, Infallible>::Ok);
185    let mut decoder = Group4Decoder::new(reader, width).ok()?;
186
187    let max_lines = height.unwrap_or(u16::MAX);
188    let mut lines_emitted: u16 = 0;
189
190    while lines_emitted < max_lines {
191        let status = decoder.advance().ok()?;
192        if status == DecodeStatus::End {
193            break;
194        }
195        line_cb(decoder.transition());
196        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    if let Some(h) = height {
203        while lines_emitted < h {
204            line_cb(&[]);
205            lines_emitted += 1;
206        }
207    }
208
209    Some(())
210}
211
212#[derive(Debug)]
213pub enum DecodeError<E> {
214    Reader(E),
215    Invalid,
216    Unsupported,
217}
218impl<E> std::fmt::Display for DecodeError<E> {
219    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
220        write!(f, "Decode Error")
221    }
222}
223impl<E: std::error::Error> std::error::Error for DecodeError<E> {}
224
225pub struct Group4Decoder<R> {
226    reader: ByteReader<R>,
227    reference: Vec<u16>,
228    current: Vec<u16>,
229    width: u16,
230}
231impl<E, R: Iterator<Item = Result<u8, E>>> Group4Decoder<R> {
232    pub fn new(reader: R, width: u16) -> Result<Self, E> {
233        Ok(Group4Decoder {
234            reader: ByteReader::new(reader)?,
235            reference: Vec::new(),
236            current: Vec::new(),
237            width,
238        })
239    }
240    // when Complete::Complete is returned, there is no useful data in .transitions() or .line()
241    pub fn advance(&mut self) -> Result<DecodeStatus, DecodeError<E>> {
242        let mut transitions = Transitions::new(&self.reference);
243        let mut a0 = 0;
244        let mut color = Color::White;
245        let mut start_of_row = true;
246        //debug!("\n\nline {}", y);
247
248        loop {
249            //reader.print_peek();
250            let mode = match mode::decode(&mut self.reader) {
251                Some(mode) => mode,
252                None => return Err(DecodeError::Invalid),
253            };
254            //debug!("  {:?}, color={:?}, a0={}", mode, color, a0);
255
256            match mode {
257                Mode::Pass => {
258                    if start_of_row && color == Color::White {
259                        transitions.pos += 1;
260                    } else {
261                        transitions
262                            .next_color(a0, !color, false)
263                            .ok_or(DecodeError::Invalid)?;
264                    }
265                    //debug!("b1={}", b1);
266                    if let Some(b2) = transitions.next() {
267                        //debug!("b2={}", b2);
268                        a0 = b2;
269                    }
270                }
271                Mode::Vertical(delta) => {
272                    let b1 = transitions
273                        .next_color(a0, !color, start_of_row)
274                        .unwrap_or(self.width);
275                    let a1_i32 = b1 as i32 + delta as i32;
276                    if a1_i32 < 0 || a1_i32 > self.width as i32 {
277                        break;
278                    }
279                    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                    if a1 < self.width {
288                        self.current.push(a1);
289                    }
290                    color = !color;
291                    a0 = a1;
292                    if delta < 0 {
293                        transitions.seek_back(a0);
294                    }
295                }
296                Mode::Horizontal => {
297                    let a0a1 = colored(color, &mut self.reader).ok_or(DecodeError::Invalid)?;
298                    let a1a2 = colored(!color, &mut self.reader).ok_or(DecodeError::Invalid)?;
299                    let a1 = a0.checked_add(a0a1).ok_or(DecodeError::Invalid)?;
300                    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                    if a1 < self.width {
306                        self.current.push(a1);
307                    }
308                    if a2 >= self.width {
309                        break;
310                    }
311                    self.current.push(a2);
312                    a0 = a2;
313                }
314                Mode::Extension => {
315                    let _ext = self.reader.peek(3).ok_or(DecodeError::Invalid)?;
316                    let _ = self.reader.consume(3);
317                    return Err(DecodeError::Unsupported);
318                }
319                Mode::EOF => return Ok(DecodeStatus::End),
320            }
321            start_of_row = false;
322
323            if a0 >= self.width {
324                break;
325            }
326        }
327        //debug!("{:?}", current);
328
329        std::mem::swap(&mut self.reference, &mut self.current);
330        self.current.clear();
331
332        Ok(DecodeStatus::Incomplete)
333    }
334
335    pub fn transition(&self) -> &[u16] {
336        &self.reference
337    }
338
339    pub fn line(&self) -> Line {
340        Line {
341            transitions: &self.reference,
342            width: self.width,
343        }
344    }
345}
346
347pub struct Line<'a> {
348    pub transitions: &'a [u16],
349    pub width: u16,
350}
351impl<'a> Line<'a> {
352    pub fn pels(&self) -> impl Iterator<Item = Color> + 'a {
353        pels(&self.transitions, self.width)
354    }
355}
356
357#[cfg(test)]
358mod 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}