Skip to content
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ 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 }
rust_decimal = { version = "1.36", default-features = false, optional = true }
serde = { version = "1.0", default-features = false, features = [ "alloc" ], optional = true }
Expand Down
6 changes: 4 additions & 2 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ 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"] }
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"] }


# Prevent this from interfering with workspaces
[workspace]
Expand All @@ -24,4 +26,4 @@ members = ["."]
name = "fuzz"
path = "fuzz_targets/fuzz.rs"
test = false
doc = false
doc = false
Binary file not shown.
22 changes: 18 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,9 @@ fuzz_target!(|data: &[u8]| {
SocketAddrV6,
SocketAddr,
time::Time,
chrono::NaiveTime,
chrono::NaiveDate,
chrono::NaiveDateTime,
chrono::DateTime<chrono::Utc>,
);
});
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::{impl_convert, ConvertFrom},
ext::chrono::{DateTimeDecode, DateTimeEncode},
};

impl_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 ConvertFrom<DateTimeDecode> for DateTime<Utc> {
fn convert_from(enc: DateTimeDecode) -> Self {
let naive = NaiveDateTime::convert_from(enc);

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>>);
}
58 changes: 58 additions & 0 deletions src/ext/chrono/naive_date.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use chrono::{Datelike, NaiveDate};

use crate::{
convert::{impl_convert, ConvertFrom},
ext::chrono::{DateDecode, DateEncode},
};

impl_convert!(NaiveDate, DateEncode, DateDecode);

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

impl ConvertFrom<DateDecode> for NaiveDate {
fn convert_from(days: DateDecode) -> Self {
NaiveDate::from_num_days_from_ce_opt(days).unwrap()
}
}

#[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<_>);
}
85 changes: 85 additions & 0 deletions src/ext/chrono/naive_date_time.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};

use crate::{
convert::{impl_convert, ConvertFrom},
ext::chrono::{DateEncode, DateTimeDecode, DateTimeEncode, TimeEncode},
};

impl_convert!(NaiveDateTime, DateTimeEncode, DateTimeDecode);

impl ConvertFrom<&NaiveDateTime> for DateTimeEncode {
#[inline(always)]
fn convert_from(x: &NaiveDateTime) -> Self {
(
DateEncode::convert_from(&x.date()),
TimeEncode::convert_from(&x.time()),
)
}
}

impl ConvertFrom<DateTimeDecode> for NaiveDateTime {
#[inline(always)]
fn convert_from((date, time): DateTimeDecode) -> Self {
NaiveDateTime::new(NaiveDate::convert_from(date), NaiveTime::convert_from(time))
}
}

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

use crate::decode;
use crate::encode;

#[test]
fn test_chrono_naive_datetime() {
let dt = NaiveDateTime::new(
NaiveDate::from_ymd_opt(2025, 10, 6).unwrap(),
NaiveTime::from_hms_nano_opt(12, 34, 56, 123_456_789).unwrap(),
);

let encoded = encode(&dt);
let decoded: NaiveDateTime = decode(&encoded).unwrap();

assert_eq!(dt, decoded);

let dt2 = NaiveDateTime::new(
NaiveDate::from_ymd_opt(1, 1, 1).unwrap(),
NaiveTime::from_hms_nano_opt(0, 0, 0, 0).unwrap(),
);
let encoded2 = encode(&dt2);
let decoded2: NaiveDateTime = decode(&encoded2).unwrap();
assert_eq!(dt2, decoded2);
}

fn bench_data() -> Vec<NaiveDateTime> {
crate::random_data(1000)
.into_iter()
.map(
|(y, m, d, h, min, s, n): (i32, u32, u32, u8, u8, u8, u32)| {
let year = (y % 9999).max(1);
let month = (m % 12).max(1);
let day = (d % 28) + 1;
let date = NaiveDate::from_ymd_opt(year, month, day).unwrap();

let hour = h % 24;
let minute = min % 60;
let second = s % 60;
let nano = n % 1_000_000_000;
let time = NaiveTime::from_hms_nano_opt(
hour as u32,
minute as u32,
second as u32,
nano,
)
.unwrap();

NaiveDateTime::new(date, time)
},
)
.collect()
}

crate::bench_encode_decode!(data_vec: Vec<_>);
}
77 changes: 77 additions & 0 deletions src/ext/chrono/naive_time.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use crate::{
convert::{impl_convert, ConvertFrom},
ext::chrono::{TimeDecode, TimeEncode},
};
use chrono::{NaiveTime, Timelike};

impl_convert!(NaiveTime, TimeEncode, TimeDecode);

impl ConvertFrom<&NaiveTime> for TimeEncode {
#[inline(always)]
fn convert_from(value: &NaiveTime) -> Self {
(
value.hour() as u8,
value.minute() as u8,
value.second() as u8,
value.nanosecond(),
)
}
}

impl ConvertFrom<TimeDecode> for NaiveTime {
#[inline(always)]
fn convert_from(value: TimeDecode) -> Self {
let (hour, min, sec, nano) = value;

NaiveTime::from_hms_nano_opt(
hour.into_inner() as u32,
min.into_inner() as u32,
sec.into_inner() as u32,
nano.into_inner(),
)
.unwrap()
}
}

#[cfg(test)]
mod tests {
#[test]
fn test_chrono_naive_time() {
assert!(crate::decode::<NaiveTime>(&crate::encode(
&NaiveTime::from_hms_nano_opt(23, 59, 59, 999_999_999).unwrap()
))
.is_ok());
assert!(
crate::decode::<NaiveTime>(&crate::encode(&(23u8, 59u8, 59u8, 999_999_999u32))).is_ok()
);
assert!(
crate::decode::<NaiveTime>(&crate::encode(&(24u8, 59u8, 59u8, 999_999_999u32)))
.is_err()
);
assert!(
crate::decode::<NaiveTime>(&crate::encode(&(23u8, 60u8, 59u8, 999_999_999u32)))
.is_err()
);
assert!(
crate::decode::<NaiveTime>(&crate::encode(&(23u8, 59u8, 60u8, 999_999_999u32)))
.is_err()
);
assert!(
crate::decode::<NaiveTime>(&crate::encode(&(23u8, 59u8, 59u8, 1_000_000_000u32)))
.is_err()
);
}

use alloc::vec::Vec;
use chrono::NaiveTime;
use time::Time;
fn bench_data() -> Vec<Time> {
crate::random_data(1000)
.into_iter()
.map(|(h, m, s, n): (u8, u8, u8, u32)| {
Time::from_hms_nano(h % 24, m % 60, s % 60, n % 1_000_000_000).unwrap()
})
.collect()
}
crate::bench_encode_decode!(duration_vec: Vec<_>);
}
Loading
Loading