diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 6fa3e90..dcd7d30 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,3 +1,7 @@ +# 0.21.4 + +- Make `encoded_len` `const`, allowing the creation of arrays sized to encode compile-time-known data lengths + # 0.21.3 - Implement `source` instead of `cause` on Error types @@ -12,7 +16,8 @@ # 0.21.1 - Remove the possibility of panicking during decoded length calculations -- `DecoderReader` no longer sometimes erroneously ignores padding [#226](https://github.com/marshallpierce/rust-base64/issues/226) +- `DecoderReader` no longer sometimes erroneously ignores + padding [#226](https://github.com/marshallpierce/rust-base64/issues/226) ## Breaking changes @@ -64,7 +69,8 @@ precisely, see the following table. ## Breaking changes -- Re-exports of preconfigured engines in `engine` are removed in favor of `base64::prelude::...` that are better suited to those who wish to `use` the entire path to a name. +- Re-exports of preconfigured engines in `engine` are removed in favor of `base64::prelude::...` that are better suited + to those who wish to `use` the entire path to a name. # 0.21.0-beta.1 diff --git a/src/encode.rs b/src/encode.rs index 00ade74..2162d9e 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -94,25 +94,32 @@ pub(crate) fn encode_with_padding( /// /// Returns `None` if the encoded length can't be represented in `usize`. This will happen for /// input lengths in approximately the top quarter of the range of `usize`. -pub fn encoded_len(bytes_len: usize, padding: bool) -> Option { +pub const fn encoded_len(bytes_len: usize, padding: bool) -> Option { let rem = bytes_len % 3; let complete_input_chunks = bytes_len / 3; - let complete_chunk_output = complete_input_chunks.checked_mul(4); + // `let Some(_) = _ else` requires 1.65.0, whereas this messier one works on 1.48 + let complete_chunk_output = + if let Some(complete_chunk_output) = complete_input_chunks.checked_mul(4) { + complete_chunk_output + } else { + return None; + }; if rem > 0 { if padding { - complete_chunk_output.and_then(|c| c.checked_add(4)) + complete_chunk_output.checked_add(4) } else { let encoded_rem = match rem { 1 => 2, - 2 => 3, - _ => unreachable!("Impossible remainder"), + // only other possible remainder is 2 + // can't use a separate _ => unreachable!() in const fns in ancient rust versions + _ => 3, }; - complete_chunk_output.and_then(|c| c.checked_add(encoded_rem)) + complete_chunk_output.checked_add(encoded_rem) } } else { - complete_chunk_output + Some(complete_chunk_output) } } diff --git a/tests/encode.rs b/tests/encode.rs index cf61e9a..9d69447 100644 --- a/tests/encode.rs +++ b/tests/encode.rs @@ -44,3 +44,34 @@ fn encode_all_bytes_url() { &engine::GeneralPurpose::new(&URL_SAFE, PAD).encode(bytes) ); } + +#[test] +fn encoded_len_unpadded() { + assert_eq!(0, encoded_len(0, false).unwrap()); + assert_eq!(2, encoded_len(1, false).unwrap()); + assert_eq!(3, encoded_len(2, false).unwrap()); + assert_eq!(4, encoded_len(3, false).unwrap()); + assert_eq!(6, encoded_len(4, false).unwrap()); + assert_eq!(7, encoded_len(5, false).unwrap()); + assert_eq!(8, encoded_len(6, false).unwrap()); + assert_eq!(10, encoded_len(7, false).unwrap()); +} + +#[test] +fn encoded_len_padded() { + assert_eq!(0, encoded_len(0, true).unwrap()); + assert_eq!(4, encoded_len(1, true).unwrap()); + assert_eq!(4, encoded_len(2, true).unwrap()); + assert_eq!(4, encoded_len(3, true).unwrap()); + assert_eq!(8, encoded_len(4, true).unwrap()); + assert_eq!(8, encoded_len(5, true).unwrap()); + assert_eq!(8, encoded_len(6, true).unwrap()); + assert_eq!(12, encoded_len(7, true).unwrap()); +} +#[test] +fn encoded_len_overflow() { + let max_size = usize::MAX / 4 * 3 + 2; + assert_eq!(2, max_size % 3); + assert_eq!(Some(usize::MAX), encoded_len(max_size, false)); + assert_eq!(None, encoded_len(max_size + 1, false)); +}