diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 39aab6ec..1f26edd0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,25 +6,27 @@ on: - 'main' env: - BUILDER_VERSION: v0.9.52 + BUILDER_VERSION: v0.9.73 BUILDER_SOURCE: releases BUILDER_HOST: https://d19elf31gohf1l.cloudfront.net PACKAGE_NAME: aws-crt-swift RUN: ${{ github.run_id }}-${{ github.run_number }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_REGION: us-east-1 + CRT_CI_ROLE: ${{ secrets.CRT_CI_ROLE_ARN }} + AWS_DEFAULT_REGION: us-east-1 + +permissions: + id-token: write # This is required for requesting the JWT jobs: lint: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - name: GitHub Action for SwiftLint uses: norio-nomura/action-swiftlint@3.2.1 linux: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: @@ -34,10 +36,15 @@ jobs: # issue to fix centos opened against apple here: https://github.com/apple/swift-docker/issues/258 # - centos-x64 steps: - - name: Build ${{ env.PACKAGE_NAME }} - run: | - aws s3 cp --debug s3://aws-crt-test-stuff/ci/${{ env.BUILDER_VERSION }}/linux-container-ci.sh ./linux-container-ci.sh && chmod a+x ./linux-container-ci.sh - ./linux-container-ci.sh ${{ env.BUILDER_VERSION }} aws-crt-swift-5-${{ matrix.image }} build -p ${{ env.PACKAGE_NAME }} + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.CRT_CI_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} + - name: Build ${{ env.PACKAGE_NAME }} + run: | + aws s3 cp --debug s3://aws-crt-test-stuff/ci/${{ env.BUILDER_VERSION }}/linux-container-ci.sh ./linux-container-ci.sh && chmod a+x ./linux-container-ci.sh + ./linux-container-ci.sh ${{ env.BUILDER_VERSION }} aws-crt-swift-5-${{ matrix.image }} build -p ${{ env.PACKAGE_NAME }} + macos: runs-on: ${{ matrix.runner }} env: @@ -49,17 +56,20 @@ jobs: matrix: # This matrix runs tests on Mac, on oldest & newest supported Xcodes runner: - - macos-12 # x64 - macos-13 # x64 - macos-14 - macos-13-xlarge - macos-14-large #x64 steps: - - name: Build ${{ env.PACKAGE_NAME }} + consumers - run: | - python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')" - chmod a+x builder - ./builder build -p ${{ env.PACKAGE_NAME }} + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.CRT_CI_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} + - name: Build ${{ env.PACKAGE_NAME }} + consumers + run: | + python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')" + chmod a+x builder + ./builder build -p ${{ env.PACKAGE_NAME }} devices: runs-on: ${{ matrix.runner }} @@ -72,7 +82,6 @@ jobs: matrix: # This matrix runs tests on iOS, tvOS & watchOS, on oldest & newest supported Xcodes runner: - - macos-12 # x64 - macos-13 # x64 - macos-14 - macos-13-xlarge @@ -91,8 +100,6 @@ jobs: # Don't run old macOS with new Xcode - runner: macos-13-xlarge xcode: Xcode_15.2 - - runner: macos-12 - xcode: Xcode_15.2 - runner: macos-13 xcode: Xcode_15.2 # Don't run new macOS with old Xcode @@ -115,21 +122,29 @@ jobs: - target: { os: watchos, destination: 'watchOS Simulator,OS=9.1,name=Apple Watch Series 5 (40mm)'} xcode: Xcode_15.2 steps: - - name: Build ${{ env.PACKAGE_NAME }} + consumers - run: | - python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')" - chmod a+x builder - ./builder build -p ${{ env.PACKAGE_NAME }} --target=${{ matrix.target.os }}-armv8 + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.CRT_CI_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} + - name: Build ${{ env.PACKAGE_NAME }} + consumers + run: | + python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')" + chmod a+x builder + ./builder build -p ${{ env.PACKAGE_NAME }} --target=${{ matrix.target.os }}-armv8 check-submodules: - runs-on: ubuntu-22.04 # latest + runs-on: ubuntu-24.04 # latest steps: - - name: Checkout Source - uses: actions/checkout@v4 - with: - submodules: true - fetch-depth: 0 - - name: Check Submodules - # note: using "@main" because "@${{env.BUILDER_VERSION}}" doesn't work - # https://github.com/actions/runner/issues/480 - uses: awslabs/aws-crt-builder/.github/actions/check-submodules@main + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.CRT_CI_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} + - name: Checkout Source + uses: actions/checkout@v4 + with: + submodules: true + fetch-depth: 0 + - name: Check Submodules + # note: using "@main" because "@${{env.BUILDER_VERSION}}" doesn't work + # https://github.com/actions/runner/issues/480 + uses: awslabs/aws-crt-builder/.github/actions/check-submodules@main diff --git a/.github/workflows/s2n-prelude-changes.yml b/.github/workflows/s2n-prelude-changes.yml new file mode 100644 index 00000000..073e5bb1 --- /dev/null +++ b/.github/workflows/s2n-prelude-changes.yml @@ -0,0 +1,32 @@ +# Detect changes to s2n_prelude.h to update our `Package.swift` and stay in sync with it. +# See: https://github.com/awslabs/aws-crt-swift/pull/299 for updating the Package.swift. + +name: s2n_prelude.h Change Detector + +on: [push] + +jobs: + check-for-changes: + + runs-on: ubuntu-24.04 # latest + + steps: + - name: Checkout Sources + uses: actions/checkout@v4 + with: + submodules: true + + - name: Check s2n_prelude.h + run: | + TMPFILE=$(mktemp) + echo "116f1525acbc94c91b0ee2ea2af9fdef aws-common-runtime/s2n/utils/s2n_prelude.h" > $TMPFILE + md5sum --check $TMPFILE + + # No further steps if successful + + - name: Echo fail + if: failure() + run: | + echo "The aws-crt-swift has a hack to manually define macros which are defined in s2n_prelude.h in Package.Swift. + This check will fail whenever s2n_prelude.h is updated by the S2N team. You should make sure that Package.Swift is updated accordingly + with the s2n_prelude.h changes and then run `md5sum aws-common-runtime/s2n/utils/s2n_prelude.h` and update the value above." diff --git a/Package.swift b/Package.swift index 1aac45ce..98535e61 100644 --- a/Package.swift +++ b/Package.swift @@ -100,8 +100,17 @@ packageTargets.append(.target( publicHeadersPath: "api", cSettings: [ .headerSearchPath("./"), - .define("POSIX_C_SOURCE=200809L"), - .define("S2N_NO_PQ") + .define("S2N_NO_PQ"), + // This is a hack to get around the fact that S2N uses the compiler option `-include` + // to include `s2n_prelude.h` in all .c files. Since SwiftPM doesn't support compiler flags, + // we manually define the macros from `s2n_prelude.h`. When SwiftPM supports compiler flags + // or building packages using CMake, this hack should be removed. + // We are not defining `S2N_API` because we don't need to expose any symbols from S2N in crt-swift. + .define("_S2N_PRELUDE_INCLUDED"), + .define("S2N_BUILD_RELEASE"), + .define("_FORTIFY_SOURCE", to: "2"), + .define("POSIX_C_SOURCE", to: "200809L"), + ] )) #endif @@ -146,9 +155,6 @@ var awsCChecksumsExcludes = [ "cmake", "tests"] -// swift never uses Microsoft Visual C++ compiler -awsCChecksumsExcludes.append("source/intel/visualc") - // Hardware accelerated checksums are disabled because SwiftPM doesn't like the necessary compiler flags. // We can add it once SwiftPM has the necessary support for CPU flags or builds C libraries // using CMake. diff --git a/Source/AwsCommonRuntimeKit/auth/credentials/Credentials.swift b/Source/AwsCommonRuntimeKit/auth/credentials/Credentials.swift index 4dd6866e..0cedf7c2 100644 --- a/Source/AwsCommonRuntimeKit/auth/credentials/Credentials.swift +++ b/Source/AwsCommonRuntimeKit/auth/credentials/Credentials.swift @@ -8,9 +8,13 @@ public final class Credentials { let rawValue: OpaquePointer - init(rawValue: OpaquePointer) { + // TODO: remove this property once aws-c-auth supports account_id + private let accountId: String? + + init(rawValue: OpaquePointer, accountId: String? = nil) { self.rawValue = rawValue aws_credentials_acquire(rawValue) + self.accountId = accountId } /// Creates a new set of aws credentials @@ -19,12 +23,14 @@ public final class Credentials { /// - accessKey: value for the aws access key id field /// - secret: value for the secret access key field /// - sessionToken: (Optional) security token associated with the credentials + /// - accountId: (Optional) the account ID for the resolved credentials, if known /// - expiration: (Optional) Point in time after which credentials will no longer be valid. /// For credentials that do not expire, use nil. /// If expiration.timeIntervalSince1970 is greater than UInt64.max, it will be converted to nil. /// - Throws: CommonRuntimeError.crtError public init(accessKey: String, secret: String, + accountId: String? = nil, sessionToken: String? = nil, expiration: Date? = nil) throws { @@ -51,6 +57,7 @@ public final class Credentials { throw CommonRunTimeError.crtError(.makeFromLastError()) } self.rawValue = rawValue + self.accountId = accountId } /// Gets the access key from the `aws_credentials` instance @@ -69,6 +76,15 @@ public final class Credentials { return secret.toOptionalString() } + /// Gets the account ID from the `Credentials`, if any. + /// + /// Temporarily, `accountId` is backed by a Swift instance variable. + /// In the future, when the C implementation implements `account_id` the implementation will get account ID from the `aws_credentials` instance. + /// - Returns:`String?`: The AWS `accountId` or nil + public func getAccountId() -> String? { + accountId + } + /// Gets the session token from the `aws_credentials` instance /// /// - Returns:`String?`: The AWS Session token or nil diff --git a/Source/AwsCommonRuntimeKit/auth/credentials/CredentialsProvider.swift b/Source/AwsCommonRuntimeKit/auth/credentials/CredentialsProvider.swift index 881ea7c7..de757768 100644 --- a/Source/AwsCommonRuntimeKit/auth/credentials/CredentialsProvider.swift +++ b/Source/AwsCommonRuntimeKit/auth/credentials/CredentialsProvider.swift @@ -14,8 +14,12 @@ public class CredentialsProvider: CredentialsProviding { let rawValue: UnsafeMutablePointer - init(credentialsProvider: UnsafeMutablePointer) { + // TODO: remove this property once aws-c-auth supports account_id + private let accountId: String? + + init(credentialsProvider: UnsafeMutablePointer, accountId: String? = nil) { self.rawValue = credentialsProvider + self.accountId = accountId } /// Retrieves credentials from a provider by calling its implementation of get credentials and returns them to @@ -25,7 +29,10 @@ public class CredentialsProvider: CredentialsProviding { /// - Throws: CommonRuntimeError.crtError public func getCredentials() async throws -> Credentials { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - let continuationCore = ContinuationCore(continuation: continuation) + let continuationCore = ContinuationCore( + continuation: continuation, + userData: ["accountId": accountId as Any] + ) if aws_credentials_provider_get_credentials(rawValue, onGetCredentials, continuationCore.passRetained()) != AWS_OP_SUCCESS { @@ -52,6 +59,14 @@ extension CredentialsProvider { self.init(credentialsProvider: unsafeProvider) } + // TODO: Remove the following initializer when aws-c-auth provides account_id in credentials + /// Creates a credentials provider that sources the credentials from the provided source and `accountId` + @_spi(AccountIDTempSupport) + public convenience init(source: Source, accountId: String?) throws { + let unsafeProvider = try source.makeProvider() + self.init(credentialsProvider: unsafeProvider, accountId: accountId) + } + /// Create a credentials provider that depends on provider to fetch the credentials. /// It will retain the provider until shutdown callback is triggered for AwsCredentialsProvider /// - Parameters: @@ -483,6 +498,7 @@ extension CredentialsProvider.Source { Self { let shutdownCallbackCore = ShutdownCallbackCore(shutdownCallback) var stsOptions = aws_credentials_provider_sts_options() + stsOptions.bootstrap = bootstrap.rawValue stsOptions.tls_ctx = tlsContext.rawValue stsOptions.creds_provider = credentialsProvider.rawValue stsOptions.duration_seconds = UInt16(duration) @@ -593,7 +609,8 @@ private func onGetCredentials(credentials: OpaquePointer?, } // Success - continuationCore.continuation.resume(returning: Credentials(rawValue: credentials!)) + let accountId = continuationCore.userData?["accountId"] as? String + continuationCore.continuation.resume(returning: Credentials(rawValue: credentials!, accountId: accountId)) } // We need to share this pointer to C in a task block but Swift compiler complains diff --git a/Source/AwsCommonRuntimeKit/crt/CBOR.swift b/Source/AwsCommonRuntimeKit/crt/CBOR.swift new file mode 100644 index 00000000..ada5f3b5 --- /dev/null +++ b/Source/AwsCommonRuntimeKit/crt/CBOR.swift @@ -0,0 +1,419 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0. +import AwsCCommon +import Foundation + +/// CBOR Types. These types don't map one-to-one to the CBOR RFC. +/// Numbers will be encoded using the "smallest possible" encoding. +/// Warning: This enum is non-exhaustive and subject to change in the future. +public enum CBORType: Equatable { + /// UINT64 type for positive numbers. + case uint(_ value: UInt64) + /// INT64 type for negative numbers. If the number is positive, it will be encoded as UINT64 type. + case int(_ value: Int64) + /// Double type. It might be encoded as an integer if possible without loss of precision. Half-precision floats are not supported. + case double(_ value: Double) + /// Bytes type for binary data + case bytes(_ value: Data) + /// Text type for utf-8 encoded strings + case text(_ value: String) + /// Array type + case array(_ value: [CBORType]) + /// Map type + case map(_ value: [String: CBORType]) + /// Date type. It will be encoded as epoch-based time. + /// There might be some precision loss if this is encoded as an integer and + /// later converted to a double in some cases. + case date(_ value: Date) + /// Bool type + case bool(_ value: Bool) + /// Null type + case null + /// Undefined type + case undefined + /// Tag type. Refer to RFC8949, section 3.4. For tag 1 (epoch-based time), + /// you should use the `date` type, which is a helper for this. + /// Values with tag 1 will be decoded as the `date` type. + case tag(_ value: UInt64) + /// Break type for indefinite-length arrays, maps, bytes, and text. For encoding, you should start the encoding + /// with `indef_*_start` and then end the encoding with this `indef_break` type. During decoding, you will get + /// the `indef_*_start` type first, followed by N elements, and the break type at the end. + case indef_break + /// Indefinite Bytes Type + case indef_bytes_start + /// Indefinite Text Type + case indef_text_start + /// Indefinite Array Type + case indef_array_start + /// Indefinite Map Type + case indef_map_start +} + +/// Encoder for the CBOR Types. +public class CBOREncoder { + var rawValue: OpaquePointer + + public init() throws { + let rawValue = aws_cbor_encoder_new(allocator.rawValue) + guard let rawValue else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + self.rawValue = rawValue + } + + /// Encode a single type + /// - Parameters: + /// - value: value to encode + /// - Throws: CommonRuntimeError.crtError + public func encode(_ value: CBORType) { + switch value { + case .uint(let value): + aws_cbor_encoder_write_uint(self.rawValue, value) + case .int(let value): + if value >= 0 { + aws_cbor_encoder_write_uint(self.rawValue, UInt64(value)) + } else { + aws_cbor_encoder_write_negint(self.rawValue, UInt64(-1 - value)) + } + case .double(let value): + aws_cbor_encoder_write_float(self.rawValue, value) + case .bool(let value): + aws_cbor_encoder_write_bool(self.rawValue, value) + case .bytes(let data): + data.withAWSByteCursorPointer { cursor in + aws_cbor_encoder_write_bytes(self.rawValue, cursor.pointee) + } + case .text(let string): + string.withByteCursor { cursor in + aws_cbor_encoder_write_text(self.rawValue, cursor) + } + case .null: + aws_cbor_encoder_write_null(self.rawValue) + case .undefined: + aws_cbor_encoder_write_undefined(self.rawValue) + case .date(let date): + aws_cbor_encoder_write_tag(self.rawValue, UInt64(AWS_CBOR_TAG_EPOCH_TIME)) + aws_cbor_encoder_write_float(self.rawValue, date.timeIntervalSince1970) + case .tag(let tag): + aws_cbor_encoder_write_tag(self.rawValue, tag) + + case .array(let values): + do { + aws_cbor_encoder_write_array_start(self.rawValue, values.count) + for value in values { + encode(value) + } + } + case .map(let values): + do { + aws_cbor_encoder_write_map_start(self.rawValue, values.count) + for (key, value) in values { + encode(.text(key)) + encode(value) + } + } + + case .indef_break: aws_cbor_encoder_write_break(self.rawValue) + case .indef_array_start: aws_cbor_encoder_write_indef_array_start(self.rawValue) + case .indef_map_start: aws_cbor_encoder_write_indef_map_start(self.rawValue) + case .indef_bytes_start: aws_cbor_encoder_write_indef_bytes_start(self.rawValue) + case .indef_text_start: aws_cbor_encoder_write_indef_text_start(self.rawValue) + } + } + + /// Get all the values encoded so far as an array of raw bytes. + /// This won't reset the encoder, and you will get all the bytes encoded so far from the beginning. + public func getEncoded() -> [UInt8] { + aws_cbor_encoder_get_encoded_data(self.rawValue).toArray() + } + + deinit { + aws_cbor_encoder_destroy(rawValue) + } +} + +// swiftlint:disable type_body_length +/// Decoder for the CBOR encoding. +public class CBORDecoder { + var rawValue: OpaquePointer + // Keep a reference to data to make it outlive the decoder + let data: [UInt8] + + public init(data: [UInt8]) throws { + self.data = data + let count = self.data.count + let rawValue = self.data.withUnsafeBytes { + let cursor = aws_byte_cursor_from_array($0.baseAddress, count) + return aws_cbor_decoder_new(allocator.rawValue, cursor) + } + guard let rawValue else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + self.rawValue = rawValue + } + + /// Returns true if there is any data left to decode. + public func hasNext() -> Bool { + aws_cbor_decoder_get_remaining_length(self.rawValue) != 0 + } + + /// Decodes and returns the next value. If there is no value, this function will throw an error. + /// You must call `hasNext()` before calling this function. + public func popNext() throws -> CBORType { + var cbor_type: aws_cbor_type = AWS_CBOR_TYPE_UNKNOWN + guard aws_cbor_decoder_peek_type(self.rawValue, &cbor_type) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + + switch cbor_type { + case AWS_CBOR_TYPE_UINT: + return try decodeUInt() + case AWS_CBOR_TYPE_NEGINT: + return try decodeNegInt() + case AWS_CBOR_TYPE_FLOAT: + return try decodeFloat() + case AWS_CBOR_TYPE_BYTES: + return try decodeBytes() + case AWS_CBOR_TYPE_TEXT: + return try decodeText() + case AWS_CBOR_TYPE_BOOL: + return try decodeBool() + case AWS_CBOR_TYPE_NULL: + return try decodeNull() + case AWS_CBOR_TYPE_TAG: + return try decodeTag() + case AWS_CBOR_TYPE_ARRAY_START: + return try decodeDefiniteArray() + case AWS_CBOR_TYPE_MAP_START: + return try decodeDefiniteMap() + case AWS_CBOR_TYPE_UNDEFINED: + return try decodeUndefined() + case AWS_CBOR_TYPE_BREAK: + return try decodeBreak() + + case AWS_CBOR_TYPE_INDEF_ARRAY_START: + return try decodeIndefiniteArray() + case AWS_CBOR_TYPE_INDEF_MAP_START: + return try decodeIndefiniteMap() + case AWS_CBOR_TYPE_INDEF_BYTES_START: + return try decodeIndefiniteBytes() + case AWS_CBOR_TYPE_INDEF_TEXT_START: + return try decodeIndefiniteText() + + default: + throw CommonRunTimeError.crtError(CRTError(code: AWS_ERROR_CBOR_UNEXPECTED_TYPE.rawValue)) + } + } + + // Decoding helper methods for definite and simple types + private func decodeUInt() throws -> CBORType { + var out_value: UInt64 = 0 + guard aws_cbor_decoder_pop_next_unsigned_int_val(self.rawValue, &out_value) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + return .uint(out_value) + } + + private func decodeNegInt() throws -> CBORType { + var out_value: UInt64 = 0 + guard aws_cbor_decoder_pop_next_negative_int_val(self.rawValue, &out_value) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + guard out_value <= Int64.max else { + throw CommonRunTimeError.crtError(CRTError(code: AWS_ERROR_CBOR_UNEXPECTED_TYPE.rawValue)) + } + // CBOR negative integers are encoded as -1 - value, so convert accordingly. + return .int(Int64(-Int64(out_value) - 1)) + } + + private func decodeFloat() throws -> CBORType { + var out_value: Double = 0 + guard aws_cbor_decoder_pop_next_float_val(self.rawValue, &out_value) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + return .double(out_value) + } + + private func decodeBytes() throws -> CBORType { + var out_value = aws_byte_cursor() + guard aws_cbor_decoder_pop_next_bytes_val(self.rawValue, &out_value) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + return .bytes(out_value.toData()) + } + + private func decodeText() throws -> CBORType { + var out_value = aws_byte_cursor() + guard aws_cbor_decoder_pop_next_text_val(self.rawValue, &out_value) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + return .text(out_value.toString()) + } + + private func decodeBool() throws -> CBORType { + var out_value: Bool = false + guard aws_cbor_decoder_pop_next_boolean_val(self.rawValue, &out_value) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + return .bool(out_value) + } + + private func decodeNull() throws -> CBORType { + guard aws_cbor_decoder_consume_next_single_element(self.rawValue) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + return .null + } + + private func decodeUndefined() throws -> CBORType { + guard aws_cbor_decoder_consume_next_single_element(self.rawValue) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + return .undefined + } + + private func decodeTag() throws -> CBORType { + var out_value: UInt64 = 0 + guard aws_cbor_decoder_pop_next_tag_val(self.rawValue, &out_value) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + guard out_value == 1 else { + return .tag(out_value) + } + + let timestamp = try popNext() + switch timestamp { + case .double(let value): + return .date(Date(timeIntervalSince1970: value)) + case .uint(let value): + return .date(Date(timeIntervalSince1970: Double(value))) + case .int(let value): + return .date(Date(timeIntervalSince1970: Double(value))) + default: + throw CommonRunTimeError.crtError(CRTError(code: AWS_ERROR_CBOR_UNEXPECTED_TYPE.rawValue)) + } + } + + private func decodeDefiniteArray() throws -> CBORType { + var length: UInt64 = 0 + guard aws_cbor_decoder_pop_next_array_start(self.rawValue, &length) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + + var array: [CBORType] = [] + for _ in 0.. CBORType { + var out_value: UInt64 = 0 + guard + aws_cbor_decoder_pop_next_map_start(self.rawValue, &out_value) + == AWS_OP_SUCCESS + else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + var map: [String: CBORType] = [:] + for _ in 0.. CBORType { + // This should only be called inside indefinite decoding + guard aws_cbor_decoder_consume_next_single_element(self.rawValue) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + return .indef_break + } + + private func decodeIndefiniteArray() throws -> CBORType { + guard aws_cbor_decoder_consume_next_single_element(self.rawValue) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + var array: [CBORType] = [] + while true { + let cbor_type = try popNext() + if cbor_type == .indef_break { + break + } else { + array.append(cbor_type) + } + } + return .array(array) + } + + private func decodeIndefiniteMap() throws -> CBORType { + guard aws_cbor_decoder_consume_next_single_element(self.rawValue) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + var map: [String: CBORType] = [:] + while true { + let keyVal = try popNext() + if keyVal == .indef_break { + break + } else { + guard case .text(let key) = keyVal else { + throw CommonRunTimeError.crtError(CRTError(code: AWS_ERROR_CBOR_UNEXPECTED_TYPE.rawValue)) + } + + let value = try popNext() + map[key] = value + } + } + return .map(map) + } + + private func decodeIndefiniteBytes() throws -> CBORType { + guard aws_cbor_decoder_consume_next_single_element(self.rawValue) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + + var data = Data() + while true { + let cbor_type = try popNext() + if cbor_type == .indef_break { + break + } else { + guard case .bytes(let chunkData) = cbor_type else { + throw CommonRunTimeError.crtError(CRTError(code: AWS_ERROR_CBOR_UNEXPECTED_TYPE.rawValue)) + } + data.append(chunkData) + } + } + return .bytes(data) + } + + private func decodeIndefiniteText() throws -> CBORType { + guard aws_cbor_decoder_consume_next_single_element(self.rawValue) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + + var text = "" + while true { + let cbor_type = try popNext() + if cbor_type == .indef_break { + break + } else { + guard case .text(let chunkStr) = cbor_type else { + throw CommonRunTimeError.crtError(CRTError(code: AWS_ERROR_CBOR_UNEXPECTED_TYPE.rawValue)) + } + text += chunkStr + } + } + return .text(text) + } + + deinit { + aws_cbor_decoder_destroy(rawValue) + } +} diff --git a/Source/AwsCommonRuntimeKit/crt/ContinuationCore.swift b/Source/AwsCommonRuntimeKit/crt/ContinuationCore.swift index fb2d6fba..7b7a2fc8 100644 --- a/Source/AwsCommonRuntimeKit/crt/ContinuationCore.swift +++ b/Source/AwsCommonRuntimeKit/crt/ContinuationCore.swift @@ -1,13 +1,17 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0. +// TODO: Remove userData property once it is no longer needed for accountId on credentials + /// Core classes have manual memory management. /// You have to balance the retain & release calls in all cases to avoid leaking memory. class ContinuationCore { let continuation: CheckedContinuation + let userData: [String: Any]? - init(continuation: CheckedContinuation) { + init(continuation: CheckedContinuation, userData: [String: Any]? = nil) { self.continuation = continuation + self.userData = userData } func passRetained() -> UnsafeMutableRawPointer { diff --git a/Source/AwsCommonRuntimeKit/crt/Utilities.swift b/Source/AwsCommonRuntimeKit/crt/Utilities.swift index 360a1711..8dda4c10 100644 --- a/Source/AwsCommonRuntimeKit/crt/Utilities.swift +++ b/Source/AwsCommonRuntimeKit/crt/Utilities.swift @@ -124,6 +124,14 @@ extension TimeInterval { } extension aws_byte_cursor { + func toData() -> Data { + Data(bytes: self.ptr, count: self.len) + } + + func toArray() -> [UInt8] { + Array(UnsafeBufferPointer(start: self.ptr, count: self.len)) + } + func toString() -> String { if self.len == 0 { return "" diff --git a/Test/AwsCommonRuntimeKitTests/auth/CredentialsProviderTests.swift b/Test/AwsCommonRuntimeKitTests/auth/CredentialsProviderTests.swift index f8bc6adf..2e12bdcc 100644 --- a/Test/AwsCommonRuntimeKitTests/auth/CredentialsProviderTests.swift +++ b/Test/AwsCommonRuntimeKitTests/auth/CredentialsProviderTests.swift @@ -2,11 +2,12 @@ // SPDX-License-Identifier: Apache-2.0. import XCTest -@testable import AwsCommonRuntimeKit +@_spi(AccountIDTempSupport) @testable import AwsCommonRuntimeKit class CredentialsProviderTests: XCBaseTestCase { let accessKey = "AccessKey" let secret = "Sekrit" + var accountId: String? = nil let sessionToken = "Token" let shutdownWasCalled = XCTestExpectation(description: "Shutdown callback was called") @@ -68,12 +69,14 @@ class CredentialsProviderTests: XCBaseTestCase { wait(for: [shutdownWasCalled], timeout: 15) } + // TODO: change this test to not pass accountId separately once the source function handles it func testCreateCredentialsProviderStatic() async throws { + accountId = "0123456789" do { let provider = try CredentialsProvider(source: .static(accessKey: accessKey, secret: secret, sessionToken: sessionToken, - shutdownCallback: getShutdownCallback())) + shutdownCallback: getShutdownCallback()), accountId: accountId) let credentials = try await provider.getCredentials() XCTAssertNotNil(credentials) assertCredentials(credentials: credentials) @@ -221,17 +224,17 @@ class CredentialsProviderTests: XCBaseTestCase { tokenFilePath: "tokenFilePath")) } - func testCreateDestroyStsInvalidRole() async throws { + func testCreateDestroySts() async throws { let provider = try CredentialsProvider(source: .static(accessKey: accessKey, secret: secret, sessionToken: sessionToken)) - XCTAssertThrowsError(try CredentialsProvider(source: .sts(bootstrap: getClientBootstrap(), + _ = try CredentialsProvider(source: .sts(bootstrap: getClientBootstrap(), tlsContext: getTlsContext(), credentialsProvider: provider, - roleArn: "invalid-role-arn", + roleArn: "roleArn", sessionName: "test-session", duration: 10, - shutdownCallback: getShutdownCallback()))) + shutdownCallback: getShutdownCallback())) } func testCreateDestroyEcsMissingCreds() async throws { diff --git a/Test/AwsCommonRuntimeKitTests/auth/CredentialsTests.swift b/Test/AwsCommonRuntimeKitTests/auth/CredentialsTests.swift index 62d18ad9..3ffdcc23 100644 --- a/Test/AwsCommonRuntimeKitTests/auth/CredentialsTests.swift +++ b/Test/AwsCommonRuntimeKitTests/auth/CredentialsTests.swift @@ -8,13 +8,15 @@ class CredentialsTests: XCBaseTestCase { func testCreateAWSCredentials() async throws { let accessKey = "AccessKey" let secret = "Secret" + let accountId = "0123456789" let sessionToken = "Token" let expiration = Date(timeIntervalSinceNow: 10) - let credentials = try Credentials(accessKey: accessKey, secret: secret, sessionToken: sessionToken, expiration: expiration) + let credentials = try Credentials(accessKey: accessKey, secret: secret, accountId: accountId, sessionToken: sessionToken, expiration: expiration) XCTAssertEqual(accessKey, credentials.getAccessKey()) XCTAssertEqual(secret, credentials.getSecret()) + XCTAssertEqual(accountId, credentials.getAccountId()) XCTAssertEqual(sessionToken, credentials.getSessionToken()) XCTAssertEqual(UInt64(expiration.timeIntervalSince1970), UInt64(credentials.getExpiration()!.timeIntervalSince1970)) @@ -38,6 +40,22 @@ class CredentialsTests: XCBaseTestCase { XCTAssertNil(credentials2.getExpiration()) } + func testCreateAWSCredentialsWithoutAccountId() async throws { + let accessKey = "AccessKey" + let secret = "Secret" + let sessionToken = "Token" + let expiration = Date(timeIntervalSinceNow: 10) + + let credentials = try Credentials(accessKey: accessKey, secret: secret, accountId: nil, sessionToken: sessionToken, expiration: expiration) + + XCTAssertEqual(accessKey, credentials.getAccessKey()) + XCTAssertEqual(secret, credentials.getSecret()) + XCTAssertNil(credentials.getAccountId()) + XCTAssertEqual(sessionToken, credentials.getSessionToken()) + XCTAssertEqual(UInt64(expiration.timeIntervalSince1970), UInt64(credentials.getExpiration()!.timeIntervalSince1970)) + + } + func testCreateAWSCredentialsWithoutSessionToken() async throws { let accessKey = "AccessKey" let secret = "Secret" diff --git a/Test/AwsCommonRuntimeKitTests/crt/CBOR.swift b/Test/AwsCommonRuntimeKitTests/crt/CBOR.swift new file mode 100644 index 00000000..44478d45 --- /dev/null +++ b/Test/AwsCommonRuntimeKitTests/crt/CBOR.swift @@ -0,0 +1,108 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0. + +import AwsCCommon +import XCTest + +@testable import AwsCommonRuntimeKit + +class CBORTests: XCBaseTestCase { + + func testCBOR() async throws { + let values_to_encode: [CBORType] = [ + // simple types + .uint(100), + .uint(UInt64.min), + .uint(UInt64.max), + .int(-100), + .int(Int64.min), + .int(Int64.max), + .double(10.59), + .double(10.0), + .bool(true), + .null, + .undefined, + // test tag + .tag(0), + .uint(100), + // test that tag 1 is decoded as date + .tag(1), + .double(Date(timeIntervalSince1970: 10.5).timeIntervalSince1970), + .date(Date(timeIntervalSince1970: 20.5)), + // complex types + .array([.int(-100), .uint(1000)]), + .map(["key": .uint(100), "key2": .int(-100)]), + .bytes("hello".data(using: .utf8)!), + .text("hello"), + // indef types + .indef_array_start, + .uint(100), + .int(-100), + .indef_break, + .indef_map_start, + .text("key1"), + .uint(100), + .text("key2"), + .int(-100), + .indef_break, + .indef_text_start, + .text("hello"), + .indef_break, + .indef_bytes_start, + .bytes(Data([0x01, 0x02, 0x03])), // First chunk of bytes + .bytes(Data([0x04, 0x05])), // Second chunk of bytes + .indef_break, + ] + let expected_decoded_values: [CBORType] = [ + // simple types + .uint(100), + .uint(UInt64.min), + .uint(UInt64.max), + .int(-100), + .int(Int64.min), + .uint(UInt64(Int64.max)), + .double(10.59), + .uint(10), + .bool(true), + .null, + .undefined, + // test tag + .tag(0), + .uint(100), + // test that tag 1 is decoded as date + .date(Date(timeIntervalSince1970: 10.5)), + .date(Date(timeIntervalSince1970: 20.5)), + // complex types + .array([.int(-100), .uint(1000)]), + .map(["key": .uint(100), "key2": .int(-100)]), + .bytes("hello".data(using: .utf8)!), + .text("hello"), + // indef types + .array([.uint(100), .int(-100)]), + .map(["key1": .uint(100), "key2": .int(-100)]), + .text("hello"), + .bytes(Data([0x01, 0x02, 0x03, 0x04, 0x05])), + ] + + + // encode the values. Drop the encoder to verify lifetime semantics. + var encoded: [UInt8] = [] + do { + let encoder = try! CBOREncoder() + for value in values_to_encode { + encoder.encode(value) + } + encoded = encoder.getEncoded() + } + + // decode the values + let decoder = try! CBORDecoder(data: encoded) + for expected in expected_decoded_values { + XCTAssertTrue(decoder.hasNext()) + let actual = try! decoder.popNext() + XCTAssertEqual(actual, expected) + } + XCTAssertFalse(decoder.hasNext()) + } + +} diff --git a/aws-common-runtime/aws-c-auth b/aws-common-runtime/aws-c-auth index 48d647bf..3982bd75 160000 --- a/aws-common-runtime/aws-c-auth +++ b/aws-common-runtime/aws-c-auth @@ -1 +1 @@ -Subproject commit 48d647bf43f8872e4dc5ec6343b0c5974195fbdd +Subproject commit 3982bd75fea74efd8f9b462b27fedd4599db4f53 diff --git a/aws-common-runtime/aws-c-cal b/aws-common-runtime/aws-c-cal index 2cb1d2ea..656762ae 160000 --- a/aws-common-runtime/aws-c-cal +++ b/aws-common-runtime/aws-c-cal @@ -1 +1 @@ -Subproject commit 2cb1d2eac925e2dbc45025eb89af82bd790c23a0 +Subproject commit 656762aefbee2bc8f509cb23cd107abff20a72bb diff --git a/aws-common-runtime/aws-c-common b/aws-common-runtime/aws-c-common index faa6c0f0..63187b97 160000 --- a/aws-common-runtime/aws-c-common +++ b/aws-common-runtime/aws-c-common @@ -1 +1 @@ -Subproject commit faa6c0f00802fc861e7252404f65fb1e0617ca8e +Subproject commit 63187b976a482309e23296c5f967fc19c4131746 diff --git a/aws-common-runtime/aws-c-compression b/aws-common-runtime/aws-c-compression index f36d0167..c6c1191e 160000 --- a/aws-common-runtime/aws-c-compression +++ b/aws-common-runtime/aws-c-compression @@ -1 +1 @@ -Subproject commit f36d01672d61e49d96a777870d456f66fa391cd4 +Subproject commit c6c1191e525e5aa6ead9e1afc392e35d3b50331e diff --git a/aws-common-runtime/aws-c-event-stream b/aws-common-runtime/aws-c-event-stream index 1b3825fc..d2dcc934 160000 --- a/aws-common-runtime/aws-c-event-stream +++ b/aws-common-runtime/aws-c-event-stream @@ -1 +1 @@ -Subproject commit 1b3825fc9cae2e9c7ed7479ee5d354d52ebdf7a0 +Subproject commit d2dcc9344dae24de320866045d85166d8a91a0d1 diff --git a/aws-common-runtime/aws-c-http b/aws-common-runtime/aws-c-http index 6068653e..fc3eded2 160000 --- a/aws-common-runtime/aws-c-http +++ b/aws-common-runtime/aws-c-http @@ -1 +1 @@ -Subproject commit 6068653e1d582bd8e7d1c9f81f86beaf10444e3d +Subproject commit fc3eded2465c37d07fd9cc15e9b5b011224c9c9a diff --git a/aws-common-runtime/aws-c-io b/aws-common-runtime/aws-c-io index e3637404..fcb38c80 160000 --- a/aws-common-runtime/aws-c-io +++ b/aws-common-runtime/aws-c-io @@ -1 +1 @@ -Subproject commit e36374047beadc72a0eb6df14ce3cbc822a789a3 +Subproject commit fcb38c804364dd627c335da752a99a125a88f6e9 diff --git a/aws-common-runtime/aws-c-sdkutils b/aws-common-runtime/aws-c-sdkutils index 4658412a..ce09f797 160000 --- a/aws-common-runtime/aws-c-sdkutils +++ b/aws-common-runtime/aws-c-sdkutils @@ -1 +1 @@ -Subproject commit 4658412a61ad5749db92a8d1e0717cb5e76ada1c +Subproject commit ce09f79768653dbdc810fc14cad8685dd90acba1 diff --git a/aws-common-runtime/aws-checksums b/aws-common-runtime/aws-checksums index ce04ab00..3e4101b9 160000 --- a/aws-common-runtime/aws-checksums +++ b/aws-common-runtime/aws-checksums @@ -1 +1 @@ -Subproject commit ce04ab00b3ecc41912f478bfedca39f8e1919d6b +Subproject commit 3e4101b9f85a2c090774d27ae2131fca1082f522 diff --git a/aws-common-runtime/config/aws/common/config.h b/aws-common-runtime/config/aws/common/config.h index 7d00728c..df0e2d39 100644 --- a/aws-common-runtime/config/aws/common/config.h +++ b/aws-common-runtime/config/aws/common/config.h @@ -5,6 +5,7 @@ #ifndef AWS_COMMON_CONFIG_H #define AWS_COMMON_CONFIG_H + /* * This header exposes compiler feature test results determined during cmake * configure time to inline function implementations. The macros defined here @@ -13,7 +14,7 @@ */ #ifdef __APPLE__ /* This is a trick to skip OpenSSL header on Apple platforms since Swift Package Manager is not smart enough to exclude - * some headers. + * some headers. */ # define AWS_C_CAL_OPENSSLCRYPTO_COMMON_H #endif diff --git a/aws-common-runtime/s2n b/aws-common-runtime/s2n index ffe0bf42..493b7716 160000 --- a/aws-common-runtime/s2n +++ b/aws-common-runtime/s2n @@ -1 +1 @@ -Subproject commit ffe0bf42da8f139eff8fd2237f47fbde40b478fb +Subproject commit 493b77167dc367c394de23cfe78a029298e2a254