use core::cell::Cell;
use crate::error::Limit;
use crate::memory::Memory;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Limits {
pub max_depth: u32,
pub max_elements: u64,
pub max_element_length: u64,
pub max_work: u64,
}
impl Default for Limits {
fn default() -> Self {
Self {
max_depth: 1_000,
max_elements: 1_000_000,
max_element_length: 1 << 32,
max_work: 16_000_000,
}
}
}
impl Limits {
#[must_use]
pub fn none() -> Self {
Self {
max_depth: 0,
max_elements: 0,
max_element_length: 0,
max_work: 0,
}
}
}
#[derive(Debug)]
pub(crate) struct Budget {
limits: Limits,
elements: Cell<u64>,
work: Cell<u64>,
memory: Memory,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct BudgetView<'a> {
origin: &'a Budget,
operation: Option<&'a Budget>,
}
impl<'a> BudgetView<'a> {
pub(crate) const fn root(origin: &'a Budget) -> Self {
Self {
origin,
operation: None,
}
}
pub(crate) fn query(origin: &'a Budget, operation: &'a Budget) -> Self {
Self {
origin,
operation: if core::ptr::eq(origin, operation) {
None
} else {
Some(operation)
},
}
}
pub(crate) fn operation(self) -> &'a Budget {
self.operation.unwrap_or(self.origin)
}
pub(crate) fn work_in(self, amount: u64, execution: &Memory) -> Result<(), Limit> {
if amount > self.origin.limits.max_work - self.origin.work.get() {
return Err(Limit::Work);
}
if let Some(operation) = self.operation
&& amount > operation.limits.max_work - operation.work.get()
{
return Err(Limit::Work);
}
execution.charge_work(amount).map_err(|_| Limit::Work)?;
self.origin.work.set(self.origin.work.get() + amount);
if let Some(operation) = self.operation {
operation.work.set(operation.work.get() + amount);
}
Ok(())
}
pub(crate) fn work_remaining(self) -> u64 {
let origin = self.origin.limits.max_work - self.origin.work.get();
match self.operation {
Some(operation) => origin.min(operation.limits.max_work - operation.work.get()),
None => origin,
}
}
pub(crate) fn local_work(self, amount: u64) -> Result<(), Limit> {
if amount > self.origin.limits.max_work - self.origin.work.get() {
return Err(Limit::Work);
}
if let Some(operation) = self.operation
&& amount > operation.limits.max_work - operation.work.get()
{
return Err(Limit::Work);
}
self.origin.work.set(self.origin.work.get() + amount);
if let Some(operation) = self.operation {
operation.work.set(operation.work.get() + amount);
}
Ok(())
}
pub(crate) fn element(self) -> Result<(), Limit> {
if self.origin.elements.get() >= self.origin.limits.max_elements {
return Err(Limit::Elements);
}
if let Some(operation) = self.operation
&& operation.elements.get() >= operation.limits.max_elements
{
return Err(Limit::Elements);
}
self.origin.elements.set(self.origin.elements.get() + 1);
if let Some(operation) = self.operation {
operation.elements.set(operation.elements.get() + 1);
}
Ok(())
}
pub(crate) fn depth(self, depth: u32) -> Result<(), Limit> {
self.origin.depth(depth)?;
if let Some(operation) = self.operation {
operation.depth(depth)?;
}
Ok(())
}
pub(crate) fn length(self, length: u64) -> Result<(), Limit> {
self.origin.length(length)?;
if let Some(operation) = self.operation {
operation.length(length)?;
}
Ok(())
}
}
impl Budget {
pub(crate) fn new(limits: Limits, memory: Memory) -> Self {
Self {
limits,
elements: Cell::new(0),
work: Cell::new(0),
memory,
}
}
pub(crate) fn limits(&self) -> Limits {
self.limits
}
pub(crate) fn memory(&self) -> &Memory {
&self.memory
}
#[cfg(test)]
pub(crate) fn element(&self) -> Result<(), Limit> {
BudgetView::root(self).element()
}
#[cfg(test)]
pub(crate) fn work(&self, amount: u64) -> Result<(), Limit> {
BudgetView::root(self).work_in(amount, &self.memory)
}
pub(crate) fn depth(&self, depth: u32) -> Result<(), Limit> {
if depth > self.limits.max_depth {
return Err(Limit::Depth);
}
Ok(())
}
pub(crate) fn length(&self, length: u64) -> Result<(), Limit> {
if length > self.limits.max_element_length {
return Err(Limit::ElementLength);
}
Ok(())
}
}
#[cfg(test)]
mod tests;