1use 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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
20pub enum AccessError {
21 OutOfBounds,
23 InvalidRange,
25 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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39pub struct PrefaultResult {
40 pub requested_bytes: usize,
42 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
62pub(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 fn guard_installed(&self) -> bool {
83 false
84 }
85 #[allow(dead_code)]
86 fn liveness_state(&self) -> Option<LivenessState> {
87 None
88 }
89}
90
91pub(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 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#[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 pub const fn len(&self) -> usize {
216 self.logical_len
217 }
218
219 pub const fn is_empty(&self) -> bool {
221 self.logical_len == 0
222 }
223
224 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 core::ptr::NonNull::new(self.owner.as_ptr().cast_mut())
242 .expect("active mapping base is never null")
243 }
244
245 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 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 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 #[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
293pub 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 pub const fn len(&self) -> usize {
366 self.logical_len
367 }
368
369 pub const fn is_empty(&self) -> bool {
371 self.logical_len == 0
372 }
373
374 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 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 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 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 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 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 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 #[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 #[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 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;