use std::fmt;
use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
pub const CLOCK_TRUST_LOCAL_MS: i64 = 2_000;
static SKEW_MS: AtomicI64 = AtomicI64::new(0);
static SKEW_SEEN: AtomicBool = AtomicBool::new(false);
pub(crate) fn local_unix_ms() -> i64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| i64::try_from(d.as_millis()).unwrap_or(i64::MAX))
.unwrap_or(0)
}
fn record_skew(s3_unix_ms: i64) {
SKEW_MS.store(s3_unix_ms - local_unix_ms(), Ordering::Relaxed);
SKEW_SEEN.store(true, Ordering::Relaxed);
}
pub fn s3_clock_skew_ms() -> Option<i64> {
SKEW_SEEN.load(Ordering::Relaxed).then(|| SKEW_MS.load(Ordering::Relaxed))
}
pub(crate) fn now_ms() -> i64 {
let local = local_unix_ms();
match s3_clock_skew_ms() {
Some(skew) if skew.abs() > CLOCK_TRUST_LOCAL_MS => local.saturating_add(skew),
_ => local,
}
}
#[derive(Clone, Default)]
pub struct S3ClockInterceptor;
impl fmt::Debug for S3ClockInterceptor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "S3ClockInterceptor")
}
}
impl aws_sdk_s3::config::Intercept for S3ClockInterceptor {
fn name(&self) -> &'static str {
"S3ClockInterceptor"
}
fn read_after_execution(
&self,
ctx: &aws_sdk_s3::config::interceptors::FinalizerInterceptorContextRef<'_>,
_: &aws_sdk_s3::config::RuntimeComponents,
_: &mut aws_sdk_s3::config::ConfigBag,
) -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
if let Some(resp) = ctx.response() {
if let Some(date) = resp.headers().get("date") {
if let Ok(dt) = chrono::DateTime::parse_from_rfc2822(date) {
record_skew(dt.timestamp_millis());
}
}
}
Ok(())
}
}