use std::cmp;
use std::convert::TryFrom;
use std::fmt;
use std::ops;
use std::time;
use byteorder::{LittleEndian, ReadBytesExt};
use fog_crypto::serde::{
CryptoEnum, FOG_TYPE_ENUM, FOG_TYPE_ENUM_TIME_INDEX, FOG_TYPE_ENUM_TIME_NAME,
};
use serde::{
de::{Deserialize, Deserializer, EnumAccess, Error, Unexpected, VariantAccess},
ser::{Serialize, SerializeStructVariant, Serializer},
};
use serde_bytes::ByteBuf;
const MAX_NANOSEC: u32 = 1_999_999_999;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Timestamp {
sec: i64,
nano: u32,
standard: u8,
}
impl Timestamp {
pub fn from_utc(sec: i64, nano: u32) -> Option<Timestamp> {
if nano > MAX_NANOSEC {
None
} else {
Some(Timestamp {
sec,
nano,
standard: 0,
})
}
}
pub fn from_sec(sec: i64) -> Timestamp {
Timestamp {
sec,
nano: 0,
standard: 0,
}
}
pub const fn zero() -> Timestamp {
Timestamp {
sec: 0,
nano: 0,
standard: 0,
}
}
pub const fn min_value() -> Timestamp {
Timestamp {
sec: i64::MIN,
nano: 0,
standard: 0,
}
}
pub const fn max_value() -> Timestamp {
Timestamp {
sec: i64::MAX,
nano: MAX_NANOSEC,
standard: 0,
}
}
pub fn min(self, other: Timestamp) -> Timestamp {
if self < other {
self
} else {
other
}
}
pub fn max(self, other: Timestamp) -> Timestamp {
if self > other {
self
} else {
other
}
}
pub fn next(mut self) -> Timestamp {
if self.nano < MAX_NANOSEC {
self.nano += 1;
} else {
self.nano = 0;
self.sec += 1;
}
self
}
pub fn prev(mut self) -> Timestamp {
if self.nano > 0 {
self.nano -= 1;
} else {
self.nano = MAX_NANOSEC;
self.sec -= 1;
}
self
}
pub fn timestamp_utc(&self) -> i64 {
self.sec
}
pub fn timestamp_subsec_nanos(&self) -> u32 {
self.nano
}
pub fn as_vec(&self) -> Vec<u8> {
let mut v = Vec::new();
self.encode_vec(&mut v);
v
}
pub fn encode_vec(&self, vec: &mut Vec<u8>) {
if self.nano != 0 {
vec.reserve(1 + 8 + 4);
vec.push(self.standard);
vec.extend_from_slice(&self.sec.to_le_bytes());
vec.extend_from_slice(&self.nano.to_le_bytes());
} else if (self.sec <= u32::MAX as i64) && (self.sec >= 0) {
vec.reserve(1 + 4);
vec.push(self.standard);
vec.extend_from_slice(&(self.sec as u32).to_le_bytes());
} else {
vec.reserve(1 + 8);
vec.push(self.standard);
vec.extend_from_slice(&self.sec.to_le_bytes());
}
}
pub fn size(&self) -> usize {
if self.nano != 0 {
1 + 8 + 4
} else if (self.sec <= u32::MAX as i64) && (self.sec >= 0) {
1 + 4
} else {
1 + 8
}
}
pub fn now() -> Option<Timestamp> {
match time::SystemTime::now().duration_since(time::SystemTime::UNIX_EPOCH) {
Ok(t) => Timestamp::from_utc(t.as_secs() as i64, t.subsec_nanos()),
Err(_) => None,
}
}
}
impl ops::Add<i64> for Timestamp {
type Output = Timestamp;
fn add(self, rhs: i64) -> Self {
Timestamp {
sec: self.sec + rhs,
nano: self.nano,
standard: self.standard,
}
}
}
impl ops::Sub<i64> for Timestamp {
type Output = Timestamp;
fn sub(self, rhs: i64) -> Self {
Timestamp {
sec: self.sec - rhs,
nano: self.nano,
standard: self.standard,
}
}
}
impl cmp::Ord for Timestamp {
fn cmp(&self, other: &Timestamp) -> cmp::Ordering {
match self.sec.cmp(&other.sec) {
cmp::Ordering::Equal => self.nano.cmp(&other.nano),
other => other,
}
}
}
impl cmp::PartialOrd for Timestamp {
fn partial_cmp(&self, other: &Timestamp) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl fmt::Display for Timestamp {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "UTC: {} sec + {} ns", self.sec, self.nano)
}
}
impl TryFrom<&[u8]> for Timestamp {
type Error = String;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let mut raw = value;
let standard = raw
.read_u8()
.map_err(|_| String::from("missing time standard byte"))?;
let (sec, nano) = match value.len() {
13 => {
let sec = raw.read_i64::<LittleEndian>().unwrap();
let nano = raw.read_u32::<LittleEndian>().unwrap();
(sec, nano)
}
9 => {
let sec = raw.read_i64::<LittleEndian>().unwrap();
(sec, 0)
}
5 => {
let sec = raw.read_u32::<LittleEndian>().unwrap() as i64;
(sec, 0)
}
_ => {
return Err(format!(
"not a recognized Timestamp length ({} bytes)",
value.len()
))
}
};
Ok(Timestamp {
sec,
nano,
standard,
})
}
}
impl Serialize for Timestamp {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if serializer.is_human_readable() {
let mut sv = serializer.serialize_struct_variant(
FOG_TYPE_ENUM,
FOG_TYPE_ENUM_TIME_INDEX as u32,
FOG_TYPE_ENUM_TIME_NAME,
2,
)?;
sv.serialize_field("std", &self.standard)?;
sv.serialize_field("secs", &self.sec)?;
sv.serialize_field("nanos", &self.sec)?;
sv.end()
} else {
let value = ByteBuf::from(self.as_vec());
serializer.serialize_newtype_variant(
FOG_TYPE_ENUM,
FOG_TYPE_ENUM_TIME_INDEX as u32,
FOG_TYPE_ENUM_TIME_NAME,
&value,
)
}
}
}
impl<'de> Deserialize<'de> for Timestamp {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct TimeVisitor {
is_human_readable: bool,
}
impl<'de> serde::de::Visitor<'de> for TimeVisitor {
type Value = Timestamp;
fn expecting(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(
fmt,
"{} enum with variant {} (id {})",
FOG_TYPE_ENUM, FOG_TYPE_ENUM_TIME_NAME, FOG_TYPE_ENUM_TIME_INDEX
)
}
fn visit_enum<A>(self, data: A) -> Result<Self::Value, A::Error>
where
A: EnumAccess<'de>,
{
let variant = match data.variant()? {
(CryptoEnum::Time, variant) => variant,
(e, _) => {
return Err(A::Error::invalid_type(
Unexpected::Other(e.as_str()),
&"Time",
))
}
};
if self.is_human_readable {
use serde::de::MapAccess;
struct TimeStructVisitor;
impl<'de> serde::de::Visitor<'de> for TimeStructVisitor {
type Value = Timestamp;
fn expecting(
&self,
fmt: &mut fmt::Formatter<'_>,
) -> Result<(), fmt::Error> {
write!(fmt, "timestamp struct")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut secs: Option<i64> = None;
let mut nanos: u32 = 0;
while let Some(key) = map.next_key::<String>()? {
match key.as_ref() {
"std" => {
let v: u8 = map.next_value()?;
if v != 0 {
return Err(A::Error::invalid_value(
Unexpected::Unsigned(v as u64),
&"0",
));
}
}
"secs" => {
secs = Some(map.next_value()?);
}
"nanos" => {
nanos = map.next_value()?;
}
_ => {
return Err(A::Error::unknown_field(
key.as_ref(),
&["std", "secs", "nanos"],
))
}
}
}
let secs = secs.ok_or_else(|| A::Error::missing_field("secs"))?;
Timestamp::from_utc(secs, nanos)
.ok_or_else(|| A::Error::custom("Invalid timestamp"))
}
}
variant.struct_variant(&["std", "secs", "nanos"], TimeStructVisitor)
} else {
let bytes: ByteBuf = variant.newtype_variant()?;
Timestamp::try_from(bytes.as_ref()).map_err(A::Error::custom)
}
}
}
let is_human_readable = deserializer.is_human_readable();
deserializer.deserialize_enum(
FOG_TYPE_ENUM,
&[FOG_TYPE_ENUM_TIME_NAME],
TimeVisitor { is_human_readable },
)
}
}
#[cfg(test)]
mod test {
use super::*;
fn edge_cases() -> Vec<(usize, Timestamp)> {
let mut test_cases = Vec::new();
test_cases.push((5, Timestamp::from_utc(0, 0).unwrap()));
test_cases.push((5, Timestamp::from_utc(1, 0).unwrap()));
test_cases.push((13, Timestamp::from_utc(1, 1).unwrap()));
test_cases.push((5, Timestamp::from_utc(u32::MAX as i64 - 1, 0).unwrap()));
test_cases.push((5, Timestamp::from_utc(u32::MAX as i64 - 0, 0).unwrap()));
test_cases.push((9, Timestamp::from_utc(u32::MAX as i64 + 1, 0).unwrap()));
test_cases.push((9, Timestamp::from_utc(i64::MIN, 0).unwrap()));
test_cases.push((13, Timestamp::from_utc(i64::MIN, 1).unwrap()));
test_cases
}
#[test]
fn roundtrip() {
for (index, case) in edge_cases().iter().enumerate() {
println!(
"Test #{}: '{}' with expected length = {}",
index, case.1, case.0
);
let mut enc = Vec::new();
case.1.encode_vec(&mut enc);
assert_eq!(enc.len(), case.0);
let decoded = Timestamp::try_from(enc.as_ref()).unwrap();
assert_eq!(decoded, case.1);
}
}
#[test]
fn too_long() {
for case in edge_cases() {
println!("Test with Timestamp = {}", case.1);
let mut enc = Vec::new();
case.1.encode_vec(&mut enc);
enc.push(0u8);
assert!(Timestamp::try_from(enc.as_ref()).is_err());
}
}
#[test]
fn too_short() {
for case in edge_cases() {
println!("Test with Timestamp = {}", case.1);
let mut enc = Vec::new();
case.1.encode_vec(&mut enc);
enc.pop();
assert!(Timestamp::try_from(enc.as_ref()).is_err());
}
}
}