diff --git a/Cargo.lock b/Cargo.lock index 574c08945..5dd86745e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,7 +43,7 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "base64ct" -version = "1.3.3" +version = "1.4.0-pre" dependencies = [ "base64", "proptest", @@ -515,7 +515,7 @@ dependencies = [ [[package]] name = "pem-rfc7468" -version = "0.3.1" +version = "0.4.0-pre" dependencies = [ "base64ct", ] diff --git a/base64ct/Cargo.toml b/base64ct/Cargo.toml index fc2700845..e3b241020 100644 --- a/base64ct/Cargo.toml +++ b/base64ct/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "base64ct" -version = "1.3.3" # Also update html_root_url in lib.rs when bumping this +version = "1.4.0-pre" # Also update html_root_url in lib.rs when bumping this description = """ Pure Rust implementation of Base64 (RFC 4648) which avoids any usages of data-dependent branches/LUTs and thereby provides portable "best effort" diff --git a/base64ct/src/decoder.rs b/base64ct/src/decoder.rs index 7d3d5d4da..364f10ad4 100644 --- a/base64ct/src/decoder.rs +++ b/base64ct/src/decoder.rs @@ -1,4 +1,4 @@ -//! Stateful Base64 decoder. +//! Buffered Base64 decoder. use crate::{ variant::Variant, @@ -377,77 +377,7 @@ impl<'i> Iterator for LineReader<'i> { #[cfg(test)] mod tests { - use crate::{variant::Variant, Base64, Base64Unpadded, Decoder}; - - /// Padded Base64-encoded example - const PADDED_BASE64: &str = - "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHwf2HMM5TRXvo2SQJjsNkiDD5KqiiNjrGVv3UUh+mMT5RHxiRtOnlqvjhQtBq0VpmpCV/PwUdhOig4vkbqAcEc="; - const PADDED_BIN: &[u8] = &[ - 0, 0, 0, 19, 101, 99, 100, 115, 97, 45, 115, 104, 97, 50, 45, 110, 105, 115, 116, 112, 50, - 53, 54, 0, 0, 0, 8, 110, 105, 115, 116, 112, 50, 53, 54, 0, 0, 0, 65, 4, 124, 31, 216, 115, - 12, 229, 52, 87, 190, 141, 146, 64, 152, 236, 54, 72, 131, 15, 146, 170, 138, 35, 99, 172, - 101, 111, 221, 69, 33, 250, 99, 19, 229, 17, 241, 137, 27, 78, 158, 90, 175, 142, 20, 45, - 6, 173, 21, 166, 106, 66, 87, 243, 240, 81, 216, 78, 138, 14, 47, 145, 186, 128, 112, 71, - ]; - - /// Unpadded Base64-encoded example - const UNPADDED_BASE64: &str = - "AAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqti"; - const UNPADDED_BIN: &[u8] = &[ - 0, 0, 0, 11, 115, 115, 104, 45, 101, 100, 50, 53, 53, 49, 57, 0, 0, 0, 32, 179, 62, 174, - 243, 126, 162, 223, 124, 170, 1, 13, 239, 222, 163, 78, 36, 31, 101, 241, 181, 41, 164, - 244, 62, 209, 67, 39, 245, 197, 74, 171, 98, - ]; - - /// Padded multi-line Base64 example (from the `ssh-key` crate's `id_ed25519`) - const MULTILINE_PADDED_BASE64: &str = - "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n\ - QyNTUxOQAAACCzPq7zfqLffKoBDe/eo04kH2XxtSmk9D7RQyf1xUqrYgAAAJgAIAxdACAM\n\ - XQAAAAtzc2gtZWQyNTUxOQAAACCzPq7zfqLffKoBDe/eo04kH2XxtSmk9D7RQyf1xUqrYg\n\ - AAAEC2BsIi0QwW2uFscKTUUXNHLsYX4FxlaSDSblbAj7WR7bM+rvN+ot98qgEN796jTiQf\n\ - ZfG1KaT0PtFDJ/XFSqtiAAAAEHVzZXJAZXhhbXBsZS5jb20BAgMEBQ=="; - const MULTILINE_PADDED_BIN: &[u8] = &[ - 111, 112, 101, 110, 115, 115, 104, 45, 107, 101, 121, 45, 118, 49, 0, 0, 0, 0, 4, 110, 111, - 110, 101, 0, 0, 0, 4, 110, 111, 110, 101, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 51, 0, 0, 0, 11, - 115, 115, 104, 45, 101, 100, 50, 53, 53, 49, 57, 0, 0, 0, 32, 179, 62, 174, 243, 126, 162, - 223, 124, 170, 1, 13, 239, 222, 163, 78, 36, 31, 101, 241, 181, 41, 164, 244, 62, 209, 67, - 39, 245, 197, 74, 171, 98, 0, 0, 0, 152, 0, 32, 12, 93, 0, 32, 12, 93, 0, 0, 0, 11, 115, - 115, 104, 45, 101, 100, 50, 53, 53, 49, 57, 0, 0, 0, 32, 179, 62, 174, 243, 126, 162, 223, - 124, 170, 1, 13, 239, 222, 163, 78, 36, 31, 101, 241, 181, 41, 164, 244, 62, 209, 67, 39, - 245, 197, 74, 171, 98, 0, 0, 0, 64, 182, 6, 194, 34, 209, 12, 22, 218, 225, 108, 112, 164, - 212, 81, 115, 71, 46, 198, 23, 224, 92, 101, 105, 32, 210, 110, 86, 192, 143, 181, 145, - 237, 179, 62, 174, 243, 126, 162, 223, 124, 170, 1, 13, 239, 222, 163, 78, 36, 31, 101, - 241, 181, 41, 164, 244, 62, 209, 67, 39, 245, 197, 74, 171, 98, 0, 0, 0, 16, 117, 115, 101, - 114, 64, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 1, 2, 3, 4, 5, - ]; - - /// Unpadded multi-line Base64 example (from the `ssh-key` crate's `id_ecdsa_p256`). - const MULTILINE_UNPADDED_BASE64: &str = - "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS\n\ - 1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQR8H9hzDOU0V76NkkCY7DZIgw+Sqooj\n\ - Y6xlb91FIfpjE+UR8YkbTp5ar44ULQatFaZqQlfz8FHYTooOL5G6gHBHAAAAsB8RBhUfEQ\n\ - YVAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHwf2HMM5TRXvo2S\n\ - QJjsNkiDD5KqiiNjrGVv3UUh+mMT5RHxiRtOnlqvjhQtBq0VpmpCV/PwUdhOig4vkbqAcE\n\ - cAAAAhAMp4pkd0v643EjIkk38DmJYBiXB6ygqGRc60NZxCO6B5AAAAEHVzZXJAZXhhbXBs\n\ - ZS5jb20BAgMEBQYH"; - const MULTILINE_UNPADDED_BIN: &[u8] = &[ - 111, 112, 101, 110, 115, 115, 104, 45, 107, 101, 121, 45, 118, 49, 0, 0, 0, 0, 4, 110, 111, - 110, 101, 0, 0, 0, 4, 110, 111, 110, 101, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 104, 0, 0, 0, - 19, 101, 99, 100, 115, 97, 45, 115, 104, 97, 50, 45, 110, 105, 115, 116, 112, 50, 53, 54, - 0, 0, 0, 8, 110, 105, 115, 116, 112, 50, 53, 54, 0, 0, 0, 65, 4, 124, 31, 216, 115, 12, - 229, 52, 87, 190, 141, 146, 64, 152, 236, 54, 72, 131, 15, 146, 170, 138, 35, 99, 172, 101, - 111, 221, 69, 33, 250, 99, 19, 229, 17, 241, 137, 27, 78, 158, 90, 175, 142, 20, 45, 6, - 173, 21, 166, 106, 66, 87, 243, 240, 81, 216, 78, 138, 14, 47, 145, 186, 128, 112, 71, 0, - 0, 0, 176, 31, 17, 6, 21, 31, 17, 6, 21, 0, 0, 0, 19, 101, 99, 100, 115, 97, 45, 115, 104, - 97, 50, 45, 110, 105, 115, 116, 112, 50, 53, 54, 0, 0, 0, 8, 110, 105, 115, 116, 112, 50, - 53, 54, 0, 0, 0, 65, 4, 124, 31, 216, 115, 12, 229, 52, 87, 190, 141, 146, 64, 152, 236, - 54, 72, 131, 15, 146, 170, 138, 35, 99, 172, 101, 111, 221, 69, 33, 250, 99, 19, 229, 17, - 241, 137, 27, 78, 158, 90, 175, 142, 20, 45, 6, 173, 21, 166, 106, 66, 87, 243, 240, 81, - 216, 78, 138, 14, 47, 145, 186, 128, 112, 71, 0, 0, 0, 33, 0, 202, 120, 166, 71, 116, 191, - 174, 55, 18, 50, 36, 147, 127, 3, 152, 150, 1, 137, 112, 122, 202, 10, 134, 69, 206, 180, - 53, 156, 66, 59, 160, 121, 0, 0, 0, 16, 117, 115, 101, 114, 64, 101, 120, 97, 109, 112, - 108, 101, 46, 99, 111, 109, 1, 2, 3, 4, 5, 6, 7, - ]; + use crate::{test_vectors::*, variant::Variant, Base64, Base64Unpadded, Decoder}; #[test] fn decode_padded() { diff --git a/base64ct/src/encoder.rs b/base64ct/src/encoder.rs new file mode 100644 index 000000000..75c36ca3b --- /dev/null +++ b/base64ct/src/encoder.rs @@ -0,0 +1,192 @@ +//! Buffered Base64 encoder. + +use crate::{ + variant::Variant, + Encoding, + Error::{self, InvalidLength}, +}; +use core::{cmp, marker::PhantomData, str}; + +/// Stateful Base64 encoder with support for buffered, incremental encoding. +/// +/// The `E` type parameter can be any type which impls [`Encoding`] such as +/// [`Base64`] or [`Base64Unpadded`]. +/// +/// Internally it uses a sealed `Variant` trait which is an implementation +/// detail of this crate, and leverages a [blanket impl] of [`Encoding`]. +/// +/// [blanket impl]: ./trait.Encoding.html#impl-Encoding +pub struct Encoder<'o, E: Variant> { + /// Output buffer. + output: &'o mut [u8], + + /// Cursor within the output buffer. + position: usize, + + /// Block buffer used for non-block-aligned data. + block_buffer: BlockBuffer, + + /// Phantom parameter for the Base64 encoding in use. + encoding: PhantomData, +} + +impl<'o, E: Variant> Encoder<'o, E> { + /// Create a new decoder for a byte slice containing contiguous + /// (non-newline-delimited) Base64-encoded data. + pub fn new(output: &'o mut [u8]) -> Result { + if output.is_empty() { + return Err(InvalidLength); + } + + Ok(Self { + output, + position: 0, + block_buffer: BlockBuffer::default(), + encoding: PhantomData, + }) + } + + /// Encode the provided buffer as Base64, writing it to the output buffer. + /// + /// # Returns + /// - `Ok(bytes)` if the expected amount of data was read + /// - `Err(Error::InvalidLength)` if there is insufficient space in the output buffer + pub fn encode(&mut self, mut input: &[u8]) -> Result<(), Error> { + while !input.is_empty() { + // If there's data in the block buffer, fill it + if !self.block_buffer.is_empty() { + self.fill_block_buffer(&mut input)?; + } + + // Attempt to decode a stride of block-aligned data + let in_blocks = input.len() / 3; + let out_blocks = self.remaining().len() / 4; + let blocks = cmp::min(in_blocks, out_blocks); + + if blocks > 0 { + let (in_aligned, in_rem) = input.split_at(blocks * 3); + input = in_rem; + self.perform_encode(in_aligned)?; + } + + // If there's remaining non-aligned data, fill the block buffer + if !input.is_empty() { + self.fill_block_buffer(&mut input)?; + } + } + + Ok(()) + } + + /// Finish encoding data, returning the resulting Base64 as a `str`. + pub fn finish(mut self) -> Result<&'o str, Error> { + if !self.block_buffer.is_empty() { + let buffer_len = self.block_buffer.position; + let block = self.block_buffer.bytes; + self.perform_encode(&block[..buffer_len])?; + } + + Ok(str::from_utf8(&self.output[..self.position])?) + } + + /// Borrow the remaining data in the buffer. + fn remaining(&mut self) -> &mut [u8] { + &mut self.output[self.position..] + } + + /// Fill the block buffer with data, consuming and encoding it when the + /// buffer is full. + fn fill_block_buffer(&mut self, input: &mut &[u8]) -> Result<(), Error> { + self.block_buffer.fill(input); + + if self.block_buffer.is_full() { + let block = self.block_buffer.take(); + self.perform_encode(&block)?; + } + + Ok(()) + } + + /// Perform Base64 encoding operation. + fn perform_encode(&mut self, input: &[u8]) -> Result { + let len = E::encode(input, self.remaining())?.as_bytes().len(); + self.position += len; + Ok(len) + } +} + +/// Base64 encode buffer for a 1-block output. +/// +/// This handles a partial block of data, i.e. data which hasn't been +#[derive(Clone, Default, Debug)] +struct BlockBuffer { + /// 3 decoded bytes to be encoded to a 4-byte Base64-encoded input. + bytes: [u8; Self::SIZE], + + /// Position within the buffer. + position: usize, +} + +impl BlockBuffer { + /// Size of the buffer in bytes: 3-bytes of unencoded input which + /// Base64 encode to 4-bytes of output. + const SIZE: usize = 3; + + /// Fill the remaining space in the buffer with the input data. + fn fill(&mut self, input: &mut &[u8]) { + let len = cmp::min(Self::SIZE - self.position, input.len()); + self.bytes[self.position..][..len].copy_from_slice(&input[..len]); + self.position += len; + *input = &input[len..]; + } + + /// Take the output buffer, resetting the position to 0. + fn take(&mut self) -> [u8; Self::SIZE] { + debug_assert!(self.is_full()); + let result = self.bytes; + *self = Default::default(); + result + } + + /// Is the buffer empty? + fn is_empty(&self) -> bool { + self.position == 0 + } + + /// Is the buffer full? + fn is_full(&self) -> bool { + self.position == Self::SIZE + } +} + +#[cfg(test)] +mod tests { + use crate::{test_vectors::*, variant::Variant, Base64, Base64Unpadded, Encoder}; + + #[test] + fn encode_padded() { + encode_test::(PADDED_BIN, PADDED_BASE64); + } + + #[test] + fn encode_unpadded() { + encode_test::(UNPADDED_BIN, UNPADDED_BASE64); + } + + /// Core functionality of an encoding test. + fn encode_test(input: &[u8], expected: &str) + where + V: Variant, + { + for chunk_size in 1..input.len() { + let mut buffer = [0u8; 1024]; + let mut encoder = Encoder::::new(&mut buffer).unwrap(); + + for chunk in input.chunks(chunk_size) { + encoder.encode(chunk).unwrap(); + } + + assert_eq!(expected, encoder.finish().unwrap()); + } + } +} diff --git a/base64ct/src/errors.rs b/base64ct/src/errors.rs index f74e2ac07..1b43a8d4e 100644 --- a/base64ct/src/errors.rs +++ b/base64ct/src/errors.rs @@ -65,5 +65,12 @@ impl From for Error { } } +impl From for Error { + #[inline] + fn from(_: core::str::Utf8Error) -> Error { + Error::InvalidEncoding + } +} + #[cfg(feature = "std")] impl std::error::Error for Error {} diff --git a/base64ct/src/lib.rs b/base64ct/src/lib.rs index 2eee6a67a..2f4b3882c 100644 --- a/base64ct/src/lib.rs +++ b/base64ct/src/lib.rs @@ -3,7 +3,7 @@ #![doc( html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_root_url = "https://docs.rs/base64ct/1.3.3" + html_root_url = "https://docs.rs/base64ct/1.4.0-pre" )] #![doc = include_str!("../README.md")] #![warn( @@ -72,12 +72,17 @@ extern crate alloc; extern crate std; mod decoder; +mod encoder; mod encoding; mod errors; mod variant; +#[cfg(test)] +mod test_vectors; + pub use crate::{ decoder::Decoder, + encoder::Encoder, encoding::Encoding, errors::{Error, InvalidEncodingError, InvalidLengthError}, variant::{ diff --git a/base64ct/src/test_vectors.rs b/base64ct/src/test_vectors.rs new file mode 100644 index 000000000..61f49581e --- /dev/null +++ b/base64ct/src/test_vectors.rs @@ -0,0 +1,70 @@ +//! Base64 test vectors. + +/// Padded Base64-encoded example +pub(crate) const PADDED_BASE64: &str = + "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHwf2HMM5TRXvo2SQJjsNkiDD5KqiiNjrGVv3UUh+mMT5RHxiRtOnlqvjhQtBq0VpmpCV/PwUdhOig4vkbqAcEc="; +pub(crate) const PADDED_BIN: &[u8] = &[ + 0, 0, 0, 19, 101, 99, 100, 115, 97, 45, 115, 104, 97, 50, 45, 110, 105, 115, 116, 112, 50, 53, + 54, 0, 0, 0, 8, 110, 105, 115, 116, 112, 50, 53, 54, 0, 0, 0, 65, 4, 124, 31, 216, 115, 12, + 229, 52, 87, 190, 141, 146, 64, 152, 236, 54, 72, 131, 15, 146, 170, 138, 35, 99, 172, 101, + 111, 221, 69, 33, 250, 99, 19, 229, 17, 241, 137, 27, 78, 158, 90, 175, 142, 20, 45, 6, 173, + 21, 166, 106, 66, 87, 243, 240, 81, 216, 78, 138, 14, 47, 145, 186, 128, 112, 71, +]; + +/// Unpadded Base64-encoded example +pub(crate) const UNPADDED_BASE64: &str = + "AAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqti"; +pub(crate) const UNPADDED_BIN: &[u8] = &[ + 0, 0, 0, 11, 115, 115, 104, 45, 101, 100, 50, 53, 53, 49, 57, 0, 0, 0, 32, 179, 62, 174, 243, + 126, 162, 223, 124, 170, 1, 13, 239, 222, 163, 78, 36, 31, 101, 241, 181, 41, 164, 244, 62, + 209, 67, 39, 245, 197, 74, 171, 98, +]; + +/// Padded multi-line Base64 example (from the `ssh-key` crate's `id_ed25519`) +pub(crate) const MULTILINE_PADDED_BASE64: &str = + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n\ + QyNTUxOQAAACCzPq7zfqLffKoBDe/eo04kH2XxtSmk9D7RQyf1xUqrYgAAAJgAIAxdACAM\n\ + XQAAAAtzc2gtZWQyNTUxOQAAACCzPq7zfqLffKoBDe/eo04kH2XxtSmk9D7RQyf1xUqrYg\n\ + AAAEC2BsIi0QwW2uFscKTUUXNHLsYX4FxlaSDSblbAj7WR7bM+rvN+ot98qgEN796jTiQf\n\ + ZfG1KaT0PtFDJ/XFSqtiAAAAEHVzZXJAZXhhbXBsZS5jb20BAgMEBQ=="; +pub(crate) const MULTILINE_PADDED_BIN: &[u8] = &[ + 111, 112, 101, 110, 115, 115, 104, 45, 107, 101, 121, 45, 118, 49, 0, 0, 0, 0, 4, 110, 111, + 110, 101, 0, 0, 0, 4, 110, 111, 110, 101, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 51, 0, 0, 0, 11, + 115, 115, 104, 45, 101, 100, 50, 53, 53, 49, 57, 0, 0, 0, 32, 179, 62, 174, 243, 126, 162, 223, + 124, 170, 1, 13, 239, 222, 163, 78, 36, 31, 101, 241, 181, 41, 164, 244, 62, 209, 67, 39, 245, + 197, 74, 171, 98, 0, 0, 0, 152, 0, 32, 12, 93, 0, 32, 12, 93, 0, 0, 0, 11, 115, 115, 104, 45, + 101, 100, 50, 53, 53, 49, 57, 0, 0, 0, 32, 179, 62, 174, 243, 126, 162, 223, 124, 170, 1, 13, + 239, 222, 163, 78, 36, 31, 101, 241, 181, 41, 164, 244, 62, 209, 67, 39, 245, 197, 74, 171, 98, + 0, 0, 0, 64, 182, 6, 194, 34, 209, 12, 22, 218, 225, 108, 112, 164, 212, 81, 115, 71, 46, 198, + 23, 224, 92, 101, 105, 32, 210, 110, 86, 192, 143, 181, 145, 237, 179, 62, 174, 243, 126, 162, + 223, 124, 170, 1, 13, 239, 222, 163, 78, 36, 31, 101, 241, 181, 41, 164, 244, 62, 209, 67, 39, + 245, 197, 74, 171, 98, 0, 0, 0, 16, 117, 115, 101, 114, 64, 101, 120, 97, 109, 112, 108, 101, + 46, 99, 111, 109, 1, 2, 3, 4, 5, +]; + +/// Unpadded multi-line Base64 example (from the `ssh-key` crate's `id_ecdsa_p256`). +pub(crate) const MULTILINE_UNPADDED_BASE64: &str = + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS\n\ + 1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQR8H9hzDOU0V76NkkCY7DZIgw+Sqooj\n\ + Y6xlb91FIfpjE+UR8YkbTp5ar44ULQatFaZqQlfz8FHYTooOL5G6gHBHAAAAsB8RBhUfEQ\n\ + YVAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHwf2HMM5TRXvo2S\n\ + QJjsNkiDD5KqiiNjrGVv3UUh+mMT5RHxiRtOnlqvjhQtBq0VpmpCV/PwUdhOig4vkbqAcE\n\ + cAAAAhAMp4pkd0v643EjIkk38DmJYBiXB6ygqGRc60NZxCO6B5AAAAEHVzZXJAZXhhbXBs\n\ + ZS5jb20BAgMEBQYH"; +pub(crate) const MULTILINE_UNPADDED_BIN: &[u8] = &[ + 111, 112, 101, 110, 115, 115, 104, 45, 107, 101, 121, 45, 118, 49, 0, 0, 0, 0, 4, 110, 111, + 110, 101, 0, 0, 0, 4, 110, 111, 110, 101, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 104, 0, 0, 0, 19, + 101, 99, 100, 115, 97, 45, 115, 104, 97, 50, 45, 110, 105, 115, 116, 112, 50, 53, 54, 0, 0, 0, + 8, 110, 105, 115, 116, 112, 50, 53, 54, 0, 0, 0, 65, 4, 124, 31, 216, 115, 12, 229, 52, 87, + 190, 141, 146, 64, 152, 236, 54, 72, 131, 15, 146, 170, 138, 35, 99, 172, 101, 111, 221, 69, + 33, 250, 99, 19, 229, 17, 241, 137, 27, 78, 158, 90, 175, 142, 20, 45, 6, 173, 21, 166, 106, + 66, 87, 243, 240, 81, 216, 78, 138, 14, 47, 145, 186, 128, 112, 71, 0, 0, 0, 176, 31, 17, 6, + 21, 31, 17, 6, 21, 0, 0, 0, 19, 101, 99, 100, 115, 97, 45, 115, 104, 97, 50, 45, 110, 105, 115, + 116, 112, 50, 53, 54, 0, 0, 0, 8, 110, 105, 115, 116, 112, 50, 53, 54, 0, 0, 0, 65, 4, 124, 31, + 216, 115, 12, 229, 52, 87, 190, 141, 146, 64, 152, 236, 54, 72, 131, 15, 146, 170, 138, 35, 99, + 172, 101, 111, 221, 69, 33, 250, 99, 19, 229, 17, 241, 137, 27, 78, 158, 90, 175, 142, 20, 45, + 6, 173, 21, 166, 106, 66, 87, 243, 240, 81, 216, 78, 138, 14, 47, 145, 186, 128, 112, 71, 0, 0, + 0, 33, 0, 202, 120, 166, 71, 116, 191, 174, 55, 18, 50, 36, 147, 127, 3, 152, 150, 1, 137, 112, + 122, 202, 10, 134, 69, 206, 180, 53, 156, 66, 59, 160, 121, 0, 0, 0, 16, 117, 115, 101, 114, + 64, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 1, 2, 3, 4, 5, 6, 7, +]; diff --git a/base64ct/tests/proptests.rs b/base64ct/tests/proptests.rs index 459e5c0d7..7ce36f608 100644 --- a/base64ct/tests/proptests.rs +++ b/base64ct/tests/proptests.rs @@ -9,6 +9,9 @@ use std::iter; /// Incremental Base64 decoder. type Decoder<'a> = base64ct::Decoder<'a, Base64ct>; +/// Incremental Base64 encoder. +type Encoder<'a> = base64ct::Encoder<'a, Base64ct>; + proptest! { /// Ensure `base64ct` decodes data encoded by `base64` ref crate #[test] @@ -47,7 +50,7 @@ proptest! { line_width in 4..128usize, chunk_size in 1..256usize ) { - for line_ending in ["\n"] { //["\r", "\n", "\r\n"] { + for line_ending in ["\r", "\n", "\r\n"] { let encoded = base64::encode(&bytes); let mut encoded_wrapped = Vec::new(); @@ -113,4 +116,24 @@ proptest! { let encoded_ref = base64::encode(&bytes); prop_assert_eq!(encoded_ct, encoded_ref); } + + /// Ensure that `base64ct`'s incremental encoder is able to encode randomly + /// generated inputs which match what's encoded by the `base64` ref crate + #[test] + fn encode_incremental(bytes in bytes_regex(".{1,256}").unwrap(), chunk_size in 1..256usize) { + let expected = base64::encode(&bytes); + let chunk_size = match chunk_size % bytes.len() { + 0 => 1, + n => n + }; + + let mut buffer = [0u8; 1024]; + let mut encoder = Encoder::new(&mut buffer).unwrap(); + + for chunk in bytes.chunks(chunk_size) { + encoder.encode(chunk).unwrap(); + } + + prop_assert_eq!(expected, encoder.finish().unwrap()); + } } diff --git a/der/Cargo.toml b/der/Cargo.toml index ba6d08f7f..cdedac4a9 100644 --- a/der/Cargo.toml +++ b/der/Cargo.toml @@ -18,7 +18,7 @@ rust-version = "1.57" [dependencies] const-oid = { version = "0.8", optional = true, path = "../const-oid" } der_derive = { version = "=0.6.0-pre.0", optional = true, path = "derive" } -pem-rfc7468 = { version = "0.3", optional = true, path = "../pem-rfc7468" } +pem-rfc7468 = { version = "=0.4.0-pre", optional = true, path = "../pem-rfc7468" } time = { version = "0.3", optional = true, default-features = false } [dev-dependencies] diff --git a/pem-rfc7468/Cargo.toml b/pem-rfc7468/Cargo.toml index bbf7db07a..90c8134aa 100644 --- a/pem-rfc7468/Cargo.toml +++ b/pem-rfc7468/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pem-rfc7468" -version = "0.3.1" # Also update html_root_url in lib.rs when bumping this +version = "0.4.0-pre" # Also update html_root_url in lib.rs when bumping this description = """ PEM Encoding (RFC 7468) for PKIX, PKCS, and CMS Structures, implementing a strict subset of the original Privacy-Enhanced Mail encoding intended @@ -18,7 +18,7 @@ edition = "2021" rust-version = "1.56" [dependencies] -base64ct = { version = "1", path = "../base64ct" } +base64ct = { version = "=1.4.0-pre", path = "../base64ct" } [features] alloc = [] diff --git a/spki/Cargo.toml b/spki/Cargo.toml index 6ac8e988c..283609040 100644 --- a/spki/Cargo.toml +++ b/spki/Cargo.toml @@ -19,7 +19,7 @@ der = { version = "=0.6.0-pre.0", features = ["oid"], path = "../der" } # Optional dependencies sha2 = { version = "0.10", optional = true, default-features = false } -base64ct = { version = "1", path = "../base64ct", optional = true, default-features = false } +base64ct = { version = "=1.4.0-pre", path = "../base64ct", optional = true, default-features = false } [dev-dependencies] hex-literal = "0.3" diff --git a/ssh-key/Cargo.toml b/ssh-key/Cargo.toml index 4f3778013..740151f90 100644 --- a/ssh-key/Cargo.toml +++ b/ssh-key/Cargo.toml @@ -16,7 +16,7 @@ edition = "2021" rust-version = "1.57" [dependencies] -base64ct = { version = "1.3.3", path = "../base64ct" } +base64ct = { version = "=1.4.0-pre", path = "../base64ct" } zeroize = { version = "1", default-features = false } # optional dependencies