Skip to main content

native_ipc/
active.rs

1//! Runtime mappings exposed only after complete batch commit.
2//!
3//! Raw-pointer consumers must quiesce every thread that dereferences an
4//! acquired pointer before aborting, closing, or dropping the owning session
5//! or mapping: a successful acquisition proves liveness only at its own call
6//! boundary, never for any later use. The [`crate::binding`] module is the
7//! safe alternative for consumers who do not need raw pointers at all; it
8//! needs no `raw-pointer` feature.
9
10use core::cell::Cell;
11use core::fmt;
12use core::marker::PhantomData;
13use core::ops::Range;
14
15use crate::liveness::{LivenessState, RegionLease, ResourceError};
16use crate::region::{GuardCapability, GuardPolicy};
17
18/// Checked active-memory access failure.
19#[derive(Clone, Copy, Debug, Eq, PartialEq)]
20pub enum AccessError {
21    /// Offset plus byte count overflowed or exceeded the logical payload.
22    OutOfBounds,
23    /// The supplied range begins after its end.
24    InvalidRange,
25    /// The retaining session was poisoned or closed before this access began.
26    SessionInactive,
27}
28
29impl fmt::Display for AccessError {
30    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
31        write!(formatter, "active-region access failed: {self:?}")
32    }
33}
34
35impl std::error::Error for AccessError {}
36
37/// Bounded result of an explicit off-thread prefault operation.
38#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39pub struct PrefaultResult {
40    /// Requested logical byte count.
41    pub requested_bytes: usize,
42    /// Number of distinct covered page locations touched.
43    pub pages_touched: usize,
44}
45
46unsafe extern "C" {
47    fn native_ipc_0_4_0_vnext_v1_external_read(
48        source: *const u8,
49        destination: *mut u8,
50        length: usize,
51    );
52    fn native_ipc_0_4_0_vnext_v1_external_write(
53        destination: *mut u8,
54        source: *const u8,
55        length: usize,
56    );
57    fn native_ipc_0_4_0_vnext_v1_external_fill(destination: *mut u8, value: u8, length: usize);
58    fn native_ipc_0_4_0_vnext_v1_external_touch_read(address: *const u8);
59    fn native_ipc_0_4_0_vnext_v1_external_touch_write(address: *mut u8);
60}
61
62/// Private lifetime/permission witness for a stable read-only active mapping.
63///
64/// # Safety
65///
66/// The pointer must remain readable and initialized for `len` bytes until the
67/// owner is dropped. This value must uniquely own the exact local VM mapping
68/// described by that pointer and `len`: it may not delegate lifetime to an
69/// `Arc`, duplicate mapping owner, or other value that can retain the local
70/// mapping after this owner is dropped. Its non-panicking destructor must
71/// synchronously destroy that exact local mapping before returning. These
72/// local-ownership obligations do not revoke or shorten the peer's separately
73/// authorized mapping. Peer mutation may occur concurrently; no Rust reference
74/// may be formed from the pointer. The pointer must be aligned to the stable,
75/// nonzero `page_size`; pointer, length, and page size must not change.
76pub(crate) unsafe trait ActiveReadOwner: Send + Sync {
77    fn as_ptr(&self) -> *const u8;
78    fn len(&self) -> usize;
79    fn page_size(&self) -> usize;
80    /// Whether inaccessible guard bands are installed immediately before and
81    /// after this owner's exact local mapping. The honest default is `false`.
82    fn guard_installed(&self) -> bool {
83        false
84    }
85    #[allow(dead_code)]
86    fn liveness_state(&self) -> Option<LivenessState> {
87        None
88    }
89}
90
91/// Private lifetime/permission witness for the sole stable writable mapping.
92///
93/// # Safety
94///
95/// In addition to [`ActiveReadOwner`], the current endpoint must have native
96/// store authority for the complete range and no safe local writer alias.
97/// `as_mut_ptr()` must be stable, writable for `len` bytes, aligned to
98/// `page_size`, and identify the exact same base/range as `as_ptr()`. This
99/// value has the same unique exact-local-mapping ownership, synchronous unmap,
100/// and non-panicking destructor obligations as [`ActiveReadOwner`].
101pub(crate) unsafe trait ActiveWriteOwner: Send {
102    fn as_ptr(&self) -> *const u8;
103    fn as_mut_ptr(&mut self) -> *mut u8;
104    fn len(&self) -> usize;
105    fn page_size(&self) -> usize;
106    /// Whether inaccessible guard bands are installed immediately before and
107    /// after this owner's exact local mapping. The honest default is `false`.
108    fn guard_installed(&self) -> bool {
109        false
110    }
111    #[allow(dead_code)]
112    fn liveness_state(&self) -> Option<LivenessState> {
113        None
114    }
115}
116
117/// Stable read-only mapping of peer-writable hostile bytes.
118///
119/// Active mappings are uniquely owned and cannot be cloned.
120///
121/// ```compile_fail
122/// use native_ipc::active::ActiveReader;
123/// fn duplicate(reader: ActiveReader) { let _ = reader.clone(); }
124/// ```
125#[cfg_attr(
126    not(feature = "raw-pointer"),
127    doc = "```compile_fail\nuse native_ipc::active::ActiveReader;\nfn pointer(reader: &ActiveReader) { let _ = unsafe { reader.as_ptr() }; }\n```"
128)]
129pub struct ActiveReader {
130    owner: Box<dyn ActiveReadOwner>,
131    logical_len: usize,
132    guard_requested: GuardPolicy,
133}
134
135#[allow(dead_code)]
136struct LeasedReadOwner {
137    owner: Option<Box<dyn ActiveReadOwner>>,
138    lease: Option<RegionLease>,
139}
140
141#[allow(dead_code)]
142struct LeasedWriteOwner {
143    owner: Option<Box<dyn ActiveWriteOwner>>,
144    lease: Option<RegionLease>,
145}
146
147pub(crate) struct LeaseReservation {
148    lease: Option<RegionLease>,
149    not_sync: PhantomData<Cell<()>>,
150}
151
152#[derive(Clone, Copy, Debug, Eq, PartialEq)]
153#[allow(dead_code)]
154pub(crate) enum ActivationError {
155    Access(AccessError),
156    Resource(ResourceError),
157    MappingLengthOverflow,
158}
159
160impl ActiveReader {
161    fn ensure_active(&self) -> Result<(), AccessError> {
162        match self.owner.liveness_state() {
163            None | Some(LivenessState::Active) => Ok(()),
164            Some(LivenessState::Poisoned | LivenessState::Closed) => {
165                Err(AccessError::SessionInactive)
166            }
167        }
168    }
169
170    fn from_owner(
171        owner: Box<dyn ActiveReadOwner>,
172        logical_len: usize,
173    ) -> Result<Self, AccessError> {
174        if logical_len == 0
175            || logical_len > ActiveReadOwner::len(&*owner)
176            || owner.page_size() == 0
177            || !(owner.as_ptr() as usize).is_multiple_of(owner.page_size())
178        {
179            return Err(AccessError::OutOfBounds);
180        }
181        Ok(Self {
182            owner,
183            logical_len,
184            guard_requested: GuardPolicy::BestEffort,
185        })
186    }
187
188    #[allow(dead_code)]
189    pub(crate) fn new_leased(
190        owner: Box<dyn ActiveReadOwner>,
191        logical_len: usize,
192        reservation: LeaseReservation,
193        guard_requested: GuardPolicy,
194    ) -> Result<Self, ActivationError> {
195        let mut active = Self::from_owner(owner, logical_len).map_err(ActivationError::Access)?;
196        active.guard_requested = guard_requested;
197        let mapped_len = u64::try_from(active.owner.len())
198            .map_err(|_| ActivationError::MappingLengthOverflow)?;
199        let lease = reservation
200            .complete(mapped_len)
201            .map_err(ActivationError::Resource)?;
202        active.owner = Box::new(LeasedReadOwner {
203            owner: Some(active.owner),
204            lease: Some(lease),
205        });
206        Ok(active)
207    }
208
209    #[allow(dead_code)]
210    pub(crate) fn liveness_state(&self) -> Option<LivenessState> {
211        self.owner.liveness_state()
212    }
213
214    /// Logical application-visible byte length.
215    pub const fn len(&self) -> usize {
216        self.logical_len
217    }
218
219    /// Whether the logical payload is empty (always false for valid regions).
220    pub const fn is_empty(&self) -> bool {
221        self.logical_len == 0
222    }
223
224    /// Reports the guard policy applied to this endpoint's own view mapping
225    /// and whether inaccessible guard bands are actually installed around it.
226    ///
227    /// Guard bands contain in-process linear overruns past this view. They do
228    /// not constrain the peer's own address space, and they do not constrain
229    /// aliases created by a hostile holder of delegated native capability.
230    /// The receiving endpoint always applies best-effort installation; the
231    /// creating endpoint applies the policy requested for the region.
232    pub fn guard_capability(&self) -> GuardCapability {
233        GuardCapability {
234            requested: self.guard_requested,
235            installed: self.owner.guard_installed(),
236        }
237    }
238
239    pub(crate) fn payload_base(&self) -> core::ptr::NonNull<u8> {
240        // Owners validate a non-null page-aligned base at construction.
241        core::ptr::NonNull::new(self.owner.as_ptr().cast_mut())
242            .expect("active mapping base is never null")
243    }
244
245    /// Copies hostile externally mutable bytes into caller-owned storage.
246    ///
247    /// The copy is byte-volatile and may be torn or internally inconsistent.
248    /// It provides memory safety and bounds checking, not payload integrity.
249    pub fn read_into(&self, offset: usize, destination: &mut [u8]) -> Result<(), AccessError> {
250        self.ensure_active()?;
251        checked_end(offset, destination.len(), self.logical_len)?;
252        // SAFETY: the owner witness and checked range keep source bytes live;
253        // the C boundary performs volatile-qualified loads into caller-owned bytes.
254        unsafe {
255            native_ipc_0_4_0_vnext_v1_external_read(
256                self.owner.as_ptr().add(offset),
257                destination.as_mut_ptr(),
258                destination.len(),
259            );
260        }
261        Ok(())
262    }
263
264    /// Touches one byte per covered page off-thread.
265    pub fn prefault(&self, range: Range<usize>) -> Result<PrefaultResult, AccessError> {
266        self.ensure_active()?;
267        prefault_read(
268            self.owner.as_ptr(),
269            self.owner.page_size(),
270            self.logical_len,
271            range,
272        )
273    }
274
275    /// Returns the stable payload address without transferring ownership.
276    ///
277    /// # Safety
278    ///
279    /// The caller must remain within `len`, preserve the mapping lifetime,
280    /// never create references invalidated by peer mutation, accept torn bytes,
281    /// and supply all alignment, synchronization, atomic-ordering, and
282    /// application-data validation required by its layout. A successful return
283    /// proves only that the session was active at this call boundary; the
284    /// caller must arrange to stop dereferencing the pointer once its session
285    /// is poisoned or closed.
286    #[cfg(feature = "raw-pointer")]
287    pub unsafe fn as_ptr(&self) -> Result<*const u8, AccessError> {
288        self.ensure_active()?;
289        Ok(self.owner.as_ptr())
290    }
291}
292
293/// Stable sole-writer mapping. It is movable between threads but deliberately
294/// not shareable between threads.
295///
296/// ```compile_fail
297/// use native_ipc::active::ActiveWriter;
298/// fn assert_sync<T: Sync>() {}
299/// assert_sync::<ActiveWriter>();
300/// ```
301pub struct ActiveWriter {
302    owner: Box<dyn ActiveWriteOwner>,
303    logical_len: usize,
304    guard_requested: GuardPolicy,
305    _not_sync: PhantomData<Cell<()>>,
306}
307
308impl ActiveWriter {
309    fn ensure_active(&self) -> Result<(), AccessError> {
310        match self.owner.liveness_state() {
311            None | Some(LivenessState::Active) => Ok(()),
312            Some(LivenessState::Poisoned | LivenessState::Closed) => {
313                Err(AccessError::SessionInactive)
314            }
315        }
316    }
317
318    fn from_owner(
319        mut owner: Box<dyn ActiveWriteOwner>,
320        logical_len: usize,
321    ) -> Result<Self, AccessError> {
322        if logical_len == 0
323            || logical_len > ActiveWriteOwner::len(&*owner)
324            || owner.page_size() == 0
325            || !(owner.as_ptr() as usize).is_multiple_of(owner.page_size())
326            || owner.as_ptr() != owner.as_mut_ptr().cast_const()
327        {
328            return Err(AccessError::OutOfBounds);
329        }
330        Ok(Self {
331            owner,
332            logical_len,
333            guard_requested: GuardPolicy::BestEffort,
334            _not_sync: PhantomData,
335        })
336    }
337
338    #[allow(dead_code)]
339    pub(crate) fn new_leased(
340        owner: Box<dyn ActiveWriteOwner>,
341        logical_len: usize,
342        reservation: LeaseReservation,
343        guard_requested: GuardPolicy,
344    ) -> Result<Self, ActivationError> {
345        let mut active = Self::from_owner(owner, logical_len).map_err(ActivationError::Access)?;
346        active.guard_requested = guard_requested;
347        let mapped_len = u64::try_from(active.owner.len())
348            .map_err(|_| ActivationError::MappingLengthOverflow)?;
349        let lease = reservation
350            .complete(mapped_len)
351            .map_err(ActivationError::Resource)?;
352        active.owner = Box::new(LeasedWriteOwner {
353            owner: Some(active.owner),
354            lease: Some(lease),
355        });
356        Ok(active)
357    }
358
359    #[allow(dead_code)]
360    pub(crate) fn liveness_state(&self) -> Option<LivenessState> {
361        self.owner.liveness_state()
362    }
363
364    /// Logical application-visible byte length.
365    pub const fn len(&self) -> usize {
366        self.logical_len
367    }
368
369    /// Whether the logical payload is empty (always false for valid regions).
370    pub const fn is_empty(&self) -> bool {
371        self.logical_len == 0
372    }
373
374    /// Reports the guard policy applied to this endpoint's own view mapping
375    /// and whether inaccessible guard bands are actually installed around it.
376    ///
377    /// Guard bands contain in-process linear overruns past this view. They do
378    /// not constrain the peer's own address space, and they do not constrain
379    /// aliases created by a hostile holder of delegated native capability.
380    /// The receiving endpoint always applies best-effort installation; the
381    /// creating endpoint applies the policy requested for the region.
382    pub fn guard_capability(&self) -> GuardCapability {
383        GuardCapability {
384            requested: self.guard_requested,
385            installed: self.owner.guard_installed(),
386        }
387    }
388
389    pub(crate) fn payload_base_mut(&mut self) -> core::ptr::NonNull<u8> {
390        core::ptr::NonNull::new(self.owner.as_mut_ptr()).expect("active mapping base is never null")
391    }
392
393    /// Copies caller bytes into the sole writable mapping.
394    pub fn write_from(&mut self, offset: usize, source: &[u8]) -> Result<(), AccessError> {
395        self.ensure_active()?;
396        checked_end(offset, source.len(), self.logical_len)?;
397        // SAFETY: exclusive self and the owner witness supply sole store
398        // authority; checked_end proves both complete ranges.
399        unsafe {
400            native_ipc_0_4_0_vnext_v1_external_write(
401                self.owner.as_mut_ptr().add(offset),
402                source.as_ptr(),
403                source.len(),
404            );
405        }
406        Ok(())
407    }
408
409    /// Fills a checked logical range with one byte value.
410    pub fn fill(&mut self, range: Range<usize>, value: u8) -> Result<(), AccessError> {
411        self.ensure_active()?;
412        validate_range(&range, self.logical_len)?;
413        let length = range.end - range.start;
414        // SAFETY: range validation and the exclusive native writer witness
415        // establish the same obligations as write_from.
416        unsafe {
417            native_ipc_0_4_0_vnext_v1_external_fill(
418                self.owner.as_mut_ptr().add(range.start),
419                value,
420                length,
421            );
422        }
423        Ok(())
424    }
425
426    /// Touches one byte per covered page off-thread without changing contents.
427    pub fn prefault(&mut self, range: Range<usize>) -> Result<PrefaultResult, AccessError> {
428        self.ensure_active()?;
429        let result = prefault_read(
430            self.owner.as_ptr(),
431            self.owner.page_size(),
432            self.logical_len,
433            range.clone(),
434        )?;
435        if !range.is_empty() {
436            let base = self.owner.as_mut_ptr();
437            let mut offset = range.start;
438            loop {
439                // SAFETY: prefault_read validated the range; exclusive self and
440                // the owner witness permit a same-value volatile store.
441                unsafe { native_ipc_0_4_0_vnext_v1_external_touch_write(base.add(offset)) };
442                let page = (offset / self.owner.page_size())
443                    .checked_add(1)
444                    .ok_or(AccessError::OutOfBounds)?;
445                let next = page
446                    .checked_mul(self.owner.page_size())
447                    .ok_or(AccessError::OutOfBounds)?;
448                if next >= range.end {
449                    break;
450                }
451                offset = next;
452            }
453        }
454        Ok(result)
455    }
456
457    /// Returns the stable readable payload address.
458    ///
459    /// # Safety
460    ///
461    /// The caller must uphold the bounds, lifetime, aliasing, synchronization,
462    /// atomic-ordering, and peer-mutation obligations in [`ActiveReader::as_ptr`].
463    #[cfg(feature = "raw-pointer")]
464    pub unsafe fn as_ptr(&self) -> Result<*const u8, AccessError> {
465        self.ensure_active()?;
466        Ok(self.owner.as_ptr())
467    }
468
469    /// Returns the stable writable payload address without transferring ownership.
470    ///
471    /// # Safety
472    ///
473    /// The caller must uphold bounds, alignment, initialization, lifetime,
474    /// aliasing, synchronization, atomic ordering, and peer-access obligations.
475    /// A successful return proves liveness only at this call boundary; the
476    /// pointer must not be dereferenced after the session becomes inactive.
477    #[cfg(feature = "raw-pointer")]
478    pub unsafe fn as_mut_ptr(&mut self) -> Result<*mut u8, AccessError> {
479        self.ensure_active()?;
480        Ok(self.owner.as_mut_ptr())
481    }
482}
483
484unsafe impl ActiveReadOwner for LeasedReadOwner {
485    fn as_ptr(&self) -> *const u8 {
486        self.owner().as_ptr()
487    }
488
489    fn len(&self) -> usize {
490        self.owner().len()
491    }
492
493    fn page_size(&self) -> usize {
494        self.owner().page_size()
495    }
496
497    fn guard_installed(&self) -> bool {
498        self.owner().guard_installed()
499    }
500
501    fn liveness_state(&self) -> Option<LivenessState> {
502        Some(self.lease().state())
503    }
504}
505
506unsafe impl ActiveWriteOwner for LeasedWriteOwner {
507    fn as_ptr(&self) -> *const u8 {
508        self.owner().as_ptr()
509    }
510
511    fn as_mut_ptr(&mut self) -> *mut u8 {
512        self.owner_mut().as_mut_ptr()
513    }
514
515    fn len(&self) -> usize {
516        self.owner().len()
517    }
518
519    fn page_size(&self) -> usize {
520        self.owner().page_size()
521    }
522
523    fn guard_installed(&self) -> bool {
524        self.owner().guard_installed()
525    }
526
527    fn liveness_state(&self) -> Option<LivenessState> {
528        Some(self.lease().state())
529    }
530}
531
532#[allow(dead_code)]
533impl LeasedReadOwner {
534    fn owner(&self) -> &dyn ActiveReadOwner {
535        &**self.owner.as_ref().expect("mapping precedes lease drop")
536    }
537
538    fn lease(&self) -> &RegionLease {
539        self.lease.as_ref().expect("lease follows mapping drop")
540    }
541}
542
543#[allow(dead_code)]
544impl LeasedWriteOwner {
545    fn owner(&self) -> &dyn ActiveWriteOwner {
546        &**self.owner.as_ref().expect("mapping precedes lease drop")
547    }
548
549    fn owner_mut(&mut self) -> &mut dyn ActiveWriteOwner {
550        &mut **self.owner.as_mut().expect("mapping precedes lease drop")
551    }
552
553    fn lease(&self) -> &RegionLease {
554        self.lease.as_ref().expect("lease follows mapping drop")
555    }
556}
557
558impl Drop for LeasedReadOwner {
559    fn drop(&mut self) {
560        let lease_guard = self.lease.take();
561        drop(self.owner.take());
562        drop(lease_guard);
563    }
564}
565
566impl Drop for LeasedWriteOwner {
567    fn drop(&mut self) {
568        let lease_guard = self.lease.take();
569        drop(self.owner.take());
570        drop(lease_guard);
571    }
572}
573
574impl LeaseReservation {
575    pub(super) fn new(lease: RegionLease) -> Self {
576        Self {
577            lease: Some(lease),
578            not_sync: PhantomData,
579        }
580    }
581
582    fn complete(mut self, actual_mapped_len: u64) -> Result<RegionLease, ResourceError> {
583        let lease = self
584            .lease
585            .as_ref()
586            .expect("reservation retains its charge until completion or drop");
587        if lease.bytes() != actual_mapped_len {
588            return Err(ResourceError::MappedLengthMismatch {
589                reserved: lease.bytes(),
590                actual: actual_mapped_len,
591            });
592        }
593        match lease.state() {
594            LivenessState::Active => {}
595            LivenessState::Poisoned => return Err(ResourceError::Poisoned),
596            LivenessState::Closed => return Err(ResourceError::Closed),
597        }
598        Ok(self
599            .lease
600            .take()
601            .expect("validated reservation still owns its charge"))
602    }
603}
604
605fn checked_end(offset: usize, length: usize, limit: usize) -> Result<usize, AccessError> {
606    let end = offset.checked_add(length).ok_or(AccessError::OutOfBounds)?;
607    if end > limit {
608        return Err(AccessError::OutOfBounds);
609    }
610    Ok(end)
611}
612
613fn validate_range(range: &Range<usize>, limit: usize) -> Result<(), AccessError> {
614    if range.start > range.end {
615        return Err(AccessError::InvalidRange);
616    }
617    checked_end(range.start, range.end - range.start, limit)?;
618    Ok(())
619}
620
621fn prefault_read(
622    base: *const u8,
623    page_size: usize,
624    logical_len: usize,
625    range: Range<usize>,
626) -> Result<PrefaultResult, AccessError> {
627    validate_range(&range, logical_len)?;
628    let requested_bytes = range.end - range.start;
629    if requested_bytes == 0 {
630        return Ok(PrefaultResult {
631            requested_bytes: 0,
632            pages_touched: 0,
633        });
634    }
635    let mut touches = 0;
636    let mut offset = range.start;
637    loop {
638        // SAFETY: the validated range is within the owner mapping; the C
639        // boundary performs one volatile-qualified read.
640        unsafe { native_ipc_0_4_0_vnext_v1_external_touch_read(base.add(offset)) };
641        touches += 1;
642        let next_page = (offset / page_size)
643            .checked_add(1)
644            .ok_or(AccessError::OutOfBounds)?;
645        let next = next_page
646            .checked_mul(page_size)
647            .ok_or(AccessError::OutOfBounds)?;
648        if next >= range.end {
649            break;
650        }
651        offset = next;
652    }
653    Ok(PrefaultResult {
654        requested_bytes,
655        pages_touched: touches,
656    })
657}
658
659#[cfg(test)]
660#[path = "active_test.rs"]
661mod tests;