blob: 3f9bb236c14533203a99494ddcfcc2890bde936c [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Utilities for safely operating on memory shared between untrusting
//! processes.
//!
//! `shared-buffer` provides support for safely operating on memory buffers
//! which are shared with another process which is untrusted. The Rust memory
//! model assumes that only code running in the current process - and thus
//! either trusted or generated by Rust itself - operates on a given region of
//! memory. As a result, simply treating a region of memory to which another,
//! untrusted process has read or write access as equivalent to normal process
//! memory is unsafe. This crate provides the `SharedBuffer` type, which has
//! methods that allow safe access to such memory.
//!
//! Examples of issues that could arise if shared memory were treated as normal
//! memory include:
//! - Unintentionally leaking sensitive values to another process
//! - Allowing other processes to cause an invalid sequence of memory to be
//! interpreted as a given type
// NOTES(joshlf) on implementation: We need to worry about the following issues:
// - If another process has write access to a given region of memory, then
// arbitrary writes may happen at any time. Thus, it is never safe to access
// this memory through any Rust type other than a raw pointer, or else the
// compiler might allow operations or make optimizations based on the
// assumption that the memory is either owned (in the case of a mutable
// reference) or immutable (in the case of an immutable reference). In either
// of these cases, any such allowance or optimization would be unsound. For
// example, the compiler might decide that, after having written a T to a
// particular memory location, it is safe to read that memory location and
// treat it as a T. This would cause undefined behavior if the other process
// modified that memory location in the meantime. Perhaps more fundamentally,
// both mutable and immutable references guarantee that nobody else is
// modifying this memory other than me (and not even me, in the case of an
// immutable reference). On this basis alone, it is clear that neither
// reference is compatible with foreign write access to the referent.
// - If another process has read access to a given region of memory, then it
// cannot affect the correctness of a Rust program. However, it can do things
// that do not technically violate correctness, but are still undesirable. The
// canonical example is reading memory which contains sensitive information.
// Even if the programmer were to construct a mutable reference to such memory
// and write a value to it which the programmer intended to be shared with the
// other process, the compiler might use the fact that it had exclusive access
// to the memory (so says the mutable reference...) to store any arbitrary
// value in the memory temporarily. So long as it's not observable from the
// Rust program, it preserves the semantics of the program. Of course, it *is*
// observable from the other process, and there are no guarantees on what the
// compiler might decide to store there, including any value currently in your
// memory space, including particularly sensitive values. As a result, while
// read-only access doesn't violate the correctness of a Rust program, it's
// still worth handling carefully.
//
// In order to address both of these issues, our approach is simple: never treat
// the memory as anything other than a raw pointer. Do not construct any
// references, mutable or immutable, even temporarily, and even if they are
// never used. This basically boils down to only accessing the memory using the
// various functions from core::ptr which operate directly on raw pointers.
// NOTE(joshlf):
// - Since you must assume that the other process might be writing to the
// memory, there's no technical requirement to have exclusive access. E.g., we
// could safely implement Clone, have write and write_at take immutable
// references, etc. (see here for a discussion of the soundness of using
// copy_nonoverlapping simultaneously in multiple threads:
// https://users.rust-lang.org/t/copy-nonoverlapping-concurrently/18353).
// However, this would be confusing because it would depart from the Rust
// idiom. Instead, we provide SharedBuffer, which has ownership semantics
// analogous to Vec, and SharedBufferSlice and SharedBufferSliceMut, which
// have reference semantics analogous to immutable and mutable slice
// references. Similarly, write, write_at, and release_writes take mutable
// references.
// Clone and provide slicing methods. There's no point not to.
// - Since all access to these buffers must go through the methods of
// SharedBuffer, correct code may not construct a reference to this memory.
// Thus, the references to dst and src passed to read, read_at, write, and
// write_at cannot overlap with the buffer itself, and so it's safe to use
// ptr::copy_nonoverlapping.
// - Note on volatility and observability: The memory in a SharedBuffer is
// either allocated by this process and then sent to another process, or
// allocated by another process and sent to this process. However, on Fuchsia,
// what's actually shared is a VMO, which is then mapped into the address
// space. While LLVM is almost certainly guaranteed to treat this call as
// opaque, and thus to be unable to prove to itself that the returned memory
// is not shared, it is worth hedging against that reasoning being wrong. If
// LLVM were, for some reason, to decide that mapping a VMO resulted in
// uniquely owned memory, it would be able to reason that writes to that
// memory could never be observed by other threads, and so if the writes were
// not observed by the _current_ thread, they could be elided altogether since
// they could have no effect. In order to hedge against this possibility, and
// to ensure that LLVM definitely cannot take this line of reasoning, we
// volatile write the pointer when we first construct the SharedBuffer. LLVM
// must conclude that it doesn't know who else is using the memory once a
// pointer to it has been written in a volatile manner, and so must assume
// that all future writes must be observable. This single volatile write which
// happens at most once per message (although more likely once when the
// connection is first established) has minimal performance overhead.
// TODO(joshlf):
// - Create a variant for read-only memory
// - Create a variant for write-only memory?
#![no_std]
use core::marker::PhantomData;
use core::ops::{Bound, Range, RangeBounds};
use core::ptr;
use core::sync::atomic::{fence, Ordering};
// A buffer with no ownership or reference semantics. It is the caller's
// responsibility to wrap this type in a type which provides ownership or
// reference semantics, and to only call methods when apporpriate.
struct SharedBufferInner {
// invariant: '(buf as usize) + len' doesn't overflow usize
buf: *mut u8,
len: usize,
}
impl SharedBufferInner {
fn read_at(&self, offset: usize, dst: &mut [u8]) -> usize {
if let Some(to_copy) = overlap(offset, dst.len(), self.len) {
// Since overlap returned Some, we're guaranteed that 'offset +
// to_copy <= self.len'. That in turn means that, so long as the
// invariant holds that '(self.buf as usize) + self.len' doesn't
// overflow usize, then this call to offset_from won't overflow, and
// neither will the call to copy_nonoverlapping.
let base = offset_from(self.buf, offset);
unsafe { ptr::copy_nonoverlapping(base, dst.as_mut_ptr(), to_copy) };
to_copy
} else {
panic!(
"byte offset {} out of range for SharedBuffer of length {}",
offset, self.len
);
}
}
fn write_at(&mut self, offset: usize, src: &[u8]) -> usize {
if let Some(to_copy) = overlap(offset, src.len(), self.len) {
// Since overlap returned Some, we're guaranteed that 'offset +
// to_copy <= self.len'. That in turn means that, so long as the
// invariant holds that '(self.buf as usize) + self.len' doesn't
// overflow usize, then this call to offset_from won't overflow, and
// neither will the call to copy_nonoverlapping.
let base = offset_from(self.buf, offset);
unsafe { ptr::copy_nonoverlapping(src.as_ptr(), base, to_copy) };
to_copy
} else {
panic!(
"byte offset {} out of range for SharedBuffer of length {}",
offset, self.len
);
}
}
fn slice<R: RangeBounds<usize>>(&self, range: R) -> SharedBufferInner {
let range = canonicalize_range_infallible(self.len, range);
SharedBufferInner {
buf: offset_from(self.buf, range.start),
len: range.end - range.start,
}
}
fn split_at(&self, idx: usize) -> (SharedBufferInner, SharedBufferInner) {
assert!(idx <= self.len, "split index out of bounds");
let a = SharedBufferInner {
buf: self.buf,
len: idx,
};
let b = SharedBufferInner {
buf: offset_from(self.buf, idx),
len: self.len - idx,
};
(a, b)
}
}
// Verifies that 'offset' is in range of range_len (that 'offset <= range_len'),
// and returns the amount of overlap between a copy of length 'copy_len'
// starting at 'offset' and a buffer of length 'range_len'. The number it
// returns is guaranteed to be less than or equal to 'range_len'.
//
// overlap is guaranteed to be correct for any three usize values.
fn overlap(offset: usize, copy_len: usize, range_len: usize) -> Option<usize> {
if offset > range_len {
None
} else if offset
.checked_add(copy_len)
.map(|sum| sum <= range_len)
.unwrap_or(false)
{
// if 'offset + copy_len' overflows usize, then 'offset + copy_len >
// range_len', so we unwrap_or(false)
Some(copy_len)
} else {
Some(range_len - offset)
}
}
// Like the offset method on primitive pointers, but for unsigned offsets. Both
// the 'offset' and 'add' methods on primitive pointers have the limitation that
// the offset cannot overflow an isize or else it will cause UB. offset_from
// function has no such restriction.
//
// The caller must guarantee that '(ptr as usize) + offset' doesn't overflow
// usize.
fn offset_from(ptr: *mut u8, offset: usize) -> *mut u8 {
// just in case our logic is wrong, better to catch it at runtime than
// invoke UB
(ptr as usize).checked_add(offset).unwrap() as *mut u8
}
// Return the inclusive equivalent of the bound.
fn canonicalize_lower_bound(bound: Bound<&usize>) -> usize {
match bound {
Bound::Included(x) => *x,
Bound::Excluded(x) => *x + 1,
Bound::Unbounded => 0,
}
}
// Return the exclusive equivalent of the bound, verifying that it is in range
// of len.
fn canonicalize_upper_bound(len: usize, bound: Bound<&usize>) -> Option<usize> {
let bound = match bound {
Bound::Included(x) => *x + 1,
Bound::Excluded(x) => *x,
Bound::Unbounded => len,
};
if bound > len {
return None;
}
Some(bound)
}
// Return the inclusive-exclusive equivalent of the bound, verifying that it is
// in range of len, and panicking if it is not or if the range is nonsensical.
fn canonicalize_range_infallible<R: RangeBounds<usize>>(len: usize, range: R) -> Range<usize> {
let lower = canonicalize_lower_bound(range.start_bound());
let upper =
canonicalize_upper_bound(len, range.end_bound()).expect("slice range out of bounds");
assert!(lower <= upper, "invalid range");
lower..upper
}
/// A shared region of memory.
///
/// A `SharedBuffer` is a view into a region of memory to which another process
/// has access. It provides methods to access this memory in a way that
/// preserves memory safety. From the perspective of the current process, it
/// owns its memory (analogous to a `Vec`).
///
/// Since the buffer is shared by an untrusted process, it is never valid to
/// assume that a given region of the buffer will not change in between method
/// calls. Even if no thread in this process wrote anything to the buffer, the
/// other process might have.
///
/// # Unmapping
///
/// `SharedBuffer`s do nothing when dropped. In order to avoid leaking memory,
/// use the `consume` method to consume the `SharedBuffer` and get back the
/// underlying pointer and length, and unmap the memory manually.
pub struct SharedBuffer {
inner: SharedBufferInner,
}
impl SharedBuffer {
/// Create a new `SharedBuffer` from a raw buffer.
///
/// `new` creates a new `SharedBuffer` from the provided buffer and lenth,
/// taking ownership of the memory.
///
/// # Safety
///
/// Memory in a shared buffer must never be accessed except through the
/// methods of `SharedBuffer`. It must not be treated as normal memory, and
/// pointers to it must not be passed to unsafe code which is designed to
/// operate on normal memory. It must be guaranteed that, for the lifetime
/// of the `SharedBuffer`, the memory region is mapped, readable, and
/// writable.
///
/// If any of these guarantees are violated, it may cause undefined
/// behavior.
#[inline]
pub unsafe fn new(buf: *mut u8, len: usize) -> SharedBuffer {
// Write the pointer and the length using a volatile write so that LLVM
// must assume that the memory has escaped, and that all future writes
// to it are observable. See the NOTE above for more details.
let mut scratch = (ptr::null_mut(), 0);
ptr::write_volatile(&mut scratch, (buf, len));
// Acquire any writes to the buffer that happened in a different thread
// or process already so they are visible without having to call the
// acquire_writes method.
fence(Ordering::Acquire);
SharedBuffer {
inner: SharedBufferInner { buf, len },
}
}
/// Read bytes from the buffer.
///
/// Read up to `dst.len()` bytes from the buffer, returning how many bytes
/// were read. The only thing that can cause fewer bytes to be read than
/// requested is if `dst` is larger than the buffer itself.
///
/// A call to `read` is only guaranteed to happen after an operation in
/// another thread or process if the mechanism used to signal the other
/// process has well-defined memory ordering semantics. Otherwise, the
/// `acquire_writes` method must be called before `read` and after receiving
/// a signal from the other process in order to provide such ordering
/// guarantees. In practice, this means that `acquire_writes` should be the
/// first read operation that happens after receiving a signal from another
/// process that the memory may be read. See the `acquire_writes`
/// documentation for more details.
#[inline]
pub fn read(&self, dst: &mut [u8]) -> usize {
self.inner.read_at(0, dst)
}
/// Read bytes from the buffer at an offset.
///
/// Read up to `dst.len()` bytes starting at `offset` into the buffer,
/// returning how many bytes were read. The only thing that can cause fewer
/// bytes to be read than requested is if there are fewer than `dst.len()`
/// bytes available starting at `offset` within the buffer.
///
/// A call to `read_at` is only guaranteed to happen after an operation in
/// another thread or process if the mechanism used to signal the other
/// process has well-defined memory ordering semantics. Otherwise, the
/// `acquire_writes` method must be called before `read_at` and after
/// receiving a signal from the other process in order to provide such
/// ordering guarantees. In practice, this means that `acquire_writes`
/// should be the first read operation that happens after receiving a signal
/// from another process that the memory may be read. See the
/// `acquire_writes` documentation for more details.
///
/// # Panics
///
/// `read_at` panics if `offset` is greater than the length of the buffer.
#[inline]
pub fn read_at(&self, offset: usize, dst: &mut [u8]) -> usize {
self.inner.read_at(offset, dst)
}
/// Write bytes to the buffer.
///
/// Write up to `src.len()` bytes into the buffer, returning how many bytes
/// were written. The only thing that can cause fewer bytes to be written
/// than requested is if `src` is larger than the buffer itself.
///
/// A call to `write` is only guaranteed to happen before an operation in
/// another thread or process if the mechanism used to signal the other
/// process has well-defined memory ordering semantics. Otherwise, the
/// `release_writes` method must be called after `write` and before
/// signalling the other process in order to provide such ordering
/// guarantees. In practice, this means that `release_writes` should be the
/// last write operation that happens before signalling another process that
/// the memory may be read. See the `release_writes` documentation for more
/// details.
#[inline]
pub fn write(&mut self, src: &[u8]) -> usize {
self.inner.write_at(0, src)
}
/// Write bytes to the buffer at an offset.
///
/// Write up to `src.len()` bytes starting at `offset` into the buffer,
/// returning how many bytes were written. The only thing that can cause
/// fewer bytes to be written than requested is if there are fewer than
/// `src.len()` bytes available starting at `offset` within the buffer.
///
/// A call to `write_at` is only guaranteed to happen before an operation in
/// another thread or process if the mechanism used to signal the other
/// process has well-defined memory ordering semantics. Otherwise, the
/// `release_writes` method must be called after `write_at` and before
/// signalling the other process in order to provide such ordering
/// guarantees. In practice, this means that `release_writes` should be the
/// last write operation that happens before signalling another process that
/// the memory may be read. See the `release_writes` documentation for more
/// details.
///
/// # Panics
///
/// `write_at` panics if `offset` is greater than the length of the buffer.
#[inline]
pub fn write_at(&mut self, offset: usize, src: &[u8]) -> usize {
self.inner.write_at(offset, src)
}
/// Acquire all writes performed by the other process.
///
/// On some systems (such as Fuchsia, currently), the communication
/// mechanism used for signalling a process that memory is readable does not
/// have well-defined synchronization semantics. On those systems, this
/// method MUST be called after receiving such a signal, or else writes
/// performed before that signal are not guaranteed to be observed by this
/// process.
///
/// `acquire_writes` acquires any writes performed on this buffer or any
/// slice within the buffer.
///
/// # Note on Fuchsia
///
/// Zircon, the Fuchsia kernel, will likely eventually have well-defined
/// semantics around the synchronization behavior of various syscalls. Once
/// that happens, calling this method in Fuchsia programs may become
/// optional. This work is tracked in [ZX-2239].
///
/// [ZX-2239]: #
// TODO(joshlf): Replace with link once issues are public.
#[inline]
pub fn acquire_writes(&self) {
fence(Ordering::Acquire);
}
/// Release all writes performed so far.
///
/// On some systems (such as Fuchsia, currently), the communication
/// mechanism used for signalling the other process that memory is readable
/// does not have well-defined synchronization semantics. On those systems,
/// this method MUST be called before such signalling, or else writes
/// performed before that signal are not guaranteed to be observed by the
/// other process.
///
/// `release_writes` releases any writes performed on this buffer or any
/// slice within the buffer.
///
/// # Note on Fuchsia
///
/// Zircon, the Fuchsia kernel, will likely eventually have well-defined
/// semantics around the synchronization behavior of various syscalls. Once
/// that happens, calling this method in Fuchsia programs may become
/// optional. This work is tracked in [ZX-2239].
///
/// [ZX-2239]: #
// TODO(joshlf): Replace with link once issues are public.
#[inline]
pub fn release_writes(&mut self) {
fence(Ordering::Release);
}
/// The number of bytes in this `SharedBuffer`.
#[inline]
pub fn len(&self) -> usize {
self.inner.len
}
/// Create a slice of the original `SharedBuffer`.
///
/// Just like the slicing operation on array and slice references, `slice`
/// constructs a `SharedBufferSlice` which points to the same memory as the
/// original `SharedBuffer`, but starting and index `from` (inclusive) and
/// ending at index `to` (exclusive).
///
/// # Panics
///
/// `slice` panics if `range` is out of bounds of `self` or if `range` is
/// nonsensical (its lower bound is larger than its upper bound).
#[inline]
pub fn slice<'a, R: RangeBounds<usize>>(&'a self, range: R) -> SharedBufferSlice<'a> {
SharedBufferSlice {
inner: self.inner.slice(range),
_marker: PhantomData,
}
}
/// Create a mutable slice of the original `SharedBuffer`.
///
/// Just like the mutable slicing operation on array and slice references,
/// `slice_mut` constructs a `SharedBufferSliceMut` which points to the same
/// memory as the original `SharedBuffer`, but starting and index `from`
/// (inclusive) and ending at index `to` (exclusive).
///
/// # Panics
///
/// `slice_mut` panics if `range` is out of bounds of `self` or if `range`
/// is nonsensical (its lower bound is larger than its upper bound).
#[inline]
pub fn slice_mut<'a, R: RangeBounds<usize>>(
&'a mut self, range: R,
) -> SharedBufferSliceMut<'a> {
SharedBufferSliceMut {
inner: self.inner.slice(range),
_marker: PhantomData,
}
}
/// Create two non-overlapping slices of the original `SharedBuffer`.
///
/// Just like the `split_at` method on array and slice references,
/// `split_at` constructs one `SharedBufferSlice` which represents bytes
/// `[0, idx)`, and one which represents bytes `[idx, len)`, where `len` is
/// the length of the buffer.
///
/// # Panics
///
/// `split_at` panics if `idx > self.len()`.
#[inline]
pub fn split_at<'a>(&'a self, idx: usize) -> (SharedBufferSlice<'a>, SharedBufferSlice<'a>) {
let (a, b) = self.inner.split_at(idx);
let a = SharedBufferSlice {
inner: a,
_marker: PhantomData,
};
let b = SharedBufferSlice {
inner: b,
_marker: PhantomData,
};
(a, b)
}
/// Create two non-overlapping mutable slices of the original `SharedBuffer`.
///
/// Just like the `split_at_mut` method on array and slice references,
/// `split_at_miut` constructs one `SharedBufferSliceMut` which represents
/// bytes `[0, idx)`, and one which represents bytes `[idx, len)`, where
/// `len` is the length of the buffer.
///
/// # Panics
///
/// `split_at_mut` panics if `idx > self.len()`.
#[inline]
pub fn split_at_mut<'a>(
&'a mut self, idx: usize,
) -> (SharedBufferSliceMut<'a>, SharedBufferSliceMut<'a>) {
let (a, b) = self.inner.split_at(idx);
let a = SharedBufferSliceMut {
inner: a,
_marker: PhantomData,
};
let b = SharedBufferSliceMut {
inner: b,
_marker: PhantomData,
};
(a, b)
}
/// Get the buffer pointer and length so that the memory can be freed.
///
/// This method is an alternative to calling `consume` if relinquishing
/// ownership of the object is infeasible (for example, when the object is a
/// struct field and thus can't be moved out of the struct). Since it allows
/// the object to continue existing, it must be used with care (see the
/// "Safety" section below).
///
/// # Safety
///
/// The returned pointer must *only* be used to free the memory. Since the
/// memory is shared by another process, using it as a normal raw pointer to
/// normal memory owned by this process is unsound.
///
/// If the pointer is used for this purpose, then the caller must ensure
/// that no methods will be called on the object after the call to
/// `as_ptr_len`. The only scenario in which the object may be used again is
/// if the caller does nothing at all with the return value of this method
/// (although that would be kind of pointless...).
pub fn as_ptr_len(&mut self) -> (*mut u8, usize) {
(self.inner.buf, self.inner.len)
}
/// Consume the `SharedBuffer`, returning the underlying buffer pointer and
/// length.
///
/// Since `SharedBuffer`s do nothing on drop, the only way to ensure that
/// resources are not leaked is to `consume` a `SharedBuffer` and then unmap
/// the memory manually.
#[inline]
pub fn consume(self) -> (*mut u8, usize) {
(self.inner.buf, self.inner.len)
}
}
impl Drop for SharedBuffer {
fn drop(&mut self) {
// Release any writes performed after the last call to
// self.release_writes().
fence(Ordering::Release);
}
}
/// An immutable slice into a `SharedBuffer`.
///
/// A `SharedBufferSlice` is created with `SharedBuffer::slice`,
/// `SharedBufferSlice::slice`, or `SharedBufferSliceMut::slice`.
pub struct SharedBufferSlice<'a> {
inner: SharedBufferInner,
_marker: PhantomData<&'a ()>,
}
impl<'a> SharedBufferSlice<'a> {
/// Read bytes from the buffer.
///
/// Read up to `dst.len()` bytes from the buffer, returning how many bytes
/// were read. The only thing that can cause fewer bytes to be read than
/// requested is if `dst` is larger than the buffer itself.
///
/// A call to `read` is only guaranteed to happen after an operation in
/// another thread or process if the mechanism used to signal the other
/// process has well-defined memory ordering semantics. Otherwise, the
/// `acquire_writes` method must be called before `read` and after receiving
/// a signal from the other process in order to provide such ordering
/// guarantees. In practice, this means that `acquire_writes` should be the
/// first read operation that happens after receiving a signal from another
/// process that the memory may be read. See the `acquire_writes`
/// documentation for more details.
#[inline]
pub fn read(&self, dst: &mut [u8]) -> usize {
self.inner.read_at(0, dst)
}
/// Read bytes from the buffer at an offset.
///
/// Read up to `dst.len()` bytes starting at `offset` into the buffer,
/// returning how many bytes were read. The only thing that can cause fewer
/// bytes to be read than requested is if there are fewer than `dst.len()`
/// bytes available starting at `offset` within the buffer.
///
/// A call to `read_at` is only guaranteed to happen after an operation in
/// another thread or process if the mechanism used to signal the other
/// process has well-defined memory ordering semantics. Otherwise, the
/// `acquire_writes` method must be called before `read_at` and after
/// receiving a signal from the other process in order to provide such
/// ordering guarantees. In practice, this means that `acquire_writes`
/// should be the first read operation that happens after receiving a signal
/// from another process that the memory may be read. See the
/// `acquire_writes` documentation for more details.
///
/// # Panics
///
/// `read_at` panics if `offset` is greater than the length of the buffer.
#[inline]
pub fn read_at(&self, offset: usize, dst: &mut [u8]) -> usize {
self.inner.read_at(offset, dst)
}
/// Acquire all writes performed by the other process.
///
/// On some systems (such as Fuchsia, currently), the communication
/// mechanism used for signalling a process that memory is readable does not
/// have well-defined synchronization semantics. On those systems, this
/// method MUST be called after receiving such a signal, or else writes
/// performed before that signal are not guaranteed to be observed by this
/// process.
///
/// `acquire_writes` acquires any writes performed on this buffer or any
/// slice within the buffer.
///
/// # Note on Fuchsia
///
/// Zircon, the Fuchsia kernel, will likely eventually have well-defined
/// semantics around the synchronization behavior of various syscalls. Once
/// that happens, calling this method in Fuchsia programs may become
/// optional. This work is tracked in [ZX-2239].
///
/// [ZX-2239]: #
// TODO(joshlf): Replace with link once issues are public.
#[inline]
pub fn acquire_writes(&self) {
fence(Ordering::Acquire);
}
/// Create a sub-slice of this `SharedBufferSlice`.
///
/// Just like the slicing operation on array and slice references, `slice`
/// constructs a new `SharedBufferSlice` which points to the same memory as
/// the original, but starting and index `from` (inclusive) and ending at
/// index `to` (exclusive).
///
/// # Panics
///
/// `slice` panics if `range` is out of bounds of `self` or if `range` is
/// nonsensical (its lower bound is larger than its upper bound).
#[inline]
pub fn slice<R: RangeBounds<usize>>(&self, range: R) -> SharedBufferSlice<'a> {
SharedBufferSlice {
inner: self.inner.slice(range),
_marker: PhantomData,
}
}
/// Split this `SharedBufferSlice` in two.
///
/// Just like the `split_at` method on array and slice references,
/// `split_at` constructs one `SharedBufferSlice` which represents bytes
/// `[0, idx)`, and one which represents bytes `[idx, len)`, where `len` is
/// the length of the buffer slice.
///
/// # Panics
///
/// `split_at` panics if `idx > self.len()`.
#[inline]
pub fn split_at(&self, idx: usize) -> (SharedBufferSlice<'a>, SharedBufferSlice<'a>) {
let (a, b) = self.inner.split_at(idx);
let a = SharedBufferSlice {
inner: a,
_marker: PhantomData,
};
let b = SharedBufferSlice {
inner: b,
_marker: PhantomData,
};
(a, b)
}
/// The number of bytes in this `SharedBufferSlice`.
#[inline]
pub fn len(&self) -> usize {
self.inner.len
}
}
/// A mutable slice into a `SharedBuffer`.
///
/// A `SharedBufferSliceMut` is created with `SharedBuffer::slice_mut` or
/// `SharedBufferSliceMut::slice_mut`.
pub struct SharedBufferSliceMut<'a> {
inner: SharedBufferInner,
_marker: PhantomData<&'a ()>,
}
impl<'a> SharedBufferSliceMut<'a> {
/// Read bytes from the buffer.
///
/// Read up to `dst.len()` bytes from the buffer, returning how many bytes
/// were read. The only thing that can cause fewer bytes to be read than
/// requested is if `dst` is larger than the buffer itself.
///
/// A call to `read` is only guaranteed to happen after an operation in
/// another thread or process if the mechanism used to signal the other
/// process has well-defined memory ordering semantics. Otherwise, the
/// `acquire_writes` method must be called before `read` and after receiving
/// a signal from the other process in order to provide such ordering
/// guarantees. In practice, this means that `acquire_writes` should be the
/// first read operation that happens after receiving a signal from another
/// process that the memory may be read. See the `acquire_writes`
/// documentation for more details.
#[inline]
pub fn read(&self, dst: &mut [u8]) -> usize {
self.inner.read_at(0, dst)
}
/// Read bytes from the buffer at an offset.
///
/// Read up to `dst.len()` bytes starting at `offset` into the buffer,
/// returning how many bytes were read. The only thing that can cause fewer
/// bytes to be read than requested is if there are fewer than `dst.len()`
/// bytes available starting at `offset` within the buffer.
///
/// A call to `read_at` is only guaranteed to happen after an operation in
/// another thread or process if the mechanism used to signal the other
/// process has well-defined memory ordering semantics. Otherwise, the
/// `acquire_writes` method must be called before `read_at` and after
/// receiving a signal from the other process in order to provide such
/// ordering guarantees. In practice, this means that `acquire_writes`
/// should be the first read operation that happens after receiving a signal
/// from another process that the memory may be read. See the
/// `acquire_writes` documentation for more details.
///
/// # Panics
///
/// `read_at` panics if `offset` is greater than the length of the buffer.
#[inline]
pub fn read_at(&self, offset: usize, dst: &mut [u8]) -> usize {
self.inner.read_at(offset, dst)
}
/// Write bytes to the buffer.
///
/// Write up to `src.len()` bytes into the buffer, returning how many bytes
/// were written. The only thing that can cause fewer bytes to be written
/// than requested is if `src` is larger than the buffer itself.
///
/// A call to `write` is only guaranteed to happen before an operation in
/// another thread or process if the mechanism used to signal the other
/// process has well-defined memory ordering semantics. Otherwise, the
/// `release_writes` method must be called after `write` and before
/// signalling the other process in order to provide such ordering
/// guarantees. In practice, this means that `release_writes` should be the
/// last write operation that happens before signalling another process that
/// the memory may be read. See the `release_writes` documentation for more
/// details.
#[inline]
pub fn write(&mut self, src: &[u8]) -> usize {
self.inner.write_at(0, src)
}
/// Write bytes to the buffer at an offset.
///
/// Write up to `src.len()` bytes starting at `offset` into the buffer,
/// returning how many bytes were written. The only thing that can cause
/// fewer bytes to be written than requested is if there are fewer than
/// `src.len()` bytes available starting at `offset` within the buffer.
///
/// A call to `write_at` is only guaranteed to happen before an operation in
/// another thread or process if the mechanism used to signal the other
/// process has well-defined memory ordering semantics. Otherwise, the
/// `release_writes` method must be called after `write_at` and before
/// signalling the other process in order to provide such ordering
/// guarantees. In practice, this means that `release_writes` should be the
/// last write operation that happens before signalling another process that
/// the memory may be read. See the `release_writes` documentation for more
/// details.
///
/// # Panics
///
/// `write_at` panics if `offset` is greater than the length of the buffer.
#[inline]
pub fn write_at(&mut self, offset: usize, src: &[u8]) -> usize {
self.inner.write_at(offset, src)
}
/// Acquire all writes performed by the other process.
///
/// On some systems (such as Fuchsia, currently), the communication
/// mechanism used for signalling a process that memory is readable does not
/// have well-defined synchronization semantics. On those systems, this
/// method MUST be called after receiving such a signal, or else writes
/// performed before that signal are not guaranteed to be observed by this
/// process.
///
/// `acquire_writes` acquires any writes performed on this buffer or any
/// slice within the buffer.
///
/// # Note on Fuchsia
///
/// Zircon, the Fuchsia kernel, will likely eventually have well-defined
/// semantics around the synchronization behavior of various syscalls. Once
/// that happens, calling this method in Fuchsia programs may become
/// optional. This work is tracked in [ZX-2239].
///
/// [ZX-2239]: #
// TODO(joshlf): Replace with link once issues are public.
#[inline]
pub fn acquire_writes(&self) {
fence(Ordering::Acquire);
}
/// Atomically release all writes performed so far.
///
/// On some systems (such as Fuchsia, currently), the communication
/// mechanism used for signalling the other process that memory is readable
/// does not have well-defined synchronization semantics. On those systems,
/// this method MUST be called before such signalling, or else writes
/// performed before that signal are not guaranteed to be observed by the
/// other process.
///
/// `release_writes` releases any writes performed on this slice or any
/// sub-slice of this slice.
///
/// # Note on Fuchsia
///
/// Zircon, the Fuchsia kernel, will likely eventually have well-defined
/// semantics around the synchronization behavior of various syscalls. Once
/// that happens, calling this method in Fuchsia programs may become
/// optional. This work is tracked in [ZX-2239].
///
/// [ZX-2239]: #
// TODO(joshlf): Replace with link once issues are public.
#[inline]
pub fn release_writes(&mut self) {
fence(Ordering::Release);
}
/// Create a sub-slice of this `SharedBufferSliceMut`.
///
/// Just like the slicing operation on array and slice references, `slice`
/// constructs a new `SharedBufferSlice` which points to the same memory as
/// the original, but starting and index `from` (inclusive) and ending at
/// index `to` (exclusive).
///
/// # Panics
///
/// `slice` panics if `range` is out of bounds of `self` or if `range` is
/// nonsensical (its lower bound is larger than its upper bound).
#[inline]
pub fn slice<R: RangeBounds<usize>>(&self, range: R) -> SharedBufferSlice<'a> {
SharedBufferSlice {
inner: self.inner.slice(range),
_marker: PhantomData,
}
}
/// Create a mutable slice of the original `SharedBufferSliceMut`.
///
/// Just like the mutable slicing operation on array and slice references,
/// `slice_mut` constructs a new `SharedBufferSliceMut` which points to the
/// same memory as the original, but starting and index `from` (inclusive)
/// and ending at index `to` (exclusive).
///
/// # Panics
///
/// `slice_mut` panics if `range` is out of bounds of `self` or if `range`
/// is nonsensical (its lower bound is larger than its upper bound).
#[inline]
pub fn slice_mut<R: RangeBounds<usize>>(&mut self, range: R) -> SharedBufferSliceMut<'a> {
SharedBufferSliceMut {
inner: self.inner.slice(range),
_marker: PhantomData,
}
}
/// Split this `SharedBufferSliceMut` into two immutable slices.
///
/// Just like the `split_at` method on array and slice references,
/// `split_at` constructs one `SharedBufferSlice` which represents bytes
/// `[0, idx)`, and one which represents bytes `[idx, len)`, where `len` is
/// the length of the buffer slice.
///
/// # Panics
///
/// `split_at` panics if `idx > self.len()`.
#[inline]
pub fn split_at(&self, idx: usize) -> (SharedBufferSlice<'a>, SharedBufferSlice<'a>) {
let (a, b) = self.inner.split_at(idx);
let a = SharedBufferSlice {
inner: a,
_marker: PhantomData,
};
let b = SharedBufferSlice {
inner: b,
_marker: PhantomData,
};
(a, b)
}
/// Split this `SharedBufferSliceMut` in two.
///
/// Just like the `split_at_mut` method on array and slice references,
/// `split_at` constructs one `SharedBufferSliceMut` which represents bytes
/// `[0, idx)`, and one which represents bytes `[idx, len)`, where `len` is
/// the length of the buffer slice.
///
/// # Panics
///
/// `split_at_mut` panics if `idx > self.len()`.
#[inline]
pub fn split_at_mut(
&mut self, idx: usize,
) -> (SharedBufferSliceMut<'a>, SharedBufferSliceMut<'a>) {
let (a, b) = self.inner.split_at(idx);
let a = SharedBufferSliceMut {
inner: a,
_marker: PhantomData,
};
let b = SharedBufferSliceMut {
inner: b,
_marker: PhantomData,
};
(a, b)
}
/// The number of bytes in this `SharedBufferSlice`.
#[inline]
pub fn len(&self) -> usize {
self.inner.len
}
}
// Send and Sync implementations. Send and Sync are definitely safe since
// SharedBufferXXX are all written under the assumption that a remote process is
// concurrently modifying the memory. However, we aim to provide a Rust-like API
// with lifetimes and an immutable/mutable distinction, so the real question is
// whether Send and Sync make sense by analogy to normal Rust types. Insofar as
// SharedBuffer is analogous to [u8], SharedBufferSlice is analogous to &[u8],
// and SharedBufferSliceMut is analogous to &mut [u8], the answer is yes - all
// of those types implement both Send and Sync.
unsafe impl Send for SharedBuffer {}
unsafe impl Sync for SharedBuffer {}
unsafe impl<'a> Send for SharedBufferSlice<'a> {}
unsafe impl<'a> Sync for SharedBufferSlice<'a> {}
unsafe impl<'a> Send for SharedBufferSliceMut<'a> {}
unsafe impl<'a> Sync for SharedBufferSliceMut<'a> {}
#[cfg(test)]
mod tests {
use core::mem;
use core::ptr;
use super::{overlap, SharedBuffer};
// use the referent as the backing memory for a SharedBuffer
unsafe fn buf_from_ref<T>(x: &mut T) -> SharedBuffer {
let size = mem::size_of::<T>();
SharedBuffer::new(x as *mut _ as *mut u8, size)
}
#[test]
fn test_buf() {
// initialize some memory and turn it into a SharedBuffer
const ONE: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
let mut buf_memory = ONE;
let mut buf = unsafe { buf_from_ref(&mut buf_memory) };
// we read the same initial contents back
let mut bytes = [0u8; 8];
assert_eq!(buf.read(&mut bytes[..]), 8);
assert_eq!(bytes, ONE);
// when we write new contents, we read those back
const TWO: [u8; 8] = [7, 6, 5, 4, 3, 2, 1, 0];
assert_eq!(buf.write(&TWO[..]), 8);
assert_eq!(buf.read(&mut bytes[..]), 8);
assert_eq!(bytes, TWO);
// even with a bigger buffer, we still only read 8 bytes
let mut bytes = [0u8; 16];
assert_eq!(buf.read(&mut bytes[..]), 8);
// starting at offset 4, there are only 4 bytes left, so we only read 4
// bytes
assert_eq!(buf.read_at(4, &mut bytes[..]), 4);
}
#[test]
fn test_slice() {
// various slices give us the lengths we expect
let buf = unsafe { SharedBuffer::new(ptr::null_mut(), 10) };
let tmp = buf.slice(..);
assert_eq!(tmp.len(), 10);
let tmp = buf.slice(..10);
assert_eq!(tmp.len(), 10);
let tmp = buf.slice(5..10);
assert_eq!(tmp.len(), 5);
let tmp = buf.slice(0..0);
assert_eq!(tmp.len(), 0);
let tmp = buf.slice(10..10);
assert_eq!(tmp.len(), 0);
// initialize some memory and turn it into a SharedBuffer
const INIT: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
let mut buf_memory = INIT;
let buf = unsafe { buf_from_ref(&mut buf_memory) };
// we read the same initial contents back
let mut bytes = [0u8; 8];
assert_eq!(buf.read_at(0, &mut bytes[..]), 8);
assert_eq!(bytes, INIT);
// create a slice to the second half of the SharedBuffer
let buf2 = buf.slice(4..8);
// now we read back only the second half of the original SharedBuffer
bytes = [0; 8];
assert_eq!(buf2.read(&mut bytes[..]), 4);
assert_eq!(bytes, [4, 5, 6, 7, 0, 0, 0, 0]);
}
#[test]
fn test_split() {
// various splits give us the lengths we expect
let buf = unsafe { SharedBuffer::new(ptr::null_mut(), 10) };
let (tmp1, tmp2) = buf.split_at(10);
assert_eq!(tmp1.len(), 10);
assert_eq!(tmp2.len(), 0);
let (tmp1, tmp2) = buf.split_at(5);
assert_eq!(tmp1.len(), 5);
assert_eq!(tmp2.len(), 5);
let (tmp1, tmp2) = buf.split_at(0);
assert_eq!(tmp1.len(), 0);
assert_eq!(tmp2.len(), 10);
// initialize some memory and turn it into a SharedBuffer
const INIT: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
let mut buf_memory = INIT;
let mut buf = unsafe { buf_from_ref(&mut buf_memory) };
// we read the same initial contents back
let mut bytes = [0u8; 8];
assert_eq!(buf.read_at(0, &mut bytes[..]), 8);
assert_eq!(bytes, INIT);
// split in two equal-sized halves
let (mut buf1, mut buf2) = buf.split_at_mut(4);
// now we read back the halves separately
bytes = [0; 8];
assert_eq!(buf1.read(&mut bytes[..4]), 4);
assert_eq!(buf2.read(&mut bytes[4..]), 4);
assert_eq!(bytes, [0, 1, 2, 3, 4, 5, 6, 7]);
// use the mutable slices to write to the buffer
assert_eq!(buf1.write(&[7, 6, 5, 4]), 4);
assert_eq!(buf2.write(&[3, 2, 1, 0]), 4);
// split again into equal-sized quarters
let ((buf1, buf2), (buf3, buf4)) = (buf1.split_at(2), buf2.split_at(2));
// now we read back the quarters separately
bytes = [0; 8];
assert_eq!(buf1.read(&mut bytes[..2]), 2);
assert_eq!(buf2.read(&mut bytes[2..4]), 2);
assert_eq!(buf3.read(&mut bytes[4..6]), 2);
assert_eq!(buf4.read(&mut bytes[6..]), 2);
assert_eq!(bytes, [7, 6, 5, 4, 3, 2, 1, 0]);
}
#[test]
fn test_overlap() {
// overlap(offset, copy_len, range_len)
// first branch: offset > range_len
assert_eq!(overlap(10, 4, 8), None);
// middle branch: offset + copy_len <= range_len
assert_eq!(overlap(0, 4, 8), Some(4));
assert_eq!(overlap(4, 4, 8), Some(4));
// middle branch: 'offset + copy_len' overflows usize
assert_eq!(overlap(4, ::core::usize::MAX, 8), Some(4));
// last branch: else
assert_eq!(overlap(6, 4, 8), Some(2));
assert_eq!(overlap(8, 4, 8), Some(0));
}
#[test]
#[should_panic]
fn test_panic_read_at() {
let buf = unsafe { SharedBuffer::new(ptr::null_mut(), 10) };
// "byte offset 11 out of range for SharedBuffer of length 10"
buf.read_at(11, &mut [][..]);
}
#[test]
#[should_panic]
fn test_panic_write_at() {
let mut buf = unsafe { SharedBuffer::new(ptr::null_mut(), 10) };
// "byte offset 11 out of range for SharedBuffer of length 10"
buf.write_at(11, &[][..]);
}
#[test]
#[should_panic]
fn test_panic_slice_1() {
let buf = unsafe { SharedBuffer::new(ptr::null_mut(), 10) };
// "byte index 11 out of range for SharedBuffer of length 10"
buf.slice(0..11);
}
#[test]
#[should_panic]
fn test_panic_slice_2() {
let buf = unsafe { SharedBuffer::new(ptr::null_mut(), 10) };
// "slice starts at byte 6 but ends at byte 5"
buf.slice(6..5);
}
}