pub(crate) mod iterator;
pub mod mode;
use std::io::{Read, Seek, Write};
#[cfg(test)]
use proptest::collection::{size_range, vec};
#[cfg(test)]
use proptest::prelude::*;
pub use crate::data::control::mode::Mode;
use crate::{RefPackError, RefPackResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Command {
Short {
offset: u16,
length: u8,
literal: u8,
},
Medium {
offset: u16,
length: u8,
literal: u8,
},
Long {
offset: u32,
length: u16,
literal: u8,
},
Literal(u8),
Stop(u8),
}
impl Command {
#[must_use]
pub fn new<M: Mode>(offset: usize, length: usize, literal: usize) -> Self {
assert!(
literal <= M::SIZES.copy_literal_max() as usize,
"Literal length must be less than or equal to {} for commands ({})",
M::SIZES.copy_literal_max(),
literal
);
if offset > M::SIZES.long_offset_max() as usize
|| length > M::SIZES.long_length_max() as usize
{
panic!(
"Invalid offset or length (Maximum offset {}, got {}) (Maximum length {}, got {})",
M::SIZES.long_offset_max(),
offset,
M::SIZES.long_length_max(),
length
);
} else if offset > M::SIZES.medium_offset_max() as usize
|| length > M::SIZES.medium_length_max() as usize
{
assert!(
length >= M::SIZES.long_length_min() as usize,
"Length must be greater than or equal to {} for long commands (Length: {}) (Offset: {})",
M::SIZES.long_length_min(),
length,
offset
);
Self::Long {
offset: offset as u32,
length: length as u16,
literal: literal as u8,
}
} else if offset > M::SIZES.short_offset_max() as usize
|| length > M::SIZES.short_length_max() as usize
{
assert!(
length >= M::SIZES.medium_length_min() as usize,
"Length must be greater than or equal to {} for medium commands (Length: {}) (Offset: {})",
M::SIZES.medium_length_min(),
length,
offset
);
Self::Medium {
offset: offset as u16,
length: length as u8,
literal: literal as u8,
}
} else {
Self::Short {
offset: offset as u16,
length: length as u8,
literal: literal as u8,
}
}
}
#[must_use]
pub fn new_literal<M: Mode>(length: usize) -> Self {
assert!(
length <= M::SIZES.literal_max() as usize,
"Literal received too long of a literal length (max {}, got {})",
M::SIZES.literal_max(),
length
);
Self::Literal(length as u8)
}
#[must_use]
pub fn new_stop<M: Mode>(literal_length: usize) -> Self {
assert!(
literal_length <= 3,
"Stopcode recieved too long of a literal length (max {}, got {})",
M::SIZES.copy_literal_max(),
literal_length
);
Self::Stop(literal_length as u8)
}
#[must_use]
pub fn num_of_literal(self) -> Option<usize> {
match self {
Command::Short { literal, .. }
| Command::Medium { literal, .. }
| Command::Long { literal, .. } => {
if literal == 0 {
None
} else {
Some(literal as usize)
}
}
Command::Literal(number) => Some(number as usize),
Command::Stop(number) => {
if number == 0 {
None
} else {
Some(number as usize)
}
}
}
}
#[must_use]
pub fn offset_copy(self) -> Option<(usize, usize)> {
match self {
Command::Short { offset, length, .. } | Command::Medium { offset, length, .. } => {
Some((offset as usize, length as usize))
}
Command::Long { offset, length, .. } => Some((offset as usize, length as usize)),
_ => None,
}
}
#[must_use]
pub fn is_stop(self) -> bool {
matches!(self, Command::Stop(_))
}
pub fn read<M: Mode>(reader: &mut (impl Read + Seek)) -> RefPackResult<Self> {
M::read(reader)
}
pub fn write<M: Mode>(self, writer: &mut (impl Write + Seek)) -> RefPackResult<()> {
M::write(self, writer)?;
Ok(())
}
}
#[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<M: Mode>(bytes: &[u8]) -> Self {
Self {
command: Command::new_literal::<M>(bytes.len()),
bytes: bytes.to_vec(),
}
}
#[must_use]
pub fn new_stop<M: Mode>(bytes: &[u8]) -> Self {
Self {
command: Command::new_stop::<M>(bytes.len()),
bytes: bytes.to_vec(),
}
}
pub fn read<M: Mode>(reader: &mut (impl Read + Seek)) -> Result<Self, RefPackError> {
let command = Command::read::<M>(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<M: Mode>(&self, writer: &mut (impl Write + Seek)) -> Result<(), RefPackError> {
self.command.write::<M>(writer)?;
writer.write_all(&self.bytes)?;
Ok(())
}
}
#[cfg(test)]
pub(crate) mod tests {
use std::io::{Cursor, SeekFrom};
use test_strategy::proptest;
use super::*;
use crate::data::control::mode::Reference;
pub fn generate_random_valid_command<M: Mode>() -> BoxedStrategy<Command> {
let sizes = M::SIZES;
let short_copy_strat = (
sizes.short_offset_min()..=sizes.short_offset_max(),
sizes.short_length_min()..=sizes.short_length_max(),
sizes.copy_literal_min()..=sizes.copy_literal_max(),
)
.prop_map(|(offset, length, literal)| Command::Short {
offset,
length,
literal,
});
let medium_copy_strat = (
sizes.medium_offset_min()..=sizes.medium_offset_max(),
sizes.medium_length_min()..=sizes.medium_length_max(),
sizes.copy_literal_min()..=sizes.copy_literal_max(),
)
.prop_map(|(offset, length, literal)| Command::Medium {
offset,
length,
literal,
});
let long_copy_strat = (
sizes.long_offset_min()..=sizes.long_offset_max(),
sizes.long_length_min()..=sizes.long_length_max(),
sizes.copy_literal_min()..=sizes.copy_literal_max(),
)
.prop_map(|(offset, length, literal)| Command::Long {
offset,
length,
literal,
});
let literal_strat = sizes.literal_effective_min()..=sizes.literal_effective_max();
let literal =
Strategy::prop_map(literal_strat, |literal| Command::Literal((literal * 4) + 4));
prop_oneof![
short_copy_strat,
medium_copy_strat,
long_copy_strat,
literal
]
.boxed()
}
pub fn generate_stopcode<M: Mode>() -> BoxedStrategy<Command> {
let sizes = M::SIZES;
(sizes.copy_literal_min()..=sizes.copy_literal_max())
.prop_map(Command::Stop)
.boxed()
}
pub fn generate_control<M: Mode>() -> BoxedStrategy<Control> {
generate_random_valid_command::<M>()
.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<M: Mode>() -> BoxedStrategy<Control> {
generate_stopcode::<M>()
.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<M: Mode>(
max_length: usize,
) -> BoxedStrategy<Vec<Control>> {
(
vec(generate_control::<M>(), 0..(max_length - 1)),
generate_stop_control::<M>(),
)
.prop_map(|(vec, stopcode)| {
let mut vec = vec;
vec.push(stopcode);
vec
})
.boxed()
}
#[proptest]
fn symmetrical_command_copy(
#[strategy(1..=131_071_usize)] offset: usize,
#[strategy(5..=1028_usize)] length: usize,
#[strategy(0..=3_usize)] literal: usize,
) {
let expected = Command::new::<Reference>(offset, length, literal);
let mut buf = Cursor::new(vec![]);
expected.write::<Reference>(&mut buf).unwrap();
buf.seek(SeekFrom::Start(0)).unwrap();
let out: Command = Command::read::<Reference>(&mut buf).unwrap();
prop_assert_eq!(out, expected);
}
#[proptest]
fn symmetrical_command_literal(#[strategy(0..=27_usize)] literal: usize) {
let real_length = (literal * 4) + 4;
let expected = Command::new_literal::<Reference>(real_length);
let mut buf = Cursor::new(vec![]);
expected.write::<Reference>(&mut buf).unwrap();
buf.seek(SeekFrom::Start(0)).unwrap();
let out: Command = Command::read::<Reference>(&mut buf).unwrap();
prop_assert_eq!(out, expected);
}
#[proptest]
fn symmetrical_command_stop(#[strategy(0..=3_usize)] input: usize) {
let expected = Command::new_stop::<Reference>(input);
let mut buf = Cursor::new(vec![]);
expected.write::<Reference>(&mut buf).unwrap();
buf.seek(SeekFrom::Start(0)).unwrap();
let out: Command = Command::read::<Reference>(&mut buf).unwrap();
prop_assert_eq!(out, expected);
}
#[proptest]
fn symmetrical_any_command(
#[strategy(generate_random_valid_command::<Reference>())] input: Command,
) {
let expected = input;
let mut buf = Cursor::new(vec![]);
expected.write::<Reference>(&mut buf).unwrap();
buf.seek(SeekFrom::Start(0)).unwrap();
let out: Command = Command::read::<Reference>(&mut buf).unwrap();
prop_assert_eq!(out, expected);
}
#[test]
#[should_panic]
fn command_reject_new_stop_invalid() {
let _invalid = Command::new_stop::<Reference>(8000);
}
#[test]
#[should_panic]
fn command_reject_new_literal_invalid() {
let _invalid = Command::new_literal::<Reference>(8000);
}
#[test]
#[should_panic]
fn command_reject_new_invalid_high_offset() {
let _invalid = Command::new::<Reference>(500_000, 0, 0);
}
#[test]
#[should_panic]
fn command_reject_new_invalid_high_length() {
let _invalid = Command::new::<Reference>(0, 500_000, 0);
}
#[test]
#[should_panic]
fn command_reject_new_invalid_high_literal() {
let _invalid = Command::new::<Reference>(0, 0, 6000);
}
#[proptest]
fn symmetrical_control(#[strategy(generate_control::<Reference>())] input: Control) {
let expected = input;
let mut buf = Cursor::new(vec![]);
expected.write::<Reference>(&mut buf).unwrap();
buf.seek(SeekFrom::Start(0)).unwrap();
let out: Control = Control::read::<Reference>(&mut buf).unwrap();
prop_assert_eq!(out, expected);
}
}