From c8b104a437dd5b9c58960ddbbea10d49bb4e9978 Mon Sep 17 00:00:00 2001 From: arthurgau0419 Date: Mon, 12 Oct 2020 00:41:25 +0800 Subject: [PATCH 1/7] Travis settings. --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3628e972..752e70f4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,11 @@ language: swift -osx_image: xcode11.1 +osx_image: xcode12.2 before_install: - wget https://www.emqx.io/downloads/broker/v3.2.7/emqx-macosx-v3.2.7.zip -O emqx.zip - unzip emqx.zip - brew install openssl + - echo "limit maxfiles 65535 unlimited" | sudo tee -a /etc/launchd.conf - emqx/bin/emqx start - git clone https://github.com/hjianbo/xcode-coveralls --depth=1 - cd xcode-coveralls && git submodule init && git submodule update && cd .. From 198a1bf79b082781563f370caef0aa9f577f566f Mon Sep 17 00:00:00 2001 From: arthurgau0419 Date: Mon, 19 Oct 2020 16:51:47 +0800 Subject: [PATCH 2/7] Create CocoaMQTTNetworkConnectionSocket --- CocoaMQTT.xcodeproj/project.pbxproj | 4 + Source/CocoaMQTTNetworkConnectionSocket.swift | 109 ++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 Source/CocoaMQTTNetworkConnectionSocket.swift diff --git a/CocoaMQTT.xcodeproj/project.pbxproj b/CocoaMQTT.xcodeproj/project.pbxproj index fd26e767..665b8702 100644 --- a/CocoaMQTT.xcodeproj/project.pbxproj +++ b/CocoaMQTT.xcodeproj/project.pbxproj @@ -42,6 +42,7 @@ 8D43DE6622FAFF2500D9A06B /* FrameDisconnect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D43DE6522FAFF2500D9A06B /* FrameDisconnect.swift */; }; 8D7A262E23AF2FEA00CE7442 /* client-keycert.p12 in Resources */ = {isa = PBXBuildFile; fileRef = 8D7A262D23AF2FEA00CE7442 /* client-keycert.p12 */; }; 8D7A262F23AF3B4700CE7442 /* Starscream.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EC871DF23A4214000F69AE8 /* Starscream.framework */; }; + 8E67B6C12533F70B00B2468E /* CocoaMQTTNetworkConnectionSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E67B6C02533F70B00B2468E /* CocoaMQTTNetworkConnectionSocket.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -106,6 +107,7 @@ 8D43DE6322FAFE5F00D9A06B /* FramePingResp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FramePingResp.swift; sourceTree = ""; }; 8D43DE6522FAFF2500D9A06B /* FrameDisconnect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FrameDisconnect.swift; sourceTree = ""; }; 8D7A262D23AF2FEA00CE7442 /* client-keycert.p12 */ = {isa = PBXFileReference; lastKnownFileType = file; path = "client-keycert.p12"; sourceTree = ""; }; + 8E67B6C02533F70B00B2468E /* CocoaMQTTNetworkConnectionSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CocoaMQTTNetworkConnectionSocket.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -187,6 +189,7 @@ 6EC871E123A4214D00F69AE8 /* Socket */ = { isa = PBXGroup; children = ( + 8E67B6C02533F70B00B2468E /* CocoaMQTTNetworkConnectionSocket.swift */, 6EC871E223A421A200F69AE8 /* CocoaMQTTSocket.swift */, 6EC871E423A421DE00F69AE8 /* CocoaMQTTWebSocket.swift */, ); @@ -410,6 +413,7 @@ 8D43DE5622FAEDDA00D9A06B /* FramePubComp.swift in Sources */, 82519C0722ACC6E100FA0815 /* CocoaMQTTTypes.swift in Sources */, 8D43DE4C22FAEB8B00D9A06B /* FrameConnect.swift in Sources */, + 8E67B6C12533F70B00B2468E /* CocoaMQTTNetworkConnectionSocket.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Source/CocoaMQTTNetworkConnectionSocket.swift b/Source/CocoaMQTTNetworkConnectionSocket.swift new file mode 100644 index 00000000..8f38e47e --- /dev/null +++ b/Source/CocoaMQTTNetworkConnectionSocket.swift @@ -0,0 +1,109 @@ +// +// CocoaMQTTNetworkConnectionSocket.swift +// CocoaMQTT +// +// Created by Arthur Kao on 2020/10/12. +// Copyright © 2020 emqx.io. All rights reserved. +// + +import Foundation +import Network + +// MARK: - CocoaMQTTNetworkConnectionSocket + +@available(OSX 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *) +public class CocoaMQTTNetworkConnectionSocket: NSObject { + + public var enableSSL = false + + /// + public var sslSettings: [String: NSObject]? + + /// Allow self-signed ca certificate. + /// + /// Default is false + public var allowUntrustCACertificate = false + + fileprivate var reference: NWConnection? + fileprivate weak var delegate: CocoaMQTTSocketDelegate? + fileprivate var delegateQueue: DispatchQueue? + + public override init() { super.init() } +} + +@available(OSX 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *) +extension CocoaMQTTNetworkConnectionSocket: CocoaMQTTSocketProtocol { + public func setDelegate(_ theDelegate: CocoaMQTTSocketDelegate?, delegateQueue: DispatchQueue?) { + delegate = theDelegate + self.delegateQueue = delegateQueue + } + + public func connect(toHost host: String, onPort port: UInt16) throws { + try connect(toHost: host, onPort: port, withTimeout: -1) + } + + public func connect(toHost host: String, onPort port: UInt16, withTimeout timeout: TimeInterval) throws { + let reference = NWConnection(host: NWEndpoint.Host(host), port: NWEndpoint.Port(rawValue: port)!, using: enableSSL ? createTLSParameters(allowInsecure: allowUntrustCACertificate, queue: delegateQueue ?? .main) : .tcp) + self.reference = reference + reference.stateUpdateHandler = { state in + switch state { + case .ready: + self.delegate?.socketConnected(self) + case .failed(let error): + self.delegate?.socketDidDisconnect(self, withError: error) + case .cancelled: + self.delegate?.socketDidDisconnect(self, withError: nil) + default: + break + } + } + reference.start(queue: delegateQueue ?? .main) + } + + public func disconnect() { + reference?.cancel() + } + + public func readData(toLength length: UInt, withTimeout timeout: TimeInterval, tag: Int) { + reference?.receive(minimumIncompleteLength: Int(length), maximumLength: Int(length)) { (data, ctx, b, errpr) in + guard let data = data else { return } + self.delegate?.socket(self, didRead: data, withTag: tag) + } + } + + public func write(_ data: Data, withTimeout timeout: TimeInterval, tag: Int) { + let completion = NWConnection.SendCompletion.contentProcessed { (error) in + if let error = error { + self.delegate?.socketDidDisconnect(self, withError: error) + } else { + self.delegate?.socket(self, didWriteDataWithTag: tag) + } + } + reference?.send(content: data, completion: completion) + } +} + +@available(OSX 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *) +extension CocoaMQTTNetworkConnectionSocket { + func createTLSParameters(allowInsecure: Bool, queue: DispatchQueue) -> NWParameters { + let options = NWProtocolTLS.Options() + if allowInsecure { + sec_protocol_options_set_verify_block(options.securityProtocolOptions, { (sec_protocol_metadata, sec_trust, sec_protocol_verify_complete) in + let trust = sec_trust_copy_ref(sec_trust).takeRetainedValue() + var error: CFError? + if SecTrustEvaluateWithError(trust, &error) { + sec_protocol_verify_complete(true) + } else { + self.delegate?.socket(self, didReceive: trust, completionHandler: sec_protocol_verify_complete) + } + }, queue) + } + if let certificates = sslSettings?[(kCFStreamSSLCertificates as String)] as? NSArray { + certificates.compactMap { sec_identity_create($0 as! SecIdentity) }.forEach { secIdentity in + sec_protocol_options_set_local_identity(options.securityProtocolOptions, secIdentity) + } + } + return NWParameters(tls: options) + } +} + From 34d5999a323d86cdead92076d542eae833055106 Mon Sep 17 00:00:00 2001 From: arthurgau0419 Date: Mon, 12 Oct 2020 10:51:07 +0800 Subject: [PATCH 3/7] Remove CocoaMQTT.socketDidSecure function. - This will never be called. --- Source/CocoaMQTT.swift | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Source/CocoaMQTT.swift b/Source/CocoaMQTT.swift index f5b1096c..3f9b2708 100644 --- a/Source/CocoaMQTT.swift +++ b/Source/CocoaMQTT.swift @@ -7,7 +7,6 @@ // import Foundation -import CocoaAsyncSocket /** * Connection State @@ -559,12 +558,6 @@ extension CocoaMQTT: CocoaMQTTSocketDelegate { didReceiveTrust(self, trust, completionHandler) } - // ? - public func socketDidSecure(_ sock: GCDAsyncSocket) { - printDebug("Socket has successfully completed SSL/TLS negotiation") - sendConnectFrame() - } - public func socket(_ socket: CocoaMQTTSocketProtocol, didWriteDataWithTag tag: Int) { // XXX: How to print writed bytes?? } From 5043fc59995579710b925e73330f5ac1079aeffc Mon Sep 17 00:00:00 2001 From: arthurgau0419 Date: Mon, 12 Oct 2020 11:05:00 +0800 Subject: [PATCH 4/7] Pod supspec --- CocoaMQTT.podspec | 13 +++++++++++-- Example/Podfile | 2 ++ Source/CocoaMQTT.swift | 14 ++++++++++++-- Source/CocoaMQTTNetworkConnectionSocket.swift | 3 ++- Source/CocoaMQTTSocket.swift | 6 ++++++ 5 files changed, 33 insertions(+), 5 deletions(-) diff --git a/CocoaMQTT.podspec b/CocoaMQTT.podspec index 8cebd96f..f8965503 100644 --- a/CocoaMQTT.podspec +++ b/CocoaMQTT.podspec @@ -13,13 +13,22 @@ Pod::Spec.new do |s| s.tvos.deployment_target = "10.0" # s.watchos.deployment_target = "2.0" s.source = { :git => "https://github.com/emqx/CocoaMQTT.git", :tag => "1.3.0-rc.1"} - s.default_subspec = 'Core' + s.default_subspecs = ['Core', 'CocoaAsyncSocket'] s.subspec 'Core' do |ss| - ss.dependency "CocoaAsyncSocket", "~> 7.6.3" ss.source_files = "Source/*.swift" ss.exclude_files = "Source/CocoaMQTTWebSocket.swift" end + + s.subspec 'Network' do |ss| + ss.dependency "CocoaMQTT/Core" + ss.framework = "Network" + end + + s.subspec 'CocoaAsyncSocket' do |ss| + ss.dependency "CocoaMQTT/Core" + ss.dependency "CocoaAsyncSocket", "~> 7.6.3" + end s.subspec 'WebSockets' do |ss| ss.dependency "CocoaMQTT/Core" diff --git a/Example/Podfile b/Example/Podfile index fa9c4da4..411dc130 100644 --- a/Example/Podfile +++ b/Example/Podfile @@ -2,5 +2,7 @@ platform :ios, 10.0 use_frameworks! target 'Example' do + pod 'CocoaMQTT', :path => '../' pod 'CocoaMQTT/WebSockets', :path => '../' + pod 'CocoaMQTT/CocoaAsyncSocket', :path => '../' end diff --git a/Source/CocoaMQTT.swift b/Source/CocoaMQTT.swift index 3f9b2708..4f768335 100644 --- a/Source/CocoaMQTT.swift +++ b/Source/CocoaMQTT.swift @@ -163,8 +163,18 @@ public class CocoaMQTT: NSObject, CocoaMQTTClient { /// /// - Note: public var backgroundOnSocket: Bool { - get { return (self.socket as? CocoaMQTTSocket)?.backgroundOnSocket ?? true } - set { (self.socket as? CocoaMQTTSocket)?.backgroundOnSocket = newValue } + get { + #if canImport(CocoaAsyncSocket) + return (self.socket as? CocoaMQTTSocket)?.backgroundOnSocket ?? true + #else + return true + #endif + } + set { + #if canImport(CocoaAsyncSocket) + (self.socket as? CocoaMQTTSocket)?.backgroundOnSocket = newValue + #endif + } } /// Delegate Executed queue. Default is `DispatchQueue.main` diff --git a/Source/CocoaMQTTNetworkConnectionSocket.swift b/Source/CocoaMQTTNetworkConnectionSocket.swift index 8f38e47e..ae5b3811 100644 --- a/Source/CocoaMQTTNetworkConnectionSocket.swift +++ b/Source/CocoaMQTTNetworkConnectionSocket.swift @@ -6,6 +6,7 @@ // Copyright © 2020 emqx.io. All rights reserved. // +#if canImport(Network) import Foundation import Network @@ -106,4 +107,4 @@ extension CocoaMQTTNetworkConnectionSocket { return NWParameters(tls: options) } } - +#endif diff --git a/Source/CocoaMQTTSocket.swift b/Source/CocoaMQTTSocket.swift index 5ea0bf76..8e11febf 100644 --- a/Source/CocoaMQTTSocket.swift +++ b/Source/CocoaMQTTSocket.swift @@ -6,7 +6,9 @@ // import Foundation +#if canImport(CocoaAsyncSocket) import CocoaAsyncSocket +#endif // MARK: - Interfaces @@ -30,6 +32,7 @@ public protocol CocoaMQTTSocketProtocol { func write(_ data: Data, withTimeout timeout: TimeInterval, tag: Int) } +#if canImport(CocoaAsyncSocket) // MARK: - CocoaMQTTSocket public class CocoaMQTTSocket: NSObject { @@ -133,3 +136,6 @@ extension CocoaMQTTSocket: GCDAsyncSocketDelegate { delegate?.socketDidDisconnect(self, withError: err) } } +#elseif canImport(Network) +public typealias CocoaMQTTSocket = CocoaMQTTNetworkConnectionSocket +#endif From 2089ec42db5eecd6926350b48407ac6cad7a80d7 Mon Sep 17 00:00:00 2001 From: arthurgau0419 Date: Fri, 23 Oct 2020 23:30:14 +0800 Subject: [PATCH 5/7] Refactor CocoaMQTTStorage --- Source/CocoaMQTTStorage.swift | 40 ++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/Source/CocoaMQTTStorage.swift b/Source/CocoaMQTTStorage.swift index a6538be0..33cb5cbe 100644 --- a/Source/CocoaMQTTStorage.swift +++ b/Source/CocoaMQTTStorage.swift @@ -34,6 +34,8 @@ final class CocoaMQTTStorage: CocoaMQTTStorageProtocol { var userDefault: UserDefaults + private let keyPrefix = "CocoaMQTTStorage" + init?(by clientId: String) { guard let userDefault = UserDefaults(suiteName: CocoaMQTTStorage.name(clientId)) else { return nil @@ -89,7 +91,7 @@ final class CocoaMQTTStorage: CocoaMQTTStorageProtocol { } private func key(_ msgid: UInt16) -> String { - return "\(msgid)" + return "\(keyPrefix).\(msgid)" } private class func name(_ clientId: String) -> String { @@ -97,6 +99,7 @@ final class CocoaMQTTStorage: CocoaMQTTStorageProtocol { } private func parse(_ bytes: [UInt8]) -> (UInt8, [UInt8])? { + guard bytes.count >= 2 else { return nil } /// bytes 1..<5 may be 'Remaining Length' for i in 1 ..< 5 { if (bytes[i] & 0x80) == 0 { @@ -108,25 +111,28 @@ final class CocoaMQTTStorage: CocoaMQTTStorageProtocol { } private func __read(needDelete: Bool) -> [Frame] { - var frames = [Frame]() - let allObjs = userDefault.dictionaryRepresentation().sorted { (k1, k2) in - return k1.key < k2.key - } - for (k, v) in allObjs { - guard let bytes = v as? [UInt8] else { continue } - guard let parsed = parse(bytes) else { continue } - - if needDelete { - userDefault.removeObject(forKey: k) + return userDefault.dictionaryRepresentation().lazy + .filter { $0.key.contains(self.keyPrefix) } + .compactMap { k, v -> (key: String, value: (UInt8, [UInt8]))? in + guard let array = v as? [UInt8], + let parse = self.parse(array) else { return nil } + return (key: k, value: parse) } + .elements + .sorted { (k1, k2) in return k1.key < k2.key } + .compactMap { key, parsed -> Frame? in + if needDelete { + userDefault.removeObject(forKey: key) + } - if let f = FramePublish(fixedHeader: parsed.0, bytes: parsed.1) { - frames.append(f) - } else if let f = FramePubRel(fixedHeader: parsed.0, bytes: parsed.1) { - frames.append(f) + if let f = FramePublish(fixedHeader: parsed.0, bytes: parsed.1) { + return f + } else if let f = FramePubRel(fixedHeader: parsed.0, bytes: parsed.1) { + return f + } else { + return nil + } } - } - return frames } } From 7800d5a6be3c7b96dd5343a1fe244770ba09290c Mon Sep 17 00:00:00 2001 From: arthurgau0419 Date: Fri, 23 Oct 2020 23:54:03 +0800 Subject: [PATCH 6/7] [WIP] CocoaMQTTMemoryStorage --- Source/CocoaMQTTDeliver.swift | 4 +-- Source/CocoaMQTTStorage.swift | 60 +++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/Source/CocoaMQTTDeliver.swift b/Source/CocoaMQTTDeliver.swift index cb87e127..b5b9240f 100644 --- a/Source/CocoaMQTTDeliver.swift +++ b/Source/CocoaMQTTDeliver.swift @@ -73,9 +73,9 @@ class CocoaMQTTDeliver: NSObject { var isInflightFull: Bool { get { return inflight.count >= inflightWindowSize }} var isInflightEmpty: Bool { get { return inflight.count == 0 }} - var storage: CocoaMQTTStorage? + var storage: CocoaMQTTStorageProtocol? - func recoverSessionBy(_ storage: CocoaMQTTStorage) { + func recoverSessionBy(_ storage: CocoaMQTTStorageProtocol) { let frames = storage.takeAll() guard frames.count >= 0 else { diff --git a/Source/CocoaMQTTStorage.swift b/Source/CocoaMQTTStorage.swift index 33cb5cbe..bff22be4 100644 --- a/Source/CocoaMQTTStorage.swift +++ b/Source/CocoaMQTTStorage.swift @@ -21,11 +21,71 @@ protocol CocoaMQTTStorageProtocol { func remove(_ frame: FramePublish) func remove(_ frame: FramePubRel) + + func remove(_ frame: Frame) func synchronize() -> Bool /// Read all stored messages by saving order func readAll() -> [Frame] + + func takeAll() -> [Frame] +} + +final class CocoaMQTTMemoryStorage: CocoaMQTTStorageProtocol { + + var clientId: String + + var dictionary = [String: Frame]() + + init?(by clientId: String) { + self.clientId = clientId + } + + private func key(_ msgid: UInt16) -> String { + return "\(msgid)" + } + + func write(_ frame: FramePublish) -> Bool { + dictionary[key(frame.msgid)] = frame + return true + } + + func write(_ frame: FramePubRel) -> Bool { + dictionary[key(frame.msgid)] = frame + return true + } + + func remove(_ frame: FramePublish) { + dictionary.removeValue(forKey: key(frame.msgid)) + } + + func remove(_ frame: FramePubRel) { + dictionary.removeValue(forKey: key(frame.msgid)) + } + + func remove(_ frame: Frame) { + if let pub = frame as? FramePublish { + remove(pub) + } else if let rel = frame as? FramePubRel { + remove(rel) + } + } + + func synchronize() -> Bool { + true + } + + func readAll() -> [Frame] { + dictionary.sorted { (k1, k2) in return k1.key < k2.key } + .map(\.value) + } + + func takeAll() -> [Frame] { + let all = readAll() + dictionary.removeAll() + return all + } } final class CocoaMQTTStorage: CocoaMQTTStorageProtocol { From e64db49ae313ea5e7078d31fc39c4e01d5f9c52d Mon Sep 17 00:00:00 2001 From: arthurgau0419 Date: Fri, 23 Oct 2020 22:04:38 +0800 Subject: [PATCH 7/7] t --- ...FC308DFA-46BF-4819-BFFC-8CA38FDABFE7.plist | 22 +++++++++++++ .../Info.plist | 33 +++++++++++++++++++ CocoaMQTTTests/CocoaMQTTDeliverTests.swift | 6 ++++ 3 files changed, 61 insertions(+) create mode 100644 CocoaMQTT.xcodeproj/xcshareddata/xcbaselines/8225B54B227C32C500E4DB51.xcbaseline/FC308DFA-46BF-4819-BFFC-8CA38FDABFE7.plist create mode 100644 CocoaMQTT.xcodeproj/xcshareddata/xcbaselines/8225B54B227C32C500E4DB51.xcbaseline/Info.plist diff --git a/CocoaMQTT.xcodeproj/xcshareddata/xcbaselines/8225B54B227C32C500E4DB51.xcbaseline/FC308DFA-46BF-4819-BFFC-8CA38FDABFE7.plist b/CocoaMQTT.xcodeproj/xcshareddata/xcbaselines/8225B54B227C32C500E4DB51.xcbaseline/FC308DFA-46BF-4819-BFFC-8CA38FDABFE7.plist new file mode 100644 index 00000000..6f5d045c --- /dev/null +++ b/CocoaMQTT.xcodeproj/xcshareddata/xcbaselines/8225B54B227C32C500E4DB51.xcbaseline/FC308DFA-46BF-4819-BFFC-8CA38FDABFE7.plist @@ -0,0 +1,22 @@ + + + + + classNames + + CocoaMQTTDeliverTests + + testPeformance() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.53048 + baselineIntegrationDisplayName + Local Baseline + + + + + + diff --git a/CocoaMQTT.xcodeproj/xcshareddata/xcbaselines/8225B54B227C32C500E4DB51.xcbaseline/Info.plist b/CocoaMQTT.xcodeproj/xcshareddata/xcbaselines/8225B54B227C32C500E4DB51.xcbaseline/Info.plist new file mode 100644 index 00000000..348eec9b --- /dev/null +++ b/CocoaMQTT.xcodeproj/xcshareddata/xcbaselines/8225B54B227C32C500E4DB51.xcbaseline/Info.plist @@ -0,0 +1,33 @@ + + + + + runDestinationsByUUID + + FC308DFA-46BF-4819-BFFC-8CA38FDABFE7 + + localComputer + + busSpeedInMHz + 400 + cpuCount + 1 + cpuKind + 8-Core Intel Core i9 + cpuSpeedInMHz + 2300 + logicalCPUCoresPerPackage + 16 + modelCode + MacBookPro16,1 + physicalCPUCoresPerPackage + 8 + platformIdentifier + com.apple.platform.macosx + + targetArchitecture + x86_64 + + + + diff --git a/CocoaMQTTTests/CocoaMQTTDeliverTests.swift b/CocoaMQTTTests/CocoaMQTTDeliverTests.swift index 9108e6d9..da368ff9 100644 --- a/CocoaMQTTTests/CocoaMQTTDeliverTests.swift +++ b/CocoaMQTTTests/CocoaMQTTDeliverTests.swift @@ -186,6 +186,12 @@ class CocoaMQTTDeliverTests: XCTestCase { ms_sleep(100) XCTAssertEqual(storage.readAll().count, 0) } + + func testPeformance() { + measure { + testStorage() + } + } func testTODO() { // TODO: How to test large of messages combined qos0/qos1/qos2