diff options
-rw-r--r-- | CHANGELOG.md | 17 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | README.md | 14 | ||||
-rw-r--r-- | bzipper/Cargo.toml | 4 | ||||
-rw-r--r-- | bzipper/src/decode/mod.rs | 106 | ||||
-rw-r--r-- | bzipper/src/decode/test.rs | 5 | ||||
-rw-r--r-- | bzipper/src/encode/mod.rs | 124 | ||||
-rw-r--r-- | bzipper/src/encode/test.rs | 11 | ||||
-rw-r--r-- | bzipper/src/error/decode_error/mod.rs | 32 | ||||
-rw-r--r-- | bzipper/src/lib.rs | 16 | ||||
-rw-r--r-- | bzipper/src/sized_encode/mod.rs | 19 | ||||
-rw-r--r-- | bzipper/src/sized_iter/mod.rs | 45 | ||||
-rw-r--r-- | bzipper/src/sized_iter/test.rs | 26 | ||||
-rw-r--r-- | bzipper/src/sized_str/mod.rs | 16 | ||||
-rw-r--r-- | bzipper_benchmarks/Cargo.toml | 4 | ||||
-rw-r--r-- | bzipper_macros/Cargo.toml | 2 | ||||
-rw-r--r-- | bzipper_macros/src/lib.rs | 2 |
17 files changed, 366 insertions, 78 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index ed93051..b46fe18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,23 @@ This is the changelog of bzipper. 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 * Clean up and refactor code @@ -82,6 +82,7 @@ match_bool = "warn" match_on_vec_items = "warn" match_same_arms = "warn" mismatching_type_param_order = "warn" +missing_transmute_annotations = "forbid" mixed_read_write_in_expression = "deny" mut_mut = "deny" mutex_atomic = "deny" @@ -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] | | :--------------------------------- | --------: | ------: | ------: | ---------: | ---------: | -| `encode_u8` | 1.262 | 1.271 | 1.153 | 2.854 | 1.270 | -| `encode_struct_unit` | 0.000 | 0.000 | 0.000 | 0.447 | 0.000 | -| `encode_struct_unnamed` | 1.270 | 1.102 | 0.998 | 1.948 | 1.182 | -| `encode_struct_named` | 4.205 | 1.186 | 1.136 | 10.395 | 1.168 | -| `encode_enum_unit` | 0.328 | 0.008 | 0.000 | 2.293 | 0.004 | -| **Total time** → | 7.065 | 3.567 | 3.286 | 17.937 | 3.625 | -| **Total deviation (p.c.)** → | +115 | +9 | ±0 | +446 | +10 | +| `encode_u8` | 1.234 | 1.096 | 0.881 | 3.076 | 1.223 | +| `encode_struct_unit` | 0.000 | 0.000 | 0.000 | 0.516 | 0.000 | +| `encode_struct_unnamed` | 1.367 | 1.154 | 1.009 | 2.051 | 1.191 | +| `encode_struct_named` | 4.101 | 1.271 | 1.181 | 9.342 | 1.182 | +| `encode_enum_unit` | 0.306 | 0.008 | 0.000 | 2.304 | 0.004 | +| **Total time** → | 7.009 | 3.528 | 3.071 | 17.289 | 3.599 | +| **Total deviation (p.c.)** → | +128 | +15 | ±0 | +463 | +17 | [Bincode]: https://crates.io/crates/bincode/ [Borsh]: https://crates.io/crates/borsh/ diff --git a/bzipper/Cargo.toml b/bzipper/Cargo.toml index 909d9c4..5912aa5 100644 --- a/bzipper/Cargo.toml +++ b/bzipper/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bzipper" -version = "0.10.1" +version = "0.11.0" edition = "2021" rust-version = "1.83" documentation = "https://docs.rs/bzipper/" @@ -24,7 +24,7 @@ alloc = [] std = [] [dependencies] -bzipper_macros = { path = "../bzipper_macros", version = "0.10.1" } +bzipper_macros = { path = "../bzipper_macros", version = "0.11.0" } [lints] workspace = true diff --git a/bzipper/src/decode/mod.rs b/bzipper/src/decode/mod.rs index 75ccb43..b7ca717 100644 --- a/bzipper/src/decode/mod.rs +++ b/bzipper/src/decode/mod.rs @@ -48,12 +48,18 @@ use core::ops::{ RangeTo, RangeToInclusive, }; +use core::ptr::copy_nonoverlapping; +use core::str; +use core::time::Duration; #[cfg(feature = "alloc")] -use std::boxed::Box; +use alloc::boxed::Box; #[cfg(feature = "alloc")] -use std::collections::LinkedList; +use alloc::collections::LinkedList; + +#[cfg(feature = "alloc")] +use alloc::ffi::CString; #[cfg(feature = "alloc")] use alloc::string::String; @@ -76,6 +82,9 @@ use std::hash::BuildHasher; #[cfg(feature = "std")] use std::sync::{Mutex, RwLock}; +#[cfg(feature = "std")] +use std::time::{SystemTime, UNIX_EPOCH}; + // Should we require `Encode` for `Decode`? /// Denotes a type capable of being decoded. @@ -96,7 +105,6 @@ where #[inline(always)] fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { let value = (Decode::decode(stream)?, ); - Ok(value) } } @@ -106,6 +114,7 @@ impl<T: Decode, const N: usize> Decode for [T; N] { fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { // Initialise the array incrementally. + // SAFETY: Always safe. let mut buf: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() }; 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_attr(doc, doc(cfg(feature = "std")))] impl<K, V, S> Decode for HashMap<K, V, S> @@ -283,7 +332,6 @@ impl Decode for Ipv4Addr { #[inline(always)] fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { let value = Decode::decode(stream)?; - Ok(Self::from_bits(value)) } } @@ -292,7 +340,6 @@ impl Decode for Ipv6Addr { #[inline(always)] fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { let value = Decode::decode(stream)?; - Ok(Self::from_bits(value)) } } @@ -512,15 +559,52 @@ impl Decode for SocketAddrV6 { impl Decode for String { #[inline(always)] fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { - let data = <Vec::<u8>>::decode(stream)?; + let len = Decode::decode(stream)?; + + let data = stream.read(len); - Self::from_utf8(data) + str::from_utf8(data) .map_err(|e| { - let data = e.as_bytes(); - let i = e.utf8_error().valid_up_to(); + let i = e.valid_up_to(); + let c = data[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)) + }; - DecodeError::BadString(Utf8Error { value: data[i], index: i }) - }) + this.ok_or_else(|| DecodeError::NarrowSystemTime { timestamp: time }) } } diff --git a/bzipper/src/decode/test.rs b/bzipper/src/decode/test.rs index a7aa089..2135114 100644 --- a/bzipper/src/decode/test.rs +++ b/bzipper/src/decode/test.rs @@ -20,6 +20,7 @@ // not, see <https://www.gnu.org/licenses/>. use alloc::vec::Vec; +use alloc::string::String; use bzipper::{Decode, IStream, SizedEncode}; use core::char; @@ -118,5 +119,7 @@ fn test_decode() { 0xC8, 0x4C, ] => 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}"); } diff --git a/bzipper/src/encode/mod.rs b/bzipper/src/encode/mod.rs index a5c3036..03b8dd9 100644 --- a/bzipper/src/encode/mod.rs +++ b/bzipper/src/encode/mod.rs @@ -27,6 +27,7 @@ use crate::error::EncodeError; use core::cell::{Cell, LazyCell, RefCell}; use core::convert::Infallible; +use core::ffi::CStr; use core::hash::BuildHasher; use core::hint::unreachable_unchecked; use core::marker::{PhantomData, PhantomPinned}; @@ -48,6 +49,7 @@ use core::ops::{ RangeTo, RangeToInclusive, }; +use core::time::Duration; #[cfg(feature = "alloc")] use alloc::borrow::{Cow, ToOwned}; @@ -59,6 +61,9 @@ use alloc::boxed::Box; use alloc::collections::LinkedList; #[cfg(feature = "alloc")] +use alloc::ffi::CString; + +#[cfg(feature = "alloc")] use alloc::string::String; #[cfg(feature = "alloc")] @@ -76,6 +81,9 @@ use std::collections::{HashMap, HashSet}; #[cfg(feature = "std")] use std::sync::{LazyLock, Mutex, RwLock}; +#[cfg(feature = "std")] +use std::time::{SystemTime, UNIX_EPOCH}; + /// Denotes a type capable of being encoded. /// /// 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] { + /// Encodes each element sequentially. + /// The length is hard-coded into the type and is therefore not encoded. #[inline(always)] fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { for value in self { @@ -159,6 +169,7 @@ impl<T: Encode, const N: usize> Encode for [T; N] { } impl<T: Encode> Encode for [T] { + /// Encodes each element sequentially with an extra length specifier (of type [`usize`]) prepended first. #[inline(always)] fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { 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_attr(doc, doc(cfg(feature = "std")))] 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 { + /// 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)] fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { // 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 { + /// Encodes the address's bits in big-endian. #[inline(always)] fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { 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 { + /// Encodes the address's bits in big-endian. #[inline(always)] fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { 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 { + /// 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] fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { 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> { + /// 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> { match *self { 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> { + /// 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> { // The sign here is `false` for `Ok` objects and // `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 { + /// 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] fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { // 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 { + /// Encodes the address's bits followed by the port number, both of which in big-endian. #[inline(always)] fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { 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 { + /// Encodes the address's bits followed by the port number, flow information, and scope identifier -- all of which in big-endian. #[inline(always)] fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { self.ip().encode(stream)?; @@ -576,6 +621,7 @@ impl Encode for SocketAddrV6 { } impl Encode for str { + /// Encodes the string identically to [a byte slice](slice) containing the string's byte values. #[inline(always)] fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { // Optimised encode. Don't just rely on `[char]`. @@ -590,12 +636,47 @@ impl Encode for str { #[cfg(feature = "alloc")] #[cfg_attr(doc, doc(cfg(feature = "alloc")))] impl Encode for String { + /// See [`str`]. #[inline(always)] fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { 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 () { #[inline(always)] 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 { + /// 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> { let value = u16::try_from(*self) .map_err(|_| EncodeError::UsizeOutOfRange(*self))?; @@ -680,11 +763,12 @@ macro_rules! impl_atomic { ty: $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_attr(doc, doc(cfg(target_has_atomic = $width)))] 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)] fn encode(&self, stream: &mut ::bzipper::OStream) -> ::core::result::Result<(), ::bzipper::error::EncodeError> { self.load(::std::sync::atomic::Ordering::Relaxed).encode(stream) diff --git a/bzipper/src/encode/test.rs b/bzipper/src/encode/test.rs index 2404859..f88b238 100644 --- a/bzipper/src/encode/test.rs +++ b/bzipper/src/encode/test.rs @@ -18,6 +18,9 @@ // er General Public License along with bzipper. If // not, see <https://www.gnu.org/licenses/>. +use core::time::Duration; +use std::time::{SystemTime, UNIX_EPOCH}; + use alloc::vec; use alloc::vec::Vec; use bzipper::{Encode, OStream, SizedEncode, SizedStr}; @@ -96,5 +99,11 @@ fn test_encode() { 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]); } diff --git a/bzipper/src/error/decode_error/mod.rs b/bzipper/src/error/decode_error/mod.rs index bef820d..28478f6 100644 --- a/bzipper/src/error/decode_error/mod.rs +++ b/bzipper/src/error/decode_error/mod.rs @@ -56,9 +56,27 @@ pub enum DecodeError { /// An invalid enumeration descriminant was provided. 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`. 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. SmallBuffer(SizeError), } @@ -77,16 +95,24 @@ impl Display for DecodeError { => write!(f, "{source}"), InvalidBoolean(value) - => write!(f, "expected boolean but got {value:#02X}"), + => write!(f, "expected boolean but got `{value:#02X}`"), InvalidCodePoint(value) => write!(f, "code point U+{value:04X} is not defined"), 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 - => 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) => write!(f, "buffer too small: {source}"), diff --git a/bzipper/src/lib.rs b/bzipper/src/lib.rs index b57732c..237770f 100644 --- a/bzipper/src/lib.rs +++ b/bzipper/src/lib.rs @@ -41,13 +41,13 @@ //! //! | Benchmark | [Bincode] | [Borsh] | bZipper | [Ciborium] | [Postcard] | //! | :--------------------------------- | --------: | ------: | ------: | ---------: | ---------: | -//! | `encode_u8` | 1.262 | 1.271 | 1.153 | 2.854 | 1.270 | -//! | `encode_struct_unit` | 0.000 | 0.000 | 0.000 | 0.447 | 0.000 | -//! | `encode_struct_unnamed` | 1.270 | 1.102 | 0.998 | 1.948 | 1.182 | -//! | `encode_struct_named` | 4.205 | 1.186 | 1.136 | 10.395 | 1.168 | -//! | `encode_enum_unit` | 0.328 | 0.008 | 0.000 | 2.293 | 0.004 | -//! | **Total time** → | 7.065 | 3.567 | 3.286 | 17.937 | 3.625 | -//! | **Total deviation (p.c.)** → | +115 | +9 | ±0 | +446 | +10 | +//! | `encode_u8` | 1.234 | 1.096 | 0.881 | 3.076 | 1.223 | +//! | `encode_struct_unit` | 0.000 | 0.000 | 0.000 | 0.516 | 0.000 | +//! | `encode_struct_unnamed` | 1.367 | 1.154 | 1.009 | 2.051 | 1.191 | +//! | `encode_struct_named` | 4.101 | 1.271 | 1.181 | 9.342 | 1.182 | +//! | `encode_enum_unit` | 0.306 | 0.008 | 0.000 | 2.304 | 0.004 | +//! | **Total time** → | 7.009 | 3.528 | 3.071 | 17.289 | 3.599 | +//! | **Total deviation (p.c.)** → | +128 | +15 | ±0 | +463 | +17 | //! //! [Bincode]: https://crates.io/crates/bincode/ //! [Borsh]: https://crates.io/crates/borsh/ @@ -430,8 +430,8 @@ use_mod!(pub i_stream); use_mod!(pub o_stream); use_mod!(pub sized_encode); use_mod!(pub sized_iter); -use_mod!(pub sized_str); use_mod!(pub sized_slice); +use_mod!(pub sized_str); #[cfg(feature = "alloc")] use_mod!(pub buf); diff --git a/bzipper/src/sized_encode/mod.rs b/bzipper/src/sized_encode/mod.rs index 378adb5..ae5d27a 100644 --- a/bzipper/src/sized_encode/mod.rs +++ b/bzipper/src/sized_encode/mod.rs @@ -45,10 +45,10 @@ use core::ops::{ RangeTo, RangeToInclusive, }; -use std::borrow::ToOwned; +use core::time::Duration; #[cfg(feature = "alloc")] -use alloc::borrow::Cow; +use alloc::borrow::{Cow, ToOwned}; #[cfg(feature = "alloc")] use alloc::boxed::Box; @@ -62,6 +62,9 @@ use alloc::sync::Arc; #[cfg(feature = "std")] use std::sync::{LazyLock, Mutex, RwLock}; +#[cfg(feature = "std")] +use std::time::SystemTime; + /// Denotes a size-constrained, encodable type. /// /// 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; } +unsafe impl SizedEncode for Duration { + const MAX_ENCODED_SIZE: usize = + u64::MAX_ENCODED_SIZE + + u32::MAX_ENCODED_SIZE; +} + unsafe impl SizedEncode for Infallible { const MAX_ENCODED_SIZE: usize = 0x0; } @@ -244,6 +253,12 @@ unsafe impl SizedEncode for SocketAddrV6 { + 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 () { const MAX_ENCODED_SIZE: usize = 0x0; } diff --git a/bzipper/src/sized_iter/mod.rs b/bzipper/src/sized_iter/mod.rs index ae5efc4..48fc15d 100644 --- a/bzipper/src/sized_iter/mod.rs +++ b/bzipper/src/sized_iter/mod.rs @@ -24,6 +24,7 @@ mod test; use core::iter::{DoubleEndedIterator, ExactSizeIterator, FusedIterator}; use core::mem::MaybeUninit; +use core::ptr::drop_in_place; use core::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> { + #[expect(clippy::borrow_deref_ref)] // Clippy is gaslighting me into believing pointers and references are identical??? #[inline] fn clone(&self) -> Self { 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; for i in start..stop { - unsafe { + unsafe { let item = (&raw const *self.buf.get_unchecked(i)).cast(); let value = Clone::clone(&*item); - buf - .get_unchecked_mut(i) - .write(value); + buf.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 item = unsafe { - self.buf - .get_unchecked(index) - .assume_init_read() - }; + let item = unsafe { self.buf.get_unchecked(index).assume_init_read() }; self.len -= 0x1; @@ -142,11 +138,7 @@ impl<T, const N: usize> Iterator for SizedIter<T, N> { let index = self.pos; - let item = unsafe { - self.buf - .get_unchecked(index) - .assume_init_read() - }; + let item = unsafe { self.buf.get_unchecked(index).assume_init_read() }; self.len -= 0x1; self.pos += 0x1; @@ -154,6 +146,31 @@ impl<T, const N: usize> Iterator for SizedIter<T, N> { 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)] fn size_hint(&self) -> (usize, Option<usize>) { let rem = unsafe { self.len.unchecked_sub(self.pos) }; diff --git a/bzipper/src/sized_iter/test.rs b/bzipper/src/sized_iter/test.rs index d99a508..cf3f028 100644 --- a/bzipper/src/sized_iter/test.rs +++ b/bzipper/src/sized_iter/test.rs @@ -19,7 +19,31 @@ // er General Public License along with bZipper. If // 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] fn test_sized_iter_double_ended() { diff --git a/bzipper/src/sized_str/mod.rs b/bzipper/src/sized_str/mod.rs index c6db3f2..fd1deac 100644 --- a/bzipper/src/sized_str/mod.rs +++ b/bzipper/src/sized_str/mod.rs @@ -138,8 +138,9 @@ impl<const N: usize> SizedStr<N> { Err(e) => { 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) } + /// 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. #[cfg(feature = "alloc")] #[cfg_attr(doc, doc(cfg(feature = "alloc")))] #[inline(always)] #[must_use] pub fn into_boxed_str(self) -> Box<str> { - let Self(vec) = self; - - unsafe { alloc::str::from_boxed_utf8_unchecked(vec.into_boxed_slice()) } + let Self(v) = self; + unsafe { alloc::str::from_boxed_utf8_unchecked(v.into_boxed_slice()) } } /// Converts the fixed-size string into a dynamic string. diff --git a/bzipper_benchmarks/Cargo.toml b/bzipper_benchmarks/Cargo.toml index b94acb7..4b1fe83 100644 --- a/bzipper_benchmarks/Cargo.toml +++ b/bzipper_benchmarks/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bzipper_benchmarks" -version = "0.10.1" +version = "0.11.0" edition = "2021" description = "bZipper benchmarks." @@ -10,7 +10,7 @@ homepage.workspace = true repository.workspace = true [dependencies] -bzipper = { path = "../bzipper", version = "0.10.1" } +bzipper = { path = "../bzipper", version = "0.11.0" } bincode = "1.3.3" ciborium = "0.2.2" diff --git a/bzipper_macros/Cargo.toml b/bzipper_macros/Cargo.toml index 8731193..1904763 100644 --- a/bzipper_macros/Cargo.toml +++ b/bzipper_macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bzipper_macros" -version = "0.10.1" +version = "0.11.0" edition = "2021" documentation = "https://docs.rs/bzipper_macros/" diff --git a/bzipper_macros/src/lib.rs b/bzipper_macros/src/lib.rs index db7ace7..e92f8b0 100644 --- a/bzipper_macros/src/lib.rs +++ b/bzipper_macros/src/lib.rs @@ -21,7 +21,7 @@ #![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 quote::quote; |