#![forbid(unsafe_code)]
pub mod corpus;
pub mod descriptor_court;
pub mod graph_court;
pub mod store_court;
use std::collections::HashMap;
use std::ops::Range;
use crate::core::extent::ChunkId;
use crate::core::limits::Limits;
use crate::core::materialize::{DecoderContext, MaterializeError};
use crate::core::representation::{RansCodec, Representation, UniverseId};
pub fn tight_limits() -> Limits {
Limits {
max_chunk_size: 16 * 1024,
chunk_class: 4096,
max_descriptor_bytes: 512,
max_reference_depth: 2,
max_decode_work: 1 << 20,
max_alloc_bytes: 64 * 1024,
max_fanout: 64,
max_model_bytes: 512,
max_inline_bytes: 256,
max_period: 64,
max_palette: 4,
}
}
pub const LIMIT_SETS: [&str; 2] = ["tight", "default"];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Expect {
MustAccept,
MustReject,
Either,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExhibitKind {
Descriptor,
Graph,
}
#[derive(Debug, Clone)]
pub struct Exhibit {
pub name: String,
pub bytes: Vec<u8>,
pub expect: Expect,
pub kind: ExhibitKind,
}
impl Exhibit {
pub fn new(name: impl Into<String>, bytes: Vec<u8>, kind: ExhibitKind, expect: Expect) -> Self {
Self {
name: name.into(),
bytes,
expect,
kind,
}
}
}
pub const GRAPH_MAX_TABLES: usize = 32;
#[derive(Debug, Clone)]
pub struct GraphSpec {
pub descs: Vec<(ChunkId, Vec<u8>)>,
pub objs: Vec<(ChunkId, Vec<u8>)>,
pub entry: ChunkId,
}
impl GraphSpec {
pub fn new(entry: ChunkId) -> Self {
Self {
descs: Vec::new(),
objs: Vec::new(),
entry,
}
}
pub fn add_desc(&mut self, id: ChunkId, bytes: Vec<u8>) -> &mut Self {
self.descs.push((id, bytes));
self
}
pub fn add_obj(&mut self, id: ChunkId, bytes: Vec<u8>) -> &mut Self {
self.objs.push((id, bytes));
self
}
}
pub fn encode_graph_spec(spec: &GraphSpec) -> Vec<u8> {
let mut out = Vec::new();
out.push(spec.descs.len().min(GRAPH_MAX_TABLES) as u8);
for (id, bytes) in spec.descs.iter().take(GRAPH_MAX_TABLES) {
out.extend_from_slice(id.as_bytes());
out.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
out.extend_from_slice(bytes);
}
out.push(spec.objs.len().min(GRAPH_MAX_TABLES) as u8);
for (id, bytes) in spec.objs.iter().take(GRAPH_MAX_TABLES) {
out.extend_from_slice(id.as_bytes());
out.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
out.extend_from_slice(bytes);
}
out.extend_from_slice(spec.entry.as_bytes());
out
}
pub fn parse_graph_spec(input: &[u8]) -> GraphSpec {
let mut pos = 0usize;
let mut take = |n: usize| -> Option<&[u8]> {
if input.len().saturating_sub(pos) < n {
return None;
}
let s = &input[pos..pos + n];
pos += n;
Some(s)
};
let mut spec = GraphSpec::new(ChunkId::of(input));
let n_desc = take(1).map(|b| b[0]).unwrap_or(0);
for _ in 0..(n_desc as usize).min(GRAPH_MAX_TABLES) {
let id = match take(32) {
Some(b) => ChunkId::new(b.try_into().expect("32-byte id")),
None => break,
};
let dlen = match take(4) {
Some(b) => u32::from_le_bytes(b.try_into().expect("4-byte len")) as usize,
None => break,
};
let Some(payload) = take(dlen) else { break };
spec.descs.push((id, payload.to_vec()));
}
let n_obj = take(1).map(|b| b[0]).unwrap_or(0);
for _ in 0..(n_obj as usize).min(GRAPH_MAX_TABLES) {
let id = match take(32) {
Some(b) => ChunkId::new(b.try_into().expect("32-byte id")),
None => break,
};
let olen = match take(4) {
Some(b) => u32::from_le_bytes(b.try_into().expect("4-byte len")) as usize,
None => break,
};
let Some(payload) = take(olen) else { break };
spec.objs.push((id, payload.to_vec()));
}
if let Some(b) = take(32) {
spec.entry = ChunkId::new(b.try_into().expect("32-byte entry id"));
}
spec
}
#[derive(Debug, Clone)]
pub struct HostileResolver {
objects: HashMap<ChunkId, Vec<u8>>,
chunks: HashMap<ChunkId, Vec<u8>>,
limits: Limits,
}
impl HostileResolver {
pub fn from_spec(spec: &GraphSpec, limits: &Limits) -> Self {
let mut objects = HashMap::with_capacity(spec.objs.len());
for (id, b) in &spec.objs {
objects.insert(*id, b.clone());
}
let mut chunks = HashMap::with_capacity(spec.descs.len());
for (id, b) in &spec.descs {
chunks.insert(*id, b.clone());
}
Self {
objects,
chunks,
limits: *limits,
}
}
}
impl DecoderContext for HostileResolver {
fn fetch_object(&self, id: &ChunkId) -> Result<Vec<u8>, MaterializeError> {
self.objects
.get(id)
.cloned()
.ok_or(MaterializeError::MissingObject(*id))
}
fn fetch_descriptor(&self, id: &ChunkId) -> Result<Representation, MaterializeError> {
match self.chunks.get(id) {
Some(bytes) => crate::format::descriptor::decode(bytes, &self.limits)
.map_err(|e| MaterializeError::InvalidDescriptor(e.to_string())),
None => Err(MaterializeError::MissingChunk(*id)),
}
}
fn decode_rans(
&self,
model: &[u8],
encoded: &[u8],
scale_bits: u8,
codec: RansCodec,
out_len: u64,
) -> Result<Vec<u8>, MaterializeError> {
let parsed = crate::rans::metadata::decode_model(model, self.limits.max_model_bytes)
.map_err(|e| MaterializeError::RansDecode(e.to_string()))?;
if parsed.scale_bits != scale_bits || parsed.codec != codec {
return Err(MaterializeError::RansDecode("model tag mismatch".into()));
}
crate::rans::residual::decode_stream(&parsed, encoded, out_len)
.map_err(|e| MaterializeError::RansDecode(e.to_string()))
}
fn universe_bytes(
&self,
universe: UniverseId,
seed: [u8; 16],
coordinate: u64,
range: Range<u64>,
) -> Result<Vec<u8>, MaterializeError> {
match universe {
UniverseId::UniformXofV1 => Ok(
crate::entropy::universe::UniformXofV1::materialize_range(seed, coordinate, range),
),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GraphOutcome {
Ok { len: u64 },
Rejected(String),
}
pub fn run_graph_oracle(spec: &GraphSpec, limits: &Limits) -> Result<GraphOutcome, String> {
let resolver = HostileResolver::from_spec(spec, limits);
let entry = match resolver.fetch_descriptor(&spec.entry) {
Ok(r) => r,
Err(e) => return Ok(GraphOutcome::Rejected(format!("{e:?}"))),
};
if entry.len() > limits.max_chunk_size {
return Err(format!(
"entry descriptor declares {} bytes, over the {} chunk cap",
entry.len(),
limits.max_chunk_size
));
}
if entry.len() > limits.max_alloc_bytes {
return Err(format!(
"entry descriptor declares {} bytes, over the {} allocation cap",
entry.len(),
limits.max_alloc_bytes
));
}
match crate::core::materialize::materialize_to_vec(&entry, &resolver, limits) {
Ok(bytes) => {
if bytes.len() as u64 != entry.len() {
return Err(format!(
"materialized {} bytes but the descriptor declares {}",
bytes.len(),
entry.len()
));
}
Ok(GraphOutcome::Ok {
len: bytes.len() as u64,
})
}
Err(e) => Ok(GraphOutcome::Rejected(format!("{e:?}"))),
}
}
pub fn seeded_bytes(n: usize, mut seed: u64) -> Vec<u8> {
let mut out = Vec::with_capacity(n);
while out.len() < n {
seed = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = seed;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^= z >> 31;
let b = z.to_le_bytes();
let take = (n - out.len()).min(8);
out.extend_from_slice(&b[..take]);
}
out
}