iosswiftmqtt

MQTT Over WebSocket and SSL connection is not working in iOS Swift


Currently we've 2 iOS Apps, one developed with React-Native and another with Swift. Now the problem i'm facing is whenever i tried to connect to the Cloud-MQTT with WebSocket/SSL port using mobile data it's getting auto disconnect in iOS Swift APP(If iPhone device and Raspberry device connected to the same network then it's working fine.), the React-Native app is working fine in all the scenarios.

And if i use Plain MQTT Port then connection is working fine using mobile data. But problem is Raspberry device is providing connection credentials during Login. And Gateway only giving WebSocket and SSL port of Device. And all the device will have different credentials so applying static value for port will not work.

MQTT Cloud Broker
Server      driver.cloudmqtt.com
User :      xxxxxx
Password:   xxxxxx
Port:       18658 //Only this port is working
SSL Port:       28658
Websockets Port (TLS only):   38658
Mosquitto version:     1.5.7 
 func connect(host: String, port: Int, username: String?, password: String?, cleanSession: Bool) {
        
        guard !host.isEmpty else {
            delegate?.onMqttDisconnected()
            return
        }
        
        status = .connecting
        let clientId = "CONNECTED_GEAR-" + String(ProcessInfo().processIdentifier)
        
        if NetworkMonitor.shared.connection == .wifi {
            mqttClient = CocoaMQTT(clientID: clientId, host: host, port: UInt16(port))
            if let mqttClient = mqttClient {
                UserManager.shared.isConnected = true
                mqttClient.username = username
                mqttClient.password = password
                mqttClient.keepAlive = UInt16(60)
                mqttClient.cleanSession = cleanSession
                mqttClient.backgroundOnSocket = true
                mqttClient.delegate = self
               
               _ = mqttClient.connect()
            } else {
                
                UserManager.shared.isConnected = false
                delegate?.onMqttError(message: "Mqtt initialization error")
                status = .error
            }
        } else {
            
                 let socket = CocoaMQTTWebSocket()
               
                 socket.enableSSL = false
        
                mqttClient = CocoaMQTT(clientID: clientId, host: host, port: UInt16(port), socket: socket)
                if let mqttClient = mqttClient {
                    UserManager.shared.isConnected = true
                    mqttClient.username = username
                    mqttClient.password = password
                    mqttClient.keepAlive = UInt16(60)
                    mqttClient.cleanSession = cleanSession
                    

                    mqttClient.logLevel = .debug
                    mqttClient.autoReconnect = false
                    mqttClient.allowUntrustCACertificate = true
                    mqttClient.backgroundOnSocket = true
                    mqttClient.delegate = self

                    _ = mqttClient.connect()
                } else {
                    
                    UserManager.shared.isConnected = false
                    delegate?.onMqttError(message: "Mqtt initialization error")
                    status = .error
                }
        }
       
    }

When i set enableSSL = false

Network reachable via Cellular 
External Host: driver.cloudmqtt.com
External Port: 38658
External User: xxxxxxx
External Password: xxxxxxx
CocoaMQTT(debug): ping
CocoaMQTT(debug): SEND: PING
CocoaMQTT(debug): =========================MQTT 3.1.1=========================
CocoaMQTT(debug): packetFixedHeaderType 192
CocoaMQTT(debug): remainingLen(len: len) [0]
CocoaMQTT(debug): variableHeader []
CocoaMQTT(debug): payload []
CocoaMQTT(debug): =============================================================
CocoaMQTT(info): Connected to 3.83.156.245 : 38658
CocoaMQTT(info): Enable backgrounding socket successfully
CocoaMQTT(debug): SEND: CONNECT(id: CONNECTED_GEAR-933, username: xxxxxxx, password: xxxxxxx, keepAlive : 60, cleansess: false)
CocoaMQTT(debug): =========================MQTT 3.1.1=========================
CocoaMQTT(debug): packetFixedHeaderType 16
CocoaMQTT(debug): remainingLen(len: len) [54]
CocoaMQTT(debug): variableHeader [0, 4, 77, 81, 84, 84, 4, 192, 0, 60]
CocoaMQTT(debug): payload [0, 18, 67, 79, 78, 78, 69, 67, 84, 69, 68, 95, 71, 69, 65, 82, 45, 57, 51, 51, 0, 8, 104, 101, 121, 103, 108, 105, 104, 103, 0, 12, 71, 103, 107, 106, 74, 101, 110, 81, 114, 119, 120, 48]
CocoaMQTT(debug): =============================================================
CocoaMQTT(debug): socket wrote data -192
CocoaMQTT(debug): socket wrote data 0
CocoaMQTT(debug): socket disconnected
Disconnect: Socket closed by remote peer

When i set enableSSL = true

External Host: driver.cloudmqtt.com
External Port: 38658
External User: xxxxxxx
External Password: xxxxxxx
CocoaMQTT(debug): ping
CocoaMQTT(debug): SEND: PING
CocoaMQTT(debug): =========================MQTT 3.1.1=========================
CocoaMQTT(debug): packetFixedHeaderType 192
CocoaMQTT(debug): remainingLen(len: len) [0]
CocoaMQTT(debug): variableHeader []
CocoaMQTT(debug): payload []
CocoaMQTT(debug): =============================================================
CocoaMQTT(info): Connected to 3.83.156.245 : 38658
CocoaMQTT(info): Enable backgrounding socket successfully
CocoaMQTT(debug): socket wrote data -192
CocoaMQTT(debug): socket disconnected
Disconnect: The operation couldn’t be completed. (kCFStreamErrorDomainSSL error -9806.)
CocoaMQTT(info): Try reconnect to server after 1s

CocoaMQTT(info): Connected to 3.83.156.245 : 38658
CocoaMQTT(info): Enable backgrounding socket successfully
CocoaMQTT(debug): Call the SSL/TLS manually validating function

Note:- I've set AllowArbitraryLoads = true in info.plist

FYI:- Due to GeoFence issue in React-Native, we've developed applications in Native Swift and Kotlin


Solution

  • At last i found a solution, and now MQTT Connection and WebSocket Connection over any network type working fine.

     func connect(host: String, port: Int, username: String?, password: String?, cleanSession: Bool) {
           
            guard !host.isEmpty else {
                delegate?.onMqttDisconnected()
                return
            }
            
            status = .connecting
            let clientId = "CONNECTED_GEAR-" + String(ProcessInfo().processIdentifier)
            
    
            //During connection without HomeNetwork wifi, required to connect with WebSocket. That's the main reason to implement different connection request for Network type.
            if NetworkMonitor.shared.connection != .wifi {
               
                
                let websocket = CocoaMQTTWebSocket(uri: "/ws")
                
                mqttClient = CocoaMQTT(clientID: clientId, host: host, port: UInt16(port), socket: websocket)
                
                if let mqttClient = mqttClient {
                    
                    UserManager.shared.isConnected = true
                    mqttClient.enableSSL = true
                    mqttClient.username = username
                    mqttClient.password = password
                    mqttClient.keepAlive = UInt16(60)
                    mqttClient.cleanSession = cleanSession
                    mqttClient.backgroundOnSocket = true
                    mqttClient.allowUntrustCACertificate = true
                    
                    mqttClient.willMessage = CocoaMQTTMessage(topic: "/will", string: "dieout")
                    mqttClient.autoReconnect = true
                    mqttClient.logLevel = .debug
                    
                    mqttClient.delegate = self
                    _ = mqttClient.connect()
                } else {
                    
                    UserManager.shared.isConnected = false
                    delegate?.onMqttError(message: "Mqtt initialization error")
                    status = .error
                }
                
            } else {
                
                mqttClient = CocoaMQTT(clientID: clientId, host: host, port: UInt16(port))
                
                if let mqttClient = mqttClient {
                    UserManager.shared.isConnected = true
                    mqttClient.delegate = self
                    mqttClient.username = username
                    mqttClient.password = password
                    mqttClient.keepAlive = UInt16(60)
                    mqttClient.cleanSession = cleanSession
                    mqttClient.backgroundOnSocket = true
                    mqttClient.logLevel = .error
                    mqttClient.deliverTimeout = 15.00
                    mqttClient.autoReconnect = true
                    mqttClient.willMessage = CocoaMQTTMessage(topic: "/will", string: "dieout")
                   _ = mqttClient.connect()
                } else {
                    
                    UserManager.shared.isConnected = false
                    delegate?.onMqttError(message: "Mqtt initialization error")
                    status = .error
                }
            }
            
           
        }
    

    Note:- I've asked to chatGPT, and got suggestion to add this 2 methods

    extension MqttManager: CocoaMQTTDelegate {
        
        func mqtt(_ mqtt: CocoaMQTT, didReceive trust: SecTrust, completionHandler: @escaping (Bool) -> Void) {
                // Implement your custom SSL validation logic here.
                // For example, you might want to always trust the certificate for testing purposes:
                completionHandler(true)
            }
    
            func mqtt(_ mqtt: CocoaMQTT, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
                if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
                    if let serverTrust = challenge.protectionSpace.serverTrust {
                        completionHandler(.useCredential, URLCredential(trust: serverTrust))
                        return
                    }
                }
                completionHandler(.performDefaultHandling, nil)
            }
        
        func mqttUrlSession(_ mqtt: CocoaMQTT, didReceiveTrust trust: SecTrust, didReceiveChallenge challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
            
            print("\(#function), \n result:- \(challenge.debugDescription)")
        }
        
    
    <key>NSAppTransportSecurity</key>
        <dict>
            <key>NSExceptionDomains</key>
            <dict>
                <key>my-domain.cloudmqtt.com</key>
                <dict>
                    <key>NSAllowsArbitraryLoads</key>
                    <true/>
                    <key>NSExceptionAllowsInsecureHTTPLoads</key>
                    <true/>
                    <key>NSIncludesSubdomains</key>
                    <true/>
                    <key>NSTemporaryExceptionRequiresForwardSecrecy</key>
                    <false/>
                </dict>
            </dict>
        </dict>