| use crate::{Config, PAD_BYTE}; |
| #[cfg(any(feature = "alloc", feature = "std", test))] |
| use crate::{chunked_encoder, STANDARD}; |
| #[cfg(any(feature = "alloc", feature = "std", test))] |
| use alloc::{string::String, vec}; |
| use core::convert::TryInto; |
| |
| ///Encode arbitrary octets as base64. |
| ///Returns a String. |
| ///Convenience for `encode_config(input, base64::STANDARD);`. |
| /// |
| ///# Example |
| /// |
| ///```rust |
| ///extern crate base64; |
| /// |
| ///fn main() { |
| /// let b64 = base64::encode(b"hello world"); |
| /// println!("{}", b64); |
| ///} |
| ///``` |
| #[cfg(any(feature = "alloc", feature = "std", test))] |
| pub fn encode<T: AsRef<[u8]>>(input: T) -> String { |
| encode_config(input, STANDARD) |
| } |
| |
| ///Encode arbitrary octets as base64. |
| ///Returns a String. |
| /// |
| ///# Example |
| /// |
| ///```rust |
| ///extern crate base64; |
| /// |
| ///fn main() { |
| /// let b64 = base64::encode_config(b"hello world~", base64::STANDARD); |
| /// println!("{}", b64); |
| /// |
| /// let b64_url = base64::encode_config(b"hello internet~", base64::URL_SAFE); |
| /// println!("{}", b64_url); |
| ///} |
| ///``` |
| #[cfg(any(feature = "alloc", feature = "std", test))] |
| pub fn encode_config<T: AsRef<[u8]>>(input: T, config: Config) -> String { |
| let mut buf = match encoded_size(input.as_ref().len(), config) { |
| Some(n) => vec![0; n], |
| None => panic!("integer overflow when calculating buffer size"), |
| }; |
| |
| encode_with_padding(input.as_ref(), config, buf.len(), &mut buf[..]); |
| |
| String::from_utf8(buf).expect("Invalid UTF8") |
| } |
| |
| ///Encode arbitrary octets as base64. |
| ///Writes into the supplied output buffer, which will grow the buffer if needed. |
| /// |
| ///# Example |
| /// |
| ///```rust |
| ///extern crate base64; |
| /// |
| ///fn main() { |
| /// let mut buf = String::new(); |
| /// base64::encode_config_buf(b"hello world~", base64::STANDARD, &mut buf); |
| /// println!("{}", buf); |
| /// |
| /// buf.clear(); |
| /// base64::encode_config_buf(b"hello internet~", base64::URL_SAFE, &mut buf); |
| /// println!("{}", buf); |
| ///} |
| ///``` |
| #[cfg(any(feature = "alloc", feature = "std", test))] |
| pub fn encode_config_buf<T: AsRef<[u8]>>(input: T, config: Config, buf: &mut String) { |
| let input_bytes = input.as_ref(); |
| |
| { |
| let mut sink = chunked_encoder::StringSink::new(buf); |
| let encoder = chunked_encoder::ChunkedEncoder::new(config); |
| |
| encoder |
| .encode(input_bytes, &mut sink) |
| .expect("Writing to a String shouldn't fail") |
| } |
| } |
| |
| /// Encode arbitrary octets as base64. |
| /// Writes into the supplied output buffer. |
| /// |
| /// This is useful if you wish to avoid allocation entirely (e.g. encoding into a stack-resident |
| /// or statically-allocated buffer). |
| /// |
| /// # Panics |
| /// |
| /// If `output` is too small to hold the encoded version of `input`, a panic will result. |
| /// |
| /// # Example |
| /// |
| /// ```rust |
| /// extern crate base64; |
| /// |
| /// fn main() { |
| /// let s = b"hello internet!"; |
| /// let mut buf = Vec::new(); |
| /// // make sure we'll have a slice big enough for base64 + padding |
| /// buf.resize(s.len() * 4 / 3 + 4, 0); |
| /// |
| /// let bytes_written = base64::encode_config_slice(s, |
| /// base64::STANDARD, &mut buf); |
| /// |
| /// // shorten our vec down to just what was written |
| /// buf.resize(bytes_written, 0); |
| /// |
| /// assert_eq!(s, base64::decode(&buf).unwrap().as_slice()); |
| /// } |
| /// ``` |
| pub fn encode_config_slice<T: AsRef<[u8]>>(input: T, config: Config, output: &mut [u8]) -> usize { |
| let input_bytes = input.as_ref(); |
| |
| let encoded_size = encoded_size(input_bytes.len(), config) |
| .expect("usize overflow when calculating buffer size"); |
| |
| let mut b64_output = &mut output[0..encoded_size]; |
| |
| encode_with_padding(&input_bytes, config, encoded_size, &mut b64_output); |
| |
| encoded_size |
| } |
| |
| /// B64-encode and pad (if configured). |
| /// |
| /// This helper exists to avoid recalculating encoded_size, which is relatively expensive on short |
| /// inputs. |
| /// |
| /// `encoded_size` is the encoded size calculated for `input`. |
| /// |
| /// `output` must be of size `encoded_size`. |
| /// |
| /// All bytes in `output` will be written to since it is exactly the size of the output. |
| fn encode_with_padding(input: &[u8], config: Config, encoded_size: usize, output: &mut [u8]) { |
| debug_assert_eq!(encoded_size, output.len()); |
| |
| let b64_bytes_written = encode_to_slice(input, output, config.char_set.encode_table()); |
| |
| let padding_bytes = if config.pad { |
| add_padding(input.len(), &mut output[b64_bytes_written..]) |
| } else { |
| 0 |
| }; |
| |
| let encoded_bytes = b64_bytes_written |
| .checked_add(padding_bytes) |
| .expect("usize overflow when calculating b64 length"); |
| |
| debug_assert_eq!(encoded_size, encoded_bytes); |
| } |
| |
| #[inline] |
| fn read_u64(s: &[u8]) -> u64 { |
| u64::from_be_bytes(s[..8].try_into().unwrap()) |
| } |
| |
| /// Encode input bytes to utf8 base64 bytes. Does not pad. |
| /// `output` must be long enough to hold the encoded `input` without padding. |
| /// Returns the number of bytes written. |
| #[inline] |
| pub fn encode_to_slice(input: &[u8], output: &mut [u8], encode_table: &[u8; 64]) -> usize { |
| let mut input_index: usize = 0; |
| |
| const BLOCKS_PER_FAST_LOOP: usize = 4; |
| const LOW_SIX_BITS: u64 = 0x3F; |
| |
| // we read 8 bytes at a time (u64) but only actually consume 6 of those bytes. Thus, we need |
| // 2 trailing bytes to be available to read.. |
| let last_fast_index = input.len().saturating_sub(BLOCKS_PER_FAST_LOOP * 6 + 2); |
| let mut output_index = 0; |
| |
| if last_fast_index > 0 { |
| while input_index <= last_fast_index { |
| // Major performance wins from letting the optimizer do the bounds check once, mostly |
| // on the output side |
| let input_chunk = &input[input_index..(input_index + (BLOCKS_PER_FAST_LOOP * 6 + 2))]; |
| let output_chunk = &mut output[output_index..(output_index + BLOCKS_PER_FAST_LOOP * 8)]; |
| |
| // Hand-unrolling for 32 vs 16 or 8 bytes produces yields performance about equivalent |
| // to unsafe pointer code on a Xeon E5-1650v3. 64 byte unrolling was slightly better for |
| // large inputs but significantly worse for 50-byte input, unsurprisingly. I suspect |
| // that it's a not uncommon use case to encode smallish chunks of data (e.g. a 64-byte |
| // SHA-512 digest), so it would be nice if that fit in the unrolled loop at least once. |
| // Plus, single-digit percentage performance differences might well be quite different |
| // on different hardware. |
| |
| let input_u64 = read_u64(&input_chunk[0..]); |
| |
| output_chunk[0] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize]; |
| output_chunk[1] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize]; |
| output_chunk[2] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize]; |
| output_chunk[3] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize]; |
| output_chunk[4] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize]; |
| output_chunk[5] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize]; |
| output_chunk[6] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize]; |
| output_chunk[7] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize]; |
| |
| let input_u64 = read_u64(&input_chunk[6..]); |
| |
| output_chunk[8] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize]; |
| output_chunk[9] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize]; |
| output_chunk[10] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize]; |
| output_chunk[11] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize]; |
| output_chunk[12] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize]; |
| output_chunk[13] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize]; |
| output_chunk[14] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize]; |
| output_chunk[15] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize]; |
| |
| let input_u64 = read_u64(&input_chunk[12..]); |
| |
| output_chunk[16] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize]; |
| output_chunk[17] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize]; |
| output_chunk[18] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize]; |
| output_chunk[19] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize]; |
| output_chunk[20] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize]; |
| output_chunk[21] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize]; |
| output_chunk[22] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize]; |
| output_chunk[23] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize]; |
| |
| let input_u64 = read_u64(&input_chunk[18..]); |
| |
| output_chunk[24] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize]; |
| output_chunk[25] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize]; |
| output_chunk[26] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize]; |
| output_chunk[27] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize]; |
| output_chunk[28] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize]; |
| output_chunk[29] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize]; |
| output_chunk[30] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize]; |
| output_chunk[31] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize]; |
| |
| output_index += BLOCKS_PER_FAST_LOOP * 8; |
| input_index += BLOCKS_PER_FAST_LOOP * 6; |
| } |
| } |
| |
| // Encode what's left after the fast loop. |
| |
| const LOW_SIX_BITS_U8: u8 = 0x3F; |
| |
| let rem = input.len() % 3; |
| let start_of_rem = input.len() - rem; |
| |
| // start at the first index not handled by fast loop, which may be 0. |
| |
| while input_index < start_of_rem { |
| let input_chunk = &input[input_index..(input_index + 3)]; |
| let output_chunk = &mut output[output_index..(output_index + 4)]; |
| |
| output_chunk[0] = encode_table[(input_chunk[0] >> 2) as usize]; |
| output_chunk[1] = |
| encode_table[((input_chunk[0] << 4 | input_chunk[1] >> 4) & LOW_SIX_BITS_U8) as usize]; |
| output_chunk[2] = |
| encode_table[((input_chunk[1] << 2 | input_chunk[2] >> 6) & LOW_SIX_BITS_U8) as usize]; |
| output_chunk[3] = encode_table[(input_chunk[2] & LOW_SIX_BITS_U8) as usize]; |
| |
| input_index += 3; |
| output_index += 4; |
| } |
| |
| if rem == 2 { |
| output[output_index] = encode_table[(input[start_of_rem] >> 2) as usize]; |
| output[output_index + 1] = encode_table[((input[start_of_rem] << 4 |
| | input[start_of_rem + 1] >> 4) |
| & LOW_SIX_BITS_U8) as usize]; |
| output[output_index + 2] = |
| encode_table[((input[start_of_rem + 1] << 2) & LOW_SIX_BITS_U8) as usize]; |
| output_index += 3; |
| } else if rem == 1 { |
| output[output_index] = encode_table[(input[start_of_rem] >> 2) as usize]; |
| output[output_index + 1] = |
| encode_table[((input[start_of_rem] << 4) & LOW_SIX_BITS_U8) as usize]; |
| output_index += 2; |
| } |
| |
| output_index |
| } |
| |
| /// calculate the base64 encoded string size, including padding if appropriate |
| pub fn encoded_size(bytes_len: usize, config: Config) -> Option<usize> { |
| let rem = bytes_len % 3; |
| |
| let complete_input_chunks = bytes_len / 3; |
| let complete_chunk_output = complete_input_chunks.checked_mul(4); |
| |
| if rem > 0 { |
| if config.pad { |
| complete_chunk_output.and_then(|c| c.checked_add(4)) |
| } else { |
| let encoded_rem = match rem { |
| 1 => 2, |
| 2 => 3, |
| _ => unreachable!("Impossible remainder"), |
| }; |
| complete_chunk_output.and_then(|c| c.checked_add(encoded_rem)) |
| } |
| } else { |
| complete_chunk_output |
| } |
| } |
| |
| /// Write padding characters. |
| /// `output` is the slice where padding should be written, of length at least 2. |
| /// |
| /// Returns the number of padding bytes written. |
| pub fn add_padding(input_len: usize, output: &mut [u8]) -> usize { |
| let rem = input_len % 3; |
| let mut bytes_written = 0; |
| for _ in 0..((3 - rem) % 3) { |
| output[bytes_written] = PAD_BYTE; |
| bytes_written += 1; |
| } |
| |
| bytes_written |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use crate::{ |
| decode::decode_config_buf, |
| tests::{assert_encode_sanity, random_config}, |
| Config, STANDARD, URL_SAFE_NO_PAD, |
| }; |
| |
| use rand::{ |
| distributions::{Distribution, Uniform}, |
| FromEntropy, Rng, |
| }; |
| use std; |
| use std::str; |
| |
| #[test] |
| fn encoded_size_correct_standard() { |
| assert_encoded_length(0, 0, STANDARD); |
| |
| assert_encoded_length(1, 4, STANDARD); |
| assert_encoded_length(2, 4, STANDARD); |
| assert_encoded_length(3, 4, STANDARD); |
| |
| assert_encoded_length(4, 8, STANDARD); |
| assert_encoded_length(5, 8, STANDARD); |
| assert_encoded_length(6, 8, STANDARD); |
| |
| assert_encoded_length(7, 12, STANDARD); |
| assert_encoded_length(8, 12, STANDARD); |
| assert_encoded_length(9, 12, STANDARD); |
| |
| assert_encoded_length(54, 72, STANDARD); |
| |
| assert_encoded_length(55, 76, STANDARD); |
| assert_encoded_length(56, 76, STANDARD); |
| assert_encoded_length(57, 76, STANDARD); |
| |
| assert_encoded_length(58, 80, STANDARD); |
| } |
| |
| #[test] |
| fn encoded_size_correct_no_pad() { |
| assert_encoded_length(0, 0, URL_SAFE_NO_PAD); |
| |
| assert_encoded_length(1, 2, URL_SAFE_NO_PAD); |
| assert_encoded_length(2, 3, URL_SAFE_NO_PAD); |
| assert_encoded_length(3, 4, URL_SAFE_NO_PAD); |
| |
| assert_encoded_length(4, 6, URL_SAFE_NO_PAD); |
| assert_encoded_length(5, 7, URL_SAFE_NO_PAD); |
| assert_encoded_length(6, 8, URL_SAFE_NO_PAD); |
| |
| assert_encoded_length(7, 10, URL_SAFE_NO_PAD); |
| assert_encoded_length(8, 11, URL_SAFE_NO_PAD); |
| assert_encoded_length(9, 12, URL_SAFE_NO_PAD); |
| |
| assert_encoded_length(54, 72, URL_SAFE_NO_PAD); |
| |
| assert_encoded_length(55, 74, URL_SAFE_NO_PAD); |
| assert_encoded_length(56, 75, URL_SAFE_NO_PAD); |
| assert_encoded_length(57, 76, URL_SAFE_NO_PAD); |
| |
| assert_encoded_length(58, 78, URL_SAFE_NO_PAD); |
| } |
| |
| #[test] |
| fn encoded_size_overflow() { |
| assert_eq!(None, encoded_size(std::usize::MAX, STANDARD)); |
| } |
| |
| #[test] |
| fn encode_config_buf_into_nonempty_buffer_doesnt_clobber_prefix() { |
| let mut orig_data = Vec::new(); |
| let mut prefix = String::new(); |
| let mut encoded_data_no_prefix = String::new(); |
| let mut encoded_data_with_prefix = String::new(); |
| let mut decoded = Vec::new(); |
| |
| let prefix_len_range = Uniform::new(0, 1000); |
| let input_len_range = Uniform::new(0, 1000); |
| |
| let mut rng = rand::rngs::SmallRng::from_entropy(); |
| |
| for _ in 0..10_000 { |
| orig_data.clear(); |
| prefix.clear(); |
| encoded_data_no_prefix.clear(); |
| encoded_data_with_prefix.clear(); |
| decoded.clear(); |
| |
| let input_len = input_len_range.sample(&mut rng); |
| |
| for _ in 0..input_len { |
| orig_data.push(rng.gen()); |
| } |
| |
| let prefix_len = prefix_len_range.sample(&mut rng); |
| for _ in 0..prefix_len { |
| // getting convenient random single-byte printable chars that aren't base64 is |
| // annoying |
| prefix.push('#'); |
| } |
| encoded_data_with_prefix.push_str(&prefix); |
| |
| let config = random_config(&mut rng); |
| encode_config_buf(&orig_data, config, &mut encoded_data_no_prefix); |
| encode_config_buf(&orig_data, config, &mut encoded_data_with_prefix); |
| |
| assert_eq!( |
| encoded_data_no_prefix.len() + prefix_len, |
| encoded_data_with_prefix.len() |
| ); |
| assert_encode_sanity(&encoded_data_no_prefix, config, input_len); |
| assert_encode_sanity(&encoded_data_with_prefix[prefix_len..], config, input_len); |
| |
| // append plain encode onto prefix |
| prefix.push_str(&mut encoded_data_no_prefix); |
| |
| assert_eq!(prefix, encoded_data_with_prefix); |
| |
| decode_config_buf(&encoded_data_no_prefix, config, &mut decoded).unwrap(); |
| assert_eq!(orig_data, decoded); |
| } |
| } |
| |
| #[test] |
| fn encode_config_slice_into_nonempty_buffer_doesnt_clobber_suffix() { |
| let mut orig_data = Vec::new(); |
| let mut encoded_data = Vec::new(); |
| let mut encoded_data_original_state = Vec::new(); |
| let mut decoded = Vec::new(); |
| |
| let input_len_range = Uniform::new(0, 1000); |
| |
| let mut rng = rand::rngs::SmallRng::from_entropy(); |
| |
| for _ in 0..10_000 { |
| orig_data.clear(); |
| encoded_data.clear(); |
| encoded_data_original_state.clear(); |
| decoded.clear(); |
| |
| let input_len = input_len_range.sample(&mut rng); |
| |
| for _ in 0..input_len { |
| orig_data.push(rng.gen()); |
| } |
| |
| // plenty of existing garbage in the encoded buffer |
| for _ in 0..10 * input_len { |
| encoded_data.push(rng.gen()); |
| } |
| |
| encoded_data_original_state.extend_from_slice(&encoded_data); |
| |
| let config = random_config(&mut rng); |
| |
| let encoded_size = encoded_size(input_len, config).unwrap(); |
| |
| assert_eq!( |
| encoded_size, |
| encode_config_slice(&orig_data, config, &mut encoded_data) |
| ); |
| |
| assert_encode_sanity( |
| std::str::from_utf8(&encoded_data[0..encoded_size]).unwrap(), |
| config, |
| input_len, |
| ); |
| |
| assert_eq!( |
| &encoded_data[encoded_size..], |
| &encoded_data_original_state[encoded_size..] |
| ); |
| |
| decode_config_buf(&encoded_data[0..encoded_size], config, &mut decoded).unwrap(); |
| assert_eq!(orig_data, decoded); |
| } |
| } |
| |
| #[test] |
| fn encode_config_slice_fits_into_precisely_sized_slice() { |
| let mut orig_data = Vec::new(); |
| let mut encoded_data = Vec::new(); |
| let mut decoded = Vec::new(); |
| |
| let input_len_range = Uniform::new(0, 1000); |
| |
| let mut rng = rand::rngs::SmallRng::from_entropy(); |
| |
| for _ in 0..10_000 { |
| orig_data.clear(); |
| encoded_data.clear(); |
| decoded.clear(); |
| |
| let input_len = input_len_range.sample(&mut rng); |
| |
| for _ in 0..input_len { |
| orig_data.push(rng.gen()); |
| } |
| |
| let config = random_config(&mut rng); |
| |
| let encoded_size = encoded_size(input_len, config).unwrap(); |
| |
| encoded_data.resize(encoded_size, 0); |
| |
| assert_eq!( |
| encoded_size, |
| encode_config_slice(&orig_data, config, &mut encoded_data) |
| ); |
| |
| assert_encode_sanity( |
| std::str::from_utf8(&encoded_data[0..encoded_size]).unwrap(), |
| config, |
| input_len, |
| ); |
| |
| decode_config_buf(&encoded_data[0..encoded_size], config, &mut decoded).unwrap(); |
| assert_eq!(orig_data, decoded); |
| } |
| } |
| |
| #[test] |
| fn encode_to_slice_random_valid_utf8() { |
| let mut input = Vec::new(); |
| let mut output = Vec::new(); |
| |
| let input_len_range = Uniform::new(0, 1000); |
| |
| let mut rng = rand::rngs::SmallRng::from_entropy(); |
| |
| for _ in 0..10_000 { |
| input.clear(); |
| output.clear(); |
| |
| let input_len = input_len_range.sample(&mut rng); |
| |
| for _ in 0..input_len { |
| input.push(rng.gen()); |
| } |
| |
| let config = random_config(&mut rng); |
| |
| // fill up the output buffer with garbage |
| let encoded_size = encoded_size(input_len, config).unwrap(); |
| for _ in 0..encoded_size { |
| output.push(rng.gen()); |
| } |
| |
| let orig_output_buf = output.to_vec(); |
| |
| let bytes_written = |
| encode_to_slice(&input, &mut output, config.char_set.encode_table()); |
| |
| // make sure the part beyond bytes_written is the same garbage it was before |
| assert_eq!(orig_output_buf[bytes_written..], output[bytes_written..]); |
| |
| // make sure the encoded bytes are UTF-8 |
| let _ = str::from_utf8(&output[0..bytes_written]).unwrap(); |
| } |
| } |
| |
| #[test] |
| fn encode_with_padding_random_valid_utf8() { |
| let mut input = Vec::new(); |
| let mut output = Vec::new(); |
| |
| let input_len_range = Uniform::new(0, 1000); |
| |
| let mut rng = rand::rngs::SmallRng::from_entropy(); |
| |
| for _ in 0..10_000 { |
| input.clear(); |
| output.clear(); |
| |
| let input_len = input_len_range.sample(&mut rng); |
| |
| for _ in 0..input_len { |
| input.push(rng.gen()); |
| } |
| |
| let config = random_config(&mut rng); |
| |
| // fill up the output buffer with garbage |
| let encoded_size = encoded_size(input_len, config).unwrap(); |
| for _ in 0..encoded_size + 1000 { |
| output.push(rng.gen()); |
| } |
| |
| let orig_output_buf = output.to_vec(); |
| |
| encode_with_padding(&input, config, encoded_size, &mut output[0..encoded_size]); |
| |
| // make sure the part beyond b64 is the same garbage it was before |
| assert_eq!(orig_output_buf[encoded_size..], output[encoded_size..]); |
| |
| // make sure the encoded bytes are UTF-8 |
| let _ = str::from_utf8(&output[0..encoded_size]).unwrap(); |
| } |
| } |
| |
| #[test] |
| fn add_padding_random_valid_utf8() { |
| let mut output = Vec::new(); |
| |
| let mut rng = rand::rngs::SmallRng::from_entropy(); |
| |
| // cover our bases for length % 3 |
| for input_len in 0..10 { |
| output.clear(); |
| |
| // fill output with random |
| for _ in 0..10 { |
| output.push(rng.gen()); |
| } |
| |
| let orig_output_buf = output.to_vec(); |
| |
| let bytes_written = add_padding(input_len, &mut output); |
| |
| // make sure the part beyond bytes_written is the same garbage it was before |
| assert_eq!(orig_output_buf[bytes_written..], output[bytes_written..]); |
| |
| // make sure the encoded bytes are UTF-8 |
| let _ = str::from_utf8(&output[0..bytes_written]).unwrap(); |
| } |
| } |
| |
| fn assert_encoded_length(input_len: usize, encoded_len: usize, config: Config) { |
| assert_eq!(encoded_len, encoded_size(input_len, config).unwrap()); |
| |
| let mut bytes: Vec<u8> = Vec::new(); |
| let mut rng = rand::rngs::SmallRng::from_entropy(); |
| |
| for _ in 0..input_len { |
| bytes.push(rng.gen()); |
| } |
| |
| let encoded = encode_config(&bytes, config); |
| assert_encode_sanity(&encoded, config, input_len); |
| |
| assert_eq!(encoded_len, encoded.len()); |
| } |
| |
| #[test] |
| fn encode_imap() { |
| assert_eq!( |
| encode_config(b"\xFB\xFF", crate::IMAP_MUTF7), |
| encode_config(b"\xFB\xFF", crate::STANDARD_NO_PAD).replace("/", ",") |
| ); |
| } |
| } |