| 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) |
| } |