diff --git a/AWSSDKSwiftCLI/Sources/AWSSDKSwiftCLI/Resources/Package.Base.txt b/AWSSDKSwiftCLI/Sources/AWSSDKSwiftCLI/Resources/Package.Base.txt index d28889b57d3..b21f6001576 100644 --- a/AWSSDKSwiftCLI/Sources/AWSSDKSwiftCLI/Resources/Package.Base.txt +++ b/AWSSDKSwiftCLI/Sources/AWSSDKSwiftCLI/Resources/Package.Base.txt @@ -110,7 +110,8 @@ private var runtimeTargets: [Target] { .smithyEventStreamsAuthAPI, .awsSDKCommon, .awsSDKHTTPAuth, - .awsSDKIdentity + .awsSDKIdentity, + .awsSDKChecksums, ], path: "Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime", resources: [ diff --git a/IntegrationTests/Services/AWSS3IntegrationTests/S3FlexibleChecksumsTests.swift b/IntegrationTests/Services/AWSS3IntegrationTests/S3FlexibleChecksumsTests.swift index cc9cfac4d00..aca7dcb6d02 100644 --- a/IntegrationTests/Services/AWSS3IntegrationTests/S3FlexibleChecksumsTests.swift +++ b/IntegrationTests/Services/AWSS3IntegrationTests/S3FlexibleChecksumsTests.swift @@ -25,6 +25,11 @@ final class S3FlexibleChecksumsTests: S3XCTestCase { // MARK: - Data uploads + func test_putGetObject_data_default_algorithm() async throws { + // CRC32 should be used correctly as default algorithm when it's not configured + try await _testPutGetObject(withChecksumAlgorithm: nil, objectNameSuffix: "default-crc32-data", upload: .data(originalData)) + } + func test_putGetObject_data_crc32() async throws { try await _testPutGetObject(withChecksumAlgorithm: .crc32, objectNameSuffix: "crc32-data", upload: .data(originalData)) } @@ -43,6 +48,12 @@ final class S3FlexibleChecksumsTests: S3XCTestCase { // MARK: - Streaming uploads + func test_putGetObject_streaming_default_algorithm() async throws { + let bufferedStream = BufferedStream(data: originalData, isClosed: true) + // CRC32 should be used correctly as default algorithm when it's not configured + try await _testPutGetObject(withChecksumAlgorithm: nil, objectNameSuffix: "default-crc32-data", upload: .stream(bufferedStream)) + } + func test_putGetObject_streaming_crc32() async throws { let bufferedStream = BufferedStream(data: originalData, isClosed: true) try await _testPutGetObject(withChecksumAlgorithm: .crc32, objectNameSuffix: "crc32", upload: .stream(bufferedStream)) @@ -129,7 +140,7 @@ final class S3FlexibleChecksumsTests: S3XCTestCase { // MARK: - Private methods private func _testPutGetObject( - withChecksumAlgorithm algorithm: S3ClientTypes.ChecksumAlgorithm, + withChecksumAlgorithm algorithm: S3ClientTypes.ChecksumAlgorithm?, objectNameSuffix: String, upload: ByteStream, file: StaticString = #filePath, line: UInt = #line ) async throws { let objectName = "flexible-checksums-s3-test-\(objectNameSuffix)" @@ -143,8 +154,8 @@ final class S3FlexibleChecksumsTests: S3XCTestCase { let output = try await client.putObject(input: input) - // Verify the checksum response based on the algorithm used. - let checksumResponse = try XCTUnwrap(getChecksumResponse(from: output, with: algorithm), file: file, line: line) + // Verify the checksum response based on the algorithm used; if algorithm was nil, crc32 should've been used so check for crc32. + let checksumResponse = try XCTUnwrap(getChecksumResponse(from: output, with: algorithm ?? .crc32), file: file, line: line) XCTAssertNotNil(checksumResponse, file: file, line: line) let getInput = GetObjectInput(bucket: bucketName, checksumMode: S3ClientTypes.ChecksumMode.enabled, key: objectName) diff --git a/IntegrationTests/Services/AWSS3IntegrationTests/S3PresignedURLTests.swift b/IntegrationTests/Services/AWSS3IntegrationTests/S3PresignedURLTests.swift index 2f76f851204..ce9a4099102 100644 --- a/IntegrationTests/Services/AWSS3IntegrationTests/S3PresignedURLTests.swift +++ b/IntegrationTests/Services/AWSS3IntegrationTests/S3PresignedURLTests.swift @@ -33,4 +33,29 @@ class S3PresignedURLTests: S3XCTestCase { XCTAssertNotNil(components?.queryItems?.first(where: { $0.name == "IfMatch" && $0.value == originalIfMatch })) XCTAssertNotNil(components?.queryItems?.first(where: { $0.name == "IfNoneMatch" && $0.value == originalIfNoneMatch })) } + + func test_putObject_putsObjectWithPresignedURL() async throws { + let originalData = UUID().uuidString + let key = UUID().uuidString + let input = PutObjectInput(body: .data(originalData.data(using: .utf8)), bucket: bucketName, key: key) + let url = try await client.presignedURLForPutObject(input: input, expiration: 600); + + var request = URLRequest(url: url) + request.httpBody = Data(originalData.utf8) + request.httpMethod = "PUT" + + _ = try await perform(urlRequest: request) + + let getObjResult = try await client.getObject(input: GetObjectInput(bucket: bucketName, key: key)) + + guard let fetchedData = try await getObjResult.body?.readData() else { + throw GetObjectError.failedToGetObject + } + + XCTAssertEqual(Data(originalData.utf8), fetchedData) + } + + enum GetObjectError: Error { + case failedToGetObject + } } diff --git a/Package.version.next b/Package.version.next index 63ade06edf6..9084fa2f716 100644 --- a/Package.version.next +++ b/Package.version.next @@ -1 +1 @@ -1.0.79 \ No newline at end of file +1.1.0 diff --git a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/AWSClientConfigDefaultsProvider.swift b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/AWSClientConfigDefaultsProvider.swift index e2472fa9115..be56bdd2af0 100644 --- a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/AWSClientConfigDefaultsProvider.swift +++ b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/AWSClientConfigDefaultsProvider.swift @@ -18,6 +18,7 @@ import enum ClientRuntime.ClientLogMode import struct SmithyRetries.DefaultRetryStrategy import struct SmithyRetries.ExponentialBackoffStrategy import struct SmithyRetriesAPI.RetryStrategyOptions +import enum AWSSDKChecksums.AWSChecksumCalculationMode typealias RuntimeConfigType = DefaultSDKRuntimeConfiguration @@ -84,6 +85,40 @@ public class AWSClientConfigDefaultsProvider { return resolvedAppID } + public static func requestChecksumCalculation( + _ requestChecksumCalculation: AWSChecksumCalculationMode? = nil + ) throws -> AWSChecksumCalculationMode { + let fileBasedConfig = try CRTFileBasedConfiguration.make() + let resolvedRequestChecksumCalculation: AWSChecksumCalculationMode + if let requestChecksumCalculation { + resolvedRequestChecksumCalculation = requestChecksumCalculation + } else { + resolvedRequestChecksumCalculation = AWSChecksumsConfig.requestChecksumCalculation( + configValue: nil, + profileName: nil, + fileBasedConfig: fileBasedConfig + ) + } + return resolvedRequestChecksumCalculation + } + + public static func responseChecksumValidation( + _ responseChecksumValidation: AWSChecksumCalculationMode? = nil + ) throws -> AWSChecksumCalculationMode { + let fileBasedConfig = try CRTFileBasedConfiguration.make() + let resolvedResponseChecksumValidation: AWSChecksumCalculationMode + if let responseChecksumValidation { + resolvedResponseChecksumValidation = responseChecksumValidation + } else { + resolvedResponseChecksumValidation = AWSChecksumsConfig.responseChecksumValidation( + configValue: nil, + profileName: nil, + fileBasedConfig: fileBasedConfig + ) + } + return resolvedResponseChecksumValidation + } + public static func retryMode(_ retryMode: AWSRetryMode? = nil) throws -> AWSRetryMode { let fileBasedConfig = try CRTFileBasedConfiguration.make() let resolvedRetryMode: AWSRetryMode? diff --git a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/Config/AWSChecksumsConfig.swift b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/Config/AWSChecksumsConfig.swift new file mode 100644 index 00000000000..530041eeecc --- /dev/null +++ b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/Config/AWSChecksumsConfig.swift @@ -0,0 +1,41 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import enum AWSSDKChecksums.AWSChecksumCalculationMode +@_spi(FileBasedConfig) import AWSSDKCommon + +public enum AWSChecksumsConfig { + static func requestChecksumCalculation( + configValue: AWSChecksumCalculationMode?, + profileName: String?, + fileBasedConfig: FileBasedConfiguration + ) -> AWSChecksumCalculationMode { + return FieldResolver( + configValue: configValue, + envVarName: "AWS_REQUEST_CHECKSUM_CALCULATION", + configFieldName: "request_checksum_calculation", + fileBasedConfig: fileBasedConfig, + profileName: profileName, + converter: { AWSChecksumCalculationMode(caseInsensitiveRawValue: $0) } + ).value ?? .whenSupported + } + + static func responseChecksumValidation( + configValue: AWSChecksumCalculationMode?, + profileName: String?, + fileBasedConfig: FileBasedConfiguration + ) -> AWSChecksumCalculationMode { + return FieldResolver( + configValue: configValue, + envVarName: "AWS_RESPONSE_CHECKSUM_VALIDATION", + configFieldName: "response_checksum_validation", + fileBasedConfig: fileBasedConfig, + profileName: profileName, + converter: { AWSChecksumCalculationMode(caseInsensitiveRawValue: $0) } + ).value ?? .whenSupported + } +} diff --git a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/Config/AWSDefaultClientConfiguration.swift b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/Config/AWSDefaultClientConfiguration.swift index cd1e36a9bd9..e32ea325f5b 100644 --- a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/Config/AWSDefaultClientConfiguration.swift +++ b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/Config/AWSDefaultClientConfiguration.swift @@ -7,6 +7,7 @@ import SmithyIdentity import SmithyIdentityAPI +import enum AWSSDKChecksums.AWSChecksumCalculationMode public protocol AWSDefaultClientConfiguration { /// The AWS credential identity resolver to be used for AWS credentials. @@ -46,6 +47,24 @@ public protocol AWSDefaultClientConfiguration { /// If set, this value gets used when resolving max attempts value from the standard progression of potential sources. If no value could be resolved, the SDK uses max attempts value of 3 by default. var maxAttempts: Int? { get set } + /// The AWS request checksum calculation mode to use. + /// + /// If `.whenRequired`, the client calculates checksum for the request payload only if the operation requires it. + /// If `.whenSupported`, the client calculates checksum for the request payload if the operation supports it. + /// + /// Default mode is `.whenSupported`. + /// + /// If no algorithm was chosen and no checksum was provided, CRC32 checksum algorithm is used by default. + var requestChecksumCalculation: AWSChecksumCalculationMode { get set } + + /// The AWS response checksum calculation mode to use. + /// + /// If `.whenRequired`, the client validates checksum of the response only if the top-level input field for `requestValidationModeMember` is set to `.enabled` and SDK supports the checksum algorithm. + /// If `.whenSupported`, the client validates checksum of the response if the operation supports it and SDK supports at least one of the checksum algorithms returend by service. + /// + /// Default mode is `.whenSupported`. + var responseChecksumValidation: AWSChecksumCalculationMode { get set } + /// Specifies whether the endpoint configured via environment variables or shared config file should be used by the service client. /// /// If `false`, the endpoint for the service client is resolved in the following order: diff --git a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/Middlewares/FlexibleChecksumsRequestMiddleware.swift b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/Middlewares/FlexibleChecksumsRequestMiddleware.swift index fce7a9b79f8..96dba77cbc0 100644 --- a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/Middlewares/FlexibleChecksumsRequestMiddleware.swift +++ b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/Middlewares/FlexibleChecksumsRequestMiddleware.swift @@ -8,7 +8,9 @@ import enum SmithyChecksumsAPI.ChecksumAlgorithm import enum SmithyChecksums.ChecksumMismatchException import enum Smithy.ClientError +import struct Smithy.URIQueryItem import class Smithy.Context +import struct Foundation.Data import AwsCommonRuntimeKit import AWSSDKChecksums import ClientRuntime @@ -18,76 +20,136 @@ public struct FlexibleChecksumsRequestMiddleware { @@ -15,29 +17,37 @@ public struct FlexibleChecksumsResponseMiddleware Bool { + let regex = "-([1-9][0-9]{0,3}|10000)$" + let range = NSRange(input.startIndex..>(name: "BusinessMetrics") -/* List of readable "feature ID" to "metric value"; last updated on 08/19/2024 - [Feature ID] [Metric Value] [Flag Supported] - "RESOURCE_MODEL" : "A" : - "WAITER" : "B" : - "PAGINATOR" : "C" : - "RETRY_MODE_LEGACY" : "D" : Y - "RETRY_MODE_STANDARD" : "E" : Y - "RETRY_MODE_ADAPTIVE" : "F" : Y - "S3_TRANSFER" : "G" : - "S3_CRYPTO_V1N" : "H" : - "S3_CRYPTO_V2" : "I" : - "S3_EXPRESS_BUCKET" : "J" : - "S3_ACCESS_GRANTS" : "K" : - "GZIP_REQUEST_COMPRESSION" : "L" : - "PROTOCOL_RPC_V2_CBOR" : "M" : Y - "ENDPOINT_OVERRIDE" : "N" : Y - "ACCOUNT_ID_ENDPOINT" : "O" : - "ACCOUNT_ID_MODE_PREFERRED" : "P" : - "ACCOUNT_ID_MODE_DISABLED" : "Q" : - "ACCOUNT_ID_MODE_REQUIRED" : "R" : - "SIGV4A_SIGNING" : "S" : Y - "RESOLVED_ACCOUNT_ID" : "T" : - */ private func setFlagsIntoContext( config: UserAgentValuesFromConfig, context: Context, headers: Headers ) { - // Handle D, E, F switch config.awsRetryMode { case .legacy: context.businessMetrics = ["RETRY_MODE_LEGACY": "D"] @@ -98,14 +74,36 @@ private func setFlagsIntoContext( case .adaptive: context.businessMetrics = ["RETRY_MODE_ADAPTIVE": "F"] } - // Handle N if let endpoint = config.endpoint, !endpoint.isEmpty { context.businessMetrics = ["ENDPOINT_OVERRIDE": "N"] } - // Handle S if context.selectedAuthScheme?.schemeID == "aws.auth#sigv4a" { context.businessMetrics = ["SIGV4A_SIGNING": "S"] } + switch context.checksum { + case .crc32: + context.businessMetrics = ["FLEXIBLE_CHECKSUMS_REQ_CRC32": "U"] + case .crc32c: + context.businessMetrics = ["FLEXIBLE_CHECKSUMS_REQ_CRC32C": "V"] + case .crc64nvme: + context.businessMetrics = ["FLEXIBLE_CHECKSUMS_REQ_CRC64": "W"] + case .sha1: + context.businessMetrics = ["FLEXIBLE_CHECKSUMS_REQ_SHA1": "X"] + case .sha256: + context.businessMetrics = ["FLEXIBLE_CHECKSUMS_REQ_SHA256": "Y"] + default: + break + } + if config.requestChecksumCalculation == .whenSupported { + context.businessMetrics = ["FLEXIBLE_CHECKSUMS_REQ_WHEN_SUPPORTED": "Z"] + } else { + context.businessMetrics = ["FLEXIBLE_CHECKSUMS_REQ_WHEN_REQUIRED": "a"] + } + if config.responseChecksumValidation == .whenSupported { + context.businessMetrics = ["FLEXIBLE_CHECKSUMS_RES_WHEN_SUPPORTED": "b"] + } else { + context.businessMetrics = ["FLEXIBLE_CHECKSUMS_RES_WHEN_REQUIRED": "c"] + } // Handle M if headers.value(for: "smithy-protocol") == "rpc-v2-cbor" { context.businessMetrics = ["PROTOCOL_RPC_V2_CBOR": "M"] diff --git a/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/Middlewares/FlexibleChecksumsMiddlewareTests.swift b/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/Middlewares/FlexibleChecksumsMiddlewareTests.swift index 41ec66327f7..bc845646131 100644 --- a/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/Middlewares/FlexibleChecksumsMiddlewareTests.swift +++ b/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/Middlewares/FlexibleChecksumsMiddlewareTests.swift @@ -33,6 +33,8 @@ class FlexibleChecksumsMiddlewareTests: XCTestCase { .withPath(value: "/") .withOperation(value: "Test Operation") .withLogger(value: testLogger) + .withRequestChecksumCalculation(value: .whenSupported) + .withResponseChecksumValidation(value: .whenSupported) .build() var metricsAttributes = Attributes() metricsAttributes.set(key: OrchestratorMetricsAttributesKeys.service, value: "Service") @@ -47,77 +49,124 @@ class FlexibleChecksumsMiddlewareTests: XCTestCase { func testNormalPayloadSha256() async throws { let checksumAlgorithm = "sha256" - let testData = ByteStream.data(Data("Hello, world!".utf8)) + let testData = ByteStream.data(Data("Hello world".utf8)) setNormalPayload(payload: testData) - addFlexibleChecksumsRequestMiddleware(checksumAlgorithm: checksumAlgorithm) - addFlexibleChecksumsResponseMiddleware(validationMode: true) + addFlexibleChecksumsRequestMiddleware(true, checksumAlgorithm) + addFlexibleChecksumsResponseMiddleware("ENABLED") try await AssertHeaderIsPresentAndValidationOccurs( expectedHeader: "x-amz-checksum-sha256", responseBody: testData, - expectedChecksum: "MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=" + expectedChecksum: "ZOyIygCyaOW6GjVnihtTFtIS9PNmskdyMlNKiuyjfzw=" ) } func testNormalPayloadSha1() async throws { let checksumAlgorithm = "sha1" - let testData = ByteStream.data(Data("Hello, world!".utf8)) + let testData = ByteStream.data(Data("Hello world".utf8)) setNormalPayload(payload: testData) - addFlexibleChecksumsRequestMiddleware(checksumAlgorithm: checksumAlgorithm) - addFlexibleChecksumsResponseMiddleware(validationMode: true) + addFlexibleChecksumsRequestMiddleware(true, checksumAlgorithm) + addFlexibleChecksumsResponseMiddleware("ENABLED") try await AssertHeaderIsPresentAndValidationOccurs( expectedHeader: "x-amz-checksum-sha1", responseBody: testData, - expectedChecksum: "lDpwLQbzRZmu4fjajvn3KWAx1pk=" + expectedChecksum: "e1AsOh9IyGCa4hLN+2Od7jlnP14=" ) } func testNormalPayloadCRC32() async throws { let checksumAlgorithm = "crc32" - let testData = ByteStream.data(Data("Hello, world!".utf8)) + let testData = ByteStream.data(Data("Hello world".utf8)) setNormalPayload(payload: testData) - addFlexibleChecksumsRequestMiddleware(checksumAlgorithm: checksumAlgorithm) - addFlexibleChecksumsResponseMiddleware(validationMode: true) + addFlexibleChecksumsRequestMiddleware(true, checksumAlgorithm) + addFlexibleChecksumsResponseMiddleware("ENABLED") try await AssertHeaderIsPresentAndValidationOccurs( expectedHeader: "x-amz-checksum-crc32", responseBody: testData, - expectedChecksum: "6+bG5g==" + expectedChecksum: "i9aeUg==" ) } func testNormalPayloadCRC32C() async throws { let checksumAlgorithm = "crc32c" - let testData = ByteStream.data(Data("Hello, world!".utf8)) + let testData = ByteStream.data(Data("Hello world".utf8)) setNormalPayload(payload: testData) - addFlexibleChecksumsRequestMiddleware(checksumAlgorithm: checksumAlgorithm) - addFlexibleChecksumsResponseMiddleware(validationMode: true) + addFlexibleChecksumsRequestMiddleware(true, checksumAlgorithm) + addFlexibleChecksumsResponseMiddleware("ENABLED") try await AssertHeaderIsPresentAndValidationOccurs( expectedHeader: "x-amz-checksum-crc32c", responseBody: testData, - expectedChecksum: "yKEG5Q==" + expectedChecksum: "crUfeA==" ) } - func testNilChecksumAlgorithm() async throws { - let testData = ByteStream.data(Data("Hello, world!".utf8)) + func testNormalPayloadCRC64NVME() async throws { + let checksumAlgorithm = "crc64nvme" + let testData = ByteStream.data(Data("Hello world".utf8)) setNormalPayload(payload: testData) - addFlexibleChecksumsRequestMiddleware(checksumAlgorithm: nil) - addFlexibleChecksumsResponseMiddleware(validationMode: false) + addFlexibleChecksumsRequestMiddleware(true, checksumAlgorithm) + addFlexibleChecksumsResponseMiddleware("ENABLED") try await AssertHeaderIsPresentAndValidationOccurs( + expectedHeader: "x-amz-checksum-crc64nvme", + responseBody: testData, + expectedChecksum: "OOJZ0D8xKts=" + ) + } + + func testUseDefaultChecksumAlgorithm1() async throws { + let testData = ByteStream.data(Data("Hello world".utf8)) + setNormalPayload(payload: testData) + builder.attributes.responseChecksumValidation = .whenRequired + addFlexibleChecksumsRequestMiddleware(false, nil) + addFlexibleChecksumsResponseMiddleware("unset") + try await AssertHeaderIsPresentAndValidationOccurs( + expectedHeader: "x-amz-checksum-crc32", checkLogs: [ - "No checksum provided! Skipping flexible checksums workflow...", + "No algorithm chosen by user. Defaulting to CRC32 checksum algorithm.", "Checksum validation should not be performed! Skipping workflow..." ] ) } - private func addFlexibleChecksumsRequestMiddleware(checksumAlgorithm: String?) { - builder.interceptors.add(FlexibleChecksumsRequestMiddleware(checksumAlgorithm: checksumAlgorithm)) + func testUseDefaultChecksumAlgorithm2() async throws { + let testData = ByteStream.data(Data("Hello world".utf8)) + setNormalPayload(payload: testData) + builder.attributes.requestChecksumCalculation = .whenRequired + builder.attributes.responseChecksumValidation = .whenRequired + addFlexibleChecksumsRequestMiddleware(true, nil) + addFlexibleChecksumsResponseMiddleware("ENABLED") + try await AssertHeaderIsPresentAndValidationOccurs( + expectedHeader: "x-amz-checksum-crc32", + responseBody: testData, + expectedChecksum: "i9aeUg==", + checkLogs: [ + "No algorithm chosen by user. Defaulting to CRC32 checksum algorithm." + ] + ) + } + + func testNoRequestChecksumCalculation() async throws { + let testData = ByteStream.data(Data("Hello, world!".utf8)) + setNormalPayload(payload: testData) + builder.attributes.requestChecksumCalculation = .whenRequired + addFlexibleChecksumsRequestMiddleware(false, nil) + addFlexibleChecksumsResponseMiddleware("unset") + try await AssertHeaderIsPresentAndValidationOccurs( + checkLogs: [ + "Checksum not required for the operation.", + "Client config `requestChecksumCalculation` set to `.whenRequired`", + "No checksum algorithm chosen by the user. Skipping checksum calculation..." + ] + ) } - private func addFlexibleChecksumsResponseMiddleware(validationMode: Bool, priorityList: [String] = []) { + private func addFlexibleChecksumsRequestMiddleware(_ requestChecksumRequired: Bool, _ checksumAlgorithm: String?) { + builder.interceptors.add(FlexibleChecksumsRequestMiddleware(requestChecksumRequired: requestChecksumRequired, checksumAlgorithm: checksumAlgorithm, checksumAlgoHeaderName: "x-amz-checksum-algorithm")) + } + + private func addFlexibleChecksumsResponseMiddleware(_ validationMode: String, _ algosSupportedByOperation: [String] = []) { builder.interceptors.add(FlexibleChecksumsResponseMiddleware( validationMode: validationMode, - priorityList: priorityList + algosSupportedByOperation: algosSupportedByOperation )) } @@ -153,8 +202,8 @@ class FlexibleChecksumsMiddlewareTests: XCTestCase { setStreamingPayload(payload: testData, checksum: checksumAlgorithm) - addFlexibleChecksumsRequestMiddleware(checksumAlgorithm: checksumAlgorithm) - addFlexibleChecksumsResponseMiddleware(validationMode: true) + addFlexibleChecksumsRequestMiddleware(true, checksumAlgorithm) + addFlexibleChecksumsResponseMiddleware("ENABLED") try await AssertionsWhenStreaming( expectedHeader: "x-amz-checksum-crc32", responseBody: testData, @@ -169,29 +218,29 @@ class FlexibleChecksumsMiddlewareTests: XCTestCase { func testAlgorithmSelectionCase1() async throws -> () { let colA = ["crc64", "crc32", "crc32c"] let colB = ["crc32c", "crc32"] - let validationMode = false + let validationMode = "unset" var colBHeaders = Headers() colB.forEach { checksum in - colBHeaders.add(name: "x-amz-checksum-\(checksum)", value: "AAAAA") + colBHeaders.add(name: "x-amz-checksum-\(checksum)", value: "yKEG5Q==") } - addFlexibleChecksumsResponseMiddleware(validationMode: validationMode, priorityList: colA) + addFlexibleChecksumsResponseMiddleware(validationMode, colA) try await AssertValidationAsExpected( responseHeaders: colBHeaders, - validationMode: validationMode, - expectedValidationHeader: nil + validationMode: true, // true since responseChecksumValidation is set to .whenSupported + expectedValidationHeader: "x-amz-checksum-crc32c" ) } func testAlgorithmSelectionCase2() async throws -> () { let colA = ["sha256", "crc32"] - let validationMode = true + let validationMode = "ENABLED" var colBHeaders = Headers() // Add only sha256 header in response colBHeaders.add(name: "x-amz-checksum-sha256", value: "MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=") - addFlexibleChecksumsResponseMiddleware(validationMode: validationMode, priorityList: colA) + addFlexibleChecksumsResponseMiddleware(validationMode, colA) try await AssertValidationAsExpected( responseHeaders: colBHeaders, - validationMode: validationMode, + validationMode: true, expectedValidationHeader: "x-amz-checksum-sha256" ) } @@ -199,43 +248,61 @@ class FlexibleChecksumsMiddlewareTests: XCTestCase { func testAlgorithmSelectionCase3() async throws -> () { let colA = ["sha256", "crc32"] let colB = ["crc32", "sha256"] - let validationMode = true + let validationMode = "ENABLED" var colBHeaders = Headers() // Add both sha256 header and crc32 in response // Add expected crc32 checksum as value since it should take priority colB.forEach { checksum in colBHeaders.add(name: "x-amz-checksum-\(checksum)", value: "6+bG5g==") } - addFlexibleChecksumsResponseMiddleware(validationMode: validationMode, priorityList: colA) + addFlexibleChecksumsResponseMiddleware(validationMode, colA) try await AssertValidationAsExpected( responseHeaders: colBHeaders, - validationMode: validationMode, + validationMode: true, expectedValidationHeader: "x-amz-checksum-crc32" ) } func testAlgorithmSelectionCase4() async throws -> () { let colA = ["crc32", "crc32c"] - let validationMode = false + let validationMode = "unset" var colBHeaders = Headers() - // crc64 is not modeled in the service so no validation should be perforemd, but we shouldnt error + // crc64 is supported for this request (not included in colA array), + // so no validation should be performed, but we shouldnt error colBHeaders.add(name: "x-amz-checksum-crc64", value: "6+bG5g==") - addFlexibleChecksumsResponseMiddleware(validationMode: validationMode, priorityList: colA) + addFlexibleChecksumsResponseMiddleware(validationMode, colA) try await AssertValidationAsExpected( responseHeaders: colBHeaders, - validationMode: validationMode, + validationMode: false, expectedValidationHeader: nil ) } - func getChecksumMismatchException() async throws -> () { - let validationMode = true + func testNoResponseValidationOccurs() async throws -> () { + let colA = ["crc32", "crc32c"] + let validationMode = "unset" + var colBHeaders = Headers() + colBHeaders.add(name: "x-amz-checksum-crc32", value: "6+bG5g==") + // Change responseChecksumValidation config to .whenRequired + builder.attributes.responseChecksumValidation = .whenSupported + addFlexibleChecksumsResponseMiddleware(validationMode, colA) + // Since responseChecksumValidation config is set to .whenRequired and validationMode is not "ENABLED", + // no response checksum validation should occur. + try await AssertValidationAsExpected( + responseHeaders: colBHeaders, + validationMode: true, + expectedValidationHeader: "x-amz-checksum-crc32" + ) + } + + func testChecksumMismatchException() async throws -> () { + let validationMode = "ENABLED" var testHeaders = Headers() testHeaders.add(name: "x-amz-checksum-crc32", value: "AAAA==") - addFlexibleChecksumsResponseMiddleware(validationMode: validationMode) + addFlexibleChecksumsResponseMiddleware(validationMode) try await AssertValidationAsExpected( responseHeaders: testHeaders, - validationMode: validationMode, + validationMode: true, expectedValidationHeader: "x-amz-checksum-crc32", expectedChecksumMismatch: true ) @@ -355,7 +422,7 @@ class FlexibleChecksumsMiddlewareTests: XCTestCase { let expectedChecksumSHA256 = "ZOyIygCyaOW6GjVnihtTFtIS9PNmskdyMlNKiuyjfzw=" let signingConfig = SigningConfig(algorithm: .signingV4, signatureType: .requestHeaders, service: "S3", region: "us-west-2", signedBodyValue: .unsignedPayload) setPutPayload(payload: testData, checksumSHA256: expectedChecksumSHA256, signingConfig: signingConfig) - addFlexibleChecksumsRequestMiddleware(checksumAlgorithm: checksumAlgorithm) + addFlexibleChecksumsRequestMiddleware(true, checksumAlgorithm) try await assertPutRequestHeaders(expectedChecksumSHA256: expectedChecksumSHA256) } diff --git a/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/UserAgent/BusinessMetricsTests.swift b/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/UserAgent/BusinessMetricsTests.swift index 7728e718ccd..7d98483f746 100644 --- a/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/UserAgent/BusinessMetricsTests.swift +++ b/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/UserAgent/BusinessMetricsTests.swift @@ -31,7 +31,13 @@ class BusinessMetricsTests: XCTestCase { let userAgent = AWSUserAgentMetadata.fromConfigAndContext( serviceID: "test", version: "1.0", - config: UserAgentValuesFromConfig(appID: nil, endpoint: nil, awsRetryMode: .standard), + config: UserAgentValuesFromConfig( + appID: nil, + endpoint: nil, + awsRetryMode: .standard, + requestChecksumCalculation: .whenRequired, + responseChecksumValidation: .whenRequired + ), context: context, headers: headers ) @@ -54,12 +60,18 @@ class BusinessMetricsTests: XCTestCase { let userAgent = AWSUserAgentMetadata.fromConfigAndContext( serviceID: "test", version: "1.0", - config: UserAgentValuesFromConfig(appID: nil, endpoint: "test-endpoint", awsRetryMode: .adaptive), + config: UserAgentValuesFromConfig( + appID: nil, + endpoint: "test-endpoint", + awsRetryMode: .adaptive, + requestChecksumCalculation: .whenSupported, + responseChecksumValidation: .whenSupported + ), context: context, headers: headers ) // F comes from retry mode being adaptive & N comes from endpoint override - let expectedString = "m/A,B,F,N,S" + let expectedString = "m/A,B,F,N,S,Z,b" XCTAssertEqual(userAgent.businessMetrics?.description, expectedString) } } diff --git a/Sources/Core/AWSSDKChecksums/Sources/AWSSDKChecksums/AWSChecksumCalculationMode.swift b/Sources/Core/AWSSDKChecksums/Sources/AWSSDKChecksums/AWSChecksumCalculationMode.swift new file mode 100644 index 00000000000..70c2d490cf9 --- /dev/null +++ b/Sources/Core/AWSSDKChecksums/Sources/AWSSDKChecksums/AWSChecksumCalculationMode.swift @@ -0,0 +1,24 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +public enum AWSChecksumCalculationMode: String { + case whenRequired = "when_required" + case whenSupported = "when_supported" + + public init?(caseInsensitiveRawValue: String) { + let lowercasedValue = caseInsensitiveRawValue.lowercased() + + switch lowercasedValue { + case AWSChecksumCalculationMode.whenRequired.rawValue: + self = .whenRequired + case AWSChecksumCalculationMode.whenSupported.rawValue: + self = .whenSupported + default: + return nil + } + } +} diff --git a/Sources/Core/AWSSDKChecksums/Sources/AWSSDKChecksums/Context+Checksum.swift b/Sources/Core/AWSSDKChecksums/Sources/AWSSDKChecksums/Context+Checksum.swift new file mode 100644 index 00000000000..7bf5e059578 --- /dev/null +++ b/Sources/Core/AWSSDKChecksums/Sources/AWSSDKChecksums/Context+Checksum.swift @@ -0,0 +1,39 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import class Smithy.Context +import class Smithy.ContextBuilder +import struct Smithy.AttributeKey + +public extension Context { + var requestChecksumCalculation: AWSChecksumCalculationMode { + get { get(key: requestChecksumCalculationKey) ?? .whenSupported } + set { set(key: requestChecksumCalculationKey, value: newValue) } + } + + var responseChecksumValidation: AWSChecksumCalculationMode { + get { get(key: responseChecksumValidationKey) ?? .whenSupported } + set { set(key: responseChecksumValidationKey, value: newValue) } + } +} + +public extension ContextBuilder { + @discardableResult + func withRequestChecksumCalculation(value: AWSChecksumCalculationMode) -> Self { + self.attributes.set(key: requestChecksumCalculationKey, value: value) + return self + } + + @discardableResult + func withResponseChecksumValidation(value: AWSChecksumCalculationMode) -> Self { + self.attributes.set(key: responseChecksumValidationKey, value: value) + return self + } +} + +private let requestChecksumCalculationKey = AttributeKey(name: "requestChecksumCalculation") +private let responseChecksumValidationKey = AttributeKey(name: "responseChecksumValidation") diff --git a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/AWSHTTPProtocolCustomizations.kt b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/AWSHTTPProtocolCustomizations.kt index 5e03894b2b9..e3c37fef9ba 100644 --- a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/AWSHTTPProtocolCustomizations.kt +++ b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/AWSHTTPProtocolCustomizations.kt @@ -30,6 +30,8 @@ abstract class AWSHTTPProtocolCustomizations : DefaultHTTPProtocolCustomizations writer.write(" .withIdentityResolver(value: config.awsCredentialIdentityResolver, schemeID: \$S)", "aws.auth#sigv4") writer.write(" .withIdentityResolver(value: config.awsCredentialIdentityResolver, schemeID: \$S)", "aws.auth#sigv4a") writer.write(" .withRegion(value: config.region)") + writer.write(" .withRequestChecksumCalculation(value: config.requestChecksumCalculation)") + writer.write(" .withResponseChecksumValidation(value: config.responseChecksumValidation)") if (AWSAuthUtils.hasSigV4AuthScheme(ctx.model, ctx.service, op)) { val signingName = AWSAuthUtils.signingServiceName(serviceShape) writer.write(" .withSigningName(value: \$S)", signingName) diff --git a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/AWSHttpProtocolServiceClient.kt b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/AWSHttpProtocolServiceClient.kt index b21859ac6d2..ebdddcfc72b 100644 --- a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/AWSHttpProtocolServiceClient.kt +++ b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/AWSHttpProtocolServiceClient.kt @@ -130,6 +130,12 @@ class AWSHttpProtocolServiceClient( "retryStrategyOptions" -> { "try AWSClientConfigDefaultsProvider.retryStrategyOptions()" } + "requestChecksumCalculation" -> { + "try AWSClientConfigDefaultsProvider.requestChecksumCalculation()" + } + "responseChecksumValidation" -> { + "try AWSClientConfigDefaultsProvider.responseChecksumValidation()" + } else -> { it.default?.render(writer) ?: "nil" } diff --git a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/AWSSwiftDependency.kt b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/AWSSwiftDependency.kt index 0d1dcdcf80a..cbc42b08bc3 100644 --- a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/AWSSwiftDependency.kt +++ b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/AWSSwiftDependency.kt @@ -8,6 +8,15 @@ import software.amazon.smithy.swift.codegen.SwiftDependency class AWSSwiftDependency { companion object { + val AWS_SDK_CHECKSUMS = SwiftDependency( + "AWSSDKChecksums", + "main", + "0.0.1", + "aws-sdk-swift", + "../../../aws-sdk-swift", + "AWSSDKChecksums", + SwiftDependency.DistributionMethod.SPR, + ) val AWS_SDK_IDENTITY = SwiftDependency( "AWSSDKIdentity", "main", diff --git a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/config/AWSDefaultClientConfiguration.kt b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/config/AWSDefaultClientConfiguration.kt index da9e67e0d28..852e2f95855 100644 --- a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/config/AWSDefaultClientConfiguration.kt +++ b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/config/AWSDefaultClientConfiguration.kt @@ -6,6 +6,7 @@ package software.amazon.smithy.aws.swift.codegen.config import software.amazon.smithy.aws.swift.codegen.swiftmodules.AWSClientRuntimeTypes +import software.amazon.smithy.aws.swift.codegen.swiftmodules.AWSSDKChecksumsTypes import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.swift.codegen.config.ClientConfiguration import software.amazon.smithy.swift.codegen.config.ConfigProperty @@ -40,6 +41,18 @@ class AWSDefaultClientConfiguration : ClientConfiguration { true ), ConfigProperty("maxAttempts", SwiftTypes.Int.toOptional()), + ConfigProperty( + "requestChecksumCalculation", + AWSSDKChecksumsTypes.AWSChecksumCalculationMode, + { it.format("\$N.requestChecksumCalculation(requestChecksumCalculation)", AWSClientRuntimeTypes.Core.AWSClientConfigDefaultsProvider) }, + true + ), + ConfigProperty( + "responseChecksumValidation", + AWSSDKChecksumsTypes.AWSChecksumCalculationMode, + { it.format("\$N.responseChecksumValidation(responseChecksumValidation)", AWSClientRuntimeTypes.Core.AWSClientConfigDefaultsProvider) }, + true + ), ConfigProperty("ignoreConfiguredEndpointURLs", SwiftTypes.Bool.toOptional()) ) } diff --git a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/customization/flexiblechecksums/FlexibleChecksumsRequestIntegration.kt b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/customization/flexiblechecksums/FlexibleChecksumsRequestIntegration.kt index 51fe7441fe9..3f010619ebb 100644 --- a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/customization/flexiblechecksums/FlexibleChecksumsRequestIntegration.kt +++ b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/customization/flexiblechecksums/FlexibleChecksumsRequestIntegration.kt @@ -5,6 +5,7 @@ import software.amazon.smithy.aws.traits.HttpChecksumTrait import software.amazon.smithy.model.Model import software.amazon.smithy.model.shapes.OperationShape import software.amazon.smithy.model.shapes.StructureShape +import software.amazon.smithy.model.traits.HttpHeaderTrait import software.amazon.smithy.swift.codegen.SwiftSettings import software.amazon.smithy.swift.codegen.SwiftWriter import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator @@ -13,7 +14,10 @@ import software.amazon.smithy.swift.codegen.integration.middlewares.handlers.Mid import software.amazon.smithy.swift.codegen.middleware.MiddlewareRenderable import software.amazon.smithy.swift.codegen.middleware.OperationMiddleware import software.amazon.smithy.swift.codegen.model.expectShape +import software.amazon.smithy.swift.codegen.model.getTrait +import software.amazon.smithy.swift.codegen.model.hasTrait import software.amazon.smithy.swift.codegen.model.shapes +import kotlin.jvm.optionals.getOrNull class FlexibleChecksumsRequestIntegration : SwiftIntegration { override fun enabledForService(model: Model, settings: SwiftSettings): Boolean = model @@ -28,9 +32,21 @@ class FlexibleChecksumsRequestIntegration : SwiftIntegration { val httpChecksumTrait = operationShape.getTrait(HttpChecksumTrait::class.java).orElse(null) val input = operationShape.input.orElse(null)?.let { ctx.model.expectShape(it) } - val useFlexibleChecksum = (httpChecksumTrait != null) && - (httpChecksumTrait.requestAlgorithmMember?.orElse(null) != null) && - (input?.memberNames?.any { it == httpChecksumTrait.requestAlgorithmMember.get() } == true) + /* + Flexible checksum request middleware must be added only if: + - The operation has @httpChecksum trait + - (The `requestChecksumRequired` of @httpChecksum trait is set to `true`) || + (the `requestAlgorithmMember` is modeled) + - Of course, add the middleware if both are true as well. + In the case that `requestAlgorithmMember` is not modeled but `requestChecksumRequired` is `true`, + SDK clients use default checksum algorithm: CRC32. + */ + val useFlexibleChecksum = httpChecksumTrait != null && ( + ( + (httpChecksumTrait.requestAlgorithmMember?.isPresent ?: false) && + (input?.memberNames?.any { it == httpChecksumTrait.requestAlgorithmMember.get() } == true) + ) || (httpChecksumTrait.isRequestChecksumRequired) + ) if (useFlexibleChecksum) { operationMiddleware.appendMiddleware(operationShape, FlexibleChecksumRequestMiddleware) @@ -38,7 +54,7 @@ class FlexibleChecksumsRequestIntegration : SwiftIntegration { } } -private fun String.lowercaseFirstLetter(): String = +fun String.lowercaseFirstLetter(): String = takeIf { it.isNotEmpty() }?.let { it.first().lowercase() + it.substring(1) } ?: this private object FlexibleChecksumRequestMiddleware : MiddlewareRenderable { @@ -52,14 +68,24 @@ private object FlexibleChecksumRequestMiddleware : MiddlewareRenderable { val inputShapeName = MiddlewareShapeUtils.inputSymbol(ctx.symbolProvider, ctx.model, op).name val outputShapeName = MiddlewareShapeUtils.outputSymbol(ctx.symbolProvider, ctx.model, op).name val httpChecksumTrait = op.getTrait(HttpChecksumTrait::class.java).orElse(null) - val inputMemberName = httpChecksumTrait?.requestAlgorithmMember?.get()?.lowercaseFirstLetter() + val algorithmMemberName = httpChecksumTrait?.requestAlgorithmMember?.getOrNull()?.lowercaseFirstLetter()?.let { + "input.$it?.rawValue" + } ?: "nil" + val requestChecksumIsRequired = httpChecksumTrait?.isRequestChecksumRequired + val algoHeaderName = if (httpChecksumTrait?.requestAlgorithmMember?.getOrNull() != null) { + ctx.model.expectShape(op.inputShape).getMember(httpChecksumTrait.requestAlgorithmMember.get())?.getOrNull()?.getTrait()?.value?.let { + "\"$it\"" + } ?: "nil" + } else { "nil" } writer.write( - "\$N<\$L, \$L>(checksumAlgorithm: input.\$L?.rawValue)", + "\$N<\$L, \$L>(requestChecksumRequired: \$L, checksumAlgorithm: \$L, checksumAlgoHeaderName: \$L)", AWSClientRuntimeTypes.Core.FlexibleChecksumsRequestMiddleware, inputShapeName, outputShapeName, - inputMemberName, + requestChecksumIsRequired, + algorithmMemberName, + algoHeaderName ) } } diff --git a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/customization/flexiblechecksums/FlexibleChecksumsResponseIntegration.kt b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/customization/flexiblechecksums/FlexibleChecksumsResponseIntegration.kt index b26d9cd2b11..14ac5234546 100644 --- a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/customization/flexiblechecksums/FlexibleChecksumsResponseIntegration.kt +++ b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/customization/flexiblechecksums/FlexibleChecksumsResponseIntegration.kt @@ -50,18 +50,24 @@ private object FlexibleChecksumResponseMiddleware : MiddlewareRenderable { val inputShapeName = MiddlewareShapeUtils.inputSymbol(ctx.symbolProvider, ctx.model, op).name val outputShapeName = MiddlewareShapeUtils.outputSymbol(ctx.symbolProvider, ctx.model, op).name val httpChecksumTrait = op.getTrait(HttpChecksumTrait::class.java).orElse(null) - val inputMemberName = httpChecksumTrait?.requestValidationModeMember?.get() - val validationModeMember = ctx.model.expectShape(op.inputShape).getMember(inputMemberName) - val requestValidationModeEnumShape = ctx.model.expectShape(validationModeMember.orElse(null)?.target) + val validationModeMemberName = httpChecksumTrait?.requestValidationModeMember?.get()?.lowercaseFirstLetter() + val checksumAlgosSupportedByResponse = httpChecksumTrait?.responseAlgorithms?.let { + if (it.isEmpty()) { "" } else { + ", algosSupportedByOperation: " + it.joinToString( + prefix = "[\"", + postfix = "\"]", + separator = "\", \"" + ) + } + } ?: "" - // Will pass the validation mode to validation middleware - val validationMode: Boolean = requestValidationModeEnumShape.members().map { it.memberName }.first().equals("ENABLED") writer.write( - "\$N<\$L, \$L>(validationMode: \$L)", + "\$N<\$L, \$L>(validationMode: input.\$L?.rawValue ?? \"unset\"\$L)", AWSClientRuntimeTypes.Core.FlexibleChecksumsResponseMiddleware, inputShapeName, outputShapeName, - validationMode, + validationModeMemberName, + checksumAlgosSupportedByResponse ) } } diff --git a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/swiftmodules/AWSSDKChecksumsTypes.kt b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/swiftmodules/AWSSDKChecksumsTypes.kt new file mode 100644 index 00000000000..e4b50b3fdab --- /dev/null +++ b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/swiftmodules/AWSSDKChecksumsTypes.kt @@ -0,0 +1,18 @@ +package software.amazon.smithy.aws.swift.codegen.swiftmodules + +import software.amazon.smithy.aws.swift.codegen.AWSSwiftDependency +import software.amazon.smithy.codegen.core.Symbol +import software.amazon.smithy.swift.codegen.SwiftDeclaration +import software.amazon.smithy.swift.codegen.swiftmodules.SwiftSymbol + +object AWSSDKChecksumsTypes { + val AWSChecksumCalculationMode = runtimeSymbol("AWSChecksumCalculationMode", SwiftDeclaration.ENUM) +} + +private fun runtimeSymbol(name: String, declaration: SwiftDeclaration? = null): Symbol = SwiftSymbol.make( + name, + declaration, + AWSSwiftDependency.AWS_SDK_CHECKSUMS, + emptyList(), + emptyList(), +) diff --git a/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/PresignerGeneratorTests.kt b/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/PresignerGeneratorTests.kt index cf61617853c..9ab55fbb132 100644 --- a/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/PresignerGeneratorTests.kt +++ b/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/PresignerGeneratorTests.kt @@ -36,6 +36,8 @@ extension GetFooInput { .withIdentityResolver(value: config.awsCredentialIdentityResolver, schemeID: "aws.auth#sigv4") .withIdentityResolver(value: config.awsCredentialIdentityResolver, schemeID: "aws.auth#sigv4a") .withRegion(value: config.region) + .withRequestChecksumCalculation(value: config.requestChecksumCalculation) + .withResponseChecksumValidation(value: config.responseChecksumValidation) .withSigningName(value: "example-signing-name") .withSigningRegion(value: config.signingRegion) .build() @@ -105,6 +107,8 @@ extension PostFooInput { .withIdentityResolver(value: config.awsCredentialIdentityResolver, schemeID: "aws.auth#sigv4") .withIdentityResolver(value: config.awsCredentialIdentityResolver, schemeID: "aws.auth#sigv4a") .withRegion(value: config.region) + .withRequestChecksumCalculation(value: config.requestChecksumCalculation) + .withResponseChecksumValidation(value: config.responseChecksumValidation) .withSigningName(value: "example-signing-name") .withSigningRegion(value: config.signingRegion) .build() @@ -177,6 +181,8 @@ extension PutFooInput { .withIdentityResolver(value: config.awsCredentialIdentityResolver, schemeID: "aws.auth#sigv4") .withIdentityResolver(value: config.awsCredentialIdentityResolver, schemeID: "aws.auth#sigv4a") .withRegion(value: config.region) + .withRequestChecksumCalculation(value: config.requestChecksumCalculation) + .withResponseChecksumValidation(value: config.responseChecksumValidation) .withSigningName(value: "example-signing-name") .withSigningRegion(value: config.signingRegion) .build() @@ -249,6 +255,8 @@ extension PutObjectInput { .withIdentityResolver(value: config.awsCredentialIdentityResolver, schemeID: "aws.auth#sigv4") .withIdentityResolver(value: config.awsCredentialIdentityResolver, schemeID: "aws.auth#sigv4a") .withRegion(value: config.region) + .withRequestChecksumCalculation(value: config.requestChecksumCalculation) + .withResponseChecksumValidation(value: config.responseChecksumValidation) .withSigningName(value: "s3") .withSigningRegion(value: config.signingRegion) .build() diff --git a/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/awsrestjson/AWSRestJson1ProtocolGeneratorTests.kt b/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/awsrestjson/AWSRestJson1ProtocolGeneratorTests.kt index d6dd51556d2..4448c3c1056 100644 --- a/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/awsrestjson/AWSRestJson1ProtocolGeneratorTests.kt +++ b/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/awsrestjson/AWSRestJson1ProtocolGeneratorTests.kt @@ -91,6 +91,8 @@ extension ExampleClient { public var awsCredentialIdentityResolver: any SmithyIdentity.AWSCredentialIdentityResolver public var awsRetryMode: AWSClientRuntime.AWSRetryMode public var maxAttempts: Swift.Int? + public var requestChecksumCalculation: AWSSDKChecksums.AWSChecksumCalculationMode + public var responseChecksumValidation: AWSSDKChecksums.AWSChecksumCalculationMode public var ignoreConfiguredEndpointURLs: Swift.Bool? public var region: Swift.String? public var signingRegion: Swift.String? @@ -116,6 +118,8 @@ extension ExampleClient { _ awsCredentialIdentityResolver: any SmithyIdentity.AWSCredentialIdentityResolver, _ awsRetryMode: AWSClientRuntime.AWSRetryMode, _ maxAttempts: Swift.Int?, + _ requestChecksumCalculation: AWSSDKChecksums.AWSChecksumCalculationMode, + _ responseChecksumValidation: AWSSDKChecksums.AWSChecksumCalculationMode, _ ignoreConfiguredEndpointURLs: Swift.Bool?, _ region: Swift.String?, _ signingRegion: Swift.String?, @@ -139,6 +143,8 @@ extension ExampleClient { self.awsCredentialIdentityResolver = awsCredentialIdentityResolver self.awsRetryMode = awsRetryMode self.maxAttempts = maxAttempts + self.requestChecksumCalculation = requestChecksumCalculation + self.responseChecksumValidation = responseChecksumValidation self.ignoreConfiguredEndpointURLs = ignoreConfiguredEndpointURLs self.region = region self.signingRegion = signingRegion @@ -165,6 +171,8 @@ extension ExampleClient { awsCredentialIdentityResolver: (any SmithyIdentity.AWSCredentialIdentityResolver)? = nil, awsRetryMode: AWSClientRuntime.AWSRetryMode? = nil, maxAttempts: Swift.Int? = nil, + requestChecksumCalculation: AWSSDKChecksums.AWSChecksumCalculationMode? = nil, + responseChecksumValidation: AWSSDKChecksums.AWSChecksumCalculationMode? = nil, ignoreConfiguredEndpointURLs: Swift.Bool? = nil, region: Swift.String? = nil, signingRegion: Swift.String? = nil, @@ -189,6 +197,8 @@ extension ExampleClient { try awsCredentialIdentityResolver ?? AWSClientRuntime.AWSClientConfigDefaultsProvider.awsCredentialIdentityResolver(awsCredentialIdentityResolver), try awsRetryMode ?? AWSClientRuntime.AWSClientConfigDefaultsProvider.retryMode(), maxAttempts, + try requestChecksumCalculation ?? AWSClientRuntime.AWSClientConfigDefaultsProvider.requestChecksumCalculation(requestChecksumCalculation), + try responseChecksumValidation ?? AWSClientRuntime.AWSClientConfigDefaultsProvider.responseChecksumValidation(responseChecksumValidation), ignoreConfiguredEndpointURLs, region, signingRegion, @@ -215,6 +225,8 @@ extension ExampleClient { awsCredentialIdentityResolver: (any SmithyIdentity.AWSCredentialIdentityResolver)? = nil, awsRetryMode: AWSClientRuntime.AWSRetryMode? = nil, maxAttempts: Swift.Int? = nil, + requestChecksumCalculation: AWSSDKChecksums.AWSChecksumCalculationMode? = nil, + responseChecksumValidation: AWSSDKChecksums.AWSChecksumCalculationMode? = nil, ignoreConfiguredEndpointURLs: Swift.Bool? = nil, region: Swift.String? = nil, signingRegion: Swift.String? = nil, @@ -239,6 +251,8 @@ extension ExampleClient { try awsCredentialIdentityResolver ?? AWSClientRuntime.AWSClientConfigDefaultsProvider.awsCredentialIdentityResolver(awsCredentialIdentityResolver), try awsRetryMode ?? AWSClientRuntime.AWSClientConfigDefaultsProvider.retryMode(), maxAttempts, + try requestChecksumCalculation ?? AWSClientRuntime.AWSClientConfigDefaultsProvider.requestChecksumCalculation(requestChecksumCalculation), + try responseChecksumValidation ?? AWSClientRuntime.AWSClientConfigDefaultsProvider.responseChecksumValidation(responseChecksumValidation), ignoreConfiguredEndpointURLs, try await AWSClientRuntime.AWSClientConfigDefaultsProvider.region(region), try await AWSClientRuntime.AWSClientConfigDefaultsProvider.region(region), @@ -266,6 +280,8 @@ extension ExampleClient { awsCredentialIdentityResolver: nil, awsRetryMode: nil, maxAttempts: nil, + requestChecksumCalculation: nil, + responseChecksumValidation: nil, ignoreConfiguredEndpointURLs: nil, region: nil, signingRegion: nil, @@ -293,6 +309,8 @@ extension ExampleClient { try AWSClientConfigDefaultsProvider.awsCredentialIdentityResolver(), try AWSClientRuntime.AWSClientConfigDefaultsProvider.retryMode(), nil, + try AWSClientConfigDefaultsProvider.requestChecksumCalculation(), + try AWSClientConfigDefaultsProvider.responseChecksumValidation(), nil, region, region, diff --git a/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/customizations/FlexibleChecksumMiddlewareTests.kt b/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/customizations/FlexibleChecksumMiddlewareTests.kt index d29e5e3de3e..15b64423a4f 100644 --- a/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/customizations/FlexibleChecksumMiddlewareTests.kt +++ b/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/customizations/FlexibleChecksumMiddlewareTests.kt @@ -16,7 +16,7 @@ class FlexibleChecksumMiddlewareTests { val contents = TestUtils.getFileContents(context.manifest, "Sources/Example/ChecksumTestsClient.swift") contents.shouldSyntacticSanityCheck() val expectedContents = """ - builder.interceptors.add(AWSClientRuntime.FlexibleChecksumsRequestMiddleware(checksumAlgorithm: input.checksumAlgorithm?.rawValue)) + builder.interceptors.add(AWSClientRuntime.FlexibleChecksumsRequestMiddleware(requestChecksumRequired: true, checksumAlgorithm: input.checksumAlgorithm?.rawValue, checksumAlgoHeaderName: "x-amz-request-algorithm")) """ contents.shouldContainOnlyOnce(expectedContents) } @@ -27,7 +27,7 @@ class FlexibleChecksumMiddlewareTests { val contents = TestUtils.getFileContents(context.manifest, "Sources/Example/ChecksumTestsClient.swift") contents.shouldSyntacticSanityCheck() val expectedContents = """ - builder.interceptors.add(AWSClientRuntime.FlexibleChecksumsResponseMiddleware(validationMode: true)) + builder.interceptors.add(AWSClientRuntime.FlexibleChecksumsResponseMiddleware(validationMode: input.validationMode?.rawValue ?? "unset", algosSupportedByOperation: ["CRC32C", "CRC32", "SHA1", "SHA256"])) """ contents.shouldContainOnlyOnce(expectedContents) }