From c6dd1fb19b91b7a219b02619561eb7e419b0e304 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 2 Jul 2017 11:32:56 -0400 Subject: [PATCH] add missing updates --- Package.swift | 6 +- Socket.IO-Client-Swift.podspec | 1 + .../project.pbxproj | 64 ++++++ Source/SocketEngine.swift | 1 - Source/WebSocket.swift | 194 ++++++++++++++++-- 5 files changed, 243 insertions(+), 23 deletions(-) diff --git a/Package.swift b/Package.swift index 04e8fbb..9102d4e 100644 --- a/Package.swift +++ b/Package.swift @@ -1,5 +1,9 @@ import PackageDescription let package = Package( - name: "SocketIO" + name: "SocketIO", + dependencies: [ + .Package(url: "https://github.com/daltoniam/zlib-spm.git", majorVersion: 1), + .Package(url: "https://github.com/daltoniam/common-crypto-spm.git", majorVersion: 1) + ] ) diff --git a/Socket.IO-Client-Swift.podspec b/Socket.IO-Client-Swift.podspec index 88e2b10..35216fd 100644 --- a/Socket.IO-Client-Swift.podspec +++ b/Socket.IO-Client-Swift.podspec @@ -18,5 +18,6 @@ Pod::Spec.new do |s| s.source_files = "Source/**/*.swift" s.requires_arc = true s.pod_target_xcconfig = {'SWIFT_VERSION' => '3.1'} + s.libraries = 'z' # s.dependency 'Starscream', '~> 0.9' # currently this repo includes Starscream swift files end diff --git a/Socket.IO-Client-Swift.xcodeproj/project.pbxproj b/Socket.IO-Client-Swift.xcodeproj/project.pbxproj index de5755c..5ec5af5 100644 --- a/Socket.IO-Client-Swift.xcodeproj/project.pbxproj +++ b/Socket.IO-Client-Swift.xcodeproj/project.pbxproj @@ -99,6 +99,16 @@ 74BC45AB1D0C6675008CC431 /* SocketClientManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74BC45AA1D0C6675008CC431 /* SocketClientManager.swift */; }; 74BC45AC1D0C6675008CC431 /* SocketClientManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74BC45AA1D0C6675008CC431 /* SocketClientManager.swift */; }; 74BC45AD1D0C6675008CC431 /* SocketClientManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74BC45AA1D0C6675008CC431 /* SocketClientManager.swift */; }; + 74DA216E1F0943EE009C19EE /* include.h in Headers */ = {isa = PBXBuildFile; fileRef = 74DA216C1F09438D009C19EE /* include.h */; }; + 74DA216F1F0943F4009C19EE /* include.h in Headers */ = {isa = PBXBuildFile; fileRef = 74DA216C1F09438D009C19EE /* include.h */; }; + 74DA21701F0943F8009C19EE /* include.h in Headers */ = {isa = PBXBuildFile; fileRef = 74DA216C1F09438D009C19EE /* include.h */; }; + 74DA21721F094408009C19EE /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 74DA21711F094408009C19EE /* libz.tbd */; }; + 74DA21741F09440F009C19EE /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 74DA21731F09440F009C19EE /* libz.tbd */; }; + 74DA21761F094417009C19EE /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 74DA21751F094417009C19EE /* libz.tbd */; }; + 74DA217C1F09457B009C19EE /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 74DA21731F09440F009C19EE /* libz.tbd */; }; + 74DA21811F094887009C19EE /* Compression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74DA21801F094887009C19EE /* Compression.swift */; }; + 74DA21821F094887009C19EE /* Compression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74DA21801F094887009C19EE /* Compression.swift */; }; + 74DA21831F094887009C19EE /* Compression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74DA21801F094887009C19EE /* Compression.swift */; }; 74F124F01BC574CF002966F4 /* SocketBasicPacketTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74F124EF1BC574CF002966F4 /* SocketBasicPacketTest.swift */; }; 74F124F11BC574CF002966F4 /* SocketBasicPacketTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74F124EF1BC574CF002966F4 /* SocketBasicPacketTest.swift */; }; CEBA569A1CDA0B8200BA0389 /* SocketExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEBA56991CDA0B8200BA0389 /* SocketExtensions.swift */; }; @@ -175,6 +185,13 @@ 749642B41D3FCE5500DD32D1 /* WebSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = WebSocket.swift; path = Source/WebSocket.swift; sourceTree = ""; }; 74ABF7761C3991C10078C657 /* SocketIOClientSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SocketIOClientSpec.swift; path = Source/SocketIOClientSpec.swift; sourceTree = ""; }; 74BC45AA1D0C6675008CC431 /* SocketClientManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SocketClientManager.swift; path = Source/SocketClientManager.swift; sourceTree = ""; }; + 74DA216C1F09438D009C19EE /* include.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = include.h; path = zlib/include.h; sourceTree = ""; }; + 74DA21711F094408009C19EE /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.3.sdk/usr/lib/libz.tbd; sourceTree = DEVELOPER_DIR; }; + 74DA21731F09440F009C19EE /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; + 74DA21751F094417009C19EE /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.2.sdk/usr/lib/libz.tbd; sourceTree = DEVELOPER_DIR; }; + 74DA21771F09444E009C19EE /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; name = module.modulemap; path = zlib/module.modulemap; sourceTree = ""; }; + 74DA217D1F0945E9009C19EE /* libcommonCrypto.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libcommonCrypto.tbd; path = usr/lib/system/libcommonCrypto.tbd; sourceTree = SDKROOT; }; + 74DA21801F094887009C19EE /* Compression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Compression.swift; path = Source/Compression.swift; sourceTree = ""; }; 74F124EF1BC574CF002966F4 /* SocketBasicPacketTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketBasicPacketTest.swift; sourceTree = ""; }; CEBA56991CDA0B8200BA0389 /* SocketExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SocketExtensions.swift; path = Source/SocketExtensions.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -184,6 +201,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 74DA21721F094408009C19EE /* libz.tbd in Frameworks */, 6CA08A961D615C040061FD2A /* Security.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -200,6 +218,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 74DA21741F09440F009C19EE /* libz.tbd in Frameworks */, 6CA08A981D615C0B0061FD2A /* Security.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -208,6 +227,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 74DA217C1F09457B009C19EE /* libz.tbd in Frameworks */, 572EF2431B51F18A00EEBB58 /* SocketIO.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -216,6 +236,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 74DA21761F094417009C19EE /* libz.tbd in Frameworks */, 6CA08A9A1D615C140061FD2A /* Security.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -234,6 +255,7 @@ 572EF20D1B51F12F00EEBB58 = { isa = PBXGroup; children = ( + 74DA216B1F094371009C19EE /* zlib */, 6CA08A9B1D615C190061FD2A /* Frameworks */, 572EF21A1B51F16C00EEBB58 /* Products */, 572EF21B1B51F16C00EEBB58 /* SocketIO-iOS */, @@ -347,6 +369,10 @@ 6CA08A9B1D615C190061FD2A /* Frameworks */ = { isa = PBXGroup; children = ( + 74DA217D1F0945E9009C19EE /* libcommonCrypto.tbd */, + 74DA21751F094417009C19EE /* libz.tbd */, + 74DA21731F09440F009C19EE /* libz.tbd */, + 74DA21711F094408009C19EE /* libz.tbd */, 6CA08A9E1D615C340061FD2A /* tvOS */, 6CA08A9D1D615C2C0061FD2A /* Mac */, 6CA08A9C1D615C270061FD2A /* iOS */, @@ -381,12 +407,22 @@ 74B4AD1B1D09A5C30062A523 /* Websocket */ = { isa = PBXGroup; children = ( + 74DA21801F094887009C19EE /* Compression.swift */, 749642B31D3FCE5500DD32D1 /* SSLSecurity.swift */, 749642B41D3FCE5500DD32D1 /* WebSocket.swift */, ); name = Websocket; sourceTree = ""; }; + 74DA216B1F094371009C19EE /* zlib */ = { + isa = PBXGroup; + children = ( + 74DA216C1F09438D009C19EE /* include.h */, + 74DA21771F09444E009C19EE /* module.modulemap */, + ); + name = zlib; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -395,6 +431,7 @@ buildActionMask = 2147483647; files = ( 572EF21F1B51F16C00EEBB58 /* SocketIO.h in Headers */, + 74DA21701F0943F8009C19EE /* include.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -403,6 +440,7 @@ buildActionMask = 2147483647; files = ( 572EF23D1B51F18A00EEBB58 /* SocketIO-Mac.h in Headers */, + 74DA216F1F0943F4009C19EE /* include.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -411,6 +449,7 @@ buildActionMask = 2147483647; files = ( 57634A111BD9B46A00E19CD7 /* SocketIO.h in Headers */, + 74DA216E1F0943EE009C19EE /* include.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -643,6 +682,7 @@ 749642B51D3FCE5500DD32D1 /* SSLSecurity.swift in Sources */, 74171EB71C10CD240062D398 /* SocketParsable.swift in Sources */, 74171E811C10CD240062D398 /* SocketEnginePacketType.swift in Sources */, + 74DA21811F094887009C19EE /* Compression.swift in Sources */, 74171E6F1C10CD240062D398 /* SocketAnyEvent.swift in Sources */, 747BC5991D5F943500CA5FA4 /* SocketIOClientConfiguration.swift in Sources */, 74171E9F1C10CD240062D398 /* SocketIOClientOption.swift in Sources */, @@ -685,6 +725,7 @@ 749642B61D3FCE5500DD32D1 /* SSLSecurity.swift in Sources */, 74171EB91C10CD240062D398 /* SocketParsable.swift in Sources */, 74171E831C10CD240062D398 /* SocketEnginePacketType.swift in Sources */, + 74DA21821F094887009C19EE /* Compression.swift in Sources */, 74171E711C10CD240062D398 /* SocketAnyEvent.swift in Sources */, 747BC59A1D5F943500CA5FA4 /* SocketIOClientConfiguration.swift in Sources */, 74171EA11C10CD240062D398 /* SocketIOClientOption.swift in Sources */, @@ -731,6 +772,7 @@ 749642B71D3FCE5500DD32D1 /* SSLSecurity.swift in Sources */, 74171EBB1C10CD240062D398 /* SocketParsable.swift in Sources */, 74171E851C10CD240062D398 /* SocketEnginePacketType.swift in Sources */, + 74DA21831F094887009C19EE /* Compression.swift in Sources */, 74171E731C10CD240062D398 /* SocketAnyEvent.swift in Sources */, 747BC59B1D5F943500CA5FA4 /* SocketIOClientConfiguration.swift in Sources */, 74171EA31C10CD240062D398 /* SocketIOClientOption.swift in Sources */, @@ -885,6 +927,7 @@ PRODUCT_BUNDLE_IDENTIFIER = io.socket.SocketIOClientSwift; SDKROOT = iphoneos; SKIP_INSTALL = YES; + SWIFT_INCLUDE_PATHS = $SRCROOT/zlib; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -937,6 +980,7 @@ PRODUCT_BUNDLE_IDENTIFIER = io.socket.SocketIOClientSwift; SDKROOT = iphoneos; SKIP_INSTALL = YES; + SWIFT_INCLUDE_PATHS = $SRCROOT/zlib; SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; @@ -1088,11 +1132,16 @@ INFOPLIST_FILE = "SocketIO-Mac/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(SDKROOT)/usr/lib/system", + ); MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = "io.socket.$(PRODUCT_NAME:rfc1034identifier)"; SDKROOT = macosx; SKIP_INSTALL = YES; + SWIFT_INCLUDE_PATHS = $SRCROOT/zlib; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 3.0; VERSIONING_SYSTEM = "apple-generic"; @@ -1142,10 +1191,15 @@ INFOPLIST_FILE = "SocketIO-Mac/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(SDKROOT)/usr/lib/system", + ); MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "io.socket.$(PRODUCT_NAME:rfc1034identifier)"; SDKROOT = macosx; SKIP_INSTALL = YES; + SWIFT_INCLUDE_PATHS = $SRCROOT/zlib; SWIFT_VERSION = 3.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -1196,6 +1250,10 @@ GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = "SocketIO-MacTests/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(SDKROOT)/usr/lib/system", + ); MACOSX_DEPLOYMENT_TARGET = 10.10; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; @@ -1245,6 +1303,10 @@ GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = "SocketIO-MacTests/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(SDKROOT)/usr/lib/system", + ); MACOSX_DEPLOYMENT_TARGET = 10.10; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "io.socket.$(PRODUCT_NAME:rfc1034identifier)"; @@ -1304,6 +1366,7 @@ PRODUCT_NAME = SocketIO; SDKROOT = appletvos; SKIP_INSTALL = YES; + SWIFT_INCLUDE_PATHS = $SRCROOT/zlib; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = 3; TVOS_DEPLOYMENT_TARGET = 9.0; @@ -1356,6 +1419,7 @@ PRODUCT_NAME = SocketIO; SDKROOT = appletvos; SKIP_INSTALL = YES; + SWIFT_INCLUDE_PATHS = $SRCROOT/zlib; TARGETED_DEVICE_FAMILY = 3; TVOS_DEPLOYMENT_TARGET = 9.0; VALIDATE_PRODUCT = YES; diff --git a/Source/SocketEngine.swift b/Source/SocketEngine.swift index 5c1f841..fc43ea3 100644 --- a/Source/SocketEngine.swift +++ b/Source/SocketEngine.swift @@ -331,7 +331,6 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll } ws?.callbackQueue = engineQueue - ws?.voipEnabled = voipEnabled ws?.delegate = self ws?.disableSSLCertValidation = selfSigned ws?.security = security diff --git a/Source/WebSocket.swift b/Source/WebSocket.swift index f57ec3d..38a25bd 100644 --- a/Source/WebSocket.swift +++ b/Source/WebSocket.swift @@ -20,7 +20,7 @@ ////////////////////////////////////////////////////////////////////////////////////////////////// import Foundation import CoreFoundation -import Security +import CommonCrypto public let WebsocketDidConnectNotification = "WebsocketDidConnectNotification" public let WebsocketDidDisconnectNotification = "WebsocketDidDisconnectNotification" @@ -37,9 +37,19 @@ public protocol WebSocketPongDelegate: class { func websocketDidReceivePong(socket: WebSocket, data: Data?) } +// A Delegate with more advanced info on messages and connection etc. +public protocol WebSocketAdvancedDelegate: class { + func websocketDidConnect(socket: WebSocket) + func websocketDidDisconnect(socket: WebSocket, error: NSError?) + func websocketDidReceiveMessage(socket: WebSocket, text: String, response: WebSocket.WSResponse) + func websocketDidReceiveData(socket: WebSocket, data: Data, response: WebSocket.WSResponse) + func websocketHttpUpgrade(socket: WebSocket, request: CFHTTPMessage) + func websocketHttpUpgrade(socket: WebSocket, response: CFHTTPMessage) +} + open class WebSocket : NSObject, StreamDelegate { - enum OpCode : UInt8 { + public enum OpCode : UInt8 { case continueFrame = 0x0 case textFrame = 0x1 case binaryFrame = 0x2 @@ -68,6 +78,9 @@ open class WebSocket : NSObject, StreamDelegate { enum InternalErrorCode: UInt16 { // 0-999 WebSocket status codes not used case outputStreamWriteError = 1 + case compressionError = 2 + case invalidSSLError = 3 + case writeTimeoutError = 4 } // Where the callback is executed. It defaults to the main UI thread queue. @@ -84,6 +97,7 @@ open class WebSocket : NSObject, StreamDelegate { let headerWSProtocolName = "Sec-WebSocket-Protocol" let headerWSVersionName = "Sec-WebSocket-Version" let headerWSVersionValue = "13" + let headerWSExtensionName = "Sec-WebSocket-Extensions" let headerWSKeyName = "Sec-WebSocket-Key" let headerOriginName = "Origin" let headerWSAcceptName = "Sec-WebSocket-Accept" @@ -91,18 +105,22 @@ open class WebSocket : NSObject, StreamDelegate { let FinMask: UInt8 = 0x80 let OpCodeMask: UInt8 = 0x0F let RSVMask: UInt8 = 0x70 + let RSV1Mask: UInt8 = 0x40 let MaskMask: UInt8 = 0x80 let PayloadLenMask: UInt8 = 0x7F let MaxFrameSize: Int = 32 let httpSwitchProtocolCode = 101 let supportedSSLSchemes = ["wss", "https"] - class WSResponse { + public class WSResponse { var isFin = false - var code: OpCode = .continueFrame + public var code: OpCode = .continueFrame var bytesLeft = 0 - var frameCount = 0 - var buffer: NSMutableData? + public var frameCount = 0 + public var buffer: NSMutableData? + public let firstFrame = { + return Date() + }() } // MARK: - Delegates @@ -110,20 +128,46 @@ open class WebSocket : NSObject, StreamDelegate { /// and also connection/disconnect messages. public weak var delegate: WebSocketDelegate? + /// The optional advanced delegate can be used insteadof of the delegate + public weak var advancedDelegate: WebSocketAdvancedDelegate? + /// Receives a callback for each pong message recived. public weak var pongDelegate: WebSocketPongDelegate? // MARK: - Block based API. + public enum HTTPMethod { + case get + case post + case put + case connect + case custom(value: String) + var representation: String { + switch self { + case .get: + return "GET" + case .post: + return "POST" + case .put: + return "PUT" + case .connect: + return "CONNECT" + case .custom(let value): + return value.capitalized + } + } + } + public var onConnect: (() -> Void)? public var onDisconnect: ((NSError?) -> Void)? public var onText: ((String) -> Void)? public var onData: ((Data) -> Void)? public var onPong: ((Data?) -> Void)? + public var httpMethod: HTTPMethod = .get public var headers = [String: String]() - public var voipEnabled = false public var disableSSLCertValidation = false + public var enableCompression = true public var security: SSLTrustValidator? public var enabledSSLCipherSuites: [SSLCipherSuite]? public var origin: String? @@ -135,11 +179,24 @@ open class WebSocket : NSObject, StreamDelegate { public var currentURL: URL { return url } // MARK: - Private + + private struct CompressionState { + var supportsCompression = false + var messageNeedsDecompression = false + var serverMaxWindowBits = 15 + var clientMaxWindowBits = 15 + var clientNoContextTakeover = false + var serverNoContextTakeover = false + var decompressor:Decompressor? = nil + var compressor:Compressor? = nil + } + private var url: URL private var inputStream: InputStream? private var outputStream: OutputStream? private var connected = false private var isConnecting = false + private var compressionState = CompressionState() private var writeQueue = OperationQueue() private var readStack = [WSResponse]() private var inputQueue = [Data]() @@ -147,6 +204,7 @@ open class WebSocket : NSObject, StreamDelegate { private var certValidated = false private var didDisconnect = false private var readyToWrite = false + private var headerSecKey = "" private let mutex = NSLock() private let notificationCenter = NotificationCenter.default private var canDispatch: Bool { @@ -246,7 +304,7 @@ open class WebSocket : NSObject, StreamDelegate { Private method that starts the connection. */ private func createHTTPRequest() { - let urlRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, "GET" as CFString, + let urlRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, httpMethod.representation as CFString, url as CFURL, kCFHTTPVersion1_1).takeRetainedValue() var port = url.port @@ -262,11 +320,16 @@ open class WebSocket : NSObject, StreamDelegate { if let protocols = optionalProtocols { addHeader(urlRequest, key: headerWSProtocolName, val: protocols.joined(separator: ",")) } + headerSecKey = generateWebSocketKey() addHeader(urlRequest, key: headerWSVersionName, val: headerWSVersionValue) - addHeader(urlRequest, key: headerWSKeyName, val: generateWebSocketKey()) + addHeader(urlRequest, key: headerWSKeyName, val: headerSecKey) if let origin = origin { addHeader(urlRequest, key: headerOriginName, val: origin) } + if enableCompression { + let val = "permessage-deflate; client_max_window_bits; server_max_window_bits=15" + addHeader(urlRequest, key: headerWSExtensionName, val: val) + } addHeader(urlRequest, key: headerWSHostName, val: "\(url.host!):\(port!)") for (key, value) in headers { addHeader(urlRequest, key: key, val: value) @@ -274,6 +337,7 @@ open class WebSocket : NSObject, StreamDelegate { if let cfHTTPMessage = CFHTTPMessageCopySerializedMessage(urlRequest) { let serializedRequest = cfHTTPMessage.takeRetainedValue() initStreamsWithData(serializedRequest as Data, Int(port!)) + self.advancedDelegate?.websocketHttpUpgrade(socket: self, request: urlRequest) } } @@ -346,10 +410,6 @@ open class WebSocket : NSObject, StreamDelegate { } else { certValidated = true //not a https session, so no need to check SSL pinning } - if voipEnabled { - inStream.setProperty(StreamNetworkServiceTypeValue.voIP as AnyObject, forKey: Stream.PropertyKey.networkServiceType) - outStream.setProperty(StreamNetworkServiceTypeValue.voIP as AnyObject, forKey: Stream.PropertyKey.networkServiceType) - } CFReadStreamSetDispatchQueue(inStream, WebSocket.sharedWorkQueue) CFWriteStreamSetDispatchQueue(outStream, WebSocket.sharedWorkQueue) @@ -373,7 +433,8 @@ open class WebSocket : NSObject, StreamDelegate { WebSocket.sharedWorkQueue.async { self?.cleanupStream() } - self?.doDisconnect(self?.errorWithDetail("write wait timed out", code: 2)) + let errCode = InternalErrorCode.writeTimeoutError.rawValue + self?.doDisconnect(self?.errorWithDetail("write wait timed out", code: errCode)) return } else if outStream.streamError != nil { return // disconnectStream will be called. @@ -382,12 +443,16 @@ open class WebSocket : NSObject, StreamDelegate { guard !sOperation.isCancelled, let s = self else { return } // Do the pinning now if needed if let sec = s.security, !s.certValidated { - let trust = outStream.property(forKey: kCFStreamPropertySSLPeerTrust as Stream.PropertyKey) as! SecTrust - let domain = outStream.property(forKey: kCFStreamSSLPeerName as Stream.PropertyKey) as? String - s.certValidated = sec.isValid(trust, domain: domain) + if let possibleTrust = outStream.property(forKey: kCFStreamPropertySSLPeerTrust as Stream.PropertyKey) { + let domain = outStream.property(forKey: kCFStreamSSLPeerName as Stream.PropertyKey) as? String + s.certValidated = sec.isValid(possibleTrust as! SecTrust, domain: domain) + } else { + s.certValidated = false + } if !s.certValidated { WebSocket.sharedWorkQueue.async { - let error = s.errorWithDetail("Invalid SSL certificate", code: 1) + let errCode = InternalErrorCode.invalidSSLError.rawValue + let error = s.errorWithDetail("Invalid SSL certificate", code: errCode) s.disconnectStream(error) } return @@ -539,6 +604,7 @@ open class WebSocket : NSObject, StreamDelegate { guard let s = self else { return } s.onConnect?() s.delegate?.websocketDidConnect(socket: s) + s.advancedDelegate?.websocketDidConnect(socket: s) s.notificationCenter.post(name: NSNotification.Name(WebsocketDidConnectNotification), object: self) } } @@ -559,13 +625,24 @@ open class WebSocket : NSObject, StreamDelegate { let response = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, false).takeRetainedValue() CFHTTPMessageAppendBytes(response, buffer, bufferLen) let code = CFHTTPMessageGetResponseStatusCode(response) + self.advancedDelegate?.websocketHttpUpgrade(socket: self, response: response) if code != httpSwitchProtocolCode { return code } if let cfHeaders = CFHTTPMessageCopyAllHeaderFields(response) { let headers = cfHeaders.takeRetainedValue() as NSDictionary + if let extensionHeader = headers[headerWSExtensionName as NSString] as? String { + processExtensionHeader(extensionHeader) + } + if let acceptKey = headers[headerWSAcceptName as NSString] as? NSString { if acceptKey.length > 0 { + if headerSecKey.characters.count > 0 { + let sha = "\(headerSecKey)258EAFA5-E914-47DA-95CA-C5AB0DC85B11".sha1Base64() + if sha != acceptKey as String { + return -1 + } + } return 0 } } @@ -573,6 +650,37 @@ open class WebSocket : NSObject, StreamDelegate { return -1 } + /** + Parses the extension header, setting up the compression parameters. + */ + func processExtensionHeader(_ extensionHeader: String) { + let parts = extensionHeader.components(separatedBy: ";") + for p in parts { + let part = p.trimmingCharacters(in: .whitespaces) + if part == "permessage-deflate" { + compressionState.supportsCompression = true + } else if part.hasPrefix("server_max_window_bits=") { + let valString = part.components(separatedBy: "=")[1] + if let val = Int(valString.trimmingCharacters(in: .whitespaces)) { + compressionState.serverMaxWindowBits = val + } + } else if part.hasPrefix("client_max_window_bits=") { + let valString = part.components(separatedBy: "=")[1] + if let val = Int(valString.trimmingCharacters(in: .whitespaces)) { + compressionState.clientMaxWindowBits = val + } + } else if part == "client_no_context_takeover" { + compressionState.clientNoContextTakeover = true + } else if part == "server_no_context_takeover" { + compressionState.serverNoContextTakeover = true + } + } + if compressionState.supportsCompression { + compressionState.decompressor = Decompressor(windowBits: compressionState.serverMaxWindowBits) + compressionState.compressor = Compressor(windowBits: compressionState.clientMaxWindowBits) + } + } + /** Read a 16 bit big endian value from a buffer */ @@ -637,7 +745,10 @@ open class WebSocket : NSObject, StreamDelegate { let isMasked = (MaskMask & baseAddress[1]) let payloadLen = (PayloadLenMask & baseAddress[1]) var offset = 2 - if (isMasked > 0 || (RSVMask & baseAddress[0]) > 0) && receivedOpcode != .pong { + if compressionState.supportsCompression && receivedOpcode != .continueFrame { + compressionState.messageNeedsDecompression = (RSV1Mask & baseAddress[0]) > 0 + } + if (isMasked > 0 || (RSVMask & baseAddress[0]) > 0) && receivedOpcode != .pong && !compressionState.messageNeedsDecompression { let errCode = CloseCode.protocolError.rawValue doDisconnect(errorWithDetail("masked and rsv data is not currently supported", code: errCode)) writeError(errCode) @@ -697,7 +808,23 @@ open class WebSocket : NSObject, StreamDelegate { offset += size len -= UInt64(size) } - let data = Data(bytes: baseAddress+offset, count: Int(len)) + let data: Data + if compressionState.messageNeedsDecompression, let decompressor = compressionState.decompressor { + do { + data = try decompressor.decompress(bytes: baseAddress+offset, count: Int(len), finish: isFin > 0) + if isFin > 0 && compressionState.serverNoContextTakeover{ + try decompressor.reset() + } + } catch { + let closeReason = "Decompression failed: \(error)" + let closeCode = CloseCode.encoding.rawValue + doDisconnect(errorWithDetail(closeReason, code: closeCode)) + writeError(closeCode) + return emptyBuffer + } + } else { + data = Data(bytes: baseAddress+offset, count: Int(len)) + } if receivedOpcode == .connectionClose { var closeReason = "connection closed by server" @@ -804,6 +931,7 @@ open class WebSocket : NSObject, StreamDelegate { guard let s = self else { return } s.onText?(str! as String) s.delegate?.websocketDidReceiveMessage(socket: s, text: str! as String) + s.advancedDelegate?.websocketDidReceiveMessage(socket: s, text: str! as String, response: response) } } } else if response.code == .binaryFrame { @@ -813,6 +941,7 @@ open class WebSocket : NSObject, StreamDelegate { guard let s = self else { return } s.onData?(data as Data) s.delegate?.websocketDidReceiveData(socket: s, data: data as Data) + s.advancedDelegate?.websocketDidReceiveData(socket: s, data: data as Data, response: response) } } } @@ -851,10 +980,23 @@ open class WebSocket : NSObject, StreamDelegate { guard let s = self else { return } guard let sOperation = operation else { return } var offset = 2 + var firstByte:UInt8 = s.FinMask | code.rawValue + var data = data + if [.textFrame, .binaryFrame].contains(code), let compressor = s.compressionState.compressor { + do { + data = try compressor.compress(data) + if s.compressionState.clientNoContextTakeover { + try compressor.reset() + } + firstByte |= s.RSV1Mask + } catch { + // TODO: report error? We can just send the uncompressed frame. + } + } let dataLength = data.count let frame = NSMutableData(capacity: dataLength + s.MaxFrameSize) let buffer = UnsafeMutableRawPointer(frame!.mutableBytes).assumingMemoryBound(to: UInt8.self) - buffer[0] = s.FinMask | code.rawValue + buffer[0] = firstByte if dataLength < 126 { buffer[1] = CUnsignedChar(dataLength) } else if dataLength <= Int(UInt16.max) { @@ -920,6 +1062,7 @@ open class WebSocket : NSObject, StreamDelegate { guard let s = self else { return } s.onDisconnect?(error) s.delegate?.websocketDidDisconnect(socket: s, error: error) + s.advancedDelegate?.websocketDidDisconnect(socket: s, error: error) let userInfo = error.map{ [WebsocketDisconnectionErrorKeyName: $0] } s.notificationCenter.post(name: NSNotification.Name(WebsocketDidDisconnectNotification), object: self, userInfo: userInfo) } @@ -936,6 +1079,15 @@ open class WebSocket : NSObject, StreamDelegate { } +private extension String { + func sha1Base64() -> String { + let data = self.data(using: String.Encoding.utf8)! + var digest = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH)) + data.withUnsafeBytes { _ = CC_SHA1($0, CC_LONG(data.count), &digest) } + return Data(bytes: digest).base64EncodedString() + } +} + private extension Data { init(buffer: UnsafeBufferPointer) {