jsdw_ios/Pods/CocoaMQTT/Source/CocoaMQTT5.swift

904 lines
32 KiB
Swift

//
// CocoaMQTT5.swift
// CocoaMQTT5
//
// Created by Feng Lee<feng@eqmtt.io> on 14/8/3.
// Copyright (c) 2015 emqx.io. All rights reserved.
//
import Foundation
import MqttCocoaAsyncSocket
/**
* Connection State
*/
@objc public enum CocoaMQTTConnState: UInt8, CustomStringConvertible {
case disconnected = 0
case connecting
case connected
public var description: String {
switch self {
case .connecting: return "connecting"
case .connected: return "connected"
case .disconnected: return "disconnected"
}
}
}
/// CocoaMQTT5 Delegate
@objc public protocol CocoaMQTT5Delegate {
///
func mqtt5(_ mqtt5: CocoaMQTT5, didConnectAck ack: CocoaMQTTCONNACKReasonCode, connAckData: MqttDecodeConnAck?)
///
func mqtt5(_ mqtt5: CocoaMQTT5, didPublishMessage message: CocoaMQTT5Message, id: UInt16)
///
func mqtt5(_ mqtt5: CocoaMQTT5, didPublishAck id: UInt16, pubAckData: MqttDecodePubAck?)
///
func mqtt5(_ mqtt5: CocoaMQTT5, didPublishRec id: UInt16, pubRecData: MqttDecodePubRec?)
///
func mqtt5(_ mqtt5: CocoaMQTT5, didReceiveMessage message: CocoaMQTT5Message, id: UInt16, publishData: MqttDecodePublish?)
///
func mqtt5(_ mqtt5: CocoaMQTT5, didSubscribeTopics success: NSDictionary, failed: [String], subAckData: MqttDecodeSubAck?)
///
func mqtt5(_ mqtt5: CocoaMQTT5, didUnsubscribeTopics topics: [String], unsubAckData: MqttDecodeUnsubAck?)
///
func mqtt5(_ mqtt5: CocoaMQTT5, didReceiveDisconnectReasonCode reasonCode: CocoaMQTTDISCONNECTReasonCode)
///
func mqtt5(_ mqtt5: CocoaMQTT5, didReceiveAuthReasonCode reasonCode: CocoaMQTTAUTHReasonCode)
///
func mqtt5DidPing(_ mqtt5: CocoaMQTT5)
///
func mqtt5DidReceivePong(_ mqtt5: CocoaMQTT5)
///
func mqtt5DidDisconnect(_ mqtt5: CocoaMQTT5, withError err: Error?)
/// Manually validate SSL/TLS server certificate.
///
/// This method will be called if enable `allowUntrustCACertificate`
@objc optional func mqtt5(_ mqtt5: CocoaMQTT5, didReceive trust: SecTrust, completionHandler: @escaping (Bool) -> Void)
@objc optional func mqtt5UrlSession(_ mqtt: CocoaMQTT5, didReceiveTrust trust: SecTrust, didReceiveChallenge challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
///
@objc optional func mqtt5(_ mqtt5: CocoaMQTT5, didPublishComplete id: UInt16, pubCompData: MqttDecodePubComp?)
///
@objc optional func mqtt5(_ mqtt5: CocoaMQTT5, didStateChangeTo state: CocoaMQTTConnState)
/// Called when auto-reconnect schedules a reconnect attempt after an unexpected disconnect.
@objc optional func mqtt5(_ mqtt5: CocoaMQTT5, didScheduleReconnect attemptCount: UInt, after interval: UInt16)
}
/// set mqtt version to 5.0
public func setMqtt5Version() {
if let storage = CocoaMQTTStorage() {
storage.setMQTTVersion("5.0")
}
}
/**
* Blueprint of the MQTT Client
*/
protocol CocoaMQTT5Client {
/* Basic Properties */
var host: String { get set }
var port: UInt16 { get set }
var clientID: String { get }
var username: String? {get set}
var password: String? {get set}
var cleanSession: Bool {get set}
var keepAlive: UInt16 {get set}
var willMessage: CocoaMQTT5Message? {get set}
var connectProperties: MqttConnectProperties? {get set}
var authProperties: MqttAuthProperties? {get set}
/* Basic Properties */
/* CONNNEC/DISCONNECT */
func connect() -> Bool
func connect(timeout: TimeInterval) -> Bool
func disconnect()
func ping()
/* CONNNEC/DISCONNECT */
/* PUBLISH/SUBSCRIBE */
func subscribe(_ topic: String, qos: CocoaMQTTQoS)
func subscribe(_ topics: [MqttSubscription])
func unsubscribe(_ topic: String)
func unsubscribe(_ topics: [MqttSubscription])
func publish(_ topic: String, withString string: String, qos: CocoaMQTTQoS, DUP: Bool, retained: Bool, properties: MqttPublishProperties) -> Int
func publish(_ message: CocoaMQTT5Message, DUP: Bool, retained: Bool, properties: MqttPublishProperties) -> Int
/* PUBLISH/SUBSCRIBE */
}
/// MQTT Client
///
/// - Note: MGCDAsyncSocket need delegate to extend NSObject
public class CocoaMQTT5: NSObject, CocoaMQTT5Client {
public weak var delegate: CocoaMQTT5Delegate?
private var version = "5.0"
public var host = "localhost"
public var port: UInt16 = 1883
public var clientID: String
public var username: String?
public var password: String?
/// Clean Session flag. Default is true
///
/// - TODO: What's behavior each Clean Session flags???
public var cleanSession = true
/// Setup a **Last Will Message** to client before connecting to broker
public var willMessage: CocoaMQTT5Message?
/// Enable backgounding socket if running on iOS platform. Default is true
///
/// - Note:
public var backgroundOnSocket: Bool {
get { return (self.socket as? CocoaMQTTSocket)?.backgroundOnSocket ?? true }
set { (self.socket as? CocoaMQTTSocket)?.backgroundOnSocket = newValue }
}
/// Delegate Executed queue. Default is `DispatchQueue.main`
///
/// The delegate/closure callback function will be committed asynchronously to it
public var delegateQueue = DispatchQueue.main
@ConcurrentAtomic(wrappedValue: CocoaMQTTConnState.disconnected, label: "CocoaMQTT5.connState")
public var connState {
didSet {
__delegate_queue {
self.delegate?.mqtt5?(self, didStateChangeTo: self.connState)
self.didChangeState(self, self.connState)
}
}
}
// deliver
private var deliver = CocoaMQTTDeliver()
/// Re-deliver the un-acked messages
public var deliverTimeout: Double {
get { return deliver.retryTimeInterval }
set { deliver.retryTimeInterval = newValue }
}
/// Message queue size. default 1000
///
/// The new publishing messages of Qos1/Qos2 will be drop, if the queue is full
public var messageQueueSize: UInt {
get { return deliver.mqueueSize }
set { deliver.mqueueSize = newValue }
}
/// In-flight window size. default 10
public var inflightWindowSize: UInt {
get { return deliver.inflightWindowSize }
set { deliver.inflightWindowSize = newValue }
}
/// Keep alive time interval
public var keepAlive: UInt16 = 60
private var aliveTimer: CocoaMQTTTimer?
/// Enable auto-reconnect mechanism
public var autoReconnect = false
/// Reconnect time interval
///
/// - note: This value will be increased with `autoReconnectTimeInterval *= 2`
/// if reconnect failed
public var autoReconnectTimeInterval: UInt16 = 1 // starts from 1 second
/// Maximum auto reconnect time interval
///
/// The timer starts from `autoReconnectTimeInterval` second and grows exponentially until this value
/// After that, it uses this value for subsequent requests.
public var maxAutoReconnectTimeInterval: UInt16 = 128 // 128 seconds
/// 3.1.2.11 CONNECT Properties
public var connectProperties: MqttConnectProperties?
/// 3.15.2.2 AUTH Properties
public var authProperties: MqttAuthProperties?
/// Auto-reconnect backoff interval in seconds for the current reconnect cycle.
///
/// This value is advanced for the next reconnect attempt while auto-reconnect is active,
/// and resets to `0` when auto-reconnect is inactive.
public private(set) var reconnectTimeInterval: UInt16 = 0
/// Number of reconnect attempts scheduled in the current auto-reconnect cycle.
///
/// The value resets to `0` after a successful connection or expected disconnect.
public private(set) var reconnectAttemptCount: UInt = 0
private var autoReconnTimer: CocoaMQTTTimer?
private var is_internal_disconnected = false
/// Console log level
public var logLevel: CocoaMQTTLoggerLevel {
get {
return CocoaMQTTLogger.logger.minLevel
}
set {
CocoaMQTTLogger.logger.minLevel = newValue
}
}
/// Enable SSL connection
public var enableSSL: Bool {
get { return self.socket.enableSSL }
set { socket.enableSSL = newValue }
}
///
public var sslSettings: [String: NSObject]? {
get { return (self.socket as? CocoaMQTTSocket)?.sslSettings ?? nil }
set { (self.socket as? CocoaMQTTSocket)?.sslSettings = newValue }
}
/// Allow self-signed ca certificate.
///
/// Default is false
public var allowUntrustCACertificate: Bool {
get { return (self.socket as? CocoaMQTTSocket)?.allowUntrustCACertificate ?? false }
set { (self.socket as? CocoaMQTTSocket)?.allowUntrustCACertificate = newValue }
}
/// The subscribed topics in current communication
public var subscriptions = ThreadSafeDictionary<String, CocoaMQTTQoS>(label: "subscriptions")
fileprivate var subscriptionsWaitingAck = ThreadSafeDictionary<UInt16, [MqttSubscription]>(label: "subscriptionsWaitingAck")
fileprivate var unsubscriptionsWaitingAck = ThreadSafeDictionary<UInt16, [MqttSubscription]>(label: "unsubscriptionsWaitingAck")
/// Sending messages
fileprivate var sendingMessages: [UInt16: CocoaMQTT5Message] = [:]
/// message id counter
private var _msgid: UInt16 = 0
fileprivate var socket: CocoaMQTTSocketProtocol
fileprivate var reader: CocoaMQTTReader?
// Closures
public var didConnectAck: (CocoaMQTT5, CocoaMQTTCONNACKReasonCode, MqttDecodeConnAck?) -> Void = { _, _, _ in }
public var didPublishMessage: (CocoaMQTT5, CocoaMQTT5Message, UInt16) -> Void = { _, _, _ in }
public var didPublishAck: (CocoaMQTT5, UInt16, MqttDecodePubAck?) -> Void = { _, _, _ in }
public var didPublishRec: (CocoaMQTT5, UInt16, MqttDecodePubRec?) -> Void = { _, _, _ in }
public var didReceiveMessage: (CocoaMQTT5, CocoaMQTT5Message, UInt16, MqttDecodePublish?) -> Void = { _, _, _, _ in }
public var didSubscribeTopics: (CocoaMQTT5, NSDictionary, [String], MqttDecodeSubAck?) -> Void = { _, _, _, _ in }
public var didUnsubscribeTopics: (CocoaMQTT5, [String], MqttDecodeUnsubAck?) -> Void = { _, _, _ in }
public var didPing: (CocoaMQTT5) -> Void = { _ in }
public var didReceivePong: (CocoaMQTT5) -> Void = { _ in }
public var didDisconnect: (CocoaMQTT5, Error?) -> Void = { _, _ in }
public var didDisconnectReasonCode: (CocoaMQTT5, CocoaMQTTDISCONNECTReasonCode) -> Void = { _, _ in }
public var didAuthReasonCode: (CocoaMQTT5, CocoaMQTTAUTHReasonCode) -> Void = { _, _ in }
public var didReceiveTrust: (CocoaMQTT5, SecTrust, @escaping (Bool) -> Swift.Void) -> Void = { _, _, _ in }
public var didCompletePublish: (CocoaMQTT5, UInt16, MqttDecodePubComp?) -> Void = { _, _, _ in }
public var didChangeState: (CocoaMQTT5, CocoaMQTTConnState) -> Void = { _, _ in }
public var didScheduleReconnect: (CocoaMQTT5, UInt, UInt16) -> Void = { _, _, _ in }
/// Initial client object
///
/// - Parameters:
/// - clientID: Client Identifier
/// - host: The MQTT broker host domain or IP address. Default is "localhost"
/// - port: The MQTT service port of host. Default is 1883
public init(clientID: String, host: String = "localhost", port: UInt16 = 1883, socket: CocoaMQTTSocketProtocol = CocoaMQTTSocket()) {
self.clientID = clientID
self.host = host
self.port = port
self.socket = socket
super.init()
deliver.delegate = self
if let storage = CocoaMQTTStorage() {
storage.setMQTTVersion("5.0")
} else {
printWarning("Localstorage initial failed for key: \(clientID)")
}
}
deinit {
aliveTimer?.suspend()
autoReconnTimer?.suspend()
socket.setDelegate(nil, delegateQueue: nil)
socket.disconnect()
}
fileprivate func send(_ frame: Frame, tag: Int = 0) {
printDebug("SEND: \(frame)")
let data = frame.bytes(version: version)
socket.write(Data(bytes: data, count: data.count), withTimeout: 5, tag: tag)
}
fileprivate func sendConnectFrame() {
var connect = FrameConnect(clientID: clientID)
connect.keepAlive = keepAlive
connect.username = username
connect.password = password
connect.willMsg5 = willMessage
connect.cleansess = cleanSession
connect.connectProperties = connectProperties
send(connect)
reader!.start()
}
fileprivate func nextMessageID() -> UInt16 {
if _msgid == UInt16.max {
_msgid = 0
}
_msgid += 1
return _msgid
}
fileprivate func puback(_ type: FrameType, msgid: UInt16) {
switch type {
case .puback:
send(FramePubAck(msgid: msgid, reasonCode: CocoaMQTTPUBACKReasonCode.success))
case .pubrec:
send(FramePubRec(msgid: msgid, reasonCode: CocoaMQTTPUBRECReasonCode.success))
case .pubcomp:
send(FramePubComp(msgid: msgid, reasonCode: CocoaMQTTPUBCOMPReasonCode.success))
default: return
}
}
/// Connect to MQTT broker
///
/// - Returns:
/// - Bool: It indicates whether successfully calling socket connect function.
/// Not yet established correct MQTT session
public func connect() -> Bool {
return connect(timeout: -1)
}
/// Connect to MQTT broker
/// - Parameters:
/// - timeout: Connect timeout
/// - Returns:
/// - Bool: It indicates whether successfully calling socket connect function.
/// Not yet established correct MQTT session
public func connect(timeout: TimeInterval) -> Bool {
socket.setDelegate(self, delegateQueue: delegateQueue)
reader = CocoaMQTTReader(socket: socket, delegate: self)
do {
if timeout > 0 {
try socket.connect(toHost: self.host, onPort: self.port, withTimeout: timeout)
} else {
try socket.connect(toHost: self.host, onPort: self.port)
}
delegateQueue.async { [weak self] in
guard let self = self else { return }
self.connState = .connecting
}
return true
} catch let error as NSError {
printError("socket connect error: \(error.description)")
return false
}
}
/// Send a DISCONNECT packet to the broker then close the connection
///
/// - Note: Only can be called from outside.
/// This closes the connection expectedly, so auto-reconnect will not run.
public func disconnect() {
expected_disconnect(reasonCode: .normalDisconnection)
}
public func disconnect(reasonCode: CocoaMQTTDISCONNECTReasonCode, userProperties: [String: String] ) {
expected_disconnect(reasonCode: reasonCode, userProperties: userProperties)
}
/// Disconnect unexpectedly.
/// This keeps auto-reconnect behavior enabled.
func internal_disconnect() {
is_internal_disconnected = false
socket.disconnect()
}
func internal_disconnect_withProperties(reasonCode: CocoaMQTTDISCONNECTReasonCode, userProperties: [String: String] ) {
expected_disconnect(reasonCode: reasonCode, userProperties: userProperties)
}
private func expected_disconnect(reasonCode: CocoaMQTTDISCONNECTReasonCode, userProperties: [String: String]? = nil) {
is_internal_disconnected = true
var frameDisconnect = FrameDisconnect(disconnectReasonCode: reasonCode)
frameDisconnect.userProperties = userProperties ?? [:]
send(frameDisconnect, tag: -0xE0)
socket.disconnect()
}
/// Send a PING request to broker
public func ping() {
printDebug("ping")
send(FramePingReq(), tag: -0xC0)
__delegate_queue {
self.delegate?.mqtt5DidPing(self)
self.didPing(self)
}
}
/// Publish a message to broker
///
/// - Parameters:
/// - topic: Topic Name. It can not contain '#', '+' wildcards
/// - string: Payload string
/// - qos: Qos. Default is Qos1
/// - retained: Retained flag. Mark this message is a retained message. default is false
/// - properties: Publish Properties
/// - Returns:
/// - 0 will be returned, if the message's qos is qos0
/// - 1-65535 will be returned, if the messages's qos is qos1/qos2
/// - -1 will be returned, if the messages queue is full
@discardableResult
public func publish(_ topic: String, withString string: String, qos: CocoaMQTTQoS = .qos1, DUP: Bool = false, retained: Bool = false, properties: MqttPublishProperties) -> Int {
assert(!(DUP && qos == .qos0), "DUP=true with QoS0 is invalid for MQTT PUBLISH.")
guard !(DUP && qos == .qos0) else {
printError("Invalid PUBLISH flags: DUP=true requires QoS1 or QoS2.")
return -1
}
let message = CocoaMQTT5Message(topic: topic, string: string, qos: qos, retained: retained)
return publish(message, DUP: DUP, retained: retained, properties: properties)
}
/// Publish a message to broker
///
/// - Parameters:
/// - message: Message
/// - properties: Publish Properties
@discardableResult
public func publish(_ message: CocoaMQTT5Message, DUP: Bool = false, retained: Bool = false, properties: MqttPublishProperties) -> Int {
assert(!(DUP && message.qos == .qos0), "DUP=true with QoS0 is invalid for MQTT PUBLISH.")
guard !(DUP && message.qos == .qos0) else {
printError("Invalid PUBLISH flags: DUP=true requires QoS1 or QoS2.")
return -1
}
let msgid: UInt16
if message.qos == .qos0 {
msgid = 0
} else {
msgid = nextMessageID()
}
printDebug("message.topic \(message.topic ) = message.payload \(message.payload)")
var frame = FramePublish(topic: message.topic,
payload: message.payload,
qos: message.qos,
msgid: msgid)
frame.qos = message.qos
frame.dup = DUP
frame.publishProperties = properties
frame.retained = message.retained
delegateQueue.async {
self.sendingMessages[msgid] = message
}
// Push frame to deliver message queue
guard deliver.add(frame) else {
delegateQueue.async {
self.sendingMessages.removeValue(forKey: msgid)
}
return -1
}
return Int(msgid)
}
/// Subscribe a `<Topic Name>/<Topic Filter>`
///
/// - Parameters:
/// - topic: Topic Name or Topic Filter
/// - qos: Qos. Default is qos1
public func subscribe(_ topic: String, qos: CocoaMQTTQoS = .qos1) {
let filter = MqttSubscription(topic: topic, qos: qos)
return subscribe([filter])
}
/// Subscribe a lists of topics
///
/// - Parameters:
/// - topics: A list of tuples presented by `(<Topic Names>/<Topic Filters>, Qos)`
public func subscribe(_ topics: [MqttSubscription]) {
let msgid = nextMessageID()
let frame = FrameSubscribe(msgid: msgid, subscriptionList: topics)
send(frame, tag: Int(msgid))
subscriptionsWaitingAck[msgid] = topics
}
/// Subscribe a lists of topics
///
/// - Parameters:
/// - topics: A list of tuples presented by `(<Topic Names>/<Topic Filters>, Qos)`
/// - packetIdentifier: SUBSCRIBE Variable Header
/// - subscriptionIdentifier: Subscription Identifier
/// - userProperty: User Property
public func subscribe(_ topics: [MqttSubscription], packetIdentifier: UInt16? = nil, subscriptionIdentifier: UInt32? = nil, userProperty: [String: String] = [:]) {
let msgid = nextMessageID()
let frame = FrameSubscribe(msgid: msgid, subscriptionList: topics, packetIdentifier: packetIdentifier, subscriptionIdentifier: subscriptionIdentifier, userProperty: userProperty)
send(frame, tag: Int(msgid))
subscriptionsWaitingAck[msgid] = topics
}
/// Unsubscribe a Topic
///
/// - Parameters:
/// - topic: A Topic Name or Topic Filter
public func unsubscribe(_ topic: String) {
let filter = MqttSubscription(topic: topic)
return unsubscribe([filter])
}
/// Unsubscribe a list of topics
///
/// - Parameters:
/// - topics: A list of `<Topic Names>/<Topic Filters>`
public func unsubscribe(_ topics: [MqttSubscription]) {
let msgid = nextMessageID()
let frame = FrameUnsubscribe(msgid: msgid, topics: topics)
unsubscriptionsWaitingAck[msgid] = topics
send(frame, tag: Int(msgid))
}
/// Authentication exchange
///
///
public func auth(reasonCode: CocoaMQTTAUTHReasonCode, authProperties: MqttAuthProperties) {
printDebug("auth")
let frame = FrameAuth(reasonCode: reasonCode, authProperties: authProperties)
send(frame)
}
}
// MARK: CocoaMQTTDeliverProtocol
extension CocoaMQTT5: CocoaMQTTDeliverProtocol {
func deliver(_ deliver: CocoaMQTTDeliver, wantToSend frame: Frame) {
if let publish = frame as? FramePublish {
let msgid = publish.msgid
var message: CocoaMQTT5Message?
if let sendingMessage = sendingMessages[msgid] {
message = sendingMessage
// printError("Want send \(frame), but not found in CocoaMQTT cache")
} else {
message = CocoaMQTT5Message(topic: publish.topic, payload: publish.payload())
}
send(publish, tag: Int(msgid))
if let message = message {
self.delegate?.mqtt5(self, didPublishMessage: message, id: msgid)
self.didPublishMessage(self, message, msgid)
}
} else if let pubrel = frame as? FramePubRel {
// -- Send PUBREL
send(pubrel, tag: Int(pubrel.msgid))
}
}
}
extension CocoaMQTT5 {
func __delegate_queue(_ fun: @escaping () -> Void) {
delegateQueue.async { [weak self] in
guard self != nil else { return }
fun()
}
}
private func prepareAutoReconnectAttempt() {
if reconnectTimeInterval == 0 {
reconnectTimeInterval = min(autoReconnectTimeInterval, maxAutoReconnectTimeInterval)
}
reconnectAttemptCount += 1
}
private func updateAutoReconnectIntervalForNextAttempt() {
let doubledInterval = UInt32(reconnectTimeInterval) * 2
reconnectTimeInterval = UInt16(min(doubledInterval, UInt32(maxAutoReconnectTimeInterval)))
}
private func resetAutoReconnectState() {
reconnectTimeInterval = 0
reconnectAttemptCount = 0
autoReconnTimer = nil
}
private func notifyAutoReconnectScheduled() {
delegate?.mqtt5?(self, didScheduleReconnect: reconnectAttemptCount, after: reconnectTimeInterval)
didScheduleReconnect(self, reconnectAttemptCount, reconnectTimeInterval)
}
}
// MARK: - CocoaMQTTSocketDelegate
extension CocoaMQTT5: CocoaMQTTSocketDelegate {
public func socketConnected(_ socket: CocoaMQTTSocketProtocol) {
sendConnectFrame()
}
public func socket(_ socket: CocoaMQTTSocketProtocol,
didReceive trust: SecTrust,
completionHandler: @escaping (Bool) -> Swift.Void) {
printDebug("Call the SSL/TLS manually validating function")
delegate?.mqtt5?(self, didReceive: trust, completionHandler: completionHandler)
didReceiveTrust(self, trust, completionHandler)
}
public func socketUrlSession(_ socket: CocoaMQTTSocketProtocol, didReceiveTrust trust: SecTrust, didReceiveChallenge challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
printDebug("Call the SSL/TLS manually validating function - socketUrlSession")
delegate?.mqtt5UrlSession?(self, didReceiveTrust: trust, didReceiveChallenge: challenge, completionHandler: completionHandler)
}
// ?
public func socketDidSecure(_ sock: MGCDAsyncSocket) {
printDebug("Socket has successfully completed SSL/TLS negotiation")
sendConnectFrame()
}
public func socket(_ socket: CocoaMQTTSocketProtocol, didWriteDataWithTag tag: Int) {
// XXX: How to print writed bytes??
}
public func socket(_ socket: CocoaMQTTSocketProtocol, didRead data: Data, withTag tag: Int) {
let etag = CocoaMQTTReadTag(rawValue: tag)!
var bytes = [UInt8]([0])
switch etag {
case CocoaMQTTReadTag.header:
data.copyBytes(to: &bytes, count: 1)
reader!.headerReady(bytes[0])
case CocoaMQTTReadTag.length:
data.copyBytes(to: &bytes, count: 1)
reader!.lengthReady(bytes[0])
case CocoaMQTTReadTag.payload:
reader!.payloadReady(data)
}
}
public func socketDidDisconnect(_ socket: CocoaMQTTSocketProtocol, withError err: Error?) {
// Clean up
socket.setDelegate(nil, delegateQueue: nil)
if is_internal_disconnected || !autoReconnect {
resetAutoReconnectState()
}
connState = .disconnected
delegate?.mqtt5DidDisconnect(self, withError: err)
didDisconnect(self, err)
guard !is_internal_disconnected else {
is_internal_disconnected = false
return
}
guard autoReconnect else {
resetAutoReconnectState()
return
}
prepareAutoReconnectAttempt()
// Start reconnector once socket error occurred
printInfo("Try reconnect to server after \(reconnectTimeInterval)s")
notifyAutoReconnectScheduled()
autoReconnTimer = CocoaMQTTTimer.after(Double(reconnectTimeInterval), name: "autoReconnTimer", { [weak self] in
guard let self = self else { return }
self.updateAutoReconnectIntervalForNextAttempt()
_ = self.connect()
})
}
}
// MARK: - CocoaMQTTReaderDelegate
extension CocoaMQTT5: CocoaMQTTReaderDelegate {
func didReceive(_ reader: CocoaMQTTReader, disconnect: FrameDisconnect) {
let reasonCode = disconnect.receiveReasonCode ?? .normalDisconnection
delegate?.mqtt5(self, didReceiveDisconnectReasonCode: reasonCode)
didDisconnectReasonCode(self, reasonCode)
}
func didReceive(_ reader: CocoaMQTTReader, auth: FrameAuth) {
let reasonCode = auth.receiveReasonCode ?? .success
delegate?.mqtt5(self, didReceiveAuthReasonCode: reasonCode)
didAuthReasonCode(self, reasonCode)
}
func didReceive(_ reader: CocoaMQTTReader, connack: FrameConnAck) {
printDebug("RECV: \(connack)")
if connack.reasonCode == .success {
// Disable auto-reconnect
resetAutoReconnectState()
is_internal_disconnected = false
// Start keepalive timer
let interval = Double(keepAlive <= 0 ? 60: keepAlive)
aliveTimer = CocoaMQTTTimer.every(interval, name: "aliveTimer") { [weak self] in
guard let self = self else { return }
self.delegateQueue.async {
guard self.connState == .connected else {
self.aliveTimer = nil
return
}
self.ping()
}
}
// recover session if enable
if cleanSession {
deliver.cleanAll()
} else {
if let storage = CocoaMQTTStorage(by: clientID) {
deliver.recoverSessionBy(storage)
} else {
printWarning("Localstorage initial failed for key: \(clientID)")
}
}
connState = .connected
} else {
connState = .disconnected
expected_disconnect(reasonCode: .normalDisconnection)
}
delegate?.mqtt5(self, didConnectAck: connack.reasonCode ?? CocoaMQTTCONNACKReasonCode.unspecifiedError, connAckData: connack.connackProperties ?? nil)
didConnectAck(self, connack.reasonCode ?? CocoaMQTTCONNACKReasonCode.unspecifiedError, connack.connackProperties ?? nil)
}
func didReceive(_ reader: CocoaMQTTReader, publish: FramePublish) {
printDebug("RECV: \(publish)")
let message = CocoaMQTT5Message(topic: publish.mqtt5Topic, payload: publish.payload5(), qos: publish.qos, retained: publish.retained)
message.duplicated = publish.dup
message.contentType = publish.publishRecProperties?.contentType
printInfo("Received message: \(message)")
delegate?.mqtt5(self, didReceiveMessage: message, id: publish.msgid, publishData: publish.publishRecProperties ?? nil)
didReceiveMessage(self, message, publish.msgid, publish.publishRecProperties ?? nil)
if message.qos == .qos1 {
puback(FrameType.puback, msgid: publish.msgid)
} else if message.qos == .qos2 {
puback(FrameType.pubrec, msgid: publish.msgid)
}
}
func didReceive(_ reader: CocoaMQTTReader, puback: FramePubAck) {
printDebug("RECV: \(puback)")
deliver.ack(by: puback)
delegate?.mqtt5(self, didPublishAck: puback.msgid, pubAckData: puback.pubAckProperties ?? nil)
didPublishAck(self, puback.msgid, puback.pubAckProperties ?? nil)
}
func didReceive(_ reader: CocoaMQTTReader, pubrec: FramePubRec) {
printDebug("RECV: \(pubrec)")
deliver.ack(by: pubrec)
delegate?.mqtt5(self, didPublishRec: pubrec.msgid, pubRecData: pubrec.pubRecProperties ?? nil)
didPublishRec(self, pubrec.msgid, pubrec.pubRecProperties ?? nil)
}
func didReceive(_ reader: CocoaMQTTReader, pubrel: FramePubRel) {
printDebug("RECV: \(pubrel)")
puback(FrameType.pubcomp, msgid: pubrel.msgid)
}
func didReceive(_ reader: CocoaMQTTReader, pubcomp: FramePubComp) {
printDebug("RECV: \(pubcomp)")
deliver.ack(by: pubcomp)
delegate?.mqtt5?(self, didPublishComplete: pubcomp.msgid, pubCompData: pubcomp.pubCompProperties ?? nil)
didCompletePublish(self, pubcomp.msgid, pubcomp.pubCompProperties ?? nil)
}
func didReceive(_ reader: CocoaMQTTReader, suback: FrameSubAck) {
printDebug("RECV: \(suback)")
guard let topicsAndQos = subscriptionsWaitingAck.removeValue(forKey: suback.msgid) else {
printWarning("UNEXPECT SUBACK Received: \(suback)")
return
}
guard topicsAndQos.count == suback.grantedQos.count else {
printWarning("UNEXPECT SUBACK Recivied: \(suback)")
return
}
let success: NSMutableDictionary = NSMutableDictionary()
var failed = [String]()
for (idx, subscriptionList) in topicsAndQos.enumerated() {
if suback.grantedQos[idx] != .FAILURE {
subscriptions[subscriptionList.topic] = suback.grantedQos[idx]
success[subscriptionList.topic] = suback.grantedQos[idx].rawValue
} else {
failed.append(subscriptionList.topic)
}
}
delegate?.mqtt5(self, didSubscribeTopics: success, failed: failed, subAckData: suback.subAckProperties ?? nil)
didSubscribeTopics(self, success, failed, suback.subAckProperties ?? nil)
}
func didReceive(_ reader: CocoaMQTTReader, unsuback: FrameUnsubAck) {
printDebug("RECV: \(unsuback)")
guard let topics = unsubscriptionsWaitingAck.removeValue(forKey: unsuback.msgid) else {
printWarning("UNEXPECT UNSUBACK Received: \(unsuback.msgid)")
return
}
// Remove local subscription
var removeTopics: [String] = []
for t in topics {
removeTopics.append(t.topic)
subscriptions.removeValue(forKey: t.topic)
}
delegate?.mqtt5(self, didUnsubscribeTopics: removeTopics, unsubAckData: unsuback.unSubAckProperties ?? nil)
didUnsubscribeTopics(self, removeTopics, unsuback.unSubAckProperties ?? nil)
}
func didReceive(_ reader: CocoaMQTTReader, pingresp: FramePingResp) {
printDebug("RECV: \(pingresp)")
delegate?.mqtt5DidReceivePong(self)
didReceivePong(self)
}
}