use std::{
collections::{BTreeMap, BTreeSet},
sync::OnceLock,
};
use crate::{
capability::{CapabilityName, CapabilitySet, fact_private_capability},
claim::{Claim, ClaimKind, ClaimPattern, Visibility},
datum::Datum,
datum_store::{BTreeDatumStore, DatumStore},
env::Cx,
error::{Error, Result},
id::Symbol,
ref_id::{ContentId, Ref},
};
pub trait FactStore {
fn insert_authorized(
&mut self,
capabilities: &CapabilitySet,
data: &mut dyn DatumStore,
claim: Claim,
) -> Result<Ref>;
fn query_authorized(&self, cx: &Cx, pattern: ClaimPattern) -> Result<Vec<Claim>>;
}
#[derive(Clone, Debug, Default)]
pub struct BTreeFactStore {
claims: BTreeMap<ContentId, Claim>,
index: BTreeMap<ClaimIndexKey, BTreeSet<ContentId>>,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
struct ClaimIndexKey {
subject: Option<Ref>,
predicate: Option<Symbol>,
object: Option<Ref>,
}
impl BTreeFactStore {
pub fn new() -> Self {
Self::default()
}
pub fn len(&self) -> usize {
self.claims.len()
}
pub fn is_empty(&self) -> bool {
self.claims.is_empty()
}
pub fn get(&self, id: &ContentId) -> Option<&Claim> {
self.claims.get(id)
}
pub fn remove(&mut self, id: &ContentId) -> Option<Claim> {
let claim = self.claims.remove(id)?;
for key in index_keys_for_claim(&claim) {
let should_remove_key = if let Some(ids) = self.index.get_mut(&key) {
ids.remove(id);
ids.is_empty()
} else {
false
};
if should_remove_key {
self.index.remove(&key);
}
}
Some(claim)
}
pub fn insert_boot_claims(&mut self, data: &mut BTreeDatumStore) {
for record in boot_claim_records() {
data.insert_known(record.id.clone(), record.datum.clone());
self.insert_indexed(record.claim.clone());
}
}
fn insert_indexed(&mut self, claim: Claim) {
let Some(id) = claim.id.clone() else {
return;
};
if !self.claims.contains_key(&id) {
for key in index_keys_for_claim(&claim) {
self.index.entry(key).or_default().insert(id.clone());
}
self.claims.insert(id, claim);
}
}
}
impl FactStore for BTreeFactStore {
fn insert_authorized(
&mut self,
capabilities: &CapabilitySet,
data: &mut dyn DatumStore,
mut claim: Claim,
) -> Result<Ref> {
authorize_insert(capabilities, &claim)?;
let id = claim.content_id(data)?;
claim.id = Some(id.clone());
self.insert_indexed(claim);
Ok(Ref::Content(id))
}
fn query_authorized(&self, cx: &Cx, pattern: ClaimPattern) -> Result<Vec<Claim>> {
let key = ClaimIndexKey {
subject: pattern.subject,
predicate: pattern.predicate,
object: pattern.object,
};
let Some(ids) = self.index.get(&key) else {
return Ok(Vec::new());
};
Ok(ids
.iter()
.filter_map(|id| self.claims.get(id))
.filter(|claim| visible_to(cx, claim, pattern.include_revoked))
.cloned()
.collect())
}
}
fn authorize_insert(capabilities: &CapabilitySet, claim: &Claim) -> Result<()> {
if claim.visibility == Visibility::Private {
require_capability(capabilities, &fact_private_capability())?;
}
Ok(())
}
fn visible_to(cx: &Cx, claim: &Claim, include_revoked: bool) -> bool {
if claim.kind == ClaimKind::Revoked && !include_revoked {
return false;
}
match claim.visibility {
Visibility::Public => true,
Visibility::CapabilityGated => has_all_requirements(cx.capabilities(), claim),
Visibility::Private => {
cx.capabilities().contains(&fact_private_capability())
&& has_all_requirements(cx.capabilities(), claim)
}
}
}
fn has_all_requirements(capabilities: &CapabilitySet, claim: &Claim) -> bool {
claim
.requires
.iter()
.all(|capability| capabilities.contains(capability))
}
fn require_capability(capabilities: &CapabilitySet, capability: &CapabilityName) -> Result<()> {
if capabilities.contains(capability) {
Ok(())
} else {
Err(Error::CapabilityDenied {
capability: capability.clone(),
})
}
}
fn index_keys_for_claim(claim: &Claim) -> Vec<ClaimIndexKey> {
let subjects = [None, Some(claim.subject.clone())];
let predicates = [None, Some(claim.predicate.clone())];
let objects = [None, Some(claim.object.clone())];
let mut keys = Vec::with_capacity(8);
for subject in subjects {
for predicate in predicates.clone() {
for object in objects.clone() {
keys.push(ClaimIndexKey {
subject: subject.clone(),
predicate: predicate.clone(),
object,
});
}
}
}
keys
}
fn core_boot_claims() -> Vec<Claim> {
core_class_names()
.iter()
.map(|name| {
Claim::public(
Ref::Symbol(core_symbol(name)),
core_symbol("kind"),
Ref::Symbol(core_symbol("class")),
)
})
.collect()
}
#[derive(Clone)]
struct BootClaimRecord {
id: ContentId,
datum: Datum,
claim: Claim,
}
fn boot_claim_records() -> &'static [BootClaimRecord] {
static RECORDS: OnceLock<Vec<BootClaimRecord>> = OnceLock::new();
RECORDS.get_or_init(|| {
core_boot_claims()
.into_iter()
.map(|mut claim| {
let datum = claim.canonical_datum();
let id = datum.content_id().expect("core boot claim datum is valid");
claim.id = Some(id.clone());
BootClaimRecord { id, datum, claim }
})
.collect()
})
}
fn core_class_names() -> &'static [&'static str] {
&[
"Class",
"Nil",
"Bool",
"Number",
"Symbol",
"String",
"Bytes",
"List",
"Table",
"Expr",
"Function",
"Shape",
"Thunk",
"EvalRequest",
"EvalReply",
"Macro",
"ShapeMatch",
"Codec",
"Help",
"Test",
"NumberDomain",
"LocalEvalFabric",
"Card",
]
}
fn core_symbol(name: &str) -> Symbol {
Symbol::qualified("core", name)
}