diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index 802c8cc..8f04185 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -102,9 +102,8 @@ fn do_encode_bench_slice(b: &mut Bencher, &size: &usize) { fn do_encode_bench_stream(b: &mut Bencher, &size: &usize) { let mut v: Vec = Vec::with_capacity(size); fill(&mut v); - let mut buf = Vec::new(); + let mut buf = Vec::with_capacity(size * 2); - buf.reserve(size * 2); b.iter(|| { buf.clear(); let mut stream_enc = write::EncoderWriter::new(&mut buf, &STANDARD); diff --git a/src/decode.rs b/src/decode.rs index 0f66c74..d042b09 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -52,9 +52,7 @@ impl error::Error for DecodeError {} pub enum DecodeSliceError { /// A [DecodeError] occurred DecodeError(DecodeError), - /// The provided slice _may_ be too small. - /// - /// The check is conservative (assumes the last triplet of output bytes will all be needed). + /// The provided slice is too small. OutputSliceTooSmall, } diff --git a/src/engine/general_purpose/decode.rs b/src/engine/general_purpose/decode.rs index 31c289e..98ce043 100644 --- a/src/engine/general_purpose/decode.rs +++ b/src/engine/general_purpose/decode.rs @@ -1,12 +1,13 @@ use crate::{ engine::{general_purpose::INVALID_VALUE, DecodeEstimate, DecodeMetadata, DecodePaddingMode}, - DecodeError, PAD_BYTE, + DecodeError, DecodeSliceError, PAD_BYTE, }; #[doc(hidden)] pub struct GeneralPurposeEstimate { + /// input len % 4 rem: usize, - conservative_len: usize, + conservative_decoded_len: usize, } impl GeneralPurposeEstimate { @@ -14,14 +15,14 @@ impl GeneralPurposeEstimate { let rem = encoded_len % 4; Self { rem, - conservative_len: (encoded_len / 4 + (rem > 0) as usize) * 3, + conservative_decoded_len: (encoded_len / 4 + (rem > 0) as usize) * 3, } } } impl DecodeEstimate for GeneralPurposeEstimate { fn decoded_len_estimate(&self) -> usize { - self.conservative_len + self.conservative_decoded_len } } @@ -38,25 +39,9 @@ pub(crate) fn decode_helper( decode_table: &[u8; 256], decode_allow_trailing_bits: bool, padding_mode: DecodePaddingMode, -) -> Result { - // detect a trailing invalid byte, like a newline, as a user convenience - if estimate.rem == 1 { - let last_byte = input[input.len() - 1]; - // exclude pad bytes; might be part of padding that extends from earlier in the input - if last_byte != PAD_BYTE && decode_table[usize::from(last_byte)] == INVALID_VALUE { - return Err(DecodeError::InvalidByte(input.len() - 1, last_byte)); - } - } - - // skip last quad, even if it's complete, as it may have padding - let input_complete_nonterminal_quads_len = input - .len() - .saturating_sub(estimate.rem) - // if rem was 0, subtract 4 to avoid padding - .saturating_sub((estimate.rem == 0) as usize * 4); - debug_assert!( - input.is_empty() || (1..=4).contains(&(input.len() - input_complete_nonterminal_quads_len)) - ); +) -> Result { + let input_complete_nonterminal_quads_len = + complete_quads_len(input, estimate.rem, output.len(), decode_table)?; const UNROLLED_INPUT_CHUNK_SIZE: usize = 32; const UNROLLED_OUTPUT_CHUNK_SIZE: usize = UNROLLED_INPUT_CHUNK_SIZE / 4 * 3; @@ -135,6 +120,48 @@ pub(crate) fn decode_helper( ) } +/// Returns the length of complete quads, except for the last one, even if it is complete. +/// +/// Returns an error if the output len is not big enough for decoding those complete quads, or if +/// the input % 4 == 1, and that last byte is an invalid value other than a pad byte. +/// +/// - `input` is the base64 input +/// - `input_len_rem` is input len % 4 +/// - `output_len` is the length of the output slice +pub(crate) fn complete_quads_len( + input: &[u8], + input_len_rem: usize, + output_len: usize, + decode_table: &[u8; 256], +) -> Result { + debug_assert!(input.len() % 4 == input_len_rem); + + // detect a trailing invalid byte, like a newline, as a user convenience + if input_len_rem == 1 { + let last_byte = input[input.len() - 1]; + // exclude pad bytes; might be part of padding that extends from earlier in the input + if last_byte != PAD_BYTE && decode_table[usize::from(last_byte)] == INVALID_VALUE { + return Err(DecodeError::InvalidByte(input.len() - 1, last_byte).into()); + } + }; + + // skip last quad, even if it's complete, as it may have padding + let input_complete_nonterminal_quads_len = input + .len() + .saturating_sub(input_len_rem) + // if rem was 0, subtract 4 to avoid padding + .saturating_sub((input_len_rem == 0) as usize * 4); + debug_assert!( + input.is_empty() || (1..=4).contains(&(input.len() - input_complete_nonterminal_quads_len)) + ); + + // check that everything except the last quad handled by decode_suffix will fit + if output_len < input_complete_nonterminal_quads_len / 4 * 3 { + return Err(DecodeSliceError::OutputSliceTooSmall); + }; + Ok(input_complete_nonterminal_quads_len) +} + /// Decode 8 bytes of input into 6 bytes of output. /// /// `input` is the 8 bytes to decode. @@ -321,7 +348,7 @@ mod tests { let len_128 = encoded_len as u128; let estimate = GeneralPurposeEstimate::new(encoded_len); - assert_eq!((len_128 + 3) / 4 * 3, estimate.conservative_len as u128); + assert_eq!((len_128 + 3) / 4 * 3, estimate.conservative_decoded_len as u128); }) } } diff --git a/src/engine/general_purpose/decode_suffix.rs b/src/engine/general_purpose/decode_suffix.rs index 3d52ae5..02aaf51 100644 --- a/src/engine/general_purpose/decode_suffix.rs +++ b/src/engine/general_purpose/decode_suffix.rs @@ -1,6 +1,6 @@ use crate::{ engine::{general_purpose::INVALID_VALUE, DecodeMetadata, DecodePaddingMode}, - DecodeError, PAD_BYTE, + DecodeError, DecodeSliceError, PAD_BYTE, }; /// Decode the last 0-4 bytes, checking for trailing set bits and padding per the provided @@ -16,11 +16,11 @@ pub(crate) fn decode_suffix( decode_table: &[u8; 256], decode_allow_trailing_bits: bool, padding_mode: DecodePaddingMode, -) -> Result { +) -> Result { debug_assert!((input.len() - input_index) <= 4); - // Decode any leftovers that might not be a complete input chunk of 8 bytes. - // Use a u64 as a stack-resident 8 byte buffer. + // Decode any leftovers that might not be a complete input chunk of 4 bytes. + // Use a u32 as a stack-resident 4 byte buffer. let mut morsels_in_leftover = 0; let mut padding_bytes_count = 0; // offset from input_index @@ -44,22 +44,14 @@ pub(crate) fn decode_suffix( // may be treated as an error condition. if leftover_index < 2 { - // Check for case #2. - let bad_padding_index = input_index - + if padding_bytes_count > 0 { - // If we've already seen padding, report the first padding index. - // This is to be consistent with the normal decode logic: it will report an - // error on the first padding character (since it doesn't expect to see - // anything but actual encoded data). - // This could only happen if the padding started in the previous quad since - // otherwise this case would have been hit at i == 4 if it was the same - // quad. - first_padding_offset - } else { - // haven't seen padding before, just use where we are now - leftover_index - }; - return Err(DecodeError::InvalidByte(bad_padding_index, b)); + // Check for error #2. + // Either the previous byte was padding, in which case we would have already hit + // this case, or it wasn't, in which case this is the first such error. + debug_assert!( + leftover_index == 0 || (leftover_index == 1 && padding_bytes_count == 0) + ); + let bad_padding_index = input_index + leftover_index; + return Err(DecodeError::InvalidByte(bad_padding_index, b).into()); } if padding_bytes_count == 0 { @@ -75,10 +67,9 @@ pub(crate) fn decode_suffix( // non-suffix '=' in trailing chunk either. Report error as first // erroneous padding. if padding_bytes_count > 0 { - return Err(DecodeError::InvalidByte( - input_index + first_padding_offset, - PAD_BYTE, - )); + return Err( + DecodeError::InvalidByte(input_index + first_padding_offset, PAD_BYTE).into(), + ); } last_symbol = b; @@ -87,7 +78,7 @@ pub(crate) fn decode_suffix( // Pack the leftovers from left to right. let morsel = decode_table[b as usize]; if morsel == INVALID_VALUE { - return Err(DecodeError::InvalidByte(input_index + leftover_index, b)); + return Err(DecodeError::InvalidByte(input_index + leftover_index, b).into()); } morsels[morsels_in_leftover] = morsel; @@ -97,9 +88,7 @@ pub(crate) fn decode_suffix( // If there was 1 trailing byte, and it was valid, and we got to this point without hitting // an invalid byte, now we can report invalid length if !input.is_empty() && morsels_in_leftover < 2 { - return Err(DecodeError::InvalidLength( - input_index + morsels_in_leftover, - )); + return Err(DecodeError::InvalidLength(input_index + morsels_in_leftover).into()); } match padding_mode { @@ -107,14 +96,14 @@ pub(crate) fn decode_suffix( DecodePaddingMode::RequireCanonical => { // allow empty input if (padding_bytes_count + morsels_in_leftover) % 4 != 0 { - return Err(DecodeError::InvalidPadding); + return Err(DecodeError::InvalidPadding.into()); } } DecodePaddingMode::RequireNone => { if padding_bytes_count > 0 { // check at the end to make sure we let the cases of padding that should be InvalidByte // get hit - return Err(DecodeError::InvalidPadding); + return Err(DecodeError::InvalidPadding.into()); } } } @@ -127,7 +116,7 @@ pub(crate) fn decode_suffix( // bits in the bottom 6, but would be a non-canonical encoding. So, we calculate a // mask based on how many bits are used for just the canonical encoding, and optionally // error if any other bits are set. In the example of one encoded byte -> 2 symbols, - // 2 symbols can technically encode 12 bits, but the last 4 are non canonical, and + // 2 symbols can technically encode 12 bits, but the last 4 are non-canonical, and // useless since there are no more symbols to provide the necessary 4 additional bits // to finish the second original byte. @@ -147,7 +136,8 @@ pub(crate) fn decode_suffix( return Err(DecodeError::InvalidLastSymbol( input_index + morsels_in_leftover - 1, last_symbol, - )); + ) + .into()); } // Strangely, this approach benchmarks better than writing bytes one at a time, @@ -155,8 +145,9 @@ pub(crate) fn decode_suffix( for _ in 0..leftover_bytes_to_append { let hi_byte = (leftover_num >> 24) as u8; leftover_num <<= 8; - // TODO use checked writes - output[output_index] = hi_byte; + *output + .get_mut(output_index) + .ok_or(DecodeSliceError::OutputSliceTooSmall)? = hi_byte; output_index += 1; } diff --git a/src/engine/general_purpose/mod.rs b/src/engine/general_purpose/mod.rs index e0227f3..6fe9580 100644 --- a/src/engine/general_purpose/mod.rs +++ b/src/engine/general_purpose/mod.rs @@ -3,11 +3,11 @@ use crate::{ alphabet, alphabet::Alphabet, engine::{Config, DecodeMetadata, DecodePaddingMode}, - DecodeError, + DecodeSliceError, }; use core::convert::TryInto; -mod decode; +pub(crate) mod decode; pub(crate) mod decode_suffix; pub use decode::GeneralPurposeEstimate; @@ -173,7 +173,7 @@ impl super::Engine for GeneralPurpose { input: &[u8], output: &mut [u8], estimate: Self::DecodeEstimate, - ) -> Result { + ) -> Result { decode::decode_helper( input, estimate, diff --git a/src/engine/mod.rs b/src/engine/mod.rs index 16c05d7..77dcd14 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -83,17 +83,13 @@ pub trait Engine: Send + Sync { /// /// Non-canonical trailing bits in the final tokens or non-canonical padding must be reported as /// errors unless the engine is configured otherwise. - /// - /// # Panics - /// - /// Panics if `output` is too small. #[doc(hidden)] fn internal_decode( &self, input: &[u8], output: &mut [u8], decode_estimate: Self::DecodeEstimate, - ) -> Result; + ) -> Result; /// Returns the config for this engine. fn config(&self) -> &Self::Config; @@ -253,7 +249,13 @@ pub trait Engine: Send + Sync { let mut buffer = vec![0; estimate.decoded_len_estimate()]; let bytes_written = engine - .internal_decode(input_bytes, &mut buffer, estimate)? + .internal_decode(input_bytes, &mut buffer, estimate) + .map_err(|e| match e { + DecodeSliceError::DecodeError(e) => e, + DecodeSliceError::OutputSliceTooSmall => { + unreachable!("Vec is sized conservatively") + } + })? .decoded_len; buffer.truncate(bytes_written); @@ -318,7 +320,13 @@ pub trait Engine: Send + Sync { let buffer_slice = &mut buffer.as_mut_slice()[starting_output_len..]; let bytes_written = engine - .internal_decode(input_bytes, buffer_slice, estimate)? + .internal_decode(input_bytes, buffer_slice, estimate) + .map_err(|e| match e { + DecodeSliceError::DecodeError(e) => e, + DecodeSliceError::OutputSliceTooSmall => { + unreachable!("Vec is sized conservatively") + } + })? .decoded_len; buffer.truncate(starting_output_len + bytes_written); @@ -354,15 +362,12 @@ pub trait Engine: Send + Sync { where E: Engine + ?Sized, { - let estimate = engine.internal_decoded_len_estimate(input_bytes.len()); - - if output.len() < estimate.decoded_len_estimate() { - return Err(DecodeSliceError::OutputSliceTooSmall); - } - engine - .internal_decode(input_bytes, output, estimate) - .map_err(|e| e.into()) + .internal_decode( + input_bytes, + output, + engine.internal_decoded_len_estimate(input_bytes.len()), + ) .map(|dm| dm.decoded_len) } @@ -400,6 +405,12 @@ pub trait Engine: Send + Sync { engine.internal_decoded_len_estimate(input_bytes.len()), ) .map(|dm| dm.decoded_len) + .map_err(|e| match e { + DecodeSliceError::DecodeError(e) => e, + DecodeSliceError::OutputSliceTooSmall => { + panic!("Output slice is too small") + } + }) } inner(self, input.as_ref(), output) diff --git a/src/engine/naive.rs b/src/engine/naive.rs index 2546a6f..15c07cc 100644 --- a/src/engine/naive.rs +++ b/src/engine/naive.rs @@ -4,7 +4,7 @@ use crate::{ general_purpose::{self, decode_table, encode_table}, Config, DecodeEstimate, DecodeMetadata, DecodePaddingMode, Engine, }, - DecodeError, PAD_BYTE, + DecodeError, DecodeSliceError, }; use std::ops::{BitAnd, BitOr, Shl, Shr}; @@ -111,60 +111,36 @@ impl Engine for Naive { input: &[u8], output: &mut [u8], estimate: Self::DecodeEstimate, - ) -> Result { - if estimate.rem == 1 { - // trailing whitespace is so common that it's worth it to check the last byte to - // possibly return a better error message - let last_byte = input[input.len() - 1]; - if last_byte != PAD_BYTE - && self.decode_table[usize::from(last_byte)] == general_purpose::INVALID_VALUE - { - return Err(DecodeError::InvalidByte(input.len() - 1, last_byte)); - } - } + ) -> Result { + let complete_nonterminal_quads_len = + general_purpose::decode::complete_quads_len(input, estimate.rem, output.len(), &self.decode_table)?; - let mut input_index = 0_usize; - let mut output_index = 0_usize; const BOTTOM_BYTE: u32 = 0xFF; - // can only use the main loop on non-trailing chunks - if input.len() > Self::DECODE_INPUT_CHUNK_SIZE { - // skip the last chunk, whether it's partial or full, since it might - // have padding, and start at the beginning of the chunk before that - let last_complete_chunk_start_index = estimate.complete_chunk_len - - if estimate.rem == 0 { - // Trailing chunk is also full chunk, so there must be at least 2 chunks, and - // this won't underflow - Self::DECODE_INPUT_CHUNK_SIZE * 2 - } else { - // Trailing chunk is partial, so it's already excluded in - // complete_chunk_len - Self::DECODE_INPUT_CHUNK_SIZE - }; - - while input_index <= last_complete_chunk_start_index { - let chunk = &input[input_index..input_index + Self::DECODE_INPUT_CHUNK_SIZE]; - let decoded_int: u32 = self.decode_byte_into_u32(input_index, chunk[0])?.shl(18) - | self - .decode_byte_into_u32(input_index + 1, chunk[1])? - .shl(12) - | self.decode_byte_into_u32(input_index + 2, chunk[2])?.shl(6) - | self.decode_byte_into_u32(input_index + 3, chunk[3])?; - - output[output_index] = decoded_int.shr(16_u8).bitand(BOTTOM_BYTE) as u8; - output[output_index + 1] = decoded_int.shr(8_u8).bitand(BOTTOM_BYTE) as u8; - output[output_index + 2] = decoded_int.bitand(BOTTOM_BYTE) as u8; - - input_index += Self::DECODE_INPUT_CHUNK_SIZE; - output_index += 3; - } + for (chunk_index, chunk) in input[..complete_nonterminal_quads_len] + .chunks_exact(4) + .enumerate() + { + let input_index = chunk_index * 4; + let output_index = chunk_index * 3; + + let decoded_int: u32 = self.decode_byte_into_u32(input_index, chunk[0])?.shl(18) + | self + .decode_byte_into_u32(input_index + 1, chunk[1])? + .shl(12) + | self.decode_byte_into_u32(input_index + 2, chunk[2])?.shl(6) + | self.decode_byte_into_u32(input_index + 3, chunk[3])?; + + output[output_index] = decoded_int.shr(16_u8).bitand(BOTTOM_BYTE) as u8; + output[output_index + 1] = decoded_int.shr(8_u8).bitand(BOTTOM_BYTE) as u8; + output[output_index + 2] = decoded_int.bitand(BOTTOM_BYTE) as u8; } general_purpose::decode_suffix::decode_suffix( input, - input_index, + complete_nonterminal_quads_len, output, - output_index, + complete_nonterminal_quads_len / 4 * 3, &self.decode_table, self.config.decode_allow_trailing_bits, self.config.decode_padding_mode, diff --git a/src/engine/tests.rs b/src/engine/tests.rs index b73f108..72bbf4b 100644 --- a/src/engine/tests.rs +++ b/src/engine/tests.rs @@ -19,7 +19,7 @@ use crate::{ }, read::DecoderReader, tests::{assert_encode_sanity, random_alphabet, random_config}, - DecodeError, PAD_BYTE, + DecodeError, DecodeSliceError, PAD_BYTE, }; // the case::foo syntax includes the "foo" in the generated test method names @@ -803,7 +803,8 @@ fn decode_too_little_data_before_padding_error_invalid_byte(en PAD_BYTE, )), engine.decode(&encoded), - "suffix data len {} pad len {}", + "input {} suffix data len {} pad len {}", + String::from_utf8(encoded).unwrap(), suffix_data_len, padding_len ); @@ -1077,16 +1078,15 @@ fn decode_into_slice_fits_in_precisely_sized_slice(engine_wrap assert_eq!(orig_data.len(), decode_bytes_written); assert_eq!(orig_data, decode_buf); - // TODO // same for checked variant - // decode_buf.clear(); - // decode_buf.resize(input_len, 0); - // // decode into the non-empty buf - // let decode_bytes_written = engine - // .decode_slice(encoded_data.as_bytes(), &mut decode_buf[..]) - // .unwrap(); - // assert_eq!(orig_data.len(), decode_bytes_written); - // assert_eq!(orig_data, decode_buf); + decode_buf.clear(); + decode_buf.resize(input_len, 0); + // decode into the non-empty buf + let decode_bytes_written = engine + .decode_slice(encoded_data.as_bytes(), &mut decode_buf[..]) + .unwrap(); + assert_eq!(orig_data.len(), decode_bytes_written); + assert_eq!(orig_data, decode_buf); } } @@ -1118,7 +1118,10 @@ fn inner_decode_reports_padding_position(engine_wrapper: E) { if pad_position % 4 < 2 { // impossible padding assert_eq!( - Err(DecodeError::InvalidByte(pad_position, PAD_BYTE)), + Err(DecodeSliceError::DecodeError(DecodeError::InvalidByte( + pad_position, + PAD_BYTE + ))), decode_res ); } else { @@ -1186,6 +1189,63 @@ fn estimate_via_u128_inflation(engine_wrapper: E) { }) } +#[apply(all_engines)] +fn decode_slice_checked_fails_gracefully_at_all_output_lengths( + engine_wrapper: E, +) { + let mut rng = seeded_rng(); + for original_len in 0..1000 { + let mut original = vec![0; original_len]; + rng.fill(&mut original[..]); + + for mode in all_pad_modes() { + let engine = E::standard_with_pad_mode( + match mode { + DecodePaddingMode::Indifferent | DecodePaddingMode::RequireCanonical => true, + DecodePaddingMode::RequireNone => false, + }, + mode, + ); + + let encoded = engine.encode(&original); + let mut decode_buf = Vec::with_capacity(original_len); + for decode_buf_len in 0..original_len { + decode_buf.resize(decode_buf_len, 0); + assert_eq!( + DecodeSliceError::OutputSliceTooSmall, + engine + .decode_slice(&encoded, &mut decode_buf[..]) + .unwrap_err(), + "original len: {}, encoded len: {}, buf len: {}, mode: {:?}", + original_len, + encoded.len(), + decode_buf_len, + mode + ); + // internal method works the same + assert_eq!( + DecodeSliceError::OutputSliceTooSmall, + engine + .internal_decode( + encoded.as_bytes(), + &mut decode_buf[..], + engine.internal_decoded_len_estimate(encoded.len()) + ) + .unwrap_err() + ); + } + + decode_buf.resize(original_len, 0); + rng.fill(&mut decode_buf[..]); + assert_eq!( + original_len, + engine.decode_slice(&encoded, &mut decode_buf[..]).unwrap() + ); + assert_eq!(original, decode_buf); + } + } +} + /// Returns a tuple of the original data length, the encoded data length (just data), and the length including padding. /// /// Vecs provided should be empty. @@ -1346,7 +1406,7 @@ impl EngineWrapper for NaiveWrapper { naive::Naive::new( &STANDARD, naive::NaiveConfig { - encode_padding: false, + encode_padding: encode_pad, decode_allow_trailing_bits: false, decode_padding_mode: decode_pad_mode, }, @@ -1415,7 +1475,7 @@ impl Engine for DecoderReaderEngine { input: &[u8], output: &mut [u8], decode_estimate: Self::DecodeEstimate, - ) -> Result { + ) -> Result { let mut reader = DecoderReader::new(input, &self.engine); let mut buf = vec![0; input.len()]; // to avoid effects like not detecting invalid length due to progressively growing @@ -1434,6 +1494,9 @@ impl Engine for DecoderReaderEngine { .and_then(|inner| inner.downcast::().ok()) .unwrap() })?; + if output.len() < buf.len() { + return Err(DecodeSliceError::OutputSliceTooSmall); + } output[..buf.len()].copy_from_slice(&buf); Ok(DecodeMetadata::new( buf.len(), diff --git a/src/read/decoder.rs b/src/read/decoder.rs index 125eeab..781f6f8 100644 --- a/src/read/decoder.rs +++ b/src/read/decoder.rs @@ -1,4 +1,4 @@ -use crate::{engine::Engine, DecodeError, PAD_BYTE}; +use crate::{engine::Engine, DecodeError, DecodeSliceError, PAD_BYTE}; use std::{cmp, fmt, io}; // This should be large, but it has to fit on the stack. @@ -133,6 +133,10 @@ impl<'e, E: Engine, R: io::Read> DecoderReader<'e, E, R> { /// caller's responsibility to choose the number of b64 bytes to decode correctly. /// /// Returns a Result with the number of decoded bytes written to `buf`. + /// + /// # Panics + /// + /// panics if `buf` is too small fn decode_to_buf(&mut self, b64_len_to_decode: usize, buf: &mut [u8]) -> io::Result { debug_assert!(self.b64_len >= b64_len_to_decode); debug_assert!(self.b64_offset + self.b64_len <= BUF_SIZE); @@ -146,26 +150,35 @@ impl<'e, E: Engine, R: io::Read> DecoderReader<'e, E, R> { buf, self.engine.internal_decoded_len_estimate(b64_len_to_decode), ) - .map_err(|e| match e { - DecodeError::InvalidByte(offset, byte) => { - match (byte, self.padding_offset) { - // if there was padding in a previous block of decoding that happened to - // be correct, and we now find more padding that happens to be incorrect, - // to be consistent with non-reader decodes, record the error at the first - // padding - (PAD_BYTE, Some(first_pad_offset)) => { - DecodeError::InvalidByte(first_pad_offset, PAD_BYTE) + .map_err(|dse| match dse { + DecodeSliceError::DecodeError(de) => { + match de { + DecodeError::InvalidByte(offset, byte) => { + match (byte, self.padding_offset) { + // if there was padding in a previous block of decoding that happened to + // be correct, and we now find more padding that happens to be incorrect, + // to be consistent with non-reader decodes, record the error at the first + // padding + (PAD_BYTE, Some(first_pad_offset)) => { + DecodeError::InvalidByte(first_pad_offset, PAD_BYTE) + } + _ => { + DecodeError::InvalidByte(self.input_consumed_len + offset, byte) + } + } + } + DecodeError::InvalidLength(len) => { + DecodeError::InvalidLength(self.input_consumed_len + len) } - _ => DecodeError::InvalidByte(self.input_consumed_len + offset, byte), + DecodeError::InvalidLastSymbol(offset, byte) => { + DecodeError::InvalidLastSymbol(self.input_consumed_len + offset, byte) + } + DecodeError::InvalidPadding => DecodeError::InvalidPadding, } } - DecodeError::InvalidLength(len) => { - DecodeError::InvalidLength(self.input_consumed_len + len) - } - DecodeError::InvalidLastSymbol(offset, byte) => { - DecodeError::InvalidLastSymbol(self.input_consumed_len + offset, byte) + DecodeSliceError::OutputSliceTooSmall => { + unreachable!("buf is sized correctly in calling code") } - DecodeError::InvalidPadding => DecodeError::InvalidPadding, }) .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;