use std::ops::{Deref, DerefMut};
use std::str::{from_utf8_unchecked, from_utf8_unchecked_mut};
macro_rules! prefix_str {
( ($n:tt, $p:tt), $(($name:tt, $prefix:tt)),+ ) => {
prefix_str!(($n, $p));
prefix_str!($( ($name, $prefix) ),+);
};
( ($name:tt, $prefix_type:tt) ) => {
pub struct $name<'a> {
value: &'a [u8],
}
impl<'a> $name<'a> {
pub fn from_bytes(bytes: &'a [u8]) -> Self {
let (length, value) = bytes.split_at(std::mem::size_of::<$prefix_type>());
let length = bytemuck::from_bytes::<$prefix_type>(length);
let value = bytemuck::cast_slice(&value[..*length as usize]);
Self { value }
}
}
};
}
macro_rules! prefix_str_mut {
( ($n:tt, $p:tt), $(($name:tt, $prefix:tt)),+ ) => {
prefix_str_mut!(($n, $p));
prefix_str_mut!($( ($name, $prefix) ),+);
};
( ($name:tt, $prefix_type:tt) ) => {
pub struct $name<'a> {
value: &'a mut [u8],
}
impl<'a> $name<'a> {
pub fn new(data: &'a mut [u8]) -> Self {
let type_length = std::mem::size_of::<$prefix_type>();
let length = (data.len().saturating_sub(type_length) as $prefix_type).to_le_bytes();
data[..type_length].copy_from_slice(&length);
Self::from_bytes_mut(data)
}
pub fn from_bytes_mut(bytes: &'a mut [u8]) -> Self {
let (length, value) = bytes.split_at_mut(std::mem::size_of::<$prefix_type>());
let length = bytemuck::from_bytes_mut::<$prefix_type>(length);
let value = bytemuck::cast_slice_mut(&mut value[..*length as usize]);
Self { value }
}
pub fn copy_from_slice(&mut self, slice: &[u8]) {
let length = std::cmp::min(self.value.len(), slice.len());
self.value[..length].clone_from_slice(&slice[..length]);
self.value[length..].fill(0);
}
pub fn copy_from_str(&mut self, string: &str) {
self.copy_from_slice(string.as_bytes())
}
}
impl<'a> DerefMut for $name<'a> {
#[inline]
fn deref_mut(&mut self) -> &mut str {
unsafe { from_utf8_unchecked_mut(self.value) }
}
}
};
}
macro_rules! prefix_str_type {
( ($n:tt, $p:tt), $(($name:tt, $prefix:tt)),+ ) => {
prefix_str_type!(($n, $p));
prefix_str_type!($( ($name, $prefix) ),+);
};
( ($name:tt, $prefix_type:tt) ) => {
impl<'a> $name<'a> {
pub fn as_str(&self) -> &str {
self
}
#[allow(clippy::len_without_is_empty)]
pub fn size(&self) -> usize {
std::mem::size_of::<$prefix_type>() + self.value.len()
}
}
impl<'a> Deref for $name<'a> {
type Target = str;
#[inline]
fn deref(&self) -> &str {
unsafe { from_utf8_unchecked(self.value) }
}
}
};
}
prefix_str!((U8PrefixStr, u8), (U16PrefixStr, u16));
prefix_str_mut!((U8PrefixStrMut, u8), (U16PrefixStrMut, u16));
prefix_str_type!(
(U8PrefixStr, u8),
(U8PrefixStrMut, u8),
(U16PrefixStr, u16),
(U16PrefixStrMut, u16)
);
#[cfg(test)]
mod tests {
use crate::types::{U16PrefixStr, U16PrefixStrMut, U8PrefixStr, U8PrefixStrMut};
#[test]
fn test_new() {
let mut data = [0u8; 4];
let mut prefix_str = U8PrefixStrMut::new(&mut data);
prefix_str.copy_from_str("str");
assert_eq!(prefix_str.as_str(), "str");
assert_eq!(prefix_str.size(), data.len());
#[repr(C, align(2))]
struct Aligned([u8; 5]);
let mut data = Aligned([0u8; 5]);
let mut prefix_str = U16PrefixStrMut::new(&mut data.0);
prefix_str.copy_from_str("str");
assert_eq!(prefix_str.as_str(), "str");
assert_eq!(prefix_str.size(), data.0.len());
}
#[test]
fn test_new_with_shorter_str() {
let mut data = [0u8; 10];
let mut prefix_str = U8PrefixStrMut::new(&mut data);
prefix_str.copy_from_str("string");
assert_eq!(prefix_str.as_str(), "string\0\0\0");
#[repr(C, align(2))]
struct Aligned([u8; 11]);
let mut data = Aligned([0u8; 11]);
let mut prefix_str = U16PrefixStrMut::new(&mut data.0);
prefix_str.copy_from_str("string");
assert_eq!(prefix_str.as_str(), "string\0\0\0");
}
#[test]
fn test_new_with_larger_str() {
let mut data = [0u8; 4];
let mut prefix_str = U8PrefixStrMut::new(&mut data);
prefix_str.copy_from_str("string");
assert_eq!(prefix_str.as_str(), "str");
#[repr(C, align(2))]
struct Aligned([u8; 5]);
let mut data = Aligned([0u8; 5]);
let mut prefix_str = U16PrefixStrMut::new(&mut data.0);
prefix_str.copy_from_str("string");
assert_eq!(prefix_str.as_str(), "str");
}
#[test]
fn test_from_bytes() {
let mut data = [0u8; 4];
data[0] = 3;
data[1..].copy_from_slice("str".as_bytes());
let prefix_str = U8PrefixStr::from_bytes(&data);
assert_eq!(prefix_str.as_str(), "str");
let mut data = [0u8; 5];
data[..2].copy_from_slice(&3u16.to_le_bytes());
data[2..].copy_from_slice("str".as_bytes());
let prefix_str = U16PrefixStr::from_bytes(&data);
assert_eq!(prefix_str.as_str(), "str");
}
}