From aa570be4b8eed806811621cbb633313fbb41a04d Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Mon, 14 Dec 2015 17:13:45 +0000 Subject: [PATCH 01/10] [objc] WebSocket: NSObject (removed Hashable) Xcode: Trim trailing whitespace including whitespace-only lines --- Source/WebSocket.swift | 76 +++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/Source/WebSocket.swift b/Source/WebSocket.swift index 5eddc0b..f6ff40f 100644 --- a/Source/WebSocket.swift +++ b/Source/WebSocket.swift @@ -386,18 +386,18 @@ private struct z_stream { var next_in : UnsafePointer = nil var avail_in : CUnsignedInt = 0 var total_in : CUnsignedLong = 0 - + var next_out : UnsafeMutablePointer = nil var avail_out : CUnsignedInt = 0 var total_out : CUnsignedLong = 0 - + var msg : UnsafePointer = nil var state : COpaquePointer = nil - + var zalloc : COpaquePointer = nil var zfree : COpaquePointer = nil var opaque : COpaquePointer = nil - + var data_type : CInt = 0 var adler : CUnsignedLong = 0 var reserved : CUnsignedLong = 0 @@ -520,7 +520,7 @@ private class InnerWebSocket: Hashable { var _binaryType = WebSocketBinaryType.UInt8Array var _readyState = WebSocketReadyState.Connecting var _networkTimeout = NSTimeInterval(-1) - + var url : String { return request.URL!.description } @@ -562,7 +562,7 @@ private class InnerWebSocket: Hashable { get { lock(); defer { unlock() }; return _readyState } set { lock(); defer { unlock() }; _readyState = newValue } } - + func copyOpen(request: NSURLRequest, subProtocols : [String] = []) -> InnerWebSocket{ let ws = InnerWebSocket(request: request, subProtocols: subProtocols, stub: false) ws.compression = compression @@ -573,9 +573,9 @@ private class InnerWebSocket: Hashable { ws.binaryType = binaryType return ws } - + var hashValue: Int { return id } - + init(request: NSURLRequest, subProtocols : [String] = [], stub : Bool = false){ pthread_mutex_init(&mutex, nil) self.id = manager.nextId() @@ -611,7 +611,7 @@ private class InnerWebSocket: Hashable { @inline(__always) private func unlock(){ pthread_mutex_unlock(&mutex) } - + private var dirty : Bool { lock() defer { unlock() } @@ -738,7 +738,7 @@ private class InnerWebSocket: Hashable { manager.remove(self) } } catch WebSocketError.NeedMoreInput { - + } catch { if finalError != nil { return @@ -865,7 +865,7 @@ private class InnerWebSocket: Hashable { block() } } - + var readStateSaved = false var readStateFrame : Frame? var readStateFinished = false @@ -920,7 +920,7 @@ private class InnerWebSocket: Hashable { readStateFinished = false return frame } - + func closeConn() { rd.removeFromRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode) wr.removeFromRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode) @@ -929,14 +929,14 @@ private class InnerWebSocket: Hashable { rd.close() wr.close() } - + func openConn() throws { let req = request.mutableCopy() as! NSMutableURLRequest req.setValue("websocket", forHTTPHeaderField: "Upgrade") req.setValue("Upgrade", forHTTPHeaderField: "Connection") req.setValue("SwiftWebSocket", forHTTPHeaderField: "User-Agent") req.setValue("13", forHTTPHeaderField: "Sec-WebSocket-Version") - + if req.URL == nil || req.URL!.host == nil{ throw WebSocketError.InvalidAddress } @@ -1058,7 +1058,7 @@ private class InnerWebSocket: Hashable { wr.open() try write(header, length: header.count) } - + func write(bytes: UnsafePointer, length: Int) throws { if outputBytesStart+outputBytesLength+length > outputBytesSize { var size = outputBytesSize @@ -1075,7 +1075,7 @@ private class InnerWebSocket: Hashable { memcpy(outputBytes+outputBytesStart+outputBytesLength, bytes, length) outputBytesLength += length } - + func readResponse() throws { let end : [UInt8] = [ 0x0D, 0x0A, 0x0D, 0x0A ] let ptr = UnsafeMutablePointer(memmem(inputBytes+inputBytesStart, inputBytesLength, end, 4)) @@ -1156,7 +1156,7 @@ private class InnerWebSocket: Hashable { inputBytesStart += bufferCount+4 } } - + class ByteReader { var start : UnsafePointer var end : UnsafePointer @@ -1186,7 +1186,7 @@ private class InnerWebSocket: Hashable { } } } - + var fragStateSaved = false var fragStatePosition = 0 var fragStateInflate = false @@ -1210,7 +1210,7 @@ private class InnerWebSocket: Hashable { var payload : Payload var statusCode : UInt16 var headerLen : Int - + let reader = ByteReader(bytes: inputBytes+inputBytesStart, length: inputBytesLength) if fragStateSaved { // load state @@ -1312,7 +1312,7 @@ private class InnerWebSocket: Hashable { } headerLen = reader.position } - + let rlen : Int let rfin : Bool let chopped : Bool @@ -1333,13 +1333,13 @@ private class InnerWebSocket: Hashable { (bytes, bytesLen) = (UnsafeMutablePointer(reader.bytes), rlen) } reader.bytes += rlen - + if leaderCode == .Text || leaderCode == .Close { try utf8.append(bytes, length: bytesLen) } else { payload.append(bytes, length: bytesLen) } - + if chopped { // save state fragStateHeaderLen = headerLen @@ -1355,19 +1355,19 @@ private class InnerWebSocket: Hashable { fragStateSaved = true throw WebSocketError.NeedMoreInput } - + inputBytesLength -= reader.position if inputBytesLength == 0 { inputBytesStart = 0 } else { inputBytesStart += reader.position } - + let f = Frame() (f.code, f.payload, f.utf8, f.statusCode, f.inflate, f.finished) = (code, payload, utf8, statusCode, inflate, fin) return f } - + var head = [UInt8](count: 0xFF, repeatedValue: 0) func writeFrame(f : Frame) throws { if !f.finished{ @@ -1392,7 +1392,7 @@ private class InnerWebSocket: Hashable { } payloadLen += payloadBytes.count if deflate { - + } var usingStatusCode = false if f.statusCode != 0 && payloadLen != 0 { @@ -1586,11 +1586,11 @@ private class Manager { private let manager = Manager() /// WebSocket objects are bidirectional network streams that communicate over HTTP. RFC 6455. -public class WebSocket: Hashable { - private var ws : InnerWebSocket +public class WebSocket: NSObject { + private var ws: InnerWebSocket private var id = manager.nextId() - private var opened : Bool - public var hashValue: Int { return id } + private var opened: Bool + public override var hashValue: Int { return id } /// Create a WebSocket connection to a URL; this should be the URL to which the WebSocket server will respond. public convenience init(_ url: String){ self.init(request: NSURLRequest(URL: NSURL(string: url)!), subProtocols: []) @@ -1609,9 +1609,10 @@ public class WebSocket: Hashable { ws = InnerWebSocket(request: request, subProtocols: subProtocols, stub: false) } /// Create a WebSocket object with a deferred connection; the connection is not opened until the .open() method is called. - public init(){ + public override init(){ opened = false ws = InnerWebSocket(request: NSURLRequest(), subProtocols: [], stub: true) + super.init() } /// The URL as resolved by the constructor. This is always an absolute URL. Read only. public var url : String{ return ws.url } @@ -1652,15 +1653,15 @@ public class WebSocket: Hashable { return ws.readyState } /// Opens a deferred or closed WebSocket connection to a URL; this should be the URL to which the WebSocket server will respond. - public func open(url: String){ + public func open(url url: String){ open(NSURLRequest(URL: NSURL(string: url)!), subProtocols: []) } /// Opens a deferred or closed WebSocket connection to a URL; this should be the URL to which the WebSocket server will respond. Also include a list of protocols. - public func open(url: String, subProtocols : [String]){ + public func open(url url: String, subProtocols : [String]){ open(NSURLRequest(URL: NSURL(string: url)!), subProtocols: subProtocols) } /// Opens a deferred or closed WebSocket connection to a URL; this should be the URL to which the WebSocket server will respond. Also include a protocol. - public func open(url: String, subProtocol : String){ + public func open(url url: String, subProtocol : String){ open(NSURLRequest(URL: NSURL(string: url)!), subProtocols: [subProtocol]) } /// Opens a deferred or closed WebSocket connection from an NSURLRequest; Also include a list of protocols. @@ -1677,7 +1678,7 @@ public class WebSocket: Hashable { } /** Closes the WebSocket connection or connection attempt, if any. If the connection is already closed or in the state of closing, this method does nothing. - + :param: code An integer indicating the status code explaining why the connection is being closed. If this parameter is not specified, a default value of 1000 (indicating a normal closure) is assumed. :param: reason A human-readable string explaining why the connection is closing. This string must be no longer than 123 bytes of UTF-8 text (not characters). */ @@ -1690,7 +1691,7 @@ public class WebSocket: Hashable { } /** Transmits message to the server over the WebSocket connection. - + :param: message The data to be sent to the server. */ public func send(message : Any){ @@ -1701,7 +1702,7 @@ public class WebSocket: Hashable { } /** Transmits a ping to the server over the WebSocket connection. - + :param: optional message The data to be sent to the server. */ public func ping(message : Any){ @@ -1724,4 +1725,3 @@ public class WebSocket: Hashable { public func ==(lhs: WebSocket, rhs: WebSocket) -> Bool { return lhs.id == rhs.id } - From 2af27de30a68d46643b0c25b6cf1adbcec311282 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Mon, 14 Dec 2015 19:11:02 +0000 Subject: [PATCH 02/10] [objc] WebSocketDelegate --- Source/WebSocket.swift | 58 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/Source/WebSocket.swift b/Source/WebSocket.swift index f6ff40f..fdaad38 100644 --- a/Source/WebSocket.swift +++ b/Source/WebSocket.swift @@ -97,6 +97,24 @@ private enum OpCode : UInt8, CustomStringConvertible { } } +@objc +public protocol WebSocketDelegate { + /// A function to be called when the WebSocket connection's readyState changes to .Open; this indicates that the connection is ready to send and receive data. + func webSocketOpen() + /// A function to be called when the WebSocket connection's readyState changes to .Closed. + func webSocketClose(code: Int, reason: String, wasClean: Bool) + /// A function to be called when an error occurs. + func webSocketError(error: NSError) + /// A function to be called when a message (string) is received from the server. + optional func webSocketMessageText(text: String) + /// A function to be called when a message (binary) is received from the server. + optional func webSocketMessageData(data: NSData) + /// A function to be called when a pong is received from the server. + optional func webSocketPong() + /// A function to be called when the WebSocket process has ended; this event is guarenteed to be called once and can be used as an alternative to the "close" or "error" events. + optional func webSocketEnd(code: Int, reason: String, wasClean: Bool, error: NSError?) +} + /// The WebSocketEvents struct is used by the events property and manages the events for the WebSocket connection. public struct WebSocketEvents { /// An event to be called when the WebSocket connection's readyState changes to .Open; this indicates that the connection is ready to send and receive data. @@ -517,6 +535,7 @@ private class InnerWebSocket: Hashable { var _allowSelfSignedSSL = false var _services = WebSocketService.None var _event = WebSocketEvents() + var _eventDelegate: WebSocketDelegate? var _binaryType = WebSocketBinaryType.UInt8Array var _readyState = WebSocketReadyState.Connecting var _networkTimeout = NSTimeInterval(-1) @@ -547,6 +566,10 @@ private class InnerWebSocket: Hashable { get { lock(); defer { unlock() }; return _event } set { lock(); defer { unlock() }; _event = newValue } } + var eventDelegate : WebSocketDelegate? { + get { lock(); defer { unlock() }; return _eventDelegate } + set { lock(); defer { unlock() }; _eventDelegate = newValue } + } var eventQueue : dispatch_queue_t? { get { lock(); defer { unlock() }; return _eventQueue; } set { lock(); defer { unlock() }; _eventQueue = newValue } @@ -674,6 +697,7 @@ private class InnerWebSocket: Hashable { privateReadyState = .Open fire { self.event.open() + self.eventDelegate?.webSocketOpen() } stage = .HandleFrames case .HandleFrames: @@ -688,13 +712,24 @@ private class InnerWebSocket: Hashable { case .Text: fire { self.event.message(data: frame.utf8.text) + self.eventDelegate?.webSocketMessageText?(frame.utf8.text) } case .Binary: fire { switch self.binaryType { - case .UInt8Array: self.event.message(data: frame.payload.array) - case .NSData: self.event.message(data: frame.payload.nsdata) - case .UInt8UnsafeBufferPointer: self.event.message(data: frame.payload.buffer) + case .UInt8Array: + self.event.message(data: frame.payload.array) + // TODO: review + let array = frame.payload.array.map({ Int($0) }) + self.eventDelegate?.webSocketMessageData?(NSKeyedArchiver.archivedDataWithRootObject(array)) + case .NSData: + self.event.message(data: frame.payload.nsdata) + self.eventDelegate?.webSocketMessageData?(frame.payload.nsdata) + case .UInt8UnsafeBufferPointer: + self.event.message(data: frame.payload.buffer) + // TODO: review + let array = [UInt8](frame.payload.buffer).map({ Int($0) }) + self.eventDelegate?.webSocketMessageData?(NSKeyedArchiver.archivedDataWithRootObject(array)) } } case .Ping: @@ -706,10 +741,14 @@ private class InnerWebSocket: Hashable { case .Pong: fire { switch self.binaryType { - case .UInt8Array: self.event.pong(data: frame.payload.array) - case .NSData: self.event.pong(data: frame.payload.nsdata) - case .UInt8UnsafeBufferPointer: self.event.pong(data: frame.payload.buffer) + case .UInt8Array: + self.event.pong(data: frame.payload.array) + case .NSData: + self.event.pong(data: frame.payload.nsdata) + case .UInt8UnsafeBufferPointer: + self.event.pong(data: frame.payload.buffer) } + self.eventDelegate?.webSocketPong?() } case .Close: lock() @@ -721,18 +760,21 @@ private class InnerWebSocket: Hashable { case .CloseConn: if let error = finalError { self.event.error(error: error) + self.eventDelegate?.webSocketError(error as NSError) } privateReadyState = .Closed if rd != nil { closeConn() fire { self.event.close(code: Int(self.closeCode), reason: self.closeReason, wasClean: self.closeFinal) + self.eventDelegate?.webSocketClose(Int(self.closeCode), reason: self.closeReason, wasClean: self.closeFinal) } } stage = .End case .End: fire { self.event.end(code: Int(self.closeCode), reason: self.closeReason, wasClean: self.closeClean, error: self.finalError) + self.eventDelegate?.webSocketEnd?(Int(self.closeCode), reason: self.closeReason, wasClean: self.closeClean, error: self.finalError as? NSError) } exit = true manager.remove(self) @@ -1638,6 +1680,10 @@ public class WebSocket: NSObject { get { return ws.event } set { ws.event = newValue } } + public var delegate : WebSocketDelegate? { + get { return ws.eventDelegate } + set { ws.eventDelegate = newValue } + } /// The queue for firing off events. default is main_queue public var eventQueue : dispatch_queue_t?{ get { return ws.eventQueue } From 5f36218b5ad729e7edbb9b8d04973b157a507ebd Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Mon, 14 Dec 2015 19:53:22 +0000 Subject: [PATCH 03/10] Open with NSURL --- Source/WebSocket.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/WebSocket.swift b/Source/WebSocket.swift index fdaad38..741588c 100644 --- a/Source/WebSocket.swift +++ b/Source/WebSocket.swift @@ -1702,6 +1702,10 @@ public class WebSocket: NSObject { public func open(url url: String){ open(NSURLRequest(URL: NSURL(string: url)!), subProtocols: []) } + /// Opens a deferred or closed WebSocket connection to a URL; this should be the URL to which the WebSocket server will respond. + public func open(nsurl url: NSURL){ + open(NSURLRequest(URL: url), subProtocols: []) + } /// Opens a deferred or closed WebSocket connection to a URL; this should be the URL to which the WebSocket server will respond. Also include a list of protocols. public func open(url url: String, subProtocols : [String]){ open(NSURLRequest(URL: NSURL(string: url)!), subProtocols: subProtocols) From 34b0a3013ab37bbff3a45b2af9e65cb1eb471273 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Mon, 14 Dec 2015 20:21:53 +0000 Subject: [PATCH 04/10] [objc] sendWithText and sendWithData --- Source/WebSocket.swift | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Source/WebSocket.swift b/Source/WebSocket.swift index 741588c..d447a4d 100644 --- a/Source/WebSocket.swift +++ b/Source/WebSocket.swift @@ -1742,7 +1742,7 @@ public class WebSocket: NSObject { /** Transmits message to the server over the WebSocket connection. - :param: message The data to be sent to the server. + :param: message The message to be sent to the server. */ public func send(message : Any){ if !opened{ @@ -1750,6 +1750,24 @@ public class WebSocket: NSObject { } ws.send(message) } + /** + Transmits message to the server over the WebSocket connection. + + :param: text The message (string) to be sent to the server. + */ + @objc + public func send(text text: String){ + send(text) + } + /** + Transmits message to the server over the WebSocket connection. + + :param: data The message (binary) to be sent to the server. + */ + @objc + public func send(data data: NSData){ + send(data) + } /** Transmits a ping to the server over the WebSocket connection. From 2724ae1faf15e3dfb5c3d132da0b93855f8de86d Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Tue, 15 Dec 2015 07:06:00 +0000 Subject: [PATCH 05/10] [objc] Send binary data only with NSData Isolate Objective-C implementation with an extension to WebSocket. --- Source/WebSocket.swift | 93 +++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/Source/WebSocket.swift b/Source/WebSocket.swift index d447a4d..bafb540 100644 --- a/Source/WebSocket.swift +++ b/Source/WebSocket.swift @@ -97,24 +97,6 @@ private enum OpCode : UInt8, CustomStringConvertible { } } -@objc -public protocol WebSocketDelegate { - /// A function to be called when the WebSocket connection's readyState changes to .Open; this indicates that the connection is ready to send and receive data. - func webSocketOpen() - /// A function to be called when the WebSocket connection's readyState changes to .Closed. - func webSocketClose(code: Int, reason: String, wasClean: Bool) - /// A function to be called when an error occurs. - func webSocketError(error: NSError) - /// A function to be called when a message (string) is received from the server. - optional func webSocketMessageText(text: String) - /// A function to be called when a message (binary) is received from the server. - optional func webSocketMessageData(data: NSData) - /// A function to be called when a pong is received from the server. - optional func webSocketPong() - /// A function to be called when the WebSocket process has ended; this event is guarenteed to be called once and can be used as an alternative to the "close" or "error" events. - optional func webSocketEnd(code: Int, reason: String, wasClean: Bool, error: NSError?) -} - /// The WebSocketEvents struct is used by the events property and manages the events for the WebSocket connection. public struct WebSocketEvents { /// An event to be called when the WebSocket connection's readyState changes to .Open; this indicates that the connection is ready to send and receive data. @@ -719,17 +701,12 @@ private class InnerWebSocket: Hashable { switch self.binaryType { case .UInt8Array: self.event.message(data: frame.payload.array) - // TODO: review - let array = frame.payload.array.map({ Int($0) }) - self.eventDelegate?.webSocketMessageData?(NSKeyedArchiver.archivedDataWithRootObject(array)) case .NSData: self.event.message(data: frame.payload.nsdata) + // The WebSocketDelegate is necessary to add Objective-C compability and it is only possible to send binary data with NSData. self.eventDelegate?.webSocketMessageData?(frame.payload.nsdata) case .UInt8UnsafeBufferPointer: self.event.message(data: frame.payload.buffer) - // TODO: review - let array = [UInt8](frame.payload.buffer).map({ Int($0) }) - self.eventDelegate?.webSocketMessageData?(NSKeyedArchiver.archivedDataWithRootObject(array)) } } case .Ping: @@ -1680,10 +1657,6 @@ public class WebSocket: NSObject { get { return ws.event } set { ws.event = newValue } } - public var delegate : WebSocketDelegate? { - get { return ws.eventDelegate } - set { ws.eventDelegate = newValue } - } /// The queue for firing off events. default is main_queue public var eventQueue : dispatch_queue_t?{ get { return ws.eventQueue } @@ -1750,24 +1723,6 @@ public class WebSocket: NSObject { } ws.send(message) } - /** - Transmits message to the server over the WebSocket connection. - - :param: text The message (string) to be sent to the server. - */ - @objc - public func send(text text: String){ - send(text) - } - /** - Transmits message to the server over the WebSocket connection. - - :param: data The message (binary) to be sent to the server. - */ - @objc - public func send(data data: NSData){ - send(data) - } /** Transmits a ping to the server over the WebSocket connection. @@ -1793,3 +1748,49 @@ public class WebSocket: NSObject { public func ==(lhs: WebSocket, rhs: WebSocket) -> Bool { return lhs.id == rhs.id } + +// MARK: - Objective-C + +@objc +public protocol WebSocketDelegate { + /// A function to be called when the WebSocket connection's readyState changes to .Open; this indicates that the connection is ready to send and receive data. + func webSocketOpen() + /// A function to be called when the WebSocket connection's readyState changes to .Closed. + func webSocketClose(code: Int, reason: String, wasClean: Bool) + /// A function to be called when an error occurs. + func webSocketError(error: NSError) + /// A function to be called when a message (string) is received from the server. + optional func webSocketMessageText(text: String) + /// A function to be called when a message (binary) is received from the server. + optional func webSocketMessageData(data: NSData) + /// A function to be called when a pong is received from the server. + optional func webSocketPong() + /// A function to be called when the WebSocket process has ended; this event is guarenteed to be called once and can be used as an alternative to the "close" or "error" events. + optional func webSocketEnd(code: Int, reason: String, wasClean: Bool, error: NSError?) +} + +extension WebSocket { + /// The events of the WebSocket using a delegate. + public var delegate : WebSocketDelegate? { + get { return ws.eventDelegate } + set { ws.eventDelegate = newValue } + } + /** + Transmits message to the server over the WebSocket connection. + + :param: text The message (string) to be sent to the server. + */ + @objc + public func send(text text: String){ + send(text) + } + /** + Transmits message to the server over the WebSocket connection. + + :param: data The message (binary) to be sent to the server. + */ + @objc + public func send(data data: NSData){ + send(data) + } +} From 6caa48bee1243b19c5588d23efe4cb5ff3d1131d Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Tue, 15 Dec 2015 07:15:01 +0000 Subject: [PATCH 06/10] Initialiser with NSURL --- Source/WebSocket.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/WebSocket.swift b/Source/WebSocket.swift index bafb540..b5843cc 100644 --- a/Source/WebSocket.swift +++ b/Source/WebSocket.swift @@ -1614,6 +1614,10 @@ public class WebSocket: NSObject { public convenience init(_ url: String){ self.init(request: NSURLRequest(URL: NSURL(string: url)!), subProtocols: []) } + /// Create a WebSocket connection to a URL; this should be the URL to which the WebSocket server will respond. + public convenience init(url: NSURL){ + self.init(request: NSURLRequest(URL: url), subProtocols: []) + } /// Create a WebSocket connection to a URL; this should be the URL to which the WebSocket server will respond. Also include a list of protocols. public convenience init(_ url: String, subProtocols : [String]){ self.init(request: NSURLRequest(URL: NSURL(string: url)!), subProtocols: subProtocols) From 4f9f638eadc0e6e0a692ad4de21d276e6c4be477 Mon Sep 17 00:00:00 2001 From: Josh Baker Date: Tue, 15 Dec 2015 10:33:55 -0700 Subject: [PATCH 07/10] minor reformat Moved the delegate protocol above the inner class to allow for the Autobahn test to complete. --- Source/WebSocket.swift | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/Source/WebSocket.swift b/Source/WebSocket.swift index b5843cc..064a046 100644 --- a/Source/WebSocket.swift +++ b/Source/WebSocket.swift @@ -491,6 +491,24 @@ private class Deflater { } } +/// WebSocketDelegate is an Objective-C alternative to WebSocketEvents and is used to delegate the events for the WebSocket connection. +@objc public protocol WebSocketDelegate { + /// A function to be called when the WebSocket connection's readyState changes to .Open; this indicates that the connection is ready to send and receive data. + func webSocketOpen() + /// A function to be called when the WebSocket connection's readyState changes to .Closed. + func webSocketClose(code: Int, reason: String, wasClean: Bool) + /// A function to be called when an error occurs. + func webSocketError(error: NSError) + /// A function to be called when a message (string) is received from the server. + optional func webSocketMessageText(text: String) + /// A function to be called when a message (binary) is received from the server. + optional func webSocketMessageData(data: NSData) + /// A function to be called when a pong is received from the server. + optional func webSocketPong() + /// A function to be called when the WebSocket process has ended; this event is guarenteed to be called once and can be used as an alternative to the "close" or "error" events. + optional func webSocketEnd(code: Int, reason: String, wasClean: Bool, error: NSError?) +} + /// WebSocket objects are bidirectional network streams that communicate over HTTP. RFC 6455. private class InnerWebSocket: Hashable { var id : Int @@ -1753,26 +1771,6 @@ public func ==(lhs: WebSocket, rhs: WebSocket) -> Bool { return lhs.id == rhs.id } -// MARK: - Objective-C - -@objc -public protocol WebSocketDelegate { - /// A function to be called when the WebSocket connection's readyState changes to .Open; this indicates that the connection is ready to send and receive data. - func webSocketOpen() - /// A function to be called when the WebSocket connection's readyState changes to .Closed. - func webSocketClose(code: Int, reason: String, wasClean: Bool) - /// A function to be called when an error occurs. - func webSocketError(error: NSError) - /// A function to be called when a message (string) is received from the server. - optional func webSocketMessageText(text: String) - /// A function to be called when a message (binary) is received from the server. - optional func webSocketMessageData(data: NSData) - /// A function to be called when a pong is received from the server. - optional func webSocketPong() - /// A function to be called when the WebSocket process has ended; this event is guarenteed to be called once and can be used as an alternative to the "close" or "error" events. - optional func webSocketEnd(code: Int, reason: String, wasClean: Bool, error: NSError?) -} - extension WebSocket { /// The events of the WebSocket using a delegate. public var delegate : WebSocketDelegate? { From 6dc86d1212c0f026475c9947eea49113ff4ef502 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Thu, 17 Dec 2015 18:05:01 +0000 Subject: [PATCH 08/10] [objc] Test integration --- SwiftWebSocket.xcodeproj/project.pbxproj | 115 ++++++++++++++++++ .../xcschemes/SwiftWebSocket-iOS.xcscheme | 19 +++ Test-ObjectiveC/Connection.h | 15 +++ Test-ObjectiveC/Connection.m | 64 ++++++++++ Test-ObjectiveC/Info.plist | 24 ++++ Test-ObjectiveC/Test_ObjectiveC.m | 32 +++++ 6 files changed, 269 insertions(+) create mode 100644 Test-ObjectiveC/Connection.h create mode 100644 Test-ObjectiveC/Connection.m create mode 100644 Test-ObjectiveC/Info.plist create mode 100644 Test-ObjectiveC/Test_ObjectiveC.m diff --git a/SwiftWebSocket.xcodeproj/project.pbxproj b/SwiftWebSocket.xcodeproj/project.pbxproj index 21380c2..ed873ba 100755 --- a/SwiftWebSocket.xcodeproj/project.pbxproj +++ b/SwiftWebSocket.xcodeproj/project.pbxproj @@ -17,6 +17,9 @@ D71948F51B35E5670015C529 /* SwiftWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = D71948F11B35E5670015C529 /* SwiftWebSocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; D719491A1B35E6640015C529 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D71948FA1B35E59D0015C529 /* libz.dylib */; }; D719491B1B35E7510015C529 /* SwiftWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = D71948F11B35E5670015C529 /* SwiftWebSocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D734D9EC1C232E6A00437555 /* Connection.m in Sources */ = {isa = PBXBuildFile; fileRef = D734D9EB1C232E6A00437555 /* Connection.m */; }; + D78C40331C232C3800EB72AA /* Test_ObjectiveC.m in Sources */ = {isa = PBXBuildFile; fileRef = D78C40321C232C3800EB72AA /* Test_ObjectiveC.m */; }; + D78C40351C232C3800EB72AA /* SwiftWebSocket.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03D1E7031B20897100AC49AC /* SwiftWebSocket.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -34,6 +37,13 @@ remoteGlobalIDString = 03D1E7021B20897100AC49AC; remoteInfo = "SwiftWebSocket-iOS"; }; + D78C40361C232C3800EB72AA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 03D1E6FA1B20897100AC49AC /* Project object */; + proxyType = 1; + remoteGlobalIDString = 03D1E7021B20897100AC49AC; + remoteInfo = "SwiftWebSocket-iOS"; + }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ @@ -48,6 +58,11 @@ D71948F11B35E5670015C529 /* SwiftWebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SwiftWebSocket.h; sourceTree = ""; }; D71948FA1B35E59D0015C529 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/lib/libz.dylib; sourceTree = DEVELOPER_DIR; }; D71949011B35E6130015C529 /* SwiftWebSocket.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftWebSocket.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D734D9EA1C232E6A00437555 /* Connection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Connection.h; sourceTree = ""; }; + D734D9EB1C232E6A00437555 /* Connection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Connection.m; sourceTree = ""; }; + D78C40301C232C3800EB72AA /* Test-ObjectiveC.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Test-ObjectiveC.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + D78C40321C232C3800EB72AA /* Test_ObjectiveC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Test_ObjectiveC.m; sourceTree = ""; }; + D78C40341C232C3800EB72AA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -83,6 +98,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D78C402D1C232C3800EB72AA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D78C40351C232C3800EB72AA /* SwiftWebSocket.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -93,6 +116,7 @@ 03D1E7211B208A5C00AC49AC /* libz.dylib */, D71948F01B35E5670015C529 /* Source */, 03D70CE71BDAC70100D245C3 /* Test */, + D78C40311C232C3800EB72AA /* Test-ObjectiveC */, 03D1E7041B20897100AC49AC /* Products */, ); sourceTree = ""; @@ -104,6 +128,7 @@ D71949011B35E6130015C529 /* SwiftWebSocket.framework */, 03D70CCC1BDAC5EC00D245C3 /* Test-OSX.xctest */, 03D70CDB1BDAC63600D245C3 /* Test-iOS.xctest */, + D78C40301C232C3800EB72AA /* Test-ObjectiveC.xctest */, ); name = Products; sourceTree = ""; @@ -127,6 +152,17 @@ path = Source; sourceTree = ""; }; + D78C40311C232C3800EB72AA /* Test-ObjectiveC */ = { + isa = PBXGroup; + children = ( + D78C40341C232C3800EB72AA /* Info.plist */, + D734D9EA1C232E6A00437555 /* Connection.h */, + D734D9EB1C232E6A00437555 /* Connection.m */, + D78C40321C232C3800EB72AA /* Test_ObjectiveC.m */, + ); + path = "Test-ObjectiveC"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -221,6 +257,24 @@ productReference = D71949011B35E6130015C529 /* SwiftWebSocket.framework */; productType = "com.apple.product-type.framework"; }; + D78C402F1C232C3800EB72AA /* Test-ObjectiveC */ = { + isa = PBXNativeTarget; + buildConfigurationList = D78C403A1C232C3800EB72AA /* Build configuration list for PBXNativeTarget "Test-ObjectiveC" */; + buildPhases = ( + D78C402C1C232C3800EB72AA /* Sources */, + D78C402D1C232C3800EB72AA /* Frameworks */, + D78C402E1C232C3800EB72AA /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + D78C40371C232C3800EB72AA /* PBXTargetDependency */, + ); + name = "Test-ObjectiveC"; + productName = "Test-ObjectiveC"; + productReference = D78C40301C232C3800EB72AA /* Test-ObjectiveC.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -243,6 +297,9 @@ D71949001B35E6130015C529 = { CreatedOnToolsVersion = 6.3.2; }; + D78C402F1C232C3800EB72AA = { + CreatedOnToolsVersion = 7.1.1; + }; }; }; buildConfigurationList = 03D1E6FD1B20897100AC49AC /* Build configuration list for PBXProject "SwiftWebSocket" */; @@ -261,6 +318,7 @@ D71949001B35E6130015C529 /* SwiftWebSocket-OSX */, 03D70CCB1BDAC5EC00D245C3 /* Test-OSX */, 03D70CDA1BDAC63600D245C3 /* Test-iOS */, + D78C402F1C232C3800EB72AA /* Test-ObjectiveC */, ); }; /* End PBXProject section */ @@ -294,6 +352,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D78C402E1C232C3800EB72AA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -329,6 +394,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D78C402C1C232C3800EB72AA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D78C40331C232C3800EB72AA /* Test_ObjectiveC.m in Sources */, + D734D9EC1C232E6A00437555 /* Connection.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -342,6 +416,11 @@ target = 03D1E7021B20897100AC49AC /* SwiftWebSocket-iOS */; targetProxy = 03D70CE11BDAC63600D245C3 /* PBXContainerItemProxy */; }; + D78C40371C232C3800EB72AA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 03D1E7021B20897100AC49AC /* SwiftWebSocket-iOS */; + targetProxy = D78C40361C232C3800EB72AA /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -572,6 +651,31 @@ }; name = Release; }; + D78C40381C232C3800EB72AA /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + DEBUG_INFORMATION_FORMAT = dwarf; + EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; + INFOPLIST_FILE = "Test-ObjectiveC/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 9.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.oncast.Test-ObjectiveC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + D78C40391C232C3800EB72AA /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; + INFOPLIST_FILE = "Test-ObjectiveC/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 9.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.oncast.Test-ObjectiveC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -600,6 +704,7 @@ 03D70CD51BDAC5EC00D245C3 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; 03D70CE31BDAC63600D245C3 /* Build configuration list for PBXNativeTarget "Test-iOS" */ = { isa = XCConfigurationList; @@ -608,6 +713,7 @@ 03D70CE51BDAC63600D245C3 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; D71949141B35E6160015C529 /* Build configuration list for PBXNativeTarget "SwiftWebSocket-OSX" */ = { isa = XCConfigurationList; @@ -618,6 +724,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + D78C403A1C232C3800EB72AA /* Build configuration list for PBXNativeTarget "Test-ObjectiveC" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D78C40381C232C3800EB72AA /* Debug */, + D78C40391C232C3800EB72AA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 03D1E6FA1B20897100AC49AC /* Project object */; diff --git a/SwiftWebSocket.xcodeproj/xcshareddata/xcschemes/SwiftWebSocket-iOS.xcscheme b/SwiftWebSocket.xcodeproj/xcshareddata/xcschemes/SwiftWebSocket-iOS.xcscheme index 2c9a4dc..0f08b79 100755 --- a/SwiftWebSocket.xcodeproj/xcshareddata/xcschemes/SwiftWebSocket-iOS.xcscheme +++ b/SwiftWebSocket.xcodeproj/xcshareddata/xcschemes/SwiftWebSocket-iOS.xcscheme @@ -28,7 +28,26 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + + + + + diff --git a/Test-ObjectiveC/Connection.h b/Test-ObjectiveC/Connection.h new file mode 100644 index 0000000..2644f97 --- /dev/null +++ b/Test-ObjectiveC/Connection.h @@ -0,0 +1,15 @@ +// +// Connection.h +// SwiftWebSocket +// +// Created by Ricardo Pereira on 17/12/15. +// Copyright © 2015 ONcast, LLC. All rights reserved. +// + +#import + +@interface Connection : NSObject + +- (void)open; + +@end diff --git a/Test-ObjectiveC/Connection.m b/Test-ObjectiveC/Connection.m new file mode 100644 index 0000000..139a01c --- /dev/null +++ b/Test-ObjectiveC/Connection.m @@ -0,0 +1,64 @@ +// +// Connection.m +// SwiftWebSocket +// +// Created by Ricardo Pereira on 17/12/15. +// Copyright © 2015 ONcast, LLC. All rights reserved. +// + +#import "Connection.h" +#import + +@interface Connection () + +@end + +@implementation Connection { + WebSocket *_webSocket; +} + +- (instancetype)init { + if (self = [super init]) { + _webSocket = nil; + } + return self; +} + +- (void)open { + _webSocket = [[WebSocket alloc] init:@"ws://localhost:9000"]; + _webSocket.delegate = self; + [_webSocket open]; +} + +- (void)webSocketOpen { + NSLog(@"Open"); + [_webSocket sendWithText:@"test"]; + [_webSocket sendWithData:[@"test" dataUsingEncoding:NSUTF8StringEncoding]]; + [_webSocket close:0 reason:@""]; +} + +- (void)webSocketClose:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean { + NSLog(@"Close: %@", reason); +} + +- (void)webSocketMessageText:(NSString *)text { + NSLog(@"Message: %@", text); +} + +- (void)webSocketMessageData:(NSData *)data { + NSLog(@"Message: %@", data); +} + +- (void)webSocketPong { + NSLog(@"Pong"); +} + +- (void)webSocketError:(NSError *)error { + NSLog(@"Error: %@", error); +} + +- (void)webSocketEnd:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean error:(NSError *)error { + NSLog(@"End: %@", error); +} + +@end diff --git a/Test-ObjectiveC/Info.plist b/Test-ObjectiveC/Info.plist new file mode 100644 index 0000000..ba72822 --- /dev/null +++ b/Test-ObjectiveC/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/Test-ObjectiveC/Test_ObjectiveC.m b/Test-ObjectiveC/Test_ObjectiveC.m new file mode 100644 index 0000000..902e365 --- /dev/null +++ b/Test-ObjectiveC/Test_ObjectiveC.m @@ -0,0 +1,32 @@ +// +// Test_ObjectiveC.m +// Test-ObjectiveC +// +// Created by Ricardo Pereira on 17/12/15. +// Copyright © 2015 ONcast, LLC. All rights reserved. +// + +#import +#import "Connection.h" + +@interface Test_ObjectiveC : XCTestCase + +@end + +@implementation Test_ObjectiveC + +- (void)setUp { + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testObjectiveC { + [[[Connection alloc] init] open]; +} + +@end From e550b3004b0cac10a589bc7703a46112a8ea2109 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Thu, 17 Dec 2015 18:05:28 +0000 Subject: [PATCH 09/10] [objc] Script to run the test from terminal --- tools/run-objc-test.sh | 54 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100755 tools/run-objc-test.sh diff --git a/tools/run-objc-test.sh b/tools/run-objc-test.sh new file mode 100755 index 0000000..f09dac6 --- /dev/null +++ b/tools/run-objc-test.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +set -o pipefail + +: ${BUILDTOOL:=xcodebuild} #Default + +# Xcode Build Command Line +# https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/xcodebuild.1.html +: ${PROJECT:="SwiftWebSocket.xcodeproj"} +: ${SCHEME:="SwiftWebSocket-iOS"} +: ${TARGET:="Test-ObjectiveC"} +: ${SDK:="iphonesimulator"} + +echo "Started: $(date)" + +init() { + # Launch the simulator before running the tests + # Avoid "iPhoneSimulator: Timed out waiting" + open -b com.apple.iphonesimulator +} + +COMMAND="-project \"${PROJECT}\" -scheme \"${SCHEME}\" -sdk \"${SDK}\"" + +case "${BUILDTOOL}" in + xctool) echo "Selected build tool: xctool" + init + # Tests (Swift & Objective-C) + case "${CLASS}" in + "") echo "Testing all classes" + COMMAND="xctool clean test "${COMMAND} + ;; + *) echo "Testing ${CLASS}" + COMMAND="xctool clean test -only ${CLASS} "${COMMAND} + ;; + esac + ;; + xcodebuild-travis) echo "Selected tool: xcodebuild + xcpretty (format: travisci)" + init + # Use xcpretty together with tee to store the raw log in a file, and get the pretty output in the terminal + COMMAND="xcodebuild clean test "${COMMAND}" | tee xcodebuild.log | xcpretty -f `xcpretty-travis-formatter`" + ;; + xcodebuild-pretty) echo "Selected tool: xcodebuild + xcpretty" + init + COMMAND="xcodebuild clean test "${COMMAND}" | xcpretty --test" + ;; + xcodebuild) echo "Selected tool: xcodebuild" + init + COMMAND="xcodebuild clean test "${COMMAND} + ;; + *) echo "No build tool especified" && exit 2 +esac + +set -x +eval "${COMMAND}" From 2b741740c9a7d8247c3f4de1e4a169bcff923630 Mon Sep 17 00:00:00 2001 From: Josh Baker Date: Thu, 17 Dec 2015 13:25:25 -0700 Subject: [PATCH 10/10] Objective-C compatibility --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f810761..6b40249 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ SwiftWebSocket passes all 521 of the Autobahn's fuzzing tests, including strict - Strict UTF-8 processing. - `binaryType` property to choose between `[UInt8]` or `NSData` messages. - Zero asserts. All networking, stream, and protocol errors are routed through the `error` event. +- Objective-C compatibility. ## Example