123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937 |
- #![doc = include_str!("../README.md")]
- #![warn(missing_docs)]
- use std::{char, mem::MaybeUninit};
- mod const_buffers;
- mod const_vec;
- pub use const_buffers::ConstReadBuffer;
- pub use const_serialize_macro::SerializeConst;
- pub use const_vec::ConstVec;
- /// Plain old data for a field. Stores the offset of the field in the struct and the layout of the field.
- #[derive(Debug, Copy, Clone)]
- pub struct StructFieldLayout {
- offset: usize,
- layout: Layout,
- }
- impl StructFieldLayout {
- /// Create a new struct field layout
- pub const fn new(offset: usize, layout: Layout) -> Self {
- Self { offset, layout }
- }
- }
- /// Layout for a struct. The struct layout is just a list of fields with offsets
- #[derive(Debug, Copy, Clone)]
- pub struct StructLayout {
- size: usize,
- data: &'static [StructFieldLayout],
- }
- impl StructLayout {
- /// Create a new struct layout
- pub const fn new(size: usize, data: &'static [StructFieldLayout]) -> Self {
- Self { size, data }
- }
- }
- /// The layout for an enum. The enum layout is just a discriminate size and a tag layout.
- #[derive(Debug, Copy, Clone)]
- pub struct EnumLayout {
- size: usize,
- discriminant: PrimitiveLayout,
- variants_offset: usize,
- variants: &'static [EnumVariant],
- }
- impl EnumLayout {
- /// Create a new enum layout
- pub const fn new(
- size: usize,
- discriminant: PrimitiveLayout,
- variants: &'static [EnumVariant],
- ) -> Self {
- let mut max_align = 1;
- let mut i = 0;
- while i < variants.len() {
- let EnumVariant { align, .. } = &variants[i];
- if *align > max_align {
- max_align = *align;
- }
- i += 1;
- }
- let variants_offset_raw = discriminant.size;
- let padding = (max_align - (variants_offset_raw % max_align)) % max_align;
- let variants_offset = variants_offset_raw + padding;
- assert!(variants_offset % max_align == 0);
- Self {
- size,
- discriminant,
- variants_offset,
- variants,
- }
- }
- }
- /// The layout for an enum variant. The enum variant layout is just a struct layout with a tag and alignment.
- #[derive(Debug, Copy, Clone)]
- pub struct EnumVariant {
- // Note: tags may not be sequential
- tag: u32,
- data: StructLayout,
- align: usize,
- }
- impl EnumVariant {
- /// Create a new enum variant layout
- pub const fn new(tag: u32, data: StructLayout, align: usize) -> Self {
- Self { tag, data, align }
- }
- }
- /// The layout for a constant sized array. The array layout is just a length and an item layout.
- #[derive(Debug, Copy, Clone)]
- pub struct ListLayout {
- len: usize,
- item_layout: &'static Layout,
- }
- impl ListLayout {
- /// Create a new list layout
- pub const fn new(len: usize, item_layout: &'static Layout) -> Self {
- Self { len, item_layout }
- }
- }
- /// The layout for a primitive type. The bytes will be reversed if the target is big endian.
- #[derive(Debug, Copy, Clone)]
- pub struct PrimitiveLayout {
- size: usize,
- }
- impl PrimitiveLayout {
- /// Create a new primitive layout
- pub const fn new(size: usize) -> Self {
- Self { size }
- }
- }
- /// The layout for a type. This layout defines a sequence of locations and reversed or not bytes. These bytes will be copied from during serialization and copied into during deserialization.
- #[derive(Debug, Copy, Clone)]
- pub enum Layout {
- /// An enum layout
- Enum(EnumLayout),
- /// A struct layout
- Struct(StructLayout),
- /// A list layout
- List(ListLayout),
- /// A primitive layout
- Primitive(PrimitiveLayout),
- }
- impl Layout {
- /// The size of the type in bytes.
- const fn size(&self) -> usize {
- match self {
- Layout::Enum(layout) => layout.size,
- Layout::Struct(layout) => layout.size,
- Layout::List(layout) => layout.len * layout.item_layout.size(),
- Layout::Primitive(layout) => layout.size,
- }
- }
- }
- /// A trait for types that can be serialized and deserialized in const.
- ///
- /// # Safety
- /// The layout must accurately describe the memory layout of the type
- pub unsafe trait SerializeConst: Sized {
- /// The memory layout of the type. This type must have plain old data; no pointers or references.
- const MEMORY_LAYOUT: Layout;
- /// Assert that the memory layout of the type is the same as the size of the type
- const _ASSERT: () = assert!(Self::MEMORY_LAYOUT.size() == std::mem::size_of::<Self>());
- }
- macro_rules! impl_serialize_const {
- ($type:ty) => {
- unsafe impl SerializeConst for $type {
- const MEMORY_LAYOUT: Layout = Layout::Primitive(PrimitiveLayout {
- size: std::mem::size_of::<$type>(),
- });
- }
- };
- }
- impl_serialize_const!(u8);
- impl_serialize_const!(u16);
- impl_serialize_const!(u32);
- impl_serialize_const!(u64);
- impl_serialize_const!(i8);
- impl_serialize_const!(i16);
- impl_serialize_const!(i32);
- impl_serialize_const!(i64);
- impl_serialize_const!(bool);
- impl_serialize_const!(f32);
- impl_serialize_const!(f64);
- unsafe impl<const N: usize, T: SerializeConst> SerializeConst for [T; N] {
- const MEMORY_LAYOUT: Layout = Layout::List(ListLayout {
- len: N,
- item_layout: &T::MEMORY_LAYOUT,
- });
- }
- macro_rules! impl_serialize_const_tuple {
- ($($generic:ident: $generic_number:expr),*) => {
- impl_serialize_const_tuple!(@impl ($($generic,)*) = $($generic: $generic_number),*);
- };
- (@impl $inner:ty = $($generic:ident: $generic_number:expr),*) => {
- unsafe impl<$($generic: SerializeConst),*> SerializeConst for ($($generic,)*) {
- const MEMORY_LAYOUT: Layout = {
- Layout::Struct(StructLayout {
- size: std::mem::size_of::<($($generic,)*)>(),
- data: &[
- $(
- StructFieldLayout::new(std::mem::offset_of!($inner, $generic_number), $generic::MEMORY_LAYOUT),
- )*
- ],
- })
- };
- }
- };
- }
- impl_serialize_const_tuple!(T1: 0);
- impl_serialize_const_tuple!(T1: 0, T2: 1);
- impl_serialize_const_tuple!(T1: 0, T2: 1, T3: 2);
- impl_serialize_const_tuple!(T1: 0, T2: 1, T3: 2, T4: 3);
- impl_serialize_const_tuple!(T1: 0, T2: 1, T3: 2, T4: 3, T5: 4);
- impl_serialize_const_tuple!(T1: 0, T2: 1, T3: 2, T4: 3, T5: 4, T6: 5);
- impl_serialize_const_tuple!(T1: 0, T2: 1, T3: 2, T4: 3, T5: 4, T6: 5, T7: 6);
- impl_serialize_const_tuple!(T1: 0, T2: 1, T3: 2, T4: 3, T5: 4, T6: 5, T7: 6, T8: 7);
- impl_serialize_const_tuple!(T1: 0, T2: 1, T3: 2, T4: 3, T5: 4, T6: 5, T7: 6, T8: 7, T9: 8);
- impl_serialize_const_tuple!(T1: 0, T2: 1, T3: 2, T4: 3, T5: 4, T6: 5, T7: 6, T8: 7, T9: 8, T10: 9);
- const MAX_STR_SIZE: usize = 256;
- /// A string that is stored in a constant sized buffer that can be serialized and deserialized at compile time
- #[derive(PartialEq, PartialOrd, Clone, Copy, Hash)]
- #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
- pub struct ConstStr {
- #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
- bytes: [u8; MAX_STR_SIZE],
- len: u32,
- }
- #[cfg(feature = "serde")]
- mod serde_bytes {
- use serde::{Deserialize, Serializer};
- pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
- where
- S: Serializer,
- {
- serializer.serialize_bytes(bytes)
- }
- pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; super::MAX_STR_SIZE], D::Error>
- where
- D: serde::Deserializer<'de>,
- {
- let bytes = Vec::<u8>::deserialize(deserializer)?;
- bytes
- .try_into()
- .map_err(|_| serde::de::Error::custom("Failed to convert bytes to a fixed size array"))
- }
- }
- unsafe impl SerializeConst for ConstStr {
- const MEMORY_LAYOUT: Layout = Layout::Struct(StructLayout {
- size: std::mem::size_of::<Self>(),
- data: &[
- StructFieldLayout::new(
- std::mem::offset_of!(Self, bytes),
- Layout::List(ListLayout {
- len: MAX_STR_SIZE,
- item_layout: &Layout::Primitive(PrimitiveLayout {
- size: std::mem::size_of::<u8>(),
- }),
- }),
- ),
- StructFieldLayout::new(
- std::mem::offset_of!(Self, len),
- Layout::Primitive(PrimitiveLayout {
- size: std::mem::size_of::<u32>(),
- }),
- ),
- ],
- });
- }
- impl ConstStr {
- /// Create a new constant string
- pub const fn new(s: &str) -> Self {
- let str_bytes = s.as_bytes();
- let mut bytes = [0; MAX_STR_SIZE];
- let mut i = 0;
- while i < str_bytes.len() {
- bytes[i] = str_bytes[i];
- i += 1;
- }
- Self {
- bytes,
- len: str_bytes.len() as u32,
- }
- }
- /// Get a reference to the string
- pub const fn as_str(&self) -> &str {
- let str_bytes = self.bytes.split_at(self.len as usize).0;
- match std::str::from_utf8(str_bytes) {
- Ok(s) => s,
- Err(_) => panic!(
- "Invalid utf8; ConstStr should only ever be constructed from valid utf8 strings"
- ),
- }
- }
- /// Get the length of the string
- pub const fn len(&self) -> usize {
- self.len as usize
- }
- /// Check if the string is empty
- pub const fn is_empty(&self) -> bool {
- self.len == 0
- }
- /// Push a character onto the string
- pub const fn push(self, byte: char) -> Self {
- assert!(byte.is_ascii(), "Only ASCII bytes are supported");
- let (bytes, len) = char_to_bytes(byte);
- let (str, _) = bytes.split_at(len);
- let Ok(str) = std::str::from_utf8(str) else {
- panic!("Invalid utf8; char_to_bytes should always return valid utf8 bytes")
- };
- self.push_str(str)
- }
- /// Push a str onto the string
- pub const fn push_str(self, str: &str) -> Self {
- let Self { mut bytes, len } = self;
- assert!(
- str.len() + len as usize <= MAX_STR_SIZE,
- "String is too long"
- );
- let str_bytes = str.as_bytes();
- let new_len = len as usize + str_bytes.len();
- let mut i = 0;
- while i < str_bytes.len() {
- bytes[len as usize + i] = str_bytes[i];
- i += 1;
- }
- Self {
- bytes,
- len: new_len as u32,
- }
- }
- /// Split the string at a byte index. The byte index must be a char boundary
- pub const fn split_at(self, index: usize) -> (Self, Self) {
- let (left, right) = self.bytes.split_at(index);
- let left = match std::str::from_utf8(left) {
- Ok(s) => s,
- Err(_) => {
- panic!("Invalid utf8; you cannot split at a byte that is not a char boundary")
- }
- };
- let right = match std::str::from_utf8(right) {
- Ok(s) => s,
- Err(_) => {
- panic!("Invalid utf8; you cannot split at a byte that is not a char boundary")
- }
- };
- (Self::new(left), Self::new(right))
- }
- /// Split the string at the last occurrence of a character
- pub const fn rsplit_once(&self, char: char) -> Option<(Self, Self)> {
- let str = self.as_str();
- let mut index = str.len() - 1;
- // First find the bytes we are searching for
- let (char_bytes, len) = char_to_bytes(char);
- let (char_bytes, _) = char_bytes.split_at(len);
- let bytes = str.as_bytes();
- // Then walk backwards from the end of the string
- loop {
- let byte = bytes[index];
- // Look for char boundaries in the string and check if the bytes match
- if let Some(char_boundary_len) = utf8_char_boundary_to_char_len(byte) {
- // Split up the string into three sections: [before_char, in_char, after_char]
- let (before_char, after_index) = bytes.split_at(index);
- let (in_char, after_char) = after_index.split_at(char_boundary_len as usize);
- if in_char.len() != char_boundary_len as usize {
- panic!("in_char.len() should always be equal to char_boundary_len as usize")
- }
- // Check if the bytes for the current char and the target char match
- let mut in_char_eq = true;
- let mut i = 0;
- let min_len = if in_char.len() < char_bytes.len() {
- in_char.len()
- } else {
- char_bytes.len()
- };
- while i < min_len {
- in_char_eq &= in_char[i] == char_bytes[i];
- i += 1;
- }
- // If they do, convert the bytes to strings and return the split strings
- if in_char_eq {
- let Ok(before_char_str) = std::str::from_utf8(before_char) else {
- panic!("Invalid utf8; utf8_char_boundary_to_char_len should only return Some when the byte is a character boundary")
- };
- let Ok(after_char_str) = std::str::from_utf8(after_char) else {
- panic!("Invalid utf8; utf8_char_boundary_to_char_len should only return Some when the byte is a character boundary")
- };
- return Some((Self::new(before_char_str), Self::new(after_char_str)));
- }
- }
- match index.checked_sub(1) {
- Some(new_index) => index = new_index,
- None => return None,
- }
- }
- }
- /// Split the string at the first occurrence of a character
- pub const fn split_once(&self, char: char) -> Option<(Self, Self)> {
- let str = self.as_str();
- let mut index = 0;
- // First find the bytes we are searching for
- let (char_bytes, len) = char_to_bytes(char);
- let (char_bytes, _) = char_bytes.split_at(len);
- let bytes = str.as_bytes();
- // Then walk forwards from the start of the string
- while index < bytes.len() {
- let byte = bytes[index];
- // Look for char boundaries in the string and check if the bytes match
- if let Some(char_boundary_len) = utf8_char_boundary_to_char_len(byte) {
- // Split up the string into three sections: [before_char, in_char, after_char]
- let (before_char, after_index) = bytes.split_at(index);
- let (in_char, after_char) = after_index.split_at(char_boundary_len as usize);
- if in_char.len() != char_boundary_len as usize {
- panic!("in_char.len() should always be equal to char_boundary_len as usize")
- }
- // Check if the bytes for the current char and the target char match
- let mut in_char_eq = true;
- let mut i = 0;
- let min_len = if in_char.len() < char_bytes.len() {
- in_char.len()
- } else {
- char_bytes.len()
- };
- while i < min_len {
- in_char_eq &= in_char[i] == char_bytes[i];
- i += 1;
- }
- // If they do, convert the bytes to strings and return the split strings
- if in_char_eq {
- let Ok(before_char_str) = std::str::from_utf8(before_char) else {
- panic!("Invalid utf8; utf8_char_boundary_to_char_len should only return Some when the byte is a character boundary")
- };
- let Ok(after_char_str) = std::str::from_utf8(after_char) else {
- panic!("Invalid utf8; utf8_char_boundary_to_char_len should only return Some when the byte is a character boundary")
- };
- return Some((Self::new(before_char_str), Self::new(after_char_str)));
- }
- }
- index += 1
- }
- None
- }
- }
- impl std::fmt::Debug for ConstStr {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "{:?}", self.as_str())
- }
- }
- #[test]
- fn test_rsplit_once() {
- let str = ConstStr::new("hello world");
- assert_eq!(
- str.rsplit_once(' '),
- Some((ConstStr::new("hello"), ConstStr::new("world")))
- );
- let unicode_str = ConstStr::new("hi😀hello😀world😀world");
- assert_eq!(
- unicode_str.rsplit_once('😀'),
- Some((ConstStr::new("hi😀hello😀world"), ConstStr::new("world")))
- );
- assert_eq!(unicode_str.rsplit_once('❌'), None);
- for _ in 0..100 {
- let random_str: String = (0..rand::random::<u8>() % 50)
- .map(|_| rand::random::<char>())
- .collect();
- let konst = ConstStr::new(&random_str);
- let mut seen_chars = std::collections::HashSet::new();
- for char in random_str.chars().rev() {
- let (char_bytes, len) = char_to_bytes(char);
- let char_bytes = &char_bytes[..len];
- assert_eq!(char_bytes, char.to_string().as_bytes());
- if seen_chars.contains(&char) {
- continue;
- }
- seen_chars.insert(char);
- let (correct_left, correct_right) = random_str.rsplit_once(char).unwrap();
- let (left, right) = konst.rsplit_once(char).unwrap();
- println!("splitting {random_str:?} at {char:?}");
- assert_eq!(left.as_str(), correct_left);
- assert_eq!(right.as_str(), correct_right);
- }
- }
- }
- const CONTINUED_CHAR_MASK: u8 = 0b10000000;
- const BYTE_CHAR_BOUNDARIES: [u8; 4] = [0b00000000, 0b11000000, 0b11100000, 0b11110000];
- // Const version of https://doc.rust-lang.org/src/core/char/methods.rs.html#1765-1797
- const fn char_to_bytes(char: char) -> ([u8; 4], usize) {
- let code = char as u32;
- let len = char.len_utf8();
- let mut bytes = [0; 4];
- match len {
- 1 => {
- bytes[0] = code as u8;
- }
- 2 => {
- bytes[0] = (code >> 6 & 0x1F) as u8 | BYTE_CHAR_BOUNDARIES[1];
- bytes[1] = (code & 0x3F) as u8 | CONTINUED_CHAR_MASK;
- }
- 3 => {
- bytes[0] = (code >> 12 & 0x0F) as u8 | BYTE_CHAR_BOUNDARIES[2];
- bytes[1] = (code >> 6 & 0x3F) as u8 | CONTINUED_CHAR_MASK;
- bytes[2] = (code & 0x3F) as u8 | CONTINUED_CHAR_MASK;
- }
- 4 => {
- bytes[0] = (code >> 18 & 0x07) as u8 | BYTE_CHAR_BOUNDARIES[3];
- bytes[1] = (code >> 12 & 0x3F) as u8 | CONTINUED_CHAR_MASK;
- bytes[2] = (code >> 6 & 0x3F) as u8 | CONTINUED_CHAR_MASK;
- bytes[3] = (code & 0x3F) as u8 | CONTINUED_CHAR_MASK;
- }
- _ => panic!(
- "encode_utf8: need more than 4 bytes to encode the unicode character, but the buffer has 4 bytes"
- ),
- };
- (bytes, len)
- }
- #[test]
- fn fuzz_char_to_bytes() {
- use std::char;
- for _ in 0..100 {
- let char = rand::random::<char>();
- let (bytes, len) = char_to_bytes(char);
- let str = std::str::from_utf8(&bytes[..len]).unwrap();
- assert_eq!(char.to_string(), str);
- }
- }
- const fn utf8_char_boundary_to_char_len(byte: u8) -> Option<u8> {
- match byte {
- 0b00000000..=0b01111111 => Some(1),
- 0b11000000..=0b11011111 => Some(2),
- 0b11100000..=0b11101111 => Some(3),
- 0b11110000..=0b11111111 => Some(4),
- _ => None,
- }
- }
- #[test]
- fn fuzz_utf8_byte_to_char_len() {
- for _ in 0..100 {
- let random_string: String = (0..rand::random::<u8>())
- .map(|_| rand::random::<char>())
- .collect();
- let bytes = random_string.as_bytes();
- let chars: std::collections::HashMap<_, _> = random_string.char_indices().collect();
- for (i, byte) in bytes.iter().enumerate() {
- match utf8_char_boundary_to_char_len(*byte) {
- Some(char_len) => {
- let char = chars
- .get(&i)
- .unwrap_or_else(|| panic!("{byte:b} is not a character boundary"));
- assert_eq!(char.len_utf8(), char_len as usize);
- }
- None => {
- assert!(!chars.contains_key(&i), "{byte:b} is a character boundary");
- }
- }
- }
- }
- }
- /// Serialize a struct that is stored at the pointer passed in
- const fn serialize_const_struct(
- ptr: *const (),
- mut to: ConstVec<u8>,
- layout: &StructLayout,
- ) -> ConstVec<u8> {
- let mut i = 0;
- while i < layout.data.len() {
- // Serialize the field at the offset pointer in the struct
- let StructFieldLayout { offset, layout } = &layout.data[i];
- let field = ptr.wrapping_byte_add(*offset as _);
- to = serialize_const_ptr(field, to, layout);
- i += 1;
- }
- to
- }
- /// Serialize an enum that is stored at the pointer passed in
- const fn serialize_const_enum(
- ptr: *const (),
- mut to: ConstVec<u8>,
- layout: &EnumLayout,
- ) -> ConstVec<u8> {
- let mut discriminant = 0;
- let byte_ptr = ptr as *const u8;
- let mut offset = 0;
- while offset < layout.discriminant.size {
- // If the bytes are reversed, walk backwards from the end of the number when pushing bytes
- let byte = if cfg!(target_endian = "big") {
- unsafe {
- byte_ptr
- .wrapping_byte_add((layout.discriminant.size - offset - 1) as _)
- .read()
- }
- } else {
- unsafe { byte_ptr.wrapping_byte_add(offset as _).read() }
- };
- to = to.push(byte);
- discriminant |= (byte as u32) << (offset * 8);
- offset += 1;
- }
- let mut i = 0;
- while i < layout.variants.len() {
- // If the variant is the discriminated one, serialize it
- let EnumVariant { tag, data, .. } = &layout.variants[i];
- if discriminant == *tag {
- let data_ptr = ptr.wrapping_byte_offset(layout.variants_offset as _);
- to = serialize_const_struct(data_ptr, to, data);
- break;
- }
- i += 1;
- }
- to
- }
- /// Serialize a primitive type that is stored at the pointer passed in
- const fn serialize_const_primitive(
- ptr: *const (),
- mut to: ConstVec<u8>,
- layout: &PrimitiveLayout,
- ) -> ConstVec<u8> {
- let ptr = ptr as *const u8;
- let mut offset = 0;
- while offset < layout.size {
- // If the bytes are reversed, walk backwards from the end of the number when pushing bytes
- if cfg!(any(target_endian = "big", feature = "test-big-endian")) {
- to = to.push(unsafe {
- ptr.wrapping_byte_offset((layout.size - offset - 1) as _)
- .read()
- });
- } else {
- to = to.push(unsafe { ptr.wrapping_byte_offset(offset as _).read() });
- }
- offset += 1;
- }
- to
- }
- /// Serialize a constant sized array that is stored at the pointer passed in
- const fn serialize_const_list(
- ptr: *const (),
- mut to: ConstVec<u8>,
- layout: &ListLayout,
- ) -> ConstVec<u8> {
- let len = layout.len;
- let mut i = 0;
- while i < len {
- let field = ptr.wrapping_byte_offset((i * layout.item_layout.size()) as _);
- to = serialize_const_ptr(field, to, layout.item_layout);
- i += 1;
- }
- to
- }
- /// Serialize a pointer to a type that is stored at the pointer passed in
- const fn serialize_const_ptr(ptr: *const (), to: ConstVec<u8>, layout: &Layout) -> ConstVec<u8> {
- match layout {
- Layout::Enum(layout) => serialize_const_enum(ptr, to, layout),
- Layout::Struct(layout) => serialize_const_struct(ptr, to, layout),
- Layout::List(layout) => serialize_const_list(ptr, to, layout),
- Layout::Primitive(layout) => serialize_const_primitive(ptr, to, layout),
- }
- }
- /// Serialize a type into a buffer
- ///
- /// # Example
- ///
- /// ```rust
- /// use const_serialize::{ConstVec, SerializeConst, serialize_const};
- ///
- /// #[derive(Clone, Copy, Debug, PartialEq, SerializeConst)]
- /// struct Struct {
- /// a: u32,
- /// b: u8,
- /// c: u32,
- /// }
- ///
- /// let mut buffer = ConstVec::new();
- /// buffer = serialize_const(&Struct {
- /// a: 0x11111111,
- /// b: 0x22,
- /// c: 0x33333333,
- /// }, buffer);
- /// let buf = buffer.read();
- /// assert_eq!(buf.as_ref(), &[0x11, 0x11, 0x11, 0x11, 0x22, 0x33, 0x33, 0x33, 0x33]);
- /// ```
- #[must_use = "The data is serialized into the returned buffer"]
- pub const fn serialize_const<T: SerializeConst>(data: &T, to: ConstVec<u8>) -> ConstVec<u8> {
- let ptr = data as *const T as *const ();
- serialize_const_ptr(ptr, to, &T::MEMORY_LAYOUT)
- }
- /// Deserialize a primitive type into the out buffer at the offset passed in. Returns a new version of the buffer with the data added.
- const fn deserialize_const_primitive<'a, const N: usize>(
- mut from: ConstReadBuffer<'a>,
- layout: &PrimitiveLayout,
- out: (usize, [MaybeUninit<u8>; N]),
- ) -> Option<(ConstReadBuffer<'a>, [MaybeUninit<u8>; N])> {
- let (start, mut out) = out;
- let mut offset = 0;
- while offset < layout.size {
- // If the bytes are reversed, walk backwards from the end of the number when filling in bytes
- let (from_new, value) = match from.get() {
- Some(data) => data,
- None => return None,
- };
- from = from_new;
- if cfg!(any(target_endian = "big", feature = "test-big-endian")) {
- out[start + layout.size - offset - 1] = MaybeUninit::new(value);
- } else {
- out[start + offset] = MaybeUninit::new(value);
- }
- offset += 1;
- }
- Some((from, out))
- }
- /// Deserialize a struct type into the out buffer at the offset passed in. Returns a new version of the buffer with the data added.
- const fn deserialize_const_struct<'a, const N: usize>(
- mut from: ConstReadBuffer<'a>,
- layout: &StructLayout,
- out: (usize, [MaybeUninit<u8>; N]),
- ) -> Option<(ConstReadBuffer<'a>, [MaybeUninit<u8>; N])> {
- let (start, mut out) = out;
- let mut i = 0;
- while i < layout.data.len() {
- // Deserialize the field at the offset pointer in the struct
- let StructFieldLayout { offset, layout } = &layout.data[i];
- let (new_from, new_out) = match deserialize_const_ptr(from, layout, (start + *offset, out))
- {
- Some(data) => data,
- None => return None,
- };
- from = new_from;
- out = new_out;
- i += 1;
- }
- Some((from, out))
- }
- /// Deserialize an enum type into the out buffer at the offset passed in. Returns a new version of the buffer with the data added.
- const fn deserialize_const_enum<'a, const N: usize>(
- mut from: ConstReadBuffer<'a>,
- layout: &EnumLayout,
- out: (usize, [MaybeUninit<u8>; N]),
- ) -> Option<(ConstReadBuffer<'a>, [MaybeUninit<u8>; N])> {
- let (start, mut out) = out;
- let mut discriminant = 0;
- // First, deserialize the discriminant
- let mut offset = 0;
- while offset < layout.discriminant.size {
- // If the bytes are reversed, walk backwards from the end of the number when filling in bytes
- let (from_new, value) = match from.get() {
- Some(data) => data,
- None => return None,
- };
- from = from_new;
- if cfg!(target_endian = "big") {
- out[start + layout.size - offset - 1] = MaybeUninit::new(value);
- discriminant |= (value as u32) << ((layout.discriminant.size - offset - 1) * 8);
- } else {
- out[start + offset] = MaybeUninit::new(value);
- discriminant |= (value as u32) << (offset * 8);
- }
- offset += 1;
- }
- // Then, deserialize the variant
- let mut i = 0;
- let mut matched_variant = false;
- while i < layout.variants.len() {
- // If the variant is the discriminated one, deserialize it
- let EnumVariant { tag, data, .. } = &layout.variants[i];
- if discriminant == *tag {
- let offset = layout.variants_offset;
- let (new_from, new_out) =
- match deserialize_const_struct(from, data, (start + offset, out)) {
- Some(data) => data,
- None => return None,
- };
- from = new_from;
- out = new_out;
- matched_variant = true;
- break;
- }
- i += 1;
- }
- if !matched_variant {
- return None;
- }
- Some((from, out))
- }
- /// Deserialize a list type into the out buffer at the offset passed in. Returns a new version of the buffer with the data added.
- const fn deserialize_const_list<'a, const N: usize>(
- mut from: ConstReadBuffer<'a>,
- layout: &ListLayout,
- out: (usize, [MaybeUninit<u8>; N]),
- ) -> Option<(ConstReadBuffer<'a>, [MaybeUninit<u8>; N])> {
- let (start, mut out) = out;
- let len = layout.len;
- let item_layout = layout.item_layout;
- let mut i = 0;
- while i < len {
- let (new_from, new_out) =
- match deserialize_const_ptr(from, item_layout, (start + i * item_layout.size(), out)) {
- Some(data) => data,
- None => return None,
- };
- from = new_from;
- out = new_out;
- i += 1;
- }
- Some((from, out))
- }
- /// Deserialize a type into the out buffer at the offset passed in. Returns a new version of the buffer with the data added.
- const fn deserialize_const_ptr<'a, const N: usize>(
- from: ConstReadBuffer<'a>,
- layout: &Layout,
- out: (usize, [MaybeUninit<u8>; N]),
- ) -> Option<(ConstReadBuffer<'a>, [MaybeUninit<u8>; N])> {
- match layout {
- Layout::Enum(layout) => deserialize_const_enum(from, layout, out),
- Layout::Struct(layout) => deserialize_const_struct(from, layout, out),
- Layout::List(layout) => deserialize_const_list(from, layout, out),
- Layout::Primitive(layout) => deserialize_const_primitive(from, layout, out),
- }
- }
- /// Deserialize a type into the output buffer. Accepts (Type, ConstVec<u8>) as input and returns Option<(ConstReadBuffer, Instance of type)>
- ///
- /// # Example
- /// ```rust
- /// # use const_serialize::{deserialize_const, serialize_const, ConstVec, SerializeConst};
- /// #[derive(Clone, Copy, Debug, PartialEq, SerializeConst)]
- /// struct Struct {
- /// a: u32,
- /// b: u8,
- /// c: u32,
- /// d: u32,
- /// }
- ///
- /// let mut buffer = ConstVec::new();
- /// buffer = serialize_const(&Struct {
- /// a: 0x11111111,
- /// b: 0x22,
- /// c: 0x33333333,
- /// d: 0x44444444,
- /// }, buffer);
- /// let buf = buffer.read();
- /// assert_eq!(deserialize_const!(Struct, buf).unwrap().1, Struct {
- /// a: 0x11111111,
- /// b: 0x22,
- /// c: 0x33333333,
- /// d: 0x44444444,
- /// });
- /// ```
- #[macro_export]
- macro_rules! deserialize_const {
- ($type:ty, $buffer:expr) => {
- unsafe {
- const __SIZE: usize = std::mem::size_of::<$type>();
- $crate::deserialize_const_raw::<__SIZE, $type>($buffer)
- }
- };
- }
- /// Deserialize a buffer into a type. This will return None if the buffer doesn't have enough data to fill the type.
- /// # Safety
- /// N must be `std::mem::size_of::<T>()`
- #[must_use = "The data is deserialized from the input buffer"]
- pub const unsafe fn deserialize_const_raw<const N: usize, T: SerializeConst>(
- from: ConstReadBuffer,
- ) -> Option<(ConstReadBuffer, T)> {
- // Create uninitized memory with the size of the type
- let out = [MaybeUninit::uninit(); N];
- // Fill in the bytes into the buffer for the type
- let (from, out) = match deserialize_const_ptr(from, &T::MEMORY_LAYOUT, (0, out)) {
- Some(data) => data,
- None => return None,
- };
- // Now that the memory is filled in, transmute it into the type
- Some((from, unsafe {
- std::mem::transmute_copy::<[MaybeUninit<u8>; N], T>(&out)
- }))
- }
- /// Check if the serialized representation of two items are the same
- pub const fn serialize_eq<T: SerializeConst>(first: &T, second: &T) -> bool {
- let first_serialized = ConstVec::<u8>::new();
- let first_serialized = serialize_const(first, first_serialized);
- let second_serialized = ConstVec::<u8>::new();
- let second_serialized = serialize_const(second, second_serialized);
- let first_buf = first_serialized.as_ref();
- let second_buf = second_serialized.as_ref();
- if first_buf.len() != second_buf.len() {
- return false;
- }
- let mut i = 0;
- while i < first_buf.len() {
- if first_buf[i] != second_buf[i] {
- return false;
- }
- i += 1;
- }
- true
- }
|