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