xref: /aosp_15_r20/external/aws-sdk-java-v2/core/sdk-core/src/main/java/software/amazon/awssdk/core/retry/ClockSkew.java (revision 8a52c7834d808308836a99fc2a6e0ed8db339086)
1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */
15 
16 package software.amazon.awssdk.core.retry;
17 
18 import java.time.Duration;
19 import java.time.Instant;
20 import java.util.Optional;
21 import software.amazon.awssdk.annotations.SdkProtectedApi;
22 import software.amazon.awssdk.annotations.ThreadSafe;
23 import software.amazon.awssdk.core.internal.http.HttpClientDependencies;
24 import software.amazon.awssdk.http.SdkHttpResponse;
25 import software.amazon.awssdk.utils.DateUtils;
26 import software.amazon.awssdk.utils.Logger;
27 
28 /**
29  * Utility methods for checking and reacting to the current client-side clock being different from the service-side clock.
30  */
31 @ThreadSafe
32 @SdkProtectedApi
33 public final class ClockSkew {
34     private static final Logger log = Logger.loggerFor(ClockSkew.class);
35 
36     /**
37      * When we get an error that may be due to a clock skew error, and our clock is different than the service clock, this is
38      * the difference threshold beyond which we will recommend a clock skew adjustment.
39      */
40     private static final Duration CLOCK_SKEW_ADJUST_THRESHOLD = Duration.ofMinutes(4);
41 
ClockSkew()42     private ClockSkew() {
43     }
44 
45     /**
46      * Determine whether the request-level client time was sufficiently skewed from the server time as to possibly cause a
47      * clock skew error.
48      */
isClockSkewed(Instant clientTime, Instant serverTime)49     public static boolean isClockSkewed(Instant clientTime, Instant serverTime) {
50         Duration requestClockSkew = getClockSkew(clientTime, serverTime);
51         return requestClockSkew.abs().compareTo(CLOCK_SKEW_ADJUST_THRESHOLD) >= 0;
52     }
53 
54     /**
55      * Calculate the time skew between a client and server date. This value has the same semantics of
56      * {@link HttpClientDependencies#timeOffset()}. Positive values imply the client clock is "fast" and negative values imply
57      * the client clock is "slow".
58      */
getClockSkew(Instant clientTime, Instant serverTime)59     public static Duration getClockSkew(Instant clientTime, Instant serverTime) {
60         if (clientTime == null || serverTime == null) {
61             // If we do not have a client or server time, 0 is the safest skew to apply
62             return Duration.ZERO;
63         }
64 
65         return Duration.between(serverTime, clientTime);
66     }
67 
68     /**
69      * Get the server time from the service response, or empty if the time could not be determined.
70      */
getServerTime(SdkHttpResponse serviceResponse)71     public static Optional<Instant> getServerTime(SdkHttpResponse serviceResponse) {
72         Optional<String> responseDateHeader = serviceResponse.firstMatchingHeader("Date");
73 
74         if (responseDateHeader.isPresent()) {
75             String serverDate = responseDateHeader.get();
76             log.debug(() -> "Reported service date: " + serverDate);
77 
78             try {
79                 return Optional.of(DateUtils.parseRfc822Date(serverDate));
80             } catch (RuntimeException e) {
81                 log.warn(() -> "Unable to parse clock skew offset from response: " + serverDate, e);
82                 return Optional.empty();
83             }
84         }
85 
86         log.debug(() -> "Service did not return a Date header, so clock skew adjustments will not be applied.");
87         return Optional.empty();
88     }
89 }
90