Skip to content

Commit

Permalink
Add decode_slice_unchecked to restore ability to decode into a precis…
Browse files Browse the repository at this point in the history
…ely sized slice
  • Loading branch information
marshallpierce committed Jan 1, 2023
1 parent a51e822 commit 0f981bd
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 46 deletions.
3 changes: 3 additions & 0 deletions RELEASE-NOTES.md
Expand Up @@ -37,6 +37,9 @@ precisely, see the following table.
| URL_SAFE | URL_SAFE | true | Indifferent |
| URL_SAFE_NO_PAD | URL_SAFE | false | Indifferent |

# 0.21.0-rc.1

- Restore the ability to decode into a slice of precisely the correct length with `Engine.decode_slice_unchecked`.

# 0.21.0-beta.2

Expand Down
108 changes: 62 additions & 46 deletions src/decode.rs
Expand Up @@ -235,7 +235,66 @@ mod tests {
}

#[test]
fn decode_into_slice_doesnt_clobber_existing_prefix_or_suffix() {
fn decode_slice_doesnt_clobber_existing_prefix_or_suffix() {
do_decode_slice_doesnt_clobber_existing_prefix_or_suffix(|e, input, output| {
e.decode_slice(input, output).unwrap()
})
}

#[test]
fn decode_slice_unchecked_doesnt_clobber_existing_prefix_or_suffix() {
do_decode_slice_doesnt_clobber_existing_prefix_or_suffix(|e, input, output| {
e.decode_slice_unchecked(input, output).unwrap()
})
}

#[test]
fn decode_engine_estimation_works_for_various_lengths() {
let engine = GeneralPurpose::new(&alphabet::STANDARD, general_purpose::NO_PAD);
for num_prefix_quads in 0..100 {
for suffix in &["AA", "AAA", "AAAA"] {
let mut prefix = "AAAA".repeat(num_prefix_quads);
prefix.push_str(suffix);
// make sure no overflow (and thus a panic) occurs
let res = engine.decode(prefix);
assert!(res.is_ok());
}
}
}

#[test]
fn decode_slice_output_length_errors() {
for num_quads in 1..100 {
let input = "AAAA".repeat(num_quads);
let mut vec = vec![0; (num_quads - 1) * 3];
assert_eq!(
DecodeSliceError::OutputSliceTooSmall,
STANDARD.decode_slice(&input, &mut vec).unwrap_err()
);
vec.push(0);
assert_eq!(
DecodeSliceError::OutputSliceTooSmall,
STANDARD.decode_slice(&input, &mut vec).unwrap_err()
);
vec.push(0);
assert_eq!(
DecodeSliceError::OutputSliceTooSmall,
STANDARD.decode_slice(&input, &mut vec).unwrap_err()
);
vec.push(0);
// now it works
assert_eq!(
num_quads * 3,
STANDARD.decode_slice(&input, &mut vec).unwrap()
);
}
}

fn do_decode_slice_doesnt_clobber_existing_prefix_or_suffix<
F: Fn(&GeneralPurpose, &[u8], &mut [u8]) -> usize,
>(
call_decode: F,
) {
let mut orig_data = Vec::new();
let mut encoded_data = String::new();
let mut decode_buf = Vec::new();
Expand Down Expand Up @@ -272,9 +331,8 @@ mod tests {
let offset = 1000;

// decode into the non-empty buf
let decode_bytes_written = engine
.decode_slice(&encoded_data, &mut decode_buf[offset..])
.unwrap();
let decode_bytes_written =
call_decode(&engine, encoded_data.as_bytes(), &mut decode_buf[offset..]);

assert_eq!(orig_data.len(), decode_bytes_written);
assert_eq!(
Expand All @@ -288,46 +346,4 @@ mod tests {
);
}
}

#[test]
fn decode_engine_estimation_works_for_various_lengths() {
let engine = GeneralPurpose::new(&alphabet::STANDARD, general_purpose::NO_PAD);
for num_prefix_quads in 0..100 {
for suffix in &["AA", "AAA", "AAAA"] {
let mut prefix = "AAAA".repeat(num_prefix_quads);
prefix.push_str(suffix);
// make sure no overflow (and thus a panic) occurs
let res = engine.decode(prefix);
assert!(res.is_ok());
}
}
}

#[test]
fn decode_slice_output_length_errors() {
for num_quads in 1..100 {
let input = "AAAA".repeat(num_quads);
let mut vec = vec![0; (num_quads - 1) * 3];
assert_eq!(
DecodeSliceError::OutputSliceTooSmall,
STANDARD.decode_slice(&input, &mut vec).unwrap_err()
);
vec.push(0);
assert_eq!(
DecodeSliceError::OutputSliceTooSmall,
STANDARD.decode_slice(&input, &mut vec).unwrap_err()
);
vec.push(0);
assert_eq!(
DecodeSliceError::OutputSliceTooSmall,
STANDARD.decode_slice(&input, &mut vec).unwrap_err()
);
vec.push(0);
// now it works
assert_eq!(
num_quads * 3,
STANDARD.decode_slice(&input, &mut vec).unwrap()
);
}
}
}
34 changes: 34 additions & 0 deletions src/engine/mod.rs
Expand Up @@ -304,10 +304,15 @@ pub trait Engine: Send + Sync {

/// Decode the input into the provided output slice.
///
/// Returns an error if `output` is smaller than the estimated decoded length.
///
/// This will not write any bytes past exactly what is decoded (no stray garbage bytes at the end).
///
/// See [crate::decoded_len_estimate] for calculating buffer sizes.
///
/// See [Engine::decode_slice_unchecked] for a version that panics instead of returning an error
/// if the output buffer is too small.
///
/// # Panics
///
/// Panics if decoded length estimation overflows.
Expand All @@ -327,6 +332,35 @@ pub trait Engine: Send + Sync {
self.internal_decode(input_bytes, output, estimate)
.map_err(|e| e.into())
}

/// Decode the input into the provided output slice.
///
/// This will not write any bytes past exactly what is decoded (no stray garbage bytes at the end).
///
/// See [crate::decoded_len_estimate] for calculating buffer sizes.
///
/// See [Engine::decode_slice] for a version that returns an error instead of panicking if the output
/// buffer is too small.
///
/// # Panics
///
/// Panics if decoded length estimation overflows.
/// This would happen for sizes within a few bytes of the maximum value of `usize`.
///
/// Panics if the provided output buffer is too small for the decoded data.
fn decode_slice_unchecked<T: AsRef<[u8]>>(
&self,
input: T,
output: &mut [u8],
) -> Result<usize, DecodeError> {
let input_bytes = input.as_ref();

self.internal_decode(
input_bytes,
output,
self.internal_decoded_len_estimate(input_bytes.len()),
)
}
}

/// The minimal level of configuration that engines must support.
Expand Down

0 comments on commit 0f981bd

Please sign in to comment.