#[cfg(test)]
mod iterator;
use std::io::{Read, Seek, Write};
use byteorder::{ReadBytesExt, WriteBytesExt};
#[cfg(test)]
use proptest::collection::{size_range, vec};
#[cfg(test)]
use proptest::prelude::*;
use crate::{RefPackError, RefPackResult};
pub const LITERAL_MIN: u8 = 4;
pub const LITERAL_MAX: u8 = 112;
pub const LITERAL_EFFECTIVE_MIN: u8 = (LITERAL_MIN - 4) / 4;
pub const LITERAL_EFFECTIVE_MAX: u8 = (LITERAL_MAX - 4) / 4;
pub const COPY_LITERAL_MIN: u8 = 0;
pub const COPY_LITERAL_MAX: u8 = 3;
pub const SHORT_OFFSET_MIN: u32 = 1;
pub const SHORT_OFFSET_MAX: u32 = 1_024;
pub const SHORT_LENGTH_MIN: u16 = 3;
pub const SHORT_LENGTH_MAX: u16 = 10;
pub const MEDIUM_OFFSET_MIN: u32 = 1;
pub const MEDIUM_OFFSET_MAX: u32 = 16_384;
pub const MEDIUM_LENGTH_MIN: u16 = 4;
pub const MEDIUM_LENGTH_MAX: u16 = 67;
pub const LONG_OFFSET_MIN: u32 = 1;
pub const LONG_OFFSET_MAX: u32 = 131_072;
pub const LONG_LENGTH_MIN: u16 = 5;
pub const LONG_LENGTH_MAX: u16 = 1_028;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Command {
pub offset: u32,
pub length: u16,
pub literal: u8,
pub kind: CommandKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum CommandKind {
Short,
Medium,
Long,
Literal,
Stop,
}
impl Command {
#[must_use]
pub fn new(offset: u32, length: u16, literal: u8) -> Self {
assert!(
literal <= COPY_LITERAL_MAX,
"Literal length must be less than or equal to {COPY_LITERAL_MAX} for commands \
({literal})"
);
if offset > LONG_OFFSET_MAX || length > LONG_LENGTH_MAX {
panic!(
"Invalid offset or length (Maximum offset {LONG_OFFSET_MAX}, got {offset}) \
(Maximum length {LONG_LENGTH_MAX}, got {length})"
);
} else if offset > MEDIUM_OFFSET_MAX || length > MEDIUM_LENGTH_MAX {
assert!(
length >= LONG_LENGTH_MIN,
"Length must be greater than or equal to {LONG_LENGTH_MIN} for long commands \
(Length: {length}) (Offset: {offset})"
);
Self {
offset,
length,
literal,
kind: CommandKind::Long,
}
} else if offset > SHORT_OFFSET_MAX || length > SHORT_LENGTH_MAX {
assert!(
length >= MEDIUM_LENGTH_MIN,
"Length must be greater than or equal to {MEDIUM_LENGTH_MIN} for medium commands \
(Length: {length}) (Offset: {offset})"
);
Self {
offset,
length,
literal,
kind: CommandKind::Medium,
}
} else {
Self {
offset,
length,
literal,
kind: CommandKind::Short,
}
}
}
#[must_use]
pub fn new_literal(length: u8) -> Self {
assert!(
length <= LITERAL_MAX,
"Literal received too long of a literal length (max {LITERAL_MAX}, got {length})"
);
Self {
offset: 0,
length: 0,
literal: length,
kind: CommandKind::Literal,
}
}
#[must_use]
pub fn new_stop(literal_length: usize) -> Self {
assert!(
literal_length <= COPY_LITERAL_MAX as usize,
"Stopcode recieved too long of a literal length (max {COPY_LITERAL_MAX}, got \
{literal_length})"
);
Self {
offset: 0,
length: 0,
literal: literal_length as u8,
kind: CommandKind::Stop,
}
}
#[inline(always)]
#[must_use]
pub fn new_stop_unchecked(literal_length: u8) -> Self {
Self {
offset: 0,
length: 0,
literal: literal_length,
kind: CommandKind::Stop,
}
}
#[must_use]
pub fn num_of_literal(self) -> Option<usize> {
if self.literal == 0 {
None
} else {
Some(self.literal as usize)
}
}
#[must_use]
pub fn offset_copy(self) -> Option<(usize, usize)> {
match self.kind {
CommandKind::Short | CommandKind::Medium | CommandKind::Long => {
Some((self.offset as usize, self.length as usize))
}
_ => None,
}
}
#[must_use]
pub fn is_stop(self) -> bool {
self.kind == CommandKind::Stop
}
#[inline(always)]
pub fn read_short(first: u8, reader: &mut (impl Read + Seek)) -> RefPackResult<Self> {
let byte1 = first as usize;
let byte2: usize = reader.read_u8()?.into();
let offset = ((((byte1 & 0b0110_0000) << 3) | byte2) + 1) as u32;
let length = (((byte1 & 0b0001_1100) >> 2) + 3) as u16;
let literal = (byte1 & 0b0000_0011) as u8;
Ok(Self {
offset,
length,
literal,
kind: CommandKind::Short,
})
}
#[inline(always)]
pub fn read_medium(first: u8, reader: &mut (impl Read + Seek)) -> RefPackResult<Self> {
let byte1: usize = first as usize;
let byte2: usize = reader.read_u8()?.into();
let byte3: usize = reader.read_u8()?.into();
let offset = ((((byte2 & 0b0011_1111) << 8) | byte3) + 1) as u32;
let length = ((byte1 & 0b0011_1111) + 4) as u16;
let literal = ((byte2 & 0b1100_0000) >> 6) as u8;
Ok(Self {
offset,
length,
literal,
kind: CommandKind::Medium,
})
}
#[inline(always)]
pub fn read_long(first: u8, reader: &mut (impl Read + Seek)) -> RefPackResult<Self> {
let byte1: usize = first as usize;
let byte2: usize = reader.read_u8()?.into();
let byte3: usize = reader.read_u8()?.into();
let byte4: usize = reader.read_u8()?.into();
let offset = ((((byte1 & 0b0001_0000) << 12) | (byte2 << 8) | byte3) + 1) as u32;
let length = ((((byte1 & 0b0000_1100) << 6) | byte4) + 5) as u16;
let literal = (byte1 & 0b0000_0011) as u8;
Ok(Self {
offset,
length,
literal,
kind: CommandKind::Long,
})
}
#[inline(always)]
#[must_use]
pub fn read_literal(first: u8) -> Self {
Self {
offset: 0,
length: 0,
literal: ((first & 0b0001_1111) << 2) + 4,
kind: CommandKind::Literal,
}
}
#[inline(always)]
#[must_use]
pub fn read_stop(first: u8) -> Self {
Self::new_stop_unchecked(first & 0b0000_0011)
}
#[inline(always)]
pub fn read(reader: &mut (impl Read + Seek)) -> RefPackResult<Self> {
let first = reader.read_u8()?;
match first {
0x00..=0x7F => Self::read_short(first, reader),
0x80..=0xBF => Self::read_medium(first, reader),
0xC0..=0xDF => Self::read_long(first, reader),
0xE0..=0xFB => Ok(Self::read_literal(first)),
0xFC..=0xFF => Ok(Self::read_stop(first)),
}
}
#[inline]
pub fn write_short(
offset: u32,
length: u16,
literal: u8,
writer: &mut (impl Write + Seek),
) -> RefPackResult<()> {
let length_adjusted = length - 3;
let offset_adjusted = offset - 1;
let first = ((offset_adjusted & 0b0000_0011_0000_0000) >> 3) as u8
| ((length_adjusted & 0b0000_0111) << 2) as u8
| literal & 0b0000_0011;
let second = (offset_adjusted & 0b0000_0000_1111_1111) as u8;
writer.write_u8(first)?;
writer.write_u8(second)?;
Ok(())
}
#[inline]
pub fn write_medium(
offset: u32,
length: u16,
literal: u8,
writer: &mut (impl Write + Seek),
) -> RefPackResult<()> {
let length_adjusted = length - 4;
let offset_adjusted = offset - 1;
let first = (0b1000_0000 | length_adjusted & 0b0011_1111) as u8;
let second = ((literal & 0b0000_0011) << 6) | (offset_adjusted >> 8) as u8;
let third = (offset_adjusted & 0b0000_0000_1111_1111) as u8;
writer.write_u8(first)?;
writer.write_u8(second)?;
writer.write_u8(third)?;
Ok(())
}
#[inline]
pub fn write_long(
offset: u32,
length: u16,
literal: u8,
writer: &mut (impl Write + Seek),
) -> RefPackResult<()> {
let length_adjusted = length - 5;
let offset_adjusted = offset - 1;
let first = 0b1100_0000u8
| ((offset_adjusted >> 12) & 0b0001_0000) as u8
| ((length_adjusted >> 6) & 0b0000_1100) as u8
| literal & 0b0000_0011;
let second = ((offset_adjusted >> 8) & 0b1111_1111) as u8;
let third = (offset_adjusted & 0b1111_1111) as u8;
let fourth = (length_adjusted & 0b1111_1111) as u8;
writer.write_u8(first)?;
writer.write_u8(second)?;
writer.write_u8(third)?;
writer.write_u8(fourth)?;
Ok(())
}
#[inline]
pub fn write_literal(literal: u8, writer: &mut (impl Write + Seek)) -> RefPackResult<()> {
let adjusted = (literal - 4) >> 2;
let out = 0b1110_0000 | (adjusted & 0b0001_1111);
writer.write_u8(out)?;
Ok(())
}
#[inline]
pub fn write_stop(number: u8, writer: &mut (impl Write + Seek)) -> RefPackResult<()> {
let out = 0b1111_1100 | (number & 0b0000_0011);
writer.write_u8(out)?;
Ok(())
}
pub fn write(self, writer: &mut (impl Write + Seek)) -> RefPackResult<()> {
match self.kind {
CommandKind::Short => Self::write_short(self.offset, self.length, self.literal, writer),
CommandKind::Medium => {
Self::write_medium(self.offset, self.length, self.literal, writer)
}
CommandKind::Long => Self::write_long(self.offset, self.length, self.literal, writer),
CommandKind::Literal => Self::write_literal(self.literal, writer),
CommandKind::Stop => Self::write_stop(self.literal, writer),
}
}
}
#[cfg(test)]
prop_compose! {
fn bytes_strategy(
length: usize,
)(
vec in vec(any::<u8>(), size_range(length)),
) -> Vec<u8> {
vec
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Control {
pub command: Command,
pub bytes: Vec<u8>,
}
impl Control {
#[must_use]
pub fn new(command: Command, bytes: Vec<u8>) -> Self {
Self { command, bytes }
}
#[must_use]
pub fn new_literal_block(bytes: &[u8]) -> Self {
Self {
command: Command::new_literal(bytes.len() as u8),
bytes: bytes.to_vec(),
}
}
#[must_use]
pub fn new_stop(bytes: &[u8]) -> Self {
Self {
command: Command::new_stop(bytes.len()),
bytes: bytes.to_vec(),
}
}
pub fn read(reader: &mut (impl Read + Seek)) -> Result<Self, RefPackError> {
let command = Command::read(reader)?;
let mut buf = vec![0u8; command.num_of_literal().unwrap_or(0)];
reader.read_exact(&mut buf)?;
Ok(Control {
command,
bytes: buf,
})
}
pub fn write(&self, writer: &mut (impl Write + Seek)) -> Result<(), RefPackError> {
self.command.write(writer)?;
writer.write_all(&self.bytes)?;
Ok(())
}
}
#[cfg(test)]
pub(crate) mod tests {
use std::io::{Cursor, SeekFrom};
use test_strategy::proptest;
use super::*;
pub fn generate_random_valid_command() -> BoxedStrategy<Command> {
let short_copy_strat = (
SHORT_OFFSET_MIN..=SHORT_OFFSET_MAX,
SHORT_LENGTH_MIN..=SHORT_LENGTH_MAX,
COPY_LITERAL_MIN..=COPY_LITERAL_MAX,
)
.prop_map(|(offset, length, literal)| {
Command {
offset,
length,
literal,
kind: CommandKind::Short,
}
});
let medium_copy_strat = (
MEDIUM_OFFSET_MIN..=MEDIUM_OFFSET_MAX,
MEDIUM_LENGTH_MIN..=MEDIUM_LENGTH_MAX,
COPY_LITERAL_MIN..=COPY_LITERAL_MAX,
)
.prop_map(|(offset, length, literal)| {
Command {
offset,
length,
literal,
kind: CommandKind::Medium,
}
});
let long_copy_strat = (
LONG_OFFSET_MIN..=LONG_OFFSET_MAX,
LONG_LENGTH_MIN..=LONG_LENGTH_MAX,
COPY_LITERAL_MIN..=COPY_LITERAL_MAX,
)
.prop_map(|(offset, length, literal)| {
Command {
offset,
length,
literal,
kind: CommandKind::Long,
}
});
let literal_strat = LITERAL_EFFECTIVE_MIN..=LITERAL_EFFECTIVE_MAX;
let literal = Strategy::prop_map(literal_strat, |literal| {
Command::new_literal((literal * 4) + 4)
});
prop_oneof![
short_copy_strat,
medium_copy_strat,
long_copy_strat,
literal
]
.boxed()
}
pub fn generate_stopcode() -> BoxedStrategy<Command> {
(COPY_LITERAL_MIN..=COPY_LITERAL_MAX)
.prop_map(Command::new_stop_unchecked)
.boxed()
}
pub fn generate_control() -> BoxedStrategy<Control> {
generate_random_valid_command()
.prop_flat_map(|command| {
(
Just(command),
vec(any::<u8>(), command.num_of_literal().unwrap_or(0)),
)
})
.prop_map(|(command, bytes)| Control { command, bytes })
.boxed()
}
pub fn generate_stop_control() -> BoxedStrategy<Control> {
generate_stopcode()
.prop_flat_map(|command| {
(
Just(command),
vec(any::<u8>(), command.num_of_literal().unwrap_or(0)),
)
})
.prop_map(|(command, bytes)| Control { command, bytes })
.boxed()
}
pub fn generate_valid_control_sequence(max_length: usize) -> BoxedStrategy<Vec<Control>> {
(
vec(generate_control(), 0..(max_length - 1)),
generate_stop_control(),
)
.prop_map(|(vec, stopcode)| {
let mut vec = vec;
vec.push(stopcode);
vec
})
.boxed()
}
#[proptest]
fn symmetrical_command_copy(
#[strategy(1..=131_071_u32)] offset: u32,
#[strategy(5..=1028_u16)] length: u16,
#[strategy(0..=3_u8)] literal: u8,
) {
let expected = Command::new(offset, length, literal);
let mut buf = Cursor::new(vec![]);
expected.write(&mut buf).unwrap();
buf.seek(SeekFrom::Start(0)).unwrap();
let out: Command = Command::read(&mut buf).unwrap();
prop_assert_eq!(out, expected);
}
#[proptest]
fn symmetrical_command_literal(#[strategy(0..=27_u8)] literal: u8) {
let real_length = (literal * 4) + 4;
let expected = Command::new_literal(real_length);
let mut buf = Cursor::new(vec![]);
expected.write(&mut buf).unwrap();
buf.seek(SeekFrom::Start(0)).unwrap();
let out: Command = Command::read(&mut buf).unwrap();
prop_assert_eq!(out, expected);
}
#[proptest]
fn symmetrical_command_stop(#[strategy(0..=3_usize)] input: usize) {
let expected = Command::new_stop(input);
let mut buf = Cursor::new(vec![]);
expected.write(&mut buf).unwrap();
buf.seek(SeekFrom::Start(0)).unwrap();
let out: Command = Command::read(&mut buf).unwrap();
prop_assert_eq!(out, expected);
}
#[proptest]
fn symmetrical_any_command(#[strategy(generate_random_valid_command())] input: Command) {
let expected = input;
let mut buf = Cursor::new(vec![]);
expected.write(&mut buf).unwrap();
buf.seek(SeekFrom::Start(0)).unwrap();
let out: Command = Command::read(&mut buf).unwrap();
prop_assert_eq!(out, expected);
}
#[test]
#[should_panic]
fn command_reject_new_stop_invalid() {
let _invalid = Command::new_stop(8000);
}
#[test]
#[should_panic]
fn command_reject_new_literal_invalid() {
let _invalid = Command::new_literal(u8::MAX);
}
#[test]
#[should_panic]
fn command_reject_new_invalid_high_offset() {
let _invalid = Command::new(500_000, 0, 0);
}
#[test]
#[should_panic]
fn command_reject_new_invalid_high_length() {
let _invalid = Command::new(0, u16::MAX, 0);
}
#[test]
#[should_panic]
fn command_reject_new_invalid_high_literal() {
let _invalid = Command::new(0, 0, u8::MAX);
}
#[proptest]
fn symmetrical_control(#[strategy(generate_control())] input: Control) {
let expected = input;
let mut buf = Cursor::new(vec![]);
expected.write(&mut buf).unwrap();
buf.seek(SeekFrom::Start(0)).unwrap();
let out: Control = Control::read(&mut buf).unwrap();
prop_assert_eq!(out, expected);
}
}