use aws_smithy_runtime_api::box_error::BoxError;
use aws_smithy_runtime_api::client::interceptors::context::BeforeDeserializationInterceptorContextMut;
use aws_smithy_runtime_api::client::interceptors::Intercept;
use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
use aws_smithy_types::config_bag::{ConfigBag, Storable, StoreReplace};
use aws_smithy_types::date_time::Format;
use aws_smithy_types::DateTime;
use std::time::Duration;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub(crate) struct ServiceClockSkew {
inner: Duration,
}
impl ServiceClockSkew {
fn new(inner: Duration) -> Self {
Self { inner }
}
}
impl Storable for ServiceClockSkew {
type Storer = StoreReplace<Self>;
}
impl From<ServiceClockSkew> for Duration {
fn from(skew: ServiceClockSkew) -> Duration {
skew.inner
}
}
#[derive(Debug, Default)]
#[non_exhaustive]
pub struct ServiceClockSkewInterceptor;
impl ServiceClockSkewInterceptor {
pub fn new() -> Self {
Self::default()
}
}
fn calculate_skew(time_sent: DateTime, time_received: DateTime) -> Duration {
let skew = (time_sent.as_secs_f64() - time_received.as_secs_f64()).max(0.0);
Duration::from_secs_f64(skew)
}
fn extract_time_sent_from_response(
ctx: &mut BeforeDeserializationInterceptorContextMut<'_>,
) -> Result<DateTime, BoxError> {
let date_header = ctx
.response()
.headers()
.get("date")
.ok_or("Response from server does not include a `date` header")?;
DateTime::from_str(date_header, Format::HttpDate).map_err(Into::into)
}
impl Intercept for ServiceClockSkewInterceptor {
fn name(&self) -> &'static str {
"ServiceClockSkewInterceptor"
}
fn modify_before_deserialization(
&self,
ctx: &mut BeforeDeserializationInterceptorContextMut<'_>,
runtime_components: &RuntimeComponents,
cfg: &mut ConfigBag,
) -> Result<(), BoxError> {
let time_received = DateTime::from(
runtime_components
.time_source()
.ok_or("a time source is required (service clock skew)")?
.now(),
);
let time_sent = match extract_time_sent_from_response(ctx) {
Ok(time_sent) => time_sent,
Err(e) => {
tracing::trace!("failed to calculate clock skew of service from response: {e}. Ignoring this error...",);
return Ok(());
}
};
let skew = ServiceClockSkew::new(calculate_skew(time_sent, time_received));
cfg.interceptor_state().store_put(skew);
Ok(())
}
}