use std::io::{self, Read, Write};
use brotli::CompressorWriter;
use brotli::Decompressor;
use brotli::enc::BrotliEncoderParams;
use super::skippable_frame::{self, FrameReader, MAX_HEADER_SIZE, SKIPPABLE_FRAME_MAGIC};
use super::{Decoder, Encoder, method};
const BROTLI_HEADER_SIZE: usize = 16;
const BROTLI_FRAME_SIZE: u32 = 8;
const BROTLI_MAGIC: u16 = 0x5242;
const BUFFER_SIZE: usize = 4096;
pub struct BrotliDecoder<R: Read> {
inner: Option<Decompressor<FrameReader<R, BROTLI_HEADER_SIZE>>>,
buffer_size: usize,
}
impl<R: Read> std::fmt::Debug for BrotliDecoder<R> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BrotliDecoder").finish_non_exhaustive()
}
}
impl<R: Read + Send> BrotliDecoder<R> {
pub fn new(mut input: R) -> io::Result<Self> {
let mut header = [0u8; MAX_HEADER_SIZE];
let header_read =
skippable_frame::read_full_or_eof(&mut input, &mut header[..BROTLI_HEADER_SIZE])?;
if header_read == 0 {
return Ok(Self {
inner: None,
buffer_size: BUFFER_SIZE,
});
}
let frame_reader = if header_read == BROTLI_HEADER_SIZE {
if let Some(compressed_size) =
validate_brotli_header(&header[..BROTLI_HEADER_SIZE].try_into().unwrap())
{
FrameReader::new_skippable(input, compressed_size)
} else {
FrameReader::new_standard(input, header, header_read)
}
} else {
FrameReader::new_standard(input, header, header_read)
};
let decompressor = Decompressor::new(frame_reader, BUFFER_SIZE);
Ok(Self {
inner: Some(decompressor),
buffer_size: BUFFER_SIZE,
})
}
}
impl<R: Read + Send> Read for BrotliDecoder<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let Some(inner) = &mut self.inner else {
return Ok(0);
};
match inner.read(buf) {
Ok(0) => {
let frame_reader = inner.get_mut();
match frame_reader.try_read_next_frame(validate_brotli_header)? {
Some(_compressed_size) => {
let reader = std::mem::replace(frame_reader, FrameReader::Empty);
let mut new_decompressor = Decompressor::new(reader, self.buffer_size);
let result = new_decompressor.read(buf);
self.inner = Some(new_decompressor);
result
}
None => {
self.inner = None;
Ok(0)
}
}
}
result => result,
}
}
}
impl<R: Read + Send> Decoder for BrotliDecoder<R> {
fn method_id(&self) -> &'static [u8] {
method::BROTLI
}
}
fn validate_brotli_header(header: &[u8; BROTLI_HEADER_SIZE]) -> Option<u32> {
let magic = u32::from_le_bytes([header[0], header[1], header[2], header[3]]);
let frame_size = u32::from_le_bytes([header[4], header[5], header[6], header[7]]);
let brotli_magic = u16::from_le_bytes([header[12], header[13]]);
if magic == SKIPPABLE_FRAME_MAGIC
&& frame_size == BROTLI_FRAME_SIZE
&& brotli_magic == BROTLI_MAGIC
{
Some(u32::from_le_bytes([
header[8], header[9], header[10], header[11],
]))
} else {
None
}
}
#[derive(Debug, Clone)]
pub struct BrotliEncoderOptions {
pub quality: u32,
pub lg_window_size: u32,
}
impl Default for BrotliEncoderOptions {
fn default() -> Self {
Self {
quality: 4,
lg_window_size: 22,
}
}
}
pub struct BrotliEncoder<W: Write> {
inner: CompressorWriter<W>,
}
impl<W: Write + Send> BrotliEncoder<W> {
pub fn new(output: W, options: &BrotliEncoderOptions) -> Self {
let params = BrotliEncoderParams {
quality: options.quality as i32,
lgwin: options.lg_window_size as i32,
..Default::default()
};
Self {
inner: CompressorWriter::with_params(output, BUFFER_SIZE, ¶ms),
}
}
pub fn try_finish(self) -> io::Result<W> {
Ok(self.inner.into_inner())
}
}
impl<W: Write + Send> Write for BrotliEncoder<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.inner.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
impl<W: Write + Send> Encoder for BrotliEncoder<W> {
fn method_id(&self) -> &'static [u8] {
method::BROTLI
}
fn finish(self: Box<Self>) -> io::Result<()> {
let _ = self.inner.into_inner();
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn round_trip() {
let original = b"Hello, World! This is a test of Brotli compression.";
let mut compressed = Vec::new();
{
let mut encoder = BrotliEncoder::new(&mut compressed, &BrotliEncoderOptions::default());
encoder.write_all(original).unwrap();
encoder.try_finish().unwrap();
}
let mut decoder = BrotliDecoder::new(Cursor::new(compressed)).unwrap();
let mut decompressed = Vec::new();
decoder.read_to_end(&mut decompressed).unwrap();
assert_eq!(decompressed, original);
}
#[test]
fn method_id() {
let mut compressed = Vec::new();
{
let mut encoder = BrotliEncoder::new(&mut compressed, &BrotliEncoderOptions::default());
encoder.write_all(b"test").unwrap();
encoder.try_finish().unwrap();
}
let decoder = BrotliDecoder::new(Cursor::new(compressed)).unwrap();
assert_eq!(decoder.method_id(), method::BROTLI);
}
#[test]
fn empty_input() {
let mut decoder = BrotliDecoder::new(Cursor::new(Vec::new())).unwrap();
let mut output = Vec::new();
let n = decoder.read_to_end(&mut output).unwrap();
assert_eq!(n, 0);
}
#[test]
fn skippable_frame_single() {
let original = b"test data for skippable frame";
let mut brotli_data = Vec::new();
{
let mut encoder =
BrotliEncoder::new(&mut brotli_data, &BrotliEncoderOptions::default());
encoder.write_all(original).unwrap();
encoder.try_finish().unwrap();
}
let mut framed = Vec::new();
framed.extend_from_slice(&SKIPPABLE_FRAME_MAGIC.to_le_bytes());
framed.extend_from_slice(&BROTLI_FRAME_SIZE.to_le_bytes());
framed.extend_from_slice(&(brotli_data.len() as u32).to_le_bytes());
framed.extend_from_slice(&BROTLI_MAGIC.to_le_bytes());
framed.extend_from_slice(&0u16.to_le_bytes()); framed.extend_from_slice(&brotli_data);
let mut decoder = BrotliDecoder::new(Cursor::new(framed)).unwrap();
let mut decompressed = Vec::new();
decoder.read_to_end(&mut decompressed).unwrap();
assert_eq!(decompressed, original);
}
#[test]
fn skippable_frame_multi() {
let data1 = b"first frame data";
let data2 = b"second frame data";
let mut brotli_data1 = Vec::new();
{
let mut encoder =
BrotliEncoder::new(&mut brotli_data1, &BrotliEncoderOptions::default());
encoder.write_all(data1).unwrap();
encoder.try_finish().unwrap();
}
let mut brotli_data2 = Vec::new();
{
let mut encoder =
BrotliEncoder::new(&mut brotli_data2, &BrotliEncoderOptions::default());
encoder.write_all(data2).unwrap();
encoder.try_finish().unwrap();
}
let mut framed = Vec::new();
framed.extend_from_slice(&SKIPPABLE_FRAME_MAGIC.to_le_bytes());
framed.extend_from_slice(&BROTLI_FRAME_SIZE.to_le_bytes());
framed.extend_from_slice(&(brotli_data1.len() as u32).to_le_bytes());
framed.extend_from_slice(&BROTLI_MAGIC.to_le_bytes());
framed.extend_from_slice(&0u16.to_le_bytes());
framed.extend_from_slice(&brotli_data1);
framed.extend_from_slice(&SKIPPABLE_FRAME_MAGIC.to_le_bytes());
framed.extend_from_slice(&BROTLI_FRAME_SIZE.to_le_bytes());
framed.extend_from_slice(&(brotli_data2.len() as u32).to_le_bytes());
framed.extend_from_slice(&BROTLI_MAGIC.to_le_bytes());
framed.extend_from_slice(&0u16.to_le_bytes());
framed.extend_from_slice(&brotli_data2);
let mut decoder = BrotliDecoder::new(Cursor::new(framed)).unwrap();
let mut decompressed = Vec::new();
decoder.read_to_end(&mut decompressed).unwrap();
let mut expected = Vec::new();
expected.extend_from_slice(data1);
expected.extend_from_slice(data2);
assert_eq!(decompressed, expected);
}
#[test]
fn skippable_frame_empty_payload() {
let mut framed = Vec::new();
framed.extend_from_slice(&SKIPPABLE_FRAME_MAGIC.to_le_bytes());
framed.extend_from_slice(&BROTLI_FRAME_SIZE.to_le_bytes());
framed.extend_from_slice(&0u32.to_le_bytes()); framed.extend_from_slice(&BROTLI_MAGIC.to_le_bytes());
framed.extend_from_slice(&0u16.to_le_bytes());
let mut decoder = BrotliDecoder::new(Cursor::new(framed)).unwrap();
let mut output = Vec::new();
let _ = decoder.read_to_end(&mut output);
}
}