use crate::{Error, Limits, Payload, Result};
use std::mem;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct ContextState {
pub state_index: u8,
pub mps: bool,
}
#[derive(Debug)]
pub struct ContextBank {
states: Vec<ContextState>,
}
impl ContextBank {
pub fn new(count: usize, limits: &Limits) -> Result<Self> {
let invalid = || Error::invalid(INVALID_CONTEXT);
if count == 0 {
return Err(invalid());
}
let bytes = count
.checked_mul(mem::size_of::<ContextState>())
.and_then(|value| u64::try_from(value).ok())
.ok_or_else(invalid)?;
limits.check_allocation(bytes)?;
let mut states = Vec::new();
states
.try_reserve_exact(count)
.map_err(|_| limits.allocation_refused("arithmetic context bytes", bytes))?;
states.resize(count, ContextState::default());
Ok(Self { states })
}
pub fn reset(&mut self) {
self.states.fill(ContextState::default());
}
pub fn get(&self, index: usize) -> Option<ContextState> {
self.states.get(index).copied()
}
pub fn len(&self) -> usize {
self.states.len()
}
pub fn is_empty(&self) -> bool {
self.states.is_empty()
}
pub(crate) fn state(&self, index: usize) -> ContextState {
self.states[index]
}
pub(crate) fn update(&mut self, index: usize, state: ContextState) {
self.states[index] = state;
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct CodedSpan {
pub offset: u64,
pub length: u64,
}
pub(crate) const INVALID_CONTEXT: &str = "invalid arithmetic context index or count";
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ArithmeticSnapshot {
pub interval: u32,
pub code: u32,
pub bit_counter: u8,
pub input_offset: u64,
pub synthesized_inputs: u64,
pub symbols_decoded: u64,
}
pub(crate) fn check_span<'a>(
span: CodedSpan,
input: Payload<'a>,
limits: &Limits,
) -> Result<&'a [u8]> {
limits
.check_input_size(span.length)
.map_err(|error| error.at(span.offset))?;
span.offset
.checked_add(span.length)
.ok_or_else(|| Error::invalid("coded span end overflows u64").at(span.offset))?;
input
.get(span.offset, span.length)
.ok_or_else(|| Error::invalid(OUTSIDE_SOURCE).at(span.offset))
}
pub(crate) const OUTSIDE_SOURCE: &str = "coded span is outside the source";
pub(crate) const SYMBOL_COUNT: &str = "decoded symbol count differs from the expected count";
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct Counters {
pub symbols_decoded: u64,
pub synthesized_inputs: u64,
}
impl Counters {
pub(crate) fn snapshot(
&self,
interval: u32,
code: u32,
bit_counter: u8,
input_offset: u64,
) -> ArithmeticSnapshot {
ArithmeticSnapshot {
interval,
code,
bit_counter,
input_offset,
synthesized_inputs: self.synthesized_inputs,
symbols_decoded: self.symbols_decoded,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn context_banks_are_nonempty_bounded_and_reset_to_state_zero() {
let limits = Limits::default();
let empty = ContextBank::new(0, &limits).unwrap_err();
assert!(matches!(empty.kind, crate::ErrorKind::Malformed));
assert_eq!(
empty.to_string(),
"invalid input: invalid arithmetic context index or count"
);
let tiny = Limits {
io_chunk_bytes: 1,
max_allocation_bytes: 1,
..limits
};
assert!(matches!(
ContextBank::new(1, &tiny).unwrap_err().kind,
crate::ErrorKind::LimitExceeded { .. }
));
let mut bank = ContextBank::new(2, &limits).unwrap();
assert_eq!((bank.len(), bank.is_empty()), (2, false));
assert_eq!(bank.get(2), None);
let adapted = ContextState {
state_index: 7,
mps: true,
};
bank.update(1, adapted);
assert_eq!(bank.get(1), Some(adapted));
bank.reset();
assert_eq!(bank.get(1), Some(ContextState::default()));
}
#[test]
fn context_banks_beyond_the_address_space_are_refused_without_allocating() {
let limits = Limits {
max_allocation_bytes: u64::MAX,
..Limits::default()
};
assert_eq!(
ContextBank::new(usize::MAX, &limits).unwrap_err().reason,
INVALID_CONTEXT
);
#[cfg(target_pointer_width = "64")]
assert!(matches!(
ContextBank::new(1 << 62, &limits).unwrap_err().kind,
crate::ErrorKind::LimitExceeded {
resource: "arithmetic context bytes",
..
}
));
}
}