use alloc::boxed::Box;
use alloc::vec::Vec;
use alloc::borrow::Cow;
use thiserror::Error;
use super::transform::{TransformList, TransformListError};
use super::varint::{self, VarintError};
use super::words::{MAX_SIZE_BITS, MIN_WORD_LENGTH, NUM_ENCODED_LENGTHS, WordList, WordListError};
pub(crate) const MAGIC: [u8; 2] = [0x91, 0x00];
pub(crate) const MAX_LISTS: usize = 64;
pub(crate) const NUM_CONTEXTS: usize = 64;
pub(crate) const MAX_LZ77_DICTIONARY_LENGTH: u64 = (1 << 30) - 16;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum ListRef {
Builtin,
Custom(u8),
}
impl ListRef {
const fn encode(self, custom: u8) -> u8 {
match self {
Self::Builtin => custom,
Self::Custom(index) => index,
}
}
const fn decode(byte: u8, custom: u8) -> Option<Self> {
if byte == custom {
Some(Self::Builtin)
} else if byte < custom {
Some(Self::Custom(byte))
} else {
None
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) struct Combination {
pub(crate) words: ListRef,
pub(crate) transforms: ListRef,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) struct SerializedLimits {
pub(crate) max_total_bytes: u64,
pub(crate) max_prefix_bytes: u64,
pub(crate) max_word_lists: usize,
pub(crate) max_word_bytes: u64,
pub(crate) max_transform_lists: usize,
pub(crate) max_transform_bytes: u64,
pub(crate) max_combinations: usize,
}
impl SerializedLimits {
#[cfg(test)]
pub(crate) const fn permissive() -> Self {
Self {
max_total_bytes: u64::MAX,
max_prefix_bytes: MAX_LZ77_DICTIONARY_LENGTH,
max_word_lists: MAX_LISTS,
max_word_bytes: u64::MAX,
max_transform_lists: MAX_LISTS,
max_transform_bytes: u64::MAX,
max_combinations: MAX_LISTS,
}
}
}
#[derive(Error, Debug, Clone, Eq, PartialEq)]
pub(crate) enum SerializedError {
#[error("a serialized dictionary starts with {MAGIC:02X?}, not {found:02X?}")]
BadMagic {
found: Vec<u8>,
},
#[error("the dictionary ends after {position} bytes, mid-{field}")]
Truncated {
field: &'static str,
position: usize,
},
#[error("the {field} varint is malformed: {source}")]
Varint {
field: &'static str,
#[source]
source: VarintError,
},
#[error("the {field} flag must be 0 or 1, not {value}")]
NotABoolean {
field: &'static str,
value: u8,
},
#[error("an LZ77 prefix of {length} bytes exceeds the format's {MAX_LZ77_DICTIONARY_LENGTH}")]
PrefixTooLongForFormat {
length: u64,
},
#[error("a dictionary holds at most {MAX_LISTS} {what}, not {count}")]
TooManyLists {
what: &'static str,
count: usize,
},
#[error("a dictionary with custom lists must declare at least one combination")]
NoCombinations,
#[error("a combination names {what} {index}, past the {available} available")]
UndefinedList {
what: &'static str,
index: u8,
available: u8,
},
#[error("context {context} maps to combination {index}, past the {available} declared")]
UndefinedCombination {
context: usize,
index: u8,
available: usize,
},
#[error("word list {index} is malformed: {source}")]
WordList {
index: usize,
#[source]
source: WordListError,
},
#[error("transform list {index} is malformed: {source}")]
TransformList {
index: usize,
#[source]
source: TransformListError,
},
#[error("the dictionary's {what} of {found} exceeds the limit of {limit}")]
LimitExceeded {
what: &'static str,
found: u64,
limit: u64,
},
#[error("{extra} byte(s) follow the end of the dictionary")]
TrailingBytes {
extra: usize,
},
}
#[derive(Debug, Clone, Default)]
pub(crate) struct SerializedDictionaryData {
prefix: Option<Box<[u8]>>,
word_lists: Vec<WordList>,
transform_lists: Vec<TransformList>,
combinations: Vec<Combination>,
context_map: Option<[u8; NUM_CONTEXTS]>,
}
impl SerializedDictionaryData {
pub(crate) fn allocation_bound(&self) -> usize {
self.prefix()
.len()
.saturating_add(self.word_lists.capacity() * size_of::<WordList>())
.saturating_add(self.transform_lists.capacity() * size_of::<TransformList>())
.saturating_add(self.combinations.capacity() * size_of::<Combination>())
.saturating_add(
self.word_lists
.iter()
.map(|w| w.data().len())
.sum::<usize>(),
)
.saturating_add(
self.transform_lists
.iter()
.map(|t| t.wire_len() + t.stringlet_count() * size_of::<u16>())
.sum::<usize>(),
)
}
pub(crate) fn prefix(&self) -> &[u8] {
self.prefix.as_deref().unwrap_or_default()
}
pub(crate) fn has_prefix(&self) -> bool {
self.prefix.is_some()
}
pub(crate) fn word_lists(&self) -> &[WordList] {
&self.word_lists
}
pub(crate) fn transform_lists(&self) -> &[TransformList] {
&self.transform_lists
}
pub(crate) fn combinations(&self) -> &[Combination] {
&self.combinations
}
pub(crate) fn context_map(&self) -> Option<&[u8; NUM_CONTEXTS]> {
self.context_map.as_ref()
}
pub(crate) fn is_custom_static(&self) -> bool {
!self.word_lists.is_empty() || !self.transform_lists.is_empty()
}
pub(crate) fn assemble(
prefix: Option<Box<[u8]>>,
word_lists: Vec<WordList>,
transform_lists: Vec<TransformList>,
combinations: Vec<Combination>,
context_map: Option<[u8; NUM_CONTEXTS]>,
limits: &SerializedLimits,
) -> Result<Self, SerializedError> {
check_count("word lists", word_lists.len(), limits.max_word_lists)?;
check_count(
"transform lists",
transform_lists.len(),
limits.max_transform_lists,
)?;
check_count("combinations", combinations.len(), limits.max_combinations)?;
let prefix_len = prefix.as_ref().map_or(0, |bytes| bytes.len() as u64);
if prefix_len > MAX_LZ77_DICTIONARY_LENGTH {
return Err(SerializedError::PrefixTooLongForFormat { length: prefix_len });
}
check_limit("LZ77 prefix", prefix_len, limits.max_prefix_bytes)?;
let word_bytes = word_lists
.iter()
.map(|list| list.data().len() as u64)
.sum::<u64>();
check_limit("word data", word_bytes, limits.max_word_bytes)?;
let transform_bytes = transform_lists
.iter()
.map(|list| list.wire_len() as u64)
.sum::<u64>();
check_limit(
"transform data",
transform_bytes,
limits.max_transform_bytes,
)?;
let custom = !word_lists.is_empty() || !transform_lists.is_empty();
if custom && combinations.is_empty() {
return Err(SerializedError::NoCombinations);
}
let words_available = word_lists.len() as u8;
let transforms_available = transform_lists.len() as u8;
for combination in &combinations {
check_reference("word list", combination.words, words_available)?;
check_reference(
"transform list",
combination.transforms,
transforms_available,
)?;
}
if let Some(map) = &context_map {
for (context, &index) in map.iter().enumerate() {
if usize::from(index) >= combinations.len() {
return Err(SerializedError::UndefinedCombination {
context,
index,
available: combinations.len(),
});
}
}
}
Ok(Self {
prefix,
word_lists,
transform_lists,
combinations: if custom { combinations } else { Vec::new() },
context_map: if custom { context_map } else { None },
})
}
pub(crate) fn wire_len(&self) -> usize {
let prefix = self.prefix();
let mut len = MAGIC.len() + varint::encoded_len(prefix.len() as u64) + prefix.len();
len += 1 + self
.word_lists
.iter()
.map(WordList::wire_len)
.sum::<usize>();
len += 1 + self
.transform_lists
.iter()
.map(TransformList::wire_len)
.sum::<usize>();
if self.is_custom_static() {
len += 1 + self.combinations.len() * 2 + 1;
if self.context_map.is_some() {
len += NUM_CONTEXTS;
}
}
len
}
pub(crate) fn serialize(&self, out: &mut Vec<u8>) -> Result<(), SerializedError> {
out.reserve(self.wire_len());
out.extend_from_slice(&MAGIC);
let prefix = self.prefix();
match varint::write(prefix.len() as u64, out) {
Ok(()) => {}
Err(source) => {
return Err(SerializedError::Varint {
field: "LZ77_DICTIONARY_LENGTH",
source,
});
}
}
out.extend_from_slice(prefix);
out.push(u8::try_from(self.word_lists.len()).unwrap_or(u8::MAX));
for list in &self.word_lists {
list.serialize(out);
}
out.push(u8::try_from(self.transform_lists.len()).unwrap_or(u8::MAX));
for list in &self.transform_lists {
list.serialize(out);
}
if self.is_custom_static() {
out.push(u8::try_from(self.combinations.len()).unwrap_or(u8::MAX));
let words = u8::try_from(self.word_lists.len()).unwrap_or(u8::MAX);
let transforms = u8::try_from(self.transform_lists.len()).unwrap_or(u8::MAX);
for combination in &self.combinations {
out.push(combination.words.encode(words));
out.push(combination.transforms.encode(transforms));
}
match &self.context_map {
Some(map) => {
out.push(1);
out.extend_from_slice(map);
}
None => out.push(0),
}
}
Ok(())
}
}
fn check_count(what: &'static str, count: usize, limit: usize) -> Result<(), SerializedError> {
if count > MAX_LISTS {
return Err(SerializedError::TooManyLists { what, count });
}
check_limit(what, count as u64, limit as u64)
}
fn check_limit(what: &'static str, found: u64, limit: u64) -> Result<(), SerializedError> {
if found > limit {
return Err(SerializedError::LimitExceeded { what, found, limit });
}
Ok(())
}
fn check_reference(
what: &'static str,
reference: ListRef,
available: u8,
) -> Result<(), SerializedError> {
match reference {
ListRef::Custom(index) if index >= available => Err(SerializedError::UndefinedList {
what,
index,
available,
}),
_ => Ok(()),
}
}
struct Reader<'a> {
bytes: &'a [u8],
position: usize,
}
impl<'a> Reader<'a> {
const fn new(bytes: &'a [u8]) -> Self {
Self { bytes, position: 0 }
}
fn u8(&mut self, field: &'static str) -> Result<u8, SerializedError> {
let Some(&byte) = self.bytes.get(self.position) else {
return Err(self.truncated(field));
};
self.position += 1;
Ok(byte)
}
fn boolean(&mut self, field: &'static str) -> Result<bool, SerializedError> {
match self.u8(field)? {
0 => Ok(false),
1 => Ok(true),
value => Err(SerializedError::NotABoolean { field, value }),
}
}
fn u16_le(&mut self, field: &'static str) -> Result<u16, SerializedError> {
let Some(chunk) = self
.bytes
.get(self.position..)
.and_then(<[u8]>::first_chunk::<2>)
else {
return Err(self.truncated(field));
};
self.position += 2;
Ok(u16::from_le_bytes(*chunk))
}
fn varint(&mut self, field: &'static str) -> Result<u64, SerializedError> {
let rest = self.bytes.get(self.position..).unwrap_or_default();
let (value, len) =
varint::read(rest).map_err(|source| SerializedError::Varint { field, source })?;
self.position += len;
Ok(value)
}
fn take(&mut self, field: &'static str, count: usize) -> Result<&'a [u8], SerializedError> {
let end = self
.position
.checked_add(count)
.ok_or(self.truncated(field))?;
let Some(slice) = self.bytes.get(self.position..end) else {
return Err(self.truncated(field));
};
self.position = end;
Ok(slice)
}
fn array<const N: usize>(&mut self, field: &'static str) -> Result<[u8; N], SerializedError> {
let mut out = [0u8; N];
out.copy_from_slice(self.take(field, N)?);
Ok(out)
}
fn truncated(&self, field: &'static str) -> SerializedError {
SerializedError::Truncated {
field,
position: self.bytes.len(),
}
}
}
pub(crate) fn parse(
bytes: &[u8],
limits: &SerializedLimits,
) -> Result<(SerializedDictionaryData, usize), SerializedError> {
check_limit("total size", bytes.len() as u64, limits.max_total_bytes)?;
let mut reader = Reader::new(bytes);
let magic = reader
.array::<2>("magic")
.map_err(|_| SerializedError::BadMagic {
found: bytes.get(..bytes.len().min(2)).unwrap_or_default().to_vec(),
})?;
if magic != MAGIC {
return Err(SerializedError::BadMagic {
found: magic.to_vec(),
});
}
let prefix_len = reader.varint("LZ77_DICTIONARY_LENGTH")?;
if prefix_len > MAX_LZ77_DICTIONARY_LENGTH {
return Err(SerializedError::PrefixTooLongForFormat { length: prefix_len });
}
check_limit("LZ77 prefix", prefix_len, limits.max_prefix_bytes)?;
let prefix = if prefix_len == 0 {
None
} else {
let Ok(count) = usize::try_from(prefix_len) else {
return Err(SerializedError::LimitExceeded {
what: "LZ77 prefix",
found: prefix_len,
limit: usize::MAX as u64,
});
};
Some(Box::from(reader.take("the LZ77 prefix", count)?))
};
let num_word_lists = usize::from(reader.u8("NUM_CUSTOM_WORD_LISTS")?);
check_count("word lists", num_word_lists, limits.max_word_lists)?;
let mut word_bytes = 0u64;
let mut word_lists = Vec::with_capacity(num_word_lists);
for index in 0..num_word_lists {
let size_bits = reader.array::<NUM_ENCODED_LENGTHS>("SIZE_BITS_BY_LENGTH")?;
let expected = word_data_len(&size_bits)
.map_err(|source| SerializedError::WordList { index, source })?;
word_bytes += expected as u64;
check_limit("word data", word_bytes, limits.max_word_bytes)?;
let data = reader.take("a word list's words", expected)?;
let list = match WordList::from_parts(&size_bits, Cow::Owned(data.to_vec())) {
Ok(list) => list,
Err(source) => return Err(SerializedError::WordList { index, source }),
};
word_lists.push(list);
}
let num_transform_lists = usize::from(reader.u8("NUM_CUSTOM_TRANSFORM_LISTS")?);
check_count(
"transform lists",
num_transform_lists,
limits.max_transform_lists,
)?;
let mut transform_bytes = 0u64;
let mut transform_lists = Vec::with_capacity(num_transform_lists);
for index in 0..num_transform_lists {
let list = parse_transform_list(&mut reader, index)?;
transform_bytes += list.wire_len() as u64;
check_limit(
"transform data",
transform_bytes,
limits.max_transform_bytes,
)?;
transform_lists.push(list);
}
let custom = num_word_lists > 0 || num_transform_lists > 0;
let mut combinations = Vec::new();
let mut context_map = None;
if custom {
let count = usize::from(reader.u8("NUM_DICTIONARIES")?);
if count == 0 {
return Err(SerializedError::NoCombinations);
}
check_count("combinations", count, limits.max_combinations)?;
combinations.reserve(count);
let words_available = num_word_lists as u8;
let transforms_available = num_transform_lists as u8;
for _ in 0..count {
let words = reader.u8("a combination's word list index")?;
let words =
ListRef::decode(words, words_available).ok_or(SerializedError::UndefinedList {
what: "word list",
index: words,
available: words_available,
})?;
let transforms = reader.u8("a combination's transform list index")?;
let transforms = ListRef::decode(transforms, transforms_available).ok_or(
SerializedError::UndefinedList {
what: "transform list",
index: transforms,
available: transforms_available,
},
)?;
combinations.push(Combination { words, transforms });
}
if reader.boolean("CONTEXT_ENABLED")? {
context_map = Some(reader.array::<NUM_CONTEXTS>("CONTEXT_MAP")?);
}
}
let consumed = reader.position;
let dictionary = SerializedDictionaryData::assemble(
prefix,
word_lists,
transform_lists,
combinations,
context_map,
limits,
)?;
Ok((dictionary, consumed))
}
pub(crate) fn parse_exact(
bytes: &[u8],
limits: &SerializedLimits,
) -> Result<SerializedDictionaryData, SerializedError> {
let (dictionary, consumed) = parse(bytes, limits)?;
match bytes.len().checked_sub(consumed) {
Some(0) | None => Ok(dictionary),
Some(extra) => Err(SerializedError::TrailingBytes { extra }),
}
}
fn word_data_len(size_bits: &[u8; NUM_ENCODED_LENGTHS]) -> Result<usize, WordListError> {
let mut total = 0usize;
for (index, &bits) in size_bits.iter().enumerate() {
let length = MIN_WORD_LENGTH + index;
if bits > MAX_SIZE_BITS {
return Err(WordListError::TooManySizeBits { length, bits });
}
if bits != 0 {
total += length << bits;
}
}
Ok(total)
}
fn parse_transform_list(
reader: &mut Reader<'_>,
index: usize,
) -> Result<TransformList, SerializedError> {
let block_len = usize::from(reader.u16_le("PREFIX_SUFFIX_LENGTH")?);
let block = reader.take("a transform list's prefix and suffix data", block_len)?;
let count = usize::from(reader.u8("NTRANSFORMS")?);
let triples = reader
.take("a transform list's transforms", count * 3)?
.to_vec();
let shifts = triples
.as_chunks::<3>()
.0
.iter()
.any(|triple| triple[1] == SHIFT_FIRST || triple[1] == SHIFT_ALL);
let params = if shifts {
reader
.take("a transform list's parameters", count * 2)?
.to_vec()
} else {
Vec::new()
};
TransformList::from_parts(
Cow::Owned(block.to_vec()),
Cow::Owned(triples),
Cow::Owned(params),
)
.map_err(|source| SerializedError::TransformList { index, source })
}
const SHIFT_FIRST: u8 = 21;
const SHIFT_ALL: u8 = 22;
#[cfg(test)]
mod tests {
use super::*;
fn minimal() -> Vec<u8> {
vec![MAGIC[0], MAGIC[1], 0, 0, 0]
}
fn parse_permissive(bytes: &[u8]) -> Result<SerializedDictionaryData, SerializedError> {
parse_exact(bytes, &SerializedLimits::permissive())
}
#[test]
fn truncated_transform_list_from_timeout_corpus_is_rejected() {
let bytes = [
0x91, 0x00, 0x0b, 0x61, 0x62, 0x96, 0x00, 0x03, 0x61, 0x62, 0x63, 0x10, 0x00, 0x63,
0x00, 0x1e,
];
assert!(matches!(
parse_permissive(&bytes),
Err(SerializedError::Truncated { position: 16, .. })
));
}
#[test]
fn overflowing_read_length_preserves_cursor() {
let mut reader = Reader::new(&[1, 2]);
assert_eq!(reader.u8("first").expect("first byte"), 1);
assert!(matches!(
reader.take("overflow", usize::MAX),
Err(SerializedError::Truncated { .. })
));
assert_eq!(reader.u8("second").expect("cursor unchanged"), 2);
}
fn round_trip(dictionary: &SerializedDictionaryData) -> SerializedDictionaryData {
let mut bytes = Vec::new();
dictionary.serialize(&mut bytes).expect("in range");
assert_eq!(bytes.len(), dictionary.wire_len());
let parsed = parse_permissive(&bytes).expect("what was written parses");
let mut again = Vec::new();
parsed.serialize(&mut again).expect("in range");
assert_eq!(again, bytes, "serializing is stable across a round trip");
parsed
}
fn with_word_list(words: &[u8], length: usize) -> SerializedDictionaryData {
let mut size_bits = [0u8; NUM_ENCODED_LENGTHS];
size_bits[length - MIN_WORD_LENGTH] = 1;
let list =
WordList::from_parts(&size_bits, Cow::Owned(words.to_vec())).expect("the parts agree");
SerializedDictionaryData::assemble(
None,
vec![list],
Vec::new(),
vec![Combination {
words: ListRef::Custom(0),
transforms: ListRef::Builtin,
}],
None,
&SerializedLimits::permissive(),
)
.expect("the parts are consistent")
}
#[test]
fn the_shortest_dictionary_parses() {
let parsed = parse_permissive(&minimal()).expect("valid");
assert!(!parsed.has_prefix());
assert!(parsed.word_lists().is_empty());
assert!(parsed.transform_lists().is_empty());
assert!(parsed.combinations().is_empty());
assert!(!parsed.is_custom_static());
}
#[test]
fn a_prefix_only_dictionary_round_trips() {
let dictionary = SerializedDictionaryData::assemble(
Some(Box::from(&b"a prefix"[..])),
Vec::new(),
Vec::new(),
Vec::new(),
None,
&SerializedLimits::permissive(),
)
.expect("valid");
let parsed = round_trip(&dictionary);
assert_eq!(parsed.prefix(), b"a prefix");
assert!(parsed.has_prefix());
}
#[test]
fn a_word_list_dictionary_round_trips() {
let dictionary = with_word_list(b"abcdefgh", 4);
let parsed = round_trip(&dictionary);
assert_eq!(parsed.word_lists().len(), 1);
assert_eq!(parsed.word_lists()[0].word(4, 1), b"efgh");
assert_eq!(parsed.combinations().len(), 1);
assert_eq!(parsed.combinations()[0].words, ListRef::Custom(0));
assert_eq!(parsed.combinations()[0].transforms, ListRef::Builtin);
}
#[test]
fn a_transform_list_dictionary_round_trips() {
let transforms = TransformList::from_parts(
Cow::Owned(vec![1, b'!', 0]),
Cow::Owned(vec![0, 0, 1]),
Cow::Owned(Vec::new()),
)
.expect("well formed");
let dictionary = SerializedDictionaryData::assemble(
None,
Vec::new(),
vec![transforms],
vec![Combination {
words: ListRef::Builtin,
transforms: ListRef::Custom(0),
}],
None,
&SerializedLimits::permissive(),
)
.expect("valid");
let parsed = round_trip(&dictionary);
assert_eq!(parsed.transform_lists().len(), 1);
assert_eq!(parsed.transform_lists()[0].stringlet(0), b"!");
}
#[test]
fn a_context_map_round_trips() {
let mut map = [0u8; NUM_CONTEXTS];
map[7] = 1;
let mut size_bits = [0u8; NUM_ENCODED_LENGTHS];
size_bits[0] = 1;
let list = WordList::from_parts(&size_bits, Cow::Owned(b"abcdefgh".to_vec()))
.expect("the parts agree");
let dictionary = SerializedDictionaryData::assemble(
None,
vec![list],
Vec::new(),
vec![
Combination {
words: ListRef::Custom(0),
transforms: ListRef::Builtin,
},
Combination {
words: ListRef::Builtin,
transforms: ListRef::Builtin,
},
],
Some(map),
&SerializedLimits::permissive(),
)
.expect("valid");
let parsed = round_trip(&dictionary);
assert_eq!(parsed.context_map().map(|map| map[7]), Some(1));
assert_eq!(parsed.context_map().map(|map| map[0]), Some(0));
}
#[test]
fn the_wrong_magic_is_refused() {
assert!(matches!(
parse_permissive(&[0x91, 0x01, 0, 0, 0]),
Err(SerializedError::BadMagic { .. })
));
assert!(matches!(
parse_permissive(&[0x00]),
Err(SerializedError::BadMagic { .. })
));
assert!(matches!(
parse_permissive(&[]),
Err(SerializedError::BadMagic { .. })
));
}
#[test]
fn every_truncation_of_a_valid_dictionary_is_refused() {
let dictionary = with_word_list(b"abcdefgh", 4);
let mut bytes = Vec::new();
dictionary.serialize(&mut bytes).expect("in range");
for cut in 0..bytes.len() {
assert!(
parse_permissive(&bytes[..cut]).is_err(),
"a dictionary cut to {cut} bytes was accepted"
);
}
assert!(parse_permissive(&bytes).is_ok());
}
#[test]
fn trailing_bytes_are_refused_but_reported_by_the_prefix_parser() {
let mut bytes = minimal();
bytes.extend_from_slice(b"tail");
assert_eq!(
parse_permissive(&bytes).err(),
Some(SerializedError::TrailingBytes { extra: 4 })
);
let (_, consumed) =
parse(&bytes, &SerializedLimits::permissive()).expect("the head parses");
assert_eq!(consumed, bytes.len() - 4);
}
#[test]
fn a_prefix_past_the_format_ceiling_is_refused() {
let mut bytes = vec![MAGIC[0], MAGIC[1]];
varint::write(MAX_LZ77_DICTIONARY_LENGTH + 1, &mut bytes).expect("in range");
assert_eq!(
parse_permissive(&bytes).err(),
Some(SerializedError::PrefixTooLongForFormat {
length: MAX_LZ77_DICTIONARY_LENGTH + 1,
})
);
}
#[test]
fn a_prefix_past_the_caller_limit_is_refused_before_it_is_copied() {
let mut bytes = vec![MAGIC[0], MAGIC[1]];
varint::write(1 << 20, &mut bytes).expect("in range");
let limits = SerializedLimits {
max_prefix_bytes: 16,
..SerializedLimits::permissive()
};
assert_eq!(
parse_exact(&bytes, &limits).err(),
Some(SerializedError::LimitExceeded {
what: "LZ77 prefix",
found: 1 << 20,
limit: 16,
})
);
}
#[test]
fn a_stream_larger_than_the_limit_is_refused_before_it_is_read() {
let limits = SerializedLimits {
max_total_bytes: 2,
..SerializedLimits::permissive()
};
assert_eq!(
parse_exact(&minimal(), &limits).err(),
Some(SerializedError::LimitExceeded {
what: "total size",
found: 5,
limit: 2,
})
);
}
#[test]
fn more_than_sixty_four_word_lists_are_refused() {
let bytes = vec![MAGIC[0], MAGIC[1], 0, 65];
assert_eq!(
parse_permissive(&bytes).err(),
Some(SerializedError::TooManyLists {
what: "word lists",
count: 65,
})
);
}
#[test]
fn more_than_sixty_four_transform_lists_are_refused() {
let bytes = vec![MAGIC[0], MAGIC[1], 0, 0, 65];
assert_eq!(
parse_permissive(&bytes).err(),
Some(SerializedError::TooManyLists {
what: "transform lists",
count: 65,
})
);
}
#[test]
fn a_word_list_count_past_the_caller_limit_is_refused() {
let mut size_bits = [0u8; NUM_ENCODED_LENGTHS];
size_bits[0] = 1;
let mut bytes = vec![MAGIC[0], MAGIC[1], 0, 1];
bytes.extend_from_slice(&size_bits);
bytes.extend_from_slice(b"abcdefgh");
bytes.extend_from_slice(&[0, 1, 0, 64, 0]);
let limits = SerializedLimits {
max_word_lists: 0,
..SerializedLimits::permissive()
};
assert_eq!(
parse_exact(&bytes, &limits).err(),
Some(SerializedError::LimitExceeded {
what: "word lists",
found: 1,
limit: 0,
})
);
}
#[test]
fn a_dictionary_with_custom_lists_and_no_combinations_is_refused() {
let mut size_bits = [0u8; NUM_ENCODED_LENGTHS];
size_bits[0] = 1;
let mut bytes = vec![MAGIC[0], MAGIC[1], 0, 1];
bytes.extend_from_slice(&size_bits);
bytes.extend_from_slice(b"abcdefgh");
bytes.extend_from_slice(&[0, 0]);
assert_eq!(
parse_permissive(&bytes).err(),
Some(SerializedError::NoCombinations)
);
}
#[test]
fn a_combination_naming_a_missing_word_list_is_refused() {
let mut size_bits = [0u8; NUM_ENCODED_LENGTHS];
size_bits[0] = 1;
let mut bytes = vec![MAGIC[0], MAGIC[1], 0, 1];
bytes.extend_from_slice(&size_bits);
bytes.extend_from_slice(b"abcdefgh");
bytes.extend_from_slice(&[0, 1, 2, 0, 0]);
assert_eq!(
parse_permissive(&bytes).err(),
Some(SerializedError::UndefinedList {
what: "word list",
index: 2,
available: 1,
})
);
}
#[test]
fn a_context_entry_naming_a_missing_combination_is_refused() {
let mut size_bits = [0u8; NUM_ENCODED_LENGTHS];
size_bits[0] = 1;
let mut bytes = vec![MAGIC[0], MAGIC[1], 0, 1];
bytes.extend_from_slice(&size_bits);
bytes.extend_from_slice(b"abcdefgh");
bytes.extend_from_slice(&[0, 1, 0, 0, 1]);
bytes.extend_from_slice(&[3u8; NUM_CONTEXTS]);
assert_eq!(
parse_permissive(&bytes).err(),
Some(SerializedError::UndefinedCombination {
context: 0,
index: 3,
available: 1,
})
);
}
#[test]
fn a_context_flag_that_is_not_a_boolean_is_refused() {
let mut size_bits = [0u8; NUM_ENCODED_LENGTHS];
size_bits[0] = 1;
let mut bytes = vec![MAGIC[0], MAGIC[1], 0, 1];
bytes.extend_from_slice(&size_bits);
bytes.extend_from_slice(b"abcdefgh");
bytes.extend_from_slice(&[0, 1, 0, 0, 2]);
assert_eq!(
parse_permissive(&bytes).err(),
Some(SerializedError::NotABoolean {
field: "CONTEXT_ENABLED",
value: 2,
})
);
}
#[test]
fn a_zero_combination_count_is_refused() {
let mut size_bits = [0u8; NUM_ENCODED_LENGTHS];
size_bits[0] = 1;
let mut bytes = vec![MAGIC[0], MAGIC[1], 0, 1];
bytes.extend_from_slice(&size_bits);
bytes.extend_from_slice(b"abcdefgh");
bytes.extend_from_slice(&[0, 0, 0]);
assert_eq!(
parse_permissive(&bytes).err(),
Some(SerializedError::NoCombinations)
);
}
#[test]
fn a_malformed_varint_names_its_field() {
let bytes = vec![MAGIC[0], MAGIC[1], 0xFF];
assert!(matches!(
parse_permissive(&bytes),
Err(SerializedError::Varint {
field: "LZ77_DICTIONARY_LENGTH",
..
})
));
}
#[test]
fn a_noncanonical_prefix_length_is_accepted_as_the_rfc_allows() {
let bytes = vec![MAGIC[0], MAGIC[1], 0x80, 0x00, 0, 0];
let parsed = parse_permissive(&bytes).expect("valid");
assert!(!parsed.has_prefix());
let mut written = Vec::new();
parsed.serialize(&mut written).expect("in range");
assert_eq!(written, minimal());
}
#[test]
fn a_combination_may_name_the_builtin_lists() {
let mut bytes = vec![MAGIC[0], MAGIC[1], 0, 0, 1];
bytes.extend_from_slice(&[1, 0, 0, 1, 0, 0, 0]);
bytes.extend_from_slice(&[1, 0, 0, 0]);
let parsed = parse_permissive(&bytes).expect("valid");
assert_eq!(parsed.combinations()[0].words, ListRef::Builtin);
assert_eq!(parsed.combinations()[0].transforms, ListRef::Custom(0));
}
#[test]
fn assembling_without_custom_lists_drops_the_combination_block() {
let dictionary = SerializedDictionaryData::assemble(
Some(Box::from(&b"prefix"[..])),
Vec::new(),
Vec::new(),
vec![Combination {
words: ListRef::Builtin,
transforms: ListRef::Builtin,
}],
Some([0; NUM_CONTEXTS]),
&SerializedLimits::permissive(),
)
.expect("valid");
assert!(dictionary.combinations().is_empty());
assert!(dictionary.context_map().is_none());
}
#[test]
fn a_prefix_longer_than_the_format_allows_is_refused_when_assembled() {
let limits = SerializedLimits {
max_prefix_bytes: 4,
..SerializedLimits::permissive()
};
let outcome = SerializedDictionaryData::assemble(
Some(Box::from(&b"too long"[..])),
Vec::new(),
Vec::new(),
Vec::new(),
None,
&limits,
);
assert_eq!(
outcome.err(),
Some(SerializedError::LimitExceeded {
what: "LZ77 prefix",
found: 8,
limit: 4,
})
);
}
#[test]
fn the_list_reference_encoding_names_the_builtin_by_the_custom_count() {
assert_eq!(ListRef::Builtin.encode(3), 3);
assert_eq!(ListRef::Custom(1).encode(3), 1);
assert_eq!(ListRef::decode(3, 3), Some(ListRef::Builtin));
assert_eq!(ListRef::decode(1, 3), Some(ListRef::Custom(1)));
assert_eq!(ListRef::decode(4, 3), None);
assert_eq!(ListRef::decode(0, 0), Some(ListRef::Builtin));
}
}