use core::fmt;
use core::sync::atomic::{Ordering, compiler_fence};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum NativePlatform {
Linux,
MacOs,
Windows,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum NativeArchitecture {
Arm64,
Amd64,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AuthorityMechanism {
DescriptorSeals,
MaximumPortRights,
ExactHandleRights,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct NativeMemoryCapabilities {
platform: NativePlatform,
architecture: NativeArchitecture,
authority: AuthorityMechanism,
}
impl NativeMemoryCapabilities {
pub const fn platform(self) -> NativePlatform {
self.platform
}
pub const fn architecture(self) -> NativeArchitecture {
self.architecture
}
pub const fn authority_mechanism(self) -> AuthorityMechanism {
self.authority
}
pub const fn supports_replacement_growth(self) -> bool {
true
}
pub const fn supports_in_place_growth(self) -> bool {
false
}
pub const fn supports_post_share_permission_changes(self) -> bool {
false
}
pub const fn releases_on_drop(self) -> bool {
true
}
}
pub const fn native_memory_capabilities() -> NativeMemoryCapabilities {
NativeMemoryCapabilities {
platform: native_platform(),
architecture: native_architecture(),
authority: authority_mechanism(),
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum WriterOwner {
Creator,
Peer,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MemoryAccess {
ReadOnly,
ReadWrite,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PermissionPlan {
writer: WriterOwner,
}
impl PermissionPlan {
pub const fn new(writer: WriterOwner) -> Self {
Self { writer }
}
pub const fn writer(self) -> WriterOwner {
self.writer
}
pub const fn creator_access(self) -> MemoryAccess {
match self.writer {
WriterOwner::Creator => MemoryAccess::ReadWrite,
WriterOwner::Peer => MemoryAccess::ReadOnly,
}
}
pub const fn peer_access(self) -> MemoryAccess {
match self.writer {
WriterOwner::Creator => MemoryAccess::ReadOnly,
WriterOwner::Peer => MemoryAccess::ReadWrite,
}
}
pub const fn executable(self) -> bool {
false
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GrowthPolicy {
Fixed,
ReplaceBeforeShare {
maximum_len: usize,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CleanupPolicy {
ReleaseOnDrop,
ClearThenRelease,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SealPolicy {
RequiredOnShare,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct RegionOptions {
logical_len: usize,
growth: GrowthPolicy,
cleanup: CleanupPolicy,
permissions: PermissionPlan,
seal: SealPolicy,
}
impl RegionOptions {
pub const fn fixed(logical_len: usize, writer: WriterOwner) -> Self {
Self {
logical_len,
growth: GrowthPolicy::Fixed,
cleanup: CleanupPolicy::ClearThenRelease,
permissions: PermissionPlan::new(writer),
seal: SealPolicy::RequiredOnShare,
}
}
pub const fn growable(logical_len: usize, maximum_len: usize, writer: WriterOwner) -> Self {
Self {
logical_len,
growth: GrowthPolicy::ReplaceBeforeShare { maximum_len },
cleanup: CleanupPolicy::ClearThenRelease,
permissions: PermissionPlan::new(writer),
seal: SealPolicy::RequiredOnShare,
}
}
pub const fn with_cleanup(mut self, cleanup: CleanupPolicy) -> Self {
self.cleanup = cleanup;
self
}
pub const fn logical_len(self) -> usize {
self.logical_len
}
pub const fn growth(self) -> GrowthPolicy {
self.growth
}
pub const fn cleanup(self) -> CleanupPolicy {
self.cleanup
}
pub const fn permissions(self) -> PermissionPlan {
self.permissions
}
pub const fn seal(self) -> SealPolicy {
self.seal
}
pub const fn maximum_len(self) -> usize {
match self.growth {
GrowthPolicy::Fixed => self.logical_len,
GrowthPolicy::ReplaceBeforeShare { maximum_len } => maximum_len,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RegionState {
Quiescent,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct RegionStatus {
pub state: RegionState,
pub logical_len: usize,
pub mapped_len: usize,
pub maximum_len: usize,
pub can_grow: bool,
pub permissions: PermissionPlan,
pub cleanup: CleanupPolicy,
pub seal: SealPolicy,
}
#[derive(Debug)]
pub enum MemoryError {
ZeroLength,
MaximumBelowInitial {
initial: usize,
maximum: usize,
},
FixedSize,
ShrinkUnsupported {
current: usize,
requested: usize,
},
MaximumExceeded {
requested: usize,
maximum: usize,
},
Platform(NativeMemoryPlatformError),
}
impl fmt::Display for MemoryError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "native shared-memory operation failed: {self:?}")
}
}
impl std::error::Error for MemoryError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Platform(error) => Some(error),
_ => None,
}
}
}
impl From<NativeMemoryPlatformError> for MemoryError {
fn from(value: NativeMemoryPlatformError) -> Self {
Self::Platform(value)
}
}
pub struct NativeRegion {
inner: Option<PlatformQuiescentRegion>,
logical_len: usize,
options: RegionOptions,
}
impl NativeRegion {
pub fn allocate(options: RegionOptions) -> Result<Self, MemoryError> {
validate_options(options)?;
let inner = PlatformQuiescentRegion::new(options.logical_len())?;
Ok(Self {
inner: Some(inner),
logical_len: options.logical_len(),
options,
})
}
pub const fn options(&self) -> RegionOptions {
self.options
}
pub fn status(&self) -> RegionStatus {
let maximum_len = self.options.maximum_len();
RegionStatus {
state: RegionState::Quiescent,
logical_len: self.logical_len,
mapped_len: self.inner().len(),
maximum_len,
can_grow: matches!(
self.options.growth(),
GrowthPolicy::ReplaceBeforeShare { .. }
) && self.logical_len < maximum_len,
permissions: self.options.permissions(),
cleanup: self.options.cleanup(),
seal: self.options.seal(),
}
}
pub fn initialize<R>(&mut self, operation: impl FnOnce(&mut [u8]) -> R) -> R {
let logical_len = self.logical_len;
operation(&mut self.inner_mut().as_bytes_mut()[..logical_len])
}
pub fn clear(&mut self) {
clear_mapping(self.inner_mut());
}
pub fn grow(&mut self, requested: usize) -> Result<(), MemoryError> {
if requested == 0 {
return Err(MemoryError::ZeroLength);
}
if requested < self.logical_len {
return Err(MemoryError::ShrinkUnsupported {
current: self.logical_len,
requested,
});
}
if requested == self.logical_len {
return Ok(());
}
let maximum = match self.options.growth() {
GrowthPolicy::Fixed => return Err(MemoryError::FixedSize),
GrowthPolicy::ReplaceBeforeShare { maximum_len } => maximum_len,
};
if requested > maximum {
return Err(MemoryError::MaximumExceeded { requested, maximum });
}
let mut replacement = PlatformQuiescentRegion::new(requested)?;
replacement.as_bytes_mut()[..self.logical_len]
.copy_from_slice(&self.inner().as_bytes()[..self.logical_len]);
let mut previous = self
.inner
.replace(replacement)
.expect("managed region always owns its mapping");
if self.options.cleanup() == CleanupPolicy::ClearThenRelease {
clear_mapping(&mut previous);
}
self.logical_len = requested;
Ok(())
}
pub fn close(mut self) {
self.release();
}
pub fn destroy(mut self) {
if let Some(inner) = self.inner.as_mut() {
clear_mapping(inner);
}
drop(self.inner.take());
}
pub fn prepare_for_sharing(mut self) -> NativeShareRequest {
NativeShareRequest {
inner: self.inner.take(),
permissions: self.options.permissions(),
seal: self.options.seal(),
cleanup: self.options.cleanup(),
}
}
fn inner(&self) -> &PlatformQuiescentRegion {
self.inner
.as_ref()
.expect("managed region always owns its mapping")
}
fn inner_mut(&mut self) -> &mut PlatformQuiescentRegion {
self.inner
.as_mut()
.expect("managed region always owns its mapping")
}
fn release(&mut self) {
if let Some(inner) = self.inner.as_mut()
&& self.options.cleanup() == CleanupPolicy::ClearThenRelease
{
clear_mapping(inner);
}
drop(self.inner.take());
}
}
pub struct NativeShareRequest {
inner: Option<PlatformQuiescentRegion>,
permissions: PermissionPlan,
seal: SealPolicy,
cleanup: CleanupPolicy,
}
impl NativeShareRequest {
pub const fn permissions(&self) -> PermissionPlan {
self.permissions
}
pub const fn seal_policy(&self) -> SealPolicy {
self.seal
}
pub fn mapped_len(&self) -> usize {
self.inner
.as_ref()
.expect("share request always owns its mapping")
.len()
}
pub fn into_platform_parts(mut self) -> (PlatformQuiescentRegion, PermissionPlan) {
let region = self
.inner
.take()
.expect("share request always owns its mapping");
(region, self.permissions)
}
pub fn destroy(mut self) {
if let Some(inner) = self.inner.as_mut() {
clear_mapping(inner);
}
drop(self.inner.take());
}
fn release(&mut self) {
if let Some(inner) = self.inner.as_mut()
&& self.cleanup == CleanupPolicy::ClearThenRelease
{
clear_mapping(inner);
}
drop(self.inner.take());
}
}
impl Drop for NativeShareRequest {
fn drop(&mut self) {
self.release();
}
}
fn clear_mapping(region: &mut PlatformQuiescentRegion) {
for byte in region.as_bytes_mut() {
unsafe { core::ptr::write_volatile(byte, 0) };
}
compiler_fence(Ordering::SeqCst);
}
impl Drop for NativeRegion {
fn drop(&mut self) {
self.release();
}
}
fn validate_options(options: RegionOptions) -> Result<(), MemoryError> {
if options.logical_len() == 0 {
return Err(MemoryError::ZeroLength);
}
let maximum = options.maximum_len();
if maximum < options.logical_len() {
return Err(MemoryError::MaximumBelowInitial {
initial: options.logical_len(),
maximum,
});
}
Ok(())
}
#[cfg(target_os = "linux")]
pub type PlatformQuiescentRegion = native_ipc_platform::linux::QuiescentRegion;
#[cfg(target_os = "linux")]
pub type NativeMemoryPlatformError = native_ipc_platform::linux::LinuxError;
#[cfg(target_os = "macos")]
pub type PlatformQuiescentRegion = native_ipc_platform::macos::QuiescentRegion;
#[cfg(target_os = "macos")]
pub type NativeMemoryPlatformError = native_ipc_platform::macos::MachError;
#[cfg(target_os = "windows")]
pub type PlatformQuiescentRegion = native_ipc_platform::windows::QuiescentRegion;
#[cfg(target_os = "windows")]
pub type NativeMemoryPlatformError = native_ipc_platform::windows::WindowsError;
#[cfg(target_os = "linux")]
const fn native_platform() -> NativePlatform {
NativePlatform::Linux
}
#[cfg(target_os = "macos")]
const fn native_platform() -> NativePlatform {
NativePlatform::MacOs
}
#[cfg(target_os = "windows")]
const fn native_platform() -> NativePlatform {
NativePlatform::Windows
}
#[cfg(target_arch = "aarch64")]
const fn native_architecture() -> NativeArchitecture {
NativeArchitecture::Arm64
}
#[cfg(target_arch = "x86_64")]
const fn native_architecture() -> NativeArchitecture {
NativeArchitecture::Amd64
}
#[cfg(target_os = "linux")]
const fn authority_mechanism() -> AuthorityMechanism {
AuthorityMechanism::DescriptorSeals
}
#[cfg(target_os = "macos")]
const fn authority_mechanism() -> AuthorityMechanism {
AuthorityMechanism::MaximumPortRights
}
#[cfg(target_os = "windows")]
const fn authority_mechanism() -> AuthorityMechanism {
AuthorityMechanism::ExactHandleRights
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn capabilities_report_the_compilation_architecture() {
let capabilities = native_memory_capabilities();
#[cfg(target_arch = "aarch64")]
assert_eq!(capabilities.architecture(), NativeArchitecture::Arm64);
#[cfg(target_arch = "x86_64")]
assert_eq!(capabilities.architecture(), NativeArchitecture::Amd64);
}
#[test]
fn growable_region_preserves_bytes_and_reports_policy() {
let options = RegionOptions::growable(32, 128, WriterOwner::Creator);
let mut region = NativeRegion::allocate(options).unwrap();
region.initialize(|bytes| bytes[..4].copy_from_slice(b"NIPC"));
region.grow(96).unwrap();
let status = region.status();
assert_eq!(status.logical_len, 96);
assert_eq!(status.maximum_len, 128);
assert!(status.can_grow);
assert_eq!(status.permissions.creator_access(), MemoryAccess::ReadWrite);
assert_eq!(status.permissions.peer_access(), MemoryAccess::ReadOnly);
region.initialize(|bytes| assert_eq!(&bytes[..4], b"NIPC"));
}
#[test]
fn fixed_and_bounded_growth_fail_closed() {
let mut fixed =
NativeRegion::allocate(RegionOptions::fixed(32, WriterOwner::Peer)).unwrap();
assert!(matches!(fixed.grow(64), Err(MemoryError::FixedSize)));
let mut bounded =
NativeRegion::allocate(RegionOptions::growable(32, 64, WriterOwner::Peer)).unwrap();
assert!(matches!(
bounded.grow(65),
Err(MemoryError::MaximumExceeded {
requested: 65,
maximum: 64
})
));
assert!(matches!(
bounded.grow(16),
Err(MemoryError::ShrinkUnsupported {
current: 32,
requested: 16
})
));
}
#[test]
fn clear_covers_logical_bytes() {
let mut region =
NativeRegion::allocate(RegionOptions::fixed(32, WriterOwner::Creator)).unwrap();
region.initialize(|bytes| bytes.fill(0xaa));
region.clear();
region.initialize(|bytes| assert!(bytes.iter().all(|byte| *byte == 0)));
region.initialize(|bytes| bytes[..4].copy_from_slice(b"REUS"));
region.initialize(|bytes| assert_eq!(&bytes[..4], b"REUS"));
region.destroy();
}
#[test]
fn share_request_preserves_permissions_and_removes_byte_access() {
let region = NativeRegion::allocate(RegionOptions::fixed(32, WriterOwner::Peer)).unwrap();
let request = region.prepare_for_sharing();
assert_eq!(request.seal_policy(), SealPolicy::RequiredOnShare);
assert_eq!(
request.permissions().creator_access(),
MemoryAccess::ReadOnly
);
assert_eq!(request.permissions().peer_access(), MemoryAccess::ReadWrite);
assert!(request.mapped_len() >= 32);
request.destroy();
}
}