use std::cmp;
use std::io::{self, BufRead, Write};
use std::ops::{Deref, DerefMut};
use memchr;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("I/O error: {0}")]
Io(#[from] io::Error),
#[error("length of shared prefix must be >= 0 and <= {previous_len} (length of previous item), but found: {shared_len}")]
SharedOutOfRange {
previous_len: usize,
shared_len: isize,
},
#[error("length of shared prefix too big: cannot add {shared_len} to {diff} without overflow")]
SharedOverflow { shared_len: isize, diff: isize },
#[error("missing terminating NUL byte for entry")]
MissingNul,
#[error("missing newline separator for entry")]
MissingNewline,
#[error("missing the shared prefix length differential for entry")]
MissingPrefixDifferential,
}
type Result<T> = std::result::Result<T, Error>;
struct ResizableBuf {
allow_resize: bool,
data: Vec<u8>,
}
impl ResizableBuf {
fn new(capacity: usize) -> ResizableBuf {
ResizableBuf {
data: vec![0; capacity],
allow_resize: true,
}
}
fn resize(&mut self, new_size: usize) -> bool {
if new_size <= self.data.len() {
return true;
}
if !self.allow_resize {
return false;
}
self.data.resize(new_size, b'\x00');
true
}
}
impl Deref for ResizableBuf {
type Target = [u8];
fn deref(&self) -> &[u8] {
&self.data
}
}
impl DerefMut for ResizableBuf {
fn deref_mut(&mut self) -> &mut [u8] {
&mut self.data
}
}
pub struct Decoder<R> {
reader: R,
last_path: usize,
partial_entry_start: usize,
shared_len: isize,
buf: ResizableBuf,
pos: usize,
}
impl<R: BufRead> Decoder<R> {
pub fn new(reader: R) -> Decoder<R> {
let capacity = 1_000_000;
Decoder {
reader,
buf: ResizableBuf::new(capacity),
pos: 0,
last_path: 0,
shared_len: 0,
partial_entry_start: 0,
}
}
fn copy_shared(&mut self) -> Result<bool> {
let shared_len = self.shared_len as usize;
let new_pos = self.pos + shared_len;
let new_last_path = self.pos;
if !self.buf.resize(new_pos) {
return Ok(false);
}
if self.shared_len < 0 || self.last_path + shared_len > self.pos {
return Err(Error::SharedOutOfRange {
previous_len: self.pos - self.last_path,
shared_len: self.shared_len,
});
}
let (_, last) = self.buf.split_at_mut(self.last_path);
let (last, new) = last.split_at_mut(self.pos - self.last_path);
new[..shared_len].copy_from_slice(&last[..shared_len]);
self.pos += shared_len;
self.last_path = new_last_path;
Ok(true)
}
fn read_to_nul(&mut self) -> Result<bool> {
loop {
let (done, len) = {
let &mut Decoder {
ref mut reader,
ref mut buf,
ref mut pos,
..
} = self;
let input = match reader.fill_buf() {
Ok(data) => data,
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(Error::from(e)),
};
if input.is_empty() {
return Ok(false);
}
let (done, len) = match memchr::memchr(b'\x00', input) {
Some(i) => (true, i + 1),
None => (false, input.len()),
};
let new_pos = *pos + len;
if buf.resize(new_pos) {
buf[*pos..new_pos].copy_from_slice(&input[..len]);
*pos = new_pos;
(done, len)
} else {
return Ok(false);
}
};
self.reader.consume(len);
if done {
return Ok(true);
}
}
}
fn decode_prefix_diff(&mut self) -> Result<i16> {
let mut buf = [0; 1];
self.reader
.read_exact(&mut buf)
.map_err(|_| Error::MissingPrefixDifferential)?;
if buf[0] != 0x80 {
Ok((buf[0] as i8) as i16)
} else {
let mut buf = [0; 2];
self.reader
.read_exact(&mut buf)
.map_err(|_| Error::MissingPrefixDifferential)?;
let high = buf[0] as i16;
let low = buf[1] as i16;
Ok(high << 8 | low)
}
}
pub fn decode(&mut self) -> Result<&mut [u8]> {
let end = self.pos;
self.pos = 0;
let mut copy_pos = cmp::min(self.partial_entry_start, self.last_path);
let item_start = self.partial_entry_start - copy_pos;
self.last_path -= copy_pos;
while copy_pos < end {
self.buf[self.pos] = self.buf[copy_pos];
self.pos += 1;
copy_pos += 1;
}
self.buf.allow_resize = true;
let mut found_nul = self.pos > 0 && self.buf[self.pos - 1] == b'\x00';
if found_nul {
self.copy_shared()?;
}
loop {
if !self.read_to_nul()? {
break;
}
self.buf.allow_resize = !found_nul;
found_nul = true;
let diff = self.decode_prefix_diff()? as isize;
self.shared_len = self
.shared_len
.checked_add(diff)
.ok_or(Error::SharedOverflow {
shared_len: self.shared_len,
diff,
})?;
if !self.copy_shared()? {
break;
}
}
self.partial_entry_start =
memchr::memrchr(b'\n', &self.buf[..self.pos]).ok_or(Error::MissingNewline)? + 1;
Ok(&mut self.buf[item_start..self.partial_entry_start])
}
}
pub struct Encoder<W: Write> {
writer: W,
last: Vec<u8>,
shared_len: i16,
footer_meta: Vec<u8>,
footer_path: Vec<u8>,
footer_written: bool,
}
impl<W: Write> Drop for Encoder<W> {
fn drop(&mut self) {
self.write_footer().expect("failed to write footer")
}
}
impl<W: Write> Encoder<W> {
pub fn new(writer: W, footer_meta: Vec<u8>, footer_path: Vec<u8>) -> Encoder<W> {
assert!(
!footer_meta.contains(&b'\x00'),
"footer meta must not contain null bytes"
);
assert!(
!footer_path.contains(&b'\x00'),
"footer path must not contain null bytes"
);
assert!(
!footer_meta.contains(&b'\n'),
"footer meta must not contain newlines"
);
assert!(
!footer_path.contains(&b'\n'),
"footer path must not contain newlines"
);
Encoder {
writer,
last: Vec::new(),
shared_len: 0,
footer_meta,
footer_path,
footer_written: false,
}
}
fn encode_diff(&mut self, diff: i16) -> io::Result<()> {
let low = (diff & 0xFF) as u8;
if diff.abs() < i8::MAX as i16 {
self.writer.write_all(&[low])?;
} else {
let high = ((diff >> 8) & 0xFF) as u8;
self.writer.write_all(&[0x80, high, low])?;
}
Ok(())
}
pub fn write_meta(&mut self, meta: &[u8]) -> io::Result<()> {
assert!(
!meta.contains(&b'\x00'),
"entry must not contain null bytes"
);
assert!(!meta.contains(&b'\n'), "entry must not contain newlines");
self.writer.write_all(meta)?;
Ok(())
}
pub fn write_path(&mut self, path: Vec<u8>) -> io::Result<()> {
assert!(
!path.contains(&b'\x00'),
"entry must not contain null bytes"
);
assert!(!path.contains(&b'\x00'), "entry must not contain newlines");
self.writer.write_all(b"\x00")?;
let mut shared: isize = 0;
let max_shared = i16::MAX as isize;
for (a, b) in self.last.iter().zip(path.iter()) {
if a != b || shared > max_shared {
break;
}
shared += 1;
}
let shared = shared as i16;
let diff = shared - self.shared_len;
self.encode_diff(diff)?;
self.last = path;
self.shared_len = shared;
let pos = shared as usize;
self.writer.write_all(&self.last[pos..])?;
self.writer.write_all(b"\n")?;
Ok(())
}
fn write_footer(&mut self) -> io::Result<()> {
if self.footer_written {
return Ok(());
}
let diff = -self.shared_len;
self.writer.write_all(&self.footer_meta)?;
self.writer.write_all(b"\x00")?;
self.encode_diff(diff)?;
self.writer.write_all(&self.footer_path)?;
self.writer.write_all(b"\n")?;
self.footer_written = true;
Ok(())
}
pub fn finish(mut self) -> io::Result<()> {
self.write_footer()?;
Ok(())
}
}