diff --git a/README.md b/README.md index de43c9d..056b0f5 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ Carthage ----------------- Add this line to your `Cartfile`: ``` -github "socketio/socket.io-client-swift" ~> 6.1.1 # Or latest version +github "socketio/socket.io-client-swift" ~> 6.1.2 # Or latest version ``` Run `carthage update --platform ios,macosx`. @@ -102,7 +102,7 @@ source 'https://github.com/CocoaPods/Specs.git' platform :ios, '8.0' use_frameworks! -pod 'Socket.IO-Client-Swift', '~> 6.1.1' # Or latest version +pod 'Socket.IO-Client-Swift', '~> 6.1.2' # Or latest version ``` Install pods: @@ -130,7 +130,7 @@ CocoaSeeds Add this line to your `Seedfile`: ``` -github "socketio/socket.io-client-swift", "v6.1.1", :files => "Source/*.swift" # Or latest version +github "socketio/socket.io-client-swift", "v6.1.2", :files => "Source/*.swift" # Or latest version ``` Run `seed install`. diff --git a/Socket.IO-Client-Swift.podspec b/Socket.IO-Client-Swift.podspec index 248adc5..ddf7a2d 100644 --- a/Socket.IO-Client-Swift.podspec +++ b/Socket.IO-Client-Swift.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "Socket.IO-Client-Swift" s.module_name = "SocketIOClientSwift" - s.version = "6.1.1" + s.version = "6.1.2" s.summary = "Socket.IO-client for iOS and OS X" s.description = <<-DESC Socket.IO-client for iOS and OS X. @@ -14,7 +14,7 @@ Pod::Spec.new do |s| s.ios.deployment_target = '8.0' s.osx.deployment_target = '10.10' s.tvos.deployment_target = '9.0' - s.source = { :git => "https://github.com/socketio/socket.io-client-swift.git", :tag => 'v6.1.1' } + s.source = { :git => "https://github.com/socketio/socket.io-client-swift.git", :tag => 'v6.1.2' } s.source_files = "Source/**/*.swift" s.requires_arc = true # s.dependency 'Starscream', '~> 0.9' # currently this repo includes Starscream swift files diff --git a/Socket.IO-Client-Swift.xcodeproj/project.pbxproj b/Socket.IO-Client-Swift.xcodeproj/project.pbxproj index 97318d8..c373933 100644 --- a/Socket.IO-Client-Swift.xcodeproj/project.pbxproj +++ b/Socket.IO-Client-Swift.xcodeproj/project.pbxproj @@ -101,11 +101,6 @@ 74171EC51C10CD240062D398 /* SocketTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74171E601C10CD240062D398 /* SocketTypes.swift */; }; 74171EC71C10CD240062D398 /* SocketTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74171E601C10CD240062D398 /* SocketTypes.swift */; }; 74171EC81C10CD240062D398 /* SocketTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74171E601C10CD240062D398 /* SocketTypes.swift */; }; - 74171EC91C10CD240062D398 /* SwiftRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74171E611C10CD240062D398 /* SwiftRegex.swift */; }; - 74171ECA1C10CD240062D398 /* SwiftRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74171E611C10CD240062D398 /* SwiftRegex.swift */; }; - 74171ECB1C10CD240062D398 /* SwiftRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74171E611C10CD240062D398 /* SwiftRegex.swift */; }; - 74171ECD1C10CD240062D398 /* SwiftRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74171E611C10CD240062D398 /* SwiftRegex.swift */; }; - 74171ECE1C10CD240062D398 /* SwiftRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74171E611C10CD240062D398 /* SwiftRegex.swift */; }; 74171ECF1C10CD240062D398 /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74171E621C10CD240062D398 /* WebSocket.swift */; }; 74171ED01C10CD240062D398 /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74171E621C10CD240062D398 /* WebSocket.swift */; }; 74171ED11C10CD240062D398 /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74171E621C10CD240062D398 /* WebSocket.swift */; }; @@ -190,7 +185,6 @@ 74171E5E1C10CD240062D398 /* SocketParsable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SocketParsable.swift; path = Source/SocketParsable.swift; sourceTree = ""; }; 74171E5F1C10CD240062D398 /* SocketStringReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SocketStringReader.swift; path = Source/SocketStringReader.swift; sourceTree = ""; }; 74171E601C10CD240062D398 /* SocketTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SocketTypes.swift; path = Source/SocketTypes.swift; sourceTree = ""; }; - 74171E611C10CD240062D398 /* SwiftRegex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftRegex.swift; path = Source/SwiftRegex.swift; sourceTree = ""; }; 74171E621C10CD240062D398 /* WebSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = WebSocket.swift; path = Source/WebSocket.swift; sourceTree = ""; }; 741F39ED1BD025D80026C9CC /* SocketEngineTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEngineTest.swift; sourceTree = ""; }; 7420CB781C49629E00956AA4 /* SocketEnginePollable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SocketEnginePollable.swift; path = Source/SocketEnginePollable.swift; sourceTree = ""; }; @@ -375,7 +369,6 @@ 74171E5E1C10CD240062D398 /* SocketParsable.swift */, 74171E5F1C10CD240062D398 /* SocketStringReader.swift */, 74171E601C10CD240062D398 /* SocketTypes.swift */, - 74171E611C10CD240062D398 /* SwiftRegex.swift */, 74171E621C10CD240062D398 /* WebSocket.swift */, ); name = Source; @@ -638,7 +631,6 @@ 74171EAB1C10CD240062D398 /* SocketLogger.swift in Sources */, 74171E991C10CD240062D398 /* SocketIOClient.swift in Sources */, 74171E8D1C10CD240062D398 /* SocketEventHandler.swift in Sources */, - 74171EC91C10CD240062D398 /* SwiftRegex.swift in Sources */, 74171E7B1C10CD240062D398 /* SocketEngineClient.swift in Sources */, 74171ECF1C10CD240062D398 /* WebSocket.swift in Sources */, 74171EB11C10CD240062D398 /* SocketPacket.swift in Sources */, @@ -663,7 +655,6 @@ 74171E8E1C10CD240062D398 /* SocketEventHandler.swift in Sources */, 74171E7C1C10CD240062D398 /* SocketEngineClient.swift in Sources */, 74171E821C10CD240062D398 /* SocketEnginePacketType.swift in Sources */, - 74171ECA1C10CD240062D398 /* SwiftRegex.swift in Sources */, 74171EB21C10CD240062D398 /* SocketPacket.swift in Sources */, 741F39EE1BD025D80026C9CC /* SocketEngineTest.swift in Sources */, 74171EBE1C10CD240062D398 /* SocketStringReader.swift in Sources */, @@ -697,7 +688,6 @@ 74171EAD1C10CD240062D398 /* SocketLogger.swift in Sources */, 74171E9B1C10CD240062D398 /* SocketIOClient.swift in Sources */, 74171E8F1C10CD240062D398 /* SocketEventHandler.swift in Sources */, - 74171ECB1C10CD240062D398 /* SwiftRegex.swift in Sources */, 74171E7D1C10CD240062D398 /* SocketEngineClient.swift in Sources */, 74171ED11C10CD240062D398 /* WebSocket.swift in Sources */, 74171EB31C10CD240062D398 /* SocketPacket.swift in Sources */, @@ -742,7 +732,6 @@ 74171EAF1C10CD240062D398 /* SocketLogger.swift in Sources */, 74171E9D1C10CD240062D398 /* SocketIOClient.swift in Sources */, 74171E911C10CD240062D398 /* SocketEventHandler.swift in Sources */, - 74171ECD1C10CD240062D398 /* SwiftRegex.swift in Sources */, 74171E7F1C10CD240062D398 /* SocketEngineClient.swift in Sources */, 74171ED31C10CD240062D398 /* WebSocket.swift in Sources */, 74171EB51C10CD240062D398 /* SocketPacket.swift in Sources */, @@ -767,7 +756,6 @@ 74171E921C10CD240062D398 /* SocketEventHandler.swift in Sources */, 74171E801C10CD240062D398 /* SocketEngineClient.swift in Sources */, 74171E861C10CD240062D398 /* SocketEnginePacketType.swift in Sources */, - 74171ECE1C10CD240062D398 /* SwiftRegex.swift in Sources */, 74171EB61C10CD240062D398 /* SocketPacket.swift in Sources */, 57634A2A1BD9B46D00E19CD7 /* SocketEngineTest.swift in Sources */, 74171EC21C10CD240062D398 /* SocketStringReader.swift in Sources */, diff --git a/SocketIO-MacTests/SocketBasicPacketTest.swift b/SocketIO-MacTests/SocketBasicPacketTest.swift index b4b112d..1161fe3 100644 --- a/SocketIO-MacTests/SocketBasicPacketTest.swift +++ b/SocketIO-MacTests/SocketBasicPacketTest.swift @@ -145,4 +145,20 @@ class SocketBasicPacketTest: XCTestCase { XCTAssertEqual(packet.packetString, expectedSendString) XCTAssertEqual(packet.binary, [data2, data]) } + + func testBinaryStringPlaceholderInMessage() { + let engineString = "52-[\"test\",\"~~0\",{\"num\":0,\"_placeholder\":true},{\"num\":1,\"_placeholder\":true}]" + let socket = SocketIOClient(socketURL: NSURL()) + socket.setTestable() + + if case let .Right(packet) = socket.parseString(engineString) { + var packet = packet + XCTAssertEqual(packet.event, "test") + packet.addData(data) + packet.addData(data2) + XCTAssertEqual(packet.args[0] as? String, "~~0") + } else { + XCTFail() + } + } } diff --git a/SocketIO-MacTests/SocketParserTest.swift b/SocketIO-MacTests/SocketParserTest.swift index b205556..c8bea5c 100644 --- a/SocketIO-MacTests/SocketParserTest.swift +++ b/SocketIO-MacTests/SocketParserTest.swift @@ -18,9 +18,10 @@ class SocketParserTest: XCTestCase { "25[\"test\"]": ("/", ["test"], [], 5), "2[\"test\",\"~~0\"]": ("/", ["test", "~~0"], [], -1), "2/swift,[\"testArrayEmitReturn\",[\"test3\",\"test4\"]]": ("/swift", ["testArrayEmitReturn", ["test3", "test4"]], [], -1), - "51-/swift,[\"testMultipleItemsWithBufferEmitReturn\",[1,2],{\"test\":\"bob\"},25,\"polo\",{\"_placeholder\":true,\"num\":0}]": ("/swift", ["testMultipleItemsWithBufferEmitReturn", [1, 2], ["test": "bob"], 25, "polo", "~~0"], [], -1), + "51-/swift,[\"testMultipleItemsWithBufferEmitReturn\",[1,2],{\"test\":\"bob\"},25,\"polo\",{\"_placeholder\":true,\"num\":0}]": ("/swift", ["testMultipleItemsWithBufferEmitReturn", [1, 2], ["test": "bob"], 25, "polo", ["_placeholder": true, "num": 0]], [], -1), "3/swift,0[[\"test3\",\"test4\"]]": ("/swift", [["test3", "test4"]], [], 0), - "61-/swift,19[[1,2],{\"test\":\"bob\"},25,\"polo\",{\"_placeholder\":true,\"num\":0}]": ("/swift", [ [1, 2], ["test": "bob"], 25, "polo", "~~0"], [], 19), + "61-/swift,19[[1,2],{\"test\":\"bob\"},25,\"polo\",{\"_placeholder\":true,\"num\":0}]": + ("/swift", [ [1, 2], ["test": "bob"], 25, "polo", ["_placeholder": true, "num": 0]], [], 19), "4/swift,": ("/swift", [], [], -1), "0/swift": ("/swift", [], [], -1), "1/swift": ("/swift", [], [], -1), @@ -123,8 +124,8 @@ class SocketParserTest: XCTestCase { if case let .Right(packet) = packet { XCTAssertEqual(packet.type, SocketPacket.PacketType(rawValue: Int(type) ?? -1)!) XCTAssertEqual(packet.nsp, validValues.0) - XCTAssertTrue((packet.data as NSArray).isEqualToArray(validValues.1)) - XCTAssertTrue((packet.binary as NSArray).isEqualToArray(validValues.2)) + XCTAssertTrue((packet.data as NSArray).isEqualToArray(validValues.1), "\(packet.data)") + XCTAssertTrue((packet.binary as NSArray).isEqualToArray(validValues.2), "\(packet.binary)") XCTAssertEqual(packet.id, validValues.3) } else { XCTFail() diff --git a/Source/SocketEngine.swift b/Source/SocketEngine.swift index e06a811..5c59434 100644 --- a/Source/SocketEngine.swift +++ b/Source/SocketEngine.swift @@ -62,9 +62,6 @@ public final class SocketEngine : NSObject, SocketEnginePollable, SocketEngineWe private weak var sessionDelegate: NSURLSessionDelegate? - private typealias Probe = (msg: String, type: SocketEnginePacketType, data: [NSData]) - private typealias ProbeWaitQueue = [Probe] - private let logType = "SocketEngine" private let url: NSURL @@ -74,6 +71,7 @@ public final class SocketEngine : NSObject, SocketEnginePollable, SocketEngineWe pongsMissedMax = Int(pingTimeout / (pingInterval ?? 25)) } } + private var pongsMissed = 0 private var pongsMissedMax = 0 private var probeWait = ProbeWaitQueue() @@ -387,7 +385,7 @@ public final class SocketEngine : NSObject, SocketEnginePollable, SocketEngineWe doPoll() } - client?.engineDidOpen?("Connect") + client?.engineDidOpen("Connect") } } catch { didError("Error parsing open packet") diff --git a/Source/SocketEngineClient.swift b/Source/SocketEngineClient.swift index a1db7f6..e3aecc6 100644 --- a/Source/SocketEngineClient.swift +++ b/Source/SocketEngineClient.swift @@ -28,7 +28,7 @@ import Foundation @objc public protocol SocketEngineClient { func engineDidError(reason: String) func engineDidClose(reason: String) - optional func engineDidOpen(reason: String) + func engineDidOpen(reason: String) func parseEngineMessage(msg: String) func parseEngineBinaryData(data: NSData) } diff --git a/Source/SocketIOClient.swift b/Source/SocketIOClient.swift index 02cda38..835a7b5 100644 --- a/Source/SocketIOClient.swift +++ b/Source/SocketIOClient.swift @@ -191,7 +191,6 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable DefaultSocketLogger.Logger.log("Disconnected: %@", type: logType, args: reason) status = .Disconnected - reconnects = false // Make sure the engine is actually dead. engine?.disconnect(reason) @@ -205,7 +204,6 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable DefaultSocketLogger.Logger.log("Closing socket", type: logType) - reconnects = false didDisconnect("Disconnect") } @@ -286,6 +284,10 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable handleEvent("error", data: [reason], isInternalMessage: true) } + + public func engineDidOpen(reason: String) { + DefaultSocketLogger.Logger.log(reason, type: "SocketEngineClient") + } // Called when the socket gets an ack for something it sent func handleAck(ack: Int, data: [AnyObject]) { @@ -298,9 +300,7 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable /// Causes an event to be handled. Only use if you know what you're doing. public func handleEvent(event: String, data: [AnyObject], isInternalMessage: Bool, withAck ack: Int = -1) { - guard status == .Connected || isInternalMessage else { - return - } + guard status == .Connected || isInternalMessage else { return } DefaultSocketLogger.Logger.log("Handling event: %@ with data: %@", type: logType, args: event, data ?? "") @@ -335,14 +335,14 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable public func off(event: String) { DefaultSocketLogger.Logger.log("Removing handler for event: %@", type: logType, args: event) - handlers = handlers.filter { $0.event != event } + handlers = handlers.filter({ $0.event != event }) } /// Removes a handler with the specified UUID gotten from an `on` or `once` public func off(id id: NSUUID) { DefaultSocketLogger.Logger.log("Removing handler with id: %@", type: logType, args: id) - handlers = handlers.filter { $0.id != id } + handlers = handlers.filter({ $0.id != id }) } /// Adds a handler for an event. diff --git a/Source/SocketPacket.swift b/Source/SocketPacket.swift index 52de38a..e73a2b6 100644 --- a/Source/SocketPacket.swift +++ b/Source/SocketPacket.swift @@ -61,7 +61,7 @@ struct SocketPacket { return createPacketString() } - init(type: SocketPacket.PacketType, data: [AnyObject] = [AnyObject](), id: Int = -1, + init(type: PacketType, data: [AnyObject] = [AnyObject](), id: Int = -1, nsp: String, placeholders: Int = 0, binary: [NSData] = [NSData]()) { self.data = data self.id = id @@ -97,7 +97,7 @@ struct SocketPacket { let jsonSend = try NSJSONSerialization.dataWithJSONObject(data, options: NSJSONWritingOptions(rawValue: 0)) guard let jsonString = String(data: jsonSend, encoding: NSUTF8StringEncoding) else { - return "[]" + return message + "[]" } restOfMessage = jsonString @@ -187,19 +187,21 @@ struct SocketPacket { data = data.map(_fillInPlaceholders) } - // Helper method that looks for placeholder strings + // Helper method that looks for placeholders // If object is a collection it will recurse - // Returns the object if it is not a placeholder string or the corresponding + // Returns the object if it is not a placeholder or the corresponding // binary data private func _fillInPlaceholders(object: AnyObject) -> AnyObject { switch object { - case let string as String where string["~~(\\d)"].groups() != nil: - return binary[Int(string["~~(\\d)"].groups()![1])!] case let dict as NSDictionary: - return dict.reduce(NSMutableDictionary(), combine: {cur, keyValue in - cur[keyValue.0 as! NSCopying] = _fillInPlaceholders(keyValue.1) - return cur - }) + if dict["_placeholder"] as? Bool ?? false { + return binary[dict["num"] as! Int] + } else { + return dict.reduce(NSMutableDictionary(), combine: {cur, keyValue in + cur[keyValue.0 as! NSCopying] = _fillInPlaceholders(keyValue.1) + return cur + }) + } case let arr as [AnyObject]: return arr.map(_fillInPlaceholders) default: @@ -227,7 +229,7 @@ extension SocketPacket { static func packetFromEmit(items: [AnyObject], id: Int, nsp: String, ack: Bool) -> SocketPacket { let (parsedData, binary) = deconstructData(items) let packet = SocketPacket(type: findType(binary.count, ack: ack), data: parsedData, - id: id, nsp: nsp, placeholders: -1, binary: binary) + id: id, nsp: nsp, binary: binary) return packet } diff --git a/Source/SocketParsable.swift b/Source/SocketParsable.swift index c74b160..b6228e2 100644 --- a/Source/SocketParsable.swift +++ b/Source/SocketParsable.swift @@ -35,8 +35,6 @@ extension SocketParsable { private func handleConnect(p: SocketPacket) { if p.nsp == "/" && nsp != "/" { joinNamespace(nsp) - } else if p.nsp != "/" && nsp == "/" { - didConnect() } else { didConnect() } @@ -91,8 +89,7 @@ extension SocketParsable { } if !parser.hasNext { - return .Right(SocketPacket(type: type, id: -1, - nsp: namespace ?? "/", placeholders: placeholders)) + return .Right(SocketPacket(type: type, nsp: namespace, placeholders: placeholders)) } var idString = "" @@ -111,12 +108,11 @@ extension SocketParsable { } let d = message[parser.currentIndex.advancedBy(1).. Void public typealias NormalCallback = ([AnyObject], SocketAckEmitter) -> Void public typealias OnAckCallback = (timeoutAfter: UInt64, callback: AckCallback) -> Void +typealias Probe = (msg: String, type: SocketEnginePacketType, data: [NSData]) +typealias ProbeWaitQueue = [Probe] + enum Either { case Left(E) case Right(V) diff --git a/Source/SwiftRegex.swift b/Source/SwiftRegex.swift deleted file mode 100644 index b704afd..0000000 --- a/Source/SwiftRegex.swift +++ /dev/null @@ -1,195 +0,0 @@ -// -// SwiftRegex.swift -// SwiftRegex -// -// Created by John Holdsworth on 26/06/2014. -// Copyright (c) 2014 John Holdsworth. -// -// $Id: //depot/SwiftRegex/SwiftRegex.swift#37 $ -// -// This code is in the public domain from: -// https://github.com/johnno1962/SwiftRegex -// - -import Foundation - -infix operator <~ { associativity none precedence 130 } - -private let lock = dispatch_semaphore_create(1) -private var swiftRegexCache = [String: NSRegularExpression]() - -internal final class SwiftRegex : NSObject, BooleanType { - var target: String - var regex: NSRegularExpression - - init(target:String, pattern:String, options:NSRegularExpressionOptions?) { - self.target = target - - if dispatch_semaphore_wait(lock, dispatch_time(DISPATCH_TIME_NOW, Int64(10 * NSEC_PER_MSEC))) != 0 { - do { - let regex = try NSRegularExpression(pattern: pattern, options: - NSRegularExpressionOptions.DotMatchesLineSeparators) - self.regex = regex - } catch let error as NSError { - SwiftRegex.failure("Error in pattern: \(pattern) - \(error)") - self.regex = NSRegularExpression() - } - - super.init() - return - } - - if let regex = swiftRegexCache[pattern] { - self.regex = regex - } else { - do { - let regex = try NSRegularExpression(pattern: pattern, options: - NSRegularExpressionOptions.DotMatchesLineSeparators) - swiftRegexCache[pattern] = regex - self.regex = regex - } catch let error as NSError { - SwiftRegex.failure("Error in pattern: \(pattern) - \(error)") - self.regex = NSRegularExpression() - } - } - dispatch_semaphore_signal(lock) - super.init() - } - - private static func failure(message: String) { - fatalError("SwiftRegex: \(message)") - } - - private var targetRange: NSRange { - return NSRange(location: 0,length: target.utf16.count) - } - - private func substring(range: NSRange) -> String? { - if range.location != NSNotFound { - return (target as NSString).substringWithRange(range) - } else { - return nil - } - } - - func doesMatch(options: NSMatchingOptions!) -> Bool { - return range(options).location != NSNotFound - } - - func range(options: NSMatchingOptions) -> NSRange { - return regex.rangeOfFirstMatchInString(target as String, options: [], range: targetRange) - } - - func match(options: NSMatchingOptions) -> String? { - return substring(range(options)) - } - - func groups() -> [String]? { - return groupsForMatch(regex.firstMatchInString(target as String, options: - NSMatchingOptions.WithoutAnchoringBounds, range: targetRange)) - } - - private func groupsForMatch(match: NSTextCheckingResult?) -> [String]? { - guard let match = match else { - return nil - } - var groups = [String]() - for groupno in 0...regex.numberOfCaptureGroups { - if let group = substring(match.rangeAtIndex(groupno)) { - groups += [group] - } else { - groups += ["_"] // avoids bridging problems - } - } - return groups - } - - subscript(groupno: Int) -> String? { - get { - return groups()?[groupno] - } - - set(newValue) { - if newValue == nil { - return - } - - for match in Array(matchResults().reverse()) { - let replacement = regex.replacementStringForResult(match, - inString: target as String, offset: 0, template: newValue!) - let mut = NSMutableString(string: target) - mut.replaceCharactersInRange(match.rangeAtIndex(groupno), withString: replacement) - - target = mut as String - } - } - } - - func matchResults() -> [NSTextCheckingResult] { - let matches = regex.matchesInString(target as String, options: - NSMatchingOptions.WithoutAnchoringBounds, range: targetRange) - as [NSTextCheckingResult] - - return matches - } - - func ranges() -> [NSRange] { - return matchResults().map { $0.range } - } - - func matches() -> [String] { - return matchResults().map( { self.substring($0.range)!}) - } - - func allGroups() -> [[String]?] { - return matchResults().map { self.groupsForMatch($0) } - } - - func dictionary(options: NSMatchingOptions!) -> Dictionary { - var out = Dictionary() - for match in matchResults() { - out[substring(match.rangeAtIndex(1))!] = substring(match.rangeAtIndex(2))! - } - return out - } - - func substituteMatches(substitution: ((NSTextCheckingResult, UnsafeMutablePointer) -> String), - options:NSMatchingOptions) -> String { - let out = NSMutableString() - var pos = 0 - - regex.enumerateMatchesInString(target as String, options: options, range: targetRange ) {match, flags, stop in - let matchRange = match!.range - out.appendString( self.substring(NSRange(location:pos, length:matchRange.location-pos))!) - out.appendString( substitution(match!, stop) ) - pos = matchRange.location + matchRange.length - } - - out.appendString(substring(NSRange(location:pos, length:targetRange.length-pos))!) - - return out as String - } - - var boolValue: Bool { - return doesMatch(nil) - } -} - -extension String { - subscript(pattern: String, options: NSRegularExpressionOptions) -> SwiftRegex { - return SwiftRegex(target: self, pattern: pattern, options: options) - } -} - -extension String { - subscript(pattern: String) -> SwiftRegex { - return SwiftRegex(target: self, pattern: pattern, options: nil) - } -} - -func <~ (left: SwiftRegex, right: String) -> String { - return left.substituteMatches({match, stop in - return left.regex.replacementStringForResult( match, - inString: left.target as String, offset: 0, template: right ) - }, options: []) -} diff --git a/Source/WebSocket.swift b/Source/WebSocket.swift index 833eece..8db427b 100644 --- a/Source/WebSocket.swift +++ b/Source/WebSocket.swift @@ -112,6 +112,7 @@ public class WebSocket : NSObject, NSStreamDelegate { public var security: SSLSecurity? public var enabledSSLCipherSuites: [SSLCipherSuite]? public var origin: String? + public var timeout = 5 public var isConnected :Bool { return connected } @@ -319,12 +320,12 @@ public class WebSocket : NSObject, NSStreamDelegate { self.mutex.unlock() let bytes = UnsafePointer(data.bytes) - var timeout = 5000000 //wait 5 seconds before giving up + var out = timeout * 1000000 //wait 5 seconds before giving up writeQueue.addOperationWithBlock { [weak self] in while !outStream.hasSpaceAvailable { usleep(100) //wait until the socket is ready - timeout -= 100 - if timeout < 0 { + out -= 100 + if out < 0 { self?.cleanupStream() self?.doDisconnect(self?.errorWithDetail("write wait timed out", code: 2)) return @@ -405,25 +406,24 @@ public class WebSocket : NSObject, NSStreamDelegate { } ///dequeue the incoming input so it is processed in order private func dequeueInput() { - guard !inputQueue.isEmpty else { return } - - let data = inputQueue[0] - var work = data - if let fragBuffer = fragBuffer { - let combine = NSMutableData(data: fragBuffer) - combine.appendData(data) - work = combine - self.fragBuffer = nil + while !inputQueue.isEmpty { + let data = inputQueue[0] + var work = data + if let fragBuffer = fragBuffer { + let combine = NSMutableData(data: fragBuffer) + combine.appendData(data) + work = combine + self.fragBuffer = nil + } + let buffer = UnsafePointer(work.bytes) + let length = work.length + if !connected { + processTCPHandshake(buffer, bufferLen: length) + } else { + processRawMessagesInBuffer(buffer, bufferLen: length) + } + inputQueue = inputQueue.filter{$0 != data} } - let buffer = UnsafePointer(work.bytes) - let length = work.length - if !connected { - processTCPHandshake(buffer, bufferLen: length) - } else { - processRawMessage(buffer, bufferLen: length) - } - inputQueue = inputQueue.filter{$0 != data} - dequeueInput() } //handle checking the inital connection status @@ -469,7 +469,7 @@ public class WebSocket : NSObject, NSStreamDelegate { totalSize += 1 //skip the last \n let restSize = bufferLen - totalSize if restSize > 0 { - processRawMessage((buffer+totalSize),bufferLen: restSize) + processRawMessagesInBuffer(buffer + totalSize, bufferLen: restSize) } return 0 //success } @@ -522,12 +522,15 @@ public class WebSocket : NSObject, NSStreamDelegate { } } - ///process the websocket data - private func processRawMessage(buffer: UnsafePointer, bufferLen: Int) { + /// Process one message at the start of `buffer`. Return another buffer (sharing storage) that contains the leftover contents of `buffer` that I didn't process. + @warn_unused_result + private func processOneRawMessage(inBuffer buffer: UnsafeBufferPointer) -> UnsafeBufferPointer { let response = readStack.last + let baseAddress = buffer.baseAddress + let bufferLen = buffer.count if response != nil && bufferLen < 2 { - fragBuffer = NSData(bytes: buffer, length: bufferLen) - return + fragBuffer = NSData(buffer: buffer) + return emptyBuffer } if let response = response where response.bytesLeft > 0 { var len = response.bytesLeft @@ -537,24 +540,20 @@ public class WebSocket : NSObject, NSStreamDelegate { extra = 0 } response.bytesLeft -= len - response.buffer?.appendData(NSData(bytes: buffer, length: len)) + response.buffer?.appendData(NSData(bytes: baseAddress, length: len)) processResponse(response) - let offset = bufferLen - extra - if extra > 0 { - processExtra((buffer+offset), bufferLen: extra) - } - return + return buffer.fromOffset(bufferLen - extra) } else { - let isFin = (FinMask & buffer[0]) - let receivedOpcode = OpCode(rawValue: (OpCodeMask & buffer[0])) - let isMasked = (MaskMask & buffer[1]) - let payloadLen = (PayloadLenMask & buffer[1]) + let isFin = (FinMask & baseAddress[0]) + let receivedOpcode = OpCode(rawValue: (OpCodeMask & baseAddress[0])) + let isMasked = (MaskMask & baseAddress[1]) + let payloadLen = (PayloadLenMask & baseAddress[1]) var offset = 2 - if (isMasked > 0 || (RSVMask & buffer[0]) > 0) && receivedOpcode != .Pong { + if (isMasked > 0 || (RSVMask & baseAddress[0]) > 0) && receivedOpcode != .Pong { let errCode = CloseCode.ProtocolError.rawValue doDisconnect(errorWithDetail("masked and rsv data is not currently supported", code: errCode)) writeError(errCode) - return + return emptyBuffer } let isControlFrame = (receivedOpcode == .ConnectionClose || receivedOpcode == .Ping) if !isControlFrame && (receivedOpcode != .BinaryFrame && receivedOpcode != .ContinueFrame && @@ -562,20 +561,20 @@ public class WebSocket : NSObject, NSStreamDelegate { let errCode = CloseCode.ProtocolError.rawValue doDisconnect(errorWithDetail("unknown opcode: \(receivedOpcode)", code: errCode)) writeError(errCode) - return + return emptyBuffer } if isControlFrame && isFin == 0 { let errCode = CloseCode.ProtocolError.rawValue doDisconnect(errorWithDetail("control frames can't be fragmented", code: errCode)) writeError(errCode) - return + return emptyBuffer } if receivedOpcode == .ConnectionClose { var code = CloseCode.Normal.rawValue if payloadLen == 1 { code = CloseCode.ProtocolError.rawValue } else if payloadLen > 1 { - code = WebSocket.readUint16(buffer, offset: offset) + code = WebSocket.readUint16(baseAddress, offset: offset) if code < 1000 || (code > 1003 && code < 1007) || (code > 1011 && code < 3000) { code = CloseCode.ProtocolError.rawValue } @@ -584,7 +583,7 @@ public class WebSocket : NSObject, NSStreamDelegate { if payloadLen > 2 { let len = Int(payloadLen-2) if len > 0 { - let bytes = UnsafePointer((buffer+offset)) + let bytes = baseAddress + offset let str: NSString? = NSString(data: NSData(bytes: bytes, length: len), encoding: NSUTF8StringEncoding) if str == nil { code = CloseCode.ProtocolError.rawValue @@ -593,23 +592,23 @@ public class WebSocket : NSObject, NSStreamDelegate { } doDisconnect(errorWithDetail("connection closed by server", code: code)) writeError(code) - return + return emptyBuffer } if isControlFrame && payloadLen > 125 { writeError(CloseCode.ProtocolError.rawValue) - return + return emptyBuffer } var dataLength = UInt64(payloadLen) if dataLength == 127 { - dataLength = WebSocket.readUint64(buffer, offset: offset) + dataLength = WebSocket.readUint64(baseAddress, offset: offset) offset += sizeof(UInt64) } else if dataLength == 126 { - dataLength = UInt64(WebSocket.readUint16(buffer, offset: offset)) + dataLength = UInt64(WebSocket.readUint16(baseAddress, offset: offset)) offset += sizeof(UInt16) } if bufferLen < offset || UInt64(bufferLen - offset) < dataLength { - fragBuffer = NSData(bytes: buffer, length: bufferLen) - return + fragBuffer = NSData(bytes: baseAddress, length: bufferLen) + return emptyBuffer } var len = dataLength if dataLength > UInt64(bufferLen) { @@ -620,7 +619,7 @@ public class WebSocket : NSObject, NSStreamDelegate { len = 0 data = NSData() } else { - data = NSData(bytes: UnsafePointer((buffer+offset)), length: Int(len)) + data = NSData(bytes: baseAddress+offset, length: Int(len)) } if receivedOpcode == .Pong { if canDispatch { @@ -630,12 +629,7 @@ public class WebSocket : NSObject, NSStreamDelegate { s.pongDelegate?.websocketDidReceivePong(s) } } - let step = Int(offset+numericCast(len)) - let extra = bufferLen-step - if extra > 0 { - processRawMessage((buffer+step), bufferLen: extra) - } - return + return buffer.fromOffset(offset + Int(len)) } var response = readStack.last if isControlFrame { @@ -645,7 +639,7 @@ public class WebSocket : NSObject, NSStreamDelegate { let errCode = CloseCode.ProtocolError.rawValue doDisconnect(errorWithDetail("continue frame before a binary or text frame", code: errCode)) writeError(errCode) - return + return emptyBuffer } var isNew = false if response == nil { @@ -654,7 +648,7 @@ public class WebSocket : NSObject, NSStreamDelegate { doDisconnect(errorWithDetail("first frame can't be a continue frame", code: errCode)) writeError(errCode) - return + return emptyBuffer } isNew = true response = WSResponse() @@ -669,7 +663,7 @@ public class WebSocket : NSObject, NSStreamDelegate { doDisconnect(errorWithDetail("second and beyond of fragment message must be a continue frame", code: errCode)) writeError(errCode) - return + return emptyBuffer } response!.buffer!.appendData(data) } @@ -684,20 +678,18 @@ public class WebSocket : NSObject, NSStreamDelegate { } let step = Int(offset+numericCast(len)) - let extra = bufferLen-step - if extra > 0 { - processExtra((buffer+step), bufferLen: extra) - } + return buffer.fromOffset(step) } - } - ///process the extra of a buffer - private func processExtra(buffer: UnsafePointer, bufferLen: Int) { - if bufferLen < 2 { - fragBuffer = NSData(bytes: buffer, length: bufferLen) - } else { - processRawMessage(buffer, bufferLen: bufferLen) + /// Process all messages in the buffer if possible. + private func processRawMessagesInBuffer(pointer: UnsafePointer, bufferLen: Int) { + var buffer = UnsafeBufferPointer(start: pointer, count: bufferLen) + repeat { + buffer = processOneRawMessage(inBuffer: buffer) + } while buffer.count >= 2 + if buffer.count > 0 { + fragBuffer = NSData(buffer: buffer) } } @@ -835,6 +827,25 @@ public class WebSocket : NSObject, NSStreamDelegate { } +private extension NSData { + + convenience init(buffer: UnsafeBufferPointer) { + self.init(bytes: buffer.baseAddress, length: buffer.count) + } + +} + +private extension UnsafeBufferPointer { + + func fromOffset(offset: Int) -> UnsafeBufferPointer { + return UnsafeBufferPointer(start: baseAddress.advancedBy(offset), count: count - offset) + } + +} + +private let emptyBuffer = UnsafeBufferPointer(start: nil, count: 0) + + public class SSLCert { var certData: NSData? var key: SecKeyRef?