Skip to content

Commit 7f93b49

Browse files
authored
add support for time::Time (#43)
1 parent dc353a9 commit 7f93b49

8 files changed

Lines changed: 142 additions & 74 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ bitcode_derive = { version = "=0.6.5", path = "./bitcode_derive", optional = tru
2020
bytemuck = { version = "1.14", features = [ "min_const_generics", "must_cast" ] }
2121
glam = { version = ">=0.21", default-features = false, optional = true }
2222
serde = { version = "1.0", default-features = false, features = [ "alloc" ], optional = true }
23+
time = { version = "0.3", default-features = false, optional = true }
2324
uuid = { version = "1.10", default-features = false, optional = true }
2425

2526
[dev-dependencies]

fuzz/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ cargo-fuzz = true
1010

1111
[dependencies]
1212
arrayvec = { version = "0.7", features = ["serde"] }
13-
bitcode = { path = "..", features = [ "arrayvec", "serde" ] }
13+
bitcode = { path = "..", features = [ "arrayvec", "serde", "time" ] }
1414
libfuzzer-sys = "0.4"
1515
serde = { version ="1.0", features = [ "derive" ] }
16+
time = { version = "0.3", features = ["serde"]}
1617

1718
# Prevent this from interfering with workspaces
1819
[workspace]

fuzz/fuzz_targets/fuzz.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::collections::{BTreeMap, HashMap};
99
use std::fmt::Debug;
1010
use std::num::NonZeroU32;
1111
use std::time::Duration;
12-
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddr, SocketAddrV6};
12+
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
1313

1414
#[inline(never)]
1515
fn test_derive<T: Debug + PartialEq + Encode + DecodeOwned>(data: &[u8]) {
@@ -216,5 +216,6 @@ fuzz_target!(|data: &[u8]| {
216216
SocketAddrV4,
217217
SocketAddrV6,
218218
SocketAddr,
219+
time::Time,
219220
);
220221
});

src/derive/convert.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ use core::num::NonZeroUsize;
55
#[allow(unused)]
66
macro_rules! impl_convert {
77
($want: path, $have: ty) => {
8+
impl_convert!($want, $have, $have);
9+
};
10+
($want: path, $have_encode: ty, $have_decode: ty) => {
811
impl crate::derive::Encode for $want {
9-
type Encoder = crate::derive::convert::ConvertIntoEncoder<$have>;
12+
type Encoder = crate::derive::convert::ConvertIntoEncoder<$have_encode>;
1013
}
1114
impl<'a> crate::derive::Decode<'a> for $want {
12-
type Decoder = crate::derive::convert::ConvertFromDecoder<'a, $have>;
15+
type Decoder = crate::derive::convert::ConvertFromDecoder<'a, $have_decode>;
1316
}
1417
};
1518
}

src/derive/duration.rs

Lines changed: 14 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,27 @@
1-
use crate::coder::{Buffer, Decoder, Encoder, Result, View};
2-
use crate::{Decode, Encode};
3-
use alloc::vec::Vec;
4-
use bytemuck::CheckedBitPattern;
5-
use core::num::NonZeroUsize;
1+
use super::convert::{impl_convert, ConvertFrom};
2+
use crate::int::ranged_int;
63
use core::time::Duration;
74

8-
#[derive(Default)]
9-
pub struct DurationEncoder {
10-
secs: <u64 as Encode>::Encoder,
11-
subsec_nanos: <u32 as Encode>::Encoder,
12-
}
13-
impl Encoder<Duration> for DurationEncoder {
14-
#[inline(always)]
15-
fn encode(&mut self, t: &Duration) {
16-
self.secs.encode(&t.as_secs());
17-
self.subsec_nanos.encode(&t.subsec_nanos());
18-
}
19-
}
20-
impl Buffer for DurationEncoder {
21-
fn collect_into(&mut self, out: &mut Vec<u8>) {
22-
self.secs.collect_into(out);
23-
self.subsec_nanos.collect_into(out);
24-
}
5+
ranged_int!(Nanosecond, u32, 0, 999_999_999);
256

26-
fn reserve(&mut self, additional: NonZeroUsize) {
27-
self.secs.reserve(additional);
28-
self.subsec_nanos.reserve(additional);
29-
}
30-
}
31-
impl Encode for Duration {
32-
type Encoder = DurationEncoder;
33-
}
7+
type DurationEncode = (u64, u32);
8+
type DurationDecode = (u64, Nanosecond);
349

35-
/// A u32 guaranteed to be < 1 billion. Prevents Duration::new from panicking.
36-
#[derive(Copy, Clone)]
37-
#[repr(transparent)]
38-
struct Nanoseconds(u32);
39-
// Safety: u32 and Nanoseconds have the same layout since Nanoseconds is #[repr(transparent)].
40-
unsafe impl CheckedBitPattern for Nanoseconds {
41-
type Bits = u32;
10+
impl ConvertFrom<&Duration> for DurationEncode {
4211
#[inline(always)]
43-
fn is_valid_bit_pattern(bits: &Self::Bits) -> bool {
44-
*bits < 1_000_000_000
12+
fn convert_from(value: &Duration) -> Self {
13+
(value.as_secs(), value.subsec_nanos())
4514
}
4615
}
47-
impl<'a> Decode<'a> for Nanoseconds {
48-
type Decoder = crate::int::CheckedIntDecoder<'a, Nanoseconds, u32>;
49-
}
5016

51-
#[derive(Default)]
52-
pub struct DurationDecoder<'a> {
53-
secs: <u64 as Decode<'a>>::Decoder,
54-
subsec_nanos: <Nanoseconds as Decode<'a>>::Decoder,
55-
}
56-
impl<'a> View<'a> for DurationDecoder<'a> {
57-
fn populate(&mut self, input: &mut &'a [u8], length: usize) -> Result<()> {
58-
self.secs.populate(input, length)?;
59-
self.subsec_nanos.populate(input, length)?;
60-
Ok(())
61-
}
62-
}
63-
impl<'a> Decoder<'a, Duration> for DurationDecoder<'a> {
17+
impl ConvertFrom<DurationDecode> for Duration {
6418
#[inline(always)]
65-
fn decode(&mut self) -> Duration {
66-
let secs = self.secs.decode();
67-
let Nanoseconds(subsec_nanos) = self.subsec_nanos.decode();
68-
// Makes Duration::new 4x faster since it can skip checks and division.
69-
// Safety: impl CheckedBitPattern for Nanoseconds guarantees this.
70-
unsafe {
71-
if !Nanoseconds::is_valid_bit_pattern(&subsec_nanos) {
72-
core::hint::unreachable_unchecked();
73-
}
74-
}
75-
Duration::new(secs, subsec_nanos)
19+
fn convert_from(value: DurationDecode) -> Self {
20+
Duration::new(value.0, value.1.into_inner())
7621
}
7722
}
78-
impl<'a> Decode<'a> for Duration {
79-
type Decoder = DurationDecoder<'a>;
80-
}
23+
24+
impl_convert!(Duration, DurationEncode, DurationDecode);
8125

8226
#[cfg(test)]
8327
mod tests {
@@ -95,5 +39,5 @@ mod tests {
9539
.map(|(s, n): (_, u32)| Duration::new(s, n % 1_000_000_000))
9640
.collect()
9741
}
98-
crate::bench_encode_decode!(duration_vec: Vec<_>);
42+
crate::bench_encode_decode!(duration_vec: Vec<Duration>);
9943
}

src/ext/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ mod arrayvec;
33
#[cfg(feature = "glam")]
44
#[rustfmt::skip] // Makes impl_struct! calls way longer.
55
mod glam;
6+
#[cfg(feature = "time")]
7+
mod time;
68
#[cfg(feature = "uuid")]
79
mod uuid;
810

src/ext/time.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use crate::convert::{impl_convert, ConvertFrom};
2+
use crate::int::ranged_int;
3+
use time::Time;
4+
5+
ranged_int!(Hour, u8, 0, 23);
6+
ranged_int!(Minute, u8, 0, 59);
7+
ranged_int!(Second, u8, 0, 59);
8+
ranged_int!(Nanosecond, u32, 0, 999_999_999);
9+
10+
type TimeEncode = (u8, u8, u8, u32);
11+
type TimeDecode = (Hour, Minute, Second, Nanosecond);
12+
impl_convert!(Time, TimeEncode, TimeDecode);
13+
14+
impl ConvertFrom<&Time> for TimeEncode {
15+
#[inline(always)]
16+
fn convert_from(value: &Time) -> Self {
17+
value.as_hms_nano()
18+
}
19+
}
20+
21+
impl ConvertFrom<TimeDecode> for Time {
22+
#[inline(always)]
23+
fn convert_from(value: TimeDecode) -> Self {
24+
Time::from_hms_nano(
25+
value.0.into_inner(),
26+
value.1.into_inner(),
27+
value.2.into_inner(),
28+
value.3.into_inner(),
29+
)
30+
.unwrap()
31+
}
32+
}
33+
34+
#[cfg(test)]
35+
mod tests {
36+
#[test]
37+
fn test() {
38+
assert!(crate::decode::<Time>(&crate::encode(
39+
&Time::from_hms_nano(23, 59, 59, 999_999_999).unwrap()
40+
))
41+
.is_ok());
42+
assert!(crate::decode::<Time>(&crate::encode(&(23u8, 59u8, 59u8, 999_999_999u32))).is_ok());
43+
assert!(
44+
crate::decode::<Time>(&crate::encode(&(24u8, 59u8, 59u8, 999_999_999u32))).is_err()
45+
);
46+
assert!(
47+
crate::decode::<Time>(&crate::encode(&(23u8, 60u8, 59u8, 999_999_999u32))).is_err()
48+
);
49+
assert!(
50+
crate::decode::<Time>(&crate::encode(&(23u8, 59u8, 60u8, 999_999_999u32))).is_err()
51+
);
52+
assert!(
53+
crate::decode::<Time>(&crate::encode(&(23u8, 59u8, 59u8, 1_000_000_000u32))).is_err()
54+
);
55+
}
56+
57+
use alloc::vec::Vec;
58+
use time::Time;
59+
fn bench_data() -> Vec<Time> {
60+
crate::random_data(1000)
61+
.into_iter()
62+
.map(|(h, m, s, n): (u8, u8, u8, u32)| {
63+
Time::from_hms_nano(h % 24, m % 60, s % 60, n % 1_000_000_000).unwrap()
64+
})
65+
.collect()
66+
}
67+
crate::bench_encode_decode!(duration_vec: Vec<_>);
68+
}

src/int.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,54 @@ where
127127
}
128128
}
129129

130+
/// Prevents callers of `ranged_int` from accessing `.0` in the same source file.
131+
#[derive(Copy, Clone)]
132+
#[repr(transparent)]
133+
pub struct Private<T>(T);
134+
135+
impl<T> Private<T> {
136+
#[inline(always)]
137+
pub fn into_inner(self) -> T {
138+
self.0
139+
}
140+
}
141+
142+
#[allow(unused)]
143+
macro_rules! ranged_int {
144+
($type: ident, $int: ty, $lower: expr, $upper: expr) => {
145+
#[derive(Copy, Clone)]
146+
#[repr(transparent)]
147+
pub struct $type(crate::int::Private<$int>);
148+
// Safety: They have the same layout because of #[repr(transparent)].
149+
unsafe impl bytemuck::CheckedBitPattern for $type {
150+
type Bits = $int;
151+
#[inline(always)]
152+
fn is_valid_bit_pattern(bits: &Self::Bits) -> bool {
153+
($lower..=$upper).contains(bits)
154+
}
155+
}
156+
impl $type {
157+
#[inline(always)]
158+
pub fn into_inner(self) -> $int {
159+
if !<Self as bytemuck::CheckedBitPattern>::is_valid_bit_pattern(
160+
&self.0.into_inner(),
161+
) {
162+
// Safety: only created subject to `CheckedBitPattern`.
163+
unsafe { core::hint::unreachable_unchecked() };
164+
}
165+
self.0.into_inner()
166+
}
167+
}
168+
169+
impl<'a> crate::derive::Decode<'a> for $type {
170+
type Decoder = crate::int::CheckedIntDecoder<'a, $type, $int>;
171+
}
172+
};
173+
}
174+
175+
#[allow(unused)]
176+
pub(crate) use ranged_int;
177+
130178
#[cfg(test)]
131179
mod tests {
132180
use crate::{decode, encode};

0 commit comments

Comments
 (0)