use std::cmp;
use std::convert::TryFrom;
use std::fmt;
use std::ops;
use std::ops::AddAssign;
use std::ops::SubAssign;
use std::sync::OnceLock;
use std::sync::RwLock;
use std::time::SystemTime;
use byteorder::{LittleEndian, ReadBytesExt};
use fog_crypto::serde::{
CryptoEnum, FOG_TYPE_ENUM, FOG_TYPE_ENUM_TIME_INDEX, FOG_TYPE_ENUM_TIME_NAME,
};
use serde::Deserialize;
use serde::Serialize;
use serde::{
de::{Deserializer, EnumAccess, Error, Unexpected, VariantAccess},
ser::{SerializeStructVariant, Serializer},
};
use serde_bytes::ByteBuf;
const NTP_EPOCH_OFFSET: i64 = -86400 * (70 * 365 + 17);
const MAX_NANOSEC: u32 = 999_999_999;
const NANOS_PER_SEC: u32 = 1_000_000_000;
const MICROS_PER_SEC: i64 = 1_000_000;
const MILLIS_PER_SEC: i64 = 1_000;
static UTC_LEAP: OnceLock<RwLock<LeapSeconds>> = OnceLock::new();
fn get_table() -> std::sync::RwLockReadGuard<'static, LeapSeconds> {
let table = UTC_LEAP.get_or_init(|| RwLock::new(LeapSeconds::default()));
match table.read() {
Ok(o) => o,
Err(e) => e.into_inner(),
}
}
fn utc_to_tai(t: Timestamp) -> Timestamp {
let table = get_table();
t - table.reverse_leap_seconds(t)
}
fn tai_to_utc(t: Timestamp) -> Timestamp {
let table = get_table();
t + table.leap_seconds(t)
}
pub fn set_utc_leap_seconds(table: LeapSeconds) {
let store = UTC_LEAP.get_or_init(|| RwLock::new(LeapSeconds::default()));
let mut store = match store.write() {
Ok(o) => o,
Err(e) => e.into_inner(),
};
*store = table;
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub struct TimeDelta {
secs: i64,
nanos: u32,
}
impl TimeDelta {
pub fn new(secs: i64, nanos: u32) -> Option<Self> {
if nanos > MAX_NANOSEC {
return None;
}
Some(Self { secs, nanos })
}
pub fn from_secs(secs: i64) -> Self {
Self { secs, nanos: 0 }
}
pub fn from_millis(millis: i64) -> Self {
Self {
secs: millis.div_euclid(MILLIS_PER_SEC),
nanos: millis.rem_euclid(MILLIS_PER_SEC) as u32,
}
}
pub fn from_micros(micros: i64) -> Self {
Self {
secs: micros.div_euclid(MICROS_PER_SEC),
nanos: micros.rem_euclid(MICROS_PER_SEC) as u32,
}
}
pub fn from_nanos(nanos: i64) -> Self {
Self {
secs: nanos.div_euclid(NANOS_PER_SEC as i64),
nanos: nanos.rem_euclid(NANOS_PER_SEC as i64) as u32,
}
}
pub fn subsec_nanos(&self) -> u32 {
self.nanos
}
pub fn as_secs(&self) -> i64 {
self.secs
}
}
impl ops::AddAssign<TimeDelta> for TimeDelta {
fn add_assign(&mut self, rhs: TimeDelta) {
self.nanos += rhs.nanos;
self.secs += rhs.secs;
if self.nanos >= NANOS_PER_SEC {
self.nanos -= NANOS_PER_SEC;
self.secs += 1;
}
}
}
impl ops::Add<TimeDelta> for TimeDelta {
type Output = TimeDelta;
fn add(mut self, rhs: TimeDelta) -> Self::Output {
self.add_assign(rhs);
self
}
}
impl ops::SubAssign<TimeDelta> for TimeDelta {
fn sub_assign(&mut self, rhs: TimeDelta) {
if self.nanos < rhs.nanos {
self.nanos += NANOS_PER_SEC;
self.secs -= 1;
}
self.nanos -= rhs.nanos;
self.secs -= rhs.secs;
}
}
impl ops::Sub<TimeDelta> for TimeDelta {
type Output = TimeDelta;
fn sub(mut self, rhs: TimeDelta) -> Self::Output {
self.sub_assign(rhs);
self
}
}
impl ops::Neg for TimeDelta {
type Output = TimeDelta;
fn neg(mut self) -> Self::Output {
self.secs = -self.secs;
if self.nanos != 0 {
self.nanos = NANOS_PER_SEC - self.nanos;
self.secs -= 1;
}
self
}
}
#[derive(Clone, Debug)]
pub struct LeapSeconds(pub Vec<(Timestamp, TimeDelta)>);
impl Default for LeapSeconds {
fn default() -> Self {
let file = include_str!("leap-seconds.list");
Self::from_ntp_file(file).unwrap()
}
}
impl LeapSeconds {
pub fn new(table: Vec<(Timestamp, TimeDelta)>) -> Self {
Self(table)
}
pub fn reverse_leap_seconds(&self, t: Timestamp) -> TimeDelta {
for leap_second in self.0.iter().rev() {
if (t - leap_second.1) >= leap_second.0 {
return leap_second.1;
}
}
TimeDelta::default()
}
pub fn leap_seconds(&self, t: Timestamp) -> TimeDelta {
for leap_second in self.0.iter().rev() {
if t >= leap_second.0 {
return leap_second.1;
}
}
TimeDelta::default()
}
pub fn from_ntp_file(file: &str) -> Option<Self> {
let mut table = Vec::new();
for line in file.lines() {
if let Some(first_char) = line.chars().next() {
if first_char == '#' {
continue;
} else {
let mut data = line.split_whitespace();
let Some(secs_utc) = data.next() else { return None };
let Ok(secs_utc) = str::parse::<i64>(secs_utc) else { return None };
let Some(delta) = data.next() else { return None };
let Ok(delta) = str::parse::<i64>(delta) else { return None };
let time = Timestamp::from_tai_secs(secs_utc + delta + NTP_EPOCH_OFFSET);
let delta = TimeDelta::from_secs(-delta);
table.push((time, delta));
}
}
}
Some(LeapSeconds(table))
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Timestamp {
secs: i64,
nanos: u32,
}
impl Timestamp {
pub fn from_tai(secs: i64, nanos: u32) -> Option<Timestamp> {
if nanos > MAX_NANOSEC {
return None;
}
Some(Timestamp { secs, nanos })
}
pub fn from_utc(secs: i64, nanos: u32) -> Option<Timestamp> {
if nanos > MAX_NANOSEC {
return None;
}
Some(utc_to_tai(Timestamp { secs, nanos }))
}
pub fn from_utc_secs(secs: i64) -> Timestamp {
utc_to_tai(Timestamp { secs, nanos: 0 })
}
pub fn from_tai_secs(secs: i64) -> Timestamp {
Timestamp { secs, nanos: 0 }
}
pub const fn zero() -> Timestamp {
Timestamp { secs: 0, nanos: 0 }
}
pub const fn min_value() -> Timestamp {
Timestamp {
secs: i64::MIN,
nanos: 0,
}
}
pub const fn max_value() -> Timestamp {
Timestamp {
secs: i64::MAX,
nanos: MAX_NANOSEC,
}
}
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.nanos < MAX_NANOSEC {
self.nanos += 1;
} else {
self.nanos = 0;
self.secs += 1;
}
self
}
pub fn prev(mut self) -> Timestamp {
if self.nanos > 0 {
self.nanos -= 1;
} else {
self.nanos = MAX_NANOSEC;
self.secs -= 1;
}
self
}
pub fn utc(&self) -> (i64, u32) {
let t = tai_to_utc(*self);
(t.secs, t.nanos)
}
pub fn tai_secs(&self) -> i64 {
self.secs
}
pub fn tai_subsec_nanos(&self) -> u32 {
self.nanos
}
pub fn time_since(&self, other: &Timestamp) -> TimeDelta {
let rhs = TimeDelta {
secs: other.secs,
nanos: other.nanos,
};
let new = *self - rhs;
TimeDelta {
secs: new.secs,
nanos: new.nanos,
}
}
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.nanos != 0 {
vec.reserve(8 + 4);
vec.extend_from_slice(&self.secs.to_le_bytes());
vec.extend_from_slice(&self.nanos.to_le_bytes());
} else if (self.secs <= u32::MAX as i64) && (self.secs >= 0) {
vec.reserve(4);
vec.extend_from_slice(&(self.secs as u32).to_le_bytes());
} else {
vec.reserve(8);
vec.extend_from_slice(&self.secs.to_le_bytes());
}
}
pub fn size(&self) -> usize {
if self.nanos != 0 {
8 + 4
} else if (self.secs <= u32::MAX as i64) && (self.secs >= 0) {
4
} else {
8
}
}
pub fn now() -> Timestamp {
Timestamp::from(SystemTime::now())
}
}
impl From<SystemTime> for Timestamp {
fn from(value: SystemTime) -> Self {
let t = value.duration_since(SystemTime::UNIX_EPOCH).unwrap();
Timestamp::from_utc(t.as_secs() as i64, t.subsec_nanos()).unwrap()
}
}
impl ops::Add<i64> for Timestamp {
type Output = Timestamp;
fn add(mut self, rhs: i64) -> Self {
self.secs += rhs;
self
}
}
impl ops::AddAssign<i64> for Timestamp {
fn add_assign(&mut self, rhs: i64) {
self.secs += rhs;
}
}
impl ops::Sub<i64> for Timestamp {
type Output = Timestamp;
fn sub(mut self, rhs: i64) -> Self {
self.secs -= rhs;
self
}
}
impl ops::SubAssign<i64> for Timestamp {
fn sub_assign(&mut self, rhs: i64) {
self.secs -= rhs;
}
}
impl ops::Add<TimeDelta> for Timestamp {
type Output = Timestamp;
fn add(mut self, rhs: TimeDelta) -> Timestamp {
self += rhs;
self
}
}
impl ops::AddAssign<TimeDelta> for Timestamp {
fn add_assign(&mut self, rhs: TimeDelta) {
self.nanos += rhs.nanos;
if self.nanos >= NANOS_PER_SEC {
self.nanos -= NANOS_PER_SEC;
self.secs += 1;
}
self.secs += rhs.secs;
}
}
impl ops::Sub<TimeDelta> for Timestamp {
type Output = Timestamp;
fn sub(mut self, rhs: TimeDelta) -> Timestamp {
self -= rhs;
self
}
}
impl ops::SubAssign<TimeDelta> for Timestamp {
fn sub_assign(&mut self, rhs: TimeDelta) {
if self.nanos < rhs.nanos {
self.nanos += NANOS_PER_SEC;
self.secs -= 1;
}
self.nanos -= rhs.nanos;
self.secs -= rhs.secs;
}
}
impl ops::Sub<Timestamp> for Timestamp {
type Output = TimeDelta;
fn sub(self, rhs: Timestamp) -> TimeDelta {
self.time_since(&rhs)
}
}
impl cmp::Ord for Timestamp {
fn cmp(&self, other: &Timestamp) -> cmp::Ordering {
match self.secs.cmp(&other.secs) {
cmp::Ordering::Equal => self.nanos.cmp(&other.nanos),
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, "TAI: {} secs + {} ns", self.secs, self.nanos)
}
}
impl TryFrom<&[u8]> for Timestamp {
type Error = String;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let mut raw = value;
let (secs, nanos) = match value.len() {
12 => {
let secs = raw.read_i64::<LittleEndian>().unwrap();
let nanos = raw.read_u32::<LittleEndian>().unwrap();
(secs, nanos)
}
8 => {
let secs = raw.read_i64::<LittleEndian>().unwrap();
(secs, 0)
}
4 => {
let secs = raw.read_u32::<LittleEndian>().unwrap() as i64;
(secs, 0)
}
_ => {
return Err(format!(
"not a recognized Timestamp length ({} bytes)",
value.len()
))
}
};
Ok(Timestamp { secs, nanos })
}
}
impl serde::ser::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("secs", &self.secs)?;
sv.serialize_field("nanos", &self.nanos)?;
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> serde::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_tai(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)> {
vec![
(4, Timestamp::from_tai(0, 0).unwrap()),
(4, Timestamp::from_tai(1, 0).unwrap()),
(12, Timestamp::from_tai(1, 1).unwrap()),
(4, Timestamp::from_tai(u32::MAX as i64 - 1, 0).unwrap()),
(4, Timestamp::from_tai(u32::MAX as i64, 0).unwrap()),
(8, Timestamp::from_tai(u32::MAX as i64 + 1, 0).unwrap()),
(8, Timestamp::from_tai(i64::MIN, 0).unwrap()),
(12, Timestamp::from_tai(i64::MIN, 1).unwrap()),
]
}
#[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());
}
}
#[test]
fn leap_seconds() {
let table = LeapSeconds::default();
let (tai_time, diff) = table.0.last().unwrap();
assert_eq!(*diff, TimeDelta::from_secs(-37));
assert_eq!(tai_time.utc().0, 3692217600 + NTP_EPOCH_OFFSET);
assert_eq!(tai_time.utc().1, 0);
for i in -5..=5 {
let time = *tai_time + i;
let utc = time.utc();
let time2 = Timestamp::from_utc(utc.0, utc.1).unwrap();
if i == -1 {
assert_eq!(
time,
time2 - 1,
"Failed for offset of {}, expected a diff of 1",
i
);
} else {
assert_eq!(time, time2, "Failed for offset of {}, expected no diff", i);
}
let utc = tai_time.tai_secs() - diff.as_secs() + i;
let tai = Timestamp::from_utc_secs(utc);
let utc2 = tai.utc();
assert_eq!(utc2.0, utc, "Failed for offset of {}, expected no diff", i);
assert_eq!(
utc2.1, 0,
"Failed for offset of {}, expected 0 ns for UTC",
i
);
}
}
#[test]
fn check_diffs() {
let time = Timestamp::from_tai(5, 5).unwrap();
let time2 = Timestamp::from_tai(6, 1).unwrap();
let diff = time2.time_since(&time);
let diff2 = time.time_since(&time2);
let neg_diff2 = -diff2;
let neg_diff3 = -neg_diff2;
assert_eq!(diff, neg_diff2);
assert_eq!(diff2, neg_diff3);
}
}