#![forbid(unsafe_code)]
#[repr(u32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CapabilityBit {
ReadsFs = 0,
WritesFs = 1,
Network = 2,
ReadsClock = 3,
ReadsEnv = 4,
UsesRng = 5,
}
impl CapabilityBit {
pub fn bit_index(self) -> u32 {
self as u32
}
pub fn as_str(self) -> &'static str {
match self {
CapabilityBit::ReadsFs => "reads_fs",
CapabilityBit::WritesFs => "writes_fs",
CapabilityBit::Network => "network",
CapabilityBit::ReadsClock => "reads_clock",
CapabilityBit::ReadsEnv => "reads_env",
CapabilityBit::UsesRng => "uses_rng",
}
}
pub fn deny_message(self) -> String {
format!(
"function declared `{}` but caller did not grant it",
self.as_str()
)
}
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct NativeFnGate {
pub reads_fs: bool,
pub writes_fs: bool,
pub network: bool,
pub reads_clock: bool,
pub reads_env: bool,
pub uses_rng: bool,
}
impl NativeFnGate {
pub fn missing_bits(&self, caps: &Capabilities) -> Vec<&'static str> {
let mut out = Vec::with_capacity(6);
if self.reads_fs && !caps.reads_fs {
out.push(CapabilityBit::ReadsFs.as_str());
}
if self.writes_fs && !caps.writes_fs {
out.push(CapabilityBit::WritesFs.as_str());
}
if self.network && !caps.network {
out.push(CapabilityBit::Network.as_str());
}
if self.reads_clock && !caps.reads_clock {
out.push(CapabilityBit::ReadsClock.as_str());
}
if self.reads_env && !caps.reads_env {
out.push(CapabilityBit::ReadsEnv.as_str());
}
if self.uses_rng && !caps.uses_rng {
out.push(CapabilityBit::UsesRng.as_str());
}
out
}
pub fn required_bit_indices(&self) -> Vec<u32> {
let mut out = Vec::with_capacity(6);
if self.reads_fs {
out.push(CapabilityBit::ReadsFs.bit_index());
}
if self.writes_fs {
out.push(CapabilityBit::WritesFs.bit_index());
}
if self.network {
out.push(CapabilityBit::Network.bit_index());
}
if self.reads_clock {
out.push(CapabilityBit::ReadsClock.bit_index());
}
if self.reads_env {
out.push(CapabilityBit::ReadsEnv.bit_index());
}
if self.uses_rng {
out.push(CapabilityBit::UsesRng.bit_index());
}
out
}
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct Capabilities {
pub reads_fs: bool,
pub writes_fs: bool,
pub network: bool,
pub reads_clock: bool,
pub reads_env: bool,
pub uses_rng: bool,
pub max_steps: Option<u64>,
pub max_value_elements: Option<usize>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum ResourceBudgetProfile {
#[default]
Off,
Dev,
Untrusted,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct ResourceBudget {
pub max_steps: Option<u64>,
pub max_value_elements: Option<usize>,
}
impl ResourceBudget {
pub const DEV_MAX_STEPS: u64 = 5_000_000;
pub const DEV_MAX_VALUE_ELEMENTS: usize = 100_000;
pub const UNTRUSTED_MAX_STEPS: u64 = 1_000_000;
pub const UNTRUSTED_MAX_VALUE_ELEMENTS: usize = 10_000;
pub fn off() -> Self {
Self::default()
}
pub fn dev() -> Self {
Self {
max_steps: Some(Self::DEV_MAX_STEPS),
max_value_elements: Some(Self::DEV_MAX_VALUE_ELEMENTS),
}
}
pub fn untrusted() -> Self {
Self {
max_steps: Some(Self::UNTRUSTED_MAX_STEPS),
max_value_elements: Some(Self::UNTRUSTED_MAX_VALUE_ELEMENTS),
}
}
pub fn from_profile(profile: ResourceBudgetProfile) -> Self {
match profile {
ResourceBudgetProfile::Off => Self::off(),
ResourceBudgetProfile::Dev => Self::dev(),
ResourceBudgetProfile::Untrusted => Self::untrusted(),
}
}
pub fn has_evaluator_limits(self) -> bool {
self.max_steps.is_some() || self.max_value_elements.is_some()
}
pub fn apply_to_capabilities(self, caps: &mut Capabilities) {
if let Some(max_steps) = self.max_steps {
caps.max_steps = Some(max_steps);
}
if let Some(max_value_elements) = self.max_value_elements {
caps.max_value_elements = Some(max_value_elements);
}
}
}
impl Capabilities {
pub fn all_granted() -> Self {
Self {
reads_fs: true,
writes_fs: true,
network: true,
reads_clock: true,
reads_env: true,
uses_rng: true,
max_steps: None,
max_value_elements: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cap_bit_indices_are_stable() {
assert_eq!(CapabilityBit::ReadsFs.bit_index(), 0);
assert_eq!(CapabilityBit::WritesFs.bit_index(), 1);
assert_eq!(CapabilityBit::Network.bit_index(), 2);
assert_eq!(CapabilityBit::ReadsClock.bit_index(), 3);
assert_eq!(CapabilityBit::ReadsEnv.bit_index(), 4);
assert_eq!(CapabilityBit::UsesRng.bit_index(), 5);
}
#[test]
fn missing_bits_uses_canonical_labels() {
let gate = NativeFnGate {
reads_fs: true,
writes_fs: true,
network: true,
reads_clock: true,
reads_env: true,
uses_rng: true,
};
assert_eq!(
gate.missing_bits(&Capabilities::default()),
vec![
"reads_fs",
"writes_fs",
"network",
"reads_clock",
"reads_env",
"uses_rng",
]
);
assert!(gate.missing_bits(&Capabilities::all_granted()).is_empty());
}
#[test]
fn resource_budget_profiles_are_stable() {
assert_eq!(
ResourceBudget::from_profile(ResourceBudgetProfile::Off),
ResourceBudget::off()
);
assert_eq!(
ResourceBudget::from_profile(ResourceBudgetProfile::Dev),
ResourceBudget {
max_steps: Some(ResourceBudget::DEV_MAX_STEPS),
max_value_elements: Some(ResourceBudget::DEV_MAX_VALUE_ELEMENTS),
}
);
assert_eq!(
ResourceBudget::from_profile(ResourceBudgetProfile::Untrusted),
ResourceBudget {
max_steps: Some(ResourceBudget::UNTRUSTED_MAX_STEPS),
max_value_elements: Some(ResourceBudget::UNTRUSTED_MAX_VALUE_ELEMENTS),
}
);
}
#[test]
fn resource_budget_does_not_grant_capabilities() {
let mut caps = Capabilities::default();
ResourceBudget::untrusted().apply_to_capabilities(&mut caps);
assert_eq!(caps.max_steps, Some(ResourceBudget::UNTRUSTED_MAX_STEPS));
assert_eq!(
caps.max_value_elements,
Some(ResourceBudget::UNTRUSTED_MAX_VALUE_ELEMENTS)
);
assert_eq!(
NativeFnGate {
reads_fs: true,
writes_fs: true,
network: true,
reads_clock: true,
reads_env: true,
uses_rng: true,
}
.missing_bits(&caps)
.len(),
6
);
}
}