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:
parent
b7a72dc0f9
commit
74b8d2caa2
17 changed files with 366 additions and 78 deletions
17
CHANGELOG.md
17
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"
|
||||
|
|
14
README.md
14
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** → | 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/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)?;
|
||||
|
||||
Self::from_utf8(data)
|
||||
let data = stream.read(len);
|
||||
|
||||
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: 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 })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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}");
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
@ -58,6 +60,9 @@ use alloc::boxed::Box;
|
|||
#[cfg(feature = "alloc")]
|
||||
use alloc::collections::LinkedList;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::ffi::CString;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::string::String;
|
||||
|
||||
|
@ -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)
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
@ -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}"),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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() };
|
||||
|
@ -100,9 +102,7 @@ impl<T: Clone, const N: usize> Clone for SizedIter<T, N> {
|
|||
|
||||
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) };
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "bzipper_macros"
|
||||
version = "0.10.1"
|
||||
version = "0.11.0"
|
||||
edition = "2021"
|
||||
documentation = "https://docs.rs/bzipper_macros/"
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue