blob: 2d8658f8b0a6312d71b3bd442cec67f62e9e264a [file] [log] [blame]
extern crate safemem;
use super::*;
#[derive(Debug, PartialEq)]
pub struct LineWrapParameters {
// number of lines that need an ending
pub lines_with_endings: usize,
// length of last line (which never needs an ending)
pub last_line_len: usize,
// length of lines that need an ending (which are always full lines), with their endings
pub total_full_wrapped_lines_len: usize,
// length of all lines, including endings for the ones that need them
pub total_len: usize,
// length of the line endings only
pub total_line_endings_len: usize,
}
/// Calculations about how many lines we'll get for a given line length, line ending, etc.
/// This assumes that the last line will not get an ending, even if it is the full line length.
pub fn line_wrap_parameters(
input_len: usize,
line_len: usize,
line_ending: LineEnding,
) -> LineWrapParameters {
let line_ending_len = line_ending.len();
if input_len <= line_len {
// no wrapping needed
return LineWrapParameters {
lines_with_endings: 0,
last_line_len: input_len,
total_full_wrapped_lines_len: 0,
total_len: input_len,
total_line_endings_len: 0,
};
};
// num_lines_with_endings > 0, last_line_length > 0
let (num_lines_with_endings, last_line_length) = if input_len % line_len > 0 {
// Every full line has an ending since there is a partial line at the end
(input_len / line_len, input_len % line_len)
} else {
// Every line is a full line, but no trailing ending.
// Subtraction will not underflow since we know input_len > line_len.
(input_len / line_len - 1, line_len)
};
// TODO should we expose exceeding usize via Result to be kind to 16-bit users? Or is that
// always going to be a panic anyway in practice? If we choose to use a Result we could pull
// line wrapping out of the normal encode path and have it be a separate step. Then only users
// who need line wrapping would care about the possibility for error.
let single_full_line_with_ending_len = line_len
.checked_add(line_ending_len)
.expect("Line length with ending exceeds usize");
// length of just the full lines with line endings
let total_full_wrapped_lines_len = num_lines_with_endings
.checked_mul(single_full_line_with_ending_len)
.expect("Full lines with endings length exceeds usize");
// all lines with appropriate endings, including the last line
let total_all_wrapped_len = total_full_wrapped_lines_len
.checked_add(last_line_length)
.expect("All lines with endings length exceeds usize");
let total_line_endings_len = num_lines_with_endings
.checked_mul(line_ending_len)
.expect("Total line endings length exceeds usize");
LineWrapParameters {
lines_with_endings: num_lines_with_endings,
last_line_len: last_line_length,
total_full_wrapped_lines_len: total_full_wrapped_lines_len,
total_len: total_all_wrapped_len,
total_line_endings_len: total_line_endings_len,
}
}
/// Insert line endings into the encoded base64 after each complete line (except the last line, even
/// if it is complete).
/// The provided buffer must be large enough to handle the increased size after endings are
/// inserted.
/// `input_len` is the length of the encoded data in `encoded_buf`.
/// `line_len` is the width without line ending characters.
/// Returns the number of line ending bytes added.
pub fn line_wrap(
encoded_buf: &mut [u8],
input_len: usize,
line_len: usize,
line_ending: LineEnding,
) -> usize {
let line_wrap_params = line_wrap_parameters(input_len, line_len, line_ending);
// ptr.offset() is undefined if it wraps, and there is no checked_offset(). However, because
// we perform this check up front to make sure we have enough capacity, we know that none of
// the subsequent pointer operations (assuming they implement the desired behavior of course!)
// will overflow.
assert!(
encoded_buf.len() >= line_wrap_params.total_len,
"Buffer must be able to hold encoded data after line wrapping"
);
// Move the last line, either partial or full, by itself as it does not have a line ending
// afterwards
let last_line_start = line_wrap_params
.lines_with_endings
.checked_mul(line_len)
.expect("Start of last line in input exceeds usize");
// last line starts immediately after all the wrapped full lines
let new_line_start = line_wrap_params.total_full_wrapped_lines_len;
safemem::copy_over(
encoded_buf,
last_line_start,
new_line_start,
line_wrap_params.last_line_len,
);
let mut line_ending_bytes = 0;
let line_ending_len = line_ending.len();
// handle the full lines
for line_num in 0..line_wrap_params.lines_with_endings {
// doesn't underflow because line_num < lines_with_endings
let lines_before_this_line = line_wrap_params.lines_with_endings - 1 - line_num;
let old_line_start = lines_before_this_line
.checked_mul(line_len)
.expect("Old line start index exceeds usize");
let new_line_start = lines_before_this_line
.checked_mul(line_ending_len)
.and_then(|i| i.checked_add(old_line_start))
.expect("New line start index exceeds usize");
safemem::copy_over(encoded_buf, old_line_start, new_line_start, line_len);
let after_new_line = new_line_start
.checked_add(line_len)
.expect("Line ending index exceeds usize");
match line_ending {
LineEnding::LF => {
encoded_buf[after_new_line] = b'\n';
line_ending_bytes += 1;
}
LineEnding::CRLF => {
encoded_buf[after_new_line] = b'\r';
encoded_buf[after_new_line
.checked_add(1)
.expect("Line ending index exceeds usize")] = b'\n';
line_ending_bytes += 2;
}
}
}
assert_eq!(line_wrap_params.total_line_endings_len, line_ending_bytes);
line_ending_bytes
}
#[cfg(test)]
mod tests {
extern crate rand;
use super::*;
use self::rand::Rng;
use self::rand::distributions::{IndependentSample, Range};
#[test]
fn line_params_perfect_multiple_of_line_length_lf() {
let params = line_wrap_parameters(100, 20, LineEnding::LF);
assert_eq!(
LineWrapParameters {
lines_with_endings: 4,
last_line_len: 20,
total_full_wrapped_lines_len: 84,
total_len: 104,
total_line_endings_len: 4,
},
params
);
}
#[test]
fn line_params_partial_last_line_crlf() {
let params = line_wrap_parameters(103, 20, LineEnding::CRLF);
assert_eq!(
LineWrapParameters {
lines_with_endings: 5,
last_line_len: 3,
total_full_wrapped_lines_len: 110,
total_len: 113,
total_line_endings_len: 10,
},
params
);
}
#[test]
fn line_params_line_len_1_crlf() {
let params = line_wrap_parameters(100, 1, LineEnding::CRLF);
assert_eq!(
LineWrapParameters {
lines_with_endings: 99,
last_line_len: 1,
total_full_wrapped_lines_len: 99 * 3,
total_len: 99 * 3 + 1,
total_line_endings_len: 99 * 2,
},
params
);
}
#[test]
fn line_params_line_len_longer_than_input_crlf() {
let params = line_wrap_parameters(100, 200, LineEnding::CRLF);
assert_eq!(
LineWrapParameters {
lines_with_endings: 0,
last_line_len: 100,
total_full_wrapped_lines_len: 0,
total_len: 100,
total_line_endings_len: 0,
},
params
);
}
#[test]
fn line_params_line_len_same_as_input_crlf() {
let params = line_wrap_parameters(100, 100, LineEnding::CRLF);
assert_eq!(
LineWrapParameters {
lines_with_endings: 0,
last_line_len: 100,
total_full_wrapped_lines_len: 0,
total_len: 100,
total_line_endings_len: 0,
},
params
);
}
#[test]
fn line_wrap_length_1_lf() {
let mut buf = vec![0x1, 0x2, 0x3, 0x4];
assert_eq!(3, do_line_wrap(&mut buf, 1, LineEnding::LF));
assert_eq!(vec![0x1, 0xA, 0x2, 0xA, 0x3, 0xA, 0x4], buf);
}
#[test]
fn line_wrap_length_1_crlf() {
let mut buf = vec![0x1, 0x2, 0x3, 0x4];
assert_eq!(6, do_line_wrap(&mut buf, 1, LineEnding::CRLF));
assert_eq!(vec![0x1, 0xD, 0xA, 0x2, 0xD, 0xA, 0x3, 0xD, 0xA, 0x4], buf);
}
#[test]
fn line_wrap_length_2_lf_full_lines() {
let mut buf = vec![0x1, 0x2, 0x3, 0x4];
assert_eq!(1, do_line_wrap(&mut buf, 2, LineEnding::LF));
assert_eq!(vec![0x1, 0x2, 0xA, 0x3, 0x4], buf);
}
#[test]
fn line_wrap_length_2_crlf_full_lines() {
let mut buf = vec![0x1, 0x2, 0x3, 0x4];
assert_eq!(2, do_line_wrap(&mut buf, 2, LineEnding::CRLF));
assert_eq!(vec![0x1, 0x2, 0xD, 0xA, 0x3, 0x4], buf);
}
#[test]
fn line_wrap_length_2_lf_partial_line() {
let mut buf = vec![0x1, 0x2, 0x3, 0x4, 0x5];
assert_eq!(2, do_line_wrap(&mut buf, 2, LineEnding::LF));
assert_eq!(vec![0x1, 0x2, 0xA, 0x3, 0x4, 0xA, 0x5], buf);
}
#[test]
fn line_wrap_length_2_crlf_partial_line() {
let mut buf = vec![0x1, 0x2, 0x3, 0x4, 0x5];
assert_eq!(4, do_line_wrap(&mut buf, 2, LineEnding::CRLF));
assert_eq!(vec![0x1, 0x2, 0xD, 0xA, 0x3, 0x4, 0xD, 0xA, 0x5], buf);
}
#[test]
fn line_wrap_random() {
let mut buf: Vec<u8> = Vec::new();
let buf_range = Range::new(10, 1000);
let line_range = Range::new(10, 100);
let mut rng = rand::weak_rng();
for _ in 0..10_000 {
buf.clear();
let buf_len = buf_range.ind_sample(&mut rng);
let line_len = line_range.ind_sample(&mut rng);
let line_ending = if rng.gen() {
LineEnding::LF
} else {
LineEnding::CRLF
};
let line_ending_len = line_ending.len();
for _ in 0..buf_len {
buf.push(rng.gen());
}
let line_wrap_params = line_wrap_parameters(buf_len, line_len, line_ending);
let not_wrapped_buf = buf.to_vec();
let _ = do_line_wrap(&mut buf, line_len, line_ending);
// remove the endings
for line_ending_num in 0..line_wrap_params.lines_with_endings {
let line_ending_offset = (line_ending_num + 1) * line_len;
for _ in 0..line_ending_len {
let _ = buf.remove(line_ending_offset);
}
}
assert_eq!(not_wrapped_buf, buf);
}
}
fn do_line_wrap(buf: &mut Vec<u8>, line_len: usize, line_ending: LineEnding) -> usize {
let mut rng = rand::weak_rng();
let orig_len = buf.len();
// A 3x inflation is enough for the worst case: line length 1, crlf ending.
// We add on extra bytes so we'll have un-wrapped bytes at the end that shouldn't get
// modified..
for _ in 0..(1000 + 2 * orig_len) {
buf.push(rng.gen());
}
let mut before_line_wrap = buf.to_vec();
let params = line_wrap_parameters(orig_len, line_len, line_ending);
let bytes_written = line_wrap(&mut buf[..], orig_len, line_len, line_ending);
assert_eq!(params.total_line_endings_len, bytes_written);
assert_eq!(params.lines_with_endings * line_ending.len(), bytes_written);
assert_eq!(params.total_len, orig_len + bytes_written);
// make sure line_wrap didn't touch anything beyond what it should
let start_of_untouched_data = orig_len + bytes_written;
assert_eq!(
before_line_wrap[start_of_untouched_data..],
buf[start_of_untouched_data..]
);
// also make sure that line wrapping will fit into a slice no bigger than what it should
// need
let bytes_written_precise_fit = line_wrap(
&mut before_line_wrap[0..(params.total_len)],
orig_len,
line_len,
line_ending,
);
assert_eq!(bytes_written, bytes_written_precise_fit);
assert_eq!(
&buf[0..(params.total_len)],
&before_line_wrap[0..(params.total_len)]
);
buf.truncate(orig_len + bytes_written);
bytes_written
}
}