messagepack_core/timestamp/
mod.rs

1//! MessagePack timestamp extension values.
2
3mod decode;
4mod encode;
5use crate::extension::{ExtensionRef, FixedExtension};
6
7pub(crate) const TIMESTAMP_EXTENSION_TYPE: i8 = -1;
8
9/// The error type returned when a checked extension conversion fails
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
11pub enum TryFromTimestampError {
12    /// The format is not a valid timestamp type
13    InvalidType,
14    /// The data length is not valid for timestamp format
15    InvalidDataLength,
16    /// The payload contains invalid field values (e.g. nanoseconds overflow)
17    InvalidData,
18}
19
20impl core::fmt::Display for TryFromTimestampError {
21    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
22        match self {
23            TryFromTimestampError::InvalidType => write!(f, "invalid timestamp extension type"),
24            TryFromTimestampError::InvalidDataLength => write!(f, "invalid timestamp data length"),
25            TryFromTimestampError::InvalidData => write!(f, "invalid timestamp data fields"),
26        }
27    }
28}
29
30impl core::error::Error for TryFromTimestampError {}
31
32/// Represents timestamp 32 extension type.
33/// This stores 32bit unsigned seconds
34#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
35pub struct Timestamp32 {
36    secs: u32,
37}
38
39impl Timestamp32 {
40    /// Create a 32‑bit seconds timestamp.
41    pub fn new(seconds: u32) -> Self {
42        Self { secs: seconds }
43    }
44
45    /// Get seconds since the UNIX epoch.
46    pub fn seconds(&self) -> u32 {
47        self.secs
48    }
49
50    pub(crate) fn to_buf(self) -> [u8; 4] {
51        self.secs.to_be_bytes()
52    }
53
54    pub(crate) fn from_buf(buf: [u8; 4]) -> Self {
55        Self {
56            secs: u32::from_be_bytes(buf),
57        }
58    }
59}
60
61impl TryFrom<ExtensionRef<'_>> for Timestamp32 {
62    type Error = TryFromTimestampError;
63
64    fn try_from(value: ExtensionRef<'_>) -> Result<Self, Self::Error> {
65        if value.r#type != TIMESTAMP_EXTENSION_TYPE {
66            return Err(TryFromTimestampError::InvalidType);
67        }
68
69        let data = value.data;
70        let mut buf = [0u8; 4];
71        if data.len() != buf.len() {
72            return Err(TryFromTimestampError::InvalidDataLength);
73        }
74
75        buf.copy_from_slice(data);
76        Ok(Self::from_buf(buf))
77    }
78}
79
80impl TryFrom<FixedExtension<4>> for Timestamp32 {
81    type Error = TryFromTimestampError;
82
83    fn try_from(value: FixedExtension<4>) -> Result<Self, Self::Error> {
84        value.as_ref().try_into()
85    }
86}
87
88impl From<Timestamp32> for FixedExtension<4> {
89    fn from(value: Timestamp32) -> Self {
90        let buf = value.to_buf();
91        FixedExtension::new_fixed(TIMESTAMP_EXTENSION_TYPE, buf)
92    }
93}
94
95impl From<Timestamp32> for core::time::Duration {
96    fn from(value: Timestamp32) -> Self {
97        core::time::Duration::from_secs(value.seconds().into())
98    }
99}
100
101impl TryFrom<core::time::Duration> for Timestamp32 {
102    type Error = core::num::TryFromIntError;
103    fn try_from(value: core::time::Duration) -> Result<Self, Self::Error> {
104        let sec = value.as_secs();
105        u32::try_from(sec).map(Self::new)
106    }
107}
108
109/// MessagePack spec says timestamp64/96 nanoseconds must not be larger than 999_999_999.
110///
111/// > In timestamp 64 and timestamp 96 formats, nanoseconds must not be larger than 999999999.
112pub(crate) const TIMESTAMP_NANO_MAX: u32 = 999_999_999;
113
114/// Represents timestamp 64 extension type.
115/// This stores 34bit unsigned seconds and 30bit nanoseconds
116#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
117pub struct Timestamp64 {
118    data: [u8; 8],
119}
120
121/// `seconds` or `nanos` cannot be represented
122#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
123pub enum ConstructTimestampError {
124    /// Requested seconds that exceeded the timestamp bit range.
125    ExceedSeconds,
126    /// Requested nanoseconds that exceeded 999999999.
127    ExceedNanos,
128}
129
130impl core::fmt::Display for ConstructTimestampError {
131    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
132        match self {
133            ConstructTimestampError::ExceedSeconds => {
134                write!(f, "seconds exceed representable range")
135            }
136            ConstructTimestampError::ExceedNanos => {
137                write!(f, "nanoseconds exceed {}", TIMESTAMP_NANO_MAX)
138            }
139        }
140    }
141}
142
143impl core::error::Error for ConstructTimestampError {}
144
145impl Timestamp64 {
146    /// Create a 64‑bit timestamp storing 34‑bit seconds and 30‑bit nanoseconds.
147    pub fn new(seconds: u64, nanos: u32) -> Result<Self, ConstructTimestampError> {
148        const SECONDS_MAX_LIMIT: u64 = 1 << 34;
149
150        if seconds >= SECONDS_MAX_LIMIT {
151            return Err(ConstructTimestampError::ExceedSeconds);
152        }
153
154        if nanos > TIMESTAMP_NANO_MAX {
155            return Err(ConstructTimestampError::ExceedNanos);
156        }
157
158        let mut buf = [0u8; 8];
159        buf[..].copy_from_slice(&seconds.to_be_bytes());
160
161        let nano = (nanos << 2).to_be_bytes();
162        buf[..3].copy_from_slice(&nano[..3]);
163        // Keep lower 2 bits of seconds; overlay top 6 bits from nanos
164        buf[3] = (buf[3] & 0b0000_0011) | (nano[3] & 0b1111_1100);
165
166        Ok(Self::from_buf(buf))
167    }
168
169    /// Get the nanoseconds component.
170    pub fn nanos(&self) -> u32 {
171        let mut buf = [0u8; 4];
172        buf.copy_from_slice(&self.data[..4]);
173        let nanosec = u32::from_be_bytes(buf);
174        nanosec >> 2
175    }
176
177    /// Get the seconds component.
178    pub fn seconds(&self) -> u64 {
179        // 34bit mask
180        const MASK: u64 = (1 << 34) - 1;
181        let mut buf = [0u8; 8];
182        buf.copy_from_slice(&self.data[..]);
183        let seconds = u64::from_be_bytes(buf);
184
185        seconds & MASK
186    }
187
188    pub(crate) fn to_buf(self) -> [u8; 8] {
189        self.data
190    }
191
192    pub(crate) fn from_buf(buf: [u8; 8]) -> Self {
193        Self { data: buf }
194    }
195}
196
197impl TryFrom<ExtensionRef<'_>> for Timestamp64 {
198    type Error = TryFromTimestampError;
199
200    fn try_from(value: ExtensionRef<'_>) -> Result<Self, Self::Error> {
201        if value.r#type != TIMESTAMP_EXTENSION_TYPE {
202            return Err(TryFromTimestampError::InvalidType);
203        }
204
205        let data = value.data;
206        let mut buf = [0u8; 8];
207        if data.len() != buf.len() {
208            return Err(TryFromTimestampError::InvalidDataLength);
209        }
210
211        buf.copy_from_slice(data);
212        let decoded = Self::from_buf(buf);
213        // Validate fields via constructor to enforce invariants
214        Self::new(decoded.seconds(), decoded.nanos())
215            .map_err(|_| TryFromTimestampError::InvalidData)
216    }
217}
218
219impl TryFrom<FixedExtension<8>> for Timestamp64 {
220    type Error = TryFromTimestampError;
221
222    fn try_from(value: FixedExtension<8>) -> Result<Self, Self::Error> {
223        value.as_ref().try_into()
224    }
225}
226
227impl From<Timestamp64> for FixedExtension<8> {
228    fn from(value: Timestamp64) -> Self {
229        let buf = value.to_buf();
230        FixedExtension::new_fixed(TIMESTAMP_EXTENSION_TYPE, buf)
231    }
232}
233
234impl From<Timestamp64> for core::time::Duration {
235    fn from(value: Timestamp64) -> Self {
236        let sec = value.seconds();
237        let nano = value.nanos();
238        core::time::Duration::from_secs(sec) + core::time::Duration::from_nanos(nano.into())
239    }
240}
241
242impl TryFrom<core::time::Duration> for Timestamp64 {
243    type Error = ConstructTimestampError;
244
245    fn try_from(value: core::time::Duration) -> Result<Self, Self::Error> {
246        let secs = value.as_secs();
247        let nanos = value.subsec_nanos();
248        Timestamp64::new(secs, nanos)
249    }
250}
251
252/// Represents timestamp 96 extension type.
253/// This stores 64bit signed seconds and 32bit nanoseconds
254#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
255pub struct Timestamp96 {
256    nanos: u32,
257    secs: i64,
258}
259
260impl Timestamp96 {
261    /// Create a 96‑bit timestamp storing signed seconds and nanoseconds.
262    pub fn new(seconds: i64, nanoseconds: u32) -> Result<Self, ConstructTimestampError> {
263        if nanoseconds > TIMESTAMP_NANO_MAX {
264            return Err(ConstructTimestampError::ExceedNanos);
265        }
266        Ok(Self {
267            nanos: nanoseconds,
268            secs: seconds,
269        })
270    }
271
272    /// Get the nanoseconds component.
273    pub fn nanos(&self) -> u32 {
274        self.nanos
275    }
276
277    /// Get the seconds component.
278    pub fn seconds(&self) -> i64 {
279        self.secs
280    }
281
282    pub(crate) fn to_buf(self) -> [u8; 12] {
283        let mut buf = [0u8; 12];
284        buf[..4].copy_from_slice(&self.nanos.to_be_bytes());
285        buf[4..].copy_from_slice(&self.secs.to_be_bytes());
286
287        buf
288    }
289
290    pub(crate) fn from_buf(buf: [u8; 12]) -> Self {
291        let mut nano = [0u8; 4];
292        nano.copy_from_slice(&buf[..4]);
293
294        let mut second = [0u8; 8];
295        second.copy_from_slice(&buf[4..]);
296
297        Self {
298            nanos: u32::from_be_bytes(nano),
299            secs: i64::from_be_bytes(second),
300        }
301    }
302}
303
304impl TryFrom<ExtensionRef<'_>> for Timestamp96 {
305    type Error = TryFromTimestampError;
306
307    fn try_from(value: ExtensionRef<'_>) -> Result<Self, Self::Error> {
308        if value.r#type != TIMESTAMP_EXTENSION_TYPE {
309            return Err(TryFromTimestampError::InvalidType);
310        }
311
312        let data = value.data;
313        let mut buf = [0u8; 12];
314        if data.len() != buf.len() {
315            return Err(TryFromTimestampError::InvalidDataLength);
316        }
317
318        buf.copy_from_slice(data);
319        let decoded = Self::from_buf(buf);
320        // Validate fields via constructor to enforce invariants
321        Self::new(decoded.seconds(), decoded.nanos())
322            .map_err(|_| TryFromTimestampError::InvalidData)
323    }
324}
325
326impl TryFrom<FixedExtension<12>> for Timestamp96 {
327    type Error = TryFromTimestampError;
328
329    fn try_from(value: FixedExtension<12>) -> Result<Self, Self::Error> {
330        value.as_ref().try_into()
331    }
332}
333
334impl From<Timestamp96> for FixedExtension<12> {
335    fn from(value: Timestamp96) -> Self {
336        let buf = value.to_buf();
337        FixedExtension::new_fixed(TIMESTAMP_EXTENSION_TYPE, buf)
338    }
339}
340
341impl TryFrom<Timestamp96> for core::time::Duration {
342    type Error = core::num::TryFromIntError;
343
344    fn try_from(value: Timestamp96) -> Result<Self, Self::Error> {
345        let secs = u64::try_from(value.seconds())?;
346        let nanos = value.nanos();
347
348        Ok(core::time::Duration::from_secs(secs) + core::time::Duration::from_nanos(nanos.into()))
349    }
350}
351
352impl TryFrom<core::time::Duration> for Timestamp96 {
353    type Error = ConstructTimestampError;
354
355    fn try_from(value: core::time::Duration) -> Result<Self, Self::Error> {
356        let secs =
357            i64::try_from(value.as_secs()).map_err(|_| ConstructTimestampError::ExceedSeconds)?;
358        let nanos = value.subsec_nanos();
359        Self::new(secs, nanos)
360    }
361}
362
363#[cfg(test)]
364mod duration_tests {
365    use super::*;
366    use rstest::rstest;
367
368    #[rstest]
369    fn duration_to_timestamp32_roundtrip_within_range() {
370        let d = core::time::Duration::from_secs(123);
371        let ts32 = Timestamp32::try_from(d).unwrap();
372        assert_eq!(ts32.seconds(), 123);
373        let back: core::time::Duration = ts32.into();
374        assert_eq!(back.as_secs(), 123);
375        assert_eq!(back.subsec_nanos(), 0);
376    }
377
378    #[rstest]
379    fn duration_to_timestamp64_roundtrip() {
380        let d = core::time::Duration::from_secs(1_234_567) + core::time::Duration::from_nanos(890);
381        let ts64 = Timestamp64::try_from(d).unwrap();
382        assert_eq!(ts64.seconds(), 1_234_567);
383        assert_eq!(ts64.nanos(), 890);
384        let back: core::time::Duration = ts64.into();
385        assert_eq!(back, d);
386    }
387
388    #[rstest]
389    fn timestamp96_to_duration_fails_on_negative() {
390        let ts96 = Timestamp96::new(-1, 0).unwrap();
391        let res: Result<core::time::Duration, core::num::TryFromIntError> =
392            core::time::Duration::try_from(ts96);
393        assert!(res.is_err());
394    }
395
396    #[rstest]
397    fn duration_to_timestamp96_roundtrip() {
398        let d = core::time::Duration::from_secs(12_345) + core::time::Duration::from_nanos(678_901);
399        let ts = Timestamp96::try_from(d).unwrap();
400        assert_eq!(ts.seconds(), 12_345);
401        assert_eq!(ts.nanos(), 678_901);
402        let back = core::time::Duration::try_from(ts).unwrap();
403        assert_eq!(back, d);
404    }
405
406    #[rstest]
407    fn timestamp64_new_rejects_invalid_nanos() {
408        let err = Timestamp64::new(0, 1_000_000_000).unwrap_err();
409        assert_eq!(err, ConstructTimestampError::ExceedNanos);
410    }
411
412    #[rstest]
413    fn timestamp96_new_rejects_invalid_nanos() {
414        let err = Timestamp96::new(0, 1_000_000_000).unwrap_err();
415        assert_eq!(err, ConstructTimestampError::ExceedNanos);
416    }
417}
418
419// Submodules provide Encode/Decode impls and their tests.
420// tests are colocated in submodules