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
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
use std::error;
use std::fmt;
use std::io;
use std::result;

/// A convenient type alias for `Result<T, snap::Error>`.
pub type Result<T> = result::Result<T, Error>;

/// `IntoInnerError` occurs when consuming a `Writer` fails.
///
/// Consuming the `Writer` causes a flush to happen. If the flush fails, then
/// this error is returned, which contains both the original `Writer` and
/// the error that occurred.
///
/// The type parameter `W` is the unconsumed writer.
pub struct IntoInnerError<W> {
    wtr: W,
    err: io::Error,
}

/// Creates a new `IntoInnerError`.
///
/// (This is a visibility hack. It's public in this module, but not in the
/// crate.)
pub fn new_into_inner_error<W>(wtr: W, err: io::Error) -> IntoInnerError<W> {
    IntoInnerError { wtr: wtr, err: err }
}

impl<W> IntoInnerError<W> {
    /// Returns the error which caused the call to `into_inner` to fail.
    ///
    /// This error was returned when attempting to flush the internal buffer.
    pub fn error(&self) -> &io::Error {
        &self.err
    }

    /// Returns the underlying writer which generated the error.
    ///
    /// The returned value can be used for error recovery, such as
    /// re-inspecting the buffer.
    pub fn into_inner(self) -> W {
        self.wtr
    }
}

impl<W: ::std::any::Any> error::Error for IntoInnerError<W> {
    fn description(&self) -> &str {
        self.err.description()
    }

    fn cause(&self) -> Option<&error::Error> {
        self.err.cause()
    }
}

impl<W> fmt::Display for IntoInnerError<W> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.err.fmt(f)
    }
}

impl<W> fmt::Debug for IntoInnerError<W> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.err.fmt(f)
    }
}

/// Error describes all the possible errors that may occur during Snappy
/// compression or decompression.
///
/// Note that it's unlikely that you'll need to care about the specific error
/// reported since all of them indicate a corrupt Snappy data or a limitation
/// that cannot be worked around. Therefore,
/// `From<snap::Error> for std::io::Error` is provided so that any Snappy
/// errors will be converted to a `std::io::Error` automatically when using
/// `try!`.
#[derive(Debug)]
pub enum Error {
    /// This error occurs when the given input is too big. This can happen
    /// during compression or decompression.
    TooBig {
        /// The size of the given input.
        given: u64,
        /// The maximum allowed size of an input buffer.
        max: u64,
    },
    /// This error occurs when the given buffer is too small to contain the
    /// maximum possible compressed bytes or the total number of decompressed
    /// bytes.
    BufferTooSmall {
        /// The size of the given output buffer.
        given: u64,
        /// The minimum size of the output buffer.
        min: u64,
    },
    /// This error occurs when trying to decompress a zero length buffer.
    Empty,
    /// This error occurs when an invalid header is found during decompression.
    Header,
    /// This error occurs when there is a mismatch between the number of
    /// decompressed bytes reported in the header and the number of
    /// actual decompressed bytes. In this error case, the number of actual
    /// decompressed bytes is always less than the number reported in the
    /// header.
    HeaderMismatch {
        /// The total number of decompressed bytes expected (i.e., the header
        /// value).
        expected_len: u64,
        /// The total number of actual decompressed bytes.
        got_len: u64,
    },
    /// This error occurs during decompression when there was a problem
    /// reading a literal.
    Literal {
        /// The expected length of the literal.
        len: u64,
        /// The number of remaining bytes in the compressed bytes.
        src_len: u64,
        /// The number of remaining slots in the decompression buffer.
        dst_len: u64,
    },
    /// This error occurs during decompression when there was a problem
    /// reading a copy.
    CopyRead {
        /// The expected length of the copy (as encoded in the compressed
        /// bytes).
        len: u64,
        /// The number of remaining bytes in the compressed bytes.
        src_len: u64,
    },
    /// This error occurs during decompression when there was a problem
    /// writing a copy to the decompression buffer.
    CopyWrite {
        /// The length of the copy (i.e., the total number of bytes to be
        /// produced by this copy in the decompression buffer).
        len: u64,
        /// The number of remaining bytes in the decompression buffer.
        dst_len: u64,
    },
    /// This error occurs during decompression when an invalid copy offset
    /// is found. An offset is invalid if it is zero or if it is out of bounds.
    Offset {
        /// The offset that was read.
        offset: u64,
        /// The current position in the decompression buffer. If the offset is
        /// non-zero, then the offset must be greater than this position.
        dst_pos: u64,
    },
    /// This error occurs when a stream header chunk type was expected but got
    /// a different chunk type.
    /// This error only occurs when reading a Snappy frame formatted stream.
    StreamHeader {
        /// The chunk type byte that was read.
        byte: u8,
    },
    /// This error occurs when the magic stream headers bytes do not match
    /// what is expected.
    /// This error only occurs when reading a Snappy frame formatted stream.
    StreamHeaderMismatch {
        /// The bytes that were read.
        bytes: Vec<u8>,
    },
    /// This error occurs when an unsupported chunk type is seen.
    /// This error only occurs when reading a Snappy frame formatted stream.
    UnsupportedChunkType {
        /// The chunk type byte that was read.
        byte: u8,
    },
    /// This error occurs when trying to read a chunk with length greater than
    /// that supported by this library when reading a Snappy frame formatted
    /// stream.
    /// This error only occurs when reading a Snappy frame formatted stream.
    UnsupportedChunkLength {
        /// The length of the chunk encountered.
        len: u64,
        /// True when this error occured while reading the stream header.
        header: bool,
    },
    /// This error occurs when a checksum validity check fails.
    /// This error only occurs when reading a Snappy frame formatted stream.
    Checksum {
        /// The expected checksum read from the stream.
        expected: u32,
        /// The computed checksum.
        got: u32,
    },
}

impl From<Error> for io::Error {
    fn from(err: Error) -> io::Error {
        io::Error::new(io::ErrorKind::Other, err)
    }
}

impl Eq for Error {}

/// This implementation of `PartialEq` returns `false` when comparing two
/// errors whose underlying type is `std::io::Error`.
impl PartialEq for Error {
    fn eq(&self, other: &Error) -> bool {
        use self::Error::*;
        match (self, other) {
            (&TooBig { given: given1, max: max1 },
             &TooBig { given: given2, max: max2 }) => {
                (given1, max1) == (given2, max2)
            }
            (&BufferTooSmall { given: given1, min: min1 },
             &BufferTooSmall { given: given2, min: min2 }) => {
                (given1, min1) == (given2, min2)
            }
            (&Empty, &Empty) |
            (&Header, &Header) => true,
            (&HeaderMismatch { expected_len: elen1, got_len: glen1 },
             &HeaderMismatch { expected_len: elen2, got_len: glen2 }) => {
                (elen1, glen1) == (elen2, glen2)
            }
            (&Literal { len: len1, src_len: src_len1, dst_len: dst_len1 },
             &Literal { len: len2, src_len: src_len2, dst_len: dst_len2 }) => {
                (len1, src_len1, dst_len1) == (len2, src_len2, dst_len2)
            }
            (&CopyRead { len: len1, src_len: src_len1 },
             &CopyRead { len: len2, src_len: src_len2 }) => {
                (len1, src_len1) == (len2, src_len2)
            }
            (&CopyWrite { len: len1, dst_len: dst_len1 },
             &CopyWrite { len: len2, dst_len: dst_len2 }) => {
                (len1, dst_len1) == (len2, dst_len2)
            }
            (&Offset { offset: offset1, dst_pos: dst_pos1 },
             &Offset { offset: offset2, dst_pos: dst_pos2 }) => {
                (offset1, dst_pos1) == (offset2, dst_pos2)
            }
            (&StreamHeader { byte: byte1 }, &StreamHeader { byte: byte2 }) => {
                byte1 == byte2
            }
            (&StreamHeaderMismatch { bytes: ref bytes1 },
             &StreamHeaderMismatch { bytes: ref bytes2 }) => {
                bytes1 == bytes2
            }
            (&UnsupportedChunkType { byte: byte1 },
             &UnsupportedChunkType { byte: byte2 }) => {
                byte1 == byte2
            }
            (&UnsupportedChunkLength { len: len1, header: header1 },
             &UnsupportedChunkLength { len: len2, header: header2 }) => {
                (len1, header1) == (len2, header2)
            }
            (&Checksum { expected: e1, got: g1 },
             &Checksum { expected: e2, got: g2 }) => {
                (e1, g1) == (e2, g2)
            }
            _ => false,
        }
    }
}

impl error::Error for Error {
    fn description(&self) -> &str {
        match *self {
            Error::TooBig { .. } => "snappy: input buffer too big",
            Error::BufferTooSmall { .. } => "snappy: output buffer too small",
            Error::Empty => "snappy: corrupt input (empty)",
            Error::Header => "snappy: corrupt input (invalid header)",
            Error::HeaderMismatch { .. } => "snappy: corrupt input \
                                             (header mismatch)",
            Error::Literal { .. } => "snappy: corrupt input (bad literal)",
            Error::CopyRead { .. } => "snappy: corrupt input (bad copy read)",
            Error::CopyWrite { .. } => "snappy: corrupt input \
                                        (bad copy write)",
            Error::Offset { .. } => "snappy: corrupt input (bad offset)",
            Error::StreamHeader { .. } => {
                "snappy: corrupt input (missing stream header)"
            }
            Error::StreamHeaderMismatch { .. } => {
                "snappy: corrupt input (stream header mismatch)"
            }
            Error::UnsupportedChunkType { .. } => {
                "snappy: corrupt input (unsupported chunk type)"
            }
            Error::UnsupportedChunkLength { header: false, .. } => {
                "snappy: corrupt input (unsupported chunk length)"
            }
            Error::UnsupportedChunkLength { header: true, .. } => {
                "snappy: corrupt input (invalid stream header)"
            }
            Error::Checksum { .. } => "snappy: corrupt input (bad checksum)",
        }
    }

    fn cause(&self) -> Option<&error::Error> {
        None
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::TooBig { given, max } => {
                write!(f, "snappy: input buffer (size = {}) is larger than \
                           allowed (size = {})", given, max)
            }
            Error::BufferTooSmall { given, min } => {
                write!(f, "snappy: output buffer (size = {}) is smaller than \
                           required (size = {})", given, min)
            }
            Error::Empty => {
                write!(f, "snappy: corrupt input (empty)")
            }
            Error::Header => {
                write!(f, "snappy: corrupt input (invalid header)")
            }
            Error::HeaderMismatch { expected_len, got_len } => {
                write!(f, "snappy: corrupt input (header mismatch; expected \
                           {} decompressed bytes but got {})",
                           expected_len, got_len)
            }
            Error::Literal { len, src_len, dst_len } => {
                write!(f, "snappy: corrupt input (expected literal read of \
                           length {}; remaining src: {}; remaining dst: {})",
                       len, src_len, dst_len)
            }
            Error::CopyRead { len, src_len } => {
                write!(f, "snappy: corrupt input (expected copy read of \
                           length {}; remaining src: {})", len, src_len)
            }
            Error::CopyWrite { len, dst_len } => {
                write!(f, "snappy: corrupt input (expected copy write of \
                           length {}; remaining dst: {})", len, dst_len)
            }
            Error::Offset { offset, dst_pos } => {
                write!(f, "snappy: corrupt input (expected valid offset but \
                           got offset {}; dst position: {})", offset, dst_pos)
            }
            Error::StreamHeader { byte } => {
                write!(f, "snappy: corrupt input (expected stream header but \
                           got unexpected chunk type byte {})", byte)
            }
            Error::StreamHeaderMismatch { ref bytes } => {
                write!(f, "snappy: corrupt input (expected sNaPpY stream \
                           header but got {})", escape(&**bytes))
            }
            Error::UnsupportedChunkType { byte } => {
                write!(f, "snappy: corrupt input (unsupported chunk type: {})",
                       byte)
            }
            Error::UnsupportedChunkLength { len, header: false } => {
                write!(f, "snappy: corrupt input \
                           (unsupported chunk length: {})", len)
            }
            Error::UnsupportedChunkLength { len, header: true } => {
                write!(f, "snappy: corrupt input \
                           (invalid stream header length: {})", len)
            }
            Error::Checksum { expected, got } => {
                write!(f, "snappy: corrupt input (bad checksum; \
                           expected: {}, got: {})", expected, got)
            }
        }
    }
}

fn escape(bytes: &[u8]) -> String {
    use std::ascii::escape_default;
    bytes.iter().flat_map(|&b| escape_default(b)).map(|b| b as char).collect()
}