capsec_core/cap.rs
1//! The zero-sized capability token [`Cap<P>`] and its thread-safe variant [`SendCap<P>`].
2//!
3//! `Cap<P>` is the core proof type in capsec. Holding a `Cap<FsRead>` proves you
4//! have permission to read files. It is:
5//!
6//! - **Zero-sized** — no runtime cost, erased at compilation
7//! - **Unconstructible externally** — `Cap::new()` is `pub(crate)`, so only
8//! [`CapRoot::grant`](crate::root::CapRoot::grant) can create them
9//! - **`!Send + !Sync`** — scoped to the creating thread by default
10//!
11//! Use [`make_send`](Cap::make_send) to explicitly opt into cross-thread transfer
12//! when needed (e.g., for `tokio::spawn`).
13
14use crate::permission::Permission;
15use std::marker::PhantomData;
16
17/// A zero-sized capability token proving the holder has permission `P`.
18///
19/// Cannot be constructed outside of `capsec-core` — only
20/// [`CapRoot::grant`](crate::root::CapRoot::grant) can create one.
21/// `!Send` and `!Sync` by default to scope capabilities to the granting thread.
22///
23/// # Example
24///
25/// ```rust,ignore
26/// # use capsec_core::root::test_root;
27/// # use capsec_core::permission::FsRead;
28/// let root = test_root();
29/// let cap = root.grant::<FsRead>();
30/// // cap is a proof token — zero bytes at runtime
31/// assert_eq!(std::mem::size_of_val(&cap), 0);
32/// ```
33pub struct Cap<P: Permission> {
34 _phantom: PhantomData<P>,
35 // PhantomData<*const ()> makes Cap !Send + !Sync
36 _not_send: PhantomData<*const ()>,
37}
38
39impl<P: Permission> Cap<P> {
40 /// Creates a new capability token. Only callable within `capsec-core`.
41 pub(crate) fn new() -> Self {
42 Self {
43 _phantom: PhantomData,
44 _not_send: PhantomData,
45 }
46 }
47
48 /// Converts this capability into a [`SendCap`] that can cross thread boundaries.
49 ///
50 /// This is an explicit opt-in — you're acknowledging that this capability
51 /// will be used in a multi-threaded context (e.g., passed into `tokio::spawn`).
52 pub fn make_send(self) -> SendCap<P> {
53 SendCap {
54 _phantom: PhantomData,
55 }
56 }
57}
58
59impl<P: Permission> Clone for Cap<P> {
60 fn clone(&self) -> Self {
61 Cap::new()
62 }
63}
64
65/// A thread-safe capability token that can be sent across threads.
66///
67/// Created explicitly via [`Cap::make_send`]. Unlike `Cap<P>`, this implements
68/// `Send + Sync`, making it usable with `std::thread::spawn`, `tokio::spawn`,
69/// `Arc`, etc.
70///
71/// # Example
72///
73/// ```rust,ignore
74/// # use capsec_core::root::test_root;
75/// # use capsec_core::permission::FsRead;
76/// let root = test_root();
77/// let send_cap = root.grant::<FsRead>().make_send();
78///
79/// std::thread::spawn(move || {
80/// let _cap = send_cap.as_cap();
81/// // use cap in this thread
82/// }).join().unwrap();
83/// ```
84pub struct SendCap<P: Permission> {
85 _phantom: PhantomData<P>,
86}
87
88// SAFETY: SendCap is explicitly opted into cross-thread transfer via make_send().
89// The capability token is a ZST proof type with no mutable state.
90unsafe impl<P: Permission> Send for SendCap<P> {}
91unsafe impl<P: Permission> Sync for SendCap<P> {}
92
93impl<P: Permission> SendCap<P> {
94 /// Returns a new `Cap<P>` from this send-capable token.
95 ///
96 /// This creates a fresh `Cap` (not a reference cast) — safe because
97 /// both types are zero-sized proof tokens.
98 pub fn as_cap(&self) -> Cap<P> {
99 Cap::new()
100 }
101}
102
103impl<P: Permission> Clone for SendCap<P> {
104 fn clone(&self) -> Self {
105 SendCap {
106 _phantom: PhantomData,
107 }
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use crate::permission::FsRead;
115 use std::mem::size_of;
116
117 #[test]
118 fn cap_is_zst() {
119 assert_eq!(size_of::<Cap<FsRead>>(), 0);
120 }
121
122 #[test]
123 fn sendcap_is_zst() {
124 assert_eq!(size_of::<SendCap<FsRead>>(), 0);
125 }
126
127 #[test]
128 fn cap_is_cloneable() {
129 let root = crate::root::test_root();
130 let cap = root.grant::<FsRead>();
131 let _cap2 = cap.clone();
132 }
133
134 #[test]
135 fn sendcap_crosses_threads() {
136 let root = crate::root::test_root();
137 let send_cap = root.grant::<FsRead>().make_send();
138
139 std::thread::spawn(move || {
140 let _cap = send_cap.as_cap();
141 })
142 .join()
143 .unwrap();
144 }
145}