use crate::error::FaucetError;
use serde_json::Value;
pub const DECODED_BYTES_PER_OBJECT_BYTE: u64 = 64;
pub const MAX_DEFAULT_DECODED_BYTES: u64 = 16 * 1024 * 1024 * 1024;
pub fn default_max_decoded_bytes(max_object_bytes: u64) -> u64 {
max_object_bytes
.saturating_mul(DECODED_BYTES_PER_OBJECT_BYTE)
.min(MAX_DEFAULT_DECODED_BYTES)
}
pub fn effective_max_decoded_bytes(configured: Option<u64>, max_object_bytes: u64) -> u64 {
configured.unwrap_or_else(|| default_max_decoded_bytes(max_object_bytes))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DecodeBudget {
limit: u64,
used: u64,
}
impl Default for DecodeBudget {
fn default() -> Self {
Self::new(MAX_DEFAULT_DECODED_BYTES)
}
}
impl DecodeBudget {
pub fn new(limit: u64) -> Self {
Self { limit, used: 0 }
}
pub fn limit(&self) -> u64 {
self.limit
}
pub fn used(&self) -> u64 {
self.used
}
pub fn charge(&mut self, bytes: u64, what: &str) -> Result<(), FaucetError> {
self.used = self.used.saturating_add(bytes);
if self.used > self.limit {
return Err(exceeded(what, self.limit));
}
Ok(())
}
pub fn charge_value(&mut self, v: &Value, what: &str) -> Result<(), FaucetError> {
self.charge(decoded_size(v), what)
}
pub fn reset(&mut self) {
self.used = 0;
}
}
pub fn exceeded(what: &str, limit: u64) -> FaucetError {
FaucetError::Source(format!(
"{what} decodes to more than `max_decoded_bytes` ({limit} bytes); raise \
`max_decoded_bytes` to read it"
))
}
#[cfg(any(test, feature = "file-format-avro"))]
pub(crate) fn is_exceeded(e: &FaucetError) -> bool {
matches!(e, FaucetError::Source(m) if m.contains("decodes to more than `max_decoded_bytes`"))
}
pub fn decoded_size(v: &Value) -> u64 {
let node = std::mem::size_of::<Value>() as u64;
match v {
Value::String(s) => node + s.len() as u64,
Value::Array(items) => items
.iter()
.fold(node, |acc, i| acc.saturating_add(decoded_size(i))),
Value::Object(m) => m.iter().fold(node, |acc, (k, i)| {
acc.saturating_add(k.len() as u64)
.saturating_add(decoded_size(i))
}),
_ => node,
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn the_default_is_a_multiple_of_the_object_limit_with_a_ceiling() {
assert_eq!(default_max_decoded_bytes(1024), 64 * 1024);
assert_eq!(
default_max_decoded_bytes(super::super::DEFAULT_MAX_OBJECT_BYTES),
MAX_DEFAULT_DECODED_BYTES
);
assert_eq!(
default_max_decoded_bytes(u64::MAX),
MAX_DEFAULT_DECODED_BYTES
);
assert_eq!(effective_max_decoded_bytes(Some(7), 1024), 7);
assert_eq!(effective_max_decoded_bytes(None, 1024), 64 * 1024);
}
#[test]
fn charging_past_the_limit_names_the_setting() {
let mut b = DecodeBudget::new(10);
b.charge(10, "x").unwrap();
assert_eq!(b.used(), 10);
let err = b.charge(1, "'f.avro'").unwrap_err();
assert!(matches!(err, FaucetError::Source(_)));
assert!(err.to_string().contains("max_decoded_bytes"), "{err}");
assert!(err.to_string().contains("'f.avro'"), "{err}");
b.reset();
assert_eq!(b.used(), 0);
assert_eq!(b.limit(), 10);
assert!(is_exceeded(&b.charge(u64::MAX, "x").unwrap_err()));
assert!(!is_exceeded(&FaucetError::Source("other".into())));
}
#[test]
fn the_default_budget_is_the_ceiling() {
assert_eq!(DecodeBudget::default().limit(), MAX_DEFAULT_DECODED_BYTES);
}
#[test]
fn decoded_size_counts_nodes_strings_and_keys() {
let node = std::mem::size_of::<Value>() as u64;
assert_eq!(decoded_size(&json!(null)), node);
assert_eq!(decoded_size(&json!("abc")), node + 3);
assert_eq!(decoded_size(&json!([1, 2])), 3 * node);
assert_eq!(decoded_size(&json!({"ab": true})), 2 * node + 2);
let mut b = DecodeBudget::new(node);
b.charge_value(&json!(1), "x").unwrap();
b.charge_value(&json!(1), "x").unwrap_err();
}
}