Skip to main content

yencoding_multi/
error.rs

1use std::ops::Range;
2
3/// Errors produced by [`crate::Assembler`] operations.
4#[derive(Debug, PartialEq, Eq)]
5#[non_exhaustive]
6pub enum AssemblyError {
7    /// A part's byte range overlaps with an already-accepted part.
8    ///
9    /// yEnc parts must cover non-overlapping, contiguous byte ranges of the
10    /// final file. When two parts claim the same byte(s), the second is
11    /// rejected. `existing` is the range already stored; `new` is the range
12    /// that was rejected.
13    ///
14    /// **Caller action**: de-duplicate incoming articles (the same article
15    /// may arrive multiple times from different Usenet servers).
16    OverlappingPart {
17        existing: Range<u64>,
18        new: Range<u64>,
19    },
20
21    /// A part's byte range falls outside `[0, total_size)`.
22    ///
23    /// Either the `=ypart begin=/end=` values were invalid, or the wrong
24    /// `total_size` was passed to [`Assembler::new`][crate::Assembler::new].
25    OutOfRange {
26        begin: u64,
27        end: u64,
28        total_size: u64,
29    },
30
31    /// Whole-file CRC32 mismatch on [`Assembler::finish`][crate::Assembler::finish].
32    ///
33    /// The reassembled bytes hash to a different CRC32 than the expected value
34    /// set via [`Assembler::set_expected_crc32`][crate::Assembler::set_expected_crc32].
35    CrcMismatch { expected: u32, actual: u32 },
36
37    /// [`Assembler::finish`][crate::Assembler::finish] was called before all
38    /// byte ranges were covered.
39    ///
40    /// `missing` lists the 0-based byte ranges that have not been received.
41    /// Call [`Assembler::is_complete`][crate::Assembler::is_complete] before
42    /// `finish()` to avoid this error.
43    Incomplete { missing: Vec<Range<u64>> },
44
45    /// A decoded part's data length does not match its declared `=ypart begin=/end=` range.
46    ///
47    /// This indicates a corrupt or malformed article: the decoded payload is
48    /// a different size than the byte range it claims to cover.
49    DataLengthMismatch {
50        /// The byte count implied by the `begin`/`end` range header.
51        declared_range_len: usize,
52        /// The actual number of decoded bytes.
53        actual_data_len: usize,
54    },
55
56    /// `total_size` exceeds the built-in safety cap or the addressable memory
57    /// on this platform.
58    ///
59    /// [`Assembler::new`][crate::Assembler::new] rejects any `total_size`
60    /// greater than [`MAX_TOTAL_SIZE`][crate::MAX_TOTAL_SIZE] (512 MiB) to
61    /// prevent an adversarial `=ybegin size=` field from forcing a multi-GiB
62    /// allocation.  On 32-bit targets the platform `usize` limit applies first.
63    TotalSizeTooLarge {
64        /// The value that was rejected.
65        total_size: u64,
66    },
67
68    /// A decoded part has `part_begin` set but `part_end` absent, or vice versa.
69    ///
70    /// yEnc `=ypart begin=/end=` always provides both values or neither.
71    /// A `DecodedPart` with only one of the two fields set indicates a corrupt
72    /// or incorrectly constructed part.
73    MalformedPartRange,
74
75    /// Two parts carry conflicting whole-file CRC32 values.
76    ///
77    /// [`Assembler::add_part`][crate::Assembler::add_part] auto-extracts the
78    /// `crc32=` whole-file CRC from each part that carries it.  If a later part
79    /// reports a different value than one already recorded, the series is
80    /// internally inconsistent and the assembly must be aborted.
81    ///
82    /// `first` is the CRC recorded from an earlier part; `new` is the
83    /// conflicting value from the current part.
84    InconsistentCrc { first: u32, new: u32 },
85}
86
87impl std::fmt::Display for AssemblyError {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        match self {
90            AssemblyError::OverlappingPart { existing, new } => write!(
91                f,
92                "part byte range {}..{} overlaps with already-stored range {}..{} — \
93                 check for duplicate articles",
94                new.start, new.end, existing.start, existing.end
95            ),
96            AssemblyError::OutOfRange {
97                begin,
98                end,
99                total_size,
100            } => write!(
101                f,
102                "part byte range {}..{} is outside total file size {} — \
103                 verify =ypart begin=/end= and total_size",
104                begin, end, total_size
105            ),
106            AssemblyError::CrcMismatch { expected, actual } => write!(
107                f,
108                "whole-file CRC32 mismatch: expected {:#010x}, got {:#010x} — \
109                 re-fetch all parts and retry",
110                expected, actual
111            ),
112            AssemblyError::Incomplete { missing } => {
113                write!(
114                    f,
115                    "assembly incomplete: missing {} byte range(s): ",
116                    missing.len()
117                )?;
118                for (i, r) in missing.iter().enumerate() {
119                    if i > 0 {
120                        write!(f, ", ")?;
121                    }
122                    write!(f, "{}..{}", r.start, r.end)?;
123                }
124                Ok(())
125            }
126            AssemblyError::DataLengthMismatch {
127                declared_range_len,
128                actual_data_len,
129            } => write!(
130                f,
131                "part data length {actual_data_len} does not match declared range length \
132                 {declared_range_len} — corrupt or malformed article"
133            ),
134            AssemblyError::TotalSizeTooLarge { total_size } => write!(
135                f,
136                "total_size {total_size} exceeds the 512 MiB safety cap or the \
137                 addressable memory on this platform (usize::MAX = {})",
138                usize::MAX
139            ),
140            AssemblyError::MalformedPartRange => write!(
141                f,
142                "part_begin and part_end must both be Some or both be None"
143            ),
144            AssemblyError::InconsistentCrc { first, new } => write!(
145                f,
146                "inconsistent whole-file CRC32 across parts: \
147                 first seen {first:#010x}, new part reports {new:#010x} — \
148                 corrupt or mismatched article series"
149            ),
150        }
151    }
152}
153
154impl std::error::Error for AssemblyError {}