aws-amplify / aws-sdk-ios

AWS SDK for iOS. For more information, see our web site:
https://aws-amplify.github.io/docs
Other
1.68k stars 885 forks source link

MQTT IoT data manager connection status occasionally registers as AWSIoTMQTTStatusUnknown, connectUsingWebSocket does not return a status callback #3564

Closed clipchak closed 3 years ago

clipchak commented 3 years ago

Describe the bug: In iOS 13+, when our application has not been built and ran for a certain amount of time, AWS IoT Data Manager occasionally has a connection status of AWSIoTMQTTStatusUnknown. MQTT connectUsingWebSocket then does not return any status callback. This issue only occurs after our application has not been built and ran for an extended amount of time and never occurs on successive runs of our application.

Which AWS service(s) are affected? AWS-iOT

Expected behavior: AWSIoTDataManager should register and connect to web socket with a status that is connected and not unknown. Calling connectUsingWebSocket should register an mqttEventCallback that prints connecting and then connected. Instead, occasionally connectUsingWebSocket does not return any mqttEventCallback and the connection status of our data manager ends up as AWSIoTMQTTStatusUnknown.

Environment: SDK Version: 2.24 (latest version) Dependency Manager: Pods Swift Version: 5.0+ Xcode Version: 12+

Device Information: Device: iPhone iOS Version: 13+

Sample Code

/// Registers a configuration to IoT.
internal func registerToAwsIot() {
        // Create AWS credentials and configuration
        let credentials = AWSCognitoCredentialsProvider(regionType:.USEast2, identityPoolId: "us-east-2: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx")
        let configuration = AWSServiceConfiguration(region:.USEast2, credentialsProvider: credentials)
        // Initialize AWS IoT And IoT DataManager
        AWSIoT.register(with: configuration!, forKey: "kAWSIoT")
        let iotEndpoint = AWSEndpoint(urlString: "https://xxxxxxxxxxxx-ats.iot.us-east-2.amazonaws.com")
        let iotDataConfiguration = AWSServiceConfiguration(
                region: .USEast2,
                endpoint: iotEndpoint,
                credentialsProvider: credentials)
        AWSIoTDataManager.register(with: iotDataConfiguration!, forKey: "kDataManager")
}

/// Creates a connection to IoT using web sockets.
internal func connectingToAwsIoT() {
        let uuid = UUID().uuidString
        let dataManager = AWSIoTDataManager(forKey: "kDataManager")
        let response =  dataManager.connectUsingWebSocket(withClientId: uuid, cleanSession: true, statusCallback: self.mqttEventCallback(_:))
        if response != true {
            logDebugger("Error connecting to IoT Data manager.")
        }
 }

/// A callback used to check what the status of MQTT is.
private func mqttEventCallback(_ status: AWSIoTMQTTStatus) {
        switch status {
        case .connecting:
            logDebugger("Connecting")
        case .connected:
            logDebugger("Connected")
        case .connectionRefused:
            logDebugger("Connection Refused")
        case .connectionError:
            logDebugger("Connection Error")
        case .disconnected:
            logDebugger("Disconnected")
        case .protocolError:
            logDebugger("Protocol Error")
        case .unknown:
            logDebugger("Unknown Error")
        @unknown default:
            logDebugger("Default Status")
            break
        }
 }

Example of retrieving data manager and getting a connection status

//retrieve data manager
let dataManager = AWSIoTDataManager(forKey: "kDataManager")

//returns 0 (unknown) when the app has not been ran for awhile, making the app unable to publish to a topic
print("\(dataManager.getConnectionStatus().rawValue)")
ruiguoamz commented 3 years ago

Hi @clipchak Thanks for reaching out

  1. Could you provide us how do you configure IoT in AWS IoT console?
  2. And also a more specific reproduction steps as in format below

To Reproduce Steps to reproduce the behavior:

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. See error
clipchak commented 3 years ago

Hi @ruiguoamz thank you for the reply. Could you specify what you mean when you ask how IoT is configured in AWS IoT console?

Also, to reproduce the issue we are seeing, this issue occurs when our application hasn't been built and ran for an unspecified amount of time (at least 30 minutes). To describe this issue more specifically, the registerToAwsIoT() and connectingToAwsIoT() methods in my post above are called in our application after following a series of methods:

/// Called once to retrieve a user.
    private func initializeAwsMobileClient() {
        AWSMobileClient.default().initialize { (userState, error) in
            if let userState = userState {
                switch userState {
                    case .signedIn:
                        CognitoService.shared.configureUserAndIdentityPools()
                        self.connectToAPIGatewayOnSignIn()
                    ...........
    }

We call this above method to initialize the AWS mobile client. Once the user is signed in, we configure the user and identity pools with this following method:

// Creates and configures credentials and configuration for cognito.
    public func configureUserAndIdentityPools() {
        // set up user pool
        let userPoolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: AWSConstants.CLIENT_ID, clientSecret: AWSConstants.CLIENT_SECRET, poolId: AWSConstants.USER_POOL_ID)
        let pool = AWSCognitoIdentityUserPool(forKey: "UserPool")
        // create credentials and configuration
        let credentials = AWSCognitoCredentialsProvider(regionType: .USEast2, identityPoolId: AWSConstants.IDENTITY_POOL_ID, unauthRoleArn: AWSConstants.UNAUTH_ROLE_ARN, authRoleArn: AWSConstants.AUTH_ROLE_ARN, identityProviderManager: pool)
        let configuration = AWSServiceConfiguration(region:.USEast2, credentialsProvider: credentials)
        userCredentials = credentials
        userConfiguration = configuration
        // register user pool
        AWSCognitoIdentityUserPool.register(with: configuration, userPoolConfiguration: userPoolConfiguration, forKey: "UserPool")
        AWSServiceManager.default()?.defaultServiceConfiguration = configuration
    }
/// Creates the connection to API Gateway if user is signedIn.
    private func connectToAPIGatewayOnSignIn() {
        // Check user attributes
        CognitoService.shared.checkAndUpdateUserCustomAttributes { (result) in
            switch result {
            case .success(()):
                AWSNetworkManager.shared.registerToAwsIot()
                AWSNetworkManager.shared.connectingToAwsIoT()
            .........
        ..........
           }
      }
}

In the successful case, the registerToAwsIot and connectingToAwsIot methods are called

/// Registers a configuration to IoT.
    internal func registerToAwsIot() {
        guard let userConfiguration = CognitoService.shared.userConfiguration else { logDebugger("User Configuration Nil."); return }
        guard let userCredentials = CognitoService.shared.userCredentials else { logDebugger("User Credentials Nil."); return }
        // Initialising AWS IoT And IoT DataManager
        AWSIoT.register(with: userConfiguration, forKey: "kAWSIoT")
        let iotEndpoint = AWSEndpoint(urlString: MQTT.ENDPOINT)
        let iotDataConfiguration = AWSServiceConfiguration(
                region: .USEast2,
                endpoint: iotEndpoint,
                credentialsProvider: userCredentials)
        AWSIoTDataManager.register(with: iotDataConfiguration!, forKey: "kDataManager")
    }

    /// Creates a connection to IoT using web sockets.
    internal func connectingToAwsIoT() {
        let uuid = UUID().uuidString
        let dataManager = AWSIoTDataManager(forKey: "kDataManager")
        let response =  dataManager.connectUsingWebSocket(withClientId: uuid, cleanSession: true, statusCallback: self.mqttEventCallback(_:))
        if response != true {
            logDebugger("Error connecting to IoT Data manager.")
        }
    }
clipchak commented 3 years ago

Hi @ruiguoamz ,

After further investigation, the issue seems to be regarding sessions. When the app has been sitting for 15 minutes or so and the session expires, the calls to IoT are ignored. However if the app is terminated or a user logs out and logs back in, it works perfectly fine. The solutions i've tried were trying to use the AWSMobileClient.default.initialize() method which worked when ApiGateway had a similar issue. In the case of IoT it does not. The reason we know it is not working is because the mqttCallback method isn't being called, when it is called IoT is connected. There is also no error's being printed or resulting in opening of the application. Is there some method that has to be implemented before calling or initializing IoT in order to make it work when there a session expired?

Steps to Reproduce this error: Have the application sitting long enough where the session expires (about 15 or so minutes, 20 to be safe) Try connecting to IoT and see if the mqttCallback method is called If IoT is setup try making a call to it and monitoring if it is being sent

After the first time of initializing, any subsequent times it will succeed.

If you have further questions please ask. Thank You!

clipchak commented 3 years ago

Closed, the issue was that occasionally our data manager was being retrieved before it was properly initialized.