Add 'into_bytes' destructor to 'SizedStr'; Clean up code; Update and add more tests; Manually implement '<SizedIter as Iterator>::nth'; Implement 'Encode' and 'Decode' for 'CString', 'SystemTime', 'Duration'; Implement 'Encode' for 'CStr'; Update docs; Fix includes in '/bzipper/src/decode/mod.rs' and '/bzipper/src/sized_encode/mod.rs'; Add new 'NullCString' and 'NarrowSystemTime' error variants to 'DecodeError'; Optimise '<String as Decode>::decode'; Update lints; Implement 'SizedEncode' for 'SystemTime' and 'Duration'; Update benchmark stats; Update readme;

This commit is contained in:
Gabriel Bjørnager Jensen 2024-11-03 21:52:22 +01:00
parent b7a72dc0f9
commit 74b8d2caa2
17 changed files with 366 additions and 78 deletions

View file

@ -3,6 +3,23 @@
This is the changelog of bzipper. This is the changelog of bzipper.
See `README.md` for more information. See `README.md` for more information.
## 0.11.0
* Add `into_bytes` destructor to `SizedStr`
* Clean up code
* Update and add more tests
* Manually implement `<SizedIter as Iterator>::nth`
* Implement `Encode` and `Decode` for `CString`, `SystemTime`, `Duration`
* Implement `Encode` for `CStr`
* Update docs
* Fix includes in `/bzipper/src/decode/mod.rs` and `/bzipper/src/sized_encode/mod.rs`
* Add new `NullCString` and `NarrowSystemTime` error variants to `DecodeError`
* Optimise `<String as Decode>::decode`
* Update lints
* Implement `SizedEncode` for `SystemTime` and `Duration`
* Update benchmark stats
* Update readme
## 0.10.1 ## 0.10.1
* Clean up and refactor code * Clean up and refactor code

View file

@ -82,6 +82,7 @@ match_bool = "warn"
match_on_vec_items = "warn" match_on_vec_items = "warn"
match_same_arms = "warn" match_same_arms = "warn"
mismatching_type_param_order = "warn" mismatching_type_param_order = "warn"
missing_transmute_annotations = "forbid"
mixed_read_write_in_expression = "deny" mixed_read_write_in_expression = "deny"
mut_mut = "deny" mut_mut = "deny"
mutex_atomic = "deny" mutex_atomic = "deny"

View file

@ -20,13 +20,13 @@ According to my runs on an AMD Ryzen 7 3700X, these benchmarks indicate that bZi
| Benchmark | [Bincode] | [Borsh] | bZipper | [Ciborium] | [Postcard] | | Benchmark | [Bincode] | [Borsh] | bZipper | [Ciborium] | [Postcard] |
| :--------------------------------- | --------: | ------: | ------: | ---------: | ---------: | | :--------------------------------- | --------: | ------: | ------: | ---------: | ---------: |
| `encode_u8` | 1.262 | 1.271 | 1.153 | 2.854 | 1.270 | | `encode_u8` | 1.234 | 1.096 | 0.881 | 3.076 | 1.223 |
| `encode_struct_unit` | 0.000 | 0.000 | 0.000 | 0.447 | 0.000 | | `encode_struct_unit` | 0.000 | 0.000 | 0.000 | 0.516 | 0.000 |
| `encode_struct_unnamed` | 1.270 | 1.102 | 0.998 | 1.948 | 1.182 | | `encode_struct_unnamed` | 1.367 | 1.154 | 1.009 | 2.051 | 1.191 |
| `encode_struct_named` | 4.205 | 1.186 | 1.136 | 10.395 | 1.168 | | `encode_struct_named` | 4.101 | 1.271 | 1.181 | 9.342 | 1.182 |
| `encode_enum_unit` | 0.328 | 0.008 | 0.000 | 2.293 | 0.004 | | `encode_enum_unit` | 0.306 | 0.008 | 0.000 | 2.304 | 0.004 |
| **Total time** &#8594; | 7.065 | 3.567 | 3.286 | 17.937 | 3.625 | | **Total time** &#8594; | 7.009 | 3.528 | 3.071 | 17.289 | 3.599 |
| **Total deviation (p.c.)** &#8594; | +115 | +9 | ±0 | +446 | +10 | | **Total deviation (p.c.)** &#8594; | +128 | +15 | ±0 | +463 | +17 |
[Bincode]: https://crates.io/crates/bincode/ [Bincode]: https://crates.io/crates/bincode/
[Borsh]: https://crates.io/crates/borsh/ [Borsh]: https://crates.io/crates/borsh/

View file

@ -1,6 +1,6 @@
[package] [package]
name = "bzipper" name = "bzipper"
version = "0.10.1" version = "0.11.0"
edition = "2021" edition = "2021"
rust-version = "1.83" rust-version = "1.83"
documentation = "https://docs.rs/bzipper/" documentation = "https://docs.rs/bzipper/"
@ -24,7 +24,7 @@ alloc = []
std = [] std = []
[dependencies] [dependencies]
bzipper_macros = { path = "../bzipper_macros", version = "0.10.1" } bzipper_macros = { path = "../bzipper_macros", version = "0.11.0" }
[lints] [lints]
workspace = true workspace = true

View file

@ -48,12 +48,18 @@ use core::ops::{
RangeTo, RangeTo,
RangeToInclusive, RangeToInclusive,
}; };
use core::ptr::copy_nonoverlapping;
use core::str;
use core::time::Duration;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use std::boxed::Box; use alloc::boxed::Box;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use std::collections::LinkedList; use alloc::collections::LinkedList;
#[cfg(feature = "alloc")]
use alloc::ffi::CString;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use alloc::string::String; use alloc::string::String;
@ -76,6 +82,9 @@ use std::hash::BuildHasher;
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::sync::{Mutex, RwLock}; use std::sync::{Mutex, RwLock};
#[cfg(feature = "std")]
use std::time::{SystemTime, UNIX_EPOCH};
// Should we require `Encode` for `Decode`? // Should we require `Encode` for `Decode`?
/// Denotes a type capable of being decoded. /// Denotes a type capable of being decoded.
@ -96,7 +105,6 @@ where
#[inline(always)] #[inline(always)]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
let value = (Decode::decode(stream)?, ); let value = (Decode::decode(stream)?, );
Ok(value) Ok(value)
} }
} }
@ -106,6 +114,7 @@ impl<T: Decode, const N: usize> Decode for [T; N] {
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
// Initialise the array incrementally. // Initialise the array incrementally.
// SAFETY: Always safe.
let mut buf: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() }; let mut buf: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() };
for item in &mut buf { for item in &mut buf {
@ -207,6 +216,46 @@ impl Decode for char {
} }
} }
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl Decode for CString {
#[inline(always)]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
let len = Decode::decode(stream)?;
let data = stream.read(len);
for (i, c) in data.iter().enumerate() {
if *c == b'\x00' { return Err(DecodeError::NullCString { index: i }) };
}
let mut buf = Vec::with_capacity(len);
unsafe {
let src = data.as_ptr();
let dst = buf.as_mut_ptr();
copy_nonoverlapping(src, dst, len);
buf.set_len(len);
}
// SAFETY: We have already tested the data.
let this = unsafe { Self::from_vec_unchecked(buf) };
Ok(this)
}
}
impl Decode for Duration {
#[inline(always)]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
let secs = Decode::decode(stream)?;
let nanos = Decode::decode(stream)?;
let this = Self::new(secs, nanos);
Ok(this)
}
}
#[cfg(feature = "std")] #[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))] #[cfg_attr(doc, doc(cfg(feature = "std")))]
impl<K, V, S> Decode for HashMap<K, V, S> impl<K, V, S> Decode for HashMap<K, V, S>
@ -283,7 +332,6 @@ impl Decode for Ipv4Addr {
#[inline(always)] #[inline(always)]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
let value = Decode::decode(stream)?; let value = Decode::decode(stream)?;
Ok(Self::from_bits(value)) Ok(Self::from_bits(value))
} }
} }
@ -292,7 +340,6 @@ impl Decode for Ipv6Addr {
#[inline(always)] #[inline(always)]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
let value = Decode::decode(stream)?; let value = Decode::decode(stream)?;
Ok(Self::from_bits(value)) Ok(Self::from_bits(value))
} }
} }
@ -512,15 +559,52 @@ impl Decode for SocketAddrV6 {
impl Decode for String { impl Decode for String {
#[inline(always)] #[inline(always)]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
let data = <Vec::<u8>>::decode(stream)?; let len = Decode::decode(stream)?;
Self::from_utf8(data) let data = stream.read(len);
str::from_utf8(data)
.map_err(|e| { .map_err(|e| {
let data = e.as_bytes(); let i = e.valid_up_to();
let i = e.utf8_error().valid_up_to(); let c = data[i];
DecodeError::BadString(Utf8Error { value: data[i], index: i }) DecodeError::BadString(Utf8Error { value: c, index: i })
}) })?;
let mut v = Vec::with_capacity(len);
unsafe {
let src = data.as_ptr();
let dst = v.as_mut_ptr();
copy_nonoverlapping(src, dst, len);
v.set_len(len);
}
// SAFETY: We have already tested the raw data.
let this = unsafe { Self::from_utf8_unchecked(v) };
Ok(this)
}
}
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl Decode for SystemTime {
#[inline]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
let time = i64::decode(stream)?;
let this = if time.is_positive() {
let time = time as u64;
UNIX_EPOCH.checked_add(Duration::from_secs(time))
} else {
let time = time.unsigned_abs();
UNIX_EPOCH.checked_sub(Duration::from_secs(time))
};
this.ok_or_else(|| DecodeError::NarrowSystemTime { timestamp: time })
} }
} }

View file

@ -20,6 +20,7 @@
// not, see <https://www.gnu.org/licenses/>. // not, see <https://www.gnu.org/licenses/>.
use alloc::vec::Vec; use alloc::vec::Vec;
use alloc::string::String;
use bzipper::{Decode, IStream, SizedEncode}; use bzipper::{Decode, IStream, SizedEncode};
use core::char; use core::char;
@ -118,5 +119,7 @@ fn test_decode() {
0xC8, 0x4C, 0xC8, 0x4C,
] => UnitOrFields::Named { timestamp: 1724237900 }); ] => UnitOrFields::Named { timestamp: 1724237900 });
test!(Vec<u16>: [0x00, 0x02, 0xFF, 0xEE, 0xDD, 0xCC] => [0xFF_EE, 0xDD_CC].as_slice()); test!(Vec<u16>: [0x00, 0x02, 0xAA, 0xBB, 0xCC, 0xDD] => [0xAA_BB, 0xCC_DD].as_slice());
test!(String: [0x00, 0x06, 0xE6, 0x97, 0xA5, 0xE6, 0x9C, 0xAC] => "\u{65E5}\u{672C}");
} }

View file

@ -27,6 +27,7 @@ use crate::error::EncodeError;
use core::cell::{Cell, LazyCell, RefCell}; use core::cell::{Cell, LazyCell, RefCell};
use core::convert::Infallible; use core::convert::Infallible;
use core::ffi::CStr;
use core::hash::BuildHasher; use core::hash::BuildHasher;
use core::hint::unreachable_unchecked; use core::hint::unreachable_unchecked;
use core::marker::{PhantomData, PhantomPinned}; use core::marker::{PhantomData, PhantomPinned};
@ -48,6 +49,7 @@ use core::ops::{
RangeTo, RangeTo,
RangeToInclusive, RangeToInclusive,
}; };
use core::time::Duration;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use alloc::borrow::{Cow, ToOwned}; use alloc::borrow::{Cow, ToOwned};
@ -58,6 +60,9 @@ use alloc::boxed::Box;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use alloc::collections::LinkedList; use alloc::collections::LinkedList;
#[cfg(feature = "alloc")]
use alloc::ffi::CString;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use alloc::string::String; use alloc::string::String;
@ -76,6 +81,9 @@ use std::collections::{HashMap, HashSet};
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::sync::{LazyLock, Mutex, RwLock}; use std::sync::{LazyLock, Mutex, RwLock};
#[cfg(feature = "std")]
use std::time::{SystemTime, UNIX_EPOCH};
/// Denotes a type capable of being encoded. /// Denotes a type capable of being encoded.
/// ///
/// It is recommended to simply derive this trait for custom types. /// It is recommended to simply derive this trait for custom types.
@ -148,6 +156,8 @@ impl<T: Encode> Encode for (T, ) {
} }
impl<T: Encode, const N: usize> Encode for [T; N] { impl<T: Encode, const N: usize> Encode for [T; N] {
/// Encodes each element sequentially.
/// The length is hard-coded into the type and is therefore not encoded.
#[inline(always)] #[inline(always)]
fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> {
for value in self { for value in self {
@ -159,6 +169,7 @@ impl<T: Encode, const N: usize> Encode for [T; N] {
} }
impl<T: Encode> Encode for [T] { impl<T: Encode> Encode for [T] {
/// Encodes each element sequentially with an extra length specifier (of type [`usize`]) prepended first.
#[inline(always)] #[inline(always)]
fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> {
self.len().encode(stream)?; self.len().encode(stream)?;
@ -242,6 +253,35 @@ impl<T: Encode + ToOwned> Encode for Cow<'_, T> {
} }
} }
impl Encode for CStr {
/// Encodes the string identically to [a byte slice](slice) containing the string's byte values **excluding** the null terminator.
#[inline(always)]
fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> {
self.to_bytes().encode(stream)
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl Encode for CString {
/// See the the implementation of [`CStr`].
#[inline(always)]
fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> {
self.as_c_str().encode(stream)
}
}
impl Encode for Duration {
/// Encodes the duration's seconds and nanoseconds counters sequentially.
#[inline(always)]
fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> {
self.as_secs().encode(stream)?;
self.subsec_nanos().encode(stream)?;
Ok(())
}
}
#[cfg(feature = "std")] #[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))] #[cfg_attr(doc, doc(cfg(feature = "std")))]
impl<K, V, S> Encode for HashMap<K, V, S> impl<K, V, S> Encode for HashMap<K, V, S>
@ -289,9 +329,10 @@ impl Encode for Infallible {
} }
} }
/// This implementation encoded as discriminant denoting the IP version of the address (i.e. `4` for IPv4 and `6` for IPv6).
/// This is then followed by the respective address' own encoding (either [`Ipv4Addr`] or [`Ipv6Addr`]).
impl Encode for IpAddr { impl Encode for IpAddr {
/// Encodes a the address with a preceding discriminant denoting the IP version of the address (i.e. `4` for IPv4 and `6` for IPv6).
///
/// See also the implementations of [`Ipv4Addr`] and [`Ipv6Addr`].
#[inline(always)] #[inline(always)]
fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> {
// The discriminant here is the IP version. // The discriminant here is the IP version.
@ -312,8 +353,8 @@ impl Encode for IpAddr {
} }
} }
/// This implementation encodes the address's bits in big-endian.
impl Encode for Ipv4Addr { impl Encode for Ipv4Addr {
/// Encodes the address's bits in big-endian.
#[inline(always)] #[inline(always)]
fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> {
let value = self.to_bits(); let value = self.to_bits();
@ -321,8 +362,8 @@ impl Encode for Ipv4Addr {
} }
} }
/// This implementation encodes the address's bits in big-endian.
impl Encode for Ipv6Addr { impl Encode for Ipv6Addr {
/// Encodes the address's bits in big-endian.
#[inline(always)] #[inline(always)]
fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> {
let value = self.to_bits(); let value = self.to_bits();
@ -330,9 +371,10 @@ impl Encode for Ipv6Addr {
} }
} }
/// This implementation casts `self` to `i16` before encoding.
/// If this conversion isn't possible for the given value, then the [`IsizeOutOfRange`](EncodeError::IsizeOutOfRange) error is returned.
impl Encode for isize { impl Encode for isize {
/// Casts `self` to [`i16`] and encodes.
///
/// If this conversion isn't possible for the given value, then the [`IsizeOutOfRange`](EncodeError::IsizeOutOfRange) error is returned.
#[inline] #[inline]
fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> {
let value = i16::try_from(*self) let value = i16::try_from(*self)
@ -383,10 +425,11 @@ impl<T: Encode> Encode for Mutex<T> {
} }
} }
/// This implementation encodes a sign denoting the optional's variant.
/// The sign is `false` for `None` instances and `true` for `Some` instances.
/// The contained value is encoded proceeding the sign.
impl<T: Encode> Encode for Option<T> { impl<T: Encode> Encode for Option<T> {
/// Encodes a sign denoting the optional's variant.
/// This is `false` for `None` instances and `true` for `Some` instances.
///
/// If `Some`, then the contained value is encoded after this sign..
fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> {
match *self { match *self {
None => false.encode(stream)?, None => false.encode(stream)?,
@ -485,10 +528,12 @@ impl<T: Encode> Encode for RefCell<T> {
} }
} }
/// This implementation encodes a sign denoting the optional's variant.
/// The sign is `false` for denoting `Ok` and `true` for denoting `Err`.
/// The contained value is encoded proceeding the sign.
impl<T: Encode, E: Encode> Encode for core::result::Result<T, E> { impl<T: Encode, E: Encode> Encode for core::result::Result<T, E> {
/// Encodes a sign denoting the result's variant.
/// This is `false` for `Ok` instances and `true` for `Err` instances.
///
/// If `Ok`, then the contained value is encoded after this sign.
#[inline]
fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> {
// The sign here is `false` for `Ok` objects and // The sign here is `false` for `Ok` objects and
// `true` for `Err` objects. // `true` for `Err` objects.
@ -528,9 +573,9 @@ impl<T: Encode> Encode for Saturating<T> {
} }
} }
/// This implementation encoded as discriminant denoting the IP version of the address (i.e. `4` for IPv4 and `6` for IPv6).
/// This is then followed by the respective address' own encoding (either [`SocketAddrV4`] or [`SocketAddrV6`]).
impl Encode for SocketAddr { impl Encode for SocketAddr {
/// This implementation encoded as discriminant denoting the IP version of the address (i.e. `4` for IPv4 and `6` for IPv6).
/// This is then followed by the respective address' own encoding (either [`SocketAddrV4`] or [`SocketAddrV6`]).
#[inline] #[inline]
fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> {
// The discriminant here is the IP version. // The discriminant here is the IP version.
@ -551,8 +596,8 @@ impl Encode for SocketAddr {
} }
} }
/// This implementation encodes the address's bits followed by the port number, all of which in big-endian.
impl Encode for SocketAddrV4 { impl Encode for SocketAddrV4 {
/// Encodes the address's bits followed by the port number, both of which in big-endian.
#[inline(always)] #[inline(always)]
fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> {
self.ip().encode(stream)?; self.ip().encode(stream)?;
@ -562,8 +607,8 @@ impl Encode for SocketAddrV4 {
} }
} }
/// This implementation encodes the address's bits followed by the port number, all of which in big-endian.
impl Encode for SocketAddrV6 { impl Encode for SocketAddrV6 {
/// Encodes the address's bits followed by the port number, flow information, and scope identifier -- all of which in big-endian.
#[inline(always)] #[inline(always)]
fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> {
self.ip().encode(stream)?; self.ip().encode(stream)?;
@ -576,6 +621,7 @@ impl Encode for SocketAddrV6 {
} }
impl Encode for str { impl Encode for str {
/// Encodes the string identically to [a byte slice](slice) containing the string's byte values.
#[inline(always)] #[inline(always)]
fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> {
// Optimised encode. Don't just rely on `[char]`. // Optimised encode. Don't just rely on `[char]`.
@ -590,12 +636,47 @@ impl Encode for str {
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))] #[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl Encode for String { impl Encode for String {
/// See [`str`].
#[inline(always)] #[inline(always)]
fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> {
self.as_str().encode(stream) self.as_str().encode(stream)
} }
} }
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl Encode for SystemTime {
/// Encodes the time point as the nearest, signed UNIX timestamp.
///
/// Examples of some timestamps and their encodings include:
///
/// | ISO 8601 | UNIX / bZipper |
/// | :-------------------------- | -------------: |
/// | `2024-11-03T12:02:01+01:00` | +1730631721 |
/// | `1989-06-03T20:00:00+09:00` | +13258800 |
/// | `1970-01-01T00:00:00Z` | +0 |
/// | `1945-05-04T18:30:00+02:00` | -778231800 |
#[expect(clippy::cast_possible_wrap)]
#[inline]
fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> {
let time = if *self >= UNIX_EPOCH {
let duration = self
.duration_since(UNIX_EPOCH)
.expect("cannot compute duration since the epoch");
duration.as_secs() as i64
} else {
let duration = UNIX_EPOCH
.duration_since(*self)
.expect("cannot compute duration until the epoch");
0x0 - duration.as_secs() as i64
};
time.encode(stream)
}
}
impl Encode for () { impl Encode for () {
#[inline(always)] #[inline(always)]
fn encode(&self, _stream: &mut OStream) -> Result<(), EncodeError> { fn encode(&self, _stream: &mut OStream) -> Result<(), EncodeError> {
@ -603,9 +684,11 @@ impl Encode for () {
} }
} }
/// This implementation casts `self` to `u16` before encoding.
/// If this conversion isn't possible for the given value, then the [`IsizeOutOfRange`](EncodeError::IsizeOutOfRange) error is returned.
impl Encode for usize { impl Encode for usize {
/// Casts `self` to [`u16`] and encodes.
///
/// If this conversion isn't possible for the given value, then the [`IsizeOutOfRange`](EncodeError::UsizeOutOfRange) error is returned.
#[inline]
fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> {
let value = u16::try_from(*self) let value = u16::try_from(*self)
.map_err(|_| EncodeError::UsizeOutOfRange(*self))?; .map_err(|_| EncodeError::UsizeOutOfRange(*self))?;
@ -680,11 +763,12 @@ macro_rules! impl_atomic {
ty: $ty:ty, ty: $ty:ty,
atomic_ty: $atomic_ty:ty$(,)? atomic_ty: $atomic_ty:ty$(,)?
} => { } => {
/// This implementation uses the same format as the atomic's primitive counterpart.
/// The atomic object itself is read with the [`Relaxed`](core::sync::atomic::Ordering) ordering scheme.
#[cfg(target_has_atomic = $width)] #[cfg(target_has_atomic = $width)]
#[cfg_attr(doc, doc(cfg(target_has_atomic = $width)))] #[cfg_attr(doc, doc(cfg(target_has_atomic = $width)))]
impl ::bzipper::Encode for $atomic_ty { impl ::bzipper::Encode for $atomic_ty {
/// Encodes the atomic with the same scheme as that of the atomic type's primitive counterpart.
///
/// The atomic object itself is read with the [`Relaxed`](core::sync::atomic::Ordering) ordering scheme.
#[inline(always)] #[inline(always)]
fn encode(&self, stream: &mut ::bzipper::OStream) -> ::core::result::Result<(), ::bzipper::error::EncodeError> { fn encode(&self, stream: &mut ::bzipper::OStream) -> ::core::result::Result<(), ::bzipper::error::EncodeError> {
self.load(::std::sync::atomic::Ordering::Relaxed).encode(stream) self.load(::std::sync::atomic::Ordering::Relaxed).encode(stream)

View file

@ -18,6 +18,9 @@
// er General Public License along with bzipper. If // er General Public License along with bzipper. If
// not, see <https://www.gnu.org/licenses/>. // not, see <https://www.gnu.org/licenses/>.
use core::time::Duration;
use std::time::{SystemTime, UNIX_EPOCH};
use alloc::vec; use alloc::vec;
use alloc::vec::Vec; use alloc::vec::Vec;
use bzipper::{Encode, OStream, SizedEncode, SizedStr}; use bzipper::{Encode, OStream, SizedEncode, SizedStr};
@ -96,5 +99,11 @@ fn test_encode() {
0x00, 0x4C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4C, 0x00, 0x00, 0x00, 0x00,
]); ]);
test!(Vec<u8>: Vec::from([0xAA, 0xBB, 0xCC]) => [0x00, 0x03, 0xAA, 0xBB, 0xCC]); test!(Vec<u16>: From::from([0x8000, 0x8000, 0x8000]) => [0x00, 0x03, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00]);
test!(SystemTime: UNIX_EPOCH => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
test!(SystemTime: UNIX_EPOCH - Duration::from_secs(0x1) => [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]);
test!(SystemTime: UNIX_EPOCH + Duration::from_secs(0x1) => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]);
} }

View file

@ -56,9 +56,27 @@ pub enum DecodeError {
/// An invalid enumeration descriminant was provided. /// An invalid enumeration descriminant was provided.
InvalidDiscriminant(isize), InvalidDiscriminant(isize),
/// The [`SystemTime`](std::time::SystemTime) type could not represent a UNIX timestamp.
///
/// This error should not occur on systems that represent timestamps with at least a signed 64-bits seconds counter.
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
NarrowSystemTime {
/// The unrepresentable timestamp.
timestamp: i64,
},
/// A non-zero integer had the value `0`. /// A non-zero integer had the value `0`.
NullInteger, NullInteger,
/// A C-like string encountered a null value within bounds.
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
NullCString {
/// The index of the null value.
index: usize,
},
/// An array could not hold the requested amount of elements. /// An array could not hold the requested amount of elements.
SmallBuffer(SizeError), SmallBuffer(SizeError),
} }
@ -77,16 +95,24 @@ impl Display for DecodeError {
=> write!(f, "{source}"), => write!(f, "{source}"),
InvalidBoolean(value) InvalidBoolean(value)
=> write!(f, "expected boolean but got {value:#02X}"), => write!(f, "expected boolean but got `{value:#02X}`"),
InvalidCodePoint(value) InvalidCodePoint(value)
=> write!(f, "code point U+{value:04X} is not defined"), => write!(f, "code point U+{value:04X} is not defined"),
InvalidDiscriminant(value) InvalidDiscriminant(value)
=> write!(f, "discriminant ({value}) is not valid for the given enumeration"), => write!(f, "discriminant `{value}` is not valid for the given enumeration"),
#[cfg(feature = "std")]
NarrowSystemTime { timestamp }
=> write!(f, "could not represent `{timestamp}` as a system timestamp"),
NullInteger NullInteger
=> write!(f, "expected non-zero integer but got (0)"), => write!(f, "expected non-zero integer but got `0`"),
#[cfg(feature = "alloc")]
NullCString { index }
=> write!(f, "expected c string but found null value at '{index}`"),
SmallBuffer(ref source) SmallBuffer(ref source)
=> write!(f, "buffer too small: {source}"), => write!(f, "buffer too small: {source}"),

View file

@ -41,13 +41,13 @@
//! //!
//! | Benchmark | [Bincode] | [Borsh] | bZipper | [Ciborium] | [Postcard] | //! | Benchmark | [Bincode] | [Borsh] | bZipper | [Ciborium] | [Postcard] |
//! | :--------------------------------- | --------: | ------: | ------: | ---------: | ---------: | //! | :--------------------------------- | --------: | ------: | ------: | ---------: | ---------: |
//! | `encode_u8` | 1.262 | 1.271 | 1.153 | 2.854 | 1.270 | //! | `encode_u8` | 1.234 | 1.096 | 0.881 | 3.076 | 1.223 |
//! | `encode_struct_unit` | 0.000 | 0.000 | 0.000 | 0.447 | 0.000 | //! | `encode_struct_unit` | 0.000 | 0.000 | 0.000 | 0.516 | 0.000 |
//! | `encode_struct_unnamed` | 1.270 | 1.102 | 0.998 | 1.948 | 1.182 | //! | `encode_struct_unnamed` | 1.367 | 1.154 | 1.009 | 2.051 | 1.191 |
//! | `encode_struct_named` | 4.205 | 1.186 | 1.136 | 10.395 | 1.168 | //! | `encode_struct_named` | 4.101 | 1.271 | 1.181 | 9.342 | 1.182 |
//! | `encode_enum_unit` | 0.328 | 0.008 | 0.000 | 2.293 | 0.004 | //! | `encode_enum_unit` | 0.306 | 0.008 | 0.000 | 2.304 | 0.004 |
//! | **Total time** &#8594; | 7.065 | 3.567 | 3.286 | 17.937 | 3.625 | //! | **Total time** &#8594; | 7.009 | 3.528 | 3.071 | 17.289 | 3.599 |
//! | **Total deviation (p.c.)** &#8594; | +115 | +9 | ±0 | +446 | +10 | //! | **Total deviation (p.c.)** &#8594; | +128 | +15 | ±0 | +463 | +17 |
//! //!
//! [Bincode]: https://crates.io/crates/bincode/ //! [Bincode]: https://crates.io/crates/bincode/
//! [Borsh]: https://crates.io/crates/borsh/ //! [Borsh]: https://crates.io/crates/borsh/
@ -430,8 +430,8 @@ use_mod!(pub i_stream);
use_mod!(pub o_stream); use_mod!(pub o_stream);
use_mod!(pub sized_encode); use_mod!(pub sized_encode);
use_mod!(pub sized_iter); use_mod!(pub sized_iter);
use_mod!(pub sized_str);
use_mod!(pub sized_slice); use_mod!(pub sized_slice);
use_mod!(pub sized_str);
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use_mod!(pub buf); use_mod!(pub buf);

View file

@ -45,10 +45,10 @@ use core::ops::{
RangeTo, RangeTo,
RangeToInclusive, RangeToInclusive,
}; };
use std::borrow::ToOwned; use core::time::Duration;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use alloc::borrow::Cow; use alloc::borrow::{Cow, ToOwned};
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use alloc::boxed::Box; use alloc::boxed::Box;
@ -62,6 +62,9 @@ use alloc::sync::Arc;
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::sync::{LazyLock, Mutex, RwLock}; use std::sync::{LazyLock, Mutex, RwLock};
#[cfg(feature = "std")]
use std::time::SystemTime;
/// Denotes a size-constrained, encodable type. /// Denotes a size-constrained, encodable type.
/// ///
/// When using [`Encode`], the size of the resulting encoding cannot always be known beforehand. /// When using [`Encode`], the size of the resulting encoding cannot always be known beforehand.
@ -131,6 +134,12 @@ unsafe impl<T: SizedEncode + ToOwned> SizedEncode for Cow<'_, T> {
const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE;
} }
unsafe impl SizedEncode for Duration {
const MAX_ENCODED_SIZE: usize =
u64::MAX_ENCODED_SIZE
+ u32::MAX_ENCODED_SIZE;
}
unsafe impl SizedEncode for Infallible { unsafe impl SizedEncode for Infallible {
const MAX_ENCODED_SIZE: usize = 0x0; const MAX_ENCODED_SIZE: usize = 0x0;
} }
@ -244,6 +253,12 @@ unsafe impl SizedEncode for SocketAddrV6 {
+ u32::MAX_ENCODED_SIZE; + u32::MAX_ENCODED_SIZE;
} }
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
unsafe impl SizedEncode for SystemTime {
const MAX_ENCODED_SIZE: usize = i64::MAX_ENCODED_SIZE;
}
unsafe impl SizedEncode for () { unsafe impl SizedEncode for () {
const MAX_ENCODED_SIZE: usize = 0x0; const MAX_ENCODED_SIZE: usize = 0x0;
} }

View file

@ -24,6 +24,7 @@ mod test;
use core::iter::{DoubleEndedIterator, ExactSizeIterator, FusedIterator}; use core::iter::{DoubleEndedIterator, ExactSizeIterator, FusedIterator};
use core::mem::MaybeUninit; use core::mem::MaybeUninit;
use core::ptr::drop_in_place;
use core::slice; use core::slice;
/// Iterator to a sized slice. /// Iterator to a sized slice.
@ -86,6 +87,7 @@ impl<T, const N: usize> AsRef<[T]> for SizedIter<T, N> {
} }
impl<T: Clone, const N: usize> Clone for SizedIter<T, N> { impl<T: Clone, const N: usize> Clone for SizedIter<T, N> {
#[expect(clippy::borrow_deref_ref)] // Clippy is gaslighting me into believing pointers and references are identical???
#[inline] #[inline]
fn clone(&self) -> Self { fn clone(&self) -> Self {
let mut buf: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() }; let mut buf: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() };
@ -95,14 +97,12 @@ impl<T: Clone, const N: usize> Clone for SizedIter<T, N> {
let stop = start + len; let stop = start + len;
for i in start..stop { for i in start..stop {
unsafe { unsafe {
let item = (&raw const *self.buf.get_unchecked(i)).cast(); let item = (&raw const *self.buf.get_unchecked(i)).cast();
let value = Clone::clone(&*item); let value = Clone::clone(&*item);
buf buf.get_unchecked_mut(i).write(value);
.get_unchecked_mut(i)
.write(value);
} }
} }
@ -117,11 +117,7 @@ impl<T, const N: usize> DoubleEndedIterator for SizedIter<T, N> {
let index = self.pos + self.len - 0x1; let index = self.pos + self.len - 0x1;
let item = unsafe { let item = unsafe { self.buf.get_unchecked(index).assume_init_read() };
self.buf
.get_unchecked(index)
.assume_init_read()
};
self.len -= 0x1; self.len -= 0x1;
@ -142,11 +138,7 @@ impl<T, const N: usize> Iterator for SizedIter<T, N> {
let index = self.pos; let index = self.pos;
let item = unsafe { let item = unsafe { self.buf.get_unchecked(index).assume_init_read() };
self.buf
.get_unchecked(index)
.assume_init_read()
};
self.len -= 0x1; self.len -= 0x1;
self.pos += 0x1; self.pos += 0x1;
@ -154,6 +146,31 @@ impl<T, const N: usize> Iterator for SizedIter<T, N> {
Some(item) Some(item)
} }
#[inline]
fn nth(&mut self, index: usize) -> Option<Self::Item> {
if index > self.len { return None };
let start = self.pos;
let stop = start + index - 0x1;
// Drop each skipped element.
for i in start..stop {
unsafe {
let item = &raw mut *self.buf.get_unchecked_mut(i);
drop_in_place(item);
}
}
// Read the final element.
let item = unsafe { self.buf.get_unchecked(index).assume_init_read() };
self.len -= index;
self.pos += index;
Some(item)
}
#[inline(always)] #[inline(always)]
fn size_hint(&self) -> (usize, Option<usize>) { fn size_hint(&self) -> (usize, Option<usize>) {
let rem = unsafe { self.len.unchecked_sub(self.pos) }; let rem = unsafe { self.len.unchecked_sub(self.pos) };

View file

@ -19,7 +19,31 @@
// er General Public License along with bZipper. If // er General Public License along with bZipper. If
// not, see <https://www.gnu.org/licenses/>. // not, see <https://www.gnu.org/licenses/>.
use bzipper::SizedSlice; use bzipper::{SizedSlice, SizedStr};
#[test]
fn test_sized_iter_clone() {
let data = SizedStr::<0x9>::new("fran\u{00E7}ais").unwrap();
let mut data0 = data.into_bytes().into_iter();
let _ = data0.nth(0x4);
let mut data1 = data0.clone();
assert_eq!(data0.next(), Some(0xC3));
assert_eq!(data1.next(), Some(0xC3));
assert_eq!(data0.next(), Some(0xA7));
assert_eq!(data1.next(), Some(0xA7));
assert_eq!(data0.next(), Some(b'a'));
assert_eq!(data1.next(), Some(b'a'));
assert_eq!(data0.next(), Some(b'i'));
assert_eq!(data1.next(), Some(b'i'));
assert_eq!(data0.next(), Some(b's'));
assert_eq!(data1.next(), Some(b's'));
assert_eq!(data0.next(), None);
assert_eq!(data1.next(), None);
}
#[test] #[test]
fn test_sized_iter_double_ended() { fn test_sized_iter_double_ended() {

View file

@ -138,8 +138,9 @@ impl<const N: usize> SizedStr<N> {
Err(e) => { Err(e) => {
let i = e.valid_up_to(); let i = e.valid_up_to();
let c = data[i];
return Err(StringError::BadUtf8(Utf8Error { value: data[i], index: i })); return Err(StringError::BadUtf8(Utf8Error { value: c, index: i }));
} }
}; };
@ -310,15 +311,22 @@ impl<const N: usize> SizedStr<N> {
(buf, len) (buf, len)
} }
/// Deconstructs the string into a fixed-size byte slice.
#[inline(always)]
#[must_use]
pub const fn into_bytes(self) -> SizedSlice<u8, N> {
let Self(v) = self;
v
}
/// Converts the fixed-size string into a boxed string slice. /// Converts the fixed-size string into a boxed string slice.
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))] #[cfg_attr(doc, doc(cfg(feature = "alloc")))]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn into_boxed_str(self) -> Box<str> { pub fn into_boxed_str(self) -> Box<str> {
let Self(vec) = self; let Self(v) = self;
unsafe { alloc::str::from_boxed_utf8_unchecked(v.into_boxed_slice()) }
unsafe { alloc::str::from_boxed_utf8_unchecked(vec.into_boxed_slice()) }
} }
/// Converts the fixed-size string into a dynamic string. /// Converts the fixed-size string into a dynamic string.

View file

@ -1,6 +1,6 @@
[package] [package]
name = "bzipper_benchmarks" name = "bzipper_benchmarks"
version = "0.10.1" version = "0.11.0"
edition = "2021" edition = "2021"
description = "bZipper benchmarks." description = "bZipper benchmarks."
@ -10,7 +10,7 @@ homepage.workspace = true
repository.workspace = true repository.workspace = true
[dependencies] [dependencies]
bzipper = { path = "../bzipper", version = "0.10.1" } bzipper = { path = "../bzipper", version = "0.11.0" }
bincode = "1.3.3" bincode = "1.3.3"
ciborium = "0.2.2" ciborium = "0.2.2"

View file

@ -1,6 +1,6 @@
[package] [package]
name = "bzipper_macros" name = "bzipper_macros"
version = "0.10.1" version = "0.11.0"
edition = "2021" edition = "2021"
documentation = "https://docs.rs/bzipper_macros/" documentation = "https://docs.rs/bzipper_macros/"

View file

@ -21,7 +21,7 @@
#![doc(html_logo_url = "https://gitlab.com/bjoernager/bzipper/-/raw/master/doc-icon.svg")] #![doc(html_logo_url = "https://gitlab.com/bjoernager/bzipper/-/raw/master/doc-icon.svg")]
//! This crate implements procedural macros for [`bzipper`](https://crates.io/crates/bzipper/). //! This crate implements procedural macros for [`bZipper`](https://crates.io/crates/bzipper/).
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use quote::quote;