aws_runtime/
service_clock_skew.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6use aws_smithy_runtime_api::box_error::BoxError;
7use aws_smithy_runtime_api::client::interceptors::context::BeforeDeserializationInterceptorContextMut;
8use aws_smithy_runtime_api::client::interceptors::Intercept;
9use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
10use aws_smithy_types::config_bag::{ConfigBag, Storable, StoreReplace};
11use aws_smithy_types::date_time::Format;
12use aws_smithy_types::DateTime;
13use std::time::Duration;
14
15/// Amount of clock skew between the client and the service.
16#[derive(Debug, Clone)]
17#[non_exhaustive]
18pub(crate) struct ServiceClockSkew {
19    inner: Duration,
20}
21
22impl ServiceClockSkew {
23    fn new(inner: Duration) -> Self {
24        Self { inner }
25    }
26}
27
28impl Storable for ServiceClockSkew {
29    type Storer = StoreReplace<Self>;
30}
31
32impl From<ServiceClockSkew> for Duration {
33    fn from(skew: ServiceClockSkew) -> Duration {
34        skew.inner
35    }
36}
37
38/// Interceptor that determines the clock skew between the client and service.
39#[derive(Debug, Default)]
40#[non_exhaustive]
41pub struct ServiceClockSkewInterceptor;
42
43impl ServiceClockSkewInterceptor {
44    /// Creates a new `ServiceClockSkewInterceptor`.
45    pub fn new() -> Self {
46        Self::default()
47    }
48}
49
50fn calculate_skew(time_sent: DateTime, time_received: DateTime) -> Duration {
51    let skew = (time_sent.as_secs_f64() - time_received.as_secs_f64()).max(0.0);
52    Duration::from_secs_f64(skew)
53}
54
55fn extract_time_sent_from_response(
56    ctx: &mut BeforeDeserializationInterceptorContextMut<'_>,
57) -> Result<DateTime, BoxError> {
58    let date_header = ctx
59        .response()
60        .headers()
61        .get("date")
62        .ok_or("Response from server does not include a `date` header")?;
63    DateTime::from_str(date_header, Format::HttpDate).map_err(Into::into)
64}
65
66impl Intercept for ServiceClockSkewInterceptor {
67    fn name(&self) -> &'static str {
68        "ServiceClockSkewInterceptor"
69    }
70
71    fn modify_before_deserialization(
72        &self,
73        ctx: &mut BeforeDeserializationInterceptorContextMut<'_>,
74        runtime_components: &RuntimeComponents,
75        cfg: &mut ConfigBag,
76    ) -> Result<(), BoxError> {
77        let time_received = DateTime::from(
78            runtime_components
79                .time_source()
80                .ok_or("a time source is required (service clock skew)")?
81                .now(),
82        );
83        let time_sent = match extract_time_sent_from_response(ctx) {
84            Ok(time_sent) => time_sent,
85            Err(e) => {
86                // We don't want to fail a request for this because 1xx and 5xx responses and
87                // responses from servers with no clock may omit this header. We still log it at the
88                // trace level to aid in debugging.
89                tracing::trace!("failed to calculate clock skew of service from response: {e}. Ignoring this error...",);
90                return Ok(());
91            }
92        };
93        let skew = ServiceClockSkew::new(calculate_skew(time_sent, time_received));
94        cfg.interceptor_state().store_put(skew);
95        Ok(())
96    }
97}