Commit 1095cca7 authored by Simon Sapin's avatar Simon Sapin
Browse files

Extend `BytesCast` to convert to bytes, not just from bytes

Although the derive macro is unchanged, the requirements for manual
`unsafe impl` blocks are slightly stronger.
Even if unlikely this could make some previously-valid impls invalid.
The incompatible version number reflects this.
parent 28af7a1176a2
[package]
name = "bytes-cast"
version = "0.1.0"
version = "0.2.0"
authors = ["Octobus"]
license = "Zlib OR Apache-2.0 OR MIT"
repository = "https://foss.heptapod.net/octobus/rust/bytes-cast"
......
//! Utilities for safely re-interpreting `&[u8]` bytes as custom structs
//! without copying, for efficiently reading structured binary data.
//! and back without copying, for efficiently reading structured binary data.
//!
//! # Example
//!
//! Reading bytes:
//!
//! ```
//! use bytes_cast::{BytesCast, unaligned};
//!
......@@ -30,6 +32,24 @@
//!
//! assert!(unaligned::U16Le::slice_from_bytes(input, 5).is_err()); // input is too short
//! ```
//!
//! Writing bytes:
//!
//! ```
//! # use bytes_cast::{BytesCast, unaligned};
//! # #[derive(BytesCast)]
//! # #[repr(C)]
//! # struct Foo {
//! # bar: [u8; 2],
//! # baz: unaligned::U32Be,
//! # }
//!
//! let foo = Foo { bar: [1, 2], baz: 0x0304_0506.into() };
//! assert_eq!(foo.as_bytes(), &[1_u8, 2, 3, 4, 5, 6]);
//!
//! let slice: &[unaligned::U16Le] = &[0x02_01.into(), 0x04_03.into()];
//! assert_eq!(slice.as_bytes(), &[1_u8, 2, 3, 4]);
//! ```
#![no_std]
......@@ -43,14 +63,15 @@ pub mod unaligned;
#[cfg(doctest)]
mod compile_fail_tests;
/// Marks a type as safe to interpret from bytes without copying.
/// Marks a type as safe to interpret from and to bytes without copying.
///
/// # Safety
///
/// For a type to implement this trait:
///
/// * All bit patterns must be valid. (This excludes `bool`, enums, etc.)
/// * All initialized bit patterns must be valid. (This excludes `bool`, enums, etc.)
/// * There must not be an alignment requirement. (`align_of() == 1`)
/// * There must be no padding or otherwise uninitialized bytes
///
/// # Deriving
///
......@@ -73,23 +94,22 @@ mod compile_fail_tests;
/// * By necessity: generics would make `align_of` potentially depend on type
/// parameters and not possible to statically check at the struct definition
/// site.
///
/// Finally, mandating `BytesCast` for all fields means they also have `align_of() ==
/// 1` and therefore the struct does not have any padding.
/// This makes memory layout / field offsets easier to reason about.
pub unsafe trait BytesCast: Sized {
pub unsafe trait BytesCast {
/// Interpret the start of the given slice of bytes as reference to this
/// type.
///
/// If the given input is large enough, returns a tuple of the new
/// reference and the remaining of the bytes.
#[inline]
fn from_bytes(bytes: &[u8]) -> Result<(&Self, &[u8]), FromBytesError> {
fn from_bytes(bytes: &[u8]) -> Result<(&Self, &[u8]), FromBytesError>
where
Self: Sized,
{
let expected_len = mem::size_of::<Self>();
remaining_bytes(bytes, expected_len).map(|rest| {
// Safety: this cast and dereference are made sound by the length
// check done in `remaining_bytes` together with the
// invariants of `FromByteSafe`.
// invariants of `BytesCast`.
let this = unsafe { &*bytes.as_ptr().cast::<Self>() };
(this, rest)
})
......@@ -100,10 +120,10 @@ pub unsafe trait BytesCast: Sized {
/// If the given input is large enough, returns a tuple of the new
/// slice and the remaining of the bytes.
#[inline]
fn slice_from_bytes(
bytes: &[u8],
slice_len: usize,
) -> Result<(&[Self], &[u8]), FromBytesError> {
fn slice_from_bytes(bytes: &[u8], slice_len: usize) -> Result<(&[Self], &[u8]), FromBytesError>
where
Self: Sized,
{
let expected_byte_len =
mem::size_of::<Self>()
.checked_mul(slice_len)
......@@ -114,11 +134,21 @@ pub unsafe trait BytesCast: Sized {
remaining_bytes(bytes, expected_byte_len).map(|rest| {
// Safety: this cast and call are made sound by the length check
// done in `remaining_bytes` together with the invariants of
// `FromByteSafe`.
// `BytesCast`.
let this = unsafe { slice::from_raw_parts(bytes.as_ptr().cast::<Self>(), slice_len) };
(this, rest)
})
}
/// Interpret this value as the bytes of its memory representation.
#[inline]
fn as_bytes(&self) -> &[u8] {
let ptr: *const Self = self;
let bytes_ptr = ptr.cast::<u8>();
let bytes_len = mem::size_of_val(self);
// Safety: the invariants of `BytesCast` make this sound by definition:
unsafe { slice::from_raw_parts(bytes_ptr, bytes_len) }
}
}
/// If the given slice is long enough, return the the remaining bytes after the
......@@ -159,6 +189,7 @@ impl fmt::Debug for FromBytesError {
}
unsafe impl<T: ?Sized> BytesCast for core::marker::PhantomData<T> {}
unsafe impl<T: BytesCast> BytesCast for [T] {}
unsafe impl BytesCast for () {}
unsafe impl BytesCast for u8 {}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment