#![forbid(unsafe_code)]
use crate::core::extent::ChunkId;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum RansCodec {
Single = 0,
Interleaved2 = 1,
}
impl RansCodec {
pub fn from_u8(v: u8) -> Option<Self> {
match v {
0 => Some(Self::Single),
1 => Some(Self::Interleaved2),
_ => None,
}
}
pub const fn tag(self) -> u8 {
self as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum UniverseId {
UniformXofV1 = 0x01,
}
impl UniverseId {
pub fn from_u8(v: u8) -> Option<Self> {
match v {
0x01 => Some(Self::UniformXofV1),
_ => None,
}
}
pub const fn tag(self) -> u8 {
self as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum TransformId {
Identity = 0x00,
}
impl TransformId {
pub fn from_u8(v: u8) -> Option<Self> {
match v {
0x00 => Some(Self::Identity),
_ => None,
}
}
pub const fn tag(self) -> u8 {
self as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Edit {
pub pos: u32,
pub val: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RangeChange {
pub start: u32,
pub end: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Residual {
XorSparse {
len: u64,
edits: Vec<Edit>,
},
RangeReplace {
len: u64,
changes: Vec<RangeChange>,
literals: Vec<u8>,
},
RansCoded {
len: u64,
enc_obj: ChunkId,
model: ChunkId,
scale_bits: u8,
codec: RansCodec,
decoded_len: u64,
},
BaseSequence {
len: u64,
enc_obj: ChunkId,
model: ChunkId,
scale_bits: u8,
codec: RansCodec,
seq_len: u32,
lit_len: u32,
off_len: u32,
cmds: u32,
lit_out: u32,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Representation {
Zero {
len: u64,
},
Fill {
value: u8,
len: u64,
},
Inline {
data: Vec<u8>,
},
Raw {
obj: ChunkId,
len: u64,
},
Rans {
model: ChunkId,
enc_obj: ChunkId,
scale_bits: u8,
codec: RansCodec,
len: u64,
},
ExactRef {
target: ChunkId,
off: u64,
len: u64,
},
BaseResidual {
base: ChunkId,
base_len: u64,
residual: Residual,
len: u64,
},
Sparse {
k: u32,
rank: u128,
literals: Vec<u8>,
len: u64,
},
Palette {
palette: Vec<u8>,
counts: Vec<u32>,
rank: u128,
len: u64,
},
Periodic {
period: u32,
pattern: Vec<u8>,
count: u32,
tail: Vec<u8>,
len: u64,
},
Permutation {
rank: u128,
alphabet: Vec<u8>,
len: u64,
},
EntropyRef {
universe: UniverseId,
seed: [u8; 16],
coordinate: u64,
transform: TransformId,
residual: Residual,
len: u64,
},
SequenceRans {
model: ChunkId,
enc_obj: ChunkId,
scale_bits: u8,
codec: RansCodec,
seq_len: u32,
lit_len: u32,
off_len: u32,
cmds: u32,
lit_out: u32,
len: u64,
},
SparseBlock64 {
model: ChunkId,
enc_obj: ChunkId,
scale_bits: u8,
codec: RansCodec,
pc_len: u32,
rank_len: u32,
lit_len: u32,
words: u32,
nonzero: u32,
lit_out: u32,
len: u64,
},
SequenceDict {
dictionary: ChunkId,
dictionary_len: u32,
model: ChunkId,
enc_obj: ChunkId,
scale_bits: u8,
codec: RansCodec,
seq_len: u32,
lit_len: u32,
off_len: u32,
src_len: u32,
cmds: u32,
lit_out: u32,
len: u64,
},
SequenceSharedDict {
dictionary: ChunkId,
dictionary_len: u32,
shared: ChunkId,
shared_len: u32,
model: ChunkId,
enc_obj: ChunkId,
scale_bits: u8,
codec: RansCodec,
seq_len: u32,
lit_len: u32,
off_len: u32,
src_len: u32,
cmds: u32,
lit_out: u32,
len: u64,
},
SequenceDeep {
model: ChunkId,
enc_obj: ChunkId,
scale_bits: u8,
codec: RansCodec,
seq_len: u32,
lit_len: u32,
off_len: u32,
len_len: u32,
cmds: u32,
lit_out: u32,
len: u64,
},
}
impl Representation {
pub const fn len(&self) -> u64 {
match self {
Representation::Zero { len }
| Representation::Fill { len, .. }
| Representation::Raw { len, .. }
| Representation::Rans { len, .. }
| Representation::ExactRef { len, .. }
| Representation::BaseResidual { len, .. }
| Representation::Sparse { len, .. }
| Representation::Palette { len, .. }
| Representation::Periodic { len, .. }
| Representation::EntropyRef { len, .. }
| Representation::Permutation { len, .. }
| Representation::SequenceRans { len, .. }
| Representation::SparseBlock64 { len, .. }
| Representation::SequenceDict { len, .. }
| Representation::SequenceSharedDict { len, .. }
| Representation::SequenceDeep { len, .. } => *len,
Representation::Inline { data } => data.len() as u64,
}
}
pub const fn is_empty(&self) -> bool {
self.len() == 0
}
pub const fn tag(&self) -> u8 {
match self {
Representation::Zero { .. } => 0x01,
Representation::Fill { .. } => 0x02,
Representation::Raw { .. } => 0x03,
Representation::Rans { .. } => 0x04,
Representation::ExactRef { .. } => 0x05,
Representation::BaseResidual { .. } => 0x06,
Representation::Sparse { .. } => 0x07,
Representation::Palette { .. } => 0x08,
Representation::Periodic { .. } => 0x09,
Representation::EntropyRef { .. } => 0x0A,
Representation::Inline { .. } => 0x0B,
Representation::Permutation { .. } => 0x0C,
Representation::SequenceRans { .. } => 0x0D,
Representation::SparseBlock64 { .. } => 0x0E,
Representation::SequenceDict { .. } => 0x0F,
Representation::SequenceSharedDict { .. } => 0x10,
Representation::SequenceDeep { .. } => 0x11,
}
}
pub const fn family(&self) -> &'static str {
match self {
Representation::Zero { .. } => "ZERO",
Representation::Fill { .. } => "FILL",
Representation::Raw { .. } => "RAW",
Representation::Rans { .. } => "RANS",
Representation::ExactRef { .. } => "EXACT_REF",
Representation::BaseResidual { .. } => "BASE_RESIDUAL",
Representation::Sparse { .. } => "SPARSE",
Representation::Palette { .. } => "PALETTE",
Representation::Periodic { .. } => "PERIODIC",
Representation::EntropyRef { .. } => "ENTROPY_REF",
Representation::Inline { .. } => "INLINE",
Representation::Permutation { .. } => "PERMUTATION",
Representation::SequenceRans { .. } => "SEQUENCE_RANS",
Representation::SparseBlock64 { .. } => "SPARSE_BLOCK64",
Representation::SequenceDict { .. } => "SEQUENCE_DICT",
Representation::SequenceSharedDict { .. } => "SEQUENCE_SHARED_DICT",
Representation::SequenceDeep { .. } => "SEQUENCE_DEEP",
}
}
pub fn encoded_size(&self) -> u64 {
let base = 5u64;
let payload: u64 = match self {
Representation::Zero { .. } => 0,
Representation::Fill { .. } => 1,
Representation::Inline { data } => data.len() as u64,
Representation::Raw { .. } => 32,
Representation::Rans { .. } => 32 + 32 + 1 + 1,
Representation::ExactRef { .. } => 32 + 4,
Representation::BaseResidual { residual, .. } => 32 + 4 + residual.encoded_size(),
Representation::Sparse { literals, .. } => 4 + 16 + literals.len() as u64,
Representation::Palette {
palette, counts, ..
} => 1 + palette.len() as u64 + 4 * counts.len() as u64 + 16,
Representation::Periodic {
period,
pattern: _,
tail,
..
} => 4 + *period as u64 + 4 + 4 + tail.len() as u64,
Representation::EntropyRef { residual, .. } => 1 + 16 + 8 + 1 + residual.encoded_size(),
Representation::Permutation { alphabet, .. } => 16 + alphabet.len() as u64,
Representation::SequenceRans { .. } => 32 + 32 + 1 + 1 + 4 + 4 + 4 + 4 + 4,
Representation::SparseBlock64 { .. } => 32 + 32 + 1 + 1 + 4 + 4 + 4 + 4 + 4 + 4,
Representation::SequenceDict { .. } => 32 + 4 + 32 + 32 + 1 + 1 + 4 + 4 + 4 + 4 + 4 + 4,
Representation::SequenceSharedDict { .. } => {
32 + 4 + 32 + 4 + 32 + 32 + 1 + 1 + 4 + 4 + 4 + 4 + 4 + 4
}
Representation::SequenceDeep { .. } => 32 + 32 + 1 + 1 + 4 + 4 + 4 + 4 + 4 + 4,
};
base + payload
}
pub fn validate(&self, limits: &crate::core::limits::Limits) -> Result<(), ReprError> {
if self.encoded_size() > limits.max_descriptor_bytes {
return Err(ReprError::DescriptorTooLarge);
}
match self {
Representation::Zero { len } => {
check_len(*len, limits)?;
}
Representation::Fill { len, .. } => {
check_len(*len, limits)?;
}
Representation::Inline { data } => {
if data.len() as u64 > limits.max_inline_bytes {
return Err(ReprError::InlineTooLarge);
}
}
Representation::Raw { obj, len } => {
check_len(*len, limits)?;
if obj.is_zero() {
return Err(ReprError::ZeroObjectId);
}
}
Representation::Rans {
model,
enc_obj,
scale_bits,
len,
..
} => {
check_len(*len, limits)?;
if model.is_zero() || enc_obj.is_zero() {
return Err(ReprError::ZeroObjectId);
}
if !(1..=16).contains(scale_bits) {
return Err(ReprError::BadScaleBits);
}
}
Representation::ExactRef { target, off, len } => {
check_len(*len, limits)?;
if target.is_zero() {
return Err(ReprError::ZeroObjectId);
}
if off.checked_add(*len).is_none() {
return Err(ReprError::Overflow);
}
}
Representation::BaseResidual {
base,
base_len,
residual,
len,
} => {
check_len(*len, limits)?;
if base.is_zero() {
return Err(ReprError::ZeroObjectId);
}
if !matches!(residual, Residual::BaseSequence { .. }) && *base_len < *len {
return Err(ReprError::BaseTooShort);
}
residual.validate(*len, limits)?;
}
Representation::Sparse {
k,
rank,
literals,
len,
} => {
check_len(*len, limits)?;
let k64 = *k as u64;
if k64 > *len {
return Err(ReprError::SparseKTooLarge);
}
if literals.len() as u64 != k64 {
return Err(ReprError::SparseLiteralCount);
}
match crate::entropy::rank::comb(*len as u128, k64 as u128) {
Some(total) if *rank < total => {}
Some(_) => return Err(ReprError::SparseRankOutOfRange),
None => return Err(ReprError::CombOverflow),
}
}
Representation::Palette {
palette,
counts,
rank,
len,
} => {
check_len(*len, limits)?;
if palette.is_empty() || palette.len() > limits.max_palette {
return Err(ReprError::BadPalette);
}
if counts.len() != palette.len() {
return Err(ReprError::BadPalette);
}
let mut total: u64 = 0;
for &c in counts.iter() {
total = total.checked_add(c as u64).ok_or(ReprError::Overflow)?;
}
if total != *len {
return Err(ReprError::PaletteCountsMismatch);
}
if counts.contains(&0) {
return Err(ReprError::BadPalette);
}
match crate::entropy::rank::multinomial(*len, counts) {
Some(total_states) if *rank < total_states => {}
Some(_) => return Err(ReprError::PaletteRankOutOfRange),
None => return Err(ReprError::CombOverflow),
}
}
Representation::Periodic {
period,
pattern,
count,
tail,
len,
} => {
check_len(*len, limits)?;
if *period == 0 || *period as u64 > limits.max_period as u64 {
return Err(ReprError::BadPeriod);
}
if pattern.len() as u64 != *period as u64 {
return Err(ReprError::BadPeriod);
}
if tail.len() as u64 >= *period as u64 {
return Err(ReprError::BadTail);
}
let expected = (*period as u64)
.checked_mul(*count as u64)
.and_then(|v| v.checked_add(tail.len() as u64))
.ok_or(ReprError::Overflow)?;
if expected != *len {
return Err(ReprError::PeriodicLenMismatch);
}
}
Representation::EntropyRef {
universe,
seed: _,
coordinate: _,
transform,
residual,
len,
} => {
check_len(*len, limits)?;
if *universe == crate::core::representation::UniverseId::UniformXofV1 {
} else {
return Err(ReprError::UnknownUniverse);
}
if *transform != crate::core::representation::TransformId::Identity {
return Err(ReprError::UnknownTransform);
}
residual.validate(*len, limits)?;
}
Representation::Permutation {
rank,
alphabet,
len,
} => {
check_len(*len, limits)?;
let m = *len;
if m == 0 || m > 34 {
return Err(ReprError::PermutationSize);
}
if alphabet.len() as u64 != m {
return Err(ReprError::BadPermutationAlphabet);
}
for w in alphabet.windows(2) {
if w[0] >= w[1] {
return Err(ReprError::BadPermutationAlphabet);
}
}
let total =
crate::entropy::rank::factorial(m as u128).ok_or(ReprError::CombOverflow)?;
if *rank >= total {
return Err(ReprError::PermutationRankOutOfRange);
}
}
Representation::SequenceRans {
model,
enc_obj,
scale_bits,
seq_len,
lit_len,
off_len,
cmds,
lit_out,
len,
..
} => {
check_len(*len, limits)?;
if model.is_zero() || enc_obj.is_zero() {
return Err(ReprError::ZeroObjectId);
}
if !(1..=16).contains(scale_bits) {
return Err(ReprError::BadScaleBits);
}
let max_stream = limits.max_chunk_size.saturating_add(64);
for s in [*seq_len, *lit_len, *off_len] {
if s as u64 > max_stream {
return Err(ReprError::SequenceStreamTooLarge);
}
}
if (*lit_out as u64) > *len {
return Err(ReprError::SequenceLitOutMismatch);
}
if *cmds == 0 && *len > 0 {
return Err(ReprError::SequenceNoCommands);
}
if (*cmds as u64) > *len {
return Err(ReprError::SequenceCmdsMismatch);
}
}
Representation::SparseBlock64 {
model,
enc_obj,
scale_bits,
pc_len,
rank_len,
lit_len,
words,
nonzero,
lit_out,
len,
..
} => {
check_len(*len, limits)?;
if model.is_zero() || enc_obj.is_zero() {
return Err(ReprError::ZeroObjectId);
}
if !(1..=16).contains(scale_bits) {
return Err(ReprError::BadScaleBits);
}
let max_stream = limits.max_chunk_size.saturating_add(64);
for s in [*pc_len, *rank_len, *lit_len] {
if s as u64 > max_stream {
return Err(ReprError::SequenceStreamTooLarge);
}
}
if (*words as u64).saturating_mul(8) < *len {
return Err(ReprError::SparseBlockWords);
}
if (*nonzero as u64) > *words as u64 {
return Err(ReprError::SparseBlockWords);
}
if (*lit_out as u64) > *len {
return Err(ReprError::SequenceLitOutMismatch);
}
if (*nonzero as u64) > *lit_out as u64 {
return Err(ReprError::SparseBlockLiteralCount);
}
}
Representation::SequenceDict {
dictionary,
dictionary_len,
model,
enc_obj,
scale_bits,
seq_len,
lit_len,
off_len,
src_len,
cmds,
lit_out,
len,
..
} => {
check_len(*len, limits)?;
if dictionary.is_zero() || model.is_zero() || enc_obj.is_zero() {
return Err(ReprError::ZeroObjectId);
}
if !(1..=16).contains(scale_bits) {
return Err(ReprError::BadScaleBits);
}
if *dictionary_len == 0
|| *dictionary_len as u64 > crate::rans::sequence::MAX_DICT as u64
|| *dictionary_len as u64 > limits.max_chunk_size
{
return Err(ReprError::BadDictionary);
}
let max_stream = limits.max_chunk_size.saturating_add(64);
for s in [*seq_len, *lit_len, *off_len, *src_len] {
if s as u64 > max_stream {
return Err(ReprError::SequenceStreamTooLarge);
}
}
if (*lit_out as u64) > *len {
return Err(ReprError::SequenceLitOutMismatch);
}
if *cmds == 0 && *len > 0 {
return Err(ReprError::SequenceNoCommands);
}
if (*cmds as u64) > *len {
return Err(ReprError::SequenceCmdsMismatch);
}
}
Representation::SequenceSharedDict {
dictionary,
dictionary_len,
shared,
shared_len,
model,
enc_obj,
scale_bits,
seq_len,
lit_len,
off_len,
src_len,
cmds,
lit_out,
len,
..
} => {
check_len(*len, limits)?;
if shared.is_zero() || model.is_zero() || enc_obj.is_zero() {
return Err(ReprError::ZeroObjectId);
}
if !(1..=16).contains(scale_bits) {
return Err(ReprError::BadScaleBits);
}
if *shared_len == 0
|| *shared_len as u64 > crate::rans::sequence::MAX_DICT as u64
|| *shared_len as u64 > limits.max_chunk_size
{
return Err(ReprError::BadDictionary);
}
let file_absent = dictionary.is_zero() && *dictionary_len == 0;
let file_present = !dictionary.is_zero()
&& *dictionary_len > 0
&& *dictionary_len as u64 <= crate::rans::sequence::MAX_DICT as u64
&& *dictionary_len as u64 <= limits.max_chunk_size;
if !file_absent && !file_present {
return Err(ReprError::BadDictionary);
}
let max_stream = limits.max_chunk_size.saturating_add(64);
for s in [*seq_len, *lit_len, *off_len, *src_len] {
if s as u64 > max_stream {
return Err(ReprError::SequenceStreamTooLarge);
}
}
if (*lit_out as u64) > *len {
return Err(ReprError::SequenceLitOutMismatch);
}
if *cmds == 0 && *len > 0 {
return Err(ReprError::SequenceNoCommands);
}
if (*cmds as u64) > *len {
return Err(ReprError::SequenceCmdsMismatch);
}
}
Representation::SequenceDeep {
model,
enc_obj,
scale_bits,
seq_len,
lit_len,
off_len,
len_len,
cmds,
lit_out,
len,
..
} => {
check_len(*len, limits)?;
if model.is_zero() || enc_obj.is_zero() {
return Err(ReprError::ZeroObjectId);
}
if !(1..=16).contains(scale_bits) {
return Err(ReprError::BadScaleBits);
}
let max_stream = limits.max_chunk_size.saturating_add(64);
for s in [*seq_len, *lit_len, *off_len, *len_len] {
if s as u64 > max_stream {
return Err(ReprError::SequenceStreamTooLarge);
}
}
if (*lit_out as u64) > *len {
return Err(ReprError::SequenceLitOutMismatch);
}
if *cmds == 0 && *len > 0 {
return Err(ReprError::SequenceNoCommands);
}
if (*cmds as u64) > *len {
return Err(ReprError::SequenceCmdsMismatch);
}
}
}
Ok(())
}
}
fn check_len(len: u64, limits: &crate::core::limits::Limits) -> Result<(), ReprError> {
if len > limits.max_chunk_size {
return Err(ReprError::ChunkTooLarge);
}
Ok(())
}
impl Residual {
pub const fn len(&self) -> u64 {
match self {
Residual::XorSparse { len, .. }
| Residual::RangeReplace { len, .. }
| Residual::RansCoded { len, .. }
| Residual::BaseSequence { len, .. } => *len,
}
}
pub const fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn encoded_size(&self) -> u64 {
match self {
Residual::XorSparse { edits, .. } => 1 + 4 + 5 * edits.len() as u64,
Residual::RangeReplace {
changes, literals, ..
} => 1 + 4 + 8 * changes.len() as u64 + literals.len() as u64,
Residual::RansCoded { .. } => 1 + 32 + 32 + 1 + 1 + 4,
Residual::BaseSequence { .. } => 1 + 32 + 32 + 1 + 1 + 4 + 4 + 4 + 4 + 4,
}
}
pub fn validate(
&self,
repr_len: u64,
limits: &crate::core::limits::Limits,
) -> Result<(), ReprError> {
if self.len() != repr_len {
return Err(ReprError::ResidualLenMismatch);
}
match self {
Residual::XorSparse { edits, .. } => {
if edits.len() as u64 > limits.max_fanout as u64 {
return Err(ReprError::FanoutTooLarge);
}
let mut prev: Option<u32> = None;
for e in edits {
if e.pos as u64 >= repr_len {
return Err(ReprError::EditOutOfRange);
}
if let Some(p) = prev {
if e.pos <= p {
return Err(ReprError::EditsNotSorted);
}
}
prev = Some(e.pos);
}
}
Residual::RangeReplace {
changes, literals, ..
} => {
if changes.len() as u64 > limits.max_fanout as u64 {
return Err(ReprError::FanoutTooLarge);
}
let mut expected_lits: u64 = 0;
let mut prev: Option<u32> = None;
for c in changes {
if c.start >= c.end || c.end as u64 > repr_len {
return Err(ReprError::RangeOutOfRange);
}
if let Some(p) = prev {
if c.start <= p {
return Err(ReprError::RangesOverlap);
}
}
prev = Some(c.end);
expected_lits = expected_lits
.checked_add((c.end - c.start) as u64)
.ok_or(ReprError::Overflow)?;
}
if literals.len() as u64 != expected_lits {
return Err(ReprError::LiteralCountMismatch);
}
}
Residual::RansCoded {
enc_obj,
model,
scale_bits,
decoded_len,
..
} => {
if enc_obj.is_zero() || model.is_zero() {
return Err(ReprError::ZeroObjectId);
}
if !(1..=16).contains(scale_bits) {
return Err(ReprError::BadScaleBits);
}
if *decoded_len != repr_len {
return Err(ReprError::ResidualLenMismatch);
}
}
Residual::BaseSequence {
enc_obj,
model,
scale_bits,
seq_len,
lit_len,
off_len,
cmds,
lit_out,
..
} => {
if enc_obj.is_zero() || model.is_zero() {
return Err(ReprError::ZeroObjectId);
}
if !(1..=16).contains(scale_bits) {
return Err(ReprError::BadScaleBits);
}
let max_stream = limits.max_chunk_size.saturating_add(64);
for s in [*seq_len, *lit_len, *off_len] {
if s as u64 > max_stream {
return Err(ReprError::SequenceStreamTooLarge);
}
}
if (*lit_out as u64) > repr_len {
return Err(ReprError::SequenceLitOutMismatch);
}
if *cmds == 0 && repr_len > 0 {
return Err(ReprError::SequenceNoCommands);
}
if (*cmds as u64) > repr_len {
return Err(ReprError::SequenceCmdsMismatch);
}
}
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReprError {
ChunkTooLarge,
ZeroObjectId,
BadScaleBits,
Overflow,
BaseTooShort,
ResidualLenMismatch,
EditOutOfRange,
EditsNotSorted,
RangeOutOfRange,
RangesOverlap,
LiteralCountMismatch,
FanoutTooLarge,
SparseKTooLarge,
SparseLiteralCount,
SparseRankOutOfRange,
CombOverflow,
BadPalette,
PaletteCountsMismatch,
PaletteRankOutOfRange,
BadPeriod,
BadTail,
PeriodicLenMismatch,
InlineTooLarge,
UnknownUniverse,
UnknownTransform,
DescriptorTooLarge,
PermutationSize,
PermutationRankOutOfRange,
BadPermutationAlphabet,
SequenceStreamTooLarge,
SequenceLitOutMismatch,
SequenceNoCommands,
SequenceCmdsMismatch,
SparseBlockWords,
SparseBlockLiteralCount,
BadDictionary,
}
impl std::fmt::Display for ReprError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
impl std::error::Error for ReprError {}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::limits::Limits;
fn l() -> Limits {
Limits::default()
}
#[test]
fn zero_valid() {
let r = Representation::Zero { len: 65536 };
assert_eq!(r.len(), 65536);
assert_eq!(r.tag(), 0x01);
r.validate(&l()).unwrap();
}
#[test]
fn zero_too_large_rejected() {
let r = Representation::Zero { len: 1 << 40 };
assert_eq!(r.validate(&l()), Err(ReprError::ChunkTooLarge));
}
#[test]
fn periodic_validation() {
let r = Representation::Periodic {
period: 4,
pattern: b"abcd".to_vec(),
count: 3,
tail: b"xy".to_vec(),
len: 14,
};
r.validate(&l()).unwrap();
let bad = Representation::Periodic {
period: 4,
pattern: b"abcd".to_vec(),
count: 3,
tail: b"xy".to_vec(),
len: 15,
};
assert_eq!(bad.validate(&l()), Err(ReprError::PeriodicLenMismatch));
}
#[test]
fn sparse_validation() {
let r = Representation::Sparse {
k: 3,
rank: 55,
literals: vec![1, 2, 3],
len: 8,
};
r.validate(&l()).unwrap();
let bad = Representation::Sparse {
k: 3,
rank: 56,
literals: vec![1, 2, 3],
len: 8,
};
assert_eq!(bad.validate(&l()), Err(ReprError::SparseRankOutOfRange));
}
#[test]
fn residual_edits_sorted() {
let res = Residual::XorSparse {
len: 8,
edits: vec![Edit { pos: 5, val: 1 }, Edit { pos: 3, val: 2 }],
};
assert_eq!(res.validate(8, &l()), Err(ReprError::EditsNotSorted));
}
}