Skip to main content

native_ipc/
region.rs

1//! Platform-neutral private and prepared shared-memory regions.
2
3use core::cell::Cell;
4use core::fmt;
5use core::marker::PhantomData;
6
7use crate::memory;
8
9/// Caller-selected opaque region identity.
10#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
11pub struct RegionId(u128);
12
13impl RegionId {
14    /// Constructs a nonzero opaque identity.
15    pub const fn new(value: u128) -> Option<Self> {
16        if value == 0 { None } else { Some(Self(value)) }
17    }
18
19    /// Returns the caller-selected numeric value.
20    pub const fn get(self) -> u128 {
21        self.0
22    }
23}
24
25/// Endpoint with store authority after commit.
26#[derive(Clone, Copy, Debug, Eq, PartialEq)]
27pub enum WriterEndpoint {
28    /// The spawning coordinator writes and the receiver reads.
29    Coordinator,
30    /// The spawned receiver writes and the coordinator reads.
31    Receiver,
32}
33
34/// Identity and writer direction attached during consuming preparation.
35#[derive(Clone, Copy, Debug, Eq, PartialEq)]
36pub struct RegionSpec {
37    /// Opaque identity, unique within a batch.
38    pub id: RegionId,
39    /// Coordinator-relative writer direction.
40    pub writer: WriterEndpoint,
41}
42
43/// Requested guard-band behavior around an endpoint's own active view mapping.
44///
45/// Guard bands are inaccessible address ranges installed immediately before
46/// and after each endpoint's own active view mapping when a committed batch
47/// maps its views. They contain in-process linear overruns past a view. They
48/// do not constrain the peer's own address space, and they do not constrain
49/// aliases created by a hostile holder of delegated native capability.
50///
51/// The creating endpoint honors the policy requested for the region. The
52/// receiving endpoint always applies best-effort installation, because the
53/// wire manifest does not carry the policy.
54#[derive(Clone, Copy, Debug, Eq, PartialEq)]
55pub enum GuardPolicy {
56    /// Install guard bands where reliable placement is available and report
57    /// the outcome honestly instead of failing.
58    BestEffort,
59    /// Fail batch preparation or commit on the creating endpoint unless its
60    /// own view mappings receive guard bands.
61    Require,
62    /// Do not request guard bands on the creating endpoint.
63    Disable,
64}
65
66/// Guard-band request and the reporting endpoint's installation outcome.
67#[derive(Clone, Copy, Debug, Eq, PartialEq)]
68pub struct GuardCapability {
69    /// Policy requested by the caller.
70    pub requested: GuardPolicy,
71    /// Whether inaccessible guard bands are actually installed around the
72    /// reporting endpoint's own view mapping.
73    pub installed: bool,
74}
75
76/// Immutable private allocation policy.
77#[derive(Clone, Copy, Debug, Eq, PartialEq)]
78pub struct RegionOptions {
79    logical_len: usize,
80    maximum_len: usize,
81    guard: GuardPolicy,
82}
83
84impl RegionOptions {
85    /// Creates a fixed-length private region.
86    pub const fn fixed(logical_len: usize) -> Self {
87        Self {
88            logical_len,
89            maximum_len: logical_len,
90            guard: GuardPolicy::BestEffort,
91        }
92    }
93
94    /// Sets the inclusive replacement-growth limit before preparation.
95    pub const fn with_max_bytes(mut self, maximum_len: usize) -> Self {
96        self.maximum_len = maximum_len;
97        self
98    }
99
100    /// Selects guard-band behavior.
101    pub const fn with_guard_policy(mut self, guard: GuardPolicy) -> Self {
102        self.guard = guard;
103        self
104    }
105}
106
107/// Portable private allocation or preparation failure.
108#[derive(Debug)]
109pub enum RegionError {
110    /// Portable or native allocation/preparation failed.
111    Memory(memory::MemoryError),
112    /// Required reliable guard-band placement is unavailable. Batch and
113    /// session operations report the equivalent commit-time failure through
114    /// their own error surfaces.
115    GuardUnavailable,
116}
117
118impl fmt::Display for RegionError {
119    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
120        match self {
121            Self::Memory(error) => error.fmt(formatter),
122            Self::GuardUnavailable => formatter.write_str("required guard bands are unavailable"),
123        }
124    }
125}
126
127impl std::error::Error for RegionError {}
128
129impl From<memory::MemoryError> for RegionError {
130    fn from(value: memory::MemoryError) -> Self {
131        Self::Memory(value)
132    }
133}
134
135/// Unique writable region before any native capability escapes.
136pub struct PrivateRegion {
137    inner: memory::NativeRegion,
138    guard: GuardPolicy,
139    _not_sync: PhantomData<Cell<()>>,
140}
141
142// SAFETY: the value uniquely owns its mapping and exposes mutation only through
143// `&mut self`; moving that ownership between threads does not create aliases.
144unsafe impl Send for PrivateRegion {}
145
146impl PrivateRegion {
147    /// Allocates zeroed anonymous memory with a non-executable library view.
148    ///
149    /// Delegated native authority follows the documented target policy; on
150    /// Linux, a malicious memfd holder may create a separate executable alias.
151    ///
152    /// Every [`GuardPolicy`] is accepted here: guard bands are installed when
153    /// a committed batch maps each endpoint's own active views, so a
154    /// [`GuardPolicy::Require`] region fails at batch preparation or commit,
155    /// not at allocation, when bands cannot install.
156    pub fn allocate(options: RegionOptions) -> Result<Self, RegionError> {
157        let native = if options.maximum_len == options.logical_len {
158            memory::RegionOptions::fixed(options.logical_len, memory::WriterOwner::Creator)
159        } else {
160            memory::RegionOptions::growable(
161                options.logical_len,
162                options.maximum_len,
163                memory::WriterOwner::Creator,
164            )
165        };
166        Ok(Self {
167            inner: memory::NativeRegion::allocate(native)?,
168            guard: options.guard,
169            _not_sync: PhantomData,
170        })
171    }
172
173    /// Runs scoped initialization over logical bytes only.
174    pub fn initialize<R>(&mut self, operation: impl FnOnce(&mut [u8]) -> R) -> R {
175        self.inner.initialize(operation)
176    }
177
178    /// Consumes private ownership and attaches opaque transfer metadata.
179    pub fn prepare(self, spec: RegionSpec) -> Result<PreparedRegion, RegionError> {
180        let writer = match spec.writer {
181            WriterEndpoint::Coordinator => memory::WriterOwner::Creator,
182            WriterEndpoint::Receiver => memory::WriterOwner::Peer,
183        };
184        let request = self.inner.prepare_with_writer(writer)?;
185        Ok(PreparedRegion {
186            request,
187            spec,
188            guard: GuardCapability {
189                requested: self.guard,
190                installed: false,
191            },
192            #[cfg(test)]
193            drop_observer: PreparedDropObserver(None),
194            _not_sync: PhantomData,
195        })
196    }
197}
198
199/// Opaque prepared native object awaiting ownership by one transfer batch.
200///
201/// This state has no payload access, cloning, or raw native-parts operation.
202///
203/// ```compile_fail
204/// use native_ipc::region::PreparedRegion;
205/// fn access(pending: &PreparedRegion) { let _ = pending.read_into(0, &mut []); }
206/// ```
207pub struct PreparedRegion {
208    #[cfg(test)]
209    drop_observer: PreparedDropObserver,
210    #[allow(dead_code)]
211    pub(crate) request: memory::NativeShareRequest,
212    #[allow(dead_code)]
213    pub(crate) spec: RegionSpec,
214    #[allow(dead_code)]
215    pub(crate) guard: GuardCapability,
216    _not_sync: PhantomData<Cell<()>>,
217}
218
219#[cfg(test)]
220struct PreparedDropObserver(Option<std::sync::Arc<std::sync::Mutex<Vec<&'static str>>>>);
221
222#[cfg(test)]
223impl Drop for PreparedDropObserver {
224    fn drop(&mut self) {
225        if let Some(events) = &self.0 {
226            events.lock().unwrap().push("prepared-drop");
227        }
228    }
229}
230
231// SAFETY: preparation retains unique ownership and exposes no shared access.
232unsafe impl Send for PreparedRegion {}
233
234impl PreparedRegion {
235    /// Reports the requested guard policy for this prepared region.
236    ///
237    /// Preparation never installs guard bands, so `installed` is always
238    /// `false` here: bands are installed when a committed batch maps each
239    /// endpoint's own active views. After commit,
240    /// [`crate::active::ActiveReader::guard_capability`] and
241    /// [`crate::active::ActiveWriter::guard_capability`] report the actual
242    /// installation outcome.
243    pub const fn guard_capability(&self) -> GuardCapability {
244        self.guard
245    }
246
247    pub(crate) const fn spec(&self) -> RegionSpec {
248        self.spec
249    }
250
251    #[allow(dead_code)]
252    pub(crate) fn logical_len(&self) -> usize {
253        self.request.logical_len()
254    }
255
256    #[allow(dead_code)]
257    pub(crate) fn mapped_len(&self) -> usize {
258        self.request.mapped_len()
259    }
260
261    #[cfg(test)]
262    pub(crate) fn observe_drop(
263        mut self,
264        events: std::sync::Arc<std::sync::Mutex<Vec<&'static str>>>,
265    ) -> Self {
266        self.drop_observer.0 = Some(events);
267        self
268    }
269
270    #[cfg(target_os = "linux")]
271    pub(crate) fn into_linux_transfer_parts(
272        self,
273    ) -> (memory::NativeShareRequest, RegionSpec, GuardCapability) {
274        let Self {
275            #[cfg(test)]
276                drop_observer: _,
277            request,
278            spec,
279            guard,
280            _not_sync: _,
281        } = self;
282        (request, spec, guard)
283    }
284
285    #[cfg(target_os = "macos")]
286    pub(crate) fn into_macos_transfer_parts(
287        self,
288    ) -> (memory::NativeShareRequest, RegionSpec, GuardCapability) {
289        let Self {
290            #[cfg(test)]
291                drop_observer: _,
292            request,
293            spec,
294            guard,
295            _not_sync: _,
296        } = self;
297        (request, spec, guard)
298    }
299
300    #[cfg(target_os = "windows")]
301    pub(crate) fn into_windows_transfer_parts(
302        self,
303    ) -> (memory::NativeShareRequest, RegionSpec, GuardCapability) {
304        let Self {
305            #[cfg(test)]
306                drop_observer: _,
307            request,
308            spec,
309            guard,
310            _not_sync: _,
311        } = self;
312        (request, spec, guard)
313    }
314}
315
316#[cfg(test)]
317#[path = "region_test.rs"]
318mod tests;