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