Skip to main content

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    /// Creates a new capability token for use by `#[capsec::permission]` generated code.
49    ///
50    /// This constructor is public so that derive macros can create `Cap<P>` for
51    /// user-defined permission types from external crates. The `SealProof` bound
52    /// ensures only types with a valid seal token can use this.
53    ///
54    /// Do not call directly — use `#[capsec::permission]` instead.
55    #[doc(hidden)]
56    pub fn __capsec_new_derived() -> Self
57    where
58        P: Permission<__CapsecSeal = crate::__private::SealProof>,
59    {
60        Self {
61            _phantom: PhantomData,
62            _not_send: PhantomData,
63        }
64    }
65
66    /// Converts this capability into a [`SendCap`] that can cross thread boundaries.
67    ///
68    /// This is an explicit opt-in — you're acknowledging that this capability
69    /// will be used in a multi-threaded context (e.g., passed into `tokio::spawn`).
70    pub fn make_send(self) -> SendCap<P> {
71        SendCap {
72            _phantom: PhantomData,
73        }
74    }
75}
76
77impl<P: Permission> Clone for Cap<P> {
78    fn clone(&self) -> Self {
79        Cap::new()
80    }
81}
82
83/// A thread-safe capability token that can be sent across threads.
84///
85/// Created explicitly via [`Cap::make_send`]. Unlike `Cap<P>`, this implements
86/// `Send + Sync`, making it usable with `std::thread::spawn`, `tokio::spawn`,
87/// `Arc`, etc.
88///
89/// # Example
90///
91/// ```rust,ignore
92/// # use capsec_core::root::test_root;
93/// # use capsec_core::permission::FsRead;
94/// let root = test_root();
95/// let send_cap = root.grant::<FsRead>().make_send();
96///
97/// std::thread::spawn(move || {
98///     let _cap = send_cap.as_cap();
99///     // use cap in this thread
100/// }).join().unwrap();
101/// ```
102pub struct SendCap<P: Permission> {
103    _phantom: PhantomData<P>,
104}
105
106// SAFETY: SendCap is explicitly opted into cross-thread transfer via make_send().
107// The capability token is a ZST proof type with no mutable state.
108unsafe impl<P: Permission> Send for SendCap<P> {}
109unsafe impl<P: Permission> Sync for SendCap<P> {}
110
111impl<P: Permission> SendCap<P> {
112    /// Creates a new send-capable token for use by `#[capsec::permission]` generated code.
113    ///
114    /// Do not call directly — use `#[capsec::permission]` instead.
115    #[doc(hidden)]
116    pub fn __capsec_new_send_derived() -> Self
117    where
118        P: Permission<__CapsecSeal = crate::__private::SealProof>,
119    {
120        Self {
121            _phantom: PhantomData,
122        }
123    }
124
125    /// Returns a new `Cap<P>` from this send-capable token.
126    ///
127    /// This creates a fresh `Cap` (not a reference cast) — safe because
128    /// both types are zero-sized proof tokens.
129    pub fn as_cap(&self) -> Cap<P> {
130        Cap::new()
131    }
132}
133
134impl<P: Permission> Clone for SendCap<P> {
135    fn clone(&self) -> Self {
136        SendCap {
137            _phantom: PhantomData,
138        }
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145    use crate::permission::FsRead;
146    use std::mem::size_of;
147
148    #[test]
149    fn cap_is_zst() {
150        assert_eq!(size_of::<Cap<FsRead>>(), 0);
151    }
152
153    #[test]
154    fn sendcap_is_zst() {
155        assert_eq!(size_of::<SendCap<FsRead>>(), 0);
156    }
157
158    #[test]
159    fn cap_is_cloneable() {
160        let root = crate::root::test_root();
161        let cap = root.grant::<FsRead>();
162        let _cap2 = cap.clone();
163    }
164
165    #[test]
166    fn sendcap_crosses_threads() {
167        let root = crate::root::test_root();
168        let send_cap = root.grant::<FsRead>().make_send();
169
170        std::thread::spawn(move || {
171            let _cap = send_cap.as_cap();
172        })
173        .join()
174        .unwrap();
175    }
176}