Skip to main content

native_ipc/
memory.rs

1//! Common lifecycle and policy interface for the best native shared-memory backend.
2
3use core::fmt;
4use core::sync::atomic::{Ordering, compiler_fence};
5
6/// Native backend selected for the compilation target.
7#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8pub enum NativePlatform {
9    /// Linux sealed anonymous `memfd` mappings.
10    Linux,
11    /// macOS Mach VM memory-entry mappings.
12    MacOs,
13    /// Windows unnamed paging-file section mappings.
14    Windows,
15}
16
17/// Processor architecture selected for the native backend.
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
19pub enum NativeArchitecture {
20    /// 64-bit Arm (Rust `aarch64`) architecture.
21    Arm64,
22    /// 64-bit x86 (Rust `x86_64`) architecture.
23    Amd64,
24}
25
26/// Kernel mechanism that freezes shared-memory authority before transfer.
27#[derive(Clone, Copy, Debug, Eq, PartialEq)]
28pub enum AuthorityMechanism {
29    /// Linux descriptor seals plus a read-only imported mapping.
30    DescriptorSeals,
31    /// Mach memory-entry maximum protections.
32    MaximumPortRights,
33    /// Windows exact-rights duplicated section handles.
34    ExactHandleRights,
35}
36
37/// Cross-platform capabilities of the selected native backend.
38#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39pub struct NativeMemoryCapabilities {
40    platform: NativePlatform,
41    architecture: NativeArchitecture,
42    authority: AuthorityMechanism,
43}
44
45impl NativeMemoryCapabilities {
46    /// Selected operating-system backend.
47    pub const fn platform(self) -> NativePlatform {
48        self.platform
49    }
50
51    /// Selected processor architecture.
52    pub const fn architecture(self) -> NativeArchitecture {
53        self.architecture
54    }
55
56    /// Native mechanism used to freeze or attenuate authority.
57    pub const fn authority_mechanism(self) -> AuthorityMechanism {
58        self.authority
59    }
60
61    /// Whether a private region can grow by allocating a replacement mapping.
62    pub const fn supports_replacement_growth(self) -> bool {
63        true
64    }
65
66    /// Whether a shared mapping can grow in place.
67    pub const fn supports_in_place_growth(self) -> bool {
68        false
69    }
70
71    /// Whether permission changes are accepted after sharing.
72    pub const fn supports_post_share_permission_changes(self) -> bool {
73        false
74    }
75
76    /// Whether dropping all owners automatically releases the anonymous object.
77    pub const fn releases_on_drop(self) -> bool {
78        true
79    }
80}
81
82/// Reports the native memory behavior selected for this target.
83pub const fn native_memory_capabilities() -> NativeMemoryCapabilities {
84    NativeMemoryCapabilities {
85        platform: native_platform(),
86        architecture: native_architecture(),
87        authority: authority_mechanism(),
88    }
89}
90
91/// Endpoint that will retain the sole writable mapping after transfer.
92#[derive(Clone, Copy, Debug, Eq, PartialEq)]
93pub enum WriterOwner {
94    /// The process creating and transferring the region remains the writer.
95    Creator,
96    /// The authenticated peer becomes the sole writer.
97    Peer,
98}
99
100/// Planned access for one endpoint after the sharing transition.
101#[derive(Clone, Copy, Debug, Eq, PartialEq)]
102pub enum MemoryAccess {
103    /// Mapping may load but cannot store.
104    ReadOnly,
105    /// Mapping is the sole store-capable view.
106    ReadWrite,
107}
108
109/// Exact creator/peer access requested for the future sharing transition.
110#[derive(Clone, Copy, Debug, Eq, PartialEq)]
111pub struct PermissionPlan {
112    writer: WriterOwner,
113}
114
115impl PermissionPlan {
116    /// Creates the only supported permission plan: one writer and one reader.
117    pub const fn new(writer: WriterOwner) -> Self {
118        Self { writer }
119    }
120
121    /// Endpoint selected as sole writer.
122    pub const fn writer(self) -> WriterOwner {
123        self.writer
124    }
125
126    /// Creator's access after authenticated transfer.
127    pub const fn creator_access(self) -> MemoryAccess {
128        match self.writer {
129            WriterOwner::Creator => MemoryAccess::ReadWrite,
130            WriterOwner::Peer => MemoryAccess::ReadOnly,
131        }
132    }
133
134    /// Peer's access after authenticated transfer.
135    pub const fn peer_access(self) -> MemoryAccess {
136        match self.writer {
137            WriterOwner::Creator => MemoryAccess::ReadOnly,
138            WriterOwner::Peer => MemoryAccess::ReadWrite,
139        }
140    }
141
142    /// Shared executable mappings are never permitted.
143    pub const fn executable(self) -> bool {
144        false
145    }
146}
147
148/// Capacity policy while a region is still private and quiescent.
149#[derive(Clone, Copy, Debug, Eq, PartialEq)]
150pub enum GrowthPolicy {
151    /// Mapping size is fixed at allocation.
152    Fixed,
153    /// Growth replaces the private mapping up to an inclusive logical limit.
154    ReplaceBeforeShare {
155        /// Maximum logical byte length.
156        maximum_len: usize,
157    },
158}
159
160/// Cleanup policy applied while the common wrapper still owns the region.
161#[derive(Clone, Copy, Debug, Eq, PartialEq)]
162pub enum CleanupPolicy {
163    /// Release the anonymous native object without an explicit clearing pass.
164    ReleaseOnDrop,
165    /// Clear the complete mapping before releasing it.
166    ClearThenRelease,
167}
168
169/// Mandatory authority-sealing policy for shared regions.
170#[derive(Clone, Copy, Debug, Eq, PartialEq)]
171pub enum SealPolicy {
172    /// Seal or attenuate native authority during the consuming share transition.
173    RequiredOnShare,
174}
175
176/// Immutable configuration for one native shared-memory region.
177#[derive(Clone, Copy, Debug, Eq, PartialEq)]
178pub struct RegionOptions {
179    logical_len: usize,
180    growth: GrowthPolicy,
181    cleanup: CleanupPolicy,
182    permissions: PermissionPlan,
183    seal: SealPolicy,
184}
185
186impl RegionOptions {
187    /// Creates a fixed-size region with clear-on-drop cleanup.
188    pub const fn fixed(logical_len: usize, writer: WriterOwner) -> Self {
189        Self {
190            logical_len,
191            growth: GrowthPolicy::Fixed,
192            cleanup: CleanupPolicy::ClearThenRelease,
193            permissions: PermissionPlan::new(writer),
194            seal: SealPolicy::RequiredOnShare,
195        }
196    }
197
198    /// Creates a private growable region with clear-on-drop cleanup.
199    ///
200    /// Growth allocates a replacement mapping. It is never available after
201    /// the region is consumed for native sharing.
202    pub const fn growable(logical_len: usize, maximum_len: usize, writer: WriterOwner) -> Self {
203        Self {
204            logical_len,
205            growth: GrowthPolicy::ReplaceBeforeShare { maximum_len },
206            cleanup: CleanupPolicy::ClearThenRelease,
207            permissions: PermissionPlan::new(writer),
208            seal: SealPolicy::RequiredOnShare,
209        }
210    }
211
212    /// Overrides cleanup behavior before the native sharing transition.
213    pub const fn with_cleanup(mut self, cleanup: CleanupPolicy) -> Self {
214        self.cleanup = cleanup;
215        self
216    }
217
218    /// Initial logical bytes requested by the caller.
219    pub const fn logical_len(self) -> usize {
220        self.logical_len
221    }
222
223    /// Private growth policy.
224    pub const fn growth(self) -> GrowthPolicy {
225        self.growth
226    }
227
228    /// Pre-transfer cleanup policy.
229    pub const fn cleanup(self) -> CleanupPolicy {
230        self.cleanup
231    }
232
233    /// Required one-writer permission plan.
234    pub const fn permissions(self) -> PermissionPlan {
235        self.permissions
236    }
237
238    /// Mandatory native authority-sealing behavior.
239    pub const fn seal(self) -> SealPolicy {
240        self.seal
241    }
242
243    /// Inclusive logical capacity limit.
244    pub const fn maximum_len(self) -> usize {
245        match self.growth {
246            GrowthPolicy::Fixed => self.logical_len,
247            GrowthPolicy::ReplaceBeforeShare { maximum_len } => maximum_len,
248        }
249    }
250}
251
252/// Current common lifecycle state.
253#[derive(Clone, Copy, Debug, Eq, PartialEq)]
254pub enum RegionState {
255    /// Region is private, writable, unshared, and not yet authority-sealed.
256    Quiescent,
257}
258
259/// Snapshot of a managed region's portable state and policy.
260#[derive(Clone, Copy, Debug, Eq, PartialEq)]
261pub struct RegionStatus {
262    /// Current lifecycle state.
263    pub state: RegionState,
264    /// Logical application-visible length.
265    pub logical_len: usize,
266    /// Native page-rounded mapping length.
267    pub mapped_len: usize,
268    /// Maximum pre-share logical length.
269    pub maximum_len: usize,
270    /// Whether another pre-share growth operation is possible.
271    pub can_grow: bool,
272    /// Future creator/peer permission assignment.
273    pub permissions: PermissionPlan,
274    /// Cleanup behavior while managed by this wrapper.
275    pub cleanup: CleanupPolicy,
276    /// Mandatory seal applied by the consuming native share transition.
277    pub seal: SealPolicy,
278}
279
280/// Portable allocation, policy, or lifecycle failure.
281#[derive(Debug)]
282pub enum MemoryError {
283    /// Shared-memory regions cannot be empty.
284    ZeroLength,
285    /// Configured maximum is smaller than the initial logical length.
286    MaximumBelowInitial {
287        /// Initial requested logical length.
288        initial: usize,
289        /// Configured maximum logical length.
290        maximum: usize,
291    },
292    /// Fixed-size policy rejects growth.
293    FixedSize,
294    /// Shrinking would silently discard user-owned bytes.
295    ShrinkUnsupported {
296        /// Current logical length.
297        current: usize,
298        /// Requested smaller logical length.
299        requested: usize,
300    },
301    /// Requested growth exceeds the configured limit.
302    MaximumExceeded {
303        /// Requested logical length.
304        requested: usize,
305        /// Configured maximum logical length.
306        maximum: usize,
307    },
308    /// Selected native backend rejected allocation.
309    Platform(NativeMemoryPlatformError),
310}
311
312impl fmt::Display for MemoryError {
313    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
314        write!(formatter, "native shared-memory operation failed: {self:?}")
315    }
316}
317
318impl std::error::Error for MemoryError {
319    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
320        match self {
321            Self::Platform(error) => Some(error),
322            _ => None,
323        }
324    }
325}
326
327impl From<NativeMemoryPlatformError> for MemoryError {
328    fn from(value: NativeMemoryPlatformError) -> Self {
329        Self::Platform(value)
330    }
331}
332
333/// Common owner of the best private native shared-memory object on this target.
334///
335/// It exposes logical initialization bytes only through a closure. Consuming
336/// [`NativeRegion::prepare_for_sharing`] hands the region to the existing
337/// authenticated platform transfer typestate, which applies real native seals
338/// and exact permissions. Growth and arbitrary permission changes are no longer
339/// possible after that transition.
340pub struct NativeRegion {
341    inner: Option<PlatformQuiescentRegion>,
342    logical_len: usize,
343    options: RegionOptions,
344}
345
346impl NativeRegion {
347    /// Allocates a zeroed, anonymous, non-executable native region.
348    pub fn allocate(options: RegionOptions) -> Result<Self, MemoryError> {
349        validate_options(options)?;
350        let inner = PlatformQuiescentRegion::new(options.logical_len())?;
351        Ok(Self {
352            inner: Some(inner),
353            logical_len: options.logical_len(),
354            options,
355        })
356    }
357
358    /// Returns the immutable allocation and authority policy.
359    pub const fn options(&self) -> RegionOptions {
360        self.options
361    }
362
363    /// Returns a portable status snapshot.
364    pub fn status(&self) -> RegionStatus {
365        let maximum_len = self.options.maximum_len();
366        RegionStatus {
367            state: RegionState::Quiescent,
368            logical_len: self.logical_len,
369            mapped_len: self.inner().len(),
370            maximum_len,
371            can_grow: matches!(
372                self.options.growth(),
373                GrowthPolicy::ReplaceBeforeShare { .. }
374            ) && self.logical_len < maximum_len,
375            permissions: self.options.permissions(),
376            cleanup: self.options.cleanup(),
377            seal: self.options.seal(),
378        }
379    }
380
381    /// Runs one initialization operation over logical bytes only.
382    ///
383    /// Page-rounded padding remains zero and inaccessible through this common
384    /// method. No capability has escaped while the closure runs.
385    pub fn initialize<R>(&mut self, operation: impl FnOnce(&mut [u8]) -> R) -> R {
386        let logical_len = self.logical_len;
387        operation(&mut self.inner_mut().as_bytes_mut()[..logical_len])
388    }
389
390    /// Clears the complete page-rounded mapping and retains it for reuse.
391    ///
392    /// Every byte, including native page padding, is overwritten through the
393    /// live mapping before this method returns.
394    pub fn clear(&mut self) {
395        clear_mapping(self.inner_mut());
396    }
397
398    /// Grows by replacing the still-private mapping and preserving logical bytes.
399    pub fn grow(&mut self, requested: usize) -> Result<(), MemoryError> {
400        if requested == 0 {
401            return Err(MemoryError::ZeroLength);
402        }
403        if requested < self.logical_len {
404            return Err(MemoryError::ShrinkUnsupported {
405                current: self.logical_len,
406                requested,
407            });
408        }
409        if requested == self.logical_len {
410            return Ok(());
411        }
412        let maximum = match self.options.growth() {
413            GrowthPolicy::Fixed => return Err(MemoryError::FixedSize),
414            GrowthPolicy::ReplaceBeforeShare { maximum_len } => maximum_len,
415        };
416        if requested > maximum {
417            return Err(MemoryError::MaximumExceeded { requested, maximum });
418        }
419
420        let mut replacement = PlatformQuiescentRegion::new(requested)?;
421        replacement.as_bytes_mut()[..self.logical_len]
422            .copy_from_slice(&self.inner().as_bytes()[..self.logical_len]);
423        let mut previous = self
424            .inner
425            .replace(replacement)
426            .expect("managed region always owns its mapping");
427        if self.options.cleanup() == CleanupPolicy::ClearThenRelease {
428            clear_mapping(&mut previous);
429        }
430        self.logical_len = requested;
431        Ok(())
432    }
433
434    /// Explicitly clears according to policy and releases the anonymous object.
435    pub fn close(mut self) {
436        self.release();
437    }
438
439    /// Overwrites the complete mapping, then explicitly releases it.
440    ///
441    /// This ignores [`CleanupPolicy`] and always performs the clearing pass.
442    /// It cannot erase copies previously made by a process, the kernel, or a
443    /// device. The region must still be quiescent and exclusively owned here;
444    /// shared regions are destroyed by their transferred lifecycle owner.
445    pub fn destroy(mut self) {
446        if let Some(inner) = self.inner.as_mut() {
447            clear_mapping(inner);
448        }
449        drop(self.inner.take());
450    }
451
452    /// Freezes common initialization/growth and creates a native share request.
453    ///
454    /// The request retains the permission and mandatory seal policy alongside
455    /// the native region. Its platform parts must be consumed by the matching
456    /// authenticated prepare/transfer operation.
457    pub fn prepare_for_sharing(mut self) -> NativeShareRequest {
458        NativeShareRequest {
459            inner: self.inner.take(),
460            permissions: self.options.permissions(),
461            seal: self.options.seal(),
462            cleanup: self.options.cleanup(),
463        }
464    }
465
466    fn inner(&self) -> &PlatformQuiescentRegion {
467        self.inner
468            .as_ref()
469            .expect("managed region always owns its mapping")
470    }
471
472    fn inner_mut(&mut self) -> &mut PlatformQuiescentRegion {
473        self.inner
474            .as_mut()
475            .expect("managed region always owns its mapping")
476    }
477
478    fn release(&mut self) {
479        if let Some(inner) = self.inner.as_mut()
480            && self.options.cleanup() == CleanupPolicy::ClearThenRelease
481        {
482            clear_mapping(inner);
483        }
484        drop(self.inner.take());
485    }
486}
487
488/// Consuming boundary between common memory management and native transfer.
489///
490/// This type exposes no bytes and supports no growth. It keeps the requested
491/// one-writer permission plan attached until the caller enters the existing
492/// platform-specific authenticated transfer typestate.
493pub struct NativeShareRequest {
494    inner: Option<PlatformQuiescentRegion>,
495    permissions: PermissionPlan,
496    seal: SealPolicy,
497    cleanup: CleanupPolicy,
498}
499
500impl NativeShareRequest {
501    /// Required creator/peer access assignment.
502    pub const fn permissions(&self) -> PermissionPlan {
503        self.permissions
504    }
505
506    /// Mandatory native authority-sealing policy.
507    pub const fn seal_policy(&self) -> SealPolicy {
508        self.seal
509    }
510
511    /// Complete native page-rounded mapping length.
512    pub fn mapped_len(&self) -> usize {
513        self.inner
514            .as_ref()
515            .expect("share request always owns its mapping")
516            .len()
517    }
518
519    /// Enters the platform authenticated transfer layer.
520    ///
521    /// The returned permission plan must select the matching creator-writer or
522    /// peer-writer consuming transition. Native code applies the actual seal or
523    /// maximum-rights attenuation; safe code cannot disable the seal policy.
524    pub fn into_platform_parts(mut self) -> (PlatformQuiescentRegion, PermissionPlan) {
525        let region = self
526            .inner
527            .take()
528            .expect("share request always owns its mapping");
529        (region, self.permissions)
530    }
531
532    /// Clears and releases a prepared request without sharing it.
533    pub fn destroy(mut self) {
534        if let Some(inner) = self.inner.as_mut() {
535            clear_mapping(inner);
536        }
537        drop(self.inner.take());
538    }
539
540    fn release(&mut self) {
541        if let Some(inner) = self.inner.as_mut()
542            && self.cleanup == CleanupPolicy::ClearThenRelease
543        {
544            clear_mapping(inner);
545        }
546        drop(self.inner.take());
547    }
548}
549
550impl Drop for NativeShareRequest {
551    fn drop(&mut self) {
552        self.release();
553    }
554}
555
556fn clear_mapping(region: &mut PlatformQuiescentRegion) {
557    for byte in region.as_bytes_mut() {
558        // SAFETY: the quiescent typestate uniquely owns the complete live
559        // mapping, and each byte pointer is valid for one volatile store.
560        unsafe { core::ptr::write_volatile(byte, 0) };
561    }
562    compiler_fence(Ordering::SeqCst);
563}
564
565impl Drop for NativeRegion {
566    fn drop(&mut self) {
567        self.release();
568    }
569}
570
571fn validate_options(options: RegionOptions) -> Result<(), MemoryError> {
572    if options.logical_len() == 0 {
573        return Err(MemoryError::ZeroLength);
574    }
575    let maximum = options.maximum_len();
576    if maximum < options.logical_len() {
577        return Err(MemoryError::MaximumBelowInitial {
578            initial: options.logical_len(),
579            maximum,
580        });
581    }
582    Ok(())
583}
584
585#[cfg(target_os = "linux")]
586/// Native quiescent region selected on Linux.
587pub type PlatformQuiescentRegion = native_ipc_platform::linux::QuiescentRegion;
588#[cfg(target_os = "linux")]
589/// Native allocation error selected on Linux.
590pub type NativeMemoryPlatformError = native_ipc_platform::linux::LinuxError;
591
592#[cfg(target_os = "macos")]
593/// Native quiescent region selected on macOS.
594pub type PlatformQuiescentRegion = native_ipc_platform::macos::QuiescentRegion;
595#[cfg(target_os = "macos")]
596/// Native allocation error selected on macOS.
597pub type NativeMemoryPlatformError = native_ipc_platform::macos::MachError;
598
599#[cfg(target_os = "windows")]
600/// Native quiescent region selected on Windows.
601pub type PlatformQuiescentRegion = native_ipc_platform::windows::QuiescentRegion;
602#[cfg(target_os = "windows")]
603/// Native allocation error selected on Windows.
604pub type NativeMemoryPlatformError = native_ipc_platform::windows::WindowsError;
605
606#[cfg(target_os = "linux")]
607const fn native_platform() -> NativePlatform {
608    NativePlatform::Linux
609}
610#[cfg(target_os = "macos")]
611const fn native_platform() -> NativePlatform {
612    NativePlatform::MacOs
613}
614#[cfg(target_os = "windows")]
615const fn native_platform() -> NativePlatform {
616    NativePlatform::Windows
617}
618
619#[cfg(target_arch = "aarch64")]
620const fn native_architecture() -> NativeArchitecture {
621    NativeArchitecture::Arm64
622}
623
624#[cfg(target_arch = "x86_64")]
625const fn native_architecture() -> NativeArchitecture {
626    NativeArchitecture::Amd64
627}
628
629#[cfg(target_os = "linux")]
630const fn authority_mechanism() -> AuthorityMechanism {
631    AuthorityMechanism::DescriptorSeals
632}
633#[cfg(target_os = "macos")]
634const fn authority_mechanism() -> AuthorityMechanism {
635    AuthorityMechanism::MaximumPortRights
636}
637#[cfg(target_os = "windows")]
638const fn authority_mechanism() -> AuthorityMechanism {
639    AuthorityMechanism::ExactHandleRights
640}
641
642#[cfg(test)]
643mod tests {
644    use super::*;
645
646    #[test]
647    fn capabilities_report_the_compilation_architecture() {
648        let capabilities = native_memory_capabilities();
649
650        #[cfg(target_arch = "aarch64")]
651        assert_eq!(capabilities.architecture(), NativeArchitecture::Arm64);
652        #[cfg(target_arch = "x86_64")]
653        assert_eq!(capabilities.architecture(), NativeArchitecture::Amd64);
654    }
655
656    #[test]
657    fn growable_region_preserves_bytes_and_reports_policy() {
658        let options = RegionOptions::growable(32, 128, WriterOwner::Creator);
659        let mut region = NativeRegion::allocate(options).unwrap();
660        region.initialize(|bytes| bytes[..4].copy_from_slice(b"NIPC"));
661        region.grow(96).unwrap();
662
663        let status = region.status();
664        assert_eq!(status.logical_len, 96);
665        assert_eq!(status.maximum_len, 128);
666        assert!(status.can_grow);
667        assert_eq!(status.permissions.creator_access(), MemoryAccess::ReadWrite);
668        assert_eq!(status.permissions.peer_access(), MemoryAccess::ReadOnly);
669        region.initialize(|bytes| assert_eq!(&bytes[..4], b"NIPC"));
670    }
671
672    #[test]
673    fn fixed_and_bounded_growth_fail_closed() {
674        let mut fixed =
675            NativeRegion::allocate(RegionOptions::fixed(32, WriterOwner::Peer)).unwrap();
676        assert!(matches!(fixed.grow(64), Err(MemoryError::FixedSize)));
677
678        let mut bounded =
679            NativeRegion::allocate(RegionOptions::growable(32, 64, WriterOwner::Peer)).unwrap();
680        assert!(matches!(
681            bounded.grow(65),
682            Err(MemoryError::MaximumExceeded {
683                requested: 65,
684                maximum: 64
685            })
686        ));
687        assert!(matches!(
688            bounded.grow(16),
689            Err(MemoryError::ShrinkUnsupported {
690                current: 32,
691                requested: 16
692            })
693        ));
694    }
695
696    #[test]
697    fn clear_covers_logical_bytes() {
698        let mut region =
699            NativeRegion::allocate(RegionOptions::fixed(32, WriterOwner::Creator)).unwrap();
700        region.initialize(|bytes| bytes.fill(0xaa));
701        region.clear();
702        region.initialize(|bytes| assert!(bytes.iter().all(|byte| *byte == 0)));
703        region.initialize(|bytes| bytes[..4].copy_from_slice(b"REUS"));
704        region.initialize(|bytes| assert_eq!(&bytes[..4], b"REUS"));
705        region.destroy();
706    }
707
708    #[test]
709    fn share_request_preserves_permissions_and_removes_byte_access() {
710        let region = NativeRegion::allocate(RegionOptions::fixed(32, WriterOwner::Peer)).unwrap();
711        let request = region.prepare_for_sharing();
712        assert_eq!(request.seal_policy(), SealPolicy::RequiredOnShare);
713        assert_eq!(
714            request.permissions().creator_access(),
715            MemoryAccess::ReadOnly
716        );
717        assert_eq!(request.permissions().peer_access(), MemoryAccess::ReadWrite);
718        assert!(request.mapped_len() >= 32);
719        request.destroy();
720    }
721}