use std::convert::TryInto;
use std::io::{self, BufRead, Cursor, Read, Write};
use crate::support::compression::FinishWrite;
#[derive(Debug, Clone)]
pub struct Reader<R> {
reader: R,
buffer: Cursor<Vec<u8>>,
next_is_encoded: bool,
}
impl<R> Reader<R> {
pub fn new(reader: R) -> Self {
Reader {
reader,
buffer: Cursor::new(Vec::with_capacity(255)),
next_is_encoded: false,
}
}
}
impl<R: Read> Read for Reader<R> {
fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
self.fill_buf()?;
self.buffer.read(dst)
}
}
impl<R: Read> BufRead for Reader<R> {
fn fill_buf(&mut self) -> io::Result<&[u8]> {
if (self.buffer.position() as usize) < self.buffer.get_ref().len() {
return self.buffer.fill_buf();
}
let mut length = [0u8; 1];
let mut is_encoded;
loop {
match self.reader.read_exact(&mut length) {
Ok(_) => (),
Err(e) if io::ErrorKind::UnexpectedEof == e.kind() => {
return Ok(&[]);
},
Err(e) => {
return Err(e);
},
}
is_encoded = self.next_is_encoded;
self.next_is_encoded = !self.next_is_encoded;
if 0 != length[0] {
break;
}
}
if is_encoded {
let mut buf = [0u8; 255 * 3];
let text_length = length[0] as usize * 4;
let bin_length = length[0] as usize * 3;
self.buffer.set_position(text_length as u64);
self.buffer.get_mut().resize(text_length, 0);
self.reader.read_exact(&mut buf[..bin_length])?;
base64::encode_config_slice(
&buf[..bin_length],
base64::STANDARD,
self.buffer.get_mut(),
);
} else {
self.buffer.set_position(length[0].into());
self.buffer.get_mut().resize(length[0].into(), 0);
self.reader.read_exact(self.buffer.get_mut())?;
}
self.buffer.set_position(0);
self.buffer.fill_buf()
}
fn consume(&mut self, n: usize) {
self.buffer.consume(n)
}
}
#[derive(Debug, Clone)]
pub struct Writer<W> {
writer: W,
unencoded_buffer: Vec<u8>,
encoded_buffer: Vec<u8>,
}
impl<W> Writer<W> {
pub fn new(writer: W) -> Self {
Writer {
writer,
unencoded_buffer: Vec::with_capacity(255),
encoded_buffer: Vec::with_capacity(255 * 4),
}
}
}
impl<W: Write> Writer<W> {
fn push(&mut self, byte: u8) -> io::Result<()> {
if is_base64(byte) {
self.encoded_buffer.push(byte);
if self.encoded_buffer.len() == 255 * 4 {
self.dump_unencoded()?;
self.dump_encoded()?;
}
} else {
self.flush_encoded()?;
if 255 == self.unencoded_buffer.len() {
self.dump_unencoded()?;
self.dump_encoded()?;
}
self.unencoded_buffer.push(byte);
}
Ok(())
}
fn flush_encoded(&mut self) -> io::Result<()> {
if !self.encoded_buffer.is_empty() {
if self.encoded_buffer.len() + self.unencoded_buffer.len() <= 255
&& self.encoded_buffer.len() < 12
{
self.unencoded_buffer.append(&mut self.encoded_buffer);
} else {
self.dump_unencoded()?;
self.split_encoded();
self.dump_encoded()?;
}
}
Ok(())
}
fn dump_unencoded(&mut self) -> io::Result<()> {
self.writer.write_all(&[self
.unencoded_buffer
.len()
.try_into()
.unwrap()])?;
self.writer.write_all(&self.unencoded_buffer)?;
self.unencoded_buffer.clear();
Ok(())
}
fn dump_encoded(&mut self) -> io::Result<()> {
debug_assert_eq!(0, self.encoded_buffer.len() % 4);
self.writer.write_all(&[(self.encoded_buffer.len() / 4)
.try_into()
.unwrap()])?;
if !self.encoded_buffer.is_empty() {
let mut buf = [0u8; 255 * 3];
let text_length = self.encoded_buffer.len();
let count = text_length / 4;
let bin_length = count * 3;
base64::decode_config_slice(
&self.encoded_buffer,
base64::STANDARD,
&mut buf[..bin_length],
)
.unwrap();
self.writer.write_all(&buf[..bin_length])?;
self.encoded_buffer.clear();
}
Ok(())
}
fn split_encoded(&mut self) {
debug_assert!(self.unencoded_buffer.is_empty());
let valid = self.encoded_buffer.len() / 4 * 4;
self.unencoded_buffer
.extend_from_slice(&self.encoded_buffer[valid..]);
self.encoded_buffer.resize(valid, 0);
}
}
impl<W: Write> Write for Writer<W> {
fn write(&mut self, src: &[u8]) -> io::Result<usize> {
for &byte in src {
self.push(byte)?;
}
Ok(src.len())
}
fn flush(&mut self) -> io::Result<()> {
self.flush_encoded()?;
if !self.unencoded_buffer.is_empty() {
self.dump_unencoded()?;
self.dump_encoded()?;
}
self.writer.flush()
}
}
impl<W: FinishWrite> FinishWrite for Writer<W> {
fn finish(&mut self) -> io::Result<()> {
self.flush()?;
self.writer.finish()
}
}
#[allow(clippy::manual_range_contains)]
fn is_base64(b: u8) -> bool {
(b >= b'a' && b <= b'z')
|| (b >= b'A' && b <= b'Z')
|| (b >= b'0' && b <= b'9')
|| b == b'/'
|| b == b'+'
}
#[cfg(test)]
mod test {
use std::io::{Cursor, Read, Write};
use proptest::prelude::*;
use super::*;
fn blob(n: usize) -> impl Strategy<Value = Vec<u8>> {
prop::collection::vec(prop::num::u8::ANY, 1..=n)
}
fn base64_string(n: usize) -> impl Strategy<Value = Vec<u8>> {
blob(n).prop_map(|bytes| base64::encode(&bytes).as_bytes().to_owned())
}
fn uncompressed_input() -> impl Strategy<Value = Vec<u8>> {
prop::collection::vec(
prop_oneof![
blob(8),
base64_string(8),
blob(32),
base64_string(32),
blob(512),
base64_string(512)
],
0..10,
)
.prop_map(|chunks| {
let mut accum = Vec::new();
for mut chunk in chunks {
accum.append(&mut chunk);
}
accum
})
}
proptest! {
#[test]
fn compress_and_decompress(input in uncompressed_input()) {
let mut compressed = Vec::<u8>::new();
{
let mut writer = Writer::new(&mut compressed);
writer.write_all(&input).unwrap();
writer.flush().unwrap();
}
let mut decompressed = Vec::<u8>::new();
{
let mut reader = Reader::new(Cursor::new(compressed));
reader.read_to_end(&mut decompressed).unwrap();
}
prop_assert_eq!(input, decompressed);
}
}
}