native-ipc 0.6.0

One safe API for least-authority native shared memory: sealed memfd on Linux, Mach memory entries on macOS, exact-rights sections on Windows
//! Platform-neutral private and prepared shared-memory regions.

use core::cell::Cell;
use core::fmt;
use core::marker::PhantomData;

use crate::memory;

/// Caller-selected opaque region identity.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct RegionId(u128);

impl RegionId {
    /// Constructs a nonzero opaque identity.
    pub const fn new(value: u128) -> Option<Self> {
        if value == 0 { None } else { Some(Self(value)) }
    }

    /// Returns the caller-selected numeric value.
    pub const fn get(self) -> u128 {
        self.0
    }
}

/// Endpoint with store authority after commit.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum WriterEndpoint {
    /// The spawning coordinator writes and the receiver reads.
    Coordinator,
    /// The spawned receiver writes and the coordinator reads.
    Receiver,
}

/// Identity and writer direction attached during consuming preparation.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct RegionSpec {
    /// Opaque identity, unique within a batch.
    pub id: RegionId,
    /// Coordinator-relative writer direction.
    pub writer: WriterEndpoint,
}

/// Requested guard-band behavior around an endpoint's own active view mapping.
///
/// Guard bands are inaccessible address ranges installed immediately before
/// and after each endpoint's own active view mapping when a committed batch
/// maps its views. They contain in-process linear overruns past a view. They
/// do not constrain the peer's own address space, and they do not constrain
/// aliases created by a hostile holder of delegated native capability.
///
/// The creating endpoint honors the policy requested for the region. The
/// receiving endpoint always applies best-effort installation, because the
/// wire manifest does not carry the policy.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GuardPolicy {
    /// Install guard bands where reliable placement is available and report
    /// the outcome honestly instead of failing.
    BestEffort,
    /// Fail batch preparation or commit on the creating endpoint unless its
    /// own view mappings receive guard bands.
    Require,
    /// Do not request guard bands on the creating endpoint.
    Disable,
}

/// Guard-band request and the reporting endpoint's installation outcome.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct GuardCapability {
    /// Policy requested by the caller.
    pub requested: GuardPolicy,
    /// Whether inaccessible guard bands are actually installed around the
    /// reporting endpoint's own view mapping.
    pub installed: bool,
}

/// Immutable private allocation policy.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct RegionOptions {
    logical_len: usize,
    maximum_len: usize,
    guard: GuardPolicy,
}

impl RegionOptions {
    /// Creates a fixed-length private region.
    pub const fn fixed(logical_len: usize) -> Self {
        Self {
            logical_len,
            maximum_len: logical_len,
            guard: GuardPolicy::BestEffort,
        }
    }

    /// Sets the inclusive replacement-growth limit before preparation.
    pub const fn with_max_bytes(mut self, maximum_len: usize) -> Self {
        self.maximum_len = maximum_len;
        self
    }

    /// Selects guard-band behavior.
    pub const fn with_guard_policy(mut self, guard: GuardPolicy) -> Self {
        self.guard = guard;
        self
    }
}

/// Portable private allocation or preparation failure.
#[derive(Debug)]
pub enum RegionError {
    /// Portable or native allocation/preparation failed.
    Memory(memory::MemoryError),
    /// Required reliable guard-band placement is unavailable. Batch and
    /// session operations report the equivalent commit-time failure through
    /// their own error surfaces.
    GuardUnavailable,
}

impl fmt::Display for RegionError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Memory(error) => error.fmt(formatter),
            Self::GuardUnavailable => formatter.write_str("required guard bands are unavailable"),
        }
    }
}

impl std::error::Error for RegionError {}

impl From<memory::MemoryError> for RegionError {
    fn from(value: memory::MemoryError) -> Self {
        Self::Memory(value)
    }
}

/// Unique writable region before any native capability escapes.
pub struct PrivateRegion {
    inner: memory::NativeRegion,
    guard: GuardPolicy,
    _not_sync: PhantomData<Cell<()>>,
}

// SAFETY: the value uniquely owns its mapping and exposes mutation only through
// `&mut self`; moving that ownership between threads does not create aliases.
unsafe impl Send for PrivateRegion {}

impl PrivateRegion {
    /// Allocates zeroed anonymous memory with a non-executable library view.
    ///
    /// Delegated native authority follows the documented target policy; on
    /// Linux, a malicious memfd holder may create a separate executable alias.
    ///
    /// Every [`GuardPolicy`] is accepted here: guard bands are installed when
    /// a committed batch maps each endpoint's own active views, so a
    /// [`GuardPolicy::Require`] region fails at batch preparation or commit,
    /// not at allocation, when bands cannot install.
    pub fn allocate(options: RegionOptions) -> Result<Self, RegionError> {
        let native = if options.maximum_len == options.logical_len {
            memory::RegionOptions::fixed(options.logical_len, memory::WriterOwner::Creator)
        } else {
            memory::RegionOptions::growable(
                options.logical_len,
                options.maximum_len,
                memory::WriterOwner::Creator,
            )
        };
        Ok(Self {
            inner: memory::NativeRegion::allocate(native)?,
            guard: options.guard,
            _not_sync: PhantomData,
        })
    }

    /// Runs scoped initialization over logical bytes only.
    pub fn initialize<R>(&mut self, operation: impl FnOnce(&mut [u8]) -> R) -> R {
        self.inner.initialize(operation)
    }

    /// Consumes private ownership and attaches opaque transfer metadata.
    pub fn prepare(self, spec: RegionSpec) -> Result<PreparedRegion, RegionError> {
        let writer = match spec.writer {
            WriterEndpoint::Coordinator => memory::WriterOwner::Creator,
            WriterEndpoint::Receiver => memory::WriterOwner::Peer,
        };
        let request = self.inner.prepare_with_writer(writer)?;
        Ok(PreparedRegion {
            request,
            spec,
            guard: GuardCapability {
                requested: self.guard,
                installed: false,
            },
            #[cfg(test)]
            drop_observer: PreparedDropObserver(None),
            _not_sync: PhantomData,
        })
    }
}

/// Opaque prepared native object awaiting ownership by one transfer batch.
///
/// This state has no payload access, cloning, or raw native-parts operation.
///
/// ```compile_fail
/// use native_ipc::region::PreparedRegion;
/// fn access(pending: &PreparedRegion) { let _ = pending.read_into(0, &mut []); }
/// ```
pub struct PreparedRegion {
    #[cfg(test)]
    drop_observer: PreparedDropObserver,
    #[allow(dead_code)]
    pub(crate) request: memory::NativeShareRequest,
    #[allow(dead_code)]
    pub(crate) spec: RegionSpec,
    #[allow(dead_code)]
    pub(crate) guard: GuardCapability,
    _not_sync: PhantomData<Cell<()>>,
}

#[cfg(test)]
struct PreparedDropObserver(Option<std::sync::Arc<std::sync::Mutex<Vec<&'static str>>>>);

#[cfg(test)]
impl Drop for PreparedDropObserver {
    fn drop(&mut self) {
        if let Some(events) = &self.0 {
            events.lock().unwrap().push("prepared-drop");
        }
    }
}

// SAFETY: preparation retains unique ownership and exposes no shared access.
unsafe impl Send for PreparedRegion {}

impl PreparedRegion {
    /// Reports the requested guard policy for this prepared region.
    ///
    /// Preparation never installs guard bands, so `installed` is always
    /// `false` here: bands are installed when a committed batch maps each
    /// endpoint's own active views. After commit,
    /// [`crate::active::ActiveReader::guard_capability`] and
    /// [`crate::active::ActiveWriter::guard_capability`] report the actual
    /// installation outcome.
    pub const fn guard_capability(&self) -> GuardCapability {
        self.guard
    }

    pub(crate) const fn spec(&self) -> RegionSpec {
        self.spec
    }

    #[allow(dead_code)]
    pub(crate) fn logical_len(&self) -> usize {
        self.request.logical_len()
    }

    #[allow(dead_code)]
    pub(crate) fn mapped_len(&self) -> usize {
        self.request.mapped_len()
    }

    #[cfg(test)]
    pub(crate) fn observe_drop(
        mut self,
        events: std::sync::Arc<std::sync::Mutex<Vec<&'static str>>>,
    ) -> Self {
        self.drop_observer.0 = Some(events);
        self
    }

    #[cfg(target_os = "linux")]
    pub(crate) fn into_linux_transfer_parts(
        self,
    ) -> (memory::NativeShareRequest, RegionSpec, GuardCapability) {
        let Self {
            #[cfg(test)]
                drop_observer: _,
            request,
            spec,
            guard,
            _not_sync: _,
        } = self;
        (request, spec, guard)
    }

    #[cfg(target_os = "macos")]
    pub(crate) fn into_macos_transfer_parts(
        self,
    ) -> (memory::NativeShareRequest, RegionSpec, GuardCapability) {
        let Self {
            #[cfg(test)]
                drop_observer: _,
            request,
            spec,
            guard,
            _not_sync: _,
        } = self;
        (request, spec, guard)
    }

    #[cfg(target_os = "windows")]
    pub(crate) fn into_windows_transfer_parts(
        self,
    ) -> (memory::NativeShareRequest, RegionSpec, GuardCapability) {
        let Self {
            #[cfg(test)]
                drop_observer: _,
            request,
            spec,
            guard,
            _not_sync: _,
        } = self;
        (request, spec, guard)
    }
}

#[cfg(test)]
#[path = "region_test.rs"]
mod tests;