pub use forensicnomicon::heuristics::{CORE_DATA_EPOCH_OFFSET_SECS, WEBKIT_EPOCH_OFFSET_US};
pub use forensicnomicon::temporal::FILETIME_EPOCH_OFFSET;
pub fn webkit_micros_to_unix_nanos(webkit_us: i64) -> i64 {
webkit_us
.saturating_sub(WEBKIT_EPOCH_OFFSET_US)
.saturating_mul(1_000)
}
pub fn core_data_secs_to_unix_nanos(core_data_secs: f64) -> i64 {
((core_data_secs as i64).saturating_add(CORE_DATA_EPOCH_OFFSET_SECS))
.saturating_mul(1_000_000_000)
}
pub fn unix_micros_to_nanos(us: i64) -> i64 {
us.saturating_mul(1_000)
}
pub fn unix_millis_to_nanos(ms: i64) -> i64 {
ms.saturating_mul(1_000_000)
}
pub fn unix_secs_to_nanos(secs: i64) -> i64 {
secs.saturating_mul(1_000_000_000)
}
pub fn unix_secs_f64_to_nanos(secs: f64) -> i64 {
let whole = secs.trunc() as i64;
let frac_ns = (secs.fract() * 1_000_000_000.0) as i64;
whole * 1_000_000_000 + frac_ns
}
#[must_use]
pub fn filetime_to_unix_nanos(ft: u64) -> i64 {
let ticks_since_unix = i128::from(ft) - i128::from(FILETIME_EPOCH_OFFSET);
let nanos = ticks_since_unix * 100;
i64::try_from(nanos).unwrap_or(if nanos.is_negative() {
i64::MIN
} else {
i64::MAX
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn webkit_epoch_at_unix_epoch_returns_zero() {
let webkit_us = WEBKIT_EPOCH_OFFSET_US;
assert_eq!(webkit_micros_to_unix_nanos(webkit_us), 0);
}
#[test]
fn webkit_one_second_after_unix_epoch() {
let webkit_us = WEBKIT_EPOCH_OFFSET_US + 1_000_000;
assert_eq!(webkit_micros_to_unix_nanos(webkit_us), 1_000_000_000);
}
#[test]
fn core_data_epoch_at_unix_offset_returns_negative_secs() {
assert_eq!(
core_data_secs_to_unix_nanos(0.0),
CORE_DATA_EPOCH_OFFSET_SECS * 1_000_000_000
);
}
#[test]
fn core_data_one_second_later() {
assert_eq!(
core_data_secs_to_unix_nanos(1.0),
(CORE_DATA_EPOCH_OFFSET_SECS + 1) * 1_000_000_000
);
}
#[test]
fn unix_micros_to_nanos_multiplies_by_1000() {
assert_eq!(unix_micros_to_nanos(1_000_000), 1_000_000_000);
assert_eq!(unix_micros_to_nanos(0), 0);
}
#[test]
fn unix_millis_to_nanos_multiplies_by_1_000_000() {
assert_eq!(unix_millis_to_nanos(1_000), 1_000_000_000);
}
#[test]
fn unix_secs_to_nanos_multiplies_by_1_000_000_000() {
assert_eq!(unix_secs_to_nanos(1), 1_000_000_000);
}
#[test]
fn conversions_saturate_on_adversarial_input_never_panic() {
assert_eq!(webkit_micros_to_unix_nanos(i64::MAX), i64::MAX);
assert_eq!(webkit_micros_to_unix_nanos(i64::MIN), i64::MIN);
assert_eq!(unix_secs_to_nanos(i64::MAX), i64::MAX);
assert_eq!(unix_millis_to_nanos(i64::MIN), i64::MIN);
assert_eq!(unix_micros_to_nanos(i64::MAX), i64::MAX);
}
#[test]
fn unix_secs_f64_preserves_subsecond() {
assert_eq!(unix_secs_f64_to_nanos(1.0), 1_000_000_000);
assert_eq!(
unix_secs_f64_to_nanos(1_700_000_000.5),
1_700_000_000_500_000_000
);
assert_eq!(unix_secs_f64_to_nanos(0.0), 0);
}
#[test]
fn filetime_at_unix_epoch_returns_zero() {
assert_eq!(filetime_to_unix_nanos(FILETIME_EPOCH_OFFSET), 0);
}
#[test]
fn filetime_one_second_after_unix_epoch() {
let ft = FILETIME_EPOCH_OFFSET + 10_000_000;
assert_eq!(filetime_to_unix_nanos(ft), 1_000_000_000);
}
#[test]
fn filetime_preserves_100ns_precision() {
let ft = FILETIME_EPOCH_OFFSET + 1;
assert_eq!(filetime_to_unix_nanos(ft), 100);
}
#[test]
fn filetime_known_value_2023_01_01() {
let ft = (1_672_531_200u64 + 11_644_473_600u64) * 10_000_000;
assert_eq!(filetime_to_unix_nanos(ft), 1_672_531_200_000_000_000);
}
#[test]
fn filetime_adversarial_extremes_never_panic() {
assert_eq!(filetime_to_unix_nanos(u64::MAX), i64::MAX);
assert_eq!(filetime_to_unix_nanos(0), i64::MIN);
}
}