1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
use std::error::Error;
use std::fmt::{self, Display, Formatter};
use std::io;
use std::sync::Arc;
/// The reason a gzip container was rejected.
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum GzipErrorKind {
/// The gzip identification bytes were absent.
BadMagic,
/// The member did not use the DEFLATE compression method.
UnsupportedCompressionMethod(u8),
/// One or more reserved flag bits were set.
ReservedFlags(u8),
/// The optional header checksum was incorrect.
HeaderChecksumMismatch {
/// Checksum stored in the header.
expected: u16,
/// Checksum computed over preceding header bytes.
actual: u16,
},
/// A zero-terminated header field reached the end of input.
UnterminatedHeaderField,
/// The member header or footer was truncated.
Truncated,
/// Bytes after a valid member were not another gzip member.
TrailingGarbage,
}
impl Display for GzipErrorKind {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::BadMagic => formatter.write_str("missing gzip magic bytes"),
Self::UnsupportedCompressionMethod(method) => {
write!(formatter, "unsupported gzip compression method {method}")
}
Self::ReservedFlags(flags) => {
write!(formatter, "reserved gzip flag bits are set: {flags:#04x}")
}
Self::HeaderChecksumMismatch { expected, actual } => write!(
formatter,
"gzip header checksum mismatch: expected {expected:#06x}, got {actual:#06x}"
),
Self::UnterminatedHeaderField => formatter.write_str("unterminated gzip header field"),
Self::Truncated => formatter.write_str("truncated gzip header or footer"),
Self::TrailingGarbage => formatter.write_str("trailing non-gzip data"),
}
}
}
/// The reason a DEFLATE stream was rejected.
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum DeflateErrorKind {
/// The backend rejected the compressed stream.
InvalidData,
/// The stream unexpectedly requested a preset dictionary.
UnexpectedDictionary,
/// The backend returned an unexpected status code.
BackendStatus(i32),
/// No progress was possible before the end of the compressed input.
Truncated,
/// The decoder made no progress despite having input and output space.
Stalled,
}
impl Display for DeflateErrorKind {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidData => formatter.write_str("invalid DEFLATE data"),
Self::UnexpectedDictionary => {
formatter.write_str("gzip DEFLATE stream requested a preset dictionary")
}
Self::BackendStatus(status) => {
write!(formatter, "unexpected DEFLATE backend status {status}")
}
Self::Truncated => formatter.write_str("truncated DEFLATE stream"),
Self::Stalled => formatter.write_str("DEFLATE decoder made no progress"),
}
}
}
/// A terminal decoding error.
///
/// This type is cloneable so a [`std::io::Read`] adapter can return the same
/// logical failure on every read after the pipeline has failed.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum DecodeError {
/// A positional input read or output write failed.
Io {
/// Compressed offset, when the operation was tied to an input offset.
offset: Option<u64>,
/// Shared original I/O error.
source: Arc<io::Error>,
},
/// The gzip framing was invalid.
InvalidGzip {
/// Compressed byte offset.
offset: u64,
/// Detailed reason.
reason: GzipErrorKind,
},
/// The raw DEFLATE payload was invalid.
InvalidDeflate {
/// Best-known compressed bit offset.
bit_offset: u64,
/// Detailed reason.
reason: DeflateErrorKind,
},
/// A member's CRC32 did not match its footer.
ChecksumMismatch {
/// Zero-based member number.
member: u64,
/// Footer value.
expected: u32,
/// Computed value.
actual: u32,
},
/// A member's modulo-2^32 output size did not match its footer.
SizeMismatch {
/// Zero-based member number.
member: u64,
/// Footer value.
expected: u32,
/// Computed value.
actual_mod32: u32,
},
/// Decoded output would exceed the configured limit.
OutputLimitExceeded {
/// Configured maximum decoded byte count.
limit: u64,
},
/// A decoder worker panicked.
WorkerPanicked,
/// Decoding was cancelled because the consumer stopped.
Cancelled,
}
impl DecodeError {
pub(crate) fn input_io(offset: u64, source: io::Error) -> Self {
Self::Io {
offset: Some(offset),
source: Arc::new(source),
}
}
pub(crate) fn output_io(source: io::Error) -> Self {
Self::Io {
offset: None,
source: Arc::new(source),
}
}
pub(crate) fn io_kind(&self) -> io::ErrorKind {
match self {
Self::Io { source, .. } => source.kind(),
Self::InvalidGzip {
reason: GzipErrorKind::Truncated,
..
}
| Self::InvalidDeflate {
reason: DeflateErrorKind::Truncated,
..
} => io::ErrorKind::UnexpectedEof,
Self::InvalidGzip { .. }
| Self::InvalidDeflate { .. }
| Self::ChecksumMismatch { .. }
| Self::SizeMismatch { .. } => io::ErrorKind::InvalidData,
Self::OutputLimitExceeded { .. } => io::ErrorKind::FileTooLarge,
Self::WorkerPanicked => io::ErrorKind::Other,
Self::Cancelled => io::ErrorKind::Interrupted,
}
}
pub(crate) fn to_io_error(&self) -> io::Error {
io::Error::new(self.io_kind(), self.clone())
}
}
impl Display for DecodeError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Io {
offset: Some(offset),
source,
} => write!(
formatter,
"I/O error at compressed offset {offset}: {source}"
),
Self::Io {
offset: None,
source,
} => write!(formatter, "output I/O error: {source}"),
Self::InvalidGzip { offset, reason } => {
write!(formatter, "invalid gzip data at byte {offset}: {reason}")
}
Self::InvalidDeflate { bit_offset, reason } => {
write!(
formatter,
"invalid DEFLATE data at bit {bit_offset}: {reason}"
)
}
Self::ChecksumMismatch {
member,
expected,
actual,
} => write!(
formatter,
"gzip member {member} CRC32 mismatch: expected {expected:#010x}, got {actual:#010x}"
),
Self::SizeMismatch {
member,
expected,
actual_mod32,
} => write!(
formatter,
"gzip member {member} ISIZE mismatch: expected {expected}, got {actual_mod32}"
),
Self::OutputLimitExceeded { limit } => {
write!(formatter, "decoded output exceeded the {limit}-byte limit")
}
Self::WorkerPanicked => formatter.write_str("a decoder worker panicked"),
Self::Cancelled => formatter.write_str("decoding was cancelled"),
}
}
}
impl Error for DecodeError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Io { source, .. } => Some(source.as_ref()),
_ => None,
}
}
}
/// Statistics produced after the complete stream has been verified.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct DecodeReport {
/// Compressed bytes consumed.
pub compressed_bytes: u64,
/// Decompressed bytes emitted.
pub decompressed_bytes: u64,
/// Number of verified gzip members.
pub member_count: u64,
/// Configured decoder-worker budget.
pub decoder_threads: usize,
}