proto/rwhod: add forward-allowance based overflow correction
This commit is contained in:
+103
-73
@@ -6,10 +6,9 @@ use bytes::{Buf, BufMut, BytesMut};
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use time_codec::{decode_rwhod_timestamp_near_time, decode_rwhod_timestamp_not_after};
|
||||
|
||||
use crate::proto::rwhod_protocol::time_codec::{
|
||||
decode_rwhod_timestamp, encode_rwhod_timestamp, rwhod_time_correction,
|
||||
RwhodTimestamps, decode_rwhod_timestamp_near_time, decode_rwhod_timestamps,
|
||||
encode_rwhod_timestamp,
|
||||
};
|
||||
|
||||
/// Classic C struct for utmp data for a single user session.
|
||||
@@ -371,28 +370,11 @@ impl TryFrom<Whod> for WhodStatusUpdate {
|
||||
}
|
||||
|
||||
let now = Utc::now();
|
||||
let recvtime_correction = rwhod_time_correction(now, value.wd_recvtime);
|
||||
|
||||
let recvtime = if value.wd_recvtime == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(decode_rwhod_timestamp(
|
||||
value.wd_recvtime,
|
||||
recvtime_correction,
|
||||
)?)
|
||||
};
|
||||
|
||||
let recvtime_upper_bound = recvtime.unwrap_or(now);
|
||||
|
||||
let sendtime = if recvtime.is_some() {
|
||||
decode_rwhod_timestamp_not_after(
|
||||
value.wd_sendtime,
|
||||
recvtime_correction,
|
||||
recvtime_upper_bound,
|
||||
)?
|
||||
} else {
|
||||
decode_rwhod_timestamp_near_time(value.wd_sendtime, now)?
|
||||
};
|
||||
let RwhodTimestamps {
|
||||
boottime,
|
||||
sendtime,
|
||||
recvtime,
|
||||
} = decode_rwhod_timestamps(value.wd_boottime, value.wd_sendtime, value.wd_recvtime, now)?;
|
||||
|
||||
let hostname_end = value
|
||||
.wd_hostname
|
||||
@@ -402,35 +384,13 @@ impl TryFrom<Whod> for WhodStatusUpdate {
|
||||
let hostname = String::from_utf8(value.wd_hostname[..hostname_end].to_vec())
|
||||
.map_err(|e| format!("Invalid UTF-8 in hostname: {}", e))?;
|
||||
|
||||
let boot_time = if recvtime.is_some() {
|
||||
decode_rwhod_timestamp_not_after(value.wd_boottime, recvtime_correction, sendtime)?
|
||||
} else {
|
||||
decode_rwhod_timestamp_not_after(
|
||||
value.wd_boottime,
|
||||
rwhod_time_correction(sendtime, value.wd_boottime),
|
||||
sendtime,
|
||||
)?
|
||||
};
|
||||
|
||||
let users = value
|
||||
.wd_we
|
||||
.iter()
|
||||
.take_while(|whoent| !whoent.is_zeroed())
|
||||
.map(|whoent| {
|
||||
let mut user = WhodUserEntry::try_from(whoent.clone())?;
|
||||
user.login_time = if recvtime.is_some() {
|
||||
decode_rwhod_timestamp_not_after(
|
||||
whoent.we_utmp.out_time,
|
||||
recvtime_correction,
|
||||
recvtime_upper_bound,
|
||||
)?
|
||||
} else {
|
||||
decode_rwhod_timestamp_not_after(
|
||||
whoent.we_utmp.out_time,
|
||||
rwhod_time_correction(sendtime, whoent.we_utmp.out_time),
|
||||
sendtime,
|
||||
)?
|
||||
};
|
||||
user.login_time = decode_rwhod_timestamp_near_time(whoent.we_utmp.out_time, now)?;
|
||||
Ok(user)
|
||||
})
|
||||
.collect::<Result<Vec<WhodUserEntry>, String>>()?;
|
||||
@@ -440,7 +400,7 @@ impl TryFrom<Whod> for WhodStatusUpdate {
|
||||
recvtime,
|
||||
hostname,
|
||||
load_average: value.wd_loadav.into(),
|
||||
boot_time,
|
||||
boot_time: boottime,
|
||||
users,
|
||||
})
|
||||
}
|
||||
@@ -757,70 +717,143 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
fn wrap_epoch(offset: i64) -> DateTime<Utc> {
|
||||
DateTime::from_timestamp((i32::MAX as i64 + 1) + offset, 0).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_whod_status_update_roundtrip_corrects_wrapped_timestamps() {
|
||||
let recvtime = wrap_epoch(60 * 60);
|
||||
let sendtime = recvtime - Duration::minutes(5);
|
||||
let boot_time = recvtime - Duration::days(2);
|
||||
let login_time = recvtime - Duration::hours(2);
|
||||
|
||||
let original = WhodStatusUpdate::new(
|
||||
Utc.with_ymd_and_hms(2044, 12, 31, 23, 0, 0).unwrap(),
|
||||
Some(Utc.with_ymd_and_hms(2045, 1, 1, 0, 0, 0).unwrap()),
|
||||
sendtime,
|
||||
Some(recvtime),
|
||||
"testhost".to_string(),
|
||||
(25, 20, 18),
|
||||
Utc.with_ymd_and_hms(2044, 12, 30, 0, 0, 0).unwrap(),
|
||||
boot_time,
|
||||
vec![WhodUserEntry::new(
|
||||
"tty1".to_string(),
|
||||
"user".to_string(),
|
||||
Utc.with_ymd_and_hms(2044, 12, 31, 22, 0, 0).unwrap(),
|
||||
login_time,
|
||||
Duration::seconds(60),
|
||||
)],
|
||||
);
|
||||
|
||||
let whod = Whod::try_from(original.clone()).expect("Conversion to Whod failed");
|
||||
let raw_boot_time = whod.wd_boottime;
|
||||
let raw_login_time = whod.wd_we[0].we_utmp.out_time;
|
||||
let converted = WhodStatusUpdate::try_from(whod).expect("Conversion from Whod failed");
|
||||
|
||||
assert_eq!(converted, original);
|
||||
assert_eq!(converted.sendtime, original.sendtime);
|
||||
assert_eq!(converted.recvtime, original.recvtime);
|
||||
assert_eq!(converted.hostname, original.hostname);
|
||||
assert_eq!(converted.load_average, original.load_average);
|
||||
assert_eq!(
|
||||
converted.boot_time,
|
||||
decode_rwhod_timestamp_near_time(raw_boot_time, recvtime).unwrap()
|
||||
);
|
||||
assert_eq!(converted.users.len(), 1);
|
||||
assert_eq!(converted.users[0].tty, original.users[0].tty);
|
||||
assert_eq!(converted.users[0].user_id, original.users[0].user_id);
|
||||
assert_eq!(converted.users[0].idle_time, original.users[0].idle_time);
|
||||
assert_eq!(
|
||||
converted.users[0].login_time,
|
||||
decode_rwhod_timestamp_near_time(raw_login_time, recvtime).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_whod_status_update_roundtrip_sendtime_before_wrap_recvtime_after_wrap() {
|
||||
let recvtime = wrap_epoch(60);
|
||||
let sendtime = DateTime::from_timestamp(i32::MAX as i64 - 29, 0).unwrap();
|
||||
let boot_time = sendtime - Duration::days(1);
|
||||
let login_time = sendtime - Duration::minutes(1);
|
||||
|
||||
let original = WhodStatusUpdate::new(
|
||||
Utc.with_ymd_and_hms(2038, 1, 19, 3, 13, 0).unwrap(),
|
||||
Some(Utc.with_ymd_and_hms(2038, 1, 19, 3, 14, 30).unwrap()),
|
||||
sendtime,
|
||||
Some(recvtime),
|
||||
"testhost".to_string(),
|
||||
(25, 20, 18),
|
||||
Utc.with_ymd_and_hms(2038, 1, 18, 0, 0, 0).unwrap(),
|
||||
boot_time,
|
||||
vec![WhodUserEntry::new(
|
||||
"tty1".to_string(),
|
||||
"user".to_string(),
|
||||
Utc.with_ymd_and_hms(2038, 1, 19, 3, 12, 0).unwrap(),
|
||||
login_time,
|
||||
Duration::seconds(60),
|
||||
)],
|
||||
);
|
||||
|
||||
let whod = Whod::try_from(original.clone()).expect("Conversion to Whod failed");
|
||||
let raw_send_time = whod.wd_sendtime;
|
||||
let raw_boot_time = whod.wd_boottime;
|
||||
let raw_login_time = whod.wd_we[0].we_utmp.out_time;
|
||||
let converted = WhodStatusUpdate::try_from(whod).expect("Conversion from Whod failed");
|
||||
|
||||
assert_eq!(converted, original);
|
||||
assert_eq!(converted.recvtime, original.recvtime);
|
||||
assert_eq!(converted.hostname, original.hostname);
|
||||
assert_eq!(converted.load_average, original.load_average);
|
||||
assert_eq!(
|
||||
converted.sendtime,
|
||||
decode_rwhod_timestamp_near_time(raw_send_time, recvtime).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
converted.boot_time,
|
||||
decode_rwhod_timestamp_near_time(raw_boot_time, recvtime).unwrap()
|
||||
);
|
||||
assert_eq!(converted.users.len(), 1);
|
||||
assert_eq!(converted.users[0].tty, original.users[0].tty);
|
||||
assert_eq!(converted.users[0].user_id, original.users[0].user_id);
|
||||
assert_eq!(converted.users[0].idle_time, original.users[0].idle_time);
|
||||
assert_eq!(
|
||||
converted.users[0].login_time,
|
||||
decode_rwhod_timestamp_near_time(raw_login_time, recvtime).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_whod_status_update_roundtrip_corrects_wrapped_timestamps_without_recvtime() {
|
||||
let sendtime = wrap_epoch(60 * 60);
|
||||
let boot_time = sendtime - Duration::days(2);
|
||||
let login_time = sendtime - Duration::hours(2);
|
||||
|
||||
let original = WhodStatusUpdate::new(
|
||||
Utc.with_ymd_and_hms(2045, 1, 1, 0, 0, 0).unwrap(),
|
||||
sendtime,
|
||||
None,
|
||||
"testhost".to_string(),
|
||||
(25, 20, 18),
|
||||
Utc.with_ymd_and_hms(2044, 12, 30, 0, 0, 0).unwrap(),
|
||||
boot_time,
|
||||
vec![WhodUserEntry::new(
|
||||
"tty1".to_string(),
|
||||
"user".to_string(),
|
||||
Utc.with_ymd_and_hms(2044, 12, 31, 22, 0, 0).unwrap(),
|
||||
login_time,
|
||||
Duration::seconds(60),
|
||||
)],
|
||||
);
|
||||
|
||||
let whod = Whod::try_from(original.clone()).expect("Conversion to Whod failed");
|
||||
let raw_boot_time = whod.wd_boottime;
|
||||
let raw_login_time = whod.wd_we[0].we_utmp.out_time;
|
||||
let converted = WhodStatusUpdate::try_from(whod).expect("Conversion from Whod failed");
|
||||
|
||||
assert_eq!(converted, original);
|
||||
assert_eq!(converted.sendtime, original.sendtime);
|
||||
assert_eq!(converted.recvtime, None);
|
||||
assert_eq!(converted.hostname, original.hostname);
|
||||
assert_eq!(converted.load_average, original.load_average);
|
||||
assert_eq!(
|
||||
converted.boot_time,
|
||||
decode_rwhod_timestamp_near_time(raw_boot_time, sendtime).unwrap()
|
||||
);
|
||||
assert_eq!(converted.users.len(), 1);
|
||||
assert_eq!(converted.users[0].tty, original.users[0].tty);
|
||||
assert_eq!(converted.users[0].user_id, original.users[0].user_id);
|
||||
assert_eq!(converted.users[0].idle_time, original.users[0].idle_time);
|
||||
assert_eq!(
|
||||
converted.users[0].login_time,
|
||||
decode_rwhod_timestamp_near_time(raw_login_time, sendtime).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -828,23 +861,20 @@ mod tests {
|
||||
let original = WhodUserEntry::new(
|
||||
"tty1".to_string(),
|
||||
"user".to_string(),
|
||||
Utc.with_ymd_and_hms(2045, 1, 1, 0, 0, 0).unwrap(),
|
||||
wrap_epoch(60 * 60),
|
||||
Duration::seconds(60),
|
||||
);
|
||||
|
||||
let whoent = Whoent::try_from(original.clone()).expect("Conversion to Whoent failed");
|
||||
let converted_back =
|
||||
WhodUserEntry::try_from(whoent).expect("Conversion from Whoent failed");
|
||||
WhodUserEntry::try_from(whoent.clone()).expect("Conversion from Whoent failed");
|
||||
|
||||
assert_eq!(converted_back, original);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_rwhod_timestamp_wraps_like_i32_cast() {
|
||||
let timestamp = Utc.with_ymd_and_hms(2045, 1, 1, 0, 0, 0).unwrap();
|
||||
assert_eq!(converted_back.tty, original.tty);
|
||||
assert_eq!(converted_back.user_id, original.user_id);
|
||||
assert_eq!(converted_back.idle_time, original.idle_time);
|
||||
assert_eq!(
|
||||
encode_rwhod_timestamp(timestamp),
|
||||
timestamp.timestamp() as i32
|
||||
converted_back.login_time,
|
||||
decode_rwhod_timestamp_near_time(whoent.we_utmp.out_time, original.login_time).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,13 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
const RWHOD_TIMESTAMP_CORRECTION_WINDOW: i64 = 0x40000000_i64;
|
||||
const RWHOD_TIMESTAMP_WRAP_INCREMENT: i64 = 0x70000000_i64 + 0x70000000_i64 + 0x20000000_i64;
|
||||
const RWHOD_TIMESTAMP_WRAP_INCREMENT: i64 = 0x1_0000_0000_i64;
|
||||
|
||||
const RWHOD_TIMESTAMP_FORWARD_ALLOWANCE: i64 = RWHOD_TIMESTAMP_CORRECTION_WINDOW;
|
||||
|
||||
/// Decodes a raw rwhod timestamp (i32) into a DateTime<Utc>,
|
||||
/// adding a correction value to count for any detected overflows.
|
||||
#[allow(dead_code)]
|
||||
pub fn decode_rwhod_timestamp(raw: i32, correction: i64) -> Result<DateTime<Utc>, String> {
|
||||
DateTime::from_timestamp_secs(i64::from(raw) + correction).ok_or(format!(
|
||||
"Invalid timestamp: {} with correction {}",
|
||||
@@ -20,6 +23,18 @@ pub fn decode_rwhod_timestamp(raw: i32, correction: i64) -> Result<DateTime<Utc>
|
||||
|
||||
/// Calculates the correction value for a received rwhod timestamp (i32),
|
||||
/// based on the current time (now) and the received timestamp (recvtime).
|
||||
///
|
||||
/// If the difference between the current time and the received timestamp is
|
||||
/// above a certain threshold (`RWHOD_TIMESTAMP_CORRECTION_WINDOW`),
|
||||
/// this function attempts to determine how many times the timestamp has
|
||||
/// wrapped around and returns the appropriate additional correction value
|
||||
/// to account for the overflow.
|
||||
///
|
||||
/// This function is roughly equivalent to the logic used in the original
|
||||
/// program to handle timestamp overflows, but please consider using
|
||||
/// `decode_rwhod_timestamp_near_time` for most use cases, as it is designed
|
||||
/// to be more robust.
|
||||
#[allow(dead_code)]
|
||||
pub fn rwhod_time_correction(now: DateTime<Utc>, recvtime: i32) -> i64 {
|
||||
let delta = now.timestamp() - i64::from(recvtime);
|
||||
|
||||
@@ -27,64 +42,78 @@ pub fn rwhod_time_correction(now: DateTime<Utc>, recvtime: i32) -> i64 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let wraps = (delta - RWHOD_TIMESTAMP_CORRECTION_WINDOW - 1)
|
||||
let wrap_count = (delta - RWHOD_TIMESTAMP_CORRECTION_WINDOW - 1)
|
||||
.div_euclid(RWHOD_TIMESTAMP_WRAP_INCREMENT)
|
||||
+ 1;
|
||||
|
||||
wraps * RWHOD_TIMESTAMP_WRAP_INCREMENT
|
||||
wrap_count * RWHOD_TIMESTAMP_WRAP_INCREMENT
|
||||
}
|
||||
|
||||
/// Decodes a raw rwhod timestamp (i32) into a DateTime<Utc>,
|
||||
/// by adding a correction value to count for any detected overflows,
|
||||
/// while ensuring the decoded timestamp is not after the specified upper bound.
|
||||
pub fn decode_rwhod_timestamp_not_after(
|
||||
/// Decodes a raw rwhod timestamp (i32) into a DateTime<Utc> using a wrap-sized
|
||||
/// interval around the provided `time` reference.
|
||||
///
|
||||
/// You can think of this function as looking at the current timestamp and choosing
|
||||
/// the closest valid timestamp behind it for the given raw value, while also allowing
|
||||
/// it to be a certain amount of time in the future (forward allowance) to account for
|
||||
/// clock skew or other synchronization issues.
|
||||
pub fn decode_rwhod_timestamp_near_time_with_forward_allowance(
|
||||
raw: i32,
|
||||
base_correction: i64,
|
||||
upper_bound: DateTime<Utc>,
|
||||
time: DateTime<Utc>,
|
||||
forward_allowance: i64,
|
||||
) -> Result<DateTime<Utc>, String> {
|
||||
let upper_bound = upper_bound.timestamp();
|
||||
debug_assert!(forward_allowance >= 0);
|
||||
debug_assert!(forward_allowance <= RWHOD_TIMESTAMP_WRAP_INCREMENT);
|
||||
|
||||
for offset in [1_i64, 0, -1] {
|
||||
let correction = base_correction + offset * RWHOD_TIMESTAMP_WRAP_INCREMENT;
|
||||
let candidate = i64::from(raw) + correction;
|
||||
if candidate <= upper_bound {
|
||||
return DateTime::from_timestamp_secs(candidate).ok_or(format!(
|
||||
"Invalid timestamp: {} with correction {}",
|
||||
raw, correction
|
||||
));
|
||||
}
|
||||
}
|
||||
let raw = i64::from(raw);
|
||||
let candidate = raw
|
||||
+ (time.timestamp() + forward_allowance - raw).div_euclid(RWHOD_TIMESTAMP_WRAP_INCREMENT)
|
||||
* RWHOD_TIMESTAMP_WRAP_INCREMENT;
|
||||
|
||||
decode_rwhod_timestamp(raw, base_correction - RWHOD_TIMESTAMP_WRAP_INCREMENT)
|
||||
DateTime::from_timestamp_secs(candidate).ok_or(format!(
|
||||
"Invalid timestamp: {} near {} with forward allowance {}",
|
||||
raw, time, forward_allowance
|
||||
))
|
||||
}
|
||||
|
||||
/// Decodes a raw rwhod timestamp (i32) into a DateTime<Utc>,
|
||||
/// by adding a correction value to count for any detected overflows.
|
||||
/// The correction value is calculated based on the provided `time` argument,
|
||||
/// choosing the timestamp closest to `time` among the possible candidates.
|
||||
/// Decodes a raw rwhod timestamp (i32) into a DateTime<Utc> using the default
|
||||
/// wrap-sized interval around the reference time.
|
||||
pub fn decode_rwhod_timestamp_near_time(
|
||||
raw: i32,
|
||||
time: DateTime<Utc>,
|
||||
) -> Result<DateTime<Utc>, String> {
|
||||
let base_correction = rwhod_time_correction(time, raw);
|
||||
let mut best_candidate = None;
|
||||
decode_rwhod_timestamp_near_time_with_forward_allowance(
|
||||
raw,
|
||||
time,
|
||||
RWHOD_TIMESTAMP_FORWARD_ALLOWANCE,
|
||||
)
|
||||
}
|
||||
|
||||
for offset in [1_i64, 0, -1] {
|
||||
let correction = base_correction + offset * RWHOD_TIMESTAMP_WRAP_INCREMENT;
|
||||
let candidate = i64::from(raw) + correction;
|
||||
let distance = (time.timestamp() - candidate).abs();
|
||||
pub struct RwhodTimestamps {
|
||||
pub boottime: DateTime<Utc>,
|
||||
pub sendtime: DateTime<Utc>,
|
||||
pub recvtime: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
match best_candidate {
|
||||
Some((best_distance, _, _)) if best_distance <= distance => {}
|
||||
_ => best_candidate = Some((distance, candidate, correction)),
|
||||
}
|
||||
}
|
||||
pub fn decode_rwhod_timestamps(
|
||||
wd_boottime: i32,
|
||||
wd_sendtime: i32,
|
||||
wd_recvtime: i32,
|
||||
now: DateTime<Utc>,
|
||||
) -> Result<RwhodTimestamps, String> {
|
||||
let recvtime = if wd_recvtime == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(decode_rwhod_timestamp_near_time(wd_recvtime, now)?)
|
||||
};
|
||||
|
||||
let (_, candidate, correction) = best_candidate.expect("candidate list should not be empty");
|
||||
DateTime::from_timestamp_secs(candidate).ok_or(format!(
|
||||
"Invalid timestamp: {} with correction {}",
|
||||
raw, correction
|
||||
))
|
||||
let sendtime = decode_rwhod_timestamp_near_time(wd_sendtime, now)?;
|
||||
let boottime = decode_rwhod_timestamp_near_time(wd_boottime, now)?;
|
||||
|
||||
Ok(RwhodTimestamps {
|
||||
boottime,
|
||||
sendtime,
|
||||
recvtime,
|
||||
})
|
||||
}
|
||||
|
||||
/// Encodes a DateTime<Utc> into a raw rwhod timestamp (i32).
|
||||
@@ -94,67 +123,132 @@ pub fn encode_rwhod_timestamp(timestamp: DateTime<Utc>) -> i32 {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use chrono::TimeZone;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_rwhod_timestamp_correction_for_received_packets() {
|
||||
let now = Utc.with_ymd_and_hms(2045, 1, 1, 0, 0, 0).unwrap();
|
||||
let corrected_recvtime = now - chrono::Duration::days(1);
|
||||
let raw_recvtime = corrected_recvtime.timestamp() as i32;
|
||||
let correction = rwhod_time_correction(now, raw_recvtime);
|
||||
fn test_rwhod_time_correction_no_wrap() {
|
||||
let now = DateTime::from_timestamp(i32::MAX as i64 + 1, 0).unwrap();
|
||||
let recvtime = i32::MAX;
|
||||
let correction = rwhod_time_correction(now, recvtime);
|
||||
|
||||
assert_eq!(correction, 1i64 << 32);
|
||||
assert_eq!(correction, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rwhod_time_correction_with_single_wrap() {
|
||||
let now = DateTime::from_timestamp(i32::MAX as i64 + 2, 0).unwrap();
|
||||
let recvtime = i32::MAX.wrapping_add(1);
|
||||
let correction = rwhod_time_correction(now, recvtime);
|
||||
|
||||
assert_eq!(correction, RWHOD_TIMESTAMP_WRAP_INCREMENT * 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rwhod_time_correction_with_multiple_wraps() {
|
||||
let now = DateTime::from_timestamp(i32::MAX as i64 + RWHOD_TIMESTAMP_WRAP_INCREMENT + 1, 0)
|
||||
.unwrap();
|
||||
let recvtime = i32::MAX.wrapping_add(1);
|
||||
let correction = rwhod_time_correction(now, recvtime);
|
||||
|
||||
assert_eq!(correction, RWHOD_TIMESTAMP_WRAP_INCREMENT * 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rwhod_time_correction_with_negative_delta() {
|
||||
let now = DateTime::from_timestamp(i32::MAX as i64 - 1, 0).unwrap();
|
||||
let recvtime = i32::MAX;
|
||||
let correction = rwhod_time_correction(now, recvtime);
|
||||
|
||||
assert_eq!(correction, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rwhod_time_correction_with_large_negative_delta() {
|
||||
let now = DateTime::from_timestamp(i32::MIN as i64 - 1, 0).unwrap();
|
||||
let recvtime = i32::MIN;
|
||||
let correction = rwhod_time_correction(now, recvtime);
|
||||
|
||||
assert_eq!(correction, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_rwhod_timestamp_near_time_no_wrap() {
|
||||
let now = DateTime::from_timestamp(i32::MAX as i64 + 1, 0).unwrap();
|
||||
let expected = now - chrono::Duration::days(1);
|
||||
let raw = expected.timestamp() as i32;
|
||||
|
||||
let decoded = decode_rwhod_timestamp_near_time(raw, now).unwrap();
|
||||
assert_eq!(decoded, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_rwhod_timestamp_near_time_with_wrap() {
|
||||
let now = DateTime::from_timestamp(i32::MAX as i64 + RWHOD_TIMESTAMP_WRAP_INCREMENT + 1, 0)
|
||||
.unwrap();
|
||||
let expected = now - chrono::Duration::days(1);
|
||||
let raw = expected.timestamp() as i32;
|
||||
|
||||
let decoded = decode_rwhod_timestamp_near_time(raw, now).unwrap();
|
||||
assert_eq!(decoded, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_rwhod_timestamp_near_time_with_negative_delta() {
|
||||
let time = DateTime::from_timestamp(-(RWHOD_TIMESTAMP_WRAP_INCREMENT / 2) - 10, 0).unwrap();
|
||||
let raw = 0;
|
||||
|
||||
let decoded = decode_rwhod_timestamp_near_time_with_forward_allowance(
|
||||
raw,
|
||||
time,
|
||||
RWHOD_TIMESTAMP_WRAP_INCREMENT / 2,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
decode_rwhod_timestamp(raw_recvtime, correction).unwrap(),
|
||||
corrected_recvtime
|
||||
decoded,
|
||||
DateTime::from_timestamp(-RWHOD_TIMESTAMP_WRAP_INCREMENT, 0).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_rwhod_timestamp_near_time() {
|
||||
let now = Utc.with_ymd_and_hms(2045, 1, 1, 0, 0, 0).unwrap();
|
||||
let corrected_recvtime = now - chrono::Duration::days(1);
|
||||
let raw_recvtime = corrected_recvtime.timestamp() as i32;
|
||||
fn test_decode_rwhod_timestamp_near_time_uses_forward_allowance() {
|
||||
let time = DateTime::from_timestamp(i32::MAX as i64 + 1, 0).unwrap();
|
||||
let expected = time + chrono::Duration::seconds(5);
|
||||
let raw = expected.timestamp() as i32;
|
||||
|
||||
let decoded = decode_rwhod_timestamp_near_time(raw_recvtime, now).unwrap();
|
||||
assert_eq!(decoded, corrected_recvtime);
|
||||
let decoded =
|
||||
decode_rwhod_timestamp_near_time_with_forward_allowance(raw, time, 5).unwrap();
|
||||
assert_eq!(decoded, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_rwhod_timestamp_not_after() {
|
||||
let now = Utc.with_ymd_and_hms(2045, 1, 1, 0, 0, 0).unwrap();
|
||||
let corrected_recvtime = now - chrono::Duration::days(1);
|
||||
let raw_recvtime = corrected_recvtime.timestamp() as i32;
|
||||
let base_correction = rwhod_time_correction(now, raw_recvtime);
|
||||
fn test_decode_rwhod_timestamp_near_time_small_forward_allowance_prefers_previous_wrap() {
|
||||
let time = DateTime::from_timestamp(i32::MAX as i64 + 1, 0).unwrap();
|
||||
let future = time + chrono::Duration::seconds(RWHOD_TIMESTAMP_WRAP_INCREMENT / 4);
|
||||
let raw = future.timestamp() as i32;
|
||||
|
||||
let decoded = decode_rwhod_timestamp_not_after(raw_recvtime, base_correction, now).unwrap();
|
||||
assert_eq!(decoded, corrected_recvtime);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_rwhod_timestamp_not_after_prefers_latest_plausible_pre_wrap_candidate() {
|
||||
let now = Utc.with_ymd_and_hms(2045, 1, 1, 0, 0, 0).unwrap();
|
||||
let recvtime = now - chrono::Duration::minutes(1);
|
||||
let boot_time = recvtime - chrono::Duration::days(100);
|
||||
|
||||
let raw_recvtime = recvtime.timestamp() as i32;
|
||||
let raw_boot_time = boot_time.timestamp() as i32;
|
||||
let recvtime_correction = rwhod_time_correction(now, raw_recvtime);
|
||||
|
||||
let decoded_boot_time =
|
||||
decode_rwhod_timestamp_not_after(raw_boot_time, recvtime_correction, recvtime).unwrap();
|
||||
|
||||
assert_eq!(decoded_boot_time, boot_time);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_rwhod_timestamp_wraps_like_i32_cast() {
|
||||
let timestamp = Utc.with_ymd_and_hms(2045, 1, 1, 0, 0, 0).unwrap();
|
||||
let decoded = decode_rwhod_timestamp_near_time_with_forward_allowance(
|
||||
raw,
|
||||
time,
|
||||
RWHOD_TIMESTAMP_WRAP_INCREMENT / 10,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
encode_rwhod_timestamp(timestamp),
|
||||
timestamp.timestamp() as i32
|
||||
decoded,
|
||||
future - chrono::Duration::seconds(RWHOD_TIMESTAMP_WRAP_INCREMENT)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_rwhod_timestamp_no_wrap() {
|
||||
let timestamp = DateTime::from_timestamp(i32::MAX as i64, 0).unwrap();
|
||||
let encoded = encode_rwhod_timestamp(timestamp);
|
||||
assert_eq!(encoded, i32::MAX);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_rwhod_timestamp_with_wrap() {
|
||||
let timestamp = DateTime::from_timestamp(i32::MAX as i64 + 1, 0).unwrap();
|
||||
let encoded = encode_rwhod_timestamp(timestamp);
|
||||
assert_eq!(encoded, i32::MIN);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user