diff --git a/.builder/actions/crt-ci-prep-xcodebuild.py b/.builder/actions/crt-ci-prep-xcodebuild.py new file mode 100644 index 000000000..61c338d36 --- /dev/null +++ b/.builder/actions/crt-ci-prep-xcodebuild.py @@ -0,0 +1,9 @@ +import Builder + +class CrtCiPrepXCodebuild(Builder.Action): + def run(self, env): + env.shell.setenv("TEST_RUNNER_AWS_TESTING_STS_ROLE_ARN", env.shell.get_secret("aws-c-auth-testing/sts-role-arn")) + actions = [ + Builder.SetupCrossCICrtEnvironment(use_xcodebuild=True) + ] + return Builder.Script(actions, name='crt-ci-prep-xcodebuild') diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 95bf9dc3c..2481ece32 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,8 +6,8 @@ on: - 'main' env: - BUILDER_VERSION: v0.9.73 - BUILDER_SOURCE: releases + BUILDER_VERSION: xcodebuild_setup + BUILDER_SOURCE: channels BUILDER_HOST: https://d19elf31gohf1l.cloudfront.net PACKAGE_NAME: aws-crt-swift RUN: ${{ github.run_id }}-${{ github.run_number }} diff --git a/Package.swift b/Package.swift index c68f0319c..c3ba3a13e 100644 --- a/Package.swift +++ b/Package.swift @@ -113,7 +113,6 @@ packageTargets.append(.target( .define("S2N_BUILD_RELEASE"), .define("_FORTIFY_SOURCE", to: "2"), .define("POSIX_C_SOURCE", to: "200809L"), - ] )) #endif diff --git a/Source/AwsCommonRuntimeKit/auth/credentials/CredentialsProvider.swift b/Source/AwsCommonRuntimeKit/auth/credentials/CredentialsProvider.swift index 19706e9a8..0cc859a1b 100644 --- a/Source/AwsCommonRuntimeKit/auth/credentials/CredentialsProvider.swift +++ b/Source/AwsCommonRuntimeKit/auth/credentials/CredentialsProvider.swift @@ -10,6 +10,68 @@ public protocol CredentialsProviding { func getCredentials() async throws -> Credentials } +/// A pair defining an identity provider and a valid login token sourced from it. +public struct CognitoLoginPair: CStruct { + public var IdentityProviderName: String + public var IdentityProviderToken: String + + public init(identityProviderName: String, + identityProviderToken: String) { + self.IdentityProviderName = identityProviderName + self.IdentityProviderToken = identityProviderToken + } + + typealias RawType = aws_cognito_identity_provider_token_pair + func withCStruct(_ body: (aws_cognito_identity_provider_token_pair) -> Result) -> Result { + var token_pair = aws_cognito_identity_provider_token_pair() + + return withByteCursorFromStrings(IdentityProviderName, + IdentityProviderToken) { identityProviderNameCursor, IdentityProviderTokenCursor in + token_pair.identity_provider_name = identityProviderNameCursor + token_pair.identity_provider_token = IdentityProviderTokenCursor + return body(token_pair) + } + } +} + +extension Array where Element == CognitoLoginPair { + func withCCognitoLoginPair(_ body: (OpaquePointer) throws -> Result) rethrows -> Result { + let array_list: UnsafeMutablePointer = allocator.allocate(capacity: 1) + defer { + aws_array_list_clean_up(array_list) + allocator.release(array_list) + } + guard aws_array_list_init_dynamic( + array_list, + allocator.rawValue, + count, + MemoryLayout.size) == AWS_OP_SUCCESS else { + fatalError("Unable to initialize array of user properties") + } + forEach { + $0.withCPointer { + // `aws_array_list_push_back` will do a memory copy of $0 into array_list + guard aws_array_list_push_back(array_list, $0) == AWS_OP_SUCCESS else { + fatalError("Unable to add user property") + } + } + } + return try body(OpaquePointer(array_list.pointee.data)) + } +} + +/// Helper function to convert Swift [CognitoLoginPair]? into a native aws_cognito_identity_provider_token_pair pointer +func withOptionalCognitoLoginPair( + of array: Array?, + _ body: (OpaquePointer?) throws -> Result) rethrows -> Result { + guard let _array = array else { + return try body(nil) + } + return try _array.withCCognitoLoginPair { opaquePointer in + return try body(opaquePointer) + } + } + public class CredentialsProvider: CredentialsProviding { let rawValue: UnsafeMutablePointer @@ -36,6 +98,7 @@ public class CredentialsProvider: CredentialsProviding { } deinit { + print("credential provider deinit") aws_credentials_provider_release(rawValue) } } @@ -561,6 +624,66 @@ extension CredentialsProvider.Source { return provider } } + + /// Credential Provider that sources credentials from Cognito Identity service + /// - Parameters: + /// - bootstrap: Connection bootstrap to use for any network connections made while sourcing credentials + /// - tlsContext: TLS configuration for secure socket connections. + /// - endpoint: Cognito service regional endpoint to source credentials from. + /// - identity: Cognito identity to fetch credentials relative to. + /// - logins: (Optional) set of identity provider token pairs to allow for authenticated identity access. + /// - customRoleArn: (Optional) ARN of the role to be assumed when multiple roles were received in the token from the identity provider. + /// - proxyOptions: (Optional) Http proxy configuration for the http request that fetches credentials + /// - shutdownCallback: (Optional) shutdown callback + /// - Returns: `CredentialsProvider` + /// - Throws: CommonRuntimeError.crtError + public static func `cognito`(bootstrap: ClientBootstrap, + tlsContext: TLSContext, + endpoint: String, + identity: String, + logins: [CognitoLoginPair]? = nil, + customRoleArn: String? = nil, + proxyOptions: HTTPProxyOptions? = nil, + shutdownCallback: ShutdownCallback? = nil) -> Self { + Self { + var cognitoOptions = aws_credentials_provider_cognito_options() + cognitoOptions.bootstrap = bootstrap.rawValue + cognitoOptions.tls_ctx = tlsContext.rawValue + let shutdownCallbackCore = ShutdownCallbackCore(shutdownCallback) + cognitoOptions.shutdown_options = shutdownCallbackCore.getRetainedCredentialProviderShutdownOptions() + + guard let provider: UnsafeMutablePointer = (withByteCursorFromStrings( + endpoint, + identity) { endpointCursor, identityCursor in + + cognitoOptions.endpoint = endpointCursor + cognitoOptions.identity = identityCursor + + return withOptionalCStructPointer(to: proxyOptions) { proxyOptionsPointer in + cognitoOptions.http_proxy_options = proxyOptionsPointer + + return withOptionalCognitoLoginPair(of: logins, { loginArrayPointer in + if let loginArrayPointer, let loginCount = logins?.count { + cognitoOptions.logins = UnsafeMutablePointer(loginArrayPointer) + cognitoOptions.login_count = loginCount + } + + return withOptionalByteCursorPointerFromString(customRoleArn, { customRoleArnCursor in + if let customRoleArnCursor { + cognitoOptions.custom_role_arn = UnsafeMutablePointer(mutating: customRoleArnCursor) + } + return aws_credentials_provider_new_cognito_caching(allocator.rawValue, &cognitoOptions) + }) + }) + } + }) + else { + shutdownCallbackCore.release() + throw CommonRunTimeError.crtError(CRTError.makeFromLastError()) + } + return provider + } + } } private func onGetCredentials(credentials: OpaquePointer?, diff --git a/Source/AwsCommonRuntimeKit/mqtt/Mqtt5Client.swift b/Source/AwsCommonRuntimeKit/mqtt/Mqtt5Client.swift index 6ca86782e..6eafbc600 100644 --- a/Source/AwsCommonRuntimeKit/mqtt/Mqtt5Client.swift +++ b/Source/AwsCommonRuntimeKit/mqtt/Mqtt5Client.swift @@ -202,6 +202,7 @@ public class Mqtt5Client { } deinit { + print("client deinit....") clientCore.close() } @@ -537,7 +538,10 @@ internal func MqttClientWebsocketTransform( } let httpRequest = HTTPRequest(nativeHttpMessage: request) @Sendable func signerTransform(request: HTTPRequestBase, errorCode: Int32) { - complete_fn?(request.rawValue, errorCode, complete_ctx) + clientCore.rwlock.read { + if clientCore.rawValue == nil { return } + complete_fn?(request.rawValue, errorCode, complete_ctx) + } } if clientCore.onWebsocketInterceptor != nil { diff --git a/Test/AwsCommonRuntimeKitTests/XCBaseTestCase.swift b/Test/AwsCommonRuntimeKitTests/XCBaseTestCase.swift index eed5a8e4b..150172cc8 100644 --- a/Test/AwsCommonRuntimeKitTests/XCBaseTestCase.swift +++ b/Test/AwsCommonRuntimeKitTests/XCBaseTestCase.swift @@ -68,6 +68,8 @@ extension XCTestCase { } func skipIfPlatformDoesntSupportTLS() throws { + // Skipped for secitem support as the unit tests requires enetitlement setup to have acces to + // the data protection keychain. try skipIfiOS() try skipIfwatchOS() try skipIftvOS() diff --git a/Test/AwsCommonRuntimeKitTests/auth/CredentialsProviderTests.swift b/Test/AwsCommonRuntimeKitTests/auth/CredentialsProviderTests.swift index 519d8bb14..675b1b09b 100644 --- a/Test/AwsCommonRuntimeKitTests/auth/CredentialsProviderTests.swift +++ b/Test/AwsCommonRuntimeKitTests/auth/CredentialsProviderTests.swift @@ -218,6 +218,50 @@ class CredentialsProviderTests: XCBaseTestCase { wait(for: [shutdownWasCalled], timeout: 15) } + + func testCreateDestroyCognitoCredsProviderWithoutHttpProxy() async throws { + let exceptionWasThrown = XCTestExpectation(description: "Exception was thrown") + do { + let cognitoEndpoint = try getEnvironmentVarOrSkipTest(environmentVarName: "AWS_TEST_MQTT5_COGNITO_ENDPOINT") + let cognitoIdentity = try getEnvironmentVarOrSkipTest(environmentVarName: "AWS_TEST_MQTT5_COGNITO_IDENTITY") + + + let provider = try CredentialsProvider(source: .cognito(bootstrap: getClientBootstrap(), tlsContext: getTlsContext(), endpoint: cognitoEndpoint, identity: cognitoIdentity, shutdownCallback: getShutdownCallback())) + let credentials = try await provider.getCredentials() + XCTAssertNotNil(credentials) + } catch is XCTSkip{ // skip the test as the environment var is not set + shutdownWasCalled.fulfill() + }catch { + exceptionWasThrown.fulfill() + } + wait(for: [shutdownWasCalled], timeout: 15) + } + + // Http proxy related tests could only run behind vpc to access the proxy + func testCreateDestroyCognitoCredsProviderWithHttpProxy() async throws { + let exceptionWasThrown = XCTestExpectation(description: "Exception was thrown") + do { + let cognitoEndpoint = try getEnvironmentVarOrSkipTest(environmentVarName: "AWS_TEST_MQTT5_COGNITO_ENDPOINT") + let cognitoIdentity = try getEnvironmentVarOrSkipTest(environmentVarName: "AWS_TEST_MQTT5_COGNITO_IDENTITY") + + let httpproxyHost = try getEnvironmentVarOrSkipTest(environmentVarName: "AWS_TEST_HTTP_PROXY_HOST") + let httpproxyPort = try getEnvironmentVarOrSkipTest(environmentVarName: "AWS_TEST_HTTP_PROXY_PORT") + + let httpProxys = HTTPProxyOptions(hostName: httpproxyHost, port: UInt32(httpproxyPort)!, connectionType: .tunnel) + + let provider = try CredentialsProvider(source: .cognito(bootstrap: getClientBootstrap(), tlsContext: getTlsContext(), endpoint: cognitoEndpoint, identity: cognitoIdentity, shutdownCallback: getShutdownCallback())) + let credentials = try await provider.getCredentials() + XCTAssertNotNil(credentials) + } + catch is XCTSkip{ // skip the test as the environment var is not set + shutdownWasCalled.fulfill() + } + catch { + exceptionWasThrown.fulfill() + } + wait(for: [shutdownWasCalled], timeout: 15) + } + func testCreateDestroyStsWebIdentityInvalidEnv() async throws { XCTAssertThrowsError(try CredentialsProvider(source: .stsWebIdentity( bootstrap: getClientBootstrap(), @@ -265,4 +309,4 @@ class CredentialsProviderTests: XCBaseTestCase { } wait(for: [exceptionWasThrown], timeout: 15) } -} +} \ No newline at end of file diff --git a/Test/AwsCommonRuntimeKitTests/io/TLSContextTests.swift b/Test/AwsCommonRuntimeKitTests/io/TLSContextTests.swift index f2326bf07..e1d0a0c22 100644 --- a/Test/AwsCommonRuntimeKitTests/io/TLSContextTests.swift +++ b/Test/AwsCommonRuntimeKitTests/io/TLSContextTests.swift @@ -42,6 +42,7 @@ class TLSContextTests: XCBaseTestCase { #if AWS_USE_SECITEM func testCreateTlsContextWithSecitemOptions() throws { + try skipIfPlatformDoesntSupportTLS() let certPath = try getEnvironmentVarOrSkipTest(environmentVarName: "AWS_TEST_MQTT311_IOT_CORE_X509_CERT") let privateKeyPath = try getEnvironmentVarOrSkipTest(environmentVarName: "AWS_TEST_MQTT311_IOT_CORE_X509_KEY") diff --git a/Test/AwsCommonRuntimeKitTests/mqtt/Mqtt5ClientTests.swift b/Test/AwsCommonRuntimeKitTests/mqtt/Mqtt5ClientTests.swift index ed1d3bb33..33f9cc8d4 100644 --- a/Test/AwsCommonRuntimeKitTests/mqtt/Mqtt5ClientTests.swift +++ b/Test/AwsCommonRuntimeKitTests/mqtt/Mqtt5ClientTests.swift @@ -4,7 +4,6 @@ import XCTest import Foundation import AwsCMqtt -import LibNative @testable import AwsCommonRuntimeKit enum MqttTestError: Error { @@ -15,11 +14,20 @@ enum MqttTestError: Error { } class Mqtt5ClientTests: XCBaseTestCase { - + + let credentialProviderShutdownWasCalled = XCTestExpectation(description: "Shutdown callback was called") + + // Provider + func credentialProviderShutdownCallback() -> ShutdownCallback { + return { + self.credentialProviderShutdownWasCalled.fulfill() + } + } + /// start client and check for connection success func connectClient(client: Mqtt5Client, testContext: MqttTestContext) throws -> Void { try client.start() - if testContext.semaphoreConnectionSuccess.wait(timeout: .now() + 5) == .timedOut { + if testContext.semaphoreConnectionSuccess.wait(timeout: .now() + 120) == .timedOut { print("Connection Success Timed out after 5 seconds") XCTFail("Connection Timed Out") throw MqttTestError.connectionFail @@ -54,7 +62,7 @@ class Mqtt5ClientTests: XCBaseTestCase { } func createClientId() -> String { - return "aws-crt-swift-unit-test-" + UUID().uuidString + return "test-aws-crt-swift-unit-" + UUID().uuidString } class MqttTestContext { @@ -82,7 +90,7 @@ class Mqtt5ClientTests: XCBaseTestCase { public var lifecycleDisconnectionData: LifecycleDisconnectData? public var publishCount = 0 public var publishTarget = 1 - + init(contextName: String = "", publishTarget: Int = 1, onPublishReceived: OnPublishReceived? = nil, @@ -158,28 +166,7 @@ class Mqtt5ClientTests: XCBaseTestCase { completCallback(httpRequest, isSuccess ? AWS_OP_SUCCESS : Int32(AWS_ERROR_UNSUPPORTED_OPERATION.rawValue)) } } - - func withIoTSigv4WebsocketTransform(region: String, provider: CredentialsProvider){ - let signingConfig = SigningConfig(algorithm: SigningAlgorithmType.signingV4, - signatureType: SignatureType.requestQueryParams, - service: "iotdevicegateway", - region: region, - credentialsProvider: provider, - omitSessionToken: true) - - - self.onWebSocketHandshake = { httpRequest, completCallback in - do - { - let returnedHttpRequest = try await Signer.signRequest(request: httpRequest, config:signingConfig) - completCallback(returnedHttpRequest, AWS_OP_SUCCESS) - } - catch - { - completCallback(httpRequest, Int32(AWS_ERROR_UNSUPPORTED_OPERATION.rawValue)) - } - } - } + } func createClient(clientOptions: MqttClientOptions?, testContext: MqttTestContext) throws -> Mqtt5Client { @@ -625,42 +612,83 @@ class Mqtt5ClientTests: XCBaseTestCase { /* * [ConnWS-UC4] websocket connection with TLS, using sigv4 */ - func testMqtt5WSConnectWithMutualTLS() throws { - try skipIfPlatformDoesntSupportTLS() + func testMqtt5WSConnectWithStaticCredentialProvider() throws { + do{ + try skipIfPlatformDoesntSupportTLS() - let inputHost = try getEnvironmentVarOrSkipTest(environmentVarName: "AWS_TEST_MQTT5_IOT_CORE_HOST") - let region = try getEnvironmentVarOrSkipTest(environmentVarName: "AWS_TEST_MQTT5_IOT_CORE_REGION") + let inputHost = try getEnvironmentVarOrSkipTest(environmentVarName: "AWS_TEST_MQTT5_IOT_CORE_HOST") + let region = try getEnvironmentVarOrSkipTest(environmentVarName: "AWS_TEST_MQTT5_IOT_CORE_REGION") - let tlsOptions = TLSContextOptions.makeDefault() - let tlsContext = try TLSContext(options: tlsOptions, mode: .client) + let tlsOptions = TLSContextOptions.makeDefault() + let tlsContext = try TLSContext(options: tlsOptions, mode: .client) - let elg = try EventLoopGroup() - let resolver = try HostResolver(eventLoopGroup: elg, - maxHosts: 8, - maxTTL: 30) - let bootstrap = try ClientBootstrap(eventLoopGroup: elg, hostResolver: resolver) + let elg = try EventLoopGroup() + let resolver = try HostResolver(eventLoopGroup: elg, + maxHosts: 8, + maxTTL: 30) + let bootstrap = try ClientBootstrap(eventLoopGroup: elg, hostResolver: resolver) + + // setup role credential + let accessKey = try getEnvironmentVarOrSkipTest(environmentVarName: "AWS_TEST_MQTT5_ROLE_CREDENTIAL_ACCESS_KEY") + let secret = try getEnvironmentVarOrSkipTest(environmentVarName: "AWS_TEST_MQTT5_ROLE_CREDENTIAL_SECRET_ACCESS_KEY") + let sessionToken = try getEnvironmentVarOrSkipTest(environmentVarName: "AWS_TEST_MQTT5_ROLE_CREDENTIAL_SESSION_TOKEN") + + let provider = try CredentialsProvider(source: .static(accessKey: accessKey, + secret: secret, + sessionToken: sessionToken,shutdownCallback: credentialProviderShutdownCallback())) + let testContext = MqttTestContext() - let clientOptions = MqttClientOptions( - hostName: inputHost, - port: UInt32(443), - bootstrap: bootstrap, - tlsCtx: tlsContext) - // setup role credential - let accessKey = try getEnvironmentVarOrSkipTest(environmentVarName: "AWS_TEST_MQTT5_ROLE_CREDENTIAL_ACCESS_KEY") - let secret = try getEnvironmentVarOrSkipTest(environmentVarName: "AWS_TEST_MQTT5_ROLE_CREDENTIAL_SECRET_ACCESS_KEY") - let sessionToken = try getEnvironmentVarOrSkipTest(environmentVarName: "AWS_TEST_MQTT5_ROLE_CREDENTIAL_SESSION_TOKEN") + let signingConfig = SigningConfig(algorithm: SigningAlgorithmType.signingV4, + signatureType: SignatureType.requestQueryParams, + service: "iotdevicegateway", + region: "us-east-1", + credentialsProvider: provider, + omitSessionToken: true) + + // We manually setup the websocket transform to avoid recursive reference between provider and test context + let onWebsocketTransform : OnWebSocketHandshakeIntercept = { httpRequest, completCallback in + do + { + let returnedHttpRequest = try await Signer.signRequest(request: httpRequest, config:signingConfig) + completCallback(returnedHttpRequest, AWS_OP_SUCCESS) + } + catch CommonRunTimeError.crtError (let error) { + completCallback(httpRequest, Int32(error.code)) + } + catch + { + completCallback(httpRequest, Int32(AWS_ERROR_UNSUPPORTED_OPERATION.rawValue)) + } + } - let provider = try CredentialsProvider(source: .static(accessKey: accessKey, - secret: secret, - sessionToken: sessionToken)) - let testContext = MqttTestContext() - testContext.withIoTSigv4WebsocketTransform(region: region, provider: provider) + let clientOptions = MqttClientOptions( + hostName: inputHost, + port: UInt32(443), + bootstrap: bootstrap, + tlsCtx: tlsContext, + onWebsocketTransform:onWebsocketTransform, + connackTimeout: 10000, + onPublishReceivedFn: testContext.onPublishReceived, + onLifecycleEventStoppedFn: testContext.onLifecycleEventStopped, + onLifecycleEventAttemptingConnectFn: testContext.onLifecycleEventAttemptingConnect, + onLifecycleEventConnectionSuccessFn: testContext.onLifecycleEventConnectionSuccess, + onLifecycleEventConnectionFailureFn: testContext.onLifecycleEventConnectionFailure, + onLifecycleEventDisconnectionFn: testContext.onLifecycleEventDisconnection) - let client = try createClient(clientOptions: clientOptions, testContext: testContext) - testContext.onWebSocketHandshake = nil - try connectClient(client: client, testContext: testContext) - try disconnectClientCleanup(client:client, testContext: testContext) + let client = try Mqtt5Client(clientOptions: clientOptions) + XCTAssertNotNil(client) + + try connectClient(client: client, testContext: testContext) + try disconnectClientCleanup(client:client, testContext: testContext) + // Clean up the WebSocket handshake function to ensure the test context is properly released + testContext.onWebSocketHandshake=nil + } + catch{ + // Fulfill the callback if the error + self.credentialProviderShutdownWasCalled.fulfill() + } + wait(for: [credentialProviderShutdownWasCalled], timeout: 15); } /* @@ -685,31 +713,59 @@ class Mqtt5ClientTests: XCBaseTestCase { maxTTL: 30) let bootstrap = try ClientBootstrap(eventLoopGroup: elg, hostResolver: resolver) let httpProxy = HTTPProxyOptions(hostName: httpHost, port: UInt32(httpPort)!, authType: .none, connectionType: HTTPProxyConnectionType.tunnel) + + let provider = try CredentialsProvider(source: .defaultChain(bootstrap: bootstrap, + fileBasedConfiguration: FileBasedConfiguration(), + tlsContext: tlsContext)) + let testContext = MqttTestContext() + + + let signingConfig = SigningConfig(algorithm: SigningAlgorithmType.signingV4, + signatureType: SignatureType.requestQueryParams, + service: "iotdevicegateway", + region: "us-east-1", + credentialsProvider: provider, + + omitSessionToken: true) + + // We manually setup the websocket transform to avoid recursive reference between provider and test context + let onWebsocketTransform : OnWebSocketHandshakeIntercept = { httpRequest, completCallback in + do + { + let returnedHttpRequest = try await Signer.signRequest(request: httpRequest, config:signingConfig) + completCallback(returnedHttpRequest, AWS_OP_SUCCESS) + } + catch CommonRunTimeError.crtError (let error) { + completCallback(httpRequest, Int32(error.code)) + } + catch + { + completCallback(httpRequest, Int32(AWS_ERROR_UNSUPPORTED_OPERATION.rawValue)) + } + } let clientOptions = MqttClientOptions( hostName: iotHost, port: UInt32(443), bootstrap: bootstrap, tlsCtx: tlsContext, - httpProxyOptions: httpProxy) + onWebsocketTransform:onWebsocketTransform, + httpProxyOptions: httpProxy, + onPublishReceivedFn: testContext.onPublishReceived, + onLifecycleEventStoppedFn: testContext.onLifecycleEventStopped, + onLifecycleEventAttemptingConnectFn: testContext.onLifecycleEventAttemptingConnect, + onLifecycleEventConnectionSuccessFn: testContext.onLifecycleEventConnectionSuccess, + onLifecycleEventConnectionFailureFn: testContext.onLifecycleEventConnectionFailure, + onLifecycleEventDisconnectionFn: testContext.onLifecycleEventDisconnection) - let provider = try CredentialsProvider(source: .defaultChain(bootstrap: bootstrap, - fileBasedConfiguration: FileBasedConfiguration(), - tlsContext: tlsContext)) - let testContext = MqttTestContext() - testContext.withIoTSigv4WebsocketTransform(region: region, provider: provider) + let client = try Mqtt5Client(clientOptions: clientOptions) + XCTAssertNotNil(client) - let client = try createClient(clientOptions: clientOptions, testContext: testContext) - testContext.onWebSocketHandshake = nil try connectClient(client: client, testContext: testContext) try disconnectClientCleanup(client:client, testContext: testContext) } - - /* - * [ConnWS-UC5] Websocket connection with HttpProxy options - */ func testMqtt5WSConnectFull() throws { let inputHost = try getEnvironmentVarOrSkipTest(environmentVarName: "AWS_TEST_MQTT5_WS_MQTT_HOST") let inputPort = try getEnvironmentVarOrSkipTest(environmentVarName: "AWS_TEST_MQTT5_WS_MQTT_PORT") @@ -763,6 +819,83 @@ class Mqtt5ClientTests: XCBaseTestCase { try connectClient(client: client, testContext: testContext) try disconnectClientCleanup(client:client, testContext: testContext) } + + func testMqttWebsocketWithCognitoCredentialProvider() async throws { + do{ + let iotEndpoint = try getEnvironmentVarOrSkipTest(environmentVarName: "AWS_TEST_MQTT5_IOT_CORE_HOST") + let port = 443 + let cognitoEndpoint = try getEnvironmentVarOrSkipTest(environmentVarName: "AWS_TEST_MQTT5_COGNITO_ENDPOINT") + let cognitoIdentity = try getEnvironmentVarOrSkipTest(environmentVarName: "AWS_TEST_MQTT5_COGNITO_IDENTITY") + let testContext = MqttTestContext(contextName: "WebsocketWithCognitoCredentialProvider") + let elg = try EventLoopGroup() + let resolver = try HostResolver(eventLoopGroup: elg, maxHosts: 16, maxTTL: 30) + let clientBootstrap = try ClientBootstrap( + eventLoopGroup: elg, + hostResolver: resolver) + + let options = TLSContextOptions.makeDefault() + let tlscontext = try TLSContext(options: options, mode: .client) + + let cognitoProvider = try CredentialsProvider(source: .cognito(bootstrap: clientBootstrap, tlsContext: tlscontext, endpoint: cognitoEndpoint, identity: cognitoIdentity, shutdownCallback: credentialProviderShutdownCallback())) + + let connectOptions = MqttConnectOptions( + keepAliveInterval: TimeInterval(100), + clientId: createClientId()) + + let signingConfig = SigningConfig(algorithm: SigningAlgorithmType.signingV4, + signatureType: SignatureType.requestQueryParams, + service: "iotdevicegateway", + region: "us-east-1", + credentialsProvider: cognitoProvider, + omitSessionToken: true) + + let onWebsocketTransform : OnWebSocketHandshakeIntercept = { httpRequest, completCallback in + do + { + let returnedHttpRequest = try await Signer.signRequest(request: httpRequest, config:signingConfig) + completCallback(returnedHttpRequest, AWS_OP_SUCCESS) + print("complete signing") + } + catch CommonRunTimeError.crtError (let error) { + completCallback(httpRequest, Int32(error.code)) + print("signing failed with crterror") + } + catch + { + completCallback(httpRequest, Int32(AWS_ERROR_UNSUPPORTED_OPERATION.rawValue)) + print("signing failed") + } + } + + let clientOptions = MqttClientOptions( + hostName: iotEndpoint, + port: UInt32(port), + bootstrap: clientBootstrap, + tlsCtx: tlscontext, + onWebsocketTransform:onWebsocketTransform, + connectOptions: connectOptions, + connackTimeout: 10000, + onPublishReceivedFn: testContext.onPublishReceived, + onLifecycleEventStoppedFn: testContext.onLifecycleEventStopped, + onLifecycleEventAttemptingConnectFn: testContext.onLifecycleEventAttemptingConnect, + onLifecycleEventConnectionSuccessFn: testContext.onLifecycleEventConnectionSuccess, + onLifecycleEventConnectionFailureFn: testContext.onLifecycleEventConnectionFailure, + onLifecycleEventDisconnectionFn: testContext.onLifecycleEventDisconnection) + + let client = try Mqtt5Client(clientOptions: clientOptions) + XCTAssertNotNil(client) + try connectClient(client: client, testContext: testContext) + try disconnectClientCleanup(client: client, testContext: testContext) + // Clean up the WebSocket handshake function to ensure the test context is properly released + testContext.onWebSocketHandshake=nil + } + catch{ + // Fulfill the shutdown callback if the test failed. + print("catch error and fulfill the shutdown callback") + self.credentialProviderShutdownWasCalled.fulfill() + } + wait(for: [credentialProviderShutdownWasCalled], timeout: 120); + } /*=============================================================== diff --git a/aws-common-runtime/aws-c-io b/aws-common-runtime/aws-c-io index d19f6a5ff..6c384957b 160000 --- a/aws-common-runtime/aws-c-io +++ b/aws-common-runtime/aws-c-io @@ -1 +1 @@ -Subproject commit d19f6a5ff081582a391002e0d57bbe9d8fd471d3 +Subproject commit 6c384957b2de79b39fe945dfb8c65100041a2005 diff --git a/builder.json b/builder.json index 7432097bf..d4e6b391f 100644 --- a/builder.json +++ b/builder.json @@ -8,7 +8,7 @@ ], "run_tests": true, "test_steps": [ - "crt-ci-prep", + "crt-ci-prep-xcodebuild", "xcode-tests" ], "hosts": {