#![allow(dead_code)]
use alloc::vec;
use alloc::vec::Vec;
use crate::error::Error;
pub const WINDOW_SIZE: usize = 0x10000;
pub struct Window {
buf: Vec<u8>,
write_pos: usize,
flush_pos: usize,
in_flight: usize,
total_written: u64,
}
impl Window {
pub fn new() -> Self {
Self {
buf: vec![0u8; WINDOW_SIZE],
write_pos: 0,
flush_pos: 0,
in_flight: 0,
total_written: 0,
}
}
pub fn total_written(&self) -> u64 {
self.total_written
}
pub fn in_flight(&self) -> usize {
self.in_flight
}
pub fn is_full(&self) -> bool {
self.in_flight == WINDOW_SIZE
}
pub fn emit_literal(&mut self, byte: u8) -> Result<(), Error> {
if self.is_full() {
return Err(Error::OutputTooSmall);
}
self.buf[self.write_pos] = byte;
self.write_pos = (self.write_pos + 1) & (WINDOW_SIZE - 1);
self.in_flight += 1;
self.total_written += 1;
Ok(())
}
pub fn emit_match(&mut self, distance: usize, length: usize) -> Result<(), Error> {
if distance == 0 || distance > WINDOW_SIZE {
return Err(Error::InvalidDistance);
}
if (distance as u64) > self.total_written {
return Err(Error::InvalidDistance);
}
if length == 0 {
return Ok(());
}
if self.in_flight + length > WINDOW_SIZE {
return Err(Error::OutputTooSmall);
}
let mask = WINDOW_SIZE - 1;
let mut src = (self.write_pos + WINDOW_SIZE - distance) & mask;
let mut dst = self.write_pos;
for _ in 0..length {
let b = self.buf[src];
self.buf[dst] = b;
src = (src + 1) & mask;
dst = (dst + 1) & mask;
}
self.write_pos = dst;
self.in_flight += length;
self.total_written += length as u64;
Ok(())
}
pub fn drain_into(&mut self, out: &mut [u8]) -> usize {
let take = self.in_flight.min(out.len());
if take == 0 {
return 0;
}
let mask = WINDOW_SIZE - 1;
let first_run = (WINDOW_SIZE - self.flush_pos).min(take);
out[..first_run].copy_from_slice(&self.buf[self.flush_pos..self.flush_pos + first_run]);
if first_run < take {
let rest = take - first_run;
out[first_run..take].copy_from_slice(&self.buf[..rest]);
}
self.flush_pos = (self.flush_pos + take) & mask;
self.in_flight -= take;
take
}
pub fn reset(&mut self) {
for b in self.buf.iter_mut() {
*b = 0;
}
self.write_pos = 0;
self.flush_pos = 0;
self.in_flight = 0;
self.total_written = 0;
}
}
impl Default for Window {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn literal_then_drain() {
let mut w = Window::new();
w.emit_literal(b'A').unwrap();
w.emit_literal(b'B').unwrap();
w.emit_literal(b'C').unwrap();
assert_eq!(w.in_flight(), 3);
assert_eq!(w.total_written(), 3);
let mut out = [0u8; 5];
assert_eq!(w.drain_into(&mut out), 3);
assert_eq!(&out[..3], b"ABC");
assert_eq!(w.in_flight(), 0);
assert_eq!(w.total_written(), 3);
}
#[test]
fn match_basic() {
let mut w = Window::new();
for &b in b"hello" {
w.emit_literal(b).unwrap();
}
w.emit_match(5, 5).unwrap();
let mut out = [0u8; 10];
let n = w.drain_into(&mut out);
assert_eq!(n, 10);
assert_eq!(&out[..10], b"hellohello");
}
#[test]
fn match_with_overlap_run_length() {
let mut w = Window::new();
w.emit_literal(b'A').unwrap();
w.emit_match(1, 8).unwrap();
let mut out = [0u8; 9];
let n = w.drain_into(&mut out);
assert_eq!(n, 9);
assert_eq!(&out, b"AAAAAAAAA");
}
#[test]
fn match_with_overlap_two_byte_period() {
let mut w = Window::new();
w.emit_literal(b'A').unwrap();
w.emit_literal(b'B').unwrap();
w.emit_match(2, 6).unwrap();
let mut out = [0u8; 8];
let n = w.drain_into(&mut out);
assert_eq!(n, 8);
assert_eq!(&out, b"ABABABAB");
}
#[test]
fn invalid_distance_zero() {
let mut w = Window::new();
w.emit_literal(b'A').unwrap();
assert_eq!(w.emit_match(0, 1).unwrap_err(), Error::InvalidDistance);
}
#[test]
fn invalid_distance_too_large() {
let mut w = Window::new();
w.emit_literal(b'A').unwrap();
assert_eq!(
w.emit_match(WINDOW_SIZE + 1, 1).unwrap_err(),
Error::InvalidDistance
);
}
#[test]
fn invalid_distance_before_stream_start() {
let mut w = Window::new();
w.emit_literal(b'A').unwrap();
assert_eq!(w.emit_match(2, 1).unwrap_err(), Error::InvalidDistance);
}
#[test]
fn output_too_small_when_full() {
let mut w = Window::new();
for i in 0..WINDOW_SIZE {
w.emit_literal((i & 0xFF) as u8).unwrap();
}
assert!(w.is_full());
assert_eq!(w.emit_literal(0).unwrap_err(), Error::OutputTooSmall);
let mut tmp = [0u8; 1];
w.drain_into(&mut tmp);
assert_eq!(w.emit_match(2, 2).unwrap_err(), Error::OutputTooSmall);
}
#[test]
fn drain_wraps_around() {
let mut w = Window::new();
for i in 0..WINDOW_SIZE {
w.emit_literal((i & 0xFF) as u8).unwrap();
}
let mut sink = vec![0u8; WINDOW_SIZE / 2];
let n = w.drain_into(&mut sink);
assert_eq!(n, WINDOW_SIZE / 2);
for i in 0..100 {
w.emit_literal((i & 0xFF) as u8).unwrap();
}
let mut sink2 = vec![0u8; WINDOW_SIZE];
let n2 = w.drain_into(&mut sink2);
assert_eq!(n2, WINDOW_SIZE / 2 + 100);
}
#[test]
fn back_reference_after_partial_drain_still_valid() {
let mut w = Window::new();
for &b in b"abcdefgh" {
w.emit_literal(b).unwrap();
}
let mut sink = [0u8; 4];
assert_eq!(w.drain_into(&mut sink), 4);
assert_eq!(&sink, b"abcd");
w.emit_match(8, 8).unwrap();
let mut sink2 = [0u8; 12];
let n = w.drain_into(&mut sink2);
assert_eq!(n, 12);
assert_eq!(&sink2, b"efghabcdefgh");
}
#[test]
fn reset_clears_state() {
let mut w = Window::new();
w.emit_literal(b'A').unwrap();
w.reset();
assert_eq!(w.total_written(), 0);
assert_eq!(w.in_flight(), 0);
assert_eq!(w.emit_match(1, 1).unwrap_err(), Error::InvalidDistance);
}
}