blob: eb4f8e32a2e87154cfe80b58ebb6caea18fca96f [file] [log] [blame]
extern crate rand;
use super::*;
use super::line_wrap::line_wrap_parameters;
use self::rand::Rng;
use self::rand::distributions::{IndependentSample, Range};
#[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_no_wrap() {
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_correct_mime() {
assert_encoded_length(0, 0, MIME);
assert_encoded_length(1, 4, MIME);
assert_encoded_length(2, 4, MIME);
assert_encoded_length(3, 4, MIME);
assert_encoded_length(4, 8, MIME);
assert_encoded_length(5, 8, MIME);
assert_encoded_length(6, 8, MIME);
assert_encoded_length(7, 12, MIME);
assert_encoded_length(8, 12, MIME);
assert_encoded_length(9, 12, MIME);
assert_encoded_length(54, 72, MIME);
assert_encoded_length(55, 76, MIME);
assert_encoded_length(56, 76, MIME);
assert_encoded_length(57, 76, MIME);
assert_encoded_length(58, 82, MIME);
assert_encoded_length(59, 82, MIME);
assert_encoded_length(60, 82, MIME);
}
#[test]
fn encoded_size_correct_lf_pad() {
let config = Config::new(
CharacterSet::Standard,
true,
false,
LineWrap::Wrap(76, LineEnding::LF),
);
assert_encoded_length(0, 0, config);
assert_encoded_length(1, 4, config);
assert_encoded_length(2, 4, config);
assert_encoded_length(3, 4, config);
assert_encoded_length(4, 8, config);
assert_encoded_length(5, 8, config);
assert_encoded_length(6, 8, config);
assert_encoded_length(7, 12, config);
assert_encoded_length(8, 12, config);
assert_encoded_length(9, 12, config);
assert_encoded_length(54, 72, config);
assert_encoded_length(55, 76, config);
assert_encoded_length(56, 76, config);
assert_encoded_length(57, 76, config);
// one fewer than MIME
assert_encoded_length(58, 81, config);
assert_encoded_length(59, 81, config);
assert_encoded_length(60, 81, config);
}
#[test]
fn encoded_size_overflow() {
assert_eq!(None, encoded_size(std::usize::MAX, &STANDARD));
}
#[test]
fn roundtrip_random_config_short() {
// exercise the slower encode/decode routines that operate on shorter buffers more vigorously
roundtrip_random_config(Range::new(0, 50), Range::new(0, 50), 10_000);
}
#[test]
fn roundtrip_random_config_long() {
roundtrip_random_config(Range::new(0, 1000), Range::new(0, 1000), 10_000);
}
#[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 = Range::new(0, 1000);
let input_len_range = Range::new(0, 1000);
let line_len_range = Range::new(1, 1000);
let mut rng = rand::weak_rng();
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.ind_sample(&mut rng);
for _ in 0..input_len {
orig_data.push(rng.gen());
}
let prefix_len = prefix_len_range.ind_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, &line_len_range);
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);
// since we know we have the correct count of line endings, it's reasonable to simply remove
// them without worrying about where they are
let encoded_no_line_endings: String = encoded_data_no_prefix
.chars()
.filter(|&c| c != '\r' && c != '\n')
.collect();
decode_config_buf(&encoded_no_line_endings, 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 = Range::new(0, 1000);
let line_len_range = Range::new(1, 1000);
let mut rng = rand::weak_rng();
for _ in 0..10_000 {
orig_data.clear();
encoded_data.clear();
encoded_data_original_state.clear();
decoded.clear();
let input_len = input_len_range.ind_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, &line_len_range);
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..]
);
// since we know we have the correct count of line endings, it's reasonable to simply remove
// them without worrying about where they are
let encoded_no_line_endings: String = String::from_utf8(
encoded_data[0..encoded_size]
.iter()
.filter(|&b| *b != '\r' as u8 && *b != '\n' as u8)
.map(|&b| b)
.collect(),
).unwrap();
decode_config_buf(&encoded_no_line_endings, config, &mut decoded).unwrap();
assert_eq!(orig_data, decoded);
}
}
#[test]
fn decode_into_nonempty_buffer_doesnt_clobber_existing_contents() {
let mut orig_data = Vec::new();
let mut encoded_data = String::new();
let mut decoded_with_prefix = Vec::new();
let mut decoded_without_prefix = Vec::new();
let mut prefix = Vec::new();
let prefix_len_range = Range::new(0, 1000);
let input_len_range = Range::new(0, 1000);
let line_len_range = Range::new(1, 1000);
let mut rng = rand::weak_rng();
for _ in 0..10_000 {
orig_data.clear();
encoded_data.clear();
decoded_with_prefix.clear();
decoded_without_prefix.clear();
prefix.clear();
let input_len = input_len_range.ind_sample(&mut rng);
for _ in 0..input_len {
orig_data.push(rng.gen());
}
let config = random_config(&mut rng, &line_len_range);
encode_config_buf(&orig_data, config, &mut encoded_data);
assert_encode_sanity(&encoded_data, &config, input_len);
let prefix_len = prefix_len_range.ind_sample(&mut rng);
// fill the buf with a prefix
for _ in 0..prefix_len {
prefix.push(rng.gen());
}
decoded_with_prefix.resize(prefix_len, 0);
decoded_with_prefix.copy_from_slice(&prefix);
// remove line wrapping
let encoded_no_line_endings: String = encoded_data
.chars()
.filter(|&c| c != '\r' && c != '\n')
.collect();
// decode into the non-empty buf
decode_config_buf(&encoded_no_line_endings, config, &mut decoded_with_prefix).unwrap();
// also decode into the empty buf
decode_config_buf(
&encoded_no_line_endings,
config,
&mut decoded_without_prefix,
).unwrap();
assert_eq!(
prefix_len + decoded_without_prefix.len(),
decoded_with_prefix.len()
);
assert_eq!(orig_data, decoded_without_prefix);
// append plain decode onto prefix
prefix.append(&mut decoded_without_prefix);
assert_eq!(prefix, decoded_with_prefix);
}
}
#[test]
fn encode_with_padding_line_wrap_random_valid_utf8() {
let mut input = Vec::new();
let mut output = Vec::new();
let input_len_range = Range::new(0, 1000);
let line_len_range = Range::new(1, 1000);
let mut rng = rand::weak_rng();
for _ in 0..10_000 {
input.clear();
output.clear();
let input_len = input_len_range.ind_sample(&mut rng);
for _ in 0..input_len {
input.push(rng.gen());
}
let config = random_config(&mut rng, &line_len_range);
// 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_line_wrap(&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 encode_to_slice_random_valid_utf8() {
let mut input = Vec::new();
let mut output = Vec::new();
let input_len_range = Range::new(0, 1000);
let line_len_range = Range::new(1, 1000);
let mut rng = rand::weak_rng();
for _ in 0..10_000 {
input.clear();
output.clear();
let input_len = input_len_range.ind_sample(&mut rng);
for _ in 0..input_len {
input.push(rng.gen());
}
let config = random_config(&mut rng, &line_len_range);
// 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 add_padding_random_valid_utf8() {
let mut output = Vec::new();
let mut rng = rand::weak_rng();
// 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::weak_rng();
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());
}
fn assert_encode_sanity(encoded: &str, config: &Config, input_len: usize) {
let input_rem = input_len % 3;
let (expected_padding_len, last_encoded_chunk_len) = if input_rem > 0 {
if config.pad {
(3 - input_rem, 4)
} else {
(0, input_rem + 1)
}
} else {
(0, 0)
};
let b64_only_len = (input_len / 3) * 4 + last_encoded_chunk_len;
let expected_line_ending_len = match config.line_wrap {
LineWrap::NoWrap => 0,
LineWrap::Wrap(line_len, line_ending) => {
line_wrap_parameters(b64_only_len, line_len, line_ending).total_line_endings_len
}
};
let expected_encoded_len = encoded_size(input_len, &config).unwrap();
assert_eq!(expected_encoded_len, encoded.len());
let line_endings_len = encoded.chars().filter(|&c| c == '\r' || c == '\n').count();
let padding_len = encoded.chars().filter(|&c| c == '=').count();
assert_eq!(expected_padding_len, padding_len);
assert_eq!(expected_line_ending_len, line_endings_len);
let _ = str::from_utf8(encoded.as_bytes()).expect("Base64 should be valid utf8");
}
fn roundtrip_random_config(
input_len_range: Range<usize>,
line_len_range: Range<usize>,
iterations: u32,
) {
let mut input_buf: Vec<u8> = Vec::new();
let mut encoded_buf = String::new();
let mut rng = rand::weak_rng();
for _ in 0..iterations {
input_buf.clear();
encoded_buf.clear();
let input_len = input_len_range.ind_sample(&mut rng);
let config = random_config(&mut rng, &line_len_range);
for _ in 0..input_len {
input_buf.push(rng.gen());
}
encode_config_buf(&input_buf, config, &mut encoded_buf);
assert_encode_sanity(&encoded_buf, &config, input_len);
// remove line wrapping
let encoded_no_line_endings: String = encoded_buf
.chars()
.filter(|&c| c != '\r' && c != '\n')
.collect();
assert_eq!(
input_buf,
decode_config(&encoded_no_line_endings, config).unwrap()
);
}
}
pub fn random_config<R: Rng>(rng: &mut R, line_len_range: &Range<usize>) -> Config {
let line_wrap = if rng.gen() {
LineWrap::NoWrap
} else {
let line_len = line_len_range.ind_sample(rng);
let line_ending = if rng.gen() {
LineEnding::LF
} else {
LineEnding::CRLF
};
LineWrap::Wrap(line_len, line_ending)
};
let charset = if rng.gen() {
CharacterSet::UrlSafe
} else {
CharacterSet::Standard
};
Config::new(charset, rng.gen(), rng.gen(), line_wrap)
}