summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md17
-rw-r--r--Cargo.toml1
-rw-r--r--README.md14
-rw-r--r--bzipper/Cargo.toml4
-rw-r--r--bzipper/src/decode/mod.rs106
-rw-r--r--bzipper/src/decode/test.rs5
-rw-r--r--bzipper/src/encode/mod.rs124
-rw-r--r--bzipper/src/encode/test.rs11
-rw-r--r--bzipper/src/error/decode_error/mod.rs32
-rw-r--r--bzipper/src/lib.rs16
-rw-r--r--bzipper/src/sized_encode/mod.rs19
-rw-r--r--bzipper/src/sized_iter/mod.rs45
-rw-r--r--bzipper/src/sized_iter/test.rs26
-rw-r--r--bzipper/src/sized_str/mod.rs16
-rw-r--r--bzipper_benchmarks/Cargo.toml4
-rw-r--r--bzipper_macros/Cargo.toml2
-rw-r--r--bzipper_macros/src/lib.rs2
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
diff --git a/Cargo.toml b/Cargo.toml
index b183495..99c1dde 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
diff --git a/README.md b/README.md
index b1f5c35..af3a3b5 100644
--- a/README.md
+++ b/README.md
@@ -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** &#8594; | 7.065 | 3.567 | 3.286 | 17.937 | 3.625 |
-| **Total deviation (p.c.)** &#8594; | +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** &#8594; | 7.009 | 3.528 | 3.071 | 17.289 | 3.599 |
+| **Total deviation (p.c.)** &#8594; | +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** &#8594; | 7.065 | 3.567 | 3.286 | 17.937 | 3.625 |
-//! | **Total deviation (p.c.)** &#8594; | +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** &#8594; | 7.009 | 3.528 | 3.071 | 17.289 | 3.599 |
+//! | **Total deviation (p.c.)** &#8594; | +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;