1use std::collections::HashMap;
16use std::sync::{Arc, Mutex};
17
18use crate::bytecode::{Cap, CapId, CapTarget, NativeMask, RevocationCell};
19
20pub use crate::bytecode::CapRights;
21
22use super::error::RuntimeError;
23use super::process::FlowId;
24use super::sync_lock;
25
26const MINT_ATTEMPTS: u32 = 32;
27
28#[derive(Clone, Debug, PartialEq, Eq)]
30pub struct Capability {
31 pub holder: FlowId,
33 pub cap: Cap,
34}
35
36impl Capability {
37 pub fn target(&self) -> Option<FlowId> {
39 self.cap.target.flow_id().map(FlowId)
40 }
41
42 pub fn rights(&self) -> CapRights {
43 self.cap.rights
44 }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum CapError {
50 Unknown,
51 NotHolder,
52 InsufficientRights,
53 WrongTarget,
54 Unavailable,
56}
57
58impl std::fmt::Display for CapError {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 match self {
61 CapError::Unknown => f.write_str("unknown or revoked capability"),
62 CapError::NotHolder => f.write_str("calling flow does not hold this capability"),
63 CapError::InsufficientRights => f.write_str("capability lacks required rights"),
64 CapError::WrongTarget => f.write_str("capability target is not a flow"),
65 CapError::Unavailable => f.write_str("capability table unavailable (poisoned lock)"),
66 }
67 }
68}
69
70impl std::error::Error for CapError {}
71
72impl From<RuntimeError> for CapError {
73 fn from(_err: RuntimeError) -> Self {
74 CapError::Unavailable
75 }
76}
77
78struct CapTableInner {
79 entries: HashMap<CapId, Capability>,
80 flow_cells: HashMap<u64, Arc<RevocationCell>>,
81}
82
83pub struct CapTable {
85 inner: Mutex<CapTableInner>,
86 native_cell: Arc<RevocationCell>,
87 scheduler_cell: Arc<RevocationCell>,
88}
89
90impl CapTable {
91 pub fn new() -> Self {
92 Self {
93 inner: Mutex::new(CapTableInner {
94 entries: HashMap::new(),
95 flow_cells: HashMap::new(),
96 }),
97 native_cell: Arc::new(RevocationCell::new()),
98 scheduler_cell: Arc::new(RevocationCell::new()),
99 }
100 }
101
102 pub fn native_cell(&self) -> Arc<RevocationCell> {
103 Arc::clone(&self.native_cell)
104 }
105
106 fn lock(&self, where_: &'static str) -> Result<std::sync::MutexGuard<'_, CapTableInner>, RuntimeError> {
107 sync_lock::lock(&self.inner, where_)
108 }
109
110 pub fn bind_flow(&self, flow: FlowId) -> Result<Arc<RevocationCell>, RuntimeError> {
112 let mut g = self.lock("CapTable::bind_flow")?;
113 Ok(g.flow_cells
114 .entry(flow.as_u64())
115 .or_insert_with(|| Arc::new(RevocationCell::new()))
116 .clone())
117 }
118
119 pub fn flow_cell(&self, flow: FlowId) -> Result<Option<Arc<RevocationCell>>, RuntimeError> {
120 Ok(self.lock("CapTable::flow_cell")?.flow_cells.get(&flow.as_u64()).cloned())
121 }
122
123 pub fn scheduler_cell(&self) -> Arc<RevocationCell> {
124 Arc::clone(&self.scheduler_cell)
125 }
126
127 fn insert_fresh(
128 table: &mut HashMap<CapId, Capability>,
129 entry: Capability,
130 ) -> Result<CapId, RuntimeError> {
131 for _ in 0..MINT_ATTEMPTS {
132 let id = CapId::random().map_err(|_| RuntimeError::EntropyFailed)?;
133 if id.is_none() || table.contains_key(&id) {
134 continue;
135 }
136 table.insert(id, entry);
137 return Ok(id);
138 }
139 Err(RuntimeError::CapIdCollision)
140 }
141
142 fn insert(&self, holder: FlowId, cap: Cap) -> Result<CapId, RuntimeError> {
143 let mut table = self.lock("CapTable::insert")?;
144 Self::insert_fresh(
145 &mut table.entries,
146 Capability { holder, cap },
147 )
148 }
149
150 pub fn mint(
155 &self,
156 holder: FlowId,
157 target: FlowId,
158 rights: CapRights,
159 ) -> Result<CapId, RuntimeError> {
160 let cell = self.bind_flow(target)?;
161 let cap = Cap::root(CapTarget::Flow(target.as_u64()), rights, None, cell.as_ref());
162 self.grant(holder, cap)
163 }
164
165 pub fn grant(&self, holder: FlowId, cap: Cap) -> Result<CapId, RuntimeError> {
168 self.insert(holder, cap)
169 }
170
171 pub fn lookup(&self, id: CapId) -> Result<Option<Capability>, RuntimeError> {
173 if id.is_none() {
174 return Ok(None);
175 }
176 Ok(self.lock("CapTable::lookup")?.entries.get(&id).cloned())
177 }
178
179 pub fn resolve(
181 &self,
182 id: CapId,
183 holder: FlowId,
184 required: CapRights,
185 ) -> Result<Capability, CapError> {
186 if id.is_none() {
187 return Err(CapError::Unknown);
188 }
189 let table = self.lock("CapTable::resolve")?;
190 let entry = table.entries.get(&id).cloned().ok_or(CapError::Unknown)?;
191 if entry.holder != holder {
192 return Err(CapError::NotHolder);
193 }
194 if !entry.cap.rights.contains(required) {
195 return Err(CapError::InsufficientRights);
196 }
197 let valid = match entry.cap.target {
198 CapTarget::Flow(fid) => match table.flow_cells.get(&fid) {
199 Some(cell) => entry.cap.is_valid(cell.as_ref()),
200 None => false,
201 },
202 CapTarget::NativeTable => entry.cap.is_valid(self.native_cell.as_ref()),
203 CapTarget::Scheduler => entry.cap.is_valid(self.scheduler_cell.as_ref()),
204 };
205 if !valid {
206 return Err(CapError::Unknown);
207 }
208 Ok(entry)
209 }
210
211 pub fn attenuate(
214 &self,
215 id: CapId,
216 from_holder: FlowId,
217 to_holder: FlowId,
218 want_rights: CapRights,
219 want_native: Option<&NativeMask>,
220 ) -> Result<CapId, CapError> {
221 let src = self.resolve(id, from_holder, CapRights::empty())?;
222 let cell = match src.cap.target {
223 CapTarget::Flow(fid) => self
224 .lock("CapTable::attenuate")?
225 .flow_cells
226 .get(&fid)
227 .cloned()
228 .ok_or(CapError::Unknown)?,
229 CapTarget::NativeTable | CapTarget::Scheduler => {
230 return Err(CapError::WrongTarget);
231 }
232 };
233 let narrowed = match super::delegate::exec_delegate(
234 &src.cap,
235 cell.as_ref(),
236 want_rights,
237 want_native,
238 ) {
239 Ok(c) => c,
240 Err(super::delegate::DelegateError::SourceRevoked) => {
241 return Err(CapError::Unknown)
242 }
243 Err(super::delegate::DelegateError::SourceLacksNative) => {
244 return Err(CapError::InsufficientRights)
245 }
246 };
247 if !narrowed.is_valid(cell.as_ref()) {
248 return Err(CapError::Unknown);
249 }
250 self.insert(to_holder, narrowed).map_err(CapError::from)
251 }
252
253 pub fn delegate(
255 &self,
256 id: CapId,
257 from_holder: FlowId,
258 to_holder: FlowId,
259 ) -> Result<CapId, CapError> {
260 let src = self.resolve(id, from_holder, CapRights::empty())?;
261 self.attenuate(id, from_holder, to_holder, src.cap.rights, src.cap.native_mask.as_ref())
262 }
263
264 pub fn reissue_for(&self, id: CapId, new_holder: FlowId) -> Result<CapId, CapError> {
268 let cap = self.lookup(id)?.ok_or(CapError::Unknown)?;
269 let narrowed = cap.cap.attenuate(cap.cap.rights, cap.cap.native_mask.as_ref());
270 self.insert(new_holder, narrowed).map_err(CapError::from)
271 }
272
273 pub fn revoke_flow(&self, flow: FlowId) -> Result<usize, RuntimeError> {
276 let mut g = self.lock("CapTable::revoke_flow")?;
277 if let Some(cell) = g.flow_cells.get(&flow.as_u64()) {
278 cell.revoke();
279 }
280 g.flow_cells.remove(&flow.as_u64());
281 let before = g.entries.len();
282 let fid = flow.as_u64();
283 g.entries.retain(|_, e| {
284 e.holder != flow && e.cap.target != CapTarget::Flow(fid)
285 });
286 Ok(before - g.entries.len())
287 }
288}
289
290impl Default for CapTable {
291 fn default() -> Self {
292 Self::new()
293 }
294}
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299 use crate::scheduler::process::next_flow_id;
300
301 #[test]
302 fn random_ids_are_not_sequential() -> Result<(), Box<dyn std::error::Error>> {
303 let table = CapTable::new();
304 let h = next_flow_id();
305 let t = next_flow_id();
306 let a = table.mint(h, t, CapRights::SEND)?;
307 let b = table.mint(h, t, CapRights::SEND)?;
308 assert_ne!(a, b);
309 assert!(!a.is_none());
310 assert_eq!(
311 table.resolve(CapId::from_raw(1), h, CapRights::SEND),
312 Err(CapError::Unknown)
313 );
314 Ok(())
315 }
316
317 #[test]
318 fn resolve_requires_holder_and_rights() -> Result<(), Box<dyn std::error::Error>> {
319 let table = CapTable::new();
320 let holder = next_flow_id();
321 let other = next_flow_id();
322 let target = next_flow_id();
323 let cap = table.mint(holder, target, CapRights::SEND)?;
324 assert_eq!(
325 table.resolve(cap, holder, CapRights::SEND)?.target(),
326 Some(target)
327 );
328 assert_eq!(
329 table.resolve(cap, other, CapRights::SEND),
330 Err(CapError::NotHolder)
331 );
332 assert_eq!(
333 table.resolve(cap, holder, CapRights::ASK),
334 Err(CapError::InsufficientRights)
335 );
336 Ok(())
337 }
338
339 #[test]
340 fn capability_isolation_is_per_table() -> Result<(), Box<dyn std::error::Error>> {
341 let a = CapTable::new();
342 let b = CapTable::new();
343 let holder = next_flow_id();
344 let target = next_flow_id();
345 let cap = a.mint(holder, target, CapRights::SEND)?;
346 assert_eq!(
347 b.resolve(cap, holder, CapRights::SEND),
348 Err(CapError::Unknown)
349 );
350 Ok(())
351 }
352
353 #[test]
354 fn finalizing_flow_revokes_held_and_targeted_caps() -> Result<(), Box<dyn std::error::Error>> {
355 let table = CapTable::new();
356 let dead = next_flow_id();
357 let alive = next_flow_id();
358 let target = next_flow_id();
359
360 let held_by_dead = table.mint(dead, target, CapRights::SEND)?;
361 let targeting_dead = table.mint(alive, dead, CapRights::SEND)?;
362 let unrelated = table.mint(alive, target, CapRights::SEND)?;
363
364 let removed = table.revoke_flow(dead)?;
365 assert_eq!(removed, 2);
366 assert_eq!(
367 table.resolve(held_by_dead, dead, CapRights::SEND),
368 Err(CapError::Unknown)
369 );
370 assert_eq!(
371 table.resolve(targeting_dead, alive, CapRights::SEND),
372 Err(CapError::Unknown)
373 );
374 assert!(table.resolve(unrelated, alive, CapRights::SEND).is_ok());
375 Ok(())
376 }
377
378 #[test]
379 fn delegate_issues_a_new_id_for_the_child() -> Result<(), Box<dyn std::error::Error>> {
380 let table = CapTable::new();
381 let parent = next_flow_id();
382 let child = next_flow_id();
383 let target = next_flow_id();
384 let original = table.mint(parent, target, CapRights::SEND_ASK)?;
385 let granted = table.delegate(original, parent, child)?;
386 assert_ne!(granted, original);
387 assert_eq!(
388 table.resolve(granted, child, CapRights::SEND)?.target(),
389 Some(target)
390 );
391 assert_eq!(
392 table.resolve(original, child, CapRights::SEND),
393 Err(CapError::NotHolder)
394 );
395 assert!(table.resolve(original, parent, CapRights::ASK).is_ok());
396 Ok(())
397 }
398
399 #[test]
400 fn attenuate_cannot_escalate() -> Result<(), Box<dyn std::error::Error>> {
401 let table = CapTable::new();
402 let parent = next_flow_id();
403 let child = next_flow_id();
404 let target = next_flow_id();
405 let original = table.mint(parent, target, CapRights::SEND)?;
406 let granted = table.attenuate(
407 original,
408 parent,
409 child,
410 CapRights::SEND.union(CapRights::ADMIN),
411 None,
412 )?;
413 let got = table.resolve(granted, child, CapRights::SEND)?;
414 assert!(!got.rights().contains(CapRights::ADMIN));
415 assert!(got.rights().contains(CapRights::SEND));
416 Ok(())
417 }
418}