use crate::contract::capability::{
EnvEntry, EnvPolicy, EnvSource, FdPolicy, NetDest, NetPolicy, SpawnPolicy,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
enum Family {
Fd = 0x01,
Spawn = 0x02,
Env = 0x03,
Net = 0x04,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CanonicalPolicy {
bytes: Vec<u8>,
}
impl CanonicalPolicy {
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
#[must_use]
pub fn of_fd(policy: &FdPolicy) -> Self {
let mut enc = Encoder::new(Family::Fd);
match policy {
FdPolicy::None => enc.variant(0),
FdPolicy::Only(fds) => {
enc.variant(1);
let mut sorted = fds.clone();
sorted.sort_unstable();
sorted.dedup();
enc.len_prefixed_u32_list(&sorted);
}
}
enc.finish()
}
#[must_use]
pub fn of_spawn(policy: &SpawnPolicy) -> Self {
let mut enc = Encoder::new(Family::Spawn);
match policy {
SpawnPolicy::DenyNewTasks => enc.variant(0),
SpawnPolicy::AllowThreadsWithinBoundary => enc.variant(1),
SpawnPolicy::AllowDescendantsWithinBoundary => enc.variant(2),
}
enc.finish()
}
#[must_use]
pub fn of_env(policy: &EnvPolicy) -> Self {
let mut enc = Encoder::new(Family::Env);
match policy {
EnvPolicy::Exact(entries) => {
enc.variant(0);
let mut sorted: Vec<&EnvEntry> = entries.iter().collect();
sorted.sort_by(|a, b| a.name.cmp(&b.name));
enc.len_prefixed_env_list(&sorted);
}
}
enc.finish()
}
#[must_use]
pub fn of_net(policy: &NetPolicy) -> Self {
let mut enc = Encoder::new(Family::Net);
match policy {
NetPolicy::DenyAll => enc.variant(0),
NetPolicy::AllowList(dests) => {
enc.variant(1);
let mut sorted = dests.clone();
sorted.sort_unstable_by(|a, b| (&a.host, a.port).cmp(&(&b.host, b.port)));
sorted.dedup();
enc.len_prefixed_dest_list(&sorted);
}
}
enc.finish()
}
}
struct Encoder {
bytes: Vec<u8>,
}
impl Encoder {
fn new(family: Family) -> Self {
Self {
bytes: vec![family as u8],
}
}
fn variant(&mut self, discriminant: u8) {
self.bytes.push(discriminant);
}
fn count(&mut self, n: usize) {
self.bytes.extend_from_slice(&(n as u64).to_be_bytes());
}
fn len_prefixed_u32_list(&mut self, items: &[u32]) {
self.count(items.len());
for &item in items {
self.bytes.extend_from_slice(&item.to_be_bytes());
}
}
fn len_prefixed_env_list(&mut self, items: &[&EnvEntry]) {
self.count(items.len());
for entry in items {
let name = entry.name.as_bytes();
self.count(name.len());
self.bytes.extend_from_slice(name);
let (tag, payload): (u8, &str) = match &entry.source {
EnvSource::Literal(value) => (0, value.as_str()),
EnvSource::SecretLease(reference) => (1, reference.id()),
};
self.bytes.push(tag);
let payload = payload.as_bytes();
self.count(payload.len());
self.bytes.extend_from_slice(payload);
}
}
fn len_prefixed_dest_list(&mut self, items: &[NetDest]) {
self.count(items.len());
for dest in items {
let raw = dest.host.as_bytes();
self.count(raw.len());
self.bytes.extend_from_slice(raw);
self.bytes.extend_from_slice(&dest.port.to_be_bytes());
}
}
fn finish(self) -> CanonicalPolicy {
CanonicalPolicy { bytes: self.bytes }
}
}
#[cfg(test)]
#[path = "canonical_policy_tests.rs"]
mod tests;