Source code
Revision control
Copy as Markdown
Other Tools
// Copyright 2019 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Fast binary serialization and deserialization for types with a known maximum size.
//!
//! ## Binary Encoding Scheme
//!
//! ## Usage
//!
//! ## Comparison to bincode
#[cfg(feature = "derive")]
pub use peek_poke_derive::*;
use core::{marker::PhantomData, mem::size_of, slice};
use crate::{slice_ext::*, vec_ext::*};
mod slice_ext;
mod vec_ext;
union MaybeUninitShim<T: Copy> {
uninit: (),
init: T,
}
/// Peek helper for constructing a `T` by `Copy`ing into an uninitialized stack
/// allocation.
///
/// # Safety
///
/// * `bytes` must denote a valid pointer to a block of memory.
///
/// * `bytes` must point to at least the number of bytes returned by
/// `Poke::max_size()`.
pub unsafe fn peek_from_uninit<T: Copy + Peek>(bytes: *const u8) -> (T, *const u8) {
let mut val = MaybeUninitShim { uninit: () };
let bytes = <T>::peek_from(bytes, &mut val.init);
(val.init, bytes)
}
/// Peek helper for constructing a `T` by `Default` initialized stack
/// allocation.
///
/// # Safety
///
/// * `bytes` must denote a valid pointer to a block of memory.
///
/// * `bytes` must point to at least the number of bytes returned by
/// `Poke::max_size()`.
pub unsafe fn peek_from_default<T: Default + Peek>(bytes: *const u8) -> (T, *const u8) {
let mut val = T::default();
let bytes = <T>::peek_from(bytes, &mut val);
(val, bytes)
}
/// Peek inplace a `T` from a slice of bytes, returning a slice of the remaining
/// bytes. `src` must contain at least `T::max_size()` bytes.
///
/// [`ensure_red_zone`] can be used to add required padding.
pub fn peek_from_slice<'a, T: Peek>(src: &'a [u8], dst: &mut T) -> &'a [u8] {
unsafe {
// If src.len() == T::max_size() then src is at the start of the red-zone.
assert!(T::max_size() < src.len(), "WRDL: unexpected end of display list");
let end_ptr = T::peek_from(src.as_ptr(), dst);
let len = end_ptr as usize - src.as_ptr() as usize;
// Did someone break the T::peek_from() can't read more than T::max_size()
// bytes contract?
assert!(len <= src.len(), "WRDL: Peek::max_size was wrong");
slice::from_raw_parts(end_ptr, src.len() - len)
}
}
/// Poke helper to insert a serialized version of `src` at the beginning for `dst`.
pub fn poke_inplace_slice<T: Poke>(src: &T, dst: &mut [u8]) {
assert!(T::max_size() <= dst.len(), "WRDL: buffer too small to write into");
unsafe {
src.poke_into(dst.as_mut_ptr());
}
}
/// Poke helper to append a serialized version of `src` to the end of `dst`.
pub fn poke_into_vec<T: Poke>(src: &T, dst: &mut Vec<u8>) {
dst.reserve(T::max_size());
unsafe {
let ptr = dst.as_end_mut_ptr();
let end_ptr = src.poke_into(ptr);
dst.set_end_ptr(end_ptr);
}
}
// TODO: Is returning the len of the iterator of any practical use?
pub fn poke_extend_vec<I>(src: I, dst: &mut Vec<u8>) -> usize
where
I: ExactSizeIterator,
I::Item: Poke,
{
let len = src.len();
let max_size = len * I::Item::max_size();
dst.reserve(max_size);
unsafe {
let ptr = dst.as_end_mut_ptr();
// Guard against the possibility of a misbehaved implementation of
// ExactSizeIterator by writing at most `len` items.
let end_ptr = src.take(len).fold(ptr, |ptr, item| item.poke_into(ptr));
dst.set_end_ptr(end_ptr);
}
len
}
/// Add `T::max_size()` "red zone" (padding of zeroes) to the end of the vec of
/// `bytes`. This allows deserialization to assert that at least `T::max_size()`
/// bytes exist at all times.
pub fn ensure_red_zone<T: Poke>(bytes: &mut Vec<u8>) {
bytes.reserve(T::max_size());
unsafe {
let end_ptr = bytes.as_end_mut_ptr();
end_ptr.write_bytes(0, T::max_size());
bytes.set_end_ptr(end_ptr.add(T::max_size()));
}
}
/// Remove the "red zone" (padding of zeroes) from the end of the vec of `bytes`.
/// This is effectively the inverse of `ensure_red_zone`, with the caveat that
/// space reserved for the red zone is not un-reserved. Callers are repsonsible
/// for making sure the vec actually has a red zone, otherwise data bytes can
/// get stripped instead.
pub fn strip_red_zone<T: Poke>(bytes: &mut Vec<u8>) {
assert!(bytes.len() >= T::max_size());
unsafe {
let end_ptr = bytes.as_end_mut_ptr();
bytes.set_end_ptr(end_ptr.sub(T::max_size()));
}
}
#[inline]
unsafe fn read_verbatim<T>(src: *const u8, dst: *mut T) -> *const u8 {
*dst = (src as *const T).read_unaligned();
src.add(size_of::<T>())
}
#[inline]
unsafe fn write_verbatim<T>(src: T, dst: *mut u8) -> *mut u8 {
(dst as *mut T).write_unaligned(src);
dst.add(size_of::<T>())
}
#[cfg(feature = "extras")]
mod euclid;
/// A trait for values that provide serialization into buffers of bytes.
///
/// # Example
///
/// ```no_run
/// use peek_poke::Poke;
///
/// struct Bar {
/// a: u32,
/// b: u8,
/// c: i16,
/// }
///
/// unsafe impl Poke for Bar {
/// fn max_size() -> usize {
/// <u32>::max_size() + <u8>::max_size() + <i16>::max_size()
/// }
/// unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 {
/// let bytes = self.a.poke_into(bytes);
/// let bytes = self.b.poke_into(bytes);
/// self.c.poke_into(bytes)
/// }
/// }
/// ```
///
/// # Safety
///
/// The `Poke` trait is an `unsafe` trait for the reasons, and implementors must
/// ensure that they adhere to these contracts:
///
/// * `max_size()` query and calculations in general must be correct. Callers
/// of this trait are expected to rely on the contract defined on each
/// method, and implementors must ensure such contracts remain true.
pub unsafe trait Poke {
/// Return the maximum number of bytes that the serialized version of `Self`
/// will occupy.
///
/// # Safety
///
/// Implementors of `Poke` guarantee to not write more than the result of
/// calling `max_size()` into the buffer pointed to by `bytes` when
/// `poke_into()` is called.
fn max_size() -> usize;
/// Serialize into the buffer pointed to by `bytes`.
///
/// Returns a pointer to the next byte after the serialized representation of `Self`.
///
/// # Safety
///
/// This function is unsafe because undefined behavior can result if the
/// caller does not ensure all of the following:
///
/// * `bytes` must denote a valid pointer to a block of memory.
///
/// * `bytes` must pointer to at least the number of bytes returned by
/// `max_size()`.
unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8;
}
/// A trait for values that provide deserialization from buffers of bytes.
///
/// # Example
///
/// ```ignore
/// use peek_poke::Peek;
///
/// struct Bar {
/// a: u32,
/// b: u8,
/// c: i16,
/// }
///
/// ...
///
/// impl Peek for Bar {
/// unsafe fn peek_from(&mut self, bytes: *const u8) -> *const u8 {
/// let bytes = self.a.peek_from(bytes);
/// let bytes = self.b.peek_from(bytes);
/// self.c.peek_from(bytes)
/// }
/// }
/// ```
///
/// # Safety
///
/// The `Peek` trait contains unsafe methods for the following reasons, and
/// implementors must ensure that they adhere to these contracts:
///
/// * Callers of this trait are expected to rely on the contract defined on each
/// method, and implementors must ensure that `peek_from()` doesn't read more
/// bytes from `bytes` than is returned by `Peek::max_size()`.
pub trait Peek: Poke {
/// Deserialize from the buffer pointed to by `bytes`.
///
/// Returns a pointer to the next byte after the unconsumed bytes not used
/// to deserialize the representation of `Self`.
///
/// # Safety
///
/// This function is unsafe because undefined behavior can result if the
/// caller does not ensure all of the following:
///
/// * `bytes` must denote a valid pointer to a block of memory.
///
/// * `bytes` must pointer to at least the number of bytes returned by
/// `Poke::max_size()`.
unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8;
}
macro_rules! impl_poke_for_deref {
(<$($desc:tt)+) => {
unsafe impl <$($desc)+ {
#[inline(always)]
fn max_size() -> usize {
<T>::max_size()
}
unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 {
(**self).poke_into(bytes)
}
}
}
}
impl_poke_for_deref!(<'a, T: Poke> Poke for &'a T);
impl_poke_for_deref!(<'a, T: Poke> Poke for &'a mut T);
macro_rules! impl_for_primitive {
($($ty:ty)+) => {
$(unsafe impl Poke for $ty {
#[inline(always)]
fn max_size() -> usize {
size_of::<Self>()
}
#[inline(always)]
unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 {
write_verbatim(*self, bytes)
}
}
impl Peek for $ty {
#[inline(always)]
unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 {
read_verbatim(bytes, output)
}
})+
};
}
impl_for_primitive! {
i8 i16 i32 i64 isize
u8 u16 u32 u64 usize
f32 f64
}
unsafe impl Poke for bool {
#[inline(always)]
fn max_size() -> usize {
u8::max_size()
}
#[inline]
unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 {
(*self as u8).poke_into(bytes)
}
}
impl Peek for bool {
#[inline]
unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 {
let mut int_bool = 0u8;
let ptr = <u8>::peek_from(bytes, &mut int_bool);
*output = int_bool != 0;
ptr
}
}
unsafe impl<T> Poke for PhantomData<T> {
#[inline(always)]
fn max_size() -> usize {
0
}
#[inline(always)]
unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 {
bytes
}
}
impl<T> Peek for PhantomData<T> {
#[inline(always)]
unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 {
*output = PhantomData;
bytes
}
}
unsafe impl<T: Poke> Poke for Option<T> {
#[inline(always)]
fn max_size() -> usize {
u8::max_size() + T::max_size()
}
#[inline]
unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 {
match self {
None => 0u8.poke_into(bytes),
Some(ref v) => {
let bytes = 1u8.poke_into(bytes);
v.poke_into(bytes)
}
}
}
}
impl<T: Default + Peek> Peek for Option<T> {
#[inline]
unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 {
let (variant, bytes) = peek_from_default::<u8>(bytes);
match variant {
0 => {
*output = None;
bytes
}
1 => {
let (val, bytes) = peek_from_default(bytes);
*output = Some(val);
bytes
}
_ => unreachable!(),
}
}
}
macro_rules! impl_for_arrays {
($($len:tt)+) => {
$(unsafe impl<T: Poke> Poke for [T; $len] {
fn max_size() -> usize {
$len * T::max_size()
}
unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 {
self.iter().fold(bytes, |bytes, e| e.poke_into(bytes))
}
}
impl<T: Peek> Peek for [T; $len] {
unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 {
(&mut *output).iter_mut().fold(bytes, |bytes, e| <T>::peek_from(bytes, e))
}
})+
}
}
impl_for_arrays! {
1 2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
31 32
}
unsafe impl Poke for () {
fn max_size() -> usize {
0
}
unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 {
bytes
}
}
impl Peek for () {
unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 {
*output = ();
bytes
}
}
macro_rules! impl_for_tuple {
($($n:tt: $ty:ident),+) => {
unsafe impl<$($ty: Poke),+> Poke for ($($ty,)+) {
#[inline(always)]
fn max_size() -> usize {
0 $(+ <$ty>::max_size())+
}
unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 {
$(let bytes = self.$n.poke_into(bytes);)+
bytes
}
}
impl<$($ty: Peek),+> Peek for ($($ty,)+) {
unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 {
$(let bytes = $ty::peek_from(bytes, &mut (*output).$n);)+
bytes
}
}
}
}
impl_for_tuple!(0: A);
impl_for_tuple!(0: A, 1: B);
impl_for_tuple!(0: A, 1: B, 2: C);
impl_for_tuple!(0: A, 1: B, 2: C, 3: D);
impl_for_tuple!(0: A, 1: B, 2: C, 3: D, 4: E);