diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:44:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:44:51 +0000 |
commit | 9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /third_party/rust/plain/src | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/plain/src')
-rw-r--r-- | third_party/rust/plain/src/error.rs | 6 | ||||
-rw-r--r-- | third_party/rust/plain/src/lib.rs | 158 | ||||
-rw-r--r-- | third_party/rust/plain/src/methods.rs | 198 | ||||
-rw-r--r-- | third_party/rust/plain/src/plain.rs | 96 | ||||
-rw-r--r-- | third_party/rust/plain/src/tests.rs | 123 |
5 files changed, 581 insertions, 0 deletions
diff --git a/third_party/rust/plain/src/error.rs b/third_party/rust/plain/src/error.rs new file mode 100644 index 0000000000..0012eba9bc --- /dev/null +++ b/third_party/rust/plain/src/error.rs @@ -0,0 +1,6 @@ + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum Error { + TooShort, + BadAlignment, +} diff --git a/third_party/rust/plain/src/lib.rs b/third_party/rust/plain/src/lib.rs new file mode 100644 index 0000000000..f4eb4badf1 --- /dev/null +++ b/third_party/rust/plain/src/lib.rs @@ -0,0 +1,158 @@ +//! A small Rust library that allows users to interpret arrays of bytes +//! as certain kinds of structures safely. +//! +//! This crate provides an unsafe trait [`Plain`](trait.Plain.html), which the user +//! of the crate uses to mark types for which operations of this library are safe. +//! See [`Plain`](trait.Plain.html) for the contractual obligation. +//! +//! Other than that, everything else in this crate is perfectly safe to use as long +//! as the `Plain` trait is not implemented on inadmissible types (similar to how +//! `Send` and `Sync` in the standard library work). +//! +//! # Purpose +//! +//! In low level systems development, it is sometimes necessary to +//! interpret locations in memory as data structures. Functions of +//! this crate serve to avoid pitfalls associated with that, without +//! having to resort to big, full-featured (de)serialization libraries. +//! +//! On the other hand, this crate contains no provisions when it comes +//! to handling differences in encoding and byte ordering between +//! platforms. As such, it is entirely unsuitable for processing +//! external data such as file contents or network packets. +//! +//! # Examples +//! +//! To start using the crate, simply do `extern crate plain;`. +//! +//! If you want your plain types to have methods from this crate, also include `use plain.Plain;`. +//! +//! Then it's just a matter of marking the right types and using them. +//! +//! ``` +//! +//! extern crate plain; +//! use plain::Plain; +//! use std::mem; +//! +//! +//! #[repr(C)] +//! #[derive(Default)] +//! struct ELF64Header { +//! pub e_ident: [u8; 16], +//! pub e_type: u16, +//! pub e_machine: u16, +//! pub e_version: u32, +//! pub e_entry: u64, +//! pub e_phoff: u64, +//! pub e_shoff: u64, +//! pub e_flags: u32, +//! pub e_ehsize: u16, +//! pub e_phentsize: u16, +//! pub e_phnum: u16, +//! pub e_shentsize: u16, +//! pub e_shnum: u16, +//! pub e_shstrndx: u16, +//! } +//! +//! // SAFE: ELF64Header satisfies all the requirements of `Plain`. +//! unsafe impl Plain for ELF64Header {} +//! +//! impl ELF64Header { +//! fn from_bytes(buf: &[u8]) -> &ELF64Header { +//! plain::from_bytes(buf).expect("The buffer is either too short or not aligned!") +//! } +//! +//! fn from_mut_bytes(buf: &mut [u8]) -> &mut ELF64Header { +//! plain::from_mut_bytes(buf).expect("The buffer is either too short or not aligned!") +//! } +//! +//! fn copy_from_bytes(buf: &[u8]) -> ELF64Header { +//! let mut h = ELF64Header::default(); +//! h.copy_from_bytes(buf).expect("The buffer is too short!"); +//! h +//! } +//! } +//! +//! # fn process_elf(elf: &ELF64Header) {} +//! +//! // Conditional copying for ultimate hackery. +//! fn opportunistic_elf_processing(buf: &[u8]) { +//! if plain::is_aligned::<ELF64Header>(buf) { +//! // No copy necessary. +//! let elf_ref = ELF64Header::from_bytes(buf); +//! process_elf(elf_ref); +//! } else { +//! // Not aligned properly, copy to stack first. +//! let elf = ELF64Header::copy_from_bytes(buf); +//! process_elf(&elf); +//! } +//! } +//! +//! #[repr(C)] +//! #[derive(Default, Copy, Clone)] +//! struct ArrayEntry { +//! pub name: [u8; 32], +//! pub tag: u32, +//! pub score: u32, +//! } +//! +//! // SAFE: ArrayEntry satisfies all the requirements of `Plain`. +//! unsafe impl Plain for ArrayEntry {} +//! +//! fn array_from_bytes(buf: &[u8]) -> &[ArrayEntry] { +//! // NOTE: length is not a concern here, +//! // since slice_from_bytes() can return empty slice. +//! +//! match plain::slice_from_bytes(buf) { +//! Err(_) => panic!("The buffer is not aligned!"), +//! Ok(arr) => arr, +//! } +//! } +//! +//! fn array_from_unaligned_bytes(buf: &[u8]) -> Vec<ArrayEntry> { +//! let length = buf.len() / mem::size_of::<ArrayEntry>(); +//! let mut result = vec![ArrayEntry::default(); length]; +//! (&mut result).copy_from_bytes(buf).expect("Cannot fail here."); +//! result +//! } +//! +//! # fn main() {} +//! +//! ``` +//! +//! # Comparison to [`pod`](https://crates.io/crates/pod) +//! +//! [`pod`](https://crates.io/crates/pod) is another crate created to help working with plain data. +//! The major difference between `pod` and `plain` is scope. +//! +//! `plain` currently provides only a few functions (+method wrappers) and its implementation +//! involves very few lines of unsafe code. It can be used in `no_std` code. Also, it doesn't +//! deal with [endianness](https://en.wikipedia.org/wiki/Endianness) in any way, +//! so it is only suitable for certain kinds of low-level work. +//! +//! `pod`, on the other hand, provides a wide arsenal +//! of various methods, most of which may be unnecessary for a given use case. +//! It has dependencies on `std` as well as other crates, but among other things +//! it provides tools to handle endianness properly. +//! +//! In short, `plain` is much, much _plainer_... +#![no_std] + +mod error; +pub use error::Error; + +mod plain; +pub use plain::Plain; + +mod methods; +pub use methods::{as_bytes, as_mut_bytes, copy_from_bytes, from_bytes, from_mut_bytes, is_aligned, + slice_from_bytes, slice_from_bytes_len, slice_from_mut_bytes, + slice_from_mut_bytes_len}; + +#[cfg(test)] +#[macro_use] +extern crate std; + +#[cfg(test)] +mod tests; diff --git a/third_party/rust/plain/src/methods.rs b/third_party/rust/plain/src/methods.rs new file mode 100644 index 0000000000..58be4a2500 --- /dev/null +++ b/third_party/rust/plain/src/methods.rs @@ -0,0 +1,198 @@ + +use core::{mem, slice}; + +use {Error, Plain}; + +/// Check if a byte slice is aligned suitably for type T. +#[inline] +pub fn is_aligned<T>(bytes: &[u8]) -> bool { + ((bytes.as_ptr() as usize) % mem::align_of::<T>()) == 0 +} + +#[inline(always)] +fn check_alignment<T>(bytes: &[u8]) -> Result<(), Error> { + if is_aligned::<T>(bytes) { + Ok(()) + } else { + Err(Error::BadAlignment) + } +} + +#[inline(always)] +fn check_length<T>(bytes: &[u8], len: usize) -> Result<(), Error> { + if mem::size_of::<T>() > 0 && (bytes.len() / mem::size_of::<T>()) < len { + Err(Error::TooShort) + } else { + Ok(()) + } +} + +/// Interpret data as bytes. Not safe for data with padding. +#[inline(always)] +pub unsafe fn as_bytes<S>(s: &S) -> &[u8] +where + S: ?Sized, +{ + let bptr = s as *const S as *const u8; + let bsize = mem::size_of_val(s); + slice::from_raw_parts(bptr, bsize) +} + +/// Interpret data as mutable bytes. +/// Reading is not safe for data with padding. Writing is ok. +#[inline(always)] +pub unsafe fn as_mut_bytes<S>(s: &mut S) -> &mut [u8] +where + S: Plain + ?Sized, +{ + let bptr = s as *mut S as *mut u8; + let bsize = mem::size_of_val(s); + slice::from_raw_parts_mut(bptr, bsize) +} + +/// Safely converts a byte slice to a reference. +/// +/// However, if the byte slice is not long enough +/// to contain target type, or if it doesn't +/// satisfy the type's alignment requirements, +/// the function returns an error. +/// +/// The function will not fail when the +/// byte slice is longer than necessary, since it is +/// a common practice to interpret the beginning of +/// a slice as a fixed-size header. +/// +/// In many cases it is preferrable to allocate +/// a value/slice of the target type and use +/// [`copy_from_bytes()`](fn.copy_from_bytes.html) to copy +/// data instead. That way, any issues with alignment +/// are implicitly avoided. +/// +#[inline] +pub fn from_bytes<T>(bytes: &[u8]) -> Result<&T, Error> +where + T: Plain, +{ + try!(check_alignment::<T>(bytes)); + try!(check_length::<T>(bytes, 1)); + Ok(unsafe { &*(bytes.as_ptr() as *const T) }) +} + +/// Similar to [`from_bytes()`](fn.from_bytes.html), +/// except that the output is a slice of T, instead +/// of a reference to a single T. All concerns about +/// alignment also apply here, but size is handled +/// differently. +/// +/// The result slice's length is set to be +/// `bytes.len() / size_of::<T>()`, and there +/// are no requirements for input size. I.e. +/// the result may be empty slice, and the input +/// slice doesn't necessarily have to end on `T`'s +/// boundary. The latter has pragmatic reasons: If the +/// length of the array is not known in advance, +/// e.g. if it's terminated by a special element, +/// it's perfectly legal to turn the whole rest +/// of data into `&[T]` and set the proper length +/// after inspecting the array. +/// +/// In many cases it is preferrable to allocate +/// a value/slice of the target type and use +/// [`copy_from_bytes()`](fn.copy_from_bytes.html) to copy +/// data instead. That way, any issues with alignment +/// are implicitly avoided. +/// +#[inline] +pub fn slice_from_bytes<T>(bytes: &[u8]) -> Result<&[T], Error> +where + T: Plain, +{ + let len = bytes.len() / mem::size_of::<T>(); + slice_from_bytes_len(bytes, len) +} + + +/// Same as [`slice_from_bytes()`](fn.slice_from_bytes.html), +/// except that it takes explicit length of the result slice. +/// +/// If the input slice cannot satisfy the length, returns error. +/// The input slice is allowed to be longer than necessary. +/// +#[inline] +pub fn slice_from_bytes_len<T>(bytes: &[u8], len: usize) -> Result<&[T], Error> +where + T: Plain, +{ + try!(check_alignment::<T>(bytes)); + try!(check_length::<T>(bytes, len)); + Ok(unsafe { + slice::from_raw_parts(bytes.as_ptr() as *const T, len) + }) +} + +/// See [`from_bytes()`](fn.from_bytes.html). +/// +/// Does the same, except with mutable references. +/// +#[inline] +pub fn from_mut_bytes<T>(bytes: &mut [u8]) -> Result<&mut T, Error> +where + T: Plain, +{ + try!(check_alignment::<T>(bytes)); + try!(check_length::<T>(bytes, 1)); + Ok(unsafe { &mut *(bytes.as_mut_ptr() as *mut T) }) +} + +/// See [`slice_from_bytes()`](fn.slice_from_bytes.html). +/// +/// Does the same, except with mutable references. +/// +#[inline] +pub fn slice_from_mut_bytes<T>(bytes: &mut [u8]) -> Result<&mut [T], Error> +where + T: Plain, +{ + let len = bytes.len() / mem::size_of::<T>(); + slice_from_mut_bytes_len(bytes, len) +} + +/// See [`slice_from_bytes_len()`](fn.slice_from_bytes_len.html). +/// +/// Does the same, except with mutable references. +/// +#[inline] +pub fn slice_from_mut_bytes_len<T>(bytes: &mut [u8], len: usize) -> Result<&mut [T], Error> +where + T: Plain, +{ + try!(check_alignment::<T>(bytes)); + try!(check_length::<T>(bytes, len)); + Ok(unsafe { + slice::from_raw_parts_mut(bytes.as_ptr() as *mut T, len) + }) +} + +/// Copies data from a byte slice into existing memory. +/// Suitable when [`from_bytes()`](fn.from_bytes.html) would normally +/// be used, but the data is not aligned properly in memory. +/// +/// For an example how to use it, see crate-level documentation. +/// +#[inline] +pub fn copy_from_bytes<T>(into: &mut T, bytes: &[u8]) -> Result<(), Error> +where + T: Plain + ?Sized, +{ + let sz = mem::size_of_val(into); + + if bytes.len() < sz { + return Err(Error::TooShort); + } + + unsafe { + as_mut_bytes(into).copy_from_slice(&bytes[..sz]); + } + + Ok(()) +} diff --git a/third_party/rust/plain/src/plain.rs b/third_party/rust/plain/src/plain.rs new file mode 100644 index 0000000000..35522006de --- /dev/null +++ b/third_party/rust/plain/src/plain.rs @@ -0,0 +1,96 @@ +use Error; + +/// A trait for plain data types that can be safely read from a byte slice. +/// +/// A type can be [`Plain`](trait.Plain.html) if it is `#repr(C)` and only contains +/// data with no possible invalid values. Types that _can't_ be `Plain` +/// include, but are not limited to, `bool`, `char`, `enum`s, tuples, +/// pointers and references. +/// +/// At this moment, `Drop` types are also not legal, because +/// compiler adds a special "drop flag" into the type. This is slated +/// to change in the future. +/// +/// On the other hand, arrays of a `Plain` type, and +/// structures where all members are plain (and not `Drop`), are okay. +/// +/// Structures that are not `#repr(C)`, while not necessarily illegal +/// in principle, are largely useless because they don't have a stable +/// layout. For example, the compiler is allowed to reorder fields +/// arbitrarily. +/// +/// All methods of this trait are implemented automatically as wrappers +/// for crate-level funtions. +/// +pub unsafe trait Plain { + #[inline(always)] + fn from_bytes(bytes: &[u8]) -> Result<&Self, Error> + where + Self: Sized, + { + ::from_bytes(bytes) + } + + #[inline(always)] + fn slice_from_bytes(bytes: &[u8]) -> Result<&[Self], Error> + where + Self: Sized, + { + ::slice_from_bytes(bytes) + } + + #[inline(always)] + fn slice_from_bytes_len(bytes: &[u8], len: usize) -> Result<&[Self], Error> + where + Self: Sized, + { + ::slice_from_bytes_len(bytes, len) + } + + #[inline(always)] + fn from_mut_bytes(bytes: &mut [u8]) -> Result<&mut Self, Error> + where + Self: Sized, + { + ::from_mut_bytes(bytes) + } + + #[inline(always)] + fn slice_from_mut_bytes(bytes: &mut [u8]) -> Result<&mut [Self], Error> + where + Self: Sized, + { + ::slice_from_mut_bytes(bytes) + } + + #[inline(always)] + fn slice_from_mut_bytes_len(bytes: &mut [u8], len: usize) -> Result<&mut [Self], Error> + where + Self: Sized, + { + ::slice_from_mut_bytes_len(bytes, len) + } + + #[inline(always)] + fn copy_from_bytes(&mut self, bytes: &[u8]) -> Result<(), Error> { + ::copy_from_bytes(self, bytes) + } +} + +unsafe impl Plain for u8 {} +unsafe impl Plain for u16 {} +unsafe impl Plain for u32 {} +unsafe impl Plain for u64 {} +unsafe impl Plain for usize {} + +unsafe impl Plain for i8 {} +unsafe impl Plain for i16 {} +unsafe impl Plain for i32 {} +unsafe impl Plain for i64 {} +unsafe impl Plain for isize {} + +unsafe impl<S> Plain for [S] +where + S: Plain, +{ +} diff --git a/third_party/rust/plain/src/tests.rs b/third_party/rust/plain/src/tests.rs new file mode 100644 index 0000000000..7ab1c3753e --- /dev/null +++ b/third_party/rust/plain/src/tests.rs @@ -0,0 +1,123 @@ + + +#![allow(dead_code)] + +use ::*; +use core::mem; + +#[repr(C)] +#[derive(Debug, Default, Copy, Eq, Clone, PartialEq)] +struct Dummy1 { + field1: u64, + field2: u32, + field3: u16, + field4: u8, + field5: u8, +} + +unsafe impl Plain for Dummy1 {} + +#[repr(C)] +#[derive(Debug, Default, Copy, Eq, Clone, PartialEq)] +struct Dummy2 { + field1: u8, + field2: u8, + field3: u16, + field4: u32, + field5: u64, +} + +unsafe impl Plain for Dummy2 {} + +fn as_bytes<T: ?Sized>(r: &T) -> &[u8] { + unsafe { methods::as_bytes(r) } +} + +fn as_mut_bytes<T: Plain + ?Sized>(r: &mut T) -> &mut [u8] { + unsafe { methods::as_mut_bytes(r) } +} + +#[test] +fn one_too_short() { + let b = vec![0u8; mem::size_of::<Dummy1>() - 1]; + + let r = Dummy1::from_bytes(&b); + assert!(r == Err(Error::TooShort)); +} + +#[test] +fn unaligned() { + let b = vec![0u8; mem::size_of::<Dummy1>() + 1]; + let b = &b[1..]; + + let r = Dummy1::from_bytes(&b); + assert!(r == Err(Error::BadAlignment)); +} + +#[test] +fn copy_test() { + let t1 = Dummy1 { + field1: 0xaaaaaaaaaaaaaaaau64, + field2: 0xbbbbbbbbu32, + field3: 0xccccu16, + field4: 0xddu8, + field5: 0xeeu8, + }; + + let mut t2 = Dummy2::default(); + + assert!(t2.copy_from_bytes(as_bytes(&t1)) == Ok(())); + + assert!(t2.field1 == 0xaau8); + assert!(t2.field2 == 0xaau8); + assert!(t2.field3 == 0xaaaau16); + assert!(t2.field4 == 0xaaaaaaaau32); + assert!(t2.field5 == 0xbbbbbbbbccccddeeu64 || t2.field5 == 0xeeddccccbbbbbbbbu64); + + let sz = mem::size_of::<Dummy2>(); + assert!(t2.copy_from_bytes(&as_bytes(&t1)[..sz - 1]) == Err(Error::TooShort)); +} + +#[test] +fn basic_function() { + let t1 = Dummy1 { + field1: 0xaaaaaaaaaaaaaaaau64, + field2: 0xbbbbbbbbu32, + field3: 0xccccu16, + field4: 0xddu8, + field5: 0xeeu8, + }; + + let r1: &Dummy2 = from_bytes(as_bytes(&t1)).unwrap(); + + assert!(r1.field1 == 0xaau8); + assert!(r1.field2 == 0xaau8); + assert!(r1.field3 == 0xaaaau16); + assert!(r1.field4 == 0xaaaaaaaau32); + assert!(r1.field5 == 0xbbbbbbbbccccddeeu64 || r1.field5 == 0xeeddccccbbbbbbbbu64); + + let r2 = as_bytes(r1); + assert!(r2.len() == mem::size_of::<Dummy1>()); + assert!(r2[5] == 0xaa); + + let size = r2.len(); + let r3 = as_bytes(r2); + assert!(r3.len() == size); + + let r4 = Dummy1::from_bytes(r3).unwrap(); + + let r5 = from_bytes::<Dummy2>(as_bytes(r4)).unwrap(); + + { + let r6 = slice_from_bytes::<Dummy1>(as_bytes(r5)).unwrap(); + + assert!(r6.len() == 1); + assert!(t1 == r6[0]); + } + + let r7 = slice_from_bytes::<u64>(as_bytes(r5)).unwrap(); + assert!(r7.len() == 2); + + assert!(r7[0] == 0xaaaaaaaaaaaaaaaau64); + assert!(r7[1] == 0xbbbbbbbbccccddeeu64 || r7[1] == 0xeeddccccbbbbbbbbu64); +} |