diff --git a/README.md b/README.md index 69280e1..63b7696 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,11 @@ Socket.IO-Client-Swift Socket.IO-client for Swift. Supports ws/wss/polling connections and binary. For socket.io 1.0+ and Swift 1.2. -For Swift 1.1 use the 1.1 branch. +For Swift 1.1 use the master branch. Installation ============ -1. Requires linking [SocketRocket](https://github.com/square/SocketRocket) against your xcode project. (Be sure to link the [frameworks](https://github.com/square/SocketRocket#framework-dependencies) required by SocketRocket) -2. Create a bridging header for SocketRocket -3. Copy the SwiftIO folder into your xcode project +1. Copy the SwiftIO folder into your Xcode project! API === @@ -21,10 +19,12 @@ Methods 1. `socket.on(name:String, callback:((data:NSArray?, ack:AckEmitter?) -> Void))` - Adds a handler for an event. Items are passed by an array. `ack` can be used to send an ack when one is requested. See example. 2. `socket.onAny(callback:((event:String, items:AnyObject?)) -> Void)` - Adds a handler for all events. It will be called on any received event. 3. `socket.emit(event:String, args:AnyObject...)` - Sends a message. Can send multiple args. -4. `socket.emitWithAck(event:String, args:AnyObject...) -> SocketAckHandler` - Sends a message that requests an acknowledgement from the server. Returns a SocketAckHandler which you can use to add an onAck handler. See example. -5. `socket.connect()` - Establishes a connection to the server. A "connect" event is fired upon successful connection. -6. `socket.connectWithParams(params:[String: AnyObject])` - Establishes a connection to the server passing the specified params. A "connect" event is fired upon successful connection. -7. `socket.close()` - Closes the socket. Once a socket is closed it should not be reopened. +4. `socket.emitObjc(event:String, args:[AnyObject])` - `emit` for Objective-C +5. `socket.emitWithAck(event:String, args:AnyObject...) -> SocketAckHandler` - Sends a message that requests an acknowledgement from the server. Returns a SocketAckHandler which you can use to add an onAck handler. See example. +6. `socket.emitWithAckObjc(event:String, _ args:[AnyObject]) -> SocketAckHandler` - `emitWithAck` for Objective-C. +7. `socket.connect()` - Establishes a connection to the server. A "connect" event is fired upon successful connection. +8. `socket.connectWithParams(params:[String: AnyObject])` - Establishes a connection to the server passing the specified params. A "connect" event is fired upon successful connection. +9. `socket.close()` - Closes the socket. Once a socket is closed it should not be reopened. Events ------ @@ -78,25 +78,6 @@ socket.on("ackEvent") {data, ack in ack?("Got your event", "dude") } -socket.on("disconnect") {data, ack in - if let reason = data?[0] as? String { - println("Socket disconnected: \(reason)") - } -} - -socket.on("reconnect") {data, ack in - if let reason = data?[0] as? String { - println("Socket reconnecting: \(reason)") - } -} - -socket.on("reconnectAttempt") {data, ack in - if let triesLeft = data?[0] as? Int { - println(triesLeft) - } -} -// End Socket Events - socket.on("jsonTest") {data, ack in if let json = data?[0] as? NSDictionary { println(json["test"]!) // foo bar @@ -122,30 +103,23 @@ socket.on("multipleItems") {data, ack in } } -// Recieving binary -socket.on("dataTest") {data, ack in - if let data = data?[0] as? NSData { - println("data is binary") - } -} - -socket.on("objectDataTest") {data, ack in - if let dict = data?[0] as? NSDictionary { - if let data = dict["data"] as? NSData { - let string = NSString(data: data, encoding: NSUTF8StringEncoding) - println("Got data: \(string!)") - } - } -} - // Connecting socket.connect() +``` + +Objective-C Example +=================== +```objective-c +SocketIOClient* socket = [[SocketIOClient alloc] initWithSocketURL:@"localhost:8080" opts:nil]; + +[socket on: @"connect" callback: ^(NSArray* data, void (^ack)(NSArray*)) { + NSLog(@"connected"); + [socket emitObjc:@"echo" :@[@"echo test"]]; + [[socket emitWithAckObjc:@"ackack" :@[@"test"]] onAck:^(NSArray* data) { + NSLog(@"Got data"); + }]; +}]; -// Sending binary -socket.emit("testData", [ - "data": "Hello World".dataUsingEncoding(NSUTF8StringEncoding, - allowLossyConversion: false)!, - "test": true]) ``` Detailed Example diff --git a/SwiftIO/SocketAckHandler.swift b/SwiftIO/SocketAckHandler.swift index 17a4236..bc506a0 100644 --- a/SwiftIO/SocketAckHandler.swift +++ b/SwiftIO/SocketAckHandler.swift @@ -24,9 +24,9 @@ import Foundation -typealias AckCallback = (NSArray?) -> Void +public typealias AckCallback = (NSArray?) -> Void -class SocketAckHandler { +@objc public class SocketAckHandler { let ackNum:Int! let event:String! var callback:AckCallback? @@ -39,4 +39,11 @@ class SocketAckHandler { func onAck(callback:AckCallback) { self.callback = callback } + + func executeAck(data:NSArray?) { + dispatch_async(dispatch_get_main_queue()) {[cb = self.callback] in + cb?(data) + return + } + } } \ No newline at end of file diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 0338090..e138d4d 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -46,7 +46,7 @@ private enum PacketType: String { case NOOP = "6" } -class SocketEngine: NSObject, SRWebSocketDelegate { +class SocketEngine: NSObject, WebSocketDelegate { unowned let client:SocketIOClient private let workQueue = NSOperationQueue() private let emitQueue = dispatch_queue_create( @@ -77,7 +77,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { var websocket:Bool { return self._websocket } - var ws:SRWebSocket? + var ws:WebSocket? init(client:SocketIOClient, forcePolling:Bool = false) { self.client = client @@ -147,7 +147,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return } - let req = NSURLRequest(URL: NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) + let req = NSMutableURLRequest(URL: NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) + req.timeoutInterval = 0.0 self.waitingForPoll = true self.session.dataTaskWithRequest(req) {[weak self] data, res, err in @@ -166,6 +167,10 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // println(str) dispatch_async(self!.parseQueue) {[weak self] in + if self == nil { + return + } + self?.parsePollingMessage(str) return } @@ -236,15 +241,14 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.flushWaitingForPost() self?.doPoll() return - } - }.resume() + }}.resume() } // We had packets waiting for send when we upgraded // Send them raw private func flushWaitingForPostToWebSocket() { for msg in self.postWait { - self.ws?.send(msg) + self.ws?.writeString(msg) } self.postWait.removeAll(keepCapacity: true) @@ -255,7 +259,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { private func handlePollingFailed(reason:NSError?) { if !self.client.reconnecting { self.connected = false - self.ws?.close() + self.ws?.disconnect() self.pingTimer?.invalidate() self.waitingForPoll = false self.waitingForPost = false @@ -309,10 +313,10 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.sid = sid if !self!.forcePolling { - self?.ws = SRWebSocket(URL: - NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) + self?.ws = WebSocket(url: NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) + self?.ws?.queue = self?.handleQueue self?.ws?.delegate = self - self?.ws?.open() + self?.ws?.connect() } } else { NSLog("Error handshaking") @@ -326,8 +330,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.doPoll() self?.startPingTimer() - } - }.resume() + }}.resume() } // Translatation of engine.io-parser#decodePayload @@ -377,8 +380,9 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // Be sure to capture the value of the msg dispatch_async(self.handleQueue) {[weak self, msg] in fixSwift = msg - self?.parseEngineMessage(fixSwift) - return + if fixSwift is String { + self?.parseEngineMessage(fixSwift as! String) + } } } @@ -388,16 +392,14 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - private func parseEngineMessage(message:AnyObject?) { + private func parseEngineData(data:NSData) { + self.client.parseBinaryData(data.subdataWithRange(NSMakeRange(1, data.length - 1))) + } + + private func parseEngineMessage(var message:String) { // println(message!) - if let data = message as? NSData { - // Strip off message type - self.client.parseSocketMessage(data.subdataWithRange(NSMakeRange(1, data.length - 1))) - return - } - var messageString = message as! String - var strMessage = RegexMutable(messageString) + var strMessage = RegexMutable(message) // We should upgrade if strMessage == "3probe" { @@ -409,16 +411,16 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if type != PacketType.MESSAGE.rawValue { // TODO Handle other packets - if messageString.hasPrefix("b4") { + if message.hasPrefix("b4") { // binary in base64 string - messageString.removeRange(Range(start: messageString.startIndex, - end: advance(messageString.startIndex, 2))) + message.removeRange(Range(start: message.startIndex, + end: advance(message.startIndex, 2))) - if let data = NSData(base64EncodedString: messageString, + if let data = NSData(base64EncodedString: message, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters) { // println("sending \(data)") - self.client.parseSocketMessage(data) + self.client.parseBinaryData(data) } return @@ -427,7 +429,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return } - if messageString == PacketType.CLOSE.rawValue { + if message == PacketType.CLOSE.rawValue { // do nothing return } @@ -436,10 +438,10 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } // Remove message type - messageString.removeAtIndex(messageString.startIndex) + message.removeAtIndex(message.startIndex) // println("sending \(messageString)") - self.client.parseSocketMessage(messageString) + self.client.parseSocketMessage(message) } private func probeWebSocket() { @@ -510,13 +512,13 @@ class SocketEngine: NSObject, SRWebSocketDelegate { private func sendWebSocketMessage(str:String, withType type:PacketType, datas:[NSData]? = nil) { // println("Sending: ws: \(str) as type: \(type.rawValue)") - self.ws?.send("\(type.rawValue)\(str)") + self.ws?.writeString("\(type.rawValue)\(str)") if datas != nil { for data in datas! { let (data, nilString) = self.createBinaryDataForSend(data) if data != nil { - self.ws?.send(data!) + self.ws?.writeData(data!) } } } @@ -546,25 +548,13 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - // Called when a message is recieved - func webSocket(webSocket:SRWebSocket!, didReceiveMessage message:AnyObject?) { - // println(message) - - dispatch_async(self.handleQueue) {[weak self] in - self?.parseEngineMessage(message) - return - } - } - - // Called when the socket is opened - func webSocketDidOpen(webSocket:SRWebSocket!) { + func websocketDidConnect(socket:WebSocket) { self.websocketConnected = true self.probing = true self.probeWebSocket() } - // Called when the socket is closed - func webSocket(webSocket:SRWebSocket!, didCloseWithCode code:Int, reason:String!, wasClean:Bool) { + func websocketDidDisconnect(socket:WebSocket, error:NSError?) { self.websocketConnected = false self.probing = false @@ -573,24 +563,17 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.connected = false self._websocket = false self._polling = true - self.client.webSocketDidCloseWithCode(code, reason: reason, wasClean: wasClean) + self.client.webSocketDidCloseWithCode(1, reason: "Socket Disconnect", wasClean: true) } else { self.flushProbeWait() } } - // Called when an error occurs. - func webSocket(webSocket:SRWebSocket!, didFailWithError error:NSError!) { - self.websocketConnected = false - self._polling = true - self.probing = false - - if self.websocket { - self.pingTimer?.invalidate() - self.connected = false - self.client.webSocketDidFailWithError(error) - } else { - self.flushProbeWait() - } + func websocketDidReceiveMessage(socket:WebSocket, text:String) { + self.parseEngineMessage(text) + } + + func websocketDidReceiveData(socket:WebSocket, data:NSData) { + self.parseEngineData(data) } } \ No newline at end of file diff --git a/SwiftIO/SocketEventHandler.swift b/SwiftIO/SocketEventHandler.swift index c59e92c..09f99b7 100644 --- a/SwiftIO/SocketEventHandler.swift +++ b/SwiftIO/SocketEventHandler.swift @@ -22,9 +22,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -typealias NormalCallback = (NSArray?, AckEmitter?) -> Void -typealias AnyHandler = (event:String, items:AnyObject?) -typealias AckEmitter = (AnyObject...) -> Void +import Foundation + +public typealias NormalCallback = (NSArray?, AckEmitter?) -> Void +public typealias AnyHandler = (event:String, items:AnyObject?) +public typealias AckEmitter = (AnyObject...) -> Void private func emitAckCallback(socket:SocketIOClient, num:Int, type:Int) -> AckEmitter { func emitter(items:AnyObject...) { diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 1e60258..ba279d8 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -24,7 +24,7 @@ import Foundation -class SocketIOClient { +public class SocketIOClient: NSObject { let socketURL:NSMutableString! let ackQueue = dispatch_queue_create("ackQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) @@ -57,7 +57,7 @@ class SocketIOClient { } var sid:String? - init(socketURL:String, opts:[String: AnyObject]? = nil) { + public init(socketURL:String, opts:[String: AnyObject]? = nil) { var mutURL = RegexMutable(socketURL) if mutURL["https://"].matches().count != 0 { @@ -96,11 +96,13 @@ class SocketIOClient { self.reconnectAttempts = -1 } + super.init() + self.engine = SocketEngine(client: self, forcePolling: self.forcePolling) } // Closes the socket - func close() { + public func close() { self.closed = true self.connecting = false self.connected = false @@ -109,7 +111,7 @@ class SocketIOClient { } // Connects to the server - func connect() { + public func connect() { if self.closed { println("Warning! This socket was previously closed. This might be dangerous!") self.closed = false @@ -119,7 +121,7 @@ class SocketIOClient { } // Connect to the server using params - func connectWithParams(params:[String: AnyObject]) { + public func connectWithParams(params:[String: AnyObject]) { if self.closed { println("Warning! This socket was previously closed. This might be dangerous!") self.closed = false @@ -139,6 +141,7 @@ class SocketIOClient { self.currentReconnectAttempt = 0 self.reconnectTimer?.invalidate() self.reconnectTimer = nil + self.handleEvent("connect", data: nil, isInternalMessage: false) } @@ -155,7 +158,7 @@ class SocketIOClient { // Sends a message with multiple args // If a message contains binary we have to send those // seperately. - func emit(event:String, _ args:AnyObject...) { + public func emit(event:String, _ args:AnyObject...) { if !self.connected { return } @@ -166,7 +169,12 @@ class SocketIOClient { } } - func emitWithAck(event:String, _ args:AnyObject...) -> SocketAckHandler { + // Objc doesn't have variadics + public func emitObjc(event:String, _ args:[AnyObject]) { + self.emit(event, args) + } + + public func emitWithAck(event:String, _ args:AnyObject...) -> SocketAckHandler { if !self.connected { return SocketAckHandler(event: "fail") } @@ -183,6 +191,10 @@ class SocketIOClient { return ackHandler } + public func emitWithAckObjc(event:String, _ args:[AnyObject]) -> SocketAckHandler { + return self.emitWithAck(event, args) + } + private func _emit(event:String, _ args:[AnyObject], ack:Bool = false) { var frame:SocketEvent var str:String @@ -217,7 +229,7 @@ class SocketIOClient { } // If the server wants to know that the client received data - func emitAck(ack:Int, withData data:[AnyObject]?, withAckType ackType:Int) { + internal func emitAck(ack:Int, withData data:[AnyObject]?, withAckType ackType:Int) { dispatch_async(self.ackQueue) {[weak self] in if self == nil || !self!.connected || data == nil { return @@ -257,11 +269,11 @@ class SocketIOClient { return true } else { if data is NSArray { - handler.callback?(data as? NSArray) + handler.executeAck(data as? NSArray) } else if data != nil { - handler.callback?([data!]) + handler.executeAck([data!]) } else { - handler.callback?(nil) + handler.executeAck(nil) } return false @@ -270,7 +282,7 @@ class SocketIOClient { } // Handles events - func handleEvent(event:String, data:AnyObject?, isInternalMessage:Bool = false, + public func handleEvent(event:String, data:AnyObject?, isInternalMessage:Bool = false, wantsAck ack:Int? = nil, withAckType ackType:Int = 3) { // println("Should do event: \(event) with data: \(data)") if !self.connected && !isInternalMessage { @@ -319,18 +331,18 @@ class SocketIOClient { } // Adds handler for an event - func on(name:String, callback:NormalCallback) { + public func on(name:String, callback:NormalCallback) { let handler = SocketEventHandler(event: name, callback: callback) self.handlers.append(handler) } // Adds a handler for any event - func onAny(handler:(AnyHandler) -> Void) { + public func onAny(handler:(AnyHandler) -> Void) { self.anyHandler = handler } // Opens the connection to the socket - func open() { + public func open() { self.connect() } @@ -497,77 +509,86 @@ class SocketIOClient { } // Parses messages recieved - func parseSocketMessage(message:AnyObject?) { - if message == nil { - return - } - + internal func parseSocketMessage(stringMessage:String) { // println(message!) - if let stringMessage = message as? String { - // Check for successful namepsace connect + // Check for successful namepsace connect + if self.nsp != nil { + if stringMessage == "0/\(self.nsp!)" { + self.didConnect() + return + } + } + + if stringMessage == "0" { if self.nsp != nil { - if stringMessage == "0/\(self.nsp!)" { - self.didConnect() - return - } + // Join namespace + self.joinNamespace() + return + } else { + // Don't handle as internal because something crazy could happen where + // we disconnect before it's handled + self.didConnect() + return + } + } + + var mutMessage = RegexMutable(stringMessage) + + /** + Begin check for message + **/ + let messageGroups = mutMessage["(\\d*)\\/?(\\w*)?,?(\\d*)?(\\[.*\\])?"].groups() + + if messageGroups[1].hasPrefix("2") { + var mesNum = messageGroups[1] + var ackNum:String + var namespace:String? + var messagePart:String! + + if messageGroups[3] != "" { + ackNum = messageGroups[3] + } else { + let range = Range(start: mesNum.startIndex, end: advance(mesNum.startIndex, 1)) + mesNum.replaceRange(range, with: "") + ackNum = mesNum } - if stringMessage == "0" { - if self.nsp != nil { - // Join namespace - self.joinNamespace() - return - } else { - // Don't handle as internal because something crazy could happen where - // we disconnect before it's handled - self.didConnect() - return - } + namespace = messageGroups[2] + messagePart = messageGroups[4] + + if namespace == "" && self.nsp != nil { + return } - var mutMessage = RegexMutable(stringMessage) - - /** - Begin check for message - **/ - let messageGroups = mutMessage["(\\d*)\\/?(\\w*)?,?(\\d*)?(\\[.*\\])?"].groups() - - if messageGroups[1].hasPrefix("2") { - var mesNum = messageGroups[1] - var ackNum:String - var namespace:String? - var messagePart:String! + let messageInternals = RegexMutable(messagePart)["\\[\"(.*?)\",(.*?)?\\]$"].groups() + if messageInternals != nil && messageInternals.count > 2 { + let event = messageInternals[1] + var data:String? - if messageGroups[3] != "" { - ackNum = messageGroups[3] + if messageInternals[2] == "" { + data = nil } else { - let range = Range(start: mesNum.startIndex, end: advance(mesNum.startIndex, 1)) - mesNum.replaceRange(range, with: "") - ackNum = mesNum + data = messageInternals[2] } - namespace = messageGroups[2] - messagePart = messageGroups[4] - - if namespace == "" && self.nsp != nil { - return - } - - let messageInternals = RegexMutable(messagePart)["\\[\"(.*?)\",(.*?)?\\]$"].groups() - if messageInternals != nil && messageInternals.count > 2 { - let event = messageInternals[1] - var data:String? - - if messageInternals[2] == "" { - data = nil + // It would be nice if socket.io only allowed one thing + // per message, but alas, it doesn't. + if let parsed:AnyObject = SocketIOClient.parseData(data) { + if ackNum == "" { + self.handleEvent(event, data: parsed) } else { - data = messageInternals[2] + self.currentAck = ackNum.toInt()! + self.handleEvent(event, data: parsed, isInternalMessage: false, + wantsAck: ackNum.toInt(), withAckType: 3) } - - // It would be nice if socket.io only allowed one thing - // per message, but alas, it doesn't. - if let parsed:AnyObject = SocketIOClient.parseData(data) { + return + } else if let strData = data { + // There are multiple items in the message + // Turn it into a String and run it through + // parseData to try and get an array. + let asArray = "[\(strData)]" + if let parsed:AnyObject = SocketIOClient.parseData(asArray) { if ackNum == "" { self.handleEvent(event, data: parsed) } else { @@ -576,78 +597,53 @@ class SocketIOClient { wantsAck: ackNum.toInt(), withAckType: 3) } return - } else if let strData = data { - // There are multiple items in the message - // Turn it into a String and run it through - // parseData to try and get an array. - let asArray = "[\(strData)]" - if let parsed:AnyObject = SocketIOClient.parseData(asArray) { - if ackNum == "" { - self.handleEvent(event, data: parsed) - } else { - self.currentAck = ackNum.toInt()! - self.handleEvent(event, data: parsed, isInternalMessage: false, - wantsAck: ackNum.toInt(), withAckType: 3) - } - return - } } } - - // Check for no item event - let noItemMessage = RegexMutable(messagePart)["\\[\"(.*?)\"]$"].groups() - if noItemMessage != nil && noItemMessage.count == 2 { - let event = noItemMessage[1] - if ackNum == "" { - self.handleEvent(event, data: nil) - } else { - self.currentAck = ackNum.toInt()! - self.handleEvent(event, data: nil, isInternalMessage: false, - wantsAck: ackNum.toInt(), withAckType: 3) - } - return - } - } else if messageGroups[1].hasPrefix("3") { - let arr = Array(messageGroups[1]) - var ackNum:String - let nsp = messageGroups[2] - - if nsp == "" && self.nsp != nil { - return - } - - if nsp == "" { - ackNum = String(arr[1...arr.count-1]) + } + + // Check for no item event + let noItemMessage = RegexMutable(messagePart)["\\[\"(.*?)\"]$"].groups() + if noItemMessage != nil && noItemMessage.count == 2 { + let event = noItemMessage[1] + if ackNum == "" { + self.handleEvent(event, data: nil) } else { - ackNum = messageGroups[3] + self.currentAck = ackNum.toInt()! + self.handleEvent(event, data: nil, isInternalMessage: false, + wantsAck: ackNum.toInt(), withAckType: 3) } - - let ackData:AnyObject? = SocketIOClient.parseData(messageGroups[4]) - self.handleAck(ackNum.toInt()!, data: ackData) - return } - /** - End Check for message - **/ + } else if messageGroups[1].hasPrefix("3") { + let arr = Array(messageGroups[1]) + var ackNum:String + let nsp = messageGroups[2] - // Check for message with binary placeholders - self.parseBinaryMessage(message: message!) + if nsp == "" && self.nsp != nil { + return + } + + if nsp == "" { + ackNum = String(arr[1...arr.count-1]) + } else { + ackNum = messageGroups[3] + } + + let ackData:AnyObject? = SocketIOClient.parseData(messageGroups[4]) + self.handleAck(ackNum.toInt()!, data: ackData) + + return } + /** + End Check for message + **/ - // Message is binary - if let binary = message as? NSData { - if self.waitingData.isEmpty { - return - } - - self.parseBinaryData(binary) - } + // Check for message with binary placeholders + self.parseBinaryMessage(message: stringMessage) } // Tries to parse a message that contains binary private func parseBinaryMessage(#message:AnyObject) { - // println(message) if let stringMessage = message as? String { var mutMessage = RegexMutable(stringMessage) @@ -734,7 +730,7 @@ class SocketIOClient { } // Handles binary data - private func parseBinaryData(data:NSData) { + internal func parseBinaryData(data:NSData) { let shouldExecute = self.waitingData[0].addData(data) if shouldExecute { @@ -779,7 +775,7 @@ class SocketIOClient { } // Something happened while polling - func pollingDidFail(err:NSError?) { + internal func pollingDidFail(err:NSError?) { if !self.reconnecting { self.connected = false self.handleEvent("reconnect", data: err?.localizedDescription, isInternalMessage: true) @@ -788,7 +784,7 @@ class SocketIOClient { } // We lost connection and should attempt to reestablish - @objc func tryReconnect() { + internal func tryReconnect() { if self.reconnectAttempts != -1 && self.currentReconnectAttempt + 1 > self.reconnectAttempts { self.didForceClose() return @@ -809,8 +805,6 @@ class SocketIOClient { target: self!, selector: "tryReconnect", userInfo: nil, repeats: true) return } - - return } self.handleEvent("reconnectAttempt", data: self.reconnectAttempts - self.currentReconnectAttempt, diff --git a/SwiftIO/WebSocket.swift b/SwiftIO/WebSocket.swift new file mode 100644 index 0000000..ad1bf7e --- /dev/null +++ b/SwiftIO/WebSocket.swift @@ -0,0 +1,713 @@ +////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Websocket.swift +// +// Created by Dalton Cherry on 7/16/14. +// +////////////////////////////////////////////////////////////////////////////////////////////////// + +import Foundation + +public protocol WebSocketDelegate: class { + func websocketDidConnect(socket: WebSocket) + func websocketDidDisconnect(socket: WebSocket, error: NSError?) + func websocketDidReceiveMessage(socket: WebSocket, text: String) + func websocketDidReceiveData(socket: WebSocket, data: NSData) +} + +public class WebSocket : NSObject, NSStreamDelegate { + + enum OpCode : UInt8 { + case ContinueFrame = 0x0 + case TextFrame = 0x1 + case BinaryFrame = 0x2 + //3-7 are reserved. + case ConnectionClose = 0x8 + case Ping = 0x9 + case Pong = 0xA + //B-F reserved. + } + + enum CloseCode : UInt16 { + case Normal = 1000 + case GoingAway = 1001 + case ProtocolError = 1002 + case ProtocolUnhandledType = 1003 + // 1004 reserved. + case NoStatusReceived = 1005 + //1006 reserved. + case Encoding = 1007 + case PolicyViolated = 1008 + case MessageTooBig = 1009 + } + + enum InternalErrorCode : UInt16 { + // 0-999 WebSocket status codes not used + case OutputStreamWriteError = 1 + } + + //Where the callback is executed. It defaults to the main UI thread queue. + public var queue = dispatch_get_main_queue() + + var optionalProtocols : Array? + //Constant Values. + let headerWSUpgradeName = "Upgrade" + let headerWSUpgradeValue = "websocket" + let headerWSHostName = "Host" + let headerWSConnectionName = "Connection" + let headerWSConnectionValue = "Upgrade" + let headerWSProtocolName = "Sec-WebSocket-Protocol" + let headerWSVersionName = "Sec-WebSocket-Version" + let headerWSVersionValue = "13" + let headerWSKeyName = "Sec-WebSocket-Key" + let headerOriginName = "Origin" + let headerWSAcceptName = "Sec-WebSocket-Accept" + let BUFFER_MAX = 2048 + let FinMask: UInt8 = 0x80 + let OpCodeMask: UInt8 = 0x0F + let RSVMask: UInt8 = 0x70 + let MaskMask: UInt8 = 0x80 + let PayloadLenMask: UInt8 = 0x7F + let MaxFrameSize: Int = 32 + + class WSResponse { + var isFin = false + var code: OpCode = .ContinueFrame + var bytesLeft = 0 + var frameCount = 0 + var buffer: NSMutableData? + } + + public weak var delegate: WebSocketDelegate? + private var url: NSURL + private var inputStream: NSInputStream? + private var outputStream: NSOutputStream? + private var isRunLoop = false + private var connected = false + private var writeQueue: NSOperationQueue? + private var readStack = Array() + private var inputQueue = Array() + private var fragBuffer: NSData? + public var headers = Dictionary() + public var voipEnabled = false + public var selfSignedSSL = false + private var connectedBlock: ((Void) -> Void)? = nil + private var disconnectedBlock: ((NSError?) -> Void)? = nil + private var receivedTextBlock: ((String) -> Void)? = nil + private var receivedDataBlock: ((NSData) -> Void)? = nil + public var isConnected :Bool { + return connected + } + + //init the websocket with a url + public init(url: NSURL) { + self.url = url + } + //used for setting protocols. + public convenience init(url: NSURL, protocols: Array) { + self.init(url: url) + optionalProtocols = protocols + } + //closure based instead of the delegate + public convenience init(url: NSURL, protocols: Array, connect:((Void) -> Void), disconnect:((NSError?) -> Void), text:((String) -> Void), data:(NSData) -> Void) { + self.init(url: url, protocols: protocols) + connectedBlock = connect + disconnectedBlock = disconnect + receivedTextBlock = text + receivedDataBlock = data + } + //same as above, just shorter + public convenience init(url: NSURL, connect:((Void) -> Void), disconnect:((NSError?) -> Void), text:((String) -> Void)) { + self.init(url: url) + connectedBlock = connect + disconnectedBlock = disconnect + receivedTextBlock = text + } + //same as above, just shorter + public convenience init(url: NSURL, connect:((Void) -> Void), disconnect:((NSError?) -> Void), data:((NSData) -> Void)) { + self.init(url: url) + connectedBlock = connect + disconnectedBlock = disconnect + receivedDataBlock = data + } + + ///Connect to the websocket server on a background thread + public func connect() { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), { + self.createHTTPRequest() + }) + } + + ///disconnect from the websocket server + public func disconnect() { + writeError(CloseCode.Normal.rawValue) + } + + ///write a string to the websocket. This sends it as a text frame. + public func writeString(str: String) { + dequeueWrite(str.dataUsingEncoding(NSUTF8StringEncoding)!, code: .TextFrame) + } + + ///write binary data to the websocket. This sends it as a binary frame. + public func writeData(data: NSData) { + dequeueWrite(data, code: .BinaryFrame) + } + + //write a ping to the websocket. This sends it as a control frame. + //yodel a sound to the planet. This sends it as an astroid. http://youtu.be/Eu5ZJELRiJ8?t=42s + public func writePing(data: NSData) { + dequeueWrite(data, code: .Ping) + } + //private methods below! + + //private method that starts the connection + private func createHTTPRequest() { + + let str: NSString = url.absoluteString! + let urlRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, "GET", + url, kCFHTTPVersion1_1) + + var port = url.port + if port == nil { + if url.scheme == "wss" || url.scheme == "https" { + port = 443 + } else { + port = 80 + } + } + self.addHeader(urlRequest, key: headerWSUpgradeName, val: headerWSUpgradeValue) + self.addHeader(urlRequest, key: headerWSConnectionName, val: headerWSConnectionValue) + if let protocols = optionalProtocols { + self.addHeader(urlRequest, key: headerWSProtocolName, val: ",".join(protocols)) + } + self.addHeader(urlRequest, key: headerWSVersionName, val: headerWSVersionValue) + self.addHeader(urlRequest, key: headerWSKeyName, val: self.generateWebSocketKey()) + self.addHeader(urlRequest, key: headerOriginName, val: url.absoluteString!) + self.addHeader(urlRequest, key: headerWSHostName, val: "\(url.host!):\(port!)") + for (key,value) in headers { + self.addHeader(urlRequest, key: key, val: value) + } + + let serializedRequest: NSData = CFHTTPMessageCopySerializedMessage(urlRequest.takeUnretainedValue()).takeUnretainedValue() + self.initStreamsWithData(serializedRequest, Int(port!)) + } + //Add a header to the CFHTTPMessage by using the NSString bridges to CFString + private func addHeader(urlRequest: Unmanaged,key: String, val: String) { + let nsKey: NSString = key + let nsVal: NSString = val + CFHTTPMessageSetHeaderFieldValue(urlRequest.takeUnretainedValue(), + nsKey, + nsVal) + } + //generate a websocket key as needed in rfc + private func generateWebSocketKey() -> String { + var key = "" + let seed = 16 + for (var i = 0; i < seed; i++) { + let uni = UnicodeScalar(UInt32(97 + arc4random_uniform(25))) + key += "\(Character(uni))" + } + var data = key.dataUsingEncoding(NSUTF8StringEncoding) + var baseKey = data?.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(0)) + return baseKey! + } + //Start the stream connection and write the data to the output stream + private func initStreamsWithData(data: NSData, _ port: Int) { + //higher level API we will cut over to at some point + //NSStream.getStreamsToHostWithName(url.host, port: url.port.integerValue, inputStream: &inputStream, outputStream: &outputStream) + + var readStream: Unmanaged? + var writeStream: Unmanaged? + let h: NSString = url.host! + CFStreamCreatePairWithSocketToHost(nil, h, UInt32(port), &readStream, &writeStream) + inputStream = readStream!.takeUnretainedValue() + outputStream = writeStream!.takeUnretainedValue() + + inputStream!.delegate = self + outputStream!.delegate = self + if url.scheme == "wss" || url.scheme == "https" { + inputStream!.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey) + outputStream!.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey) + } + if self.voipEnabled { + inputStream!.setProperty(NSStreamNetworkServiceTypeVoIP, forKey: NSStreamNetworkServiceType) + outputStream!.setProperty(NSStreamNetworkServiceTypeVoIP, forKey: NSStreamNetworkServiceType) + } + if self.selfSignedSSL { + let settings: Dictionary = [kCFStreamSSLValidatesCertificateChain: NSNumber(bool:false), kCFStreamSSLPeerName: kCFNull] + inputStream!.setProperty(settings, forKey: kCFStreamPropertySSLSettings as String) + outputStream!.setProperty(settings, forKey: kCFStreamPropertySSLSettings as String) + } + isRunLoop = true + inputStream!.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) + outputStream!.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) + inputStream!.open() + outputStream!.open() + let bytes = UnsafePointer(data.bytes) + outputStream!.write(bytes, maxLength: data.length) + while(isRunLoop) { + NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode, beforeDate: NSDate.distantFuture() as! NSDate) + } + } + //delegate for the stream methods. Processes incoming bytes + public func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) { + + if eventCode == .HasBytesAvailable { + if(aStream == inputStream) { + processInputStream() + } + } else if eventCode == .ErrorOccurred { + disconnectStream(aStream.streamError) + } else if eventCode == .EndEncountered { + disconnectStream(nil) + } + } + //disconnect the stream object + private func disconnectStream(error: NSError?) { + if writeQueue != nil { + writeQueue!.waitUntilAllOperationsAreFinished() + } + inputStream!.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) + outputStream!.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) + inputStream!.close() + outputStream!.close() + inputStream = nil + outputStream = nil + isRunLoop = false + connected = false + dispatch_async(queue,{ + if let disconnectBlock = self.disconnectedBlock { + disconnectBlock(error) + } + self.delegate?.websocketDidDisconnect(self, error: error) + }) + } + + ///handles the incoming bytes and sending them to the proper processing method + private func processInputStream() { + let buf = NSMutableData(capacity: BUFFER_MAX) + var buffer = UnsafeMutablePointer(buf!.bytes) + let length = inputStream!.read(buffer, maxLength: BUFFER_MAX) + if length > 0 { + if !connected { + connected = processHTTP(buffer, bufferLen: length) + if !connected { + dispatch_async(queue,{ + //self.workaroundMethod() + let error = self.errorWithDetail("Invalid HTTP upgrade", code: 1) + if let disconnect = self.disconnectedBlock { + disconnect(error) + } + self.delegate?.websocketDidDisconnect(self, error: error) + }) + } + } else { + var process = false + if inputQueue.count == 0 { + process = true + } + inputQueue.append(NSData(bytes: buffer, length: length)) + if process { + dequeueInput() + } + } + } + } + ///dequeue the incoming input so it is processed in order + private func dequeueInput() { + if inputQueue.count > 0 { + let data = inputQueue[0] + var work = data + if (fragBuffer != nil) { + var combine = NSMutableData(data: fragBuffer!) + combine.appendData(data) + work = combine + fragBuffer = nil + } + let buffer = UnsafePointer(work.bytes) + processRawMessage(buffer, bufferLen: work.length) + inputQueue = inputQueue.filter{$0 != data} + dequeueInput() + } + } + ///Finds the HTTP Packet in the TCP stream, by looking for the CRLF. + private func processHTTP(buffer: UnsafePointer, bufferLen: Int) -> Bool { + let CRLFBytes = [UInt8(ascii: "\r"), UInt8(ascii: "\n"), UInt8(ascii: "\r"), UInt8(ascii: "\n")] + var k = 0 + var totalSize = 0 + for var i = 0; i < bufferLen; i++ { + if buffer[i] == CRLFBytes[k] { + k++ + if k == 3 { + totalSize = i + 1 + break + } + } else { + k = 0 + } + } + if totalSize > 0 { + if validateResponse(buffer, bufferLen: totalSize) { + dispatch_async(queue,{ + //self.workaroundMethod() + if let connectBlock = self.connectedBlock { + connectBlock() + } + self.delegate?.websocketDidConnect(self) + }) + totalSize += 1 //skip the last \n + let restSize = bufferLen - totalSize + if restSize > 0 { + processRawMessage((buffer+totalSize),bufferLen: restSize) + } + return true + } + } + return false + } + + ///validates the HTTP is a 101 as per the RFC spec + private func validateResponse(buffer: UnsafePointer, bufferLen: Int) -> Bool { + let response = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, 0) + CFHTTPMessageAppendBytes(response.takeUnretainedValue(), buffer, bufferLen) + if CFHTTPMessageGetResponseStatusCode(response.takeUnretainedValue()) != 101 { + return false + } + let cfHeaders = CFHTTPMessageCopyAllHeaderFields(response.takeUnretainedValue()) + let headers: NSDictionary = cfHeaders.takeUnretainedValue() + let acceptKey = headers[headerWSAcceptName] as! NSString + if acceptKey.length > 0 { + return true + } + return false + } + + ///process the websocket data + private func processRawMessage(buffer: UnsafePointer, bufferLen: Int) { + var response = readStack.last + if response != nil && bufferLen < 2 { + fragBuffer = NSData(bytes: buffer, length: bufferLen) + return + } + if response != nil && response!.bytesLeft > 0 { + let resp = response! + var len = resp.bytesLeft + var extra = bufferLen - resp.bytesLeft + if resp.bytesLeft > bufferLen { + len = bufferLen + extra = 0 + } + resp.bytesLeft -= len + resp.buffer?.appendData(NSData(bytes: buffer, length: len)) + processResponse(resp) + var offset = bufferLen - extra + if extra > 0 { + processExtra((buffer+offset), bufferLen: extra) + } + return + } else { + let isFin = (FinMask & buffer[0]) + let receivedOpcode = (OpCodeMask & buffer[0]) + let isMasked = (MaskMask & buffer[1]) + let payloadLen = (PayloadLenMask & buffer[1]) + var offset = 2 + if((isMasked > 0 || (RSVMask & buffer[0]) > 0) && receivedOpcode != OpCode.Pong.rawValue) { + let errCode = CloseCode.ProtocolError.rawValue + let error = self.errorWithDetail("masked and rsv data is not currently supported", code: errCode) + if let disconnect = self.disconnectedBlock { + disconnect(error) + } + self.delegate?.websocketDidDisconnect(self, error: error) + writeError(errCode) + return + } + let isControlFrame = (receivedOpcode == OpCode.ConnectionClose.rawValue || receivedOpcode == OpCode.Ping.rawValue) + if !isControlFrame && (receivedOpcode != OpCode.BinaryFrame.rawValue && receivedOpcode != OpCode.ContinueFrame.rawValue && + receivedOpcode != OpCode.TextFrame.rawValue && receivedOpcode != OpCode.Pong.rawValue) { + let errCode = CloseCode.ProtocolError.rawValue + let error = self.errorWithDetail("unknown opcode: \(receivedOpcode)", code: errCode) + if let disconnect = self.disconnectedBlock { + disconnect(error) + } + self.delegate?.websocketDidDisconnect(self, error: error) + writeError(errCode) + return + } + if isControlFrame && isFin == 0 { + let errCode = CloseCode.ProtocolError.rawValue + let error = self.errorWithDetail("control frames can't be fragmented", code: errCode) + if let disconnect = self.disconnectedBlock { + disconnect(error) + } + self.delegate?.websocketDidDisconnect(self, error: error) + writeError(errCode) + return + } + if receivedOpcode == OpCode.ConnectionClose.rawValue { + var code = CloseCode.Normal.rawValue + if payloadLen == 1 { + code = CloseCode.ProtocolError.rawValue + } else if payloadLen > 1 { + var codeBuffer = UnsafePointer((buffer+offset)) + code = codeBuffer[0].byteSwapped + if code < 1000 || (code > 1003 && code < 1007) || (code > 1011 && code < 3000) { + code = CloseCode.ProtocolError.rawValue + } + offset += 2 + } + if payloadLen > 2 { + let len = Int(payloadLen-2) + if len > 0 { + let bytes = UnsafePointer((buffer+offset)) + var str: NSString? = NSString(data: NSData(bytes: bytes, length: len), encoding: NSUTF8StringEncoding) + if str == nil { + code = CloseCode.ProtocolError.rawValue + } + } + } + let error = self.errorWithDetail("connection closed by server", code: code) + if let disconnect = self.disconnectedBlock { + disconnect(error) + } + self.delegate?.websocketDidDisconnect(self, error: error) + writeError(code) + return + } + if isControlFrame && payloadLen > 125 { + writeError(CloseCode.ProtocolError.rawValue) + return + } + var dataLength = UInt64(payloadLen) + if dataLength == 127 { + let bytes = UnsafePointer((buffer+offset)) + dataLength = bytes[0].byteSwapped + offset += sizeof(UInt64) + } else if dataLength == 126 { + let bytes = UnsafePointer((buffer+offset)) + dataLength = UInt64(bytes[0].byteSwapped) + offset += sizeof(UInt16) + } + var len = dataLength + if dataLength > UInt64(bufferLen) { + len = UInt64(bufferLen-offset) + } + var data: NSData! + if len < 0 { + len = 0 + data = NSData() + } else { + data = NSData(bytes: UnsafePointer((buffer+offset)), length: Int(len)) + } + if receivedOpcode == OpCode.Pong.rawValue { + let step = offset + Int(len) + let extra = bufferLen-step + if extra > 0 { + processRawMessage((buffer+step), bufferLen: extra) + } + return + } + var response = readStack.last + if isControlFrame { + response = nil //don't append pings + } + if isFin == 0 && receivedOpcode == OpCode.ContinueFrame.rawValue && response == nil { + let errCode = CloseCode.ProtocolError.rawValue + let error = self.errorWithDetail("continue frame before a binary or text frame", code: errCode) + if let disconnect = self.disconnectedBlock { + disconnect(error) + } + self.delegate?.websocketDidDisconnect(self, error: error) + writeError(errCode) + return + } + var isNew = false + if(response == nil) { + if receivedOpcode == OpCode.ContinueFrame.rawValue { + let errCode = CloseCode.ProtocolError.rawValue + let error = self.errorWithDetail("first frame can't be a continue frame", + code: errCode) + if let disconnect = self.disconnectedBlock { + disconnect(error) + } + self.delegate?.websocketDidDisconnect(self, error: error) + writeError(errCode) + return + } + isNew = true + response = WSResponse() + response!.code = OpCode(rawValue: receivedOpcode)! + response!.bytesLeft = Int(dataLength) + response!.buffer = NSMutableData(data: data) + } else { + if receivedOpcode == OpCode.ContinueFrame.rawValue { + response!.bytesLeft = Int(dataLength) + } else { + let errCode = CloseCode.ProtocolError.rawValue + let error = self.errorWithDetail("second and beyond of fragment message must be a continue frame", + code: errCode) + if let disconnect = self.disconnectedBlock { + disconnect(error) + } + self.delegate?.websocketDidDisconnect(self, error: error) + writeError(errCode) + return + } + response!.buffer!.appendData(data) + } + if response != nil { + response!.bytesLeft -= Int(len) + response!.frameCount++ + response!.isFin = isFin > 0 ? true : false + if(isNew) { + readStack.append(response!) + } + processResponse(response!) + } + + let step = offset + Int(len) + let extra = bufferLen-step + if(extra > 0) { + processExtra((buffer+step), bufferLen: extra) + } + } + + } + + ///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 the finished response of a buffer + private func processResponse(response: WSResponse) -> Bool { + if response.isFin && response.bytesLeft <= 0 { + if response.code == .Ping { + let data = response.buffer! //local copy so it is perverse for writing + dequeueWrite(data, code: OpCode.Pong) + } else if response.code == .TextFrame { + var str: NSString? = NSString(data: response.buffer!, encoding: NSUTF8StringEncoding) + if str == nil { + writeError(CloseCode.Encoding.rawValue) + return false + } + dispatch_async(queue,{ + if let textBlock = self.receivedTextBlock{ + textBlock(str! as String) + } + self.delegate?.websocketDidReceiveMessage(self, text: str! as String) + }) + } else if response.code == .BinaryFrame { + let data = response.buffer! //local copy so it is perverse for writing + dispatch_async(queue,{ + //self.workaroundMethod() + if let dataBlock = self.receivedDataBlock{ + dataBlock(data) + } + self.delegate?.websocketDidReceiveData(self, data: data) + }) + } + readStack.removeLast() + return true + } + return false + } + + ///Create an error + private func errorWithDetail(detail: String, code: UInt16) -> NSError { + var details = Dictionary() + details[NSLocalizedDescriptionKey] = detail + return NSError(domain: "Websocket", code: Int(code), userInfo: details) + } + + ///write a an error to the socket + private func writeError(code: UInt16) { + let buf = NSMutableData(capacity: sizeof(UInt16)) + var buffer = UnsafeMutablePointer(buf!.bytes) + buffer[0] = code.byteSwapped + dequeueWrite(NSData(bytes: buffer, length: sizeof(UInt16)), code: .ConnectionClose) + } + ///used to write things to the stream in a + private func dequeueWrite(data: NSData, code: OpCode) { + if writeQueue == nil { + writeQueue = NSOperationQueue() + writeQueue!.maxConcurrentOperationCount = 1 + } + writeQueue!.addOperationWithBlock { + //stream isn't ready, let's wait + var tries = 0; + while self.outputStream == nil || !self.connected { + if(tries < 5) { + sleep(1); + } else { + break; + } + tries++; + } + if !self.connected { + return + } + var offset = 2 + UINT16_MAX + let bytes = UnsafeMutablePointer(data.bytes) + let dataLength = data.length + let frame = NSMutableData(capacity: dataLength + self.MaxFrameSize) + let buffer = UnsafeMutablePointer(frame!.mutableBytes) + buffer[0] = self.FinMask | code.rawValue + if dataLength < 126 { + buffer[1] = CUnsignedChar(dataLength) + } else if dataLength <= Int(UInt16.max) { + buffer[1] = 126 + var sizeBuffer = UnsafeMutablePointer((buffer+offset)) + sizeBuffer[0] = UInt16(dataLength).byteSwapped + offset += sizeof(UInt16) + } else { + buffer[1] = 127 + var sizeBuffer = UnsafeMutablePointer((buffer+offset)) + sizeBuffer[0] = UInt64(dataLength).byteSwapped + offset += sizeof(UInt64) + } + buffer[1] |= self.MaskMask + var maskKey = UnsafeMutablePointer(buffer + offset) + SecRandomCopyBytes(kSecRandomDefault, Int(sizeof(UInt32)), maskKey) + offset += sizeof(UInt32) + + for (var i = 0; i < dataLength; i++) { + buffer[offset] = bytes[i] ^ maskKey[i % sizeof(UInt32)] + offset += 1 + } + var total = 0 + while true { + if self.outputStream == nil { + break + } + let writeBuffer = UnsafePointer(frame!.bytes+total) + var len = self.outputStream?.write(writeBuffer, maxLength: offset-total) + if len == nil || len! < 0 { + var error: NSError? + if let streamError = self.outputStream?.streamError { + error = streamError + } else { + let errCode = InternalErrorCode.OutputStreamWriteError.rawValue + error = self.errorWithDetail("output stream error during write", code: errCode) + } + if let disconnect = self.disconnectedBlock { + disconnect(error) + } + self.delegate?.websocketDidDisconnect(self, error: error) + break + } else { + total += len! + } + if total >= offset { + break + } + } + + } + } + +}