3
3
// Websocket.swift
4
4
//
5
5
// Created by Dalton Cherry on 7/16/14.
6
- // Copyright (c) 2014-2015 Dalton Cherry.
6
+ // Copyright (c) 2014-2016 Dalton Cherry.
7
7
//
8
8
// Licensed under the Apache License, Version 2.0 (the "License");
9
9
// you may not use this file except in compliance with the License.
18
18
// limitations under the License.
19
19
//
20
20
//////////////////////////////////////////////////////////////////////////////////////////////////
21
-
22
21
import Foundation
23
22
import CoreFoundation
24
23
import Security
@@ -38,7 +37,7 @@ public protocol WebSocketPongDelegate: class {
38
37
func websocketDidReceivePong( socket: WebSocket , data: Data ? )
39
38
}
40
39
41
- public class WebSocket : NSObject , StreamDelegate {
40
+ open class WebSocket : NSObject , StreamDelegate {
42
41
43
42
enum OpCode : UInt8 {
44
43
case continueFrame = 0x0
@@ -77,7 +76,6 @@ public class WebSocket : NSObject, StreamDelegate {
77
76
var optionalProtocols : [ String ] ?
78
77
79
78
// MARK: - Constants
80
-
81
79
let headerWSUpgradeName = " Upgrade "
82
80
let headerWSUpgradeValue = " websocket "
83
81
let headerWSHostName = " Host "
@@ -108,7 +106,6 @@ public class WebSocket : NSObject, StreamDelegate {
108
106
}
109
107
110
108
// MARK: - Delegates
111
-
112
109
/// Responds to callback about new messages coming in over the WebSocket
113
110
/// and also connection/disconnect messages.
114
111
public weak var delegate : WebSocketDelegate ?
@@ -118,7 +115,6 @@ public class WebSocket : NSObject, StreamDelegate {
118
115
119
116
120
117
// MARK: - Block based API.
121
-
122
118
public var onConnect : ( ( Void ) -> Void ) ?
123
119
public var onDisconnect : ( ( NSError ? ) -> Void ) ?
124
120
public var onText : ( ( String ) -> Void ) ?
@@ -128,7 +124,7 @@ public class WebSocket : NSObject, StreamDelegate {
128
124
public var headers = [ String: String] ( )
129
125
public var voipEnabled = false
130
126
public var disableSSLCertValidation = false
131
- public var security : SSLSecurity ?
127
+ public var security : SSLTrustValidator ?
132
128
public var enabledSSLCipherSuites : [ SSLCipherSuite ] ?
133
129
public var origin : String ?
134
130
public var timeout = 5
@@ -139,7 +135,6 @@ public class WebSocket : NSObject, StreamDelegate {
139
135
public var currentURL : URL { return url }
140
136
141
137
// MARK: - Private
142
-
143
138
private var url : URL
144
139
private var inputStream : InputStream ?
145
140
private var outputStream : OutputStream ?
@@ -198,7 +193,8 @@ public class WebSocket : NSObject, StreamDelegate {
198
193
public func disconnect( forceTimeout: TimeInterval ? = nil , closeCode: UInt16 = CloseCode . normal. rawValue) {
199
194
switch forceTimeout {
200
195
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
202
198
self ? . disconnectStream ( nil )
203
199
}
204
200
fallthrough
@@ -213,7 +209,7 @@ public class WebSocket : NSObject, StreamDelegate {
213
209
/**
214
210
Write a string to the websocket. This sends it as a text frame.
215
211
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.
217
213
- parameter completion: The (optional) completion handler.
218
214
*/
219
215
public func write( string: String , completion: ( ( ) -> ( ) ) ? = nil ) {
@@ -305,7 +301,6 @@ public class WebSocket : NSObject, StreamDelegate {
305
301
private func initStreamsWithData( _ data: Data , _ port: Int ) {
306
302
//higher level API we will cut over to at some point
307
303
//NSStream.getStreamsToHostWithName(url.host, port: url.port.integerValue, inputStream: &inputStream, outputStream: &outputStream)
308
-
309
304
var readStream : Unmanaged < CFReadStream > ?
310
305
var writeStream : Unmanaged < CFWriteStream > ?
311
306
let h = url. host! as NSString
@@ -318,35 +313,35 @@ public class WebSocket : NSObject, StreamDelegate {
318
313
if supportedSSLSchemes. contains ( url. scheme!) {
319
314
inStream. setProperty ( StreamSocketSecurityLevel . negotiatedSSL as AnyObject , forKey: Stream . PropertyKey. socketSecurityLevelKey)
320
315
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
+ }
321
338
} else {
322
339
certValidated = true //not a https session, so no need to check SSL pinning
323
340
}
324
341
if voipEnabled {
325
342
inStream. setProperty ( StreamNetworkServiceTypeValue . voIP as AnyObject , forKey: Stream . PropertyKey. networkServiceType)
326
343
outStream. setProperty ( StreamNetworkServiceTypeValue . voIP as AnyObject , forKey: Stream . PropertyKey. networkServiceType)
327
344
}
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
- }
350
345
351
346
CFReadStreamSetDispatchQueue ( inStream, WebSocket . sharedWorkQueue)
352
347
CFWriteStreamSetDispatchQueue ( outStream, WebSocket . sharedWorkQueue)
@@ -358,7 +353,7 @@ public class WebSocket : NSObject, StreamDelegate {
358
353
self . mutex. unlock ( )
359
354
360
355
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
362
357
writeQueue. addOperation { [ weak self] in
363
358
while !outStream. hasSpaceAvailable {
364
359
usleep ( 100 ) // wait until the socket is ready
@@ -380,9 +375,9 @@ public class WebSocket : NSObject, StreamDelegate {
380
375
*/
381
376
public func stream( _ aStream: Stream , handle eventCode: Stream . Event ) {
382
377
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
384
379
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) {
386
381
certValidated = true
387
382
} else {
388
383
let error = errorWithDetail ( " Invalid SSL certificate " , code: 1 )
@@ -439,7 +434,6 @@ public class WebSocket : NSObject, StreamDelegate {
439
434
let buf = NSMutableData ( capacity: BUFFER_MAX)
440
435
let buffer = UnsafeMutableRawPointer ( mutating: buf!. bytes) . assumingMemoryBound ( to: UInt8 . self)
441
436
let length = inputStream!. read ( buffer, maxLength: BUFFER_MAX)
442
-
443
437
guard length > 0 else { return }
444
438
var process = false
445
439
if inputQueue. count == 0 {
@@ -635,34 +629,22 @@ public class WebSocket : NSObject, StreamDelegate {
635
629
writeError ( errCode)
636
630
return emptyBuffer
637
631
}
632
+ var closeCode = CloseCode . normal. rawValue
638
633
if receivedOpcode == . connectionClose {
639
- var code = CloseCode . normal. rawValue
640
634
if payloadLen == 1 {
641
- code = CloseCode . protocolError. rawValue
635
+ closeCode = CloseCode . protocolError. rawValue
642
636
} 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
646
640
}
647
- offset += 2
648
641
}
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
660
646
}
661
- doDisconnect ( errorWithDetail ( closeReason, code: code) )
662
- writeError ( code)
663
- return emptyBuffer
664
- }
665
- if isControlFrame && payloadLen > 125 {
647
+ } else if isControlFrame && payloadLen > 125 {
666
648
writeError ( CloseCode . protocolError. rawValue)
667
649
return emptyBuffer
668
650
}
@@ -687,8 +669,24 @@ public class WebSocket : NSObject, StreamDelegate {
687
669
len = 0
688
670
data = Data ( )
689
671
} else {
672
+ if receivedOpcode == . connectionClose && len > 0 {
673
+ let size = MemoryLayout< UInt16> . size
674
+ offset += size
675
+ len -= UInt64 ( size)
676
+ }
690
677
data = Data ( bytes: baseAddress+ offset, count: Int ( len) )
691
678
}
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
+ }
692
690
if receivedOpcode == . pong {
693
691
if canDispatch {
694
692
callbackQueue. async { [ weak self] in
@@ -902,7 +900,6 @@ public class WebSocket : NSObject, StreamDelegate {
902
900
}
903
901
904
902
// MARK: - Deinit
905
-
906
903
deinit {
907
904
mutex. lock ( )
908
905
readyToWrite = false
0 commit comments