@@ -27,7 +27,7 @@ import NIOTransportServices
2727import NIOSSL
2828#endif
2929
30- @Suite ( " MQTTConnection Tests " , . serialized )
30+ @Suite ( " MQTTConnection Tests " )
3131struct MQTTConnectionTests {
3232 static let hostname = ProcessInfo . processInfo. environment [ " MOSQUITTO_SERVER " ] ?? " localhost "
3333
@@ -97,65 +97,147 @@ struct MQTTConnectionTests {
9797 }
9898 }
9999
100- @Test ( " Connect with TLS " )
101- func tlsConnect( ) async throws {
102- try await MQTTConnection . withConnection (
103- address: . hostname( Self . hostname, port: 8883 ) ,
104- configuration: . init(
105- useSSL: true ,
106- tlsConfiguration: Self . getTLSConfiguration ( ) ,
107- sniServerName: " soto.codes "
108- ) ,
109- identifier: " tlsConnect " ,
110- eventLoop: Self . eventLoopGroupSingleton. any ( ) ,
111- logger: self . logger
112- ) { connection in
113- try await connection. ping ( )
100+ @Suite ( . serialized)
101+ struct TLS {
102+ static let hostname = ProcessInfo . processInfo. environment [ " MOSQUITTO_SERVER " ] ?? " localhost "
103+
104+ static let rootPath = #filePath
105+ . split ( separator: " / " , omittingEmptySubsequences: false )
106+ . dropLast ( 3 )
107+ . joined ( separator: " / " )
108+
109+ static var eventLoopGroupSingleton : EventLoopGroup {
110+ #if os(Linux)
111+ MultiThreadedEventLoopGroup . singleton
112+ #else
113+ // Return TS Eventloop for non-Linux builds, as we use TS TLS
114+ NIOTSEventLoopGroup . singleton
115+ #endif
114116 }
115- }
116117
117- @Test ( " Connect with WebSocket and TLS " )
118- func webSocketAndTLSConnect( ) async throws {
119- try await MQTTConnection . withConnection (
120- address: . hostname( Self . hostname, port: 8081 ) ,
121- configuration: . init(
122- timeout: . seconds( 5 ) ,
123- useSSL: true ,
124- tlsConfiguration: Self . getTLSConfiguration ( ) ,
125- sniServerName: " soto.codes " ,
126- webSocketConfiguration: . init( )
127- ) ,
128- identifier: " webSocketAndTLSConnect " ,
129- eventLoop: Self . eventLoopGroupSingleton. any ( ) ,
130- logger: self . logger
131- ) { connection in
132- try await connection. ping ( )
118+ let logger : Logger = {
119+ var logger = Logger ( label: " MQTTNIOTests " )
120+ logger. logLevel = . trace
121+ return logger
122+ } ( )
123+
124+ @Test ( " Connect with TLS " )
125+ func tlsConnect( ) async throws {
126+ try await MQTTConnection . withConnection (
127+ address: . hostname( Self . hostname, port: 8883 ) ,
128+ configuration: . init(
129+ useSSL: true ,
130+ tlsConfiguration: self . getTLSConfiguration ( ) ,
131+ sniServerName: " soto.codes "
132+ ) ,
133+ identifier: " tlsConnect " ,
134+ eventLoop: Self . eventLoopGroupSingleton. any ( ) ,
135+ logger: self . logger
136+ ) { connection in
137+ try await connection. ping ( )
138+ }
133139 }
134- }
135140
136- #if canImport(Network)
137- @Test ( " Connect with TLS from P12 " )
138- func tlsConnectFromP12( ) async throws {
139- try await MQTTConnection . withConnection (
140- address: . hostname( Self . hostname, port: 8883 ) ,
141- configuration: . init(
142- useSSL: true ,
143- tlsConfiguration: . ts(
144- . init(
145- trustRoots: . der( Self . rootPath + " /mosquitto/certs/ca.der " ) ,
146- clientIdentity: . p12( filename: Self . rootPath + " /mosquitto/certs/client.p12 " , password: " MQTTNIOClientCertPassword " )
147- )
141+ @Test ( " Connect with WebSocket and TLS " )
142+ func webSocketAndTLSConnect( ) async throws {
143+ try await MQTTConnection . withConnection (
144+ address: . hostname( Self . hostname, port: 8081 ) ,
145+ configuration: . init(
146+ timeout: . seconds( 5 ) ,
147+ useSSL: true ,
148+ tlsConfiguration: self . getTLSConfiguration ( ) ,
149+ sniServerName: " soto.codes " ,
150+ webSocketConfiguration: . init( )
148151 ) ,
149- sniServerName: " soto.codes "
150- ) ,
151- identifier: " tlsConnectFromP12 " ,
152- eventLoop: Self . eventLoopGroupSingleton. any ( ) ,
153- logger: self . logger
154- ) { connection in
155- try await connection. ping ( )
152+ identifier: " webSocketAndTLSConnect " ,
153+ eventLoop: Self . eventLoopGroupSingleton. any ( ) ,
154+ logger: self . logger
155+ ) { connection in
156+ try await connection. ping ( )
157+ }
158+ }
159+
160+ #if canImport(Network)
161+ @Test ( " Connect with TLS from P12 " )
162+ func tlsConnectFromP12( ) async throws {
163+ try await MQTTConnection . withConnection (
164+ address: . hostname( Self . hostname, port: 8883 ) ,
165+ configuration: . init(
166+ useSSL: true ,
167+ tlsConfiguration: . ts(
168+ . init(
169+ trustRoots: . der( Self . rootPath + " /mosquitto/certs/ca.der " ) ,
170+ clientIdentity: . p12( filename: Self . rootPath + " /mosquitto/certs/client.p12 " , password: " MQTTNIOClientCertPassword " )
171+ )
172+ ) ,
173+ sniServerName: " soto.codes "
174+ ) ,
175+ identifier: " tlsConnectFromP12 " ,
176+ eventLoop: Self . eventLoopGroupSingleton. any ( ) ,
177+ logger: self . logger
178+ ) { connection in
179+ try await connection. ping ( )
180+ }
181+ }
182+ #endif
183+
184+ var _tlsConfiguration : MQTTConnectionConfiguration . TLSConfigurationType {
185+ get throws {
186+ #if os(Linux)
187+ let rootCertificate = try NIOSSLCertificate . fromPEMFile ( Self . rootPath + " /mosquitto/certs/ca.pem " )
188+ let certificate = try NIOSSLCertificate . fromPEMFile ( Self . rootPath + " /mosquitto/certs/client.pem " )
189+ let privateKey = try NIOSSLPrivateKey ( file: Self . rootPath + " /mosquitto/certs/client.key " , format: . pem)
190+ var tlsConfiguration = TLSConfiguration . makeClientConfiguration ( )
191+ tlsConfiguration. trustRoots = . certificates( rootCertificate)
192+ tlsConfiguration. certificateChain = certificate. map { . certificate( $0) }
193+ tlsConfiguration. privateKey = . privateKey( privateKey)
194+ return . niossl( tlsConfiguration)
195+ #else
196+ let caData = try Data ( contentsOf: URL ( fileURLWithPath: Self . rootPath + " /mosquitto/certs/ca.der " ) )
197+ let trustRootCertificates = SecCertificateCreateWithData ( nil , caData as CFData ) . map { [ $0] }
198+ let p12Data = try Data ( contentsOf: URL ( fileURLWithPath: Self . rootPath + " /mosquitto/certs/client.p12 " ) )
199+ let options : [ String : String ] = [ kSecImportExportPassphrase as String : " MQTTNIOClientCertPassword " ]
200+ var rawItems : CFArray ?
201+ guard SecPKCS12Import ( p12Data as CFData , options as CFDictionary , & rawItems) == errSecSuccess else {
202+ throw MQTTError . wrongTLSConfig
203+ }
204+ let items = rawItems! as! [ [ String : Any ] ]
205+ let firstItem = items [ 0 ]
206+ let identity = firstItem [ kSecImportItemIdentity as String ] as! SecIdentity ?
207+ let tlsConfiguration = TSTLSConfiguration (
208+ trustRoots: trustRootCertificates,
209+ clientIdentity: identity
210+ )
211+ return . ts( tlsConfiguration)
212+ #endif
213+ }
214+ }
215+
216+ func getTLSConfiguration(
217+ withTrustRoots: Bool = true ,
218+ withClientKey: Bool = true
219+ ) throws -> MQTTConnectionConfiguration . TLSConfigurationType {
220+ switch try self . _tlsConfiguration {
221+ #if os(macOS) || os(Linux)
222+ case . niossl ( let config) :
223+ var tlsConfig = TLSConfiguration . makeClientConfiguration ( )
224+ tlsConfig. trustRoots = withTrustRoots ? ( config. trustRoots ?? . default) : . default
225+ tlsConfig. certificateChain = withClientKey ? config. certificateChain : [ ]
226+ tlsConfig. privateKey = withClientKey ? config. privateKey : nil
227+ return . niossl( tlsConfig)
228+ #endif
229+ #if canImport(Network)
230+ case . ts ( let config) :
231+ return . ts(
232+ TSTLSConfiguration (
233+ trustRoots: withTrustRoots ? config. trustRoots : nil ,
234+ clientIdentity: withClientKey ? config. clientIdentity : nil
235+ )
236+ )
237+ #endif
238+ }
156239 }
157240 }
158- #endif
159241
160242 @Test( " Connect with Unix Domain Socket " )
161243 func unixDomainSocketConnect( ) async throws {
@@ -458,7 +540,7 @@ struct MQTTConnectionTests {
458540 try await withThrowingTaskGroup { group in
459541 group. addTask {
460542 try await confirmation ( " multiLevelWildcard " , expectedCount: 2 ) { receivedMessage in
461- try await connection. subscribe ( to: [ . init( topicFilter: " home/kitchen/# " , qos: . atLeastOnce) ] ) { subscription in
543+ try await connection. subscribe ( to: [ . init( topicFilter: " multiLevel/ home/kitchen/#" , qos: . atLeastOnce) ] ) { subscription in
462544 var count = 0
463545 for try await message in subscription {
464546 var buffer = message. payload
@@ -474,9 +556,13 @@ struct MQTTConnectionTests {
474556
475557 group. addTask {
476558 try await Task . sleep ( for: . seconds( 1 ) )
477- try await connection. publish ( to: " home/kitchen/temperature " , payload: ByteBuffer ( string: " test " ) , qos: . atLeastOnce)
478- try await connection. publish ( to: " home/livingroom/temperature " , payload: ByteBuffer ( string: " error " ) , qos: . atLeastOnce)
479- try await connection. publish ( to: " home/kitchen/humidity " , payload: ByteBuffer ( string: " test " ) , qos: . atLeastOnce)
559+ try await connection. publish ( to: " multiLevel/home/kitchen/temperature " , payload: ByteBuffer ( string: " test " ) , qos: . atLeastOnce)
560+ try await connection. publish (
561+ to: " multiLevel/home/livingroom/temperature " ,
562+ payload: ByteBuffer ( string: " error " ) ,
563+ qos: . atLeastOnce
564+ )
565+ try await connection. publish ( to: " multiLevel/home/kitchen/humidity " , payload: ByteBuffer ( string: " test " ) , qos: . atLeastOnce)
480566 }
481567
482568 try await group. waitForAll ( )
@@ -494,7 +580,8 @@ struct MQTTConnectionTests {
494580 try await withThrowingTaskGroup { group in
495581 group. addTask {
496582 try await confirmation ( " singleLevelWildcard " , expectedCount: 2 ) { receivedMessage in
497- try await connection. subscribe ( to: [ . init( topicFilter: " home/+/temperature " , qos: . atLeastOnce) ] ) { subscription in
583+ try await connection. subscribe ( to: [ . init( topicFilter: " singleLevel/home/+/temperature " , qos: . atLeastOnce) ] ) {
584+ subscription in
498585 var count = 0
499586 for try await message in subscription {
500587 var buffer = message. payload
@@ -510,9 +597,13 @@ struct MQTTConnectionTests {
510597
511598 group. addTask {
512599 try await Task . sleep ( for: . seconds( 1 ) )
513- try await connection. publish ( to: " home/livingroom/temperature " , payload: ByteBuffer ( string: " test " ) , qos: . atLeastOnce)
514- try await connection. publish ( to: " home/garden/humidity " , payload: ByteBuffer ( string: " error " ) , qos: . atLeastOnce)
515- try await connection. publish ( to: " home/kitchen/temperature " , payload: ByteBuffer ( string: " test " ) , qos: . atLeastOnce)
600+ try await connection. publish (
601+ to: " singleLevel/home/livingroom/temperature " ,
602+ payload: ByteBuffer ( string: " test " ) ,
603+ qos: . atLeastOnce
604+ )
605+ try await connection. publish ( to: " singleLevel/home/garden/humidity " , payload: ByteBuffer ( string: " error " ) , qos: . atLeastOnce)
606+ try await connection. publish ( to: " singleLevel/home/kitchen/temperature " , payload: ByteBuffer ( string: " test " ) , qos: . atLeastOnce)
516607 }
517608
518609 try await group. waitForAll ( )
@@ -533,8 +624,8 @@ struct MQTTConnectionTests {
533624 group. addTask {
534625 try await confirmation ( " overlappingSubscriptions " , expectedCount: 2 ) { receivedMessage in
535626 try await connection. subscribe ( to: [
536- . init( topicFilter: " home/+/temperature " , qos: . atLeastOnce) ,
537- . init( topicFilter: " home/kitchen/# " , qos: . atLeastOnce) ,
627+ . init( topicFilter: " overlapping/ home/+/temperature" , qos: . atLeastOnce) ,
628+ . init( topicFilter: " overlapping/ home/kitchen/#" , qos: . atLeastOnce) ,
538629 ] ) { subscription in
539630 var count = 0
540631 for try await message in subscription {
@@ -551,7 +642,7 @@ struct MQTTConnectionTests {
551642
552643 group. addTask {
553644 try await Task . sleep ( for: . seconds( 1 ) )
554- try await connection. publish ( to: " home/kitchen/temperature " , payload: ByteBuffer ( string: " test " ) , qos: . atLeastOnce)
645+ try await connection. publish ( to: " overlapping/ home/kitchen/temperature" , payload: ByteBuffer ( string: " test " ) , qos: . atLeastOnce)
555646 }
556647
557648 try await group. waitForAll ( )
@@ -578,63 +669,6 @@ struct MQTTConnectionTests {
578669 NIOTSEventLoopGroup . singleton
579670 #endif
580671 }
581-
582- static var _tlsConfiguration : MQTTConnectionConfiguration . TLSConfigurationType {
583- get throws {
584- #if os(Linux)
585- let rootCertificate = try NIOSSLCertificate . fromPEMFile ( Self . rootPath + " /mosquitto/certs/ca.pem " )
586- let certificate = try NIOSSLCertificate . fromPEMFile ( Self . rootPath + " /mosquitto/certs/client.pem " )
587- let privateKey = try NIOSSLPrivateKey ( file: Self . rootPath + " /mosquitto/certs/client.key " , format: . pem)
588- var tlsConfiguration = TLSConfiguration . makeClientConfiguration ( )
589- tlsConfiguration. trustRoots = . certificates( rootCertificate)
590- tlsConfiguration. certificateChain = certificate. map { . certificate( $0) }
591- tlsConfiguration. privateKey = . privateKey( privateKey)
592- return . niossl( tlsConfiguration)
593- #else
594- let caData = try Data ( contentsOf: URL ( fileURLWithPath: Self . rootPath + " /mosquitto/certs/ca.der " ) )
595- let trustRootCertificates = SecCertificateCreateWithData ( nil , caData as CFData ) . map { [ $0] }
596- let p12Data = try Data ( contentsOf: URL ( fileURLWithPath: Self . rootPath + " /mosquitto/certs/client.p12 " ) )
597- let options : [ String : String ] = [ kSecImportExportPassphrase as String : " MQTTNIOClientCertPassword " ]
598- var rawItems : CFArray ?
599- guard SecPKCS12Import ( p12Data as CFData , options as CFDictionary , & rawItems) == errSecSuccess else {
600- throw MQTTError . wrongTLSConfig
601- }
602- let items = rawItems! as! [ [ String : Any ] ]
603- let firstItem = items [ 0 ]
604- let identity = firstItem [ kSecImportItemIdentity as String ] as! SecIdentity ?
605- let tlsConfiguration = TSTLSConfiguration (
606- trustRoots: trustRootCertificates,
607- clientIdentity: identity
608- )
609- return . ts( tlsConfiguration)
610- #endif
611- }
612- }
613-
614- static func getTLSConfiguration(
615- withTrustRoots: Bool = true ,
616- withClientKey: Bool = true
617- ) throws -> MQTTConnectionConfiguration . TLSConfigurationType {
618- switch try Self . _tlsConfiguration {
619- #if os(macOS) || os(Linux)
620- case . niossl ( let config) :
621- var tlsConfig = TLSConfiguration . makeClientConfiguration ( )
622- tlsConfig. trustRoots = withTrustRoots ? ( config. trustRoots ?? . default) : . default
623- tlsConfig. certificateChain = withClientKey ? config. certificateChain : [ ]
624- tlsConfig. privateKey = withClientKey ? config. privateKey : nil
625- return . niossl( tlsConfig)
626- #endif
627- #if canImport(Network)
628- case . ts ( let config) :
629- return . ts(
630- TSTLSConfiguration (
631- trustRoots: withTrustRoots ? config. trustRoots : nil ,
632- clientIdentity: withClientKey ? config. clientIdentity : nil
633- )
634- )
635- #endif
636- }
637- }
638672}
639673
640674extension MQTTError: Equatable {
0 commit comments