Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
804163f
add support for naive_date naive_date_time naive_time date_time_utc d…
l-7-l Oct 6, 2025
b23a415
feat(chrono): refactor chrono support and fuzz testing
l-7-l Dec 21, 2025
1954e56
Appease clippy and fix CI using a better-supported big endian arch. (…
finnbear Oct 17, 2025
7f6d6c8
Fix thread local access error. (#93)
finnbear Dec 17, 2025
d7df262
Version 0.6.8 (#96)
finnbear Dec 17, 2025
946257c
Version 0.6.9 - remove deprecated doc_auto_cfg feature. (#97)
finnbear Dec 18, 2025
7d5981f
add support for naive_date naive_date_time naive_time date_time_utc d…
l-7-l Oct 6, 2025
437ec56
Merge branch 'SoftbearStudios:main' into support_for_chrono#39
l-7-l Dec 21, 2025
bd26bd4
resolve conflicts
l-7-l Dec 21, 2025
e68f05d
chore: add chrono to fuzz
l-7-l Dec 21, 2025
be7a642
add support for naive_date naive_date_time naive_time date_time_utc d…
l-7-l Oct 6, 2025
9427f9f
feat(chrono): refactor chrono support and fuzz testing
l-7-l Dec 21, 2025
ad2a731
add support for naive_date naive_date_time naive_time date_time_utc d…
l-7-l Oct 6, 2025
843f389
resolve conflicts
l-7-l Dec 21, 2025
232a3a7
add try_convert_from
l-7-l Mar 28, 2026
fe60bb0
feat: use try_convert_from for chrono
l-7-l Mar 28, 2026
53823bb
add jiff support
l-7-l Mar 28, 2026
a0d7e2c
feat: add jiff support
l-7-l Mar 28, 2026
ada3fd1
chore: 1. Bump crate version from 0.6.9 to 0.7.0 2. change jiff depe…
l-7-l Mar 28, 2026
6d583dc
chore: bump lz4_flex from 0.11.2(yanked) to 0.13.0
l-7-l Mar 28, 2026
cbfecb7
fix(chrono): correct NaiveTime conversion assertions 23:59:59 with 1…
l-7-l Mar 28, 2026
60c51df
chore: remove chrono in default features
l-7-l Mar 28, 2026
37b09f2
test(jiff): remove jiff::Timestamp::now() for Miri compatibility
l-7-l Mar 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ members = [
[package]
name = "bitcode"
authors = [ "Cai Bear", "Finn Bear" ]
version = "0.6.9"
version = "0.7.0"
edition = "2021"
rust-version = "1.70"
license = "MIT OR Apache-2.0"
Expand All @@ -18,7 +18,9 @@ exclude = ["fuzz/"]
arrayvec = { version = "0.7", default-features = false, optional = true }
bitcode_derive = { version = "=0.6.9", path = "./bitcode_derive", optional = true }
bytemuck = { version = "1.14", features = [ "min_const_generics", "must_cast" ] }
chrono = { version = ">=0.4", default-features = false, optional = true }
glam = { version = ">=0.21", default-features = false, optional = true }
jiff = { version = ">=0.2.0", optional = true }
rust_decimal = { version = "1.36", default-features = false, optional = true }
serde = { version = "1.0", default-features = false, features = [ "alloc" ], optional = true }
time = { version = "0.3", default-features = false, optional = true }
Expand All @@ -29,7 +31,7 @@ arrayvec = { version = "0.7", features = [ "serde" ] }
bincode = "1.3.3"
flate2 = "1.0.28"
glam = { version = "0.22", default-features = false, features = [ "rand" ] }
lz4_flex = { version = "=0.11.2", default-features = false }
lz4_flex = { version = "=0.13.0", default-features = false }
paste = "1.0.14"
rand = "0.8.5"
rand_chacha = "0.3.1"
Expand Down
7 changes: 5 additions & 2 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ cargo-fuzz = true

[dependencies]
arrayvec = { version = "0.7", features = ["serde"] }
bitcode = { path = "..", features = [ "arrayvec", "rust_decimal", "serde", "time" ] }
bitcode = { path = "..", features = [ "arrayvec", "rust_decimal", "serde", "time", "chrono", "jiff" ] }
libfuzzer-sys = "0.4"
rust_decimal = "1.36.0"
serde = { version ="1.0", features = [ "derive" ] }
time = { version = "0.3", features = ["serde"]}
chrono = { version = "0.4.42", features = ["serde"] }
jiff = {version = "0.2.23", features = ["serde"]}


# Prevent this from interfering with workspaces
[workspace]
Expand All @@ -24,4 +27,4 @@ members = ["."]
name = "fuzz"
path = "fuzz_targets/fuzz.rs"
test = false
doc = false
doc = false
Binary file not shown.
26 changes: 22 additions & 4 deletions fuzz/fuzz_targets/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ use libfuzzer_sys::fuzz_target;
extern crate bitcode;
use arrayvec::{ArrayString, ArrayVec};
use bitcode::{Decode, DecodeOwned, Encode};
use rust_decimal::Decimal;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use std::fmt::Debug;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
use std::num::NonZeroU32;
use std::time::Duration;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
use rust_decimal::Decimal;

#[inline(never)]
fn test_derive<T: Debug + PartialEq + Encode + DecodeOwned>(data: &[u8]) {
Expand Down Expand Up @@ -148,10 +148,20 @@ fuzz_target!(|data: &[u8]| {
A,
B,
C(u16),
D { a: u8, b: u8, #[serde(skip)] #[bitcode(skip)] c: u8 },
D {
a: u8,
b: u8,
#[serde(skip)]
#[bitcode(skip)]
c: u8,
},
E(String),
F,
G(#[bitcode(skip)] #[serde(skip)] i16),
G(
#[bitcode(skip)]
#[serde(skip)]
i16,
),
P(BTreeMap<u16, u8>),
}

Expand Down Expand Up @@ -233,5 +243,13 @@ fuzz_target!(|data: &[u8]| {
SocketAddrV6,
SocketAddr,
time::Time,
chrono::NaiveTime,
chrono::NaiveDate,
chrono::NaiveDateTime,
chrono::DateTime<chrono::Utc>,
jiff::civil::Date,
jiff::civil::Time,
jiff::Timestamp,
jiff::Zoned,
);
});
2 changes: 2 additions & 0 deletions src/derive/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub(crate) mod convert;
mod duration;
mod empty;
mod impls;
#[cfg(any(feature = "chrono", feature = "jiff"))]
pub(crate) mod try_convert;
// TODO: When ip_in_core has been stable (https://github.com/rust-lang/rust/issues/108443)
// for long enough, remove feature check.
#[cfg(feature = "std")]
Expand Down
81 changes: 81 additions & 0 deletions src/derive/try_convert.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use crate::{
coder::{Decoder, View},
derive::Decode,
fast::{CowSlice, PushUnchecked, SliceImpl, Unaligned},
};

#[allow(unused)]
macro_rules! impl_try_convert {
($want: path, $have: ty) => {
impl_try_convert!($want, $have, $have);
};
($want: path, $have_encode: ty, $have_decode: ty) => {
impl crate::derive::Encode for $want {
type Encoder = crate::derive::convert::ConvertIntoEncoder<$have_encode>;
}
impl<'a> crate::derive::Decode<'a> for $want {
type Decoder =
crate::derive::try_convert::TryConvertFromDecoder<'a, $have_decode, $want>;
}
};
}

#[allow(unused)]
pub(crate) use impl_try_convert;

// Like [`TryFrom`] but we can implement it ourselves.
pub trait TryConvertFrom<T>: Sized {
fn try_convert_from(value: T) -> Result<Self, crate::Error>;
}
/// Decodes a `T` and then converts it with [`TryConvertFrom`].
pub struct TryConvertFromDecoder<'a, T: Decode<'a>, F: TryConvertFrom<T>> {
data: CowSlice<'a, F>,
decoder: T::Decoder,
}

// Can't derive since it would bound T: Default.
impl<'a, T: Decode<'a>, F: TryConvertFrom<T>> Default for TryConvertFromDecoder<'a, T, F> {
fn default() -> Self {
Self {
data: CowSlice::with_allocation(Vec::new()),
decoder: Default::default(),
}
}
}

impl<'a, T: Decode<'a>, F: TryConvertFrom<T>> View<'a> for TryConvertFromDecoder<'a, T, F> {
fn populate(&mut self, input: &mut &'a [u8], length: usize) -> Result<(), crate::Error> {
self.decoder.populate(input, length)?;

let out: &mut Vec<F> = &mut self.data.set_owned();
out.reserve(length);

for _ in 0..length {
let value = F::try_convert_from(self.decoder.decode())?;
unsafe { out.push_unchecked(value) };
}

Ok(())
}
}

impl<'a, T: Decode<'a>, F: TryConvertFrom<T> + Send + Sync> Decoder<'a, F>
for TryConvertFromDecoder<'a, T, F>
{
#[inline(always)]
fn as_primitive(&mut self) -> Option<&mut SliceImpl<'_, Unaligned<F>>> {
None
}

#[inline(always)]
fn decode(&mut self) -> F {
let slice = self.data.mut_slice();
let ptr = slice.as_ptr();
unsafe {
let val = ptr.read();
slice.advance(1);

val
}
}
}
20 changes: 20 additions & 0 deletions src/ext/chrono.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
mod date_time_utc;
mod naive_date;
mod naive_date_time;
mod naive_time;

use crate::int::ranged_int;

ranged_int!(Hour, u8, 0, 23);
ranged_int!(Minute, u8, 0, 59);
ranged_int!(Second, u8, 0, 59);
ranged_int!(Nanosecond, u32, 0, 1_999_999_999);

type TimeEncode = (u8, u8, u8, u32);
type TimeDecode = (Hour, Minute, Second, Nanosecond);

type DateEncode = i32;
type DateDecode = i32;

type DateTimeEncode = (DateEncode, TimeEncode);
type DateTimeDecode = (DateEncode, TimeDecode);
70 changes: 70 additions & 0 deletions src/ext/chrono/date_time_utc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use chrono::{DateTime, NaiveDateTime, Utc};

use crate::{
convert::ConvertFrom,
ext::chrono::{DateTimeDecode, DateTimeEncode},
try_convert::{impl_try_convert, TryConvertFrom},
};

impl_try_convert!(DateTime<Utc>, DateTimeEncode, DateTimeDecode);

impl ConvertFrom<&DateTime<Utc>> for DateTimeEncode {
fn convert_from(x: &DateTime<Utc>) -> Self {
DateTimeEncode::convert_from(&x.naive_utc())
}
}

impl TryConvertFrom<DateTimeDecode> for DateTime<Utc> {
fn try_convert_from(enc: DateTimeDecode) -> Result<Self, crate::Error> {
NaiveDateTime::try_convert_from(enc)
.map(|naive| DateTime::from_naive_utc_and_offset(naive, Utc))
}
}

#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use chrono::{DateTime, NaiveDate, Utc};

#[test]
fn test_chrono_datetime_utc() {
let ymds = [
(1970, 1, 1), // epoch
(2025, 10, 6),
(1, 1, 1),
(-44, 3, 15), // BCE
(9999, 12, 31),
];

for &(y, m, d) in ymds.iter() {
let naive = NaiveDate::from_ymd_opt(y, m, d)
.unwrap()
.and_hms_opt(12, 34, 56)
.unwrap();
let dt_utc = DateTime::<Utc>::from_naive_utc_and_offset(naive, Utc);

let enc = crate::encode(&dt_utc);
let decoded: DateTime<Utc> = crate::decode(&enc).unwrap();

assert_eq!(dt_utc, decoded, "failed for datetime {:?}", dt_utc);
}
}

fn bench_data() -> Vec<DateTime<Utc>> {
crate::random_data(1000)
.into_iter()
.map(
|(y, m, d, h, mi, s, n, _offset_sec): (i32, u32, u32, u32, u32, u32, u32, i32)| {
let naive =
NaiveDate::from_ymd_opt((y % 9999).max(1), (m % 12).max(1), (d % 28) + 1)
.unwrap()
.and_hms_nano_opt(h % 24, mi % 60, s % 60, n % 1_000_000_000)
.unwrap();
DateTime::<Utc>::from_naive_utc_and_offset(naive, Utc)
},
)
.collect()
}

crate::bench_encode_decode!(utc_vec: Vec<DateTime<Utc>>);
}
60 changes: 60 additions & 0 deletions src/ext/chrono/naive_date.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use chrono::{Datelike, NaiveDate};

use crate::{
convert::ConvertFrom,
ext::chrono::{DateDecode, DateEncode},
try_convert::{impl_try_convert, TryConvertFrom},
};

impl_try_convert!(NaiveDate, DateEncode, DateDecode);

impl ConvertFrom<&NaiveDate> for DateEncode {
fn convert_from(days: &NaiveDate) -> Self {
days.num_days_from_ce()
}
}

impl TryConvertFrom<DateDecode> for NaiveDate {
fn try_convert_from(days: DateDecode) -> Result<Self, crate::Error> {
NaiveDate::from_num_days_from_ce_opt(days)
.ok_or_else(|| crate::error::error("Failed to convert DateDecode to chrono::NaiveDate"))
}
}

#[cfg(test)]
mod tests {
#[test]
fn test_chrono_naive_date() {
let dates = [
NaiveDate::from_ymd_opt(1970, 1, 1).unwrap(), // epoch
NaiveDate::from_ymd_opt(2025, 10, 6).unwrap(),
NaiveDate::from_ymd_opt(1, 1, 1).unwrap(),
NaiveDate::from_ymd_opt(-44, 3, 15).unwrap(), // BCE
NaiveDate::from_ymd_opt(-44, 3, 15).unwrap(), // BCE
NaiveDate::from_ymd_opt(9999, 12, 31).unwrap(),
];

for x in dates {
let enc = crate::encode(&x);
let date: NaiveDate = crate::decode(&enc).unwrap();

assert_eq!(x, date, "failed for date {:?}", x);
}
}

use alloc::vec::Vec;
use chrono::NaiveDate;

fn bench_data() -> Vec<NaiveDate> {
crate::random_data(1000)
.into_iter()
.map(|(y, m, d): (i32, u32, u32)| {
let year = (y % 9999).max(1); // 1 ~ 9998
let month = (m % 12).max(1); // 1 ~ 12
let day = (d % 28) + 1; // 1 ~ 28
NaiveDate::from_ymd_opt(year, month, day).unwrap()
})
.collect()
}
crate::bench_encode_decode!(data: Vec<_>);
}
Loading
Loading