Skip to content

Commit bac67df

Browse files
committed
WIP: Adding Timestamp decode/encode
1 parent 114b78f commit bac67df

File tree

3 files changed

+198
-2
lines changed

3 files changed

+198
-2
lines changed

rmp/src/decode/mod.rs

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use std::error;
3131

3232
use num_traits::cast::FromPrimitive;
3333

34-
use crate::Marker;
34+
use crate::{Marker, Timestamp};
3535

3636
pub mod bytes;
3737
pub use bytes::Bytes;
@@ -244,6 +244,79 @@ pub fn read_marker<R: RmpRead>(rd: &mut R) -> Result<Marker, MarkerReadError<R::
244244
Ok(Marker::from_u8(rd.read_u8()?))
245245
}
246246

247+
/// Attempts to read an Extension struct from the given reader and to decode it as a Timestamp value.
248+
///
249+
/// According to the MessagePack specification, there are 3 types of Timestamp, 32, 64, and 96.
250+
///
251+
/// # Errors
252+
///
253+
/// This function will return `ValueReadError` on any I/O error while reading the Timestamp,
254+
/// except the EINTR, which is handled internally.
255+
///
256+
/// It also returns `ValueReadError::TypeMismatch` if the actual type is not equal with the
257+
/// expected one, indicating you with the actual type.
258+
///
259+
/// # Note
260+
///
261+
/// This function will silently retry on every EINTR received from the underlying `Read` until
262+
/// successful read.
263+
///
264+
/// # Examples
265+
///
266+
/// ```
267+
/// use rmp::Timestamp;
268+
///
269+
/// // FixExt4 with a type of -1 (0xff)
270+
/// let mut buf1 = [0xd6, 0xff, 0x66, 0xc1, 0xde, 0x7c];
271+
/// // FixExt8 with a type of -1 (0xff)
272+
/// let mut buf2 = [0xd7, 0xff, 0xee, 0x6b, 0x27, 0xfc, 0x66, 0xc1, 0xde, 0x7c];
273+
/// // Ext8 with a size of 12 (0x0c) and a type of -1 (0xff)
274+
/// let mut buf3 = [0xc7, 0x0c, 0xff, 0x3b, 0x9a, 0xc9, 0xff, 0x00, 0x00, 0x00, 0x00, 0x66, 0xc1, 0xde, 0x7c];
275+
///
276+
/// let ts1_expected = Timestamp::from_32(0x66c1de7c);
277+
/// let ts2_expected = Timestamp::from_64(0x66c1de7c, 0x3b9ac9ff).unwrap();
278+
/// let ts3_expected = Timestamp::from_96(0x66c1de7c, 0x3b9ac9ff).unwrap();
279+
///
280+
/// let ts1_result = rmp::decode::read_timestamp(&mut buf1.as_slice()).unwrap();
281+
/// let ts2_result = rmp::decode::read_timestamp(&mut buf2.as_slice()).unwrap();
282+
/// let ts3_result = rmp::decode::read_timestamp(&mut buf3.as_slice()).unwrap();
283+
///
284+
/// assert_eq!(ts1_expected, ts1_result);
285+
/// assert_eq!(ts2_expected, ts2_result);
286+
/// assert_eq!(ts3_expected, ts3_result);
287+
/// ```
288+
pub fn read_timestamp<R: RmpRead>(rd: &mut R) -> Result<Timestamp, ValueReadError<R::Error>> {
289+
let marker = read_marker(rd)?;
290+
let prefix = rd.read_data_i8()?;
291+
match (marker, prefix) {
292+
// timestamp 32
293+
(Marker::FixExt4, -1) => {
294+
let secs = rd.read_data_u32()?;
295+
Ok(Timestamp::from_32(secs))
296+
},
297+
// timestamp 64
298+
(Marker::FixExt8, -1) => {
299+
let data = rd.read_data_u64()?;
300+
Timestamp::from_combined_64(data)
301+
.ok_or(ValueReadError::TypeMismatch(Marker::Reserved))
302+
},
303+
// timestamp 96
304+
(Marker::Ext8, 12) => {
305+
let prefix = rd.read_data_i8()?;
306+
if prefix == -1 {
307+
let nsecs = rd.read_data_u32()?;
308+
let secs = rd.read_data_i64()?;
309+
Timestamp::from_96(secs, nsecs)
310+
.ok_or(ValueReadError::TypeMismatch(Marker::Reserved))
311+
} else {
312+
Err(ValueReadError::TypeMismatch(Marker::Reserved))
313+
}
314+
},
315+
(Marker::Ext8 | Marker::FixExt4 | Marker::FixExt8, _) => Err(ValueReadError::TypeMismatch(Marker::Reserved)),
316+
(marker, _) => Err(ValueReadError::TypeMismatch(marker)),
317+
}
318+
}
319+
247320
/// Attempts to read a single byte from the given reader and to decode it as a nil value.
248321
///
249322
/// According to the MessagePack specification, a nil value is represented as a single `0xc0` byte.

rmp/src/encode/mod.rs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use core::fmt::{self, Debug, Display, Formatter};
1919
#[cfg(feature = "std")]
2020
use std::error;
2121

22-
use crate::Marker;
22+
use crate::{Marker, Timestamp};
2323

2424
pub mod buffer;
2525
pub use buffer::ByteBuf;
@@ -86,6 +86,64 @@ pub fn write_nil<W: RmpWrite>(wr: &mut W) -> Result<(), W::Error> {
8686
write_marker(wr, Marker::Null).map_err(|e| e.0)
8787
}
8888

89+
/// Encodes and attempts to write a timestamp value into the given write.
90+
///
91+
/// According to the MessagePack specification, a timestamp value is represented as a 32, 64, or 96 bit Extension struct.
92+
///
93+
/// # Errors
94+
///
95+
/// This function will return `Error` on any I/O error occurred while writing the timestamp.
96+
///
97+
/// # Examples
98+
///
99+
/// ```
100+
/// use rmp::Timestamp;
101+
///
102+
/// let mut buf1 = Vec::new();
103+
/// let mut buf2 = Vec::new();
104+
/// let mut buf3 = Vec::new();
105+
///
106+
/// let ts1 = Timestamp::from_32(0x66c1de7c);
107+
/// let ts2 = Timestamp::from_64(0x66c1de7c, 0x3b9ac9ff).unwrap();
108+
/// let ts3 = Timestamp::from_96(0x66c1de7c, 0x3b9ac9ff).unwrap();
109+
///
110+
/// rmp::encode::write_timestamp(&mut buf1, ts1).ok();
111+
/// rmp::encode::write_timestamp(&mut buf2, ts2).ok();
112+
/// rmp::encode::write_timestamp(&mut buf3, ts3).ok();
113+
///
114+
/// // FixExt4 with a type of -1 (0xff)
115+
/// assert_eq!(vec![0xd6, 0xff, 0x66, 0xc1, 0xde, 0x7c], buf1);
116+
/// // FixExt8 with a type of -1 (0xff)
117+
/// assert_eq!(vec![0xd7, 0xff, 0xee, 0x6b, 0x27, 0xfc, 0x66, 0xc1, 0xde, 0x7c], buf2);
118+
/// // Ext8 with a size of 12 (0x0c) and a type of -1 (0xff)
119+
/// assert_eq!(vec![0xc7, 0x0c, 0xff, 0x3b, 0x9a, 0xc9, 0xff, 0x00, 0x00, 0x00, 0x00, 0x66, 0xc1, 0xde, 0x7c], buf3);
120+
/// ```
121+
#[inline]
122+
pub fn write_timestamp<W: RmpWrite>(wr: &mut W, timestamp: Timestamp) -> Result<(), DataWriteError<W::Error>> {
123+
match timestamp.size {
124+
32 => {
125+
write_marker(wr, Marker::FixExt4).map_err(|e| e.0)?;
126+
wr.write_data_i8(-1)?;
127+
wr.write_data_u32(timestamp.secs as u32)?;
128+
},
129+
64 => {
130+
write_marker(wr, Marker::FixExt8).map_err(|e| e.0)?;
131+
let data = ((timestamp.nsecs as u64) << 34) | (timestamp.secs as u64);
132+
wr.write_data_i8(-1)?;
133+
wr.write_data_u64(data)?;
134+
},
135+
96 => {
136+
write_marker(wr, Marker::Ext8).map_err(|e| e.0)?;
137+
wr.write_data_u8(12)?;
138+
wr.write_data_i8(-1)?;
139+
wr.write_data_u32(timestamp.nsecs)?;
140+
wr.write_data_i64(timestamp.secs)?;
141+
},
142+
_ => unreachable!(),
143+
}
144+
Ok(())
145+
}
146+
89147
/// Encodes and attempts to write a bool value into the given write.
90148
///
91149
/// According to the MessagePack specification, an encoded boolean value is represented as a single

rmp/src/lib.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,68 @@ pub use crate::marker::Marker;
1212

1313
/// Version of the MessagePack [spec](http://github.com/msgpack/msgpack/blob/master/spec.md).
1414
pub const MSGPACK_VERSION: u32 = 5;
15+
16+
/// A type for holding Timestamp information
17+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
18+
pub struct Timestamp {
19+
size: u8,
20+
secs: i64,
21+
nsecs: u32,
22+
}
23+
24+
impl Timestamp {
25+
/// Get the size of the Timestamp in bits
26+
#[inline]
27+
pub fn get_bitsize(self) -> u8 {
28+
self.size
29+
}
30+
31+
/// Get the data to pass to chrono::DateTime::from_timestamp
32+
#[inline]
33+
pub fn into_timestamp(self) -> (i64, u32) {
34+
(
35+
self.secs,
36+
self.nsecs,
37+
)
38+
}
39+
40+
/// Create a 32 bit timestamp using FixExt4
41+
#[inline]
42+
pub fn from_32(secs: u32) -> Self {
43+
Self { size: 32, secs: i64::from(secs), nsecs: 0 }
44+
}
45+
46+
/// Create a 64 bit timestamp using FixExt8 with seconds and nanoseconds passed separately
47+
#[inline]
48+
pub fn from_64(secs: i64, nsecs: u32) -> Option<Self> {
49+
if secs < 0 || secs > 0x3_ffff_ffff || nsecs > 999_999_999 {
50+
None
51+
} else {
52+
Some(Self { size: 64, secs, nsecs })
53+
}
54+
}
55+
56+
/// Create a 64 bit timestamp using FixExt8 from the combined 64 bit data
57+
#[inline]
58+
pub fn from_combined_64(data: u64) -> Option<Self> {
59+
// 30 bits fits in u32
60+
let nsecs = (data >> 34) as u32;
61+
if nsecs > 999_999_999 {
62+
return None
63+
}
64+
// 34 bits fits in i64
65+
let secs = (data & 0x3_ffff_ffff) as i64;
66+
67+
Some(Self { size: 64, secs, nsecs })
68+
}
69+
70+
/// Create a 96 bit timestamp using Ext8 (len=12)
71+
#[inline]
72+
pub fn from_96(secs: i64, nsecs: u32) -> Option<Self> {
73+
if nsecs > 999_999_999 {
74+
None
75+
} else {
76+
Some(Self { size: 96, secs, nsecs })
77+
}
78+
}
79+
}

0 commit comments

Comments
 (0)