33// Websocket.swift
44//
55// Created by Dalton Cherry on 7/16/14.
6- // Copyright (c) 2014-2015 Dalton Cherry.
6+ // Copyright (c) 2014-2016 Dalton Cherry.
77//
88// Licensed under the Apache License, Version 2.0 (the "License");
99// you may not use this file except in compliance with the License.
1818// limitations under the License.
1919//
2020//////////////////////////////////////////////////////////////////////////////////////////////////
21-
2221import Foundation
2322import CoreFoundation
2423import Security
@@ -38,7 +37,7 @@ public protocol WebSocketPongDelegate: class {
3837 func websocketDidReceivePong( socket: WebSocket , data: Data ? )
3938}
4039
41- public class WebSocket : NSObject , StreamDelegate {
40+ open class WebSocket : NSObject , StreamDelegate {
4241
4342 enum OpCode : UInt8 {
4443 case continueFrame = 0x0
@@ -77,7 +76,6 @@ public class WebSocket : NSObject, StreamDelegate {
7776 var optionalProtocols : [ String ] ?
7877
7978 // MARK: - Constants
80-
8179 let headerWSUpgradeName = " Upgrade "
8280 let headerWSUpgradeValue = " websocket "
8381 let headerWSHostName = " Host "
@@ -108,7 +106,6 @@ public class WebSocket : NSObject, StreamDelegate {
108106 }
109107
110108 // MARK: - Delegates
111-
112109 /// Responds to callback about new messages coming in over the WebSocket
113110 /// and also connection/disconnect messages.
114111 public weak var delegate : WebSocketDelegate ?
@@ -118,7 +115,6 @@ public class WebSocket : NSObject, StreamDelegate {
118115
119116
120117 // MARK: - Block based API.
121-
122118 public var onConnect : ( ( Void ) -> Void ) ?
123119 public var onDisconnect : ( ( NSError ? ) -> Void ) ?
124120 public var onText : ( ( String ) -> Void ) ?
@@ -128,7 +124,7 @@ public class WebSocket : NSObject, StreamDelegate {
128124 public var headers = [ String: String] ( )
129125 public var voipEnabled = false
130126 public var disableSSLCertValidation = false
131- public var security : SSLSecurity ?
127+ public var security : SSLTrustValidator ?
132128 public var enabledSSLCipherSuites : [ SSLCipherSuite ] ?
133129 public var origin : String ?
134130 public var timeout = 5
@@ -139,7 +135,6 @@ public class WebSocket : NSObject, StreamDelegate {
139135 public var currentURL : URL { return url }
140136
141137 // MARK: - Private
142-
143138 private var url : URL
144139 private var inputStream : InputStream ?
145140 private var outputStream : OutputStream ?
@@ -198,7 +193,8 @@ public class WebSocket : NSObject, StreamDelegate {
198193 public func disconnect( forceTimeout: TimeInterval ? = nil , closeCode: UInt16 = CloseCode . normal. rawValue) {
199194 switch forceTimeout {
200195 case . some( let seconds) where seconds > 0 :
201- callbackQueue. asyncAfter ( deadline: DispatchTime . now ( ) + Double( Int64 ( seconds * Double( NSEC_PER_SEC) ) ) / Double( NSEC_PER_SEC) ) { [ weak self] in
196+ let milliseconds = Int ( seconds * 1_000 )
197+ callbackQueue. asyncAfter ( deadline: . now( ) + . milliseconds( milliseconds) ) { [ weak self] in
202198 self ? . disconnectStream ( nil )
203199 }
204200 fallthrough
@@ -213,7 +209,7 @@ public class WebSocket : NSObject, StreamDelegate {
213209 /**
214210 Write a string to the websocket. This sends it as a text frame.
215211 If you supply a non-nil completion block, I will perform it when the write completes.
216- - parameter str : The string to write.
212+ - parameter string : The string to write.
217213 - parameter completion: The (optional) completion handler.
218214 */
219215 public func write( string: String , completion: ( ( ) -> ( ) ) ? = nil ) {
@@ -305,7 +301,6 @@ public class WebSocket : NSObject, StreamDelegate {
305301 private func initStreamsWithData( _ data: Data , _ port: Int ) {
306302 //higher level API we will cut over to at some point
307303 //NSStream.getStreamsToHostWithName(url.host, port: url.port.integerValue, inputStream: &inputStream, outputStream: &outputStream)
308-
309304 var readStream : Unmanaged < CFReadStream > ?
310305 var writeStream : Unmanaged < CFWriteStream > ?
311306 let h = url. host! as NSString
@@ -318,35 +313,35 @@ public class WebSocket : NSObject, StreamDelegate {
318313 if supportedSSLSchemes. contains ( url. scheme!) {
319314 inStream. setProperty ( StreamSocketSecurityLevel . negotiatedSSL as AnyObject , forKey: Stream . PropertyKey. socketSecurityLevelKey)
320315 outStream. setProperty ( StreamSocketSecurityLevel . negotiatedSSL as AnyObject , forKey: Stream . PropertyKey. socketSecurityLevelKey)
316+ if disableSSLCertValidation {
317+ let settings : [ NSObject : NSObject ] = [ kCFStreamSSLValidatesCertificateChain: NSNumber ( value: false ) , kCFStreamSSLPeerName: kCFNull]
318+ inStream. setProperty ( settings, forKey: kCFStreamPropertySSLSettings as Stream . PropertyKey )
319+ outStream. setProperty ( settings, forKey: kCFStreamPropertySSLSettings as Stream . PropertyKey )
320+ }
321+ if let cipherSuites = self . enabledSSLCipherSuites {
322+ if let sslContextIn = CFReadStreamCopyProperty ( inputStream, CFStreamPropertyKey ( rawValue: kCFStreamPropertySSLContext) ) as! SSLContext ? ,
323+ let sslContextOut = CFWriteStreamCopyProperty ( outputStream, CFStreamPropertyKey ( rawValue: kCFStreamPropertySSLContext) ) as! SSLContext ? {
324+ let resIn = SSLSetEnabledCiphers ( sslContextIn, cipherSuites, cipherSuites. count)
325+ let resOut = SSLSetEnabledCiphers ( sslContextOut, cipherSuites, cipherSuites. count)
326+ if resIn != errSecSuccess {
327+ let error = self . errorWithDetail ( " Error setting ingoing cypher suites " , code: UInt16 ( resIn) )
328+ disconnectStream ( error)
329+ return
330+ }
331+ if resOut != errSecSuccess {
332+ let error = self . errorWithDetail ( " Error setting outgoing cypher suites " , code: UInt16 ( resOut) )
333+ disconnectStream ( error)
334+ return
335+ }
336+ }
337+ }
321338 } else {
322339 certValidated = true //not a https session, so no need to check SSL pinning
323340 }
324341 if voipEnabled {
325342 inStream. setProperty ( StreamNetworkServiceTypeValue . voIP as AnyObject , forKey: Stream . PropertyKey. networkServiceType)
326343 outStream. setProperty ( StreamNetworkServiceTypeValue . voIP as AnyObject , forKey: Stream . PropertyKey. networkServiceType)
327344 }
328- if disableSSLCertValidation {
329- let settings : [ NSObject : NSObject ] = [ kCFStreamSSLValidatesCertificateChain: NSNumber ( value: false ) , kCFStreamSSLPeerName: kCFNull]
330- inStream. setProperty ( settings, forKey: kCFStreamPropertySSLSettings as Stream . PropertyKey )
331- outStream. setProperty ( settings, forKey: kCFStreamPropertySSLSettings as Stream . PropertyKey )
332- }
333- if let cipherSuites = self . enabledSSLCipherSuites {
334- if let sslContextIn = CFReadStreamCopyProperty ( inputStream, CFStreamPropertyKey ( rawValue: kCFStreamPropertySSLContext) ) as! SSLContext ? ,
335- let sslContextOut = CFWriteStreamCopyProperty ( outputStream, CFStreamPropertyKey ( rawValue: kCFStreamPropertySSLContext) ) as! SSLContext ? {
336- let resIn = SSLSetEnabledCiphers ( sslContextIn, cipherSuites, cipherSuites. count)
337- let resOut = SSLSetEnabledCiphers ( sslContextOut, cipherSuites, cipherSuites. count)
338- if resIn != errSecSuccess {
339- let error = self . errorWithDetail ( " Error setting ingoing cypher suites " , code: UInt16 ( resIn) )
340- disconnectStream ( error)
341- return
342- }
343- if resOut != errSecSuccess {
344- let error = self . errorWithDetail ( " Error setting outgoing cypher suites " , code: UInt16 ( resOut) )
345- disconnectStream ( error)
346- return
347- }
348- }
349- }
350345
351346 CFReadStreamSetDispatchQueue ( inStream, WebSocket . sharedWorkQueue)
352347 CFWriteStreamSetDispatchQueue ( outStream, WebSocket . sharedWorkQueue)
@@ -358,7 +353,7 @@ public class WebSocket : NSObject, StreamDelegate {
358353 self . mutex. unlock ( )
359354
360355 let bytes = UnsafeRawPointer ( ( data as NSData ) . bytes) . assumingMemoryBound ( to: UInt8 . self)
361- var out = timeout * 1000000 // wait 5 seconds before giving up
356+ var out = timeout * 1_000_000 // wait 5 seconds before giving up
362357 writeQueue. addOperation { [ weak self] in
363358 while !outStream. hasSpaceAvailable {
364359 usleep ( 100 ) // wait until the socket is ready
@@ -380,9 +375,9 @@ public class WebSocket : NSObject, StreamDelegate {
380375 */
381376 public func stream( _ aStream: Stream , handle eventCode: Stream . Event ) {
382377 if let sec = security, !certValidated && [ . hasBytesAvailable, . hasSpaceAvailable] . contains ( eventCode) {
383- let trust = aStream. property ( forKey: kCFStreamPropertySSLPeerTrust as Stream . PropertyKey ) as AnyObject
378+ let trust = aStream. property ( forKey: kCFStreamPropertySSLPeerTrust as Stream . PropertyKey ) as! SecTrust
384379 let domain = aStream. property ( forKey: kCFStreamSSLPeerName as Stream . PropertyKey ) as? String
385- if sec. isValid ( trust as! SecTrust , domain: domain) {
380+ if sec. isValid ( trust, domain: domain) {
386381 certValidated = true
387382 } else {
388383 let error = errorWithDetail ( " Invalid SSL certificate " , code: 1 )
@@ -439,7 +434,6 @@ public class WebSocket : NSObject, StreamDelegate {
439434 let buf = NSMutableData ( capacity: BUFFER_MAX)
440435 let buffer = UnsafeMutableRawPointer ( mutating: buf!. bytes) . assumingMemoryBound ( to: UInt8 . self)
441436 let length = inputStream!. read ( buffer, maxLength: BUFFER_MAX)
442-
443437 guard length > 0 else { return }
444438 var process = false
445439 if inputQueue. count == 0 {
@@ -635,34 +629,22 @@ public class WebSocket : NSObject, StreamDelegate {
635629 writeError ( errCode)
636630 return emptyBuffer
637631 }
632+ var closeCode = CloseCode . normal. rawValue
638633 if receivedOpcode == . connectionClose {
639- var code = CloseCode . normal. rawValue
640634 if payloadLen == 1 {
641- code = CloseCode . protocolError. rawValue
635+ closeCode = CloseCode . protocolError. rawValue
642636 } else if payloadLen > 1 {
643- code = WebSocket . readUint16 ( baseAddress, offset: offset)
644- if code < 1000 || ( code > 1003 && code < 1007 ) || ( code > 1011 && code < 3000 ) {
645- code = CloseCode . protocolError. rawValue
637+ closeCode = WebSocket . readUint16 ( baseAddress, offset: offset)
638+ if closeCode < 1000 || ( closeCode > 1003 && closeCode < 1007 ) || ( closeCode > 1011 && closeCode < 3000 ) {
639+ closeCode = CloseCode . protocolError. rawValue
646640 }
647- offset += 2
648641 }
649- var closeReason = " connection closed by server "
650- if payloadLen > 2 {
651- let len = Int ( payloadLen - 2 )
652- if len > 0 {
653- let bytes = baseAddress + offset
654- if let customCloseReason = String ( data: Data ( bytes: bytes, count: len) , encoding: . utf8) {
655- closeReason = customCloseReason
656- } else {
657- code = CloseCode . protocolError. rawValue
658- }
659- }
642+ if payloadLen < 2 {
643+ doDisconnect ( errorWithDetail ( " connection closed by server " , code: closeCode) )
644+ writeError ( closeCode)
645+ return emptyBuffer
660646 }
661- doDisconnect ( errorWithDetail ( closeReason, code: code) )
662- writeError ( code)
663- return emptyBuffer
664- }
665- if isControlFrame && payloadLen > 125 {
647+ } else if isControlFrame && payloadLen > 125 {
666648 writeError ( CloseCode . protocolError. rawValue)
667649 return emptyBuffer
668650 }
@@ -687,8 +669,24 @@ public class WebSocket : NSObject, StreamDelegate {
687669 len = 0
688670 data = Data ( )
689671 } else {
672+ if receivedOpcode == . connectionClose && len > 0 {
673+ let size = MemoryLayout< UInt16> . size
674+ offset += size
675+ len -= UInt64 ( size)
676+ }
690677 data = Data ( bytes: baseAddress+ offset, count: Int ( len) )
691678 }
679+ if receivedOpcode == . connectionClose {
680+ var closeReason = " connection closed by server "
681+ if let customCloseReason = String ( data: data, encoding: . utf8) {
682+ closeReason = customCloseReason
683+ } else {
684+ closeCode = CloseCode . protocolError. rawValue
685+ }
686+ doDisconnect ( errorWithDetail ( closeReason, code: closeCode) )
687+ writeError ( closeCode)
688+ return emptyBuffer
689+ }
692690 if receivedOpcode == . pong {
693691 if canDispatch {
694692 callbackQueue. async { [ weak self] in
@@ -902,7 +900,6 @@ public class WebSocket : NSObject, StreamDelegate {
902900 }
903901
904902 // MARK: - Deinit
905-
906903 deinit {
907904 mutex. lock ( )
908905 readyToWrite = false
0 commit comments