1use std::{
8 collections::{BTreeMap, BTreeSet},
9 sync::OnceLock,
10};
11
12use crate::{
13 capability::{CapabilityName, CapabilitySet, fact_private_capability},
14 claim::{Claim, ClaimKind, ClaimPattern, Visibility},
15 datum::Datum,
16 datum_store::{BTreeDatumStore, DatumStore},
17 env::Cx,
18 error::{Error, Result},
19 id::Symbol,
20 ref_id::{ContentId, Ref},
21};
22
23pub trait FactStore {
29 fn insert_authorized(
32 &mut self,
33 capabilities: &CapabilitySet,
34 data: &mut dyn DatumStore,
35 claim: Claim,
36 ) -> Result<Ref>;
37
38 fn query_authorized(&self, cx: &Cx, pattern: ClaimPattern) -> Result<Vec<Claim>>;
41}
42
43#[derive(Clone, Debug, Default)]
46pub struct BTreeFactStore {
47 claims: BTreeMap<ContentId, Claim>,
48 index: BTreeMap<ClaimIndexKey, BTreeSet<ContentId>>,
49}
50
51#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
52struct ClaimIndexKey {
53 subject: Option<Ref>,
54 predicate: Option<Symbol>,
55 object: Option<Ref>,
56}
57
58impl BTreeFactStore {
59 pub fn new() -> Self {
61 Self::default()
62 }
63
64 pub fn len(&self) -> usize {
66 self.claims.len()
67 }
68
69 pub fn is_empty(&self) -> bool {
71 self.claims.is_empty()
72 }
73
74 pub fn get(&self, id: &ContentId) -> Option<&Claim> {
76 self.claims.get(id)
77 }
78
79 pub fn remove(&mut self, id: &ContentId) -> Option<Claim> {
81 let claim = self.claims.remove(id)?;
82 for key in index_keys_for_claim(&claim) {
83 let should_remove_key = if let Some(ids) = self.index.get_mut(&key) {
84 ids.remove(id);
85 ids.is_empty()
86 } else {
87 false
88 };
89 if should_remove_key {
90 self.index.remove(&key);
91 }
92 }
93 Some(claim)
94 }
95
96 pub fn insert_boot_claims(&mut self, data: &mut BTreeDatumStore) {
99 for record in boot_claim_records() {
100 data.insert_known(record.id.clone(), record.datum.clone());
101 self.insert_indexed(record.claim.clone());
102 }
103 }
104
105 fn insert_indexed(&mut self, claim: Claim) {
106 let Some(id) = claim.id.clone() else {
107 return;
108 };
109
110 if !self.claims.contains_key(&id) {
111 for key in index_keys_for_claim(&claim) {
112 self.index.entry(key).or_default().insert(id.clone());
113 }
114 self.claims.insert(id, claim);
115 }
116 }
117}
118
119impl FactStore for BTreeFactStore {
120 fn insert_authorized(
121 &mut self,
122 capabilities: &CapabilitySet,
123 data: &mut dyn DatumStore,
124 mut claim: Claim,
125 ) -> Result<Ref> {
126 authorize_insert(capabilities, &claim)?;
127
128 let id = claim.content_id(data)?;
129 claim.id = Some(id.clone());
130
131 self.insert_indexed(claim);
132
133 Ok(Ref::Content(id))
134 }
135
136 fn query_authorized(&self, cx: &Cx, pattern: ClaimPattern) -> Result<Vec<Claim>> {
137 let key = ClaimIndexKey {
138 subject: pattern.subject,
139 predicate: pattern.predicate,
140 object: pattern.object,
141 };
142
143 let Some(ids) = self.index.get(&key) else {
144 return Ok(Vec::new());
145 };
146
147 Ok(ids
148 .iter()
149 .filter_map(|id| self.claims.get(id))
150 .filter(|claim| visible_to(cx, claim, pattern.include_revoked))
151 .cloned()
152 .collect())
153 }
154}
155
156fn authorize_insert(capabilities: &CapabilitySet, claim: &Claim) -> Result<()> {
157 if claim.visibility == Visibility::Private {
158 require_capability(capabilities, &fact_private_capability())?;
159 }
160 Ok(())
161}
162
163fn visible_to(cx: &Cx, claim: &Claim, include_revoked: bool) -> bool {
164 if claim.kind == ClaimKind::Revoked && !include_revoked {
165 return false;
166 }
167
168 match claim.visibility {
169 Visibility::Public => true,
170 Visibility::CapabilityGated => has_all_requirements(cx.capabilities(), claim),
171 Visibility::Private => {
172 cx.capabilities().contains(&fact_private_capability())
173 && has_all_requirements(cx.capabilities(), claim)
174 }
175 }
176}
177
178fn has_all_requirements(capabilities: &CapabilitySet, claim: &Claim) -> bool {
179 claim
180 .requires
181 .iter()
182 .all(|capability| capabilities.contains(capability))
183}
184
185fn require_capability(capabilities: &CapabilitySet, capability: &CapabilityName) -> Result<()> {
186 if capabilities.contains(capability) {
187 Ok(())
188 } else {
189 Err(Error::CapabilityDenied {
190 capability: capability.clone(),
191 })
192 }
193}
194
195fn index_keys_for_claim(claim: &Claim) -> Vec<ClaimIndexKey> {
196 let subjects = [None, Some(claim.subject.clone())];
197 let predicates = [None, Some(claim.predicate.clone())];
198 let objects = [None, Some(claim.object.clone())];
199 let mut keys = Vec::with_capacity(8);
200
201 for subject in subjects {
202 for predicate in predicates.clone() {
203 for object in objects.clone() {
204 keys.push(ClaimIndexKey {
205 subject: subject.clone(),
206 predicate: predicate.clone(),
207 object,
208 });
209 }
210 }
211 }
212
213 keys
214}
215
216fn core_boot_claims() -> Vec<Claim> {
217 core_class_names()
218 .iter()
219 .map(|name| {
220 Claim::public(
221 Ref::Symbol(core_symbol(name)),
222 core_symbol("kind"),
223 Ref::Symbol(core_symbol("class")),
224 )
225 })
226 .collect()
227}
228
229#[derive(Clone)]
230struct BootClaimRecord {
231 id: ContentId,
232 datum: Datum,
233 claim: Claim,
234}
235
236fn boot_claim_records() -> &'static [BootClaimRecord] {
237 static RECORDS: OnceLock<Vec<BootClaimRecord>> = OnceLock::new();
238 RECORDS.get_or_init(|| {
239 core_boot_claims()
240 .into_iter()
241 .map(|mut claim| {
242 let datum = claim.canonical_datum();
243 let id = datum.content_id().expect("core boot claim datum is valid");
244 claim.id = Some(id.clone());
245 BootClaimRecord { id, datum, claim }
246 })
247 .collect()
248 })
249}
250
251fn core_class_names() -> &'static [&'static str] {
252 &[
253 "Class",
254 "Nil",
255 "Bool",
256 "Number",
257 "Symbol",
258 "String",
259 "Bytes",
260 "List",
261 "Table",
262 "Expr",
263 "Function",
264 "Shape",
265 "Thunk",
266 "EvalRequest",
267 "EvalReply",
268 "Macro",
269 "ShapeMatch",
270 "Codec",
271 "Help",
272 "Test",
273 "NumberDomain",
274 "LocalEvalFabric",
275 "Card",
276 ]
277}
278
279fn core_symbol(name: &str) -> Symbol {
280 Symbol::qualified("core", name)
281}