Working on an MQTT client. We have a Network.framework solution, but that only supports macOS 10.14+. We need to support 10.13+. We have some code for an NSStream-based solution, but that seems to support only TLSv1, not to mention that a WWDC18 session 'highly discouraged' its use. So, we are attempting to use URLSessionStreamTask to connect and capture the streams which are then handed back to the NSStream-based code. Yes, we could just rewrite more of the code, add our own async read, etc,. but... Anyway, the modified code to use the URLSessionStreamTask has few changes: the streams are obtained by creating a URLSession & streamTask, then calling the task's captureStreams() method. An extension to the class was added to implement the URLSessionStreamDelegate; existing code was moved into the callback method to handle stream setup.
A URLSession is created & successfully connects (we are testing against a mosquitto server which has an insecure cert, but URLSessionStreamDelegate handles the challenge)
private var inputStream: InputStream? = nil
private var outputStream: OutputStream? = nil
private var sessionQueue: DispatchQueue
private var session: URLSession? = nil
private var streamTask: URLSessionStreamTask? = nil
...
session = URLSession(configuration: URLSessionConfiguration.default, delegate: self as URLSessionStreamDelegate, delegateQueue: nil)
streamTask = session!.streamTask(withHostName: host, port: port)
if secure {
streamTask!.startSecureConnection()
}
streamTask!.captureStreams()
streamTask!.resume()
...
The delegate is called with the captured streams which are 'attached' to the existing code
extension MQTTSessionStream: URLSessionStreamDelegate {
func urlSession(_ session: URLSession,
streamTask: URLSessionStreamTask,
didBecome inputStream: InputStream, outputStream: OutputStream) {
//setup the streams
self.inputStream = inputStream
self.inputStream!.delegate = self as StreamDelegate
self.outputStream = outputStream
self.outputStream!.delegate = self as StreamDelegate
self.sessionQueue.async { [weak self] in
guard let `self` = self else {
return
}
self.currentRunLoop = RunLoop.current
self.inputStream!.schedule(in: self.currentRunLoop!, forMode: RunLoop.Mode.default)
self.outputStream!.schedule(in: self.currentRunLoop!, forMode: RunLoop.Mode.default)
let timeout: TimeInterval = 30 //- temporary - later, make this a member variable and set in init...
if timeout > 0 {
DispatchQueue.global().asyncAfter(deadline: .now() + timeout) {
self.connectTimeout()
}
}
self.inputStream!.open()
self.inputReady = true;
self.outputStream!.open()
self.outputReady = true;
self.currentRunLoop!.run()
}
}
func urlSession(_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
...
}
}
Log lines:
2020-01-24 09:56:48.184007-0700 0x149a66 Default 0x0 13146 0 stmqtt: (CFNetwork) Connection 1: connected successfully
2020-01-24 09:56:48.184046-0700 0x149a66 Default 0x0 13146 0 stmqtt: (CFNetwork) Connection 1: TLS handshake complete
2020-01-24 09:56:48.184296-0700 0x149a66 Default 0x0 13146 0 stmqtt: (CFNetwork) Connection 1: ready C(N) E(N)
2020-01-24 09:56:48.184320-0700 0x149a66 Default 0x0 13146 0 stmqtt: (CFNetwork) StreamTask <99FB193B-B74A-4BE1-B6BA-EBBAA6C1C315>.<0> is complete and received server trust, marking as secure
2020-01-24 09:56:48.184390-0700 0x149a66 Default 0x0 13146 0 stmqtt: (CFNetwork) Establish Object : 0x7ffb16d08dc8 .Task : 0x7ffb189088d0
2020-01-24 09:56:48.184411-0700 0x149a66 Default 0x0 13146 0 stmqtt: (CFNetwork) Connection 1: received viability advisory(Y)
2020-01-24 09:56:54.442740-0700 0x149ad6 Activity 0x1103d4 13146 0 stmqtt: (Security) SecKeychainAddCallback
2020-01-24 09:58:13.850514-0700 0x149d3e Default 0x0 13146 0 stmqtt: (libboringssl.dylib) [com.apple.network.boringssl:BoringSSL] boringssl_context_message_handler(2259) [C1.1:2][0x7ffb1890b240] Reading SSL3_RT_ALERT 2 bytes
However, the StreamDelegate method to handle the events is never called and so the stream is soon closed.
extension MQTTSessionStream: StreamDelegate {
@objc internal func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
switch eventCode {
case Stream.Event.openCompleted:
...
case Stream.Event.hasBytesAvailable:
...
case Stream.Event.errorOccurred:
...
case Stream.Event.endEncountered:
...
case Stream.Event.hasSpaceAvailable:
...
default:
break
}
}
}
Log lines:
2020-01-24 09:58:13.850554-0700 0x149d3e Default 0x0 13146 0 stmqtt: (libboringssl.dylib) [com.apple.network.boringssl:BoringSSL] boringssl_context_handle_warning_alert(1894) [C1.1:2][0x7ffb1890b240] read alert, level: warning, description: close notify
2020-01-24 09:58:13.850571-0700 0x149d3e Default 0x0 13146 0 stmqtt: (libboringssl.dylib) [com.apple.network.boringssl:BoringSSL] nw_protocol_boringssl_input_finished(1676) [C1.1:2][0x7ffb1890b240] state: 2
2020-01-24 09:58:13.850607-0700 0x149d3e Default 0x0 13146 0 stmqtt: (CFNetwork) Connection 1: read-side closed
2020-01-24 09:58:13.850621-0700 0x149d3e Error 0x0 13146 0 stmqtt: (CFNetwork) Connection 1: missing error, so heuristics synthesized error(1:53)
2020-01-24 09:58:13.850631-0700 0x149d3e Error 0x0 13146 0 stmqtt: (CFNetwork) Connection 1: encountered error(1:53)
The WWDC18 session in which the URLSessionStreamTask was presented indicated that once the streams were captured, they could be used in code that previously had used NSStreams. Did I miss some other required setup? Does anything seem obviously wrong with