use std::time::{Duration, SystemTime, UNIX_EPOCH};
use crate::config::SignedDuration;
pub fn shave_signed(payload: &[u8], by: SignedDuration) -> Vec<u8> {
if payload.len() < 12 {
return payload.to_vec();
}
let mut out = payload.to_vec();
let secs = u64::from_le_bytes(out[0..8].try_into().unwrap());
let micros = u32::from_le_bytes(out[8..12].try_into().unwrap()) as u64;
let total_micros = secs.saturating_mul(1_000_000).saturating_add(micros);
let by_micros = by.magnitude.as_secs().saturating_mul(1_000_000)
.saturating_add(by.magnitude.subsec_micros() as u64);
let new_total = if by.negative {
total_micros.saturating_sub(by_micros)
} else {
total_micros.saturating_add(by_micros)
};
let new_secs = new_total / 1_000_000;
let new_micros = (new_total % 1_000_000) as u32;
out[0..8].copy_from_slice(&new_secs.to_le_bytes());
out[8..12].copy_from_slice(&new_micros.to_le_bytes());
out
}
pub fn read_timestamp(payload: &[u8]) -> Option<SystemTime> {
if payload.len() < 12 {
return None;
}
let secs = u64::from_le_bytes(payload[0..8].try_into().unwrap());
let micros = u32::from_le_bytes(payload[8..12].try_into().unwrap());
let dur = Duration::from_secs(secs).checked_add(Duration::from_micros(micros as u64))?;
UNIX_EPOCH.checked_add(dur)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::SignedDuration;
fn pack(secs: u64, micros: u32, tail: &[u8]) -> Vec<u8> {
let mut v = Vec::with_capacity(12 + tail.len());
v.extend_from_slice(&secs.to_le_bytes());
v.extend_from_slice(µs.to_le_bytes());
v.extend_from_slice(tail);
v
}
fn unpack(payload: &[u8]) -> (u64, u32) {
let mut s = [0u8; 8];
s.copy_from_slice(&payload[0..8]);
let mut m = [0u8; 4];
m.copy_from_slice(&payload[8..12]);
(u64::from_le_bytes(s), u32::from_le_bytes(m))
}
fn pos(d: Duration) -> SignedDuration { SignedDuration::from_duration(d) }
fn neg(d: Duration) -> SignedDuration { SignedDuration { negative: true, magnitude: d } }
#[test]
fn advances_micros_without_carry() {
let p = pack(1_000, 500_000, b"rest");
let out = shave_signed(&p, pos(Duration::from_micros(20)));
assert_eq!(unpack(&out), (1_000, 500_020));
assert_eq!(&out[12..], b"rest", "tail must be preserved");
}
#[test]
fn advances_micros_with_carry_into_seconds() {
let p = pack(1_000, 999_900, b"");
let out = shave_signed(&p, pos(Duration::from_micros(200)));
assert_eq!(unpack(&out), (1_001, 100));
}
#[test]
fn advances_whole_seconds() {
let p = pack(1_000, 250, b"");
let out = shave_signed(&p, pos(Duration::from_secs(3)));
assert_eq!(unpack(&out), (1_003, 250));
}
#[test]
fn short_payload_unchanged() {
let p = b"short".to_vec();
let out = shave_signed(&p, pos(Duration::from_millis(10)));
assert_eq!(out, p);
}
#[test]
fn short_payload_unchanged_negative() {
let p = b"short".to_vec();
let out = shave_signed(&p, neg(Duration::from_millis(10)));
assert_eq!(out, p);
}
#[test]
fn retreats_micros_without_underflow() {
let p = pack(1_000, 500_000, b"rest");
let out = shave_signed(&p, neg(Duration::from_micros(20)));
assert_eq!(unpack(&out), (1_000, 499_980));
assert_eq!(&out[12..], b"rest");
}
#[test]
fn retreats_across_seconds_boundary() {
let p = pack(1_000, 100, b"");
let out = shave_signed(&p, neg(Duration::from_micros(200)));
assert_eq!(unpack(&out), (999, 999_900));
}
#[test]
fn retreats_saturate_to_zero() {
let p = pack(0, 100, b"");
let out = shave_signed(&p, neg(Duration::from_secs(10)));
assert_eq!(unpack(&out), (0, 0));
}
#[test]
fn read_timestamp_happy_path() {
let p = pack(1_800_000_000, 250_000, b"rest");
let t = read_timestamp(&p).unwrap();
let d = t.duration_since(UNIX_EPOCH).unwrap();
assert_eq!(d.as_secs(), 1_800_000_000);
assert_eq!(d.subsec_micros(), 250_000);
}
#[test]
fn read_timestamp_short_payload_is_none() {
assert!(read_timestamp(b"short").is_none());
}
}