Skip to content

Commit

Permalink
WIP: Adding Timestamp decode/encode
Browse files Browse the repository at this point in the history
  • Loading branch information
junderw committed Aug 20, 2024
1 parent 114b78f commit bac67df
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 2 deletions.
75 changes: 74 additions & 1 deletion rmp/src/decode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use std::error;

use num_traits::cast::FromPrimitive;

use crate::Marker;
use crate::{Marker, Timestamp};

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

/// Attempts to read an Extension struct from the given reader and to decode it as a Timestamp value.
///
/// According to the MessagePack specification, there are 3 types of Timestamp, 32, 64, and 96.
///
/// # Errors
///
/// This function will return `ValueReadError` on any I/O error while reading the Timestamp,
/// except the EINTR, which is handled internally.
///
/// It also returns `ValueReadError::TypeMismatch` if the actual type is not equal with the
/// expected one, indicating you with the actual type.
///
/// # Note
///
/// This function will silently retry on every EINTR received from the underlying `Read` until
/// successful read.
///
/// # Examples
///
/// ```
/// use rmp::Timestamp;
///
/// // FixExt4 with a type of -1 (0xff)
/// let mut buf1 = [0xd6, 0xff, 0x66, 0xc1, 0xde, 0x7c];
/// // FixExt8 with a type of -1 (0xff)
/// let mut buf2 = [0xd7, 0xff, 0xee, 0x6b, 0x27, 0xfc, 0x66, 0xc1, 0xde, 0x7c];
/// // Ext8 with a size of 12 (0x0c) and a type of -1 (0xff)
/// let mut buf3 = [0xc7, 0x0c, 0xff, 0x3b, 0x9a, 0xc9, 0xff, 0x00, 0x00, 0x00, 0x00, 0x66, 0xc1, 0xde, 0x7c];
///
/// let ts1_expected = Timestamp::from_32(0x66c1de7c);
/// let ts2_expected = Timestamp::from_64(0x66c1de7c, 0x3b9ac9ff).unwrap();
/// let ts3_expected = Timestamp::from_96(0x66c1de7c, 0x3b9ac9ff).unwrap();
///
/// let ts1_result = rmp::decode::read_timestamp(&mut buf1.as_slice()).unwrap();
/// let ts2_result = rmp::decode::read_timestamp(&mut buf2.as_slice()).unwrap();
/// let ts3_result = rmp::decode::read_timestamp(&mut buf3.as_slice()).unwrap();
///
/// assert_eq!(ts1_expected, ts1_result);
/// assert_eq!(ts2_expected, ts2_result);
/// assert_eq!(ts3_expected, ts3_result);
/// ```
pub fn read_timestamp<R: RmpRead>(rd: &mut R) -> Result<Timestamp, ValueReadError<R::Error>> {
let marker = read_marker(rd)?;
let prefix = rd.read_data_i8()?;
match (marker, prefix) {
// timestamp 32
(Marker::FixExt4, -1) => {
let secs = rd.read_data_u32()?;
Ok(Timestamp::from_32(secs))
},
// timestamp 64
(Marker::FixExt8, -1) => {
let data = rd.read_data_u64()?;
Timestamp::from_combined_64(data)
.ok_or(ValueReadError::TypeMismatch(Marker::Reserved))
},
// timestamp 96
(Marker::Ext8, 12) => {
let prefix = rd.read_data_i8()?;
if prefix == -1 {
let nsecs = rd.read_data_u32()?;
let secs = rd.read_data_i64()?;
Timestamp::from_96(secs, nsecs)
.ok_or(ValueReadError::TypeMismatch(Marker::Reserved))
} else {
Err(ValueReadError::TypeMismatch(Marker::Reserved))
}
},
(Marker::Ext8 | Marker::FixExt4 | Marker::FixExt8, _) => Err(ValueReadError::TypeMismatch(Marker::Reserved)),
(marker, _) => Err(ValueReadError::TypeMismatch(marker)),
}
}

/// Attempts to read a single byte from the given reader and to decode it as a nil value.
///
/// According to the MessagePack specification, a nil value is represented as a single `0xc0` byte.
Expand Down
60 changes: 59 additions & 1 deletion rmp/src/encode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use core::fmt::{self, Debug, Display, Formatter};
#[cfg(feature = "std")]
use std::error;

use crate::Marker;
use crate::{Marker, Timestamp};

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

/// Encodes and attempts to write a timestamp value into the given write.
///
/// According to the MessagePack specification, a timestamp value is represented as a 32, 64, or 96 bit Extension struct.
///
/// # Errors
///
/// This function will return `Error` on any I/O error occurred while writing the timestamp.
///
/// # Examples
///
/// ```
/// use rmp::Timestamp;
///
/// let mut buf1 = Vec::new();
/// let mut buf2 = Vec::new();
/// let mut buf3 = Vec::new();
///
/// let ts1 = Timestamp::from_32(0x66c1de7c);
/// let ts2 = Timestamp::from_64(0x66c1de7c, 0x3b9ac9ff).unwrap();
/// let ts3 = Timestamp::from_96(0x66c1de7c, 0x3b9ac9ff).unwrap();
///
/// rmp::encode::write_timestamp(&mut buf1, ts1).ok();
/// rmp::encode::write_timestamp(&mut buf2, ts2).ok();
/// rmp::encode::write_timestamp(&mut buf3, ts3).ok();
///
/// // FixExt4 with a type of -1 (0xff)
/// assert_eq!(vec![0xd6, 0xff, 0x66, 0xc1, 0xde, 0x7c], buf1);
/// // FixExt8 with a type of -1 (0xff)
/// assert_eq!(vec![0xd7, 0xff, 0xee, 0x6b, 0x27, 0xfc, 0x66, 0xc1, 0xde, 0x7c], buf2);
/// // Ext8 with a size of 12 (0x0c) and a type of -1 (0xff)
/// assert_eq!(vec![0xc7, 0x0c, 0xff, 0x3b, 0x9a, 0xc9, 0xff, 0x00, 0x00, 0x00, 0x00, 0x66, 0xc1, 0xde, 0x7c], buf3);
/// ```
#[inline]
pub fn write_timestamp<W: RmpWrite>(wr: &mut W, timestamp: Timestamp) -> Result<(), DataWriteError<W::Error>> {
match timestamp.size {
32 => {
write_marker(wr, Marker::FixExt4).map_err(|e| e.0)?;
wr.write_data_i8(-1)?;
wr.write_data_u32(timestamp.secs as u32)?;
},
64 => {
write_marker(wr, Marker::FixExt8).map_err(|e| e.0)?;
let data = ((timestamp.nsecs as u64) << 34) | (timestamp.secs as u64);
wr.write_data_i8(-1)?;
wr.write_data_u64(data)?;
},
96 => {
write_marker(wr, Marker::Ext8).map_err(|e| e.0)?;
wr.write_data_u8(12)?;
wr.write_data_i8(-1)?;
wr.write_data_u32(timestamp.nsecs)?;
wr.write_data_i64(timestamp.secs)?;
},
_ => unreachable!(),
}
Ok(())
}

/// Encodes and attempts to write a bool value into the given write.
///
/// According to the MessagePack specification, an encoded boolean value is represented as a single
Expand Down
65 changes: 65 additions & 0 deletions rmp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,68 @@ pub use crate::marker::Marker;

/// Version of the MessagePack [spec](http://github.com/msgpack/msgpack/blob/master/spec.md).
pub const MSGPACK_VERSION: u32 = 5;

/// A type for holding Timestamp information
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Timestamp {
size: u8,
secs: i64,
nsecs: u32,
}

impl Timestamp {
/// Get the size of the Timestamp in bits
#[inline]
pub fn get_bitsize(self) -> u8 {
self.size
}

/// Get the data to pass to chrono::DateTime::from_timestamp
#[inline]
pub fn into_timestamp(self) -> (i64, u32) {
(
self.secs,
self.nsecs,
)
}

/// Create a 32 bit timestamp using FixExt4
#[inline]
pub fn from_32(secs: u32) -> Self {
Self { size: 32, secs: i64::from(secs), nsecs: 0 }
}

/// Create a 64 bit timestamp using FixExt8 with seconds and nanoseconds passed separately
#[inline]
pub fn from_64(secs: i64, nsecs: u32) -> Option<Self> {
if secs < 0 || secs > 0x3_ffff_ffff || nsecs > 999_999_999 {
None
} else {
Some(Self { size: 64, secs, nsecs })
}
}

/// Create a 64 bit timestamp using FixExt8 from the combined 64 bit data
#[inline]
pub fn from_combined_64(data: u64) -> Option<Self> {
// 30 bits fits in u32
let nsecs = (data >> 34) as u32;
if nsecs > 999_999_999 {
return None
}
// 34 bits fits in i64
let secs = (data & 0x3_ffff_ffff) as i64;

Some(Self { size: 64, secs, nsecs })
}

/// Create a 96 bit timestamp using Ext8 (len=12)
#[inline]
pub fn from_96(secs: i64, nsecs: u32) -> Option<Self> {
if nsecs > 999_999_999 {
None
} else {
Some(Self { size: 96, secs, nsecs })
}
}
}

0 comments on commit bac67df

Please sign in to comment.