Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ private var runtimeTargets: [Target] {
.SmithyIdentity,
.SmithyRetriesAPI,
.SmithyRetries,
.SmithyTimestamps,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new AWS-specific clock skew provider uses SmithyTimestamps to read the date header in HTTP responses, hence the dependency is added.

.AWSSDKCommon,
.AWSSDKHTTPAuth,
.AWSSDKChecksums,
Expand Down
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,7 @@ private var runtimeTargets: [Target] {
.SmithyIdentity,
.SmithyRetriesAPI,
.SmithyRetries,
.SmithyTimestamps,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See Package.Base.swift at top; this file's just generated from that.

.AWSSDKCommon,
.AWSSDKHTTPAuth,
.AWSSDKChecksums,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import struct Foundation.Date
import struct Foundation.TimeInterval
import class SmithyHTTPAPI.HTTPRequest
import class SmithyHTTPAPI.HTTPResponse
@_spi(SmithyTimestamps) import struct SmithyTimestamps.TimestampFormatter

public enum AWSClockSkewProvider {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the AWS-specific clock skew provider.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No further explanation, see inline code comments.

// Any clock skew less than this threshold will not be compensated.
private static var clockSkewThreshold: TimeInterval { 300.0 } // five minutes

@Sendable
public static func clockSkew(
request: HTTPRequest,
response: HTTPResponse,
error: Error, now: Date
) -> TimeInterval? {
// Need a server Date and an error code to calculate clock skew.
// If either of these aren't available, return no clock skew.
guard let httpDateString = response.headers.value(for: "Date") else { return nil }
guard let serverDate = httpDate(httpDateString: httpDateString) else { return nil }
guard let code = (error as? AWSServiceError)?.errorCode else { return nil }

if definiteClockSkewError(code: code) || probableClockSkewError(code: code, request: request) {
let clockSkew = serverDate.timeIntervalSince(now)
if abs(clockSkew) > clockSkewThreshold {
return clockSkew
}
}
return nil
}

private static func definiteClockSkewError(code: String) -> Bool {
definiteClockSkewErrorCodes.contains(code)
}

private static func probableClockSkewError(code: String, request: HTTPRequest) -> Bool {
probableClockSkewErrorCodes.contains(code) // && request.method == .head
}

private static func httpDate(httpDateString: String) -> Date? {
TimestampFormatter(format: .httpDate).date(from: httpDateString)
}
}

// These error codes indicate it's likely that the cause of the failure was clock skew.
private let definiteClockSkewErrorCodes = Set([
"RequestTimeTooSkewed",
"RequestExpired",
"RequestInTheFuture",
])

// Some S3 HEAD operations will return these codes instead of the definite codes when the
// operation fails due to clock skew.
private let probableClockSkewErrorCodes = Set([
"InvalidSignatureException",
"AuthFailure",
"SignatureDoesNotMatch",
])
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,13 @@ final class RetryIntegrationTests: XCTestCase {
.attributes(context)
.retryErrorInfoProvider(DefaultRetryErrorInfoProvider.errorInfo(for:))
.retryStrategy(subject)
.deserialize({ _, _ in TestOutputResponse() })
.deserialize({ response, _ in
if response.statusCode == .ok {
return TestOutputResponse()
} else {
throw TestHTTPError(statusCode: response.statusCode)
}
})
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test file changed comparable to the retry integration tests in smithy-swift.

.executeRequest(next)
builder.interceptors.add(AmzSdkInvocationIdMiddleware())
builder.interceptors.add(AmzSdkRequestMiddleware(maxRetries: subject.options.maxRetriesBase))
Expand Down Expand Up @@ -229,9 +235,10 @@ private class TestOutputHandler: ExecuteRequest {
// Return either a successful response or a HTTP error, depending on the directions in the test step.
switch testStep.response {
case .success:
return HTTPResponse()
return HTTPResponse(statusCode: .ok)
case .httpError(let statusCode):
throw TestHTTPError(statusCode: statusCode)
let httpStatusCode = HTTPStatusCode(rawValue: statusCode)!
return HTTPResponse(statusCode: httpStatusCode)
}
}

Expand Down Expand Up @@ -310,9 +317,8 @@ private class TestOutputHandler: ExecuteRequest {
private struct TestHTTPError: HTTPError, Error {
var httpResponse: HTTPResponse

init(statusCode: Int) {
guard let statusCodeValue = HTTPStatusCode(rawValue: statusCode) else { fatalError("Unrecognized HTTP code") }
self.httpResponse = HTTPResponse(statusCode: statusCodeValue)
init(statusCode: HTTPStatusCode) {
self.httpResponse = HTTPResponse(statusCode: statusCode)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ public final class AWSSigV4Signer: SmithyHTTPAuthAPI.Signer, Sendable {
)
}

let clockSkew: TimeInterval = signingProperties.get(key: SigningPropertyKeys.clockSkew) ?? 0.0
let expiration: TimeInterval = signingProperties.get(key: SigningPropertyKeys.expiration) ?? 0
let signedBodyHeader: AWSSignedBodyHeader =
signingProperties.get(key: SigningPropertyKeys.signedBodyHeader) ?? .none
Expand Down Expand Up @@ -156,7 +157,7 @@ public final class AWSSigV4Signer: SmithyHTTPAuthAPI.Signer, Sendable {
signedBodyHeader: signedBodyHeader,
signedBodyValue: signedBodyValue,
flags: flags,
date: Date(),
date: Date().addingTimeInterval(clockSkew),
service: signingName,
region: signingRegion,
signatureType: signatureType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import class Smithy.ContextBuilder
import class SmithyHTTPAPI.HTTPRequest
import class SmithyHTTPAPI.HTTPResponse
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file & the next few below demonstrate the changes to code-generated clients.

@_spi(SmithyReadWrite) import class SmithyJSON.Writer
import enum AWSClientRuntime.AWSClockSkewProvider
import enum AWSClientRuntime.AWSRetryErrorInfoProvider
import enum AWSClientRuntime.AWSRetryMode
import enum AWSSDKChecksums.AWSChecksumCalculationMode
Expand Down Expand Up @@ -411,6 +412,7 @@ extension CognitoIdentityClient {
builder.interceptors.add(ClientRuntime.ContentLengthMiddleware<GetCredentialsForIdentityInput, GetCredentialsForIdentityOutput>())
builder.deserialize(ClientRuntime.DeserializeMiddleware<GetCredentialsForIdentityOutput>(GetCredentialsForIdentityOutput.httpOutput(from:), GetCredentialsForIdentityOutputError.httpError(from:)))
builder.interceptors.add(ClientRuntime.LoggerMiddleware<GetCredentialsForIdentityInput, GetCredentialsForIdentityOutput>(clientLogMode: config.clientLogMode))
builder.clockSkewProvider(AWSClientRuntime.AWSClockSkewProvider.clockSkew(request:response:error:now:))
builder.retryStrategy(SmithyRetries.DefaultRetryStrategy(options: config.retryStrategyOptions))
builder.retryErrorInfoProvider(AWSClientRuntime.AWSRetryErrorInfoProvider.errorInfo(for:))
builder.applySigner(ClientRuntime.SignerMiddleware<GetCredentialsForIdentityOutput>())
Expand Down Expand Up @@ -484,6 +486,7 @@ extension CognitoIdentityClient {
builder.interceptors.add(ClientRuntime.ContentLengthMiddleware<GetIdInput, GetIdOutput>())
builder.deserialize(ClientRuntime.DeserializeMiddleware<GetIdOutput>(GetIdOutput.httpOutput(from:), GetIdOutputError.httpError(from:)))
builder.interceptors.add(ClientRuntime.LoggerMiddleware<GetIdInput, GetIdOutput>(clientLogMode: config.clientLogMode))
builder.clockSkewProvider(AWSClientRuntime.AWSClockSkewProvider.clockSkew(request:response:error:now:))
builder.retryStrategy(SmithyRetries.DefaultRetryStrategy(options: config.retryStrategyOptions))
builder.retryErrorInfoProvider(AWSClientRuntime.AWSRetryErrorInfoProvider.errorInfo(for:))
builder.applySigner(ClientRuntime.SignerMiddleware<GetIdOutput>())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import class Smithy.Context
import class Smithy.ContextBuilder
import class SmithyHTTPAPI.HTTPRequest
import class SmithyHTTPAPI.HTTPResponse
import enum AWSClientRuntime.AWSClockSkewProvider
import enum AWSClientRuntime.AWSRetryErrorInfoProvider
import enum AWSClientRuntime.AWSRetryMode
import enum AWSSDKChecksums.AWSChecksumCalculationMode
Expand Down Expand Up @@ -404,6 +405,7 @@ extension SSOClient {
builder.serialize(ClientRuntime.QueryItemMiddleware<GetRoleCredentialsInput, GetRoleCredentialsOutput>(GetRoleCredentialsInput.queryItemProvider(_:)))
builder.deserialize(ClientRuntime.DeserializeMiddleware<GetRoleCredentialsOutput>(GetRoleCredentialsOutput.httpOutput(from:), GetRoleCredentialsOutputError.httpError(from:)))
builder.interceptors.add(ClientRuntime.LoggerMiddleware<GetRoleCredentialsInput, GetRoleCredentialsOutput>(clientLogMode: config.clientLogMode))
builder.clockSkewProvider(AWSClientRuntime.AWSClockSkewProvider.clockSkew(request:response:error:now:))
builder.retryStrategy(SmithyRetries.DefaultRetryStrategy(options: config.retryStrategyOptions))
builder.retryErrorInfoProvider(AWSClientRuntime.AWSRetryErrorInfoProvider.errorInfo(for:))
builder.applySigner(ClientRuntime.SignerMiddleware<GetRoleCredentialsOutput>())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import class Smithy.ContextBuilder
import class SmithyHTTPAPI.HTTPRequest
import class SmithyHTTPAPI.HTTPResponse
@_spi(SmithyReadWrite) import class SmithyJSON.Writer
import enum AWSClientRuntime.AWSClockSkewProvider
import enum AWSClientRuntime.AWSRetryErrorInfoProvider
import enum AWSClientRuntime.AWSRetryMode
import enum AWSSDKChecksums.AWSChecksumCalculationMode
Expand Down Expand Up @@ -415,6 +416,7 @@ extension SSOOIDCClient {
builder.interceptors.add(ClientRuntime.ContentLengthMiddleware<CreateTokenInput, CreateTokenOutput>())
builder.deserialize(ClientRuntime.DeserializeMiddleware<CreateTokenOutput>(CreateTokenOutput.httpOutput(from:), CreateTokenOutputError.httpError(from:)))
builder.interceptors.add(ClientRuntime.LoggerMiddleware<CreateTokenInput, CreateTokenOutput>(clientLogMode: config.clientLogMode))
builder.clockSkewProvider(AWSClientRuntime.AWSClockSkewProvider.clockSkew(request:response:error:now:))
builder.retryStrategy(SmithyRetries.DefaultRetryStrategy(options: config.retryStrategyOptions))
builder.retryErrorInfoProvider(AWSClientRuntime.AWSRetryErrorInfoProvider.errorInfo(for:))
builder.applySigner(ClientRuntime.SignerMiddleware<CreateTokenOutput>())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import class Smithy.ContextBuilder
@_spi(SmithyReadWrite) import class SmithyFormURL.Writer
import class SmithyHTTPAPI.HTTPRequest
import class SmithyHTTPAPI.HTTPResponse
import enum AWSClientRuntime.AWSClockSkewProvider
import enum AWSClientRuntime.AWSRetryErrorInfoProvider
import enum AWSClientRuntime.AWSRetryMode
import enum AWSSDKChecksums.AWSChecksumCalculationMode
Expand Down Expand Up @@ -424,6 +425,7 @@ extension STSClient {
builder.interceptors.add(ClientRuntime.ContentLengthMiddleware<AssumeRoleInput, AssumeRoleOutput>())
builder.deserialize(ClientRuntime.DeserializeMiddleware<AssumeRoleOutput>(AssumeRoleOutput.httpOutput(from:), AssumeRoleOutputError.httpError(from:)))
builder.interceptors.add(ClientRuntime.LoggerMiddleware<AssumeRoleInput, AssumeRoleOutput>(clientLogMode: config.clientLogMode))
builder.clockSkewProvider(AWSClientRuntime.AWSClockSkewProvider.clockSkew(request:response:error:now:))
builder.retryStrategy(SmithyRetries.DefaultRetryStrategy(options: config.retryStrategyOptions))
builder.retryErrorInfoProvider(AWSClientRuntime.AWSRetryErrorInfoProvider.errorInfo(for:))
builder.applySigner(ClientRuntime.SignerMiddleware<AssumeRoleOutput>())
Expand Down Expand Up @@ -499,6 +501,7 @@ extension STSClient {
builder.interceptors.add(ClientRuntime.ContentLengthMiddleware<AssumeRoleWithWebIdentityInput, AssumeRoleWithWebIdentityOutput>())
builder.deserialize(ClientRuntime.DeserializeMiddleware<AssumeRoleWithWebIdentityOutput>(AssumeRoleWithWebIdentityOutput.httpOutput(from:), AssumeRoleWithWebIdentityOutputError.httpError(from:)))
builder.interceptors.add(ClientRuntime.LoggerMiddleware<AssumeRoleWithWebIdentityInput, AssumeRoleWithWebIdentityOutput>(clientLogMode: config.clientLogMode))
builder.clockSkewProvider(AWSClientRuntime.AWSClockSkewProvider.clockSkew(request:response:error:now:))
builder.retryStrategy(SmithyRetries.DefaultRetryStrategy(options: config.retryStrategyOptions))
builder.retryErrorInfoProvider(AWSClientRuntime.AWSRetryErrorInfoProvider.errorInfo(for:))
builder.applySigner(ClientRuntime.SignerMiddleware<AssumeRoleWithWebIdentityOutput>())
Expand Down
2 changes: 0 additions & 2 deletions Sources/Core/SDKForSwift/Documentation.docc/SDKForSwift.md
Original file line number Diff line number Diff line change
Expand Up @@ -763,8 +763,6 @@ This SDK is open-source. Code is available on Github [here](https://github.com/

[AWSSFN](../../../../../sdk-for-swift/latest/api/awssfn)

[AWSSMS](../../../../../sdk-for-swift/latest/api/awssms)

[AWSSNS](../../../../../sdk-for-swift/latest/api/awssns)

[AWSSQS](../../../../../sdk-for-swift/latest/api/awssqs)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ abstract class AWSHTTPBindingProtocolGenerator(
) : HTTPBindingProtocolGenerator(customizations) {
override var serviceErrorProtocolSymbol: Symbol = AWSClientRuntimeTypes.Core.AWSServiceError

override val clockSkewProviderSymbol: Symbol
get() = AWSClientRuntimeTypes.Core.AWSClockSkewProvider

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overrides the clock skew provider to use the AWS-specific implementation.

override val retryErrorInfoProviderSymbol: Symbol
get() = AWSClientRuntimeTypes.Core.AWSRetryErrorInfoProvider

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ object AWSClientRuntimeTypes {
runtimeSymbol("UnknownAWSHTTPServiceError", SwiftDeclaration.STRUCT, listOf("UnknownAWSHTTPServiceError"))
val AWSServiceError = runtimeSymbol("AWSServiceError", SwiftDeclaration.PROTOCOL)
val Sha256TreeHashMiddleware = runtimeSymbol("Sha256TreeHashMiddleware", SwiftDeclaration.STRUCT)
val AWSClockSkewProvider = runtimeSymbol("AWSClockSkewProvider", SwiftDeclaration.ENUM)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SwiftSymbol for the AWS-specific clock skew provider.

val AWSRetryErrorInfoProvider = runtimeSymbol("AWSRetryErrorInfoProvider", SwiftDeclaration.ENUM)
val AWSRetryMode = runtimeSymbol("AWSRetryMode", SwiftDeclaration.ENUM)
val AWSPartitionDefinition = runtimeSymbol("awsPartitionJSON", SwiftDeclaration.LET)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ extension GetFooInput {
builder.interceptors.add(ClientRuntime.URLHostMiddleware<GetFooInput, GetFooOutput>())
builder.deserialize(ClientRuntime.DeserializeMiddleware<GetFooOutput>(GetFooOutput.httpOutput(from:), GetFooOutputError.httpError(from:)))
builder.interceptors.add(ClientRuntime.LoggerMiddleware<GetFooInput, GetFooOutput>(clientLogMode: config.clientLogMode))
builder.clockSkewProvider(AWSClientRuntime.AWSClockSkewProvider.clockSkew(request:response:error:now:))
builder.retryStrategy(SmithyRetries.DefaultRetryStrategy(options: config.retryStrategyOptions))
builder.retryErrorInfoProvider(AWSClientRuntime.AWSRetryErrorInfoProvider.errorInfo(for:))
builder.applySigner(ClientRuntime.SignerMiddleware<GetFooOutput>())
Expand Down Expand Up @@ -116,6 +117,7 @@ extension PostFooInput {
builder.interceptors.add(ClientRuntime.ContentLengthMiddleware<PostFooInput, PostFooOutput>())
builder.deserialize(ClientRuntime.DeserializeMiddleware<PostFooOutput>(PostFooOutput.httpOutput(from:), PostFooOutputError.httpError(from:)))
builder.interceptors.add(ClientRuntime.LoggerMiddleware<PostFooInput, PostFooOutput>(clientLogMode: config.clientLogMode))
builder.clockSkewProvider(AWSClientRuntime.AWSClockSkewProvider.clockSkew(request:response:error:now:))
builder.retryStrategy(SmithyRetries.DefaultRetryStrategy(options: config.retryStrategyOptions))
builder.retryErrorInfoProvider(AWSClientRuntime.AWSRetryErrorInfoProvider.errorInfo(for:))
builder.applySigner(ClientRuntime.SignerMiddleware<PostFooOutput>())
Expand Down Expand Up @@ -186,6 +188,7 @@ extension PutFooInput {
builder.interceptors.add(ClientRuntime.ContentLengthMiddleware<PutFooInput, PutFooOutput>())
builder.deserialize(ClientRuntime.DeserializeMiddleware<PutFooOutput>(PutFooOutput.httpOutput(from:), PutFooOutputError.httpError(from:)))
builder.interceptors.add(ClientRuntime.LoggerMiddleware<PutFooInput, PutFooOutput>(clientLogMode: config.clientLogMode))
builder.clockSkewProvider(AWSClientRuntime.AWSClockSkewProvider.clockSkew(request:response:error:now:))
builder.retryStrategy(SmithyRetries.DefaultRetryStrategy(options: config.retryStrategyOptions))
builder.retryErrorInfoProvider(AWSClientRuntime.AWSRetryErrorInfoProvider.errorInfo(for:))
builder.applySigner(ClientRuntime.SignerMiddleware<PutFooOutput>())
Expand Down Expand Up @@ -258,6 +261,7 @@ extension PutObjectInput {
builder.interceptors.add(ClientRuntime.ContentLengthMiddleware<PutObjectInput, PutObjectOutput>())
builder.deserialize(ClientRuntime.DeserializeMiddleware<PutObjectOutput>(PutObjectOutput.httpOutput(from:), PutObjectOutputError.httpError(from:)))
builder.interceptors.add(ClientRuntime.LoggerMiddleware<PutObjectInput, PutObjectOutput>(clientLogMode: config.clientLogMode))
builder.clockSkewProvider(AWSClientRuntime.AWSClockSkewProvider.clockSkew(request:response:error:now:))
builder.retryStrategy(SmithyRetries.DefaultRetryStrategy(options: config.retryStrategyOptions))
builder.retryErrorInfoProvider(AWSClientRuntime.AWSRetryErrorInfoProvider.errorInfo(for:))
builder.applySigner(ClientRuntime.SignerMiddleware<PutObjectOutput>())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,14 @@ class AWSQueryOperationStackTest {
builder.interceptors.add(ClientRuntime.ContentLengthMiddleware<NoInputAndOutputInput, NoInputAndOutputOutput>())
builder.deserialize(ClientRuntime.DeserializeMiddleware<NoInputAndOutputOutput>(NoInputAndOutputOutput.httpOutput(from:), NoInputAndOutputOutputError.httpError(from:)))
builder.interceptors.add(ClientRuntime.LoggerMiddleware<NoInputAndOutputInput, NoInputAndOutputOutput>(clientLogMode: config.clientLogMode))
builder.clockSkewProvider(AWSClientRuntime.AWSClockSkewProvider.clockSkew(request:response:error:now:))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here & below, expectations for several tests are changed to accommodate new codegen.

builder.retryStrategy(SmithyRetries.DefaultRetryStrategy(options: config.retryStrategyOptions))
builder.retryErrorInfoProvider(AWSClientRuntime.AWSRetryErrorInfoProvider.errorInfo(for:))
builder.applySigner(ClientRuntime.SignerMiddleware<NoInputAndOutputOutput>())
let endpointParamsBlock = { [config] (context: Smithy.Context) in
EndpointParams()
}
builder.applyEndpoint(AWSClientRuntime.AWSEndpointResolverMiddleware<NoInputAndOutputOutput, EndpointParams>(paramsBlock: endpointParamsBlock, resolverBlock: { [config] in try config.endpointResolver.resolve(params: ${'$'}0) }))
builder.applyEndpoint(AWSClientRuntime.AWSEndpointResolverMiddleware<NoInputAndOutputOutput, EndpointParams>(paramsBlock: endpointParamsBlock, resolverBlock: { [config] in try config.endpointResolver.resolve(params: $0) }))
builder.serialize(ClientRuntime.BodyMiddleware<NoInputAndOutputInput, NoInputAndOutputOutput, SmithyFormURL.Writer>(rootNodeInfo: "", inputWritingClosure: NoInputAndOutputInput.write(value:to:)))
builder.interceptors.add(ClientRuntime.ContentTypeMiddleware<NoInputAndOutputInput, NoInputAndOutputOutput>(contentType: "application/x-www-form-urlencoded"))
builder.selectAuthScheme(ClientRuntime.AuthSchemeMiddleware<NoInputAndOutputOutput>())
Expand Down
Loading