Compare commits
141 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b1472f2a99 | |||
| 34ed785564 | |||
| 08d2da1221 | |||
| 5c607a80ce | |||
| 3aac891e83 | |||
| 473a1382ac | |||
| c767cb2a54 | |||
| cdd1224a71 | |||
| 7c09aa08b4 | |||
| c2782b5452 | |||
| 14bc9ad90f | |||
| bdab51f4af | |||
| 26297ed3c1 | |||
| 600815b240 | |||
| 5fba5b83b4 | |||
| c39043cc29 | |||
| dd6bfa24b7 | |||
| 64661bc6ce | |||
| 0f03fab95d | |||
| c7b52b4485 | |||
| fc35de78a5 | |||
| d911dc1adf | |||
| f5f777c507 | |||
| cca4af6112 | |||
| afad19ca84 | |||
| b159d03640 | |||
| d5aeefd853 | |||
| f0f2ceee20 | |||
| 10cfb16197 | |||
| bf0146db26 | |||
| cf6def9453 | |||
| 8db1a45c6c | |||
| 592da86d06 | |||
| 43ade49abe | |||
| e32674a1ea | |||
| 7e49eb2458 | |||
| 53c19d2388 | |||
| ab02d55700 | |||
| fa0865fcac | |||
| 5d185b41cb | |||
| ba1d7db873 | |||
| e2eea95ea5 | |||
| 363e331a84 | |||
| 75721192c9 | |||
| 0c4a99588f | |||
| 4387f497c9 | |||
| d87b422950 | |||
| c5569dff12 | |||
| 6b303eafec | |||
| 0f8dd95f70 | |||
| ede7c9aa89 | |||
| afefdff061 | |||
| 3a59333bac | |||
| 1951d74516 | |||
| 6c34076224 | |||
| f189e7c51f | |||
| 95c91f9dbf | |||
| 398d9c15bf | |||
| 5a85e4e524 | |||
| d6f2c2c409 | |||
| ad91b8f1f3 | |||
| c59125f34a | |||
| ce9062ab81 | |||
| ff64f0b349 | |||
| 65dbee5c94 | |||
| 7927195b05 | |||
| d32dcb2aaa | |||
| aeae316209 | |||
| 4f2201d6b7 | |||
| ec313a376e | |||
| d5f690123f | |||
| b00a84716c | |||
| aa11fd3bbe | |||
| b6dddff56c | |||
| ac4f5ced1e | |||
| 947601b673 | |||
| cea6ce13c8 | |||
| 09c032a138 | |||
| 3fe7363ee9 | |||
| 32925e3847 | |||
| 6ad98fb458 | |||
| f02840355d | |||
| 4bece17d89 | |||
| 3582bab0b8 | |||
| 737fca6e73 | |||
| e9446a57cd | |||
| b6054567ee | |||
| 2b8d6214e2 | |||
| c56e0104c3 | |||
| 2bbc0895d6 | |||
| f1acef9a5f | |||
| b29059a937 | |||
| e1bbc7612e | |||
| e10942bc23 | |||
| 9cf2c1f787 | |||
| bc48e654ca | |||
| 5e2768c048 | |||
| 756a42756a | |||
| 4442aed401 | |||
| 5f4db02f99 | |||
| 679d858bbb | |||
| ec32edfb4e | |||
| df98a6e363 | |||
| 47bcdfbe57 | |||
| d69b169970 | |||
| aa2cb1b8fb | |||
| fe2c26b760 | |||
| 357cd532dc | |||
| e0e4b379e7 | |||
| 4c66b22b43 | |||
| e848f186a5 | |||
| c8201ec71c | |||
| f990c68c53 | |||
| 9d37723c4d | |||
| b32048b229 | |||
| a40f448a16 | |||
| dcb046a46c | |||
| 357a178a64 | |||
| 7a15d7d7e6 | |||
| 13f7502163 | |||
| 2e5cb03ac3 | |||
| b26ffa28cd | |||
| b962e3e532 | |||
| 5dfe1fc117 | |||
| 4d072de18d | |||
| f0b188606f | |||
| fac69339ba | |||
| e2c3e38a23 | |||
| 5f46a9097e | |||
| 8376a9c70b | |||
| 02f8eac43b | |||
| 6a318b29d2 | |||
| df88d97511 | |||
| ca478d26b2 | |||
| 4b65324a8d | |||
| 9663215089 | |||
| 5d272a80ef | |||
| e39ddeecf5 | |||
| 36d4426888 | |||
| a69929fbc1 | |||
| 3178fb1705 |
@@ -0,0 +1,50 @@
|
||||
# Change Log
|
||||
All notable changes to this project will be documented in this file.
|
||||
`Starscream` adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
#### [1.1.3](https://github.com/daltoniam/Starscream/tree/1.1.3)
|
||||
|
||||
Changed:
|
||||
[#170](https://github.com/daltoniam/Starscream/issues/170)
|
||||
[#171](https://github.com/daltoniam/Starscream/issues/171)
|
||||
[#174](https://github.com/daltoniam/Starscream/issues/174)
|
||||
[#177](https://github.com/daltoniam/Starscream/issues/177)
|
||||
[#178](https://github.com/daltoniam/Starscream/issues/178)
|
||||
|
||||
#### [1.1.2](https://github.com/daltoniam/Starscream/tree/1.1.2)
|
||||
|
||||
Fixed:
|
||||
[#158](https://github.com/daltoniam/Starscream/issues/158)
|
||||
[#161](https://github.com/daltoniam/Starscream/issues/161)
|
||||
[#164](https://github.com/daltoniam/Starscream/issues/164)
|
||||
|
||||
#### [1.1.1](https://github.com/daltoniam/Starscream/tree/1.1.1)
|
||||
|
||||
Fixed:
|
||||
[#157](https://github.com/daltoniam/Starscream/issues/157)
|
||||
|
||||
#### [1.1.0](https://github.com/daltoniam/Starscream/tree/1.1.0)
|
||||
|
||||
Changed:
|
||||
Moved over to Runloop/default GCD queues to shared queue.
|
||||
|
||||
Fixed:
|
||||
[#153](https://github.com/daltoniam/Starscream/issues/153)
|
||||
[#151](https://github.com/daltoniam/Starscream/issues/151)
|
||||
[#150](https://github.com/daltoniam/Starscream/issues/150)
|
||||
[#149](https://github.com/daltoniam/Starscream/issues/149)
|
||||
[#147](https://github.com/daltoniam/Starscream/issues/147)
|
||||
[#139](https://github.com/daltoniam/Starscream/issues/139)
|
||||
[#77](https://github.com/daltoniam/Starscream/issues/77)
|
||||
|
||||
#### [1.0.2](https://github.com/daltoniam/Starscream/tree/1.0.2)
|
||||
|
||||
Added TVOS support.
|
||||
|
||||
#### [1.0.1](https://github.com/daltoniam/Starscream/tree/1.0.1)
|
||||
|
||||
Fixes for #121, #123
|
||||
|
||||
#### [1.0.0](https://github.com/daltoniam/Starscream/tree/1.0.0)
|
||||
|
||||
first release of Swift 2 support.
|
||||
@@ -1,7 +1,9 @@
|
||||
Apache License
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
Copyright (c) 2014-2015 Dalton Cherry.
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
@@ -171,31 +173,4 @@ Apache License
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
of your accepting any such warranty or additional liability.
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// Package.Swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 5/16/15.
|
||||
// Copyright (c) 2014-2015 Dalton Cherry.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Starscream"
|
||||
)
|
||||
@@ -4,12 +4,13 @@ Starscream is a conforming WebSocket ([RFC 6455](http://tools.ietf.org/html/rfc6
|
||||
|
||||
It's Objective-C counter part can be found here: [Jetfire](https://github.com/acmacalister/jetfire)
|
||||
|
||||
###Swift 3/Xcode 8
|
||||
If you are looking for Swift 3 support, see [swift 3 here](https://github.com/daltoniam/Starscream/tree/swift3)
|
||||
|
||||
## Features
|
||||
|
||||
- Conforms to all of the base [Autobahn test suite](http://autobahn.ws/testsuite/).
|
||||
- Nonblocking. Everything happens in the background, thanks to GCD.
|
||||
- Simple delegate pattern design.
|
||||
- TLS/WSS support.
|
||||
- Simple concise codebase at just a few hundred LOC.
|
||||
|
||||
@@ -21,10 +22,10 @@ First thing is to import the framework. See the Installation instructions on how
|
||||
import Starscream
|
||||
```
|
||||
|
||||
Once imported, you can open a connection to your WebSocket server. Note that `socket` is probably best as a property, so your delegate can stick around.
|
||||
Once imported, you can open a connection to your WebSocket server. Note that `socket` is probably best as a property, so it doesn't get deallocated right after being setup.
|
||||
|
||||
```swift
|
||||
var socket = WebSocket(url: NSURL(scheme: "ws", host: "localhost:8080", path: "/")!)
|
||||
socket = WebSocket(url: NSURL(string: "ws://localhost:8080/")!)
|
||||
socket.delegate = self
|
||||
socket.connect()
|
||||
```
|
||||
@@ -37,7 +38,7 @@ websocketDidConnect is called as soon as the client connects to the server.
|
||||
|
||||
```swift
|
||||
func websocketDidConnect(socket: WebSocket) {
|
||||
println("websocket is connected")
|
||||
print("websocket is connected")
|
||||
}
|
||||
```
|
||||
|
||||
@@ -47,7 +48,7 @@ websocketDidDisconnect is called as soon as the client is disconnected from the
|
||||
|
||||
```swift
|
||||
func websocketDidDisconnect(socket: WebSocket, error: NSError?) {
|
||||
println("websocket is disconnected: \(error?.localizedDescription)")
|
||||
print("websocket is disconnected: \(error?.localizedDescription)")
|
||||
}
|
||||
```
|
||||
|
||||
@@ -57,7 +58,7 @@ websocketDidReceiveMessage is called when the client gets a text frame from the
|
||||
|
||||
```swift
|
||||
func websocketDidReceiveMessage(socket: WebSocket, text: String) {
|
||||
println("got some text: \(text)")
|
||||
print("got some text: \(text)")
|
||||
}
|
||||
```
|
||||
|
||||
@@ -67,7 +68,7 @@ websocketDidReceiveData is called when the client gets a binary frame from the c
|
||||
|
||||
```swift
|
||||
func websocketDidReceiveData(socket: WebSocket, data: NSData) {
|
||||
println("got some data: \(data.length)")
|
||||
print("got some data: \(data.length)")
|
||||
}
|
||||
```
|
||||
|
||||
@@ -77,34 +78,36 @@ websocketDidReceivePong is called when the client gets a pong response from the
|
||||
|
||||
```swift
|
||||
func websocketDidReceivePong(socket: WebSocket) {
|
||||
println("Got pong!")
|
||||
print("Got pong!")
|
||||
}
|
||||
```
|
||||
|
||||
Or you can use closures.
|
||||
|
||||
```swift
|
||||
var socket = WebSocket(url: NSURL(scheme: "ws", host: "localhost:8080", path: "/")!)
|
||||
socket = WebSocket(url: NSURL(string: "ws://localhost:8080/")!)
|
||||
//websocketDidConnect
|
||||
socket.onConnect = {
|
||||
println("websocket is connected")
|
||||
print("websocket is connected")
|
||||
}
|
||||
//websocketDidDisconnect
|
||||
socket.onDisconnect = { (error: NSError?) in
|
||||
println("websocket is disconnected: \(error?.localizedDescription)")
|
||||
print("websocket is disconnected: \(error?.localizedDescription)")
|
||||
}
|
||||
//websocketDidReceiveMessage
|
||||
socket.onText = { (text: String) in
|
||||
println("got some text: \(text)")
|
||||
print("got some text: \(text)")
|
||||
}
|
||||
//websocketDidReceiveData
|
||||
socket.onData = { (data: NSData) in
|
||||
println("got some data: \(data.length)")
|
||||
print("got some data: \(data.length)")
|
||||
}
|
||||
//you could do onPong as well.
|
||||
socket.connect()
|
||||
```
|
||||
|
||||
One more: you can listen to socket connection and disconnection via notifications. Starscream posts `WebsocketDidConnectNotification` and `WebsocketDidDisconnectNotification`. You can find an `NSError` that caused the disconection by accessing `WebsocketDisconnectionErrorKeyName` on notification `userInfo`.
|
||||
|
||||
|
||||
## The delegate methods give you a simple way to handle data from the server, but how do you send data?
|
||||
|
||||
@@ -113,7 +116,7 @@ socket.connect()
|
||||
The writeData method gives you a simple way to send `NSData` (binary) data to the server.
|
||||
|
||||
```swift
|
||||
self.socket.writeData(data) //write some NSData over the socket!
|
||||
socket.writeData(data) //write some NSData over the socket!
|
||||
```
|
||||
|
||||
### writeString
|
||||
@@ -121,7 +124,7 @@ self.socket.writeData(data) //write some NSData over the socket!
|
||||
The writeString method is the same as writeData, but sends text/string.
|
||||
|
||||
```swift
|
||||
self.socket.writeString("Hi Server!") //example on how to write text over the socket!
|
||||
socket.writeString("Hi Server!") //example on how to write text over the socket!
|
||||
```
|
||||
|
||||
### writePing
|
||||
@@ -129,7 +132,7 @@ self.socket.writeString("Hi Server!") //example on how to write text over the so
|
||||
The writePing method is the same as writeData, but sends a ping control frame.
|
||||
|
||||
```swift
|
||||
self.socket.writePing(NSData()) //example on how to write a ping control frame over the socket!
|
||||
socket.writePing(NSData()) //example on how to write a ping control frame over the socket!
|
||||
```
|
||||
|
||||
### disconnect
|
||||
@@ -137,7 +140,7 @@ self.socket.writePing(NSData()) //example on how to write a ping control frame o
|
||||
The disconnect method does what you would expect and closes the socket.
|
||||
|
||||
```swift
|
||||
self.socket.disconnect()
|
||||
socket.disconnect()
|
||||
```
|
||||
|
||||
### isConnected
|
||||
@@ -145,7 +148,7 @@ self.socket.disconnect()
|
||||
Returns if the socket is connected or not.
|
||||
|
||||
```swift
|
||||
if self.socket.isConnected {
|
||||
if socket.isConnected {
|
||||
// do cool stuff.
|
||||
}
|
||||
```
|
||||
@@ -166,7 +169,7 @@ If you need to specify a protocol, simple add it to the init:
|
||||
|
||||
```swift
|
||||
//chat and superchat are the example protocols here
|
||||
var socket = WebSocket(url: NSURL(scheme: "ws", host: "localhost:8080", path: "/")!, protocols: ["chat","superchat"])
|
||||
socket = WebSocket(url: NSURL(string: "ws://localhost:8080/")!, protocols: ["chat","superchat"])
|
||||
socket.delegate = self
|
||||
socket.connect()
|
||||
```
|
||||
@@ -176,7 +179,7 @@ socket.connect()
|
||||
There are a couple of other properties that modify the stream:
|
||||
|
||||
```swift
|
||||
var socket = WebSocket(url: NSURL(scheme: "ws", host: "localhost:8080", path: "/")!, protocols: ["chat","superchat"])
|
||||
socket = WebSocket(url: NSURL(string: "ws://localhost:8080/")!, protocols: ["chat","superchat"])
|
||||
|
||||
//set this if you are planning on using the socket in a VOIP background setting (using the background VOIP service).
|
||||
socket.voipEnabled = true
|
||||
@@ -190,10 +193,10 @@ socket.selfSignedSSL = true
|
||||
SSL Pinning is also supported in Starscream.
|
||||
|
||||
```swift
|
||||
var socket = WebSocket(url: NSURL(scheme: "ws", host: "localhost:8080", path: "/")!, protocols: ["chat","superchat"])
|
||||
socket = WebSocket(url: NSURL(string: "ws://localhost:8080/")!, protocols: ["chat","superchat"])
|
||||
let data = ... //load your certificate from disk
|
||||
socket.security = Security(certs: [SSLCert(data: data)], usePublicKeys: true)
|
||||
//socket.security = Security() //uses the .cer files in your app's bundle
|
||||
socket.security = SSLSecurity(certs: [SSLCert(data: data)], usePublicKeys: true)
|
||||
//socket.security = SSLSecurity() //uses the .cer files in your app's bundle
|
||||
```
|
||||
You load either a `NSData` blob of your certificate or you can use a `SecKeyRef` if you have a public key you want to use. The `usePublicKeys` bool is whether to use the certificates for validation or the public keys. The public keys will be extracted from the certificates automatically if `usePublicKeys` is choosen.
|
||||
|
||||
@@ -202,7 +205,7 @@ You load either a `NSData` blob of your certificate or you can use a `SecKeyRef`
|
||||
A custom queue can be specified when delegate methods are called. By default `dispatch_get_main_queue` is used, thus making all delegate methods calls run on the main thread. It is important to note that all WebSocket processing is done on a background thread, only the delegate method calls are changed when modifying the queue. The actual processing is always on a background thread and will not pause your app.
|
||||
|
||||
```swift
|
||||
var socket = WebSocket(url: NSURL(scheme: "ws", host: "localhost:8080", path: "/")!, protocols: ["chat","superchat"])
|
||||
socket = WebSocket(url: NSURL(string: "ws://localhost:8080/")!, protocols: ["chat","superchat"])
|
||||
//create a custom queue
|
||||
socket.queue = dispatch_queue_create("com.vluxe.starscream.myapp", nil)
|
||||
```
|
||||
@@ -213,21 +216,21 @@ Check out the SimpleTest project in the examples directory to see how to setup a
|
||||
|
||||
## Requirements
|
||||
|
||||
Starscream works with iOS 7/OSX 10.9 or above. It is recommended to use iOS 8/10.10 or above for Cocoapods/framework support.
|
||||
Starscream works with iOS 7/OSX 10.9 or above. It is recommended to use iOS 8/10.10 or above for CocoaPods/framework support. To use Starscream with a project targeting iOS 7, you must include all Swift files directly in your project.
|
||||
|
||||
## Installation
|
||||
|
||||
### Cocoapods
|
||||
### CocoaPods
|
||||
|
||||
Check out [Get Started](http://cocoapods.org/) tab on [cocoapods.org](http://cocoapods.org/).
|
||||
|
||||
To use Starscream in your project add the following 'Podfile' to your project
|
||||
|
||||
source 'https://github.com/CocoaPods/Specs.git'
|
||||
platform :ios, '8.0'
|
||||
platform :ios, '9.0'
|
||||
use_frameworks!
|
||||
|
||||
pod 'Starscream', '~> 0.9.4'
|
||||
pod 'Starscream', '~> 1.1.3'
|
||||
|
||||
Then run:
|
||||
|
||||
@@ -239,6 +242,19 @@ Check out the [Carthage](https://github.com/Carthage/Carthage) docs on how to ad
|
||||
|
||||
[Carthage Install](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application)
|
||||
|
||||
You can install Carthage with [Homebrew](http://brew.sh/) using the following command:
|
||||
|
||||
```bash
|
||||
$ brew update
|
||||
$ brew install carthage
|
||||
```
|
||||
|
||||
To integrate Starscream into your Xcode project using Carthage, specify it in your `Cartfile`:
|
||||
|
||||
```
|
||||
github "daltoniam/Starscream" >= 1.1.3
|
||||
```
|
||||
|
||||
### Rogue
|
||||
|
||||
First see the [installation docs](https://github.com/acmacalister/Rogue) for how to install Rogue.
|
||||
@@ -263,8 +279,9 @@ If you are running this in an OSX app or on a physical iOS device you will need
|
||||
|
||||
## TODOs
|
||||
|
||||
- [ ] Complete Docs
|
||||
- [ ] Add Unit Tests
|
||||
- [ ] WatchOS?
|
||||
- [ ] Linux Support?
|
||||
- [ ] Add Unit Tests - Local Swift websocket server
|
||||
|
||||
## License
|
||||
|
||||
|
||||
-244
@@ -1,244 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Security.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 5/16/15.
|
||||
// Copyright (c) 2015 Vluxe. All rights reserved.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import Foundation
|
||||
import Security
|
||||
|
||||
public class SSLCert {
|
||||
var certData: NSData?
|
||||
var key: SecKeyRef?
|
||||
|
||||
/**
|
||||
Designated init for certificates
|
||||
|
||||
:param: data is the binary data of the certificate
|
||||
|
||||
:returns: a representation security object to be used with
|
||||
*/
|
||||
public init(data: NSData) {
|
||||
self.certData = data
|
||||
}
|
||||
|
||||
/**
|
||||
Designated init for public keys
|
||||
|
||||
:param: key is the public key to be used
|
||||
|
||||
:returns: a representation security object to be used with
|
||||
*/
|
||||
public init(key: SecKeyRef) {
|
||||
self.key = key
|
||||
}
|
||||
}
|
||||
|
||||
public class Security {
|
||||
public var validatedDN = true //should the domain name be validated?
|
||||
|
||||
var isReady = false //is the key processing done?
|
||||
var certificates: [NSData]? //the certificates
|
||||
var pubKeys: [SecKeyRef]? //the public keys
|
||||
var usePublicKeys = false //use public keys or certificate validation?
|
||||
|
||||
/**
|
||||
Use certs from main app bundle
|
||||
|
||||
:param: usePublicKeys is to specific if the publicKeys or certificates should be used for SSL pinning validation
|
||||
|
||||
:returns: a representation security object to be used with
|
||||
*/
|
||||
public convenience init(usePublicKeys: Bool = false) {
|
||||
let paths = NSBundle.mainBundle().pathsForResourcesOfType("cer", inDirectory: ".")
|
||||
var collect = Array<SSLCert>()
|
||||
for path in paths {
|
||||
if let d = NSData(contentsOfFile: path as! String) {
|
||||
collect.append(SSLCert(data: d))
|
||||
}
|
||||
}
|
||||
self.init(certs:collect, usePublicKeys: usePublicKeys)
|
||||
}
|
||||
|
||||
/**
|
||||
Designated init
|
||||
|
||||
:param: keys is the certificates or public keys to use
|
||||
:param: usePublicKeys is to specific if the publicKeys or certificates should be used for SSL pinning validation
|
||||
|
||||
:returns: a representation security object to be used with
|
||||
*/
|
||||
public init(certs: [SSLCert], usePublicKeys: Bool) {
|
||||
self.usePublicKeys = usePublicKeys
|
||||
|
||||
if self.usePublicKeys {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), {
|
||||
var collect = Array<SecKeyRef>()
|
||||
for cert in certs {
|
||||
if let data = cert.certData where cert.key == nil {
|
||||
cert.key = self.extractPublicKey(data)
|
||||
}
|
||||
if let k = cert.key {
|
||||
collect.append(k)
|
||||
}
|
||||
}
|
||||
self.pubKeys = collect
|
||||
self.isReady = true
|
||||
})
|
||||
} else {
|
||||
var collect = Array<NSData>()
|
||||
for cert in certs {
|
||||
if let d = cert.certData {
|
||||
collect.append(d)
|
||||
}
|
||||
}
|
||||
self.certificates = collect
|
||||
self.isReady = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Valid the trust and domain name.
|
||||
|
||||
:param: trust is the serverTrust to validate
|
||||
:param: domain is the CN domain to validate
|
||||
|
||||
:returns: if the key was successfully validated
|
||||
*/
|
||||
public func isValid(trust: SecTrustRef, domain: String?) -> Bool {
|
||||
|
||||
var tries = 0
|
||||
while(!self.isReady) {
|
||||
usleep(1000)
|
||||
tries += 1
|
||||
if tries > 5 {
|
||||
return false //doesn't appear it is going to ever be ready...
|
||||
}
|
||||
}
|
||||
var policy: SecPolicyRef
|
||||
if self.validatedDN {
|
||||
policy = SecPolicyCreateSSL(1, domain).takeRetainedValue()
|
||||
} else {
|
||||
policy = SecPolicyCreateBasicX509().takeRetainedValue()
|
||||
}
|
||||
SecTrustSetPolicies(trust,policy)
|
||||
if self.usePublicKeys {
|
||||
if let keys = self.pubKeys {
|
||||
var trustedCount = 0
|
||||
let serverPubKeys = publicKeyChainForTrust(trust)
|
||||
for serverKey in serverPubKeys as [AnyObject] {
|
||||
for key in keys as [AnyObject] {
|
||||
if serverKey.isEqual(key) {
|
||||
trustedCount++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if trustedCount == serverPubKeys.count {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else if let certs = self.certificates {
|
||||
let serverCerts = certificateChainForTrust(trust)
|
||||
var collect = Array<SecCertificate>()
|
||||
for cert in certs {
|
||||
collect.append(SecCertificateCreateWithData(nil,cert).takeRetainedValue())
|
||||
}
|
||||
SecTrustSetAnchorCertificates(trust,collect)
|
||||
var result: SecTrustResultType = 0
|
||||
SecTrustEvaluate(trust,&result)
|
||||
let r = Int(result)
|
||||
if r == kSecTrustResultUnspecified || r == kSecTrustResultProceed {
|
||||
var trustedCount = 0
|
||||
for serverCert in serverCerts {
|
||||
for cert in certs {
|
||||
if cert == serverCert {
|
||||
trustedCount++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if trustedCount == serverCerts.count {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
Get the public key from a certificate data
|
||||
|
||||
:param: data is the certificate to pull the public key from
|
||||
|
||||
:returns: a public key
|
||||
*/
|
||||
func extractPublicKey(data: NSData) -> SecKeyRef? {
|
||||
var publicKey: NSData?
|
||||
let possibleCert = SecCertificateCreateWithData(nil,data)
|
||||
if let cert = possibleCert {
|
||||
return extractPublicKeyFromCert(cert.takeRetainedValue(),policy: SecPolicyCreateBasicX509().takeRetainedValue())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/**
|
||||
Get the public key from a certificate
|
||||
|
||||
:param: data is the certificate to pull the public key from
|
||||
|
||||
:returns: a public key
|
||||
*/
|
||||
func extractPublicKeyFromCert(cert: SecCertificate, policy: SecPolicy) -> SecKeyRef? {
|
||||
var possibleTrust: Unmanaged<SecTrust>?
|
||||
SecTrustCreateWithCertificates(cert,policy, &possibleTrust)
|
||||
if let trust = possibleTrust {
|
||||
let t = trust.takeRetainedValue()
|
||||
var result: SecTrustResultType = 0
|
||||
SecTrustEvaluate(t,&result)
|
||||
return SecTrustCopyPublicKey(t).takeRetainedValue()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/**
|
||||
Get the certificate chain for the trust
|
||||
|
||||
:param: trust is the trust to lookup the certificate chain for
|
||||
|
||||
:returns: the certificate chain for the trust
|
||||
*/
|
||||
func certificateChainForTrust(trust: SecTrustRef) -> Array<NSData> {
|
||||
var collect = Array<NSData>()
|
||||
for var i = 0; i < SecTrustGetCertificateCount(trust); i++ {
|
||||
let cert = SecTrustGetCertificateAtIndex(trust,i)
|
||||
collect.append(SecCertificateCopyData(cert.takeRetainedValue()).takeRetainedValue())
|
||||
}
|
||||
return collect
|
||||
}
|
||||
|
||||
/**
|
||||
Get the public key chain for the trust
|
||||
|
||||
:param: trust is the trust to lookup the certificate chain and extract the public keys
|
||||
|
||||
:returns: the public keys from the certifcate chain for the trust
|
||||
*/
|
||||
func publicKeyChainForTrust(trust: SecTrustRef) -> Array<SecKeyRef> {
|
||||
var collect = Array<SecKeyRef>()
|
||||
let policy = SecPolicyCreateBasicX509().takeRetainedValue()
|
||||
for var i = 0; i < SecTrustGetCertificateCount(trust); i++ {
|
||||
let cert = SecTrustGetCertificateAtIndex(trust,i)
|
||||
if let key = extractPublicKeyFromCert(cert.takeRetainedValue(), policy: policy) {
|
||||
collect.append(key)
|
||||
}
|
||||
}
|
||||
return collect
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.1.3</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -7,7 +7,7 @@
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.vluxe.$(PRODUCT_NAME:rfc1034identifier)</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<string>1.1.3</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
@@ -0,0 +1,257 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SSLSecurity.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 5/16/15.
|
||||
// Copyright (c) 2014-2015 Dalton Cherry.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import Foundation
|
||||
import Security
|
||||
|
||||
public class SSLCert {
|
||||
var certData: Data?
|
||||
var key: SecKey?
|
||||
|
||||
/**
|
||||
Designated init for certificates
|
||||
|
||||
- parameter data: is the binary data of the certificate
|
||||
|
||||
- returns: a representation security object to be used with
|
||||
*/
|
||||
public init(data: Data) {
|
||||
self.certData = data
|
||||
}
|
||||
|
||||
/**
|
||||
Designated init for public keys
|
||||
|
||||
- parameter key: is the public key to be used
|
||||
|
||||
- returns: a representation security object to be used with
|
||||
*/
|
||||
public init(key: SecKey) {
|
||||
self.key = key
|
||||
}
|
||||
}
|
||||
|
||||
public class SSLSecurity {
|
||||
public var validatedDN = true //should the domain name be validated?
|
||||
|
||||
var isReady = false //is the key processing done?
|
||||
var certificates: [Data]? //the certificates
|
||||
var pubKeys: [SecKey]? //the public keys
|
||||
var usePublicKeys = false //use public keys or certificate validation?
|
||||
|
||||
/**
|
||||
Use certs from main app bundle
|
||||
|
||||
- parameter usePublicKeys: is to specific if the publicKeys or certificates should be used for SSL pinning validation
|
||||
|
||||
- returns: a representation security object to be used with
|
||||
*/
|
||||
public convenience init(usePublicKeys: Bool = false) {
|
||||
let paths = Bundle.main.paths(forResourcesOfType: "cer", inDirectory: ".")
|
||||
|
||||
let certs = paths.reduce([SSLCert]()) { (certs: [SSLCert], path: String) -> [SSLCert] in
|
||||
var certs = certs
|
||||
if let data = NSData(contentsOfFile: path) {
|
||||
certs.append(SSLCert(data: data as Data))
|
||||
}
|
||||
return certs
|
||||
}
|
||||
|
||||
self.init(certs: certs, usePublicKeys: usePublicKeys)
|
||||
}
|
||||
|
||||
/**
|
||||
Designated init
|
||||
|
||||
- parameter keys: is the certificates or public keys to use
|
||||
- parameter usePublicKeys: is to specific if the publicKeys or certificates should be used for SSL pinning validation
|
||||
|
||||
- returns: a representation security object to be used with
|
||||
*/
|
||||
public init(certs: [SSLCert], usePublicKeys: Bool) {
|
||||
self.usePublicKeys = usePublicKeys
|
||||
|
||||
if self.usePublicKeys {
|
||||
DispatchQueue.global(qos: .default).async {
|
||||
let pubKeys = certs.reduce([SecKey]()) { (pubKeys: [SecKey], cert: SSLCert) -> [SecKey] in
|
||||
var pubKeys = pubKeys
|
||||
if let data = cert.certData, cert.key == nil {
|
||||
cert.key = self.extractPublicKey(data)
|
||||
}
|
||||
if let key = cert.key {
|
||||
pubKeys.append(key)
|
||||
}
|
||||
return pubKeys
|
||||
}
|
||||
|
||||
self.pubKeys = pubKeys
|
||||
self.isReady = true
|
||||
}
|
||||
} else {
|
||||
let certificates = certs.reduce([Data]()) { (certificates: [Data], cert: SSLCert) -> [Data] in
|
||||
var certificates = certificates
|
||||
if let data = cert.certData {
|
||||
certificates.append(data)
|
||||
}
|
||||
return certificates
|
||||
}
|
||||
self.certificates = certificates
|
||||
self.isReady = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Valid the trust and domain name.
|
||||
|
||||
- parameter trust: is the serverTrust to validate
|
||||
- parameter domain: is the CN domain to validate
|
||||
|
||||
- returns: if the key was successfully validated
|
||||
*/
|
||||
public func isValid(_ trust: SecTrust, domain: String?) -> Bool {
|
||||
|
||||
var tries = 0
|
||||
while !self.isReady {
|
||||
usleep(1000)
|
||||
tries += 1
|
||||
if tries > 5 {
|
||||
return false //doesn't appear it is going to ever be ready...
|
||||
}
|
||||
}
|
||||
var policy: SecPolicy
|
||||
if self.validatedDN {
|
||||
policy = SecPolicyCreateSSL(true, domain as NSString?)
|
||||
} else {
|
||||
policy = SecPolicyCreateBasicX509()
|
||||
}
|
||||
SecTrustSetPolicies(trust,policy)
|
||||
if self.usePublicKeys {
|
||||
if let keys = self.pubKeys {
|
||||
let serverPubKeys = publicKeyChain(trust)
|
||||
for serverKey in serverPubKeys as [AnyObject] {
|
||||
for key in keys as [AnyObject] {
|
||||
if serverKey.isEqual(key) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let certs = self.certificates {
|
||||
let serverCerts = certificateChain(trust)
|
||||
var collect = [SecCertificate]()
|
||||
for cert in certs {
|
||||
collect.append(SecCertificateCreateWithData(nil,cert as CFData)!)
|
||||
}
|
||||
SecTrustSetAnchorCertificates(trust,collect as NSArray)
|
||||
var result: SecTrustResultType = .unspecified
|
||||
SecTrustEvaluate(trust,&result)
|
||||
if result == .unspecified || result == .proceed {
|
||||
var trustedCount = 0
|
||||
for serverCert in serverCerts {
|
||||
for cert in certs {
|
||||
if cert == serverCert {
|
||||
trustedCount += 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if trustedCount == serverCerts.count {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
Get the public key from a certificate data
|
||||
|
||||
- parameter data: is the certificate to pull the public key from
|
||||
|
||||
- returns: a public key
|
||||
*/
|
||||
func extractPublicKey(_ data: Data) -> SecKey? {
|
||||
guard let cert = SecCertificateCreateWithData(nil, data as CFData) else { return nil }
|
||||
|
||||
return extractPublicKey(cert, policy: SecPolicyCreateBasicX509())
|
||||
}
|
||||
|
||||
/**
|
||||
Get the public key from a certificate
|
||||
|
||||
- parameter data: is the certificate to pull the public key from
|
||||
|
||||
- returns: a public key
|
||||
*/
|
||||
func extractPublicKey(_ cert: SecCertificate, policy: SecPolicy) -> SecKey? {
|
||||
var possibleTrust: SecTrust?
|
||||
SecTrustCreateWithCertificates(cert, policy, &possibleTrust)
|
||||
|
||||
guard let trust = possibleTrust else { return nil }
|
||||
|
||||
var result: SecTrustResultType = .unspecified
|
||||
SecTrustEvaluate(trust, &result)
|
||||
return SecTrustCopyPublicKey(trust)
|
||||
}
|
||||
|
||||
/**
|
||||
Get the certificate chain for the trust
|
||||
|
||||
- parameter trust: is the trust to lookup the certificate chain for
|
||||
|
||||
- returns: the certificate chain for the trust
|
||||
*/
|
||||
func certificateChain(_ trust: SecTrust) -> [Data] {
|
||||
let certificates = (0..<SecTrustGetCertificateCount(trust)).reduce([Data]()) { (certificates: [Data], index: Int) -> [Data] in
|
||||
var certificates = certificates
|
||||
let cert = SecTrustGetCertificateAtIndex(trust, index)
|
||||
certificates.append(SecCertificateCopyData(cert!) as Data)
|
||||
return certificates
|
||||
}
|
||||
|
||||
return certificates
|
||||
}
|
||||
|
||||
/**
|
||||
Get the public key chain for the trust
|
||||
|
||||
- parameter trust: is the trust to lookup the certificate chain and extract the public keys
|
||||
|
||||
- returns: the public keys from the certifcate chain for the trust
|
||||
*/
|
||||
func publicKeyChain(_ trust: SecTrust) -> [SecKey] {
|
||||
let policy = SecPolicyCreateBasicX509()
|
||||
let keys = (0..<SecTrustGetCertificateCount(trust)).reduce([SecKey]()) { (keys: [SecKey], index: Int) -> [SecKey] in
|
||||
var keys = keys
|
||||
let cert = SecTrustGetCertificateAtIndex(trust, index)
|
||||
if let key = extractPublicKey(cert!, policy: policy) {
|
||||
keys.append(key)
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,880 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Websocket.swift
|
||||
//
|
||||
// Created by Dalton Cherry on 7/16/14.
|
||||
// Copyright (c) 2014-2015 Dalton Cherry.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import Foundation
|
||||
import CoreFoundation
|
||||
import Security
|
||||
|
||||
public let WebsocketDidConnectNotification = "WebsocketDidConnectNotification"
|
||||
public let WebsocketDidDisconnectNotification = "WebsocketDidDisconnectNotification"
|
||||
public let WebsocketDisconnectionErrorKeyName = "WebsocketDisconnectionErrorKeyName"
|
||||
|
||||
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: Data)
|
||||
}
|
||||
|
||||
public protocol WebSocketPongDelegate: class {
|
||||
func websocketDidReceivePong(_ socket: WebSocket)
|
||||
}
|
||||
|
||||
public class WebSocket : NSObject, StreamDelegate {
|
||||
|
||||
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.
|
||||
}
|
||||
|
||||
public 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
|
||||
}
|
||||
|
||||
public static let ErrorDomain = "WebSocket"
|
||||
|
||||
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 callbackQueue = DispatchQueue.main
|
||||
|
||||
var optionalProtocols : [String]?
|
||||
|
||||
// MARK: - Constants
|
||||
|
||||
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 = 4096
|
||||
let FinMask: UInt8 = 0x80
|
||||
let OpCodeMask: UInt8 = 0x0F
|
||||
let RSVMask: UInt8 = 0x70
|
||||
let MaskMask: UInt8 = 0x80
|
||||
let PayloadLenMask: UInt8 = 0x7F
|
||||
let MaxFrameSize: Int = 32
|
||||
let httpSwitchProtocolCode = 101
|
||||
let supportedSSLSchemes = ["wss", "https"]
|
||||
|
||||
class WSResponse {
|
||||
var isFin = false
|
||||
var code: OpCode = .continueFrame
|
||||
var bytesLeft = 0
|
||||
var frameCount = 0
|
||||
var buffer: NSMutableData?
|
||||
}
|
||||
|
||||
// MARK: - Delegates
|
||||
|
||||
/// Responds to callback about new messages coming in over the WebSocket
|
||||
/// and also connection/disconnect messages.
|
||||
public weak var delegate: WebSocketDelegate?
|
||||
|
||||
/// Recives a callback for each pong message recived.
|
||||
public weak var pongDelegate: WebSocketPongDelegate?
|
||||
|
||||
|
||||
// MARK: - Block based API.
|
||||
|
||||
public var onConnect: ((Void) -> Void)?
|
||||
public var onDisconnect: ((NSError?) -> Void)?
|
||||
public var onText: ((String) -> Void)?
|
||||
public var onData: ((Data) -> Void)?
|
||||
public var onPong: ((Void) -> Void)?
|
||||
|
||||
public var headers = [String: String]()
|
||||
public var voipEnabled = false
|
||||
public var selfSignedSSL = false
|
||||
public var security: SSLSecurity?
|
||||
public var enabledSSLCipherSuites: [SSLCipherSuite]?
|
||||
public var origin: String?
|
||||
public var timeout = 5
|
||||
public var isConnected :Bool {
|
||||
return connected
|
||||
}
|
||||
|
||||
public var currentURL: URL { return url }
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private var url: URL
|
||||
private var inputStream: InputStream?
|
||||
private var outputStream: OutputStream?
|
||||
private var connected = false
|
||||
private var isConnecting = false
|
||||
private var writeQueue = OperationQueue()
|
||||
private var readStack = [WSResponse]()
|
||||
private var inputQueue = [Data]()
|
||||
private var fragBuffer: Data?
|
||||
private var certValidated = false
|
||||
private var didDisconnect = false
|
||||
private var readyToWrite = false
|
||||
private let mutex = NSLock()
|
||||
private let notificationCenter = NotificationCenter.default
|
||||
private var canDispatch: Bool {
|
||||
mutex.lock()
|
||||
let canWork = readyToWrite
|
||||
mutex.unlock()
|
||||
return canWork
|
||||
}
|
||||
/// The shared processing queue used for all WebSocket.
|
||||
private static let sharedWorkQueue = DispatchQueue(label: "com.vluxe.starscream.websocket", attributes: [])
|
||||
|
||||
/// Used for setting protocols.
|
||||
public init(url: URL, protocols: [String]? = nil) {
|
||||
self.url = url
|
||||
self.origin = url.absoluteString
|
||||
writeQueue.maxConcurrentOperationCount = 1
|
||||
optionalProtocols = protocols
|
||||
}
|
||||
|
||||
/// Connect to the WebSocket server on a background thread.
|
||||
public func connect() {
|
||||
guard !isConnecting else { return }
|
||||
didDisconnect = false
|
||||
isConnecting = true
|
||||
createHTTPRequest()
|
||||
isConnecting = false
|
||||
}
|
||||
|
||||
/**
|
||||
Disconnect from the server. I send a Close control frame to the server, then expect the server to respond with a Close control frame and close the socket from its end. I notify my delegate once the socket has been closed.
|
||||
|
||||
If you supply a non-nil `forceTimeout`, I wait at most that long (in seconds) for the server to close the socket. After the timeout expires, I close the socket and notify my delegate.
|
||||
|
||||
If you supply a zero (or negative) `forceTimeout`, I immediately close the socket (without sending a Close control frame) and notify my delegate.
|
||||
|
||||
- Parameter forceTimeout: Maximum time to wait for the server to close the socket.
|
||||
*/
|
||||
public func disconnect(forceTimeout: TimeInterval? = nil) {
|
||||
switch forceTimeout {
|
||||
case .some(let seconds) where seconds > 0:
|
||||
callbackQueue.asyncAfter(deadline: DispatchTime.now() + Double(Int64(seconds * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)) { [weak self] in
|
||||
self?.disconnectStream(nil)
|
||||
}
|
||||
fallthrough
|
||||
case .none:
|
||||
writeError(CloseCode.normal.rawValue)
|
||||
default:
|
||||
disconnectStream(nil)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Write a string to the websocket. This sends it as a text frame.
|
||||
|
||||
If you supply a non-nil completion block, I will perform it when the write completes.
|
||||
|
||||
- parameter str: The string to write.
|
||||
- parameter completion: The (optional) completion handler.
|
||||
*/
|
||||
public func write(string: String, completion: (() -> ())? = nil) {
|
||||
guard isConnected else { return }
|
||||
dequeueWrite(string.data(using: String.Encoding.utf8)!, code: .textFrame, writeCompletion: completion)
|
||||
}
|
||||
|
||||
/**
|
||||
Write binary data to the websocket. This sends it as a binary frame.
|
||||
|
||||
If you supply a non-nil completion block, I will perform it when the write completes.
|
||||
|
||||
- parameter data: The data to write.
|
||||
- parameter completion: The (optional) completion handler.
|
||||
*/
|
||||
public func write(data: Data, completion: (() -> ())? = nil) {
|
||||
guard isConnected else { return }
|
||||
dequeueWrite(data, code: .binaryFrame, writeCompletion: completion)
|
||||
}
|
||||
|
||||
// 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 write(_ ping: Data, completion: (() -> ())? = nil) {
|
||||
guard isConnected else { return }
|
||||
dequeueWrite(ping, code: .ping, writeCompletion: completion)
|
||||
}
|
||||
|
||||
/// Private method that starts the connection.
|
||||
private func createHTTPRequest() {
|
||||
|
||||
let urlRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, "GET" as CFString,
|
||||
url as CFURL, kCFHTTPVersion1_1).takeRetainedValue()
|
||||
|
||||
var port = url.port
|
||||
if port == nil {
|
||||
if supportedSSLSchemes.contains(url.scheme!) {
|
||||
port = 443
|
||||
} else {
|
||||
port = 80
|
||||
}
|
||||
}
|
||||
addHeader(urlRequest, key: headerWSUpgradeName, val: headerWSUpgradeValue)
|
||||
addHeader(urlRequest, key: headerWSConnectionName, val: headerWSConnectionValue)
|
||||
if let protocols = optionalProtocols {
|
||||
addHeader(urlRequest, key: headerWSProtocolName, val: protocols.joined(separator: ","))
|
||||
}
|
||||
addHeader(urlRequest, key: headerWSVersionName, val: headerWSVersionValue)
|
||||
addHeader(urlRequest, key: headerWSKeyName, val: generateWebSocketKey())
|
||||
if let origin = origin {
|
||||
addHeader(urlRequest, key: headerOriginName, val: origin)
|
||||
}
|
||||
addHeader(urlRequest, key: headerWSHostName, val: "\(url.host!):\(port!)")
|
||||
for (key,value) in headers {
|
||||
addHeader(urlRequest, key: key, val: value)
|
||||
}
|
||||
if let cfHTTPMessage = CFHTTPMessageCopySerializedMessage(urlRequest) {
|
||||
let serializedRequest = cfHTTPMessage.takeRetainedValue()
|
||||
initStreamsWithData(serializedRequest as Data, Int(port!))
|
||||
}
|
||||
}
|
||||
|
||||
// Add a header to the CFHTTPMessage by using the NSString bridges to CFString
|
||||
private func addHeader(_ urlRequest: CFHTTPMessage, key: String, val: String) {
|
||||
CFHTTPMessageSetHeaderFieldValue(urlRequest, key as CFString, val as CFString)
|
||||
}
|
||||
|
||||
/// Generate a WebSocket key as needed in RFC.
|
||||
private func generateWebSocketKey() -> String {
|
||||
var key = ""
|
||||
let seed = 16
|
||||
for _ in 0..<seed {
|
||||
let uni = UnicodeScalar(UInt32(97 + arc4random_uniform(25)))
|
||||
key += "\(Character(uni!))"
|
||||
}
|
||||
let data = key.data(using: String.Encoding.utf8)
|
||||
let baseKey = data?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
|
||||
return baseKey!
|
||||
}
|
||||
|
||||
/// Start the stream connection and write the data to the output stream.
|
||||
private func initStreamsWithData(_ data: Data, _ 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<CFReadStream>?
|
||||
var writeStream: Unmanaged<CFWriteStream>?
|
||||
let h = url.host! as NSString
|
||||
CFStreamCreatePairWithSocketToHost(nil, h, UInt32(port), &readStream, &writeStream)
|
||||
inputStream = readStream!.takeRetainedValue()
|
||||
outputStream = writeStream!.takeRetainedValue()
|
||||
guard let inStream = inputStream, let outStream = outputStream else { return }
|
||||
inStream.delegate = self
|
||||
outStream.delegate = self
|
||||
if supportedSSLSchemes.contains(url.scheme!) {
|
||||
inStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL as AnyObject, forKey: Stream.PropertyKey.socketSecurityLevelKey)
|
||||
outStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL as AnyObject, forKey: Stream.PropertyKey.socketSecurityLevelKey)
|
||||
} else {
|
||||
certValidated = true //not a https session, so no need to check SSL pinning
|
||||
}
|
||||
if voipEnabled {
|
||||
inStream.setProperty(StreamNetworkServiceTypeValue.voIP as AnyObject, forKey: Stream.PropertyKey.networkServiceType)
|
||||
outStream.setProperty(StreamNetworkServiceTypeValue.voIP as AnyObject, forKey: Stream.PropertyKey.networkServiceType)
|
||||
}
|
||||
if selfSignedSSL {
|
||||
let settings: [NSObject: NSObject] = [kCFStreamSSLValidatesCertificateChain: NSNumber(value: false), kCFStreamSSLPeerName: kCFNull]
|
||||
inStream.setProperty(settings, forKey: kCFStreamPropertySSLSettings as Stream.PropertyKey)
|
||||
outStream.setProperty(settings, forKey: kCFStreamPropertySSLSettings as Stream.PropertyKey)
|
||||
}
|
||||
if let cipherSuites = self.enabledSSLCipherSuites {
|
||||
if let sslContextIn = CFReadStreamCopyProperty(inputStream, CFStreamPropertyKey(rawValue: kCFStreamPropertySSLContext)) as! SSLContext?,
|
||||
let sslContextOut = CFWriteStreamCopyProperty(outputStream, CFStreamPropertyKey(rawValue: kCFStreamPropertySSLContext)) as! SSLContext? {
|
||||
let resIn = SSLSetEnabledCiphers(sslContextIn, cipherSuites, cipherSuites.count)
|
||||
let resOut = SSLSetEnabledCiphers(sslContextOut, cipherSuites, cipherSuites.count)
|
||||
if resIn != errSecSuccess {
|
||||
let error = self.errorWithDetail("Error setting ingoing cypher suites", code: UInt16(resIn))
|
||||
disconnectStream(error)
|
||||
return
|
||||
}
|
||||
if resOut != errSecSuccess {
|
||||
let error = self.errorWithDetail("Error setting outgoing cypher suites", code: UInt16(resOut))
|
||||
disconnectStream(error)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CFReadStreamSetDispatchQueue(inStream, WebSocket.sharedWorkQueue)
|
||||
CFWriteStreamSetDispatchQueue(outStream, WebSocket.sharedWorkQueue)
|
||||
inStream.open()
|
||||
outStream.open()
|
||||
|
||||
self.mutex.lock()
|
||||
self.readyToWrite = true
|
||||
self.mutex.unlock()
|
||||
|
||||
let bytes = UnsafeRawPointer((data as NSData).bytes).assumingMemoryBound(to: UInt8.self)
|
||||
var out = timeout * 1000000 // wait 5 seconds before giving up
|
||||
writeQueue.addOperation { [weak self] in
|
||||
while !outStream.hasSpaceAvailable {
|
||||
usleep(100) // wait until the socket is ready
|
||||
out -= 100
|
||||
if out < 0 {
|
||||
self?.cleanupStream()
|
||||
self?.doDisconnect(self?.errorWithDetail("write wait timed out", code: 2))
|
||||
return
|
||||
} else if outStream.streamError != nil {
|
||||
return // disconnectStream will be called.
|
||||
}
|
||||
}
|
||||
outStream.write(bytes, maxLength: data.count)
|
||||
}
|
||||
}
|
||||
|
||||
//delegate for the stream methods. Processes incoming bytes
|
||||
public func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
|
||||
if let sec = security, !certValidated && [.hasBytesAvailable, .hasSpaceAvailable].contains(eventCode) {
|
||||
let trust = aStream.property(forKey: kCFStreamPropertySSLPeerTrust as Stream.PropertyKey) as AnyObject
|
||||
let domain = aStream.property(forKey: kCFStreamSSLPeerName as Stream.PropertyKey) as? String
|
||||
if sec.isValid(trust as! SecTrust, domain: domain) {
|
||||
certValidated = true
|
||||
} else {
|
||||
let error = errorWithDetail("Invalid SSL certificate", code: 1)
|
||||
disconnectStream(error)
|
||||
return
|
||||
}
|
||||
}
|
||||
if eventCode == .hasBytesAvailable {
|
||||
if aStream == inputStream {
|
||||
processInputStream()
|
||||
}
|
||||
} else if eventCode == .errorOccurred {
|
||||
disconnectStream(aStream.streamError as NSError?)
|
||||
} else if eventCode == .endEncountered {
|
||||
disconnectStream(nil)
|
||||
}
|
||||
}
|
||||
|
||||
/// Disconnect the stream object and notifies the delegate.
|
||||
private func disconnectStream(_ error: NSError?) {
|
||||
if error == nil {
|
||||
writeQueue.waitUntilAllOperationsAreFinished()
|
||||
} else {
|
||||
writeQueue.cancelAllOperations()
|
||||
}
|
||||
cleanupStream()
|
||||
doDisconnect(error)
|
||||
}
|
||||
|
||||
private func cleanupStream() {
|
||||
outputStream?.delegate = nil
|
||||
inputStream?.delegate = nil
|
||||
if let stream = inputStream {
|
||||
CFReadStreamSetDispatchQueue(stream, nil)
|
||||
stream.close()
|
||||
}
|
||||
if let stream = outputStream {
|
||||
CFWriteStreamSetDispatchQueue(stream, nil)
|
||||
stream.close()
|
||||
}
|
||||
outputStream = nil
|
||||
inputStream = nil
|
||||
}
|
||||
|
||||
/// Handles the incoming bytes and sending them to the proper processing method.
|
||||
private func processInputStream() {
|
||||
let buf = NSMutableData(capacity: BUFFER_MAX)
|
||||
let buffer = UnsafeMutableRawPointer(mutating: buf!.bytes).assumingMemoryBound(to: UInt8.self)
|
||||
let length = inputStream!.read(buffer, maxLength: BUFFER_MAX)
|
||||
|
||||
guard length > 0 else { return }
|
||||
var process = false
|
||||
if inputQueue.count == 0 {
|
||||
process = true
|
||||
}
|
||||
inputQueue.append(Data(bytes: buffer, count: length))
|
||||
if process {
|
||||
dequeueInput()
|
||||
}
|
||||
}
|
||||
|
||||
/// Dequeue the incoming input so it is processed in order.
|
||||
private func dequeueInput() {
|
||||
while !inputQueue.isEmpty {
|
||||
let data = inputQueue[0]
|
||||
var work = data
|
||||
if let fragBuffer = fragBuffer {
|
||||
var combine = NSData(data: fragBuffer) as Data
|
||||
combine.append(data)
|
||||
work = combine
|
||||
self.fragBuffer = nil
|
||||
}
|
||||
let buffer = UnsafeRawPointer((work as NSData).bytes).assumingMemoryBound(to: UInt8.self)
|
||||
let length = work.count
|
||||
if !connected {
|
||||
processTCPHandshake(buffer, bufferLen: length)
|
||||
} else {
|
||||
processRawMessagesInBuffer(buffer, bufferLen: length)
|
||||
}
|
||||
inputQueue = inputQueue.filter{ $0 != data }
|
||||
}
|
||||
}
|
||||
|
||||
//handle checking the inital connection status
|
||||
private func processTCPHandshake(_ buffer: UnsafePointer<UInt8>, bufferLen: Int) {
|
||||
let code = processHTTP(buffer, bufferLen: bufferLen)
|
||||
switch code {
|
||||
case 0:
|
||||
connected = true
|
||||
guard canDispatch else {return}
|
||||
callbackQueue.async { [weak self] in
|
||||
guard let s = self else { return }
|
||||
s.onConnect?()
|
||||
s.delegate?.websocketDidConnect(s)
|
||||
s.notificationCenter.post(name: NSNotification.Name(WebsocketDidConnectNotification), object: self)
|
||||
}
|
||||
case -1:
|
||||
fragBuffer = Data(bytes: buffer, count: bufferLen)
|
||||
break // do nothing, we are going to collect more data
|
||||
default:
|
||||
doDisconnect(errorWithDetail("Invalid HTTP upgrade", code: UInt16(code)))
|
||||
}
|
||||
}
|
||||
|
||||
///Finds the HTTP Packet in the TCP stream, by looking for the CRLF.
|
||||
private func processHTTP(_ buffer: UnsafePointer<UInt8>, bufferLen: Int) -> Int {
|
||||
let CRLFBytes = [UInt8(ascii: "\r"), UInt8(ascii: "\n"), UInt8(ascii: "\r"), UInt8(ascii: "\n")]
|
||||
var k = 0
|
||||
var totalSize = 0
|
||||
for i in 0..<bufferLen {
|
||||
if buffer[i] == CRLFBytes[k] {
|
||||
k += 1
|
||||
if k == 3 {
|
||||
totalSize = i + 1
|
||||
break
|
||||
}
|
||||
} else {
|
||||
k = 0
|
||||
}
|
||||
}
|
||||
if totalSize > 0 {
|
||||
let code = validateResponse(buffer, bufferLen: totalSize)
|
||||
if code != 0 {
|
||||
return code
|
||||
}
|
||||
totalSize += 1 //skip the last \n
|
||||
let restSize = bufferLen - totalSize
|
||||
if restSize > 0 {
|
||||
processRawMessagesInBuffer(buffer + totalSize, bufferLen: restSize)
|
||||
}
|
||||
return 0 //success
|
||||
}
|
||||
return -1 // Was unable to find the full TCP header.
|
||||
}
|
||||
|
||||
/// Validates the HTTP is a 101 as per the RFC spec.
|
||||
private func validateResponse(_ buffer: UnsafePointer<UInt8>, bufferLen: Int) -> Int {
|
||||
let response = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, false).takeRetainedValue()
|
||||
CFHTTPMessageAppendBytes(response, buffer, bufferLen)
|
||||
let code = CFHTTPMessageGetResponseStatusCode(response)
|
||||
if code != httpSwitchProtocolCode {
|
||||
return code
|
||||
}
|
||||
if let cfHeaders = CFHTTPMessageCopyAllHeaderFields(response) {
|
||||
let headers = cfHeaders.takeRetainedValue() as NSDictionary
|
||||
if let acceptKey = headers[headerWSAcceptName as NSString] as? NSString {
|
||||
if acceptKey.length > 0 {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
///read a 16 bit big endian value from a buffer
|
||||
private static func readUint16(_ buffer: UnsafePointer<UInt8>, offset: Int) -> UInt16 {
|
||||
return (UInt16(buffer[offset + 0]) << 8) | UInt16(buffer[offset + 1])
|
||||
}
|
||||
|
||||
///read a 64 bit big endian value from a buffer
|
||||
private static func readUint64(_ buffer: UnsafePointer<UInt8>, offset: Int) -> UInt64 {
|
||||
var value = UInt64(0)
|
||||
for i in 0...7 {
|
||||
value = (value << 8) | UInt64(buffer[offset + i])
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
/// Write a 16-bit big endian value to a buffer.
|
||||
private static func writeUint16(_ buffer: UnsafeMutablePointer<UInt8>, offset: Int, value: UInt16) {
|
||||
buffer[offset + 0] = UInt8(value >> 8)
|
||||
buffer[offset + 1] = UInt8(value & 0xff)
|
||||
}
|
||||
|
||||
/// Write a 64-bit big endian value to a buffer.
|
||||
private static func writeUint64(_ buffer: UnsafeMutablePointer<UInt8>, offset: Int, value: UInt64) {
|
||||
for i in 0...7 {
|
||||
buffer[offset + i] = UInt8((value >> (8*UInt64(7 - i))) & 0xff)
|
||||
}
|
||||
}
|
||||
|
||||
/// Process one message at the start of `buffer`. Return another buffer (sharing storage) that contains the leftover contents of `buffer` that I didn't process.
|
||||
|
||||
private func processOneRawMessage(inBuffer buffer: UnsafeBufferPointer<UInt8>) -> UnsafeBufferPointer<UInt8> {
|
||||
let response = readStack.last
|
||||
guard let baseAddress = buffer.baseAddress else {return emptyBuffer}
|
||||
let bufferLen = buffer.count
|
||||
if response != nil && bufferLen < 2 {
|
||||
fragBuffer = Data(buffer: buffer)
|
||||
return emptyBuffer
|
||||
}
|
||||
if let response = response, response.bytesLeft > 0 {
|
||||
var len = response.bytesLeft
|
||||
var extra = bufferLen - response.bytesLeft
|
||||
if response.bytesLeft > bufferLen {
|
||||
len = bufferLen
|
||||
extra = 0
|
||||
}
|
||||
response.bytesLeft -= len
|
||||
response.buffer?.append(Data(bytes: baseAddress, count: len))
|
||||
_ = processResponse(response)
|
||||
return buffer.fromOffset(bufferLen - extra)
|
||||
} else {
|
||||
let isFin = (FinMask & baseAddress[0])
|
||||
let receivedOpcode = OpCode(rawValue: (OpCodeMask & baseAddress[0]))
|
||||
let isMasked = (MaskMask & baseAddress[1])
|
||||
let payloadLen = (PayloadLenMask & baseAddress[1])
|
||||
var offset = 2
|
||||
if (isMasked > 0 || (RSVMask & baseAddress[0]) > 0) && receivedOpcode != .pong {
|
||||
let errCode = CloseCode.protocolError.rawValue
|
||||
doDisconnect(errorWithDetail("masked and rsv data is not currently supported", code: errCode))
|
||||
writeError(errCode)
|
||||
return emptyBuffer
|
||||
}
|
||||
let isControlFrame = (receivedOpcode == .connectionClose || receivedOpcode == .ping)
|
||||
if !isControlFrame && (receivedOpcode != .binaryFrame && receivedOpcode != .continueFrame &&
|
||||
receivedOpcode != .textFrame && receivedOpcode != .pong) {
|
||||
let errCode = CloseCode.protocolError.rawValue
|
||||
doDisconnect(errorWithDetail("unknown opcode: \(receivedOpcode)", code: errCode))
|
||||
writeError(errCode)
|
||||
return emptyBuffer
|
||||
}
|
||||
if isControlFrame && isFin == 0 {
|
||||
let errCode = CloseCode.protocolError.rawValue
|
||||
doDisconnect(errorWithDetail("control frames can't be fragmented", code: errCode))
|
||||
writeError(errCode)
|
||||
return emptyBuffer
|
||||
}
|
||||
if receivedOpcode == .connectionClose {
|
||||
var code = CloseCode.normal.rawValue
|
||||
if payloadLen == 1 {
|
||||
code = CloseCode.protocolError.rawValue
|
||||
} else if payloadLen > 1 {
|
||||
code = WebSocket.readUint16(baseAddress, offset: offset)
|
||||
if code < 1000 || (code > 1003 && code < 1007) || (code > 1011 && code < 3000) {
|
||||
code = CloseCode.protocolError.rawValue
|
||||
}
|
||||
offset += 2
|
||||
}
|
||||
var closeReason = "connection closed by server"
|
||||
if payloadLen > 2 {
|
||||
let len = Int(payloadLen - 2)
|
||||
if len > 0 {
|
||||
let bytes = baseAddress + offset
|
||||
if let customCloseReason = String(data: Data(bytes: bytes, count: len), encoding: .utf8) {
|
||||
closeReason = customCloseReason
|
||||
} else {
|
||||
code = CloseCode.protocolError.rawValue
|
||||
}
|
||||
}
|
||||
}
|
||||
doDisconnect(errorWithDetail(closeReason, code: code))
|
||||
writeError(code)
|
||||
return emptyBuffer
|
||||
}
|
||||
if isControlFrame && payloadLen > 125 {
|
||||
writeError(CloseCode.protocolError.rawValue)
|
||||
return emptyBuffer
|
||||
}
|
||||
var dataLength = UInt64(payloadLen)
|
||||
if dataLength == 127 {
|
||||
dataLength = WebSocket.readUint64(baseAddress, offset: offset)
|
||||
offset += MemoryLayout<UInt64>.size
|
||||
} else if dataLength == 126 {
|
||||
dataLength = UInt64(WebSocket.readUint16(baseAddress, offset: offset))
|
||||
offset += MemoryLayout<UInt16>.size
|
||||
}
|
||||
if bufferLen < offset || UInt64(bufferLen - offset) < dataLength {
|
||||
fragBuffer = Data(bytes: baseAddress, count: bufferLen)
|
||||
return emptyBuffer
|
||||
}
|
||||
var len = dataLength
|
||||
if dataLength > UInt64(bufferLen) {
|
||||
len = UInt64(bufferLen-offset)
|
||||
}
|
||||
let data: Data
|
||||
if len < 0 {
|
||||
len = 0
|
||||
data = Data()
|
||||
} else {
|
||||
data = Data(bytes: baseAddress+offset, count: Int(len))
|
||||
}
|
||||
if receivedOpcode == .pong {
|
||||
if canDispatch {
|
||||
callbackQueue.async { [weak self] in
|
||||
guard let s = self else { return }
|
||||
s.onPong?()
|
||||
s.pongDelegate?.websocketDidReceivePong(s)
|
||||
}
|
||||
}
|
||||
return buffer.fromOffset(offset + Int(len))
|
||||
}
|
||||
var response = readStack.last
|
||||
if isControlFrame {
|
||||
response = nil // Don't append pings.
|
||||
}
|
||||
if isFin == 0 && receivedOpcode == .continueFrame && response == nil {
|
||||
let errCode = CloseCode.protocolError.rawValue
|
||||
doDisconnect(errorWithDetail("continue frame before a binary or text frame", code: errCode))
|
||||
writeError(errCode)
|
||||
return emptyBuffer
|
||||
}
|
||||
var isNew = false
|
||||
if response == nil {
|
||||
if receivedOpcode == .continueFrame {
|
||||
let errCode = CloseCode.protocolError.rawValue
|
||||
doDisconnect(errorWithDetail("first frame can't be a continue frame",
|
||||
code: errCode))
|
||||
writeError(errCode)
|
||||
return emptyBuffer
|
||||
}
|
||||
isNew = true
|
||||
response = WSResponse()
|
||||
response!.code = receivedOpcode!
|
||||
response!.bytesLeft = Int(dataLength)
|
||||
response!.buffer = NSMutableData(data: data)
|
||||
} else {
|
||||
if receivedOpcode == .continueFrame {
|
||||
response!.bytesLeft = Int(dataLength)
|
||||
} else {
|
||||
let errCode = CloseCode.protocolError.rawValue
|
||||
doDisconnect(errorWithDetail("second and beyond of fragment message must be a continue frame",
|
||||
code: errCode))
|
||||
writeError(errCode)
|
||||
return emptyBuffer
|
||||
}
|
||||
response!.buffer!.append(data)
|
||||
}
|
||||
if let response = response {
|
||||
response.bytesLeft -= Int(len)
|
||||
response.frameCount += 1
|
||||
response.isFin = isFin > 0 ? true : false
|
||||
if isNew {
|
||||
readStack.append(response)
|
||||
}
|
||||
_ = processResponse(response)
|
||||
}
|
||||
|
||||
let step = Int(offset + numericCast(len))
|
||||
return buffer.fromOffset(step)
|
||||
}
|
||||
}
|
||||
|
||||
/// Process all messages in the buffer if possible.
|
||||
private func processRawMessagesInBuffer(_ pointer: UnsafePointer<UInt8>, bufferLen: Int) {
|
||||
var buffer = UnsafeBufferPointer(start: pointer, count: bufferLen)
|
||||
repeat {
|
||||
buffer = processOneRawMessage(inBuffer: buffer)
|
||||
} while buffer.count >= 2
|
||||
if buffer.count > 0 {
|
||||
fragBuffer = Data(buffer: buffer)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 as Data, code: .pong)
|
||||
} else if response.code == .textFrame {
|
||||
let str: NSString? = NSString(data: response.buffer! as Data, encoding: String.Encoding.utf8.rawValue)
|
||||
if str == nil {
|
||||
writeError(CloseCode.encoding.rawValue)
|
||||
return false
|
||||
}
|
||||
if canDispatch {
|
||||
callbackQueue.async { [weak self] in
|
||||
guard let s = self else { return }
|
||||
s.onText?(str! as String)
|
||||
s.delegate?.websocketDidReceiveMessage(s, text: str! as String)
|
||||
}
|
||||
}
|
||||
} else if response.code == .binaryFrame {
|
||||
if canDispatch {
|
||||
let data = response.buffer! // local copy so it is perverse for writing
|
||||
callbackQueue.async { [weak self] in
|
||||
guard let s = self else { return }
|
||||
s.onData?(data as Data)
|
||||
s.delegate?.websocketDidReceiveData(s, data: data as Data)
|
||||
}
|
||||
}
|
||||
}
|
||||
readStack.removeLast()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/// Create an error
|
||||
private func errorWithDetail(_ detail: String, code: UInt16) -> NSError {
|
||||
var details = [String: String]()
|
||||
details[NSLocalizedDescriptionKey] = detail
|
||||
return NSError(domain: WebSocket.ErrorDomain, code: Int(code), userInfo: details)
|
||||
}
|
||||
|
||||
/// Write an error to the socket
|
||||
private func writeError(_ code: UInt16) {
|
||||
let buf = NSMutableData(capacity: MemoryLayout<UInt16>.size)
|
||||
let buffer = UnsafeMutableRawPointer(mutating: buf!.bytes).assumingMemoryBound(to: UInt8.self)
|
||||
WebSocket.writeUint16(buffer, offset: 0, value: code)
|
||||
dequeueWrite(Data(bytes: buffer, count: MemoryLayout<UInt16>.size), code: .connectionClose)
|
||||
}
|
||||
|
||||
/// Used to write things to the stream
|
||||
private func dequeueWrite(_ data: Data, code: OpCode, writeCompletion: (() -> ())? = nil) {
|
||||
writeQueue.addOperation { [weak self] in
|
||||
//stream isn't ready, let's wait
|
||||
guard let s = self else { return }
|
||||
var offset = 2
|
||||
let dataLength = data.count
|
||||
let frame = NSMutableData(capacity: dataLength + s.MaxFrameSize)
|
||||
let buffer = UnsafeMutableRawPointer(frame!.mutableBytes).assumingMemoryBound(to: UInt8.self)
|
||||
buffer[0] = s.FinMask | code.rawValue
|
||||
if dataLength < 126 {
|
||||
buffer[1] = CUnsignedChar(dataLength)
|
||||
} else if dataLength <= Int(UInt16.max) {
|
||||
buffer[1] = 126
|
||||
WebSocket.writeUint16(buffer, offset: offset, value: UInt16(dataLength))
|
||||
offset += MemoryLayout<UInt16>.size
|
||||
} else {
|
||||
buffer[1] = 127
|
||||
WebSocket.writeUint64(buffer, offset: offset, value: UInt64(dataLength))
|
||||
offset += MemoryLayout<UInt64>.size
|
||||
}
|
||||
buffer[1] |= s.MaskMask
|
||||
let maskKey = UnsafeMutablePointer<UInt8>(buffer + offset)
|
||||
_ = SecRandomCopyBytes(kSecRandomDefault, Int(MemoryLayout<UInt32>.size), maskKey)
|
||||
offset += MemoryLayout<UInt32>.size
|
||||
|
||||
for i in 0..<dataLength {
|
||||
buffer[offset] = data[i] ^ maskKey[i % MemoryLayout<UInt32>.size]
|
||||
offset += 1
|
||||
}
|
||||
var total = 0
|
||||
while true {
|
||||
guard let outStream = s.outputStream else { break }
|
||||
let writeBuffer = UnsafeRawPointer(frame!.bytes+total).assumingMemoryBound(to: UInt8.self)
|
||||
let len = outStream.write(writeBuffer, maxLength: offset-total)
|
||||
if len < 0 {
|
||||
var error: Error?
|
||||
if let streamError = outStream.streamError {
|
||||
error = streamError
|
||||
} else {
|
||||
let errCode = InternalErrorCode.outputStreamWriteError.rawValue
|
||||
error = s.errorWithDetail("output stream error during write", code: errCode)
|
||||
}
|
||||
s.doDisconnect(error as NSError?)
|
||||
break
|
||||
} else {
|
||||
total += len
|
||||
}
|
||||
if total >= offset {
|
||||
if let queue = self?.callbackQueue, let callback = writeCompletion {
|
||||
queue.async {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to preform the disconnect delegate
|
||||
private func doDisconnect(_ error: NSError?) {
|
||||
guard !didDisconnect else { return }
|
||||
didDisconnect = true
|
||||
connected = false
|
||||
guard canDispatch else {return}
|
||||
callbackQueue.async { [weak self] in
|
||||
guard let s = self else { return }
|
||||
s.onDisconnect?(error)
|
||||
s.delegate?.websocketDidDisconnect(s, error: error)
|
||||
let userInfo = error.map{ [WebsocketDisconnectionErrorKeyName: $0] }
|
||||
s.notificationCenter.post(name: NSNotification.Name(WebsocketDidDisconnectNotification), object: self, userInfo: userInfo)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Deinit
|
||||
|
||||
deinit {
|
||||
mutex.lock()
|
||||
readyToWrite = false
|
||||
mutex.unlock()
|
||||
cleanupStream()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension Data {
|
||||
|
||||
init(buffer: UnsafeBufferPointer<UInt8>) {
|
||||
self.init(bytes: buffer.baseAddress!, count: buffer.count)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension UnsafeBufferPointer {
|
||||
|
||||
func fromOffset(_ offset: Int) -> UnsafeBufferPointer<Element> {
|
||||
return UnsafeBufferPointer<Element>(start: baseAddress?.advanced(by: offset), count: count - offset)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private let emptyBuffer = UnsafeBufferPointer<UInt8>(start: nil, count: 0)
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "Starscream"
|
||||
s.version = "0.9.6"
|
||||
s.version = "1.1.3"
|
||||
s.summary = "A conforming WebSocket RFC 6455 client library in Swift for iOS and OSX."
|
||||
s.homepage = "https://github.com/daltoniam/Starscream"
|
||||
s.license = 'Apache License, Version 2.0'
|
||||
@@ -9,6 +9,7 @@ Pod::Spec.new do |s|
|
||||
s.social_media_url = 'http://twitter.com/daltoniam'
|
||||
s.ios.deployment_target = '8.0'
|
||||
s.osx.deployment_target = '10.9'
|
||||
s.source_files = '*.swift'
|
||||
s.tvos.deployment_target = '9.0'
|
||||
s.source_files = 'Source/*.swift'
|
||||
s.requires_arc = 'true'
|
||||
end
|
||||
|
||||
@@ -7,18 +7,30 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
5C06AE8F1B08050D00D41060 /* Security.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C06AE8E1B08050D00D41060 /* Security.swift */; };
|
||||
5C06AE901B08050D00D41060 /* Security.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C06AE8E1B08050D00D41060 /* Security.swift */; };
|
||||
091277A11BD673A70003036D /* Starscream.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 091277971BD673A70003036D /* Starscream.framework */; };
|
||||
5C1360021C473BEF00AA3A01 /* SSLSecurity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C135FFF1C473BEF00AA3A01 /* SSLSecurity.swift */; };
|
||||
5C1360031C473BEF00AA3A01 /* SSLSecurity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C135FFF1C473BEF00AA3A01 /* SSLSecurity.swift */; };
|
||||
5C1360041C473BEF00AA3A01 /* SSLSecurity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C135FFF1C473BEF00AA3A01 /* SSLSecurity.swift */; };
|
||||
5C1360051C473BEF00AA3A01 /* Starscream.h in Headers */ = {isa = PBXBuildFile; fileRef = 5C1360001C473BEF00AA3A01 /* Starscream.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5C1360061C473BEF00AA3A01 /* Starscream.h in Headers */ = {isa = PBXBuildFile; fileRef = 5C1360001C473BEF00AA3A01 /* Starscream.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5C1360071C473BEF00AA3A01 /* Starscream.h in Headers */ = {isa = PBXBuildFile; fileRef = 5C1360001C473BEF00AA3A01 /* Starscream.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5C1360081C473BEF00AA3A01 /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1360011C473BEF00AA3A01 /* WebSocket.swift */; };
|
||||
5C1360091C473BEF00AA3A01 /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1360011C473BEF00AA3A01 /* WebSocket.swift */; };
|
||||
5C13600A1C473BEF00AA3A01 /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1360011C473BEF00AA3A01 /* WebSocket.swift */; };
|
||||
5CADAB511BEBD068005DE2F0 /* StarscreamTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B3E7A0119D48C2F006071F7 /* StarscreamTests.swift */; };
|
||||
6B3E7A0319D48C2F006071F7 /* StarscreamTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B3E7A0119D48C2F006071F7 /* StarscreamTests.swift */; };
|
||||
6B3E7A0519D48C41006071F7 /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B3E7A0419D48C41006071F7 /* WebSocket.swift */; };
|
||||
6B3E7A0719D48C5F006071F7 /* Starscream.h in Headers */ = {isa = PBXBuildFile; fileRef = 6B3E7A0619D48C5F006071F7 /* Starscream.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D9C3E36A19E48FF1009FC285 /* Starscream.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D9C3E35F19E48FF1009FC285 /* Starscream.framework */; };
|
||||
D9C3E37819E4903F009FC285 /* StarscreamTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B3E7A0119D48C2F006071F7 /* StarscreamTests.swift */; };
|
||||
D9C3E37919E49051009FC285 /* Starscream.h in Headers */ = {isa = PBXBuildFile; fileRef = 6B3E7A0619D48C5F006071F7 /* Starscream.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D9C3E37A19E49058009FC285 /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B3E7A0419D48C41006071F7 /* WebSocket.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
091277A21BD673A70003036D /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 6B3E79DD19D48B7F006071F7 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 091277961BD673A70003036D;
|
||||
remoteInfo = StarscreamTv;
|
||||
};
|
||||
D9C3E36B19E48FF1009FC285 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 6B3E79DD19D48B7F006071F7 /* Project object */;
|
||||
@@ -29,19 +41,37 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
5C06AE8E1B08050D00D41060 /* Security.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Security.swift; sourceTree = SOURCE_ROOT; };
|
||||
091277971BD673A70003036D /* Starscream.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Starscream.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
091277A01BD673A70003036D /* Starscream tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Starscream tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
5C135FFF1C473BEF00AA3A01 /* SSLSecurity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SSLSecurity.swift; path = Source/SSLSecurity.swift; sourceTree = SOURCE_ROOT; };
|
||||
5C1360001C473BEF00AA3A01 /* Starscream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Starscream.h; path = Source/Starscream.h; sourceTree = SOURCE_ROOT; };
|
||||
5C1360011C473BEF00AA3A01 /* WebSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = WebSocket.swift; path = Source/WebSocket.swift; sourceTree = SOURCE_ROOT; };
|
||||
5C13600B1C473BFE00AA3A01 /* Info-tvOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "Info-tvOS.plist"; path = "Source/Info-tvOS.plist"; sourceTree = SOURCE_ROOT; };
|
||||
5C13600C1C473BFE00AA3A01 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Source/Info.plist; sourceTree = SOURCE_ROOT; };
|
||||
6B3E79E619D48B7F006071F7 /* Starscream.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Starscream.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
6B3E79EA19D48B7F006071F7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
6B3E79F119D48B7F006071F7 /* StarscreamTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StarscreamTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
6B3E79F119D48B7F006071F7 /* Starscream iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Starscream iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
6B3E7A0019D48C2F006071F7 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
6B3E7A0119D48C2F006071F7 /* StarscreamTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarscreamTests.swift; sourceTree = "<group>"; };
|
||||
6B3E7A0419D48C41006071F7 /* WebSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebSocket.swift; sourceTree = SOURCE_ROOT; };
|
||||
6B3E7A0619D48C5F006071F7 /* Starscream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Starscream.h; sourceTree = SOURCE_ROOT; };
|
||||
D9C3E35F19E48FF1009FC285 /* Starscream.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Starscream.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D9C3E36919E48FF1009FC285 /* StarscreamOSXTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StarscreamOSXTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D9C3E36919E48FF1009FC285 /* Starscream OSXTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Starscream OSXTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
091277931BD673A70003036D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
0912779D1BD673A70003036D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
091277A11BD673A70003036D /* Starscream.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
6B3E79E219D48B7F006071F7 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -87,9 +117,11 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6B3E79E619D48B7F006071F7 /* Starscream.framework */,
|
||||
6B3E79F119D48B7F006071F7 /* StarscreamTests.xctest */,
|
||||
6B3E79F119D48B7F006071F7 /* Starscream iOSTests.xctest */,
|
||||
D9C3E35F19E48FF1009FC285 /* Starscream.framework */,
|
||||
D9C3E36919E48FF1009FC285 /* StarscreamOSXTests.xctest */,
|
||||
D9C3E36919E48FF1009FC285 /* Starscream OSXTests.xctest */,
|
||||
091277971BD673A70003036D /* Starscream.framework */,
|
||||
091277A01BD673A70003036D /* Starscream tvOSTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -97,9 +129,9 @@
|
||||
6B3E79E819D48B7F006071F7 /* Starscream */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6B3E7A0419D48C41006071F7 /* WebSocket.swift */,
|
||||
5C06AE8E1B08050D00D41060 /* Security.swift */,
|
||||
6B3E7A0619D48C5F006071F7 /* Starscream.h */,
|
||||
5C1360001C473BEF00AA3A01 /* Starscream.h */,
|
||||
5C135FFF1C473BEF00AA3A01 /* SSLSecurity.swift */,
|
||||
5C1360011C473BEF00AA3A01 /* WebSocket.swift */,
|
||||
6B3E79E919D48B7F006071F7 /* Supporting Files */,
|
||||
);
|
||||
path = Starscream;
|
||||
@@ -108,7 +140,8 @@
|
||||
6B3E79E919D48B7F006071F7 /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6B3E79EA19D48B7F006071F7 /* Info.plist */,
|
||||
5C13600B1C473BFE00AA3A01 /* Info-tvOS.plist */,
|
||||
5C13600C1C473BFE00AA3A01 /* Info.plist */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
@@ -125,11 +158,19 @@
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
091277941BD673A70003036D /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5C1360071C473BEF00AA3A01 /* Starscream.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
6B3E79E319D48B7F006071F7 /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6B3E7A0719D48C5F006071F7 /* Starscream.h in Headers */,
|
||||
5C1360051C473BEF00AA3A01 /* Starscream.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -137,16 +178,52 @@
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D9C3E37919E49051009FC285 /* Starscream.h in Headers */,
|
||||
5C1360061C473BEF00AA3A01 /* Starscream.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
6B3E79E519D48B7F006071F7 /* Starscream */ = {
|
||||
091277961BD673A70003036D /* Starscream tvOS */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 6B3E79F919D48B7F006071F7 /* Build configuration list for PBXNativeTarget "Starscream" */;
|
||||
buildConfigurationList = 091277A81BD673A70003036D /* Build configuration list for PBXNativeTarget "Starscream tvOS" */;
|
||||
buildPhases = (
|
||||
091277921BD673A70003036D /* Sources */,
|
||||
091277931BD673A70003036D /* Frameworks */,
|
||||
091277941BD673A70003036D /* Headers */,
|
||||
091277951BD673A70003036D /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "Starscream tvOS";
|
||||
productName = StarscreamTv;
|
||||
productReference = 091277971BD673A70003036D /* Starscream.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
0912779F1BD673A70003036D /* Starscream tvOSTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 091277AB1BD673A70003036D /* Build configuration list for PBXNativeTarget "Starscream tvOSTests" */;
|
||||
buildPhases = (
|
||||
0912779C1BD673A70003036D /* Sources */,
|
||||
0912779D1BD673A70003036D /* Frameworks */,
|
||||
0912779E1BD673A70003036D /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
091277A31BD673A70003036D /* PBXTargetDependency */,
|
||||
);
|
||||
name = "Starscream tvOSTests";
|
||||
productName = StarscreamTvTests;
|
||||
productReference = 091277A01BD673A70003036D /* Starscream tvOSTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
6B3E79E519D48B7F006071F7 /* Starscream iOS */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 6B3E79F919D48B7F006071F7 /* Build configuration list for PBXNativeTarget "Starscream iOS" */;
|
||||
buildPhases = (
|
||||
6B3E79E119D48B7F006071F7 /* Sources */,
|
||||
6B3E79E219D48B7F006071F7 /* Frameworks */,
|
||||
@@ -157,14 +234,14 @@
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Starscream;
|
||||
name = "Starscream iOS";
|
||||
productName = Starscream;
|
||||
productReference = 6B3E79E619D48B7F006071F7 /* Starscream.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
6B3E79F019D48B7F006071F7 /* StarscreamTests */ = {
|
||||
6B3E79F019D48B7F006071F7 /* Starscream iOSTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 6B3E79FC19D48B7F006071F7 /* Build configuration list for PBXNativeTarget "StarscreamTests" */;
|
||||
buildConfigurationList = 6B3E79FC19D48B7F006071F7 /* Build configuration list for PBXNativeTarget "Starscream iOSTests" */;
|
||||
buildPhases = (
|
||||
6B3E79ED19D48B7F006071F7 /* Sources */,
|
||||
6B3E79EE19D48B7F006071F7 /* Frameworks */,
|
||||
@@ -174,14 +251,14 @@
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = StarscreamTests;
|
||||
name = "Starscream iOSTests";
|
||||
productName = StarscreamTests;
|
||||
productReference = 6B3E79F119D48B7F006071F7 /* StarscreamTests.xctest */;
|
||||
productReference = 6B3E79F119D48B7F006071F7 /* Starscream iOSTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
D9C3E35E19E48FF1009FC285 /* StarscreamOSX */ = {
|
||||
D9C3E35E19E48FF1009FC285 /* Starscream OSX */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = D9C3E37619E48FF1009FC285 /* Build configuration list for PBXNativeTarget "StarscreamOSX" */;
|
||||
buildConfigurationList = D9C3E37619E48FF1009FC285 /* Build configuration list for PBXNativeTarget "Starscream OSX" */;
|
||||
buildPhases = (
|
||||
D9C3E35A19E48FF1009FC285 /* Sources */,
|
||||
D9C3E35B19E48FF1009FC285 /* Frameworks */,
|
||||
@@ -192,14 +269,14 @@
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = StarscreamOSX;
|
||||
name = "Starscream OSX";
|
||||
productName = StarscreamOSX;
|
||||
productReference = D9C3E35F19E48FF1009FC285 /* Starscream.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
D9C3E36819E48FF1009FC285 /* StarscreamOSXTests */ = {
|
||||
D9C3E36819E48FF1009FC285 /* Starscream OSXTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = D9C3E37719E48FF1009FC285 /* Build configuration list for PBXNativeTarget "StarscreamOSXTests" */;
|
||||
buildConfigurationList = D9C3E37719E48FF1009FC285 /* Build configuration list for PBXNativeTarget "Starscream OSXTests" */;
|
||||
buildPhases = (
|
||||
D9C3E36519E48FF1009FC285 /* Sources */,
|
||||
D9C3E36619E48FF1009FC285 /* Frameworks */,
|
||||
@@ -210,9 +287,9 @@
|
||||
dependencies = (
|
||||
D9C3E36C19E48FF1009FC285 /* PBXTargetDependency */,
|
||||
);
|
||||
name = StarscreamOSXTests;
|
||||
name = "Starscream OSXTests";
|
||||
productName = StarscreamOSXTests;
|
||||
productReference = D9C3E36919E48FF1009FC285 /* StarscreamOSXTests.xctest */;
|
||||
productReference = D9C3E36919E48FF1009FC285 /* Starscream OSXTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
@@ -221,12 +298,20 @@
|
||||
6B3E79DD19D48B7F006071F7 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftMigration = 0700;
|
||||
LastSwiftUpdateCheck = 0700;
|
||||
LastUpgradeCheck = 0600;
|
||||
LastUpgradeCheck = 0800;
|
||||
ORGANIZATIONNAME = Vluxe;
|
||||
TargetAttributes = {
|
||||
091277961BD673A70003036D = {
|
||||
CreatedOnToolsVersion = 7.1;
|
||||
};
|
||||
0912779F1BD673A70003036D = {
|
||||
CreatedOnToolsVersion = 7.1;
|
||||
};
|
||||
6B3E79E519D48B7F006071F7 = {
|
||||
CreatedOnToolsVersion = 6.0.1;
|
||||
LastSwiftMigration = 0800;
|
||||
};
|
||||
6B3E79F019D48B7F006071F7 = {
|
||||
CreatedOnToolsVersion = 6.0.1;
|
||||
@@ -251,15 +336,31 @@
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
6B3E79E519D48B7F006071F7 /* Starscream */,
|
||||
6B3E79F019D48B7F006071F7 /* StarscreamTests */,
|
||||
D9C3E35E19E48FF1009FC285 /* StarscreamOSX */,
|
||||
D9C3E36819E48FF1009FC285 /* StarscreamOSXTests */,
|
||||
6B3E79E519D48B7F006071F7 /* Starscream iOS */,
|
||||
6B3E79F019D48B7F006071F7 /* Starscream iOSTests */,
|
||||
D9C3E35E19E48FF1009FC285 /* Starscream OSX */,
|
||||
D9C3E36819E48FF1009FC285 /* Starscream OSXTests */,
|
||||
091277961BD673A70003036D /* Starscream tvOS */,
|
||||
0912779F1BD673A70003036D /* Starscream tvOSTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
091277951BD673A70003036D /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
0912779E1BD673A70003036D /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
6B3E79E419D48B7F006071F7 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -291,12 +392,29 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
091277921BD673A70003036D /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5C13600A1C473BEF00AA3A01 /* WebSocket.swift in Sources */,
|
||||
5C1360041C473BEF00AA3A01 /* SSLSecurity.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
0912779C1BD673A70003036D /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5CADAB511BEBD068005DE2F0 /* StarscreamTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
6B3E79E119D48B7F006071F7 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6B3E7A0519D48C41006071F7 /* WebSocket.swift in Sources */,
|
||||
5C06AE8F1B08050D00D41060 /* Security.swift in Sources */,
|
||||
5C1360081C473BEF00AA3A01 /* WebSocket.swift in Sources */,
|
||||
5C1360021C473BEF00AA3A01 /* SSLSecurity.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -312,8 +430,8 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D9C3E37A19E49058009FC285 /* WebSocket.swift in Sources */,
|
||||
5C06AE901B08050D00D41060 /* Security.swift in Sources */,
|
||||
5C1360091C473BEF00AA3A01 /* WebSocket.swift in Sources */,
|
||||
5C1360031C473BEF00AA3A01 /* SSLSecurity.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -328,18 +446,110 @@
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
091277A31BD673A70003036D /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 091277961BD673A70003036D /* Starscream tvOS */;
|
||||
targetProxy = 091277A21BD673A70003036D /* PBXContainerItemProxy */;
|
||||
};
|
||||
D9C3E36C19E48FF1009FC285 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = D9C3E35E19E48FF1009FC285 /* StarscreamOSX */;
|
||||
target = D9C3E35E19E48FF1009FC285 /* Starscream OSX */;
|
||||
targetProxy = D9C3E36B19E48FF1009FC285 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
091277A91BD673A70003036D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
BITCODE_GENERATION_MODE = marker;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
INFOPLIST_FILE = "$(SRCROOT)/Source/Info-tvOS.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.vluxe.Starscream;
|
||||
PRODUCT_NAME = Starscream;
|
||||
SDKROOT = appletvos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 3.0;
|
||||
TARGETED_DEVICE_FAMILY = 3;
|
||||
TVOS_DEPLOYMENT_TARGET = 9.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
091277AA1BD673A70003036D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
BITCODE_GENERATION_MODE = bitcode;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
INFOPLIST_FILE = "$(SRCROOT)/Source/Info-tvOS.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.vluxe.Starscream;
|
||||
PRODUCT_NAME = Starscream;
|
||||
SDKROOT = appletvos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 3.0;
|
||||
TARGETED_DEVICE_FAMILY = 3;
|
||||
TVOS_DEPLOYMENT_TARGET = 9.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
091277AC1BD673A70003036D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
INFOPLIST_FILE = Tests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.vluxe.StarscreamTvTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = appletvos;
|
||||
SWIFT_VERSION = 3.0;
|
||||
TVOS_DEPLOYMENT_TARGET = 9.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
091277AD1BD673A70003036D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = NO;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
INFOPLIST_FILE = Tests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.vluxe.StarscreamTvTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = appletvos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 3.0;
|
||||
TVOS_DEPLOYMENT_TARGET = 9.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
6B3E79F719D48B7F006071F7 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
@@ -357,8 +567,10 @@
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
@@ -386,6 +598,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
@@ -405,6 +618,7 @@
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
@@ -424,6 +638,7 @@
|
||||
6B3E79FA19D48B7F006071F7 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BITCODE_GENERATION_MODE = marker;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
@@ -431,21 +646,24 @@
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = Info.plist;
|
||||
INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
OTHER_LDFLAGS = "";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.vluxe.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = Starscream;
|
||||
PROVISIONING_PROFILE = "";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 3.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
6B3E79FB19D48B7F006071F7 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BITCODE_GENERATION_MODE = bitcode;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
@@ -453,20 +671,24 @@
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = Info.plist;
|
||||
INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
OTHER_LDFLAGS = "";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.vluxe.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = Starscream;
|
||||
PROVISIONING_PROFILE = "";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 3.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
6B3E79FD19D48B7F006071F7 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/Developer/Library/Frameworks",
|
||||
"$(inherited)",
|
||||
@@ -477,20 +699,26 @@
|
||||
);
|
||||
INFOPLIST_FILE = Tests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.vluxe.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 3.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
6B3E79FE19D48B7F006071F7 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/Developer/Library/Frameworks",
|
||||
"$(inherited)",
|
||||
);
|
||||
INFOPLIST_FILE = Tests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.vluxe.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 3.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -507,13 +735,16 @@
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
INFOPLIST_FILE = Info.plist;
|
||||
INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.vluxe.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = Starscream;
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 3.0;
|
||||
VALID_ARCHS = x86_64;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -527,19 +758,24 @@
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
FRAMEWORK_VERSION = A;
|
||||
INFOPLIST_FILE = Info.plist;
|
||||
INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.vluxe.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = Starscream;
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 3.0;
|
||||
VALID_ARCHS = x86_64;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
D9C3E37419E48FF1009FC285 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = NO;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(DEVELOPER_FRAMEWORKS_DIR)",
|
||||
@@ -552,14 +788,17 @@
|
||||
INFOPLIST_FILE = Tests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.vluxe.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
SWIFT_VERSION = 3.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
D9C3E37519E48FF1009FC285 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = NO;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@@ -569,14 +808,35 @@
|
||||
INFOPLIST_FILE = Tests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.vluxe.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 3.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
091277A81BD673A70003036D /* Build configuration list for PBXNativeTarget "Starscream tvOS" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
091277A91BD673A70003036D /* Debug */,
|
||||
091277AA1BD673A70003036D /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
091277AB1BD673A70003036D /* Build configuration list for PBXNativeTarget "Starscream tvOSTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
091277AC1BD673A70003036D /* Debug */,
|
||||
091277AD1BD673A70003036D /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
6B3E79E019D48B7F006071F7 /* Build configuration list for PBXProject "Starscream" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
@@ -586,7 +846,7 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
6B3E79F919D48B7F006071F7 /* Build configuration list for PBXNativeTarget "Starscream" */ = {
|
||||
6B3E79F919D48B7F006071F7 /* Build configuration list for PBXNativeTarget "Starscream iOS" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
6B3E79FA19D48B7F006071F7 /* Debug */,
|
||||
@@ -595,7 +855,7 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
6B3E79FC19D48B7F006071F7 /* Build configuration list for PBXNativeTarget "StarscreamTests" */ = {
|
||||
6B3E79FC19D48B7F006071F7 /* Build configuration list for PBXNativeTarget "Starscream iOSTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
6B3E79FD19D48B7F006071F7 /* Debug */,
|
||||
@@ -604,7 +864,7 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
D9C3E37619E48FF1009FC285 /* Build configuration list for PBXNativeTarget "StarscreamOSX" */ = {
|
||||
D9C3E37619E48FF1009FC285 /* Build configuration list for PBXNativeTarget "Starscream OSX" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
D9C3E37219E48FF1009FC285 /* Debug */,
|
||||
@@ -613,7 +873,7 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
D9C3E37719E48FF1009FC285 /* Build configuration list for PBXNativeTarget "StarscreamOSXTests" */ = {
|
||||
D9C3E37719E48FF1009FC285 /* Build configuration list for PBXNativeTarget "Starscream OSXTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
D9C3E37419E48FF1009FC285 /* Debug */,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0630"
|
||||
LastUpgradeVersion = "0800"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -16,35 +16,38 @@
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6B3E79E519D48B7F006071F7"
|
||||
BuildableName = "Starscream.framework"
|
||||
BlueprintName = "Starscream"
|
||||
BlueprintName = "Starscream iOS"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6B3E79E519D48B7F006071F7"
|
||||
BuildableName = "Starscream.framework"
|
||||
BlueprintName = "Starscream"
|
||||
BlueprintName = "Starscream iOS"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
@@ -52,17 +55,17 @@
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6B3E79E519D48B7F006071F7"
|
||||
BuildableName = "Starscream.framework"
|
||||
BlueprintName = "Starscream"
|
||||
BlueprintName = "Starscream iOS"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0630"
|
||||
LastUpgradeVersion = "0800"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -16,7 +16,7 @@
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D9C3E35E19E48FF1009FC285"
|
||||
BuildableName = "Starscream.framework"
|
||||
BlueprintName = "StarscreamOSX"
|
||||
BlueprintName = "Starscream OSX"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
@@ -29,26 +29,26 @@
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D9C3E36819E48FF1009FC285"
|
||||
BuildableName = "StarscreamOSXTests.xctest"
|
||||
BlueprintName = "StarscreamOSXTests"
|
||||
BuildableName = "Starscream OSXTests.xctest"
|
||||
BlueprintName = "Starscream OSXTests"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D9C3E36819E48FF1009FC285"
|
||||
BuildableName = "StarscreamOSXTests.xctest"
|
||||
BlueprintName = "StarscreamOSXTests"
|
||||
BuildableName = "Starscream OSXTests.xctest"
|
||||
BlueprintName = "Starscream OSXTests"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
@@ -58,26 +58,29 @@
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D9C3E35E19E48FF1009FC285"
|
||||
BuildableName = "Starscream.framework"
|
||||
BlueprintName = "StarscreamOSX"
|
||||
BlueprintName = "Starscream OSX"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D9C3E35E19E48FF1009FC285"
|
||||
BuildableName = "Starscream.framework"
|
||||
BlueprintName = "StarscreamOSX"
|
||||
BlueprintName = "Starscream OSX"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
@@ -85,17 +88,17 @@
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D9C3E35E19E48FF1009FC285"
|
||||
BuildableName = "Starscream.framework"
|
||||
BlueprintName = "StarscreamOSX"
|
||||
BlueprintName = "Starscream OSX"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0630"
|
||||
LastUpgradeVersion = "0800"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -15,26 +15,26 @@
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6B3E79F019D48B7F006071F7"
|
||||
BuildableName = "StarscreamTests.xctest"
|
||||
BlueprintName = "StarscreamTests"
|
||||
BuildableName = "Starscream iOSTests.xctest"
|
||||
BlueprintName = "Starscream iOSTests"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6B3E79F019D48B7F006071F7"
|
||||
BuildableName = "StarscreamTests.xctest"
|
||||
BlueprintName = "StarscreamTests"
|
||||
BuildableName = "Starscream iOSTests.xctest"
|
||||
BlueprintName = "Starscream iOSTests"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
@@ -43,27 +43,30 @@
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6B3E79F019D48B7F006071F7"
|
||||
BuildableName = "StarscreamTests.xctest"
|
||||
BlueprintName = "StarscreamTests"
|
||||
BuildableName = "Starscream iOSTests.xctest"
|
||||
BlueprintName = "Starscream iOSTests"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6B3E79F019D48B7F006071F7"
|
||||
BuildableName = "StarscreamTests.xctest"
|
||||
BlueprintName = "StarscreamTests"
|
||||
BuildableName = "Starscream iOSTests.xctest"
|
||||
BlueprintName = "Starscream iOSTests"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
@@ -71,17 +74,17 @@
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6B3E79F019D48B7F006071F7"
|
||||
BuildableName = "StarscreamTests.xctest"
|
||||
BlueprintName = "StarscreamTests"
|
||||
BuildableName = "Starscream iOSTests.xctest"
|
||||
BlueprintName = "Starscream iOSTests"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0800"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "091277961BD673A70003036D"
|
||||
BuildableName = "Starscream.framework"
|
||||
BlueprintName = "Starscream tvOS"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "NO"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0912779F1BD673A70003036D"
|
||||
BuildableName = "Starscream tvOSTests.xctest"
|
||||
BlueprintName = "Starscream tvOSTests"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0912779F1BD673A70003036D"
|
||||
BuildableName = "Starscream tvOSTests.xctest"
|
||||
BlueprintName = "Starscream tvOSTests"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "091277961BD673A70003036D"
|
||||
BuildableName = "Starscream.framework"
|
||||
BlueprintName = "Starscream tvOS"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "091277961BD673A70003036D"
|
||||
BuildableName = "Starscream.framework"
|
||||
BlueprintName = "Starscream tvOS"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "091277961BD673A70003036D"
|
||||
BuildableName = "Starscream.framework"
|
||||
BlueprintName = "Starscream tvOS"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.vluxe.$(PRODUCT_NAME:rfc1034identifier)</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
|
||||
@@ -27,7 +27,7 @@ class StarscreamTests: XCTestCase {
|
||||
|
||||
func testPerformanceExample() {
|
||||
// This is an example of a performance test case.
|
||||
self.measureBlock() {
|
||||
self.measure() {
|
||||
// Put the code you want to measure the time of here.
|
||||
}
|
||||
}
|
||||
|
||||
-726
@@ -1,726 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Websocket.swift
|
||||
//
|
||||
// Created by Dalton Cherry on 7/16/14.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import Foundation
|
||||
import CoreFoundation
|
||||
|
||||
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 protocol WebSocketPongDelegate: class {
|
||||
func websocketDidReceivePong(socket: WebSocket)
|
||||
}
|
||||
|
||||
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<String>?
|
||||
//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 = 4096
|
||||
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?
|
||||
public weak var pongDelegate: WebSocketPongDelegate?
|
||||
public var onConnect: ((Void) -> Void)?
|
||||
public var onDisconnect: ((NSError?) -> Void)?
|
||||
public var onText: ((String) -> Void)?
|
||||
public var onData: ((NSData) -> Void)?
|
||||
public var onPong: ((Void) -> Void)?
|
||||
public var headers = Dictionary<String,String>()
|
||||
public var voipEnabled = false
|
||||
public var selfSignedSSL = false
|
||||
public var security: Security?
|
||||
public var isConnected :Bool {
|
||||
return connected
|
||||
}
|
||||
private var url: NSURL
|
||||
private var inputStream: NSInputStream?
|
||||
private var outputStream: NSOutputStream?
|
||||
private var isRunLoop = false
|
||||
private var connected = false
|
||||
private var isCreated = false
|
||||
private var writeQueue: NSOperationQueue?
|
||||
private var readStack = Array<WSResponse>()
|
||||
private var inputQueue = Array<NSData>()
|
||||
private var fragBuffer: NSData?
|
||||
private var certValidated = false
|
||||
private var didDisconnect = false
|
||||
|
||||
//init the websocket with a url
|
||||
public init(url: NSURL) {
|
||||
self.url = url
|
||||
}
|
||||
//used for setting protocols.
|
||||
public convenience init(url: NSURL, protocols: Array<String>) {
|
||||
self.init(url: url)
|
||||
optionalProtocols = protocols
|
||||
}
|
||||
|
||||
///Connect to the websocket server on a background thread
|
||||
public func connect() {
|
||||
if isCreated {
|
||||
return
|
||||
}
|
||||
dispatch_async(queue,{ [weak self] in
|
||||
self?.didDisconnect = false
|
||||
})
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), { [weak self] in
|
||||
self?.isCreated = true
|
||||
self?.createHTTPRequest()
|
||||
self?.isCreated = false
|
||||
})
|
||||
}
|
||||
|
||||
///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).takeRetainedValue()
|
||||
|
||||
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).takeRetainedValue()
|
||||
self.initStreamsWithData(serializedRequest, Int(port!))
|
||||
}
|
||||
//Add a header to the CFHTTPMessage by using the NSString bridges to CFString
|
||||
private func addHeader(urlRequest: CFHTTPMessage,key: String, val: String) {
|
||||
let nsKey: NSString = key
|
||||
let nsVal: NSString = val
|
||||
CFHTTPMessageSetHeaderFieldValue(urlRequest,
|
||||
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<CFReadStream>?
|
||||
var writeStream: Unmanaged<CFWriteStream>?
|
||||
let h: NSString = url.host!
|
||||
CFStreamCreatePairWithSocketToHost(nil, h, UInt32(port), &readStream, &writeStream)
|
||||
inputStream = readStream!.takeRetainedValue()
|
||||
outputStream = writeStream!.takeRetainedValue()
|
||||
|
||||
inputStream!.delegate = self
|
||||
outputStream!.delegate = self
|
||||
if url.scheme == "wss" || url.scheme == "https" {
|
||||
inputStream!.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey)
|
||||
outputStream!.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey)
|
||||
} else {
|
||||
certValidated = true //not a https session, so no need to check SSL pinning
|
||||
}
|
||||
if self.voipEnabled {
|
||||
inputStream!.setProperty(NSStreamNetworkServiceTypeVoIP, forKey: NSStreamNetworkServiceType)
|
||||
outputStream!.setProperty(NSStreamNetworkServiceTypeVoIP, forKey: NSStreamNetworkServiceType)
|
||||
}
|
||||
if self.selfSignedSSL {
|
||||
let settings: Dictionary<NSObject, NSObject> = [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<UInt8>(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 let sec = security where !certValidated && (eventCode == .HasBytesAvailable || eventCode == .HasSpaceAvailable) {
|
||||
var possibleTrust: AnyObject? = aStream.propertyForKey(kCFStreamPropertySSLPeerTrust as! String)
|
||||
if let trust: AnyObject = possibleTrust {
|
||||
var domain: AnyObject? = aStream.propertyForKey(kCFStreamSSLPeerName as! String)
|
||||
if sec.isValid(trust as! SecTrustRef, domain: domain as! String?) {
|
||||
certValidated = true
|
||||
} else {
|
||||
let error = self.errorWithDetail("Invalid SSL certificate", code: 1)
|
||||
doDisconnect(error)
|
||||
disconnectStream(error)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
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()
|
||||
}
|
||||
if let stream = inputStream {
|
||||
stream.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
|
||||
stream.close()
|
||||
}
|
||||
if let stream = outputStream {
|
||||
stream.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
|
||||
stream.close()
|
||||
}
|
||||
outputStream = nil
|
||||
isRunLoop = false
|
||||
certValidated = false
|
||||
self.doDisconnect(error)
|
||||
connected = false
|
||||
}
|
||||
|
||||
///handles the incoming bytes and sending them to the proper processing method
|
||||
private func processInputStream() {
|
||||
let buf = NSMutableData(capacity: BUFFER_MAX)
|
||||
var buffer = UnsafeMutablePointer<UInt8>(buf!.bytes)
|
||||
let length = inputStream!.read(buffer, maxLength: BUFFER_MAX)
|
||||
if length > 0 {
|
||||
if !connected {
|
||||
let status = processHTTP(buffer, bufferLen: length)
|
||||
if !status {
|
||||
doDisconnect(self.errorWithDetail("Invalid HTTP upgrade", code: 1))
|
||||
}
|
||||
} 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<UInt8>(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<UInt8>, 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,{ [weak self] in
|
||||
self?.connected = true
|
||||
if let connectBlock = self?.onConnect {
|
||||
connectBlock()
|
||||
}
|
||||
if let s = self {
|
||||
s.delegate?.websocketDidConnect(s)
|
||||
}
|
||||
})
|
||||
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<UInt8>, bufferLen: Int) -> Bool {
|
||||
let response = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, 0).takeRetainedValue()
|
||||
CFHTTPMessageAppendBytes(response, buffer, bufferLen)
|
||||
if CFHTTPMessageGetResponseStatusCode(response) != 101 {
|
||||
return false
|
||||
}
|
||||
let cfHeaders = CFHTTPMessageCopyAllHeaderFields(response)
|
||||
let headers: NSDictionary = cfHeaders.takeRetainedValue()
|
||||
let acceptKey = headers[headerWSAcceptName] as! NSString
|
||||
if acceptKey.length > 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
///process the websocket data
|
||||
private func processRawMessage(buffer: UnsafePointer<UInt8>, 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)
|
||||
self.doDisconnect(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)
|
||||
self.doDisconnect(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)
|
||||
self.doDisconnect(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<UInt16>((buffer+offset))
|
||||
code = codeBuffer[0].bigEndian
|
||||
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<UInt8>((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)
|
||||
self.doDisconnect(error)
|
||||
writeError(code)
|
||||
return
|
||||
}
|
||||
if isControlFrame && payloadLen > 125 {
|
||||
writeError(CloseCode.ProtocolError.rawValue)
|
||||
return
|
||||
}
|
||||
var dataLength = UInt64(payloadLen)
|
||||
if dataLength == 127 {
|
||||
let bytes = UnsafePointer<UInt64>((buffer+offset))
|
||||
dataLength = bytes[0].bigEndian
|
||||
offset += sizeof(UInt64)
|
||||
} else if dataLength == 126 {
|
||||
let bytes = UnsafePointer<UInt16>((buffer+offset))
|
||||
dataLength = UInt64(bytes[0].bigEndian)
|
||||
offset += sizeof(UInt16)
|
||||
}
|
||||
if bufferLen < offset || UInt64(bufferLen - offset) < dataLength {
|
||||
fragBuffer = NSData(bytes: buffer, length: bufferLen)
|
||||
return
|
||||
}
|
||||
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<UInt8>((buffer+offset)), length: Int(len))
|
||||
}
|
||||
if receivedOpcode == OpCode.Pong.rawValue {
|
||||
dispatch_async(queue,{ [weak self] in
|
||||
if let pongBlock = self?.onPong {
|
||||
pongBlock()
|
||||
}
|
||||
if let s = self {
|
||||
s.pongDelegate?.websocketDidReceivePong(s)
|
||||
}
|
||||
})
|
||||
|
||||
let step = Int(offset+numericCast(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)
|
||||
self.doDisconnect(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)
|
||||
self.doDisconnect(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)
|
||||
self.doDisconnect(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 = Int(offset+numericCast(len))
|
||||
let extra = bufferLen-step
|
||||
if(extra > 0) {
|
||||
processExtra((buffer+step), bufferLen: extra)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
///process the extra of a buffer
|
||||
private func processExtra(buffer: UnsafePointer<UInt8>, 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,{ [weak self] in
|
||||
if let textBlock = self?.onText {
|
||||
textBlock(str! as! String)
|
||||
}
|
||||
if let s = self {
|
||||
s.delegate?.websocketDidReceiveMessage(s, text: str! as! String)
|
||||
}
|
||||
})
|
||||
} else if response.code == .BinaryFrame {
|
||||
let data = response.buffer! //local copy so it is perverse for writing
|
||||
dispatch_async(queue,{ [weak self] in
|
||||
if let dataBlock = self?.onData {
|
||||
dataBlock(data)
|
||||
}
|
||||
if let s = self {
|
||||
s.delegate?.websocketDidReceiveData(s, data: data)
|
||||
}
|
||||
})
|
||||
}
|
||||
readStack.removeLast()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
///Create an error
|
||||
private func errorWithDetail(detail: String, code: UInt16) -> NSError {
|
||||
var details = Dictionary<String,String>()
|
||||
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<UInt16>(buf!.bytes)
|
||||
buffer[0] = code.bigEndian
|
||||
dequeueWrite(NSData(bytes: buffer, length: sizeof(UInt16)), code: .ConnectionClose)
|
||||
}
|
||||
///used to write things to the stream
|
||||
private func dequeueWrite(data: NSData, code: OpCode) {
|
||||
if writeQueue == nil {
|
||||
writeQueue = NSOperationQueue()
|
||||
writeQueue!.maxConcurrentOperationCount = 1
|
||||
}
|
||||
writeQueue!.addOperationWithBlock { [unowned self] in
|
||||
//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
|
||||
let bytes = UnsafeMutablePointer<UInt8>(data.bytes)
|
||||
let dataLength = data.length
|
||||
let frame = NSMutableData(capacity: dataLength + self.MaxFrameSize)
|
||||
let buffer = UnsafeMutablePointer<UInt8>(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<UInt16>((buffer+offset))
|
||||
sizeBuffer[0] = UInt16(dataLength).bigEndian
|
||||
offset += sizeof(UInt16)
|
||||
} else {
|
||||
buffer[1] = 127
|
||||
var sizeBuffer = UnsafeMutablePointer<UInt64>((buffer+offset))
|
||||
sizeBuffer[0] = UInt64(dataLength).bigEndian
|
||||
offset += sizeof(UInt64)
|
||||
}
|
||||
buffer[1] |= self.MaskMask
|
||||
var maskKey = UnsafeMutablePointer<UInt8>(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<UInt8>(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)
|
||||
}
|
||||
self.doDisconnect(error)
|
||||
break
|
||||
} else {
|
||||
total += len!
|
||||
}
|
||||
if total >= offset {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
///used to preform the disconnect delegate
|
||||
private func doDisconnect(error: NSError?) {
|
||||
if !self.didDisconnect {
|
||||
dispatch_async(queue,{ [weak self] in
|
||||
self?.didDisconnect = true
|
||||
if let disconnect = self?.onDisconnect {
|
||||
disconnect(error)
|
||||
}
|
||||
if let s = self {
|
||||
s.delegate?.websocketDidDisconnect(s, error: error)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -207,6 +207,8 @@
|
||||
5C178E141B62D0B900A97204 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftMigration = 0700;
|
||||
LastSwiftUpdateCheck = 0700;
|
||||
LastUpgradeCheck = 0640;
|
||||
ORGANIZATIONNAME = vluxe;
|
||||
TargetAttributes = {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6211" systemVersion="14A298i" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9531" systemVersion="15C50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6204"/>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="Autobahn" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||
|
||||
@@ -11,9 +11,9 @@ import Starscream
|
||||
|
||||
class ViewController: UIViewController {
|
||||
|
||||
static let host = "localhost:9001"
|
||||
static let scheme = "ws"
|
||||
var socket = WebSocket(url: NSURL(scheme: scheme, host: host, path: "/getCaseCount")!, protocols: [])
|
||||
let host = "localhost:9001"
|
||||
let scheme = "ws"
|
||||
var socketArray = [WebSocket]()
|
||||
var caseCount = 300 //starting cases
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
@@ -21,82 +21,101 @@ class ViewController: UIViewController {
|
||||
//getTestInfo(1)
|
||||
}
|
||||
|
||||
func removeSocket(s: WebSocket) {
|
||||
self.socketArray = self.socketArray.filter{$0 != s}
|
||||
}
|
||||
|
||||
func getCaseCount() {
|
||||
socket.onText = {(text: String) in
|
||||
if let c = text.toInt() {
|
||||
println("number of cases is: \(c)")
|
||||
let s = WebSocket(url: NSURL(scheme: scheme, host: host, path: "/getCaseCount")!, protocols: [])
|
||||
socketArray.append(s)
|
||||
s.onText = {[unowned self] (text: String) in
|
||||
if let c = Int(text) {
|
||||
print("number of cases is: \(c)")
|
||||
self.caseCount = c
|
||||
}
|
||||
}
|
||||
socket.onDisconnect = {(error: NSError?) in
|
||||
s.onDisconnect = {[unowned self] (error: NSError?) in
|
||||
self.getTestInfo(1)
|
||||
self.removeSocket(s)
|
||||
}
|
||||
socket.connect()
|
||||
s.connect()
|
||||
}
|
||||
|
||||
func getTestInfo(caseNum: Int) {
|
||||
socket = createSocket("getCaseInfo",caseNum)
|
||||
socket.onText = {(text: String) in
|
||||
var error: NSError?
|
||||
let data = text.dataUsingEncoding(NSUTF8StringEncoding)
|
||||
var resp: AnyObject? = NSJSONSerialization.JSONObjectWithData(data!,
|
||||
options: NSJSONReadingOptions(), error: &error)
|
||||
if let dict = resp as? Dictionary<String,String> {
|
||||
let num = dict["id"]
|
||||
let summary = dict["description"]
|
||||
if let n = num, let sum = summary {
|
||||
//println("running case:\(caseNum) id:\(n) summary: \(sum)")
|
||||
}
|
||||
}
|
||||
let s = createSocket("getCaseInfo",caseNum)
|
||||
socketArray.append(s)
|
||||
s.onText = {(text: String) in
|
||||
// let data = text.dataUsingEncoding(NSUTF8StringEncoding)
|
||||
// do {
|
||||
// let resp: AnyObject? = try NSJSONSerialization.JSONObjectWithData(data!,
|
||||
// options: NSJSONReadingOptions())
|
||||
// if let dict = resp as? Dictionary<String,String> {
|
||||
// let num = dict["id"]
|
||||
// let summary = dict["description"]
|
||||
// if let n = num, let sum = summary {
|
||||
// print("running case:\(caseNum) id:\(n) summary: \(sum)")
|
||||
// }
|
||||
// }
|
||||
// } catch {
|
||||
// print("error parsing the json")
|
||||
// }
|
||||
|
||||
}
|
||||
var once = false
|
||||
socket.onDisconnect = {(error: NSError?) in
|
||||
s.onDisconnect = {[unowned self] (error: NSError?) in
|
||||
if !once {
|
||||
once = true
|
||||
self.runTest(caseNum)
|
||||
}
|
||||
self.removeSocket(s)
|
||||
}
|
||||
socket.connect()
|
||||
s.connect()
|
||||
}
|
||||
|
||||
func runTest(caseNum: Int) {
|
||||
socket = createSocket("runCase",caseNum)
|
||||
socket.onText = {(text: String) in
|
||||
self.socket.writeString(text)
|
||||
let s = createSocket("runCase",caseNum)
|
||||
self.socketArray.append(s)
|
||||
s.onText = {(text: String) in
|
||||
s.writeString(text)
|
||||
}
|
||||
socket.onData = {(data: NSData) in
|
||||
self.socket.writeData(data)
|
||||
s.onData = {(data: NSData) in
|
||||
s.writeData(data)
|
||||
}
|
||||
var once = false
|
||||
socket.onDisconnect = {(error: NSError?) in
|
||||
s.onDisconnect = {[unowned self] (error: NSError?) in
|
||||
if !once {
|
||||
once = true
|
||||
println("case:\(caseNum) finished")
|
||||
print("case:\(caseNum) finished")
|
||||
self.verifyTest(caseNum)
|
||||
self.removeSocket(s)
|
||||
}
|
||||
}
|
||||
socket.connect()
|
||||
s.connect()
|
||||
}
|
||||
|
||||
func verifyTest(caseNum: Int) {
|
||||
socket = createSocket("getCaseStatus",caseNum)
|
||||
socket.onText = {(text: String) in
|
||||
var error: NSError?
|
||||
let s = createSocket("getCaseStatus",caseNum)
|
||||
self.socketArray.append(s)
|
||||
s.onText = {(text: String) in
|
||||
let data = text.dataUsingEncoding(NSUTF8StringEncoding)
|
||||
var resp: AnyObject? = NSJSONSerialization.JSONObjectWithData(data!,
|
||||
options: NSJSONReadingOptions(), error: &error)
|
||||
if let dict = resp as? Dictionary<String,String> {
|
||||
if let status = dict["behavior"] {
|
||||
if status == "OK" {
|
||||
println("SUCCESS: \(caseNum)")
|
||||
return
|
||||
do {
|
||||
let resp: AnyObject? = try NSJSONSerialization.JSONObjectWithData(data!,
|
||||
options: NSJSONReadingOptions())
|
||||
if let dict = resp as? Dictionary<String,String> {
|
||||
if let status = dict["behavior"] {
|
||||
if status == "OK" {
|
||||
print("SUCCESS: \(caseNum)")
|
||||
return
|
||||
}
|
||||
}
|
||||
print("FAILURE: \(caseNum)")
|
||||
}
|
||||
println("FAILURE: \(caseNum)")
|
||||
} catch {
|
||||
print("error parsing the json")
|
||||
}
|
||||
}
|
||||
var once = false
|
||||
socket.onDisconnect = {(error: NSError?) in
|
||||
s.onDisconnect = {[unowned self] (error: NSError?) in
|
||||
if !once {
|
||||
once = true
|
||||
let nextCase = caseNum+1
|
||||
@@ -106,21 +125,24 @@ class ViewController: UIViewController {
|
||||
self.finishReports()
|
||||
}
|
||||
}
|
||||
self.removeSocket(s)
|
||||
}
|
||||
socket.connect()
|
||||
s.connect()
|
||||
}
|
||||
|
||||
func finishReports() {
|
||||
socket = createSocket("updateReports",0)
|
||||
socket.onDisconnect = {(error: NSError?) in
|
||||
println("finished all the tests!")
|
||||
let s = createSocket("updateReports",0)
|
||||
self.socketArray.append(s)
|
||||
s.onDisconnect = {[unowned self] (error: NSError?) in
|
||||
print("finished all the tests!")
|
||||
self.removeSocket(s)
|
||||
}
|
||||
socket.connect()
|
||||
s.connect()
|
||||
}
|
||||
|
||||
func createSocket(cmd: String, _ caseNum: Int) -> WebSocket {
|
||||
return WebSocket(url: NSURL(scheme: ViewController.scheme,
|
||||
host: ViewController.host, path: buildPath(cmd,caseNum))!, protocols: [])
|
||||
return WebSocket(url: NSURL(scheme: scheme,
|
||||
host: host, path: buildPath(cmd,caseNum))!, protocols: [])
|
||||
}
|
||||
|
||||
func buildPath(cmd: String, _ caseNum: Int) -> String {
|
||||
|
||||
BIN
Binary file not shown.
@@ -10,7 +10,7 @@ import UIKit
|
||||
import Starscream
|
||||
|
||||
class ViewController: UIViewController, WebSocketDelegate {
|
||||
var socket = WebSocket(url: NSURL(scheme: "ws", host: "localhost:8080", path: "/")!, protocols: ["chat", "superchat"])
|
||||
var socket = WebSocket(url: NSURL(string: "ws://localhost:8080/")!, protocols: ["chat", "superchat"])
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
@@ -21,23 +21,23 @@ class ViewController: UIViewController, WebSocketDelegate {
|
||||
// MARK: Websocket Delegate Methods.
|
||||
|
||||
func websocketDidConnect(ws: WebSocket) {
|
||||
println("websocket is connected")
|
||||
print("websocket is connected")
|
||||
}
|
||||
|
||||
func websocketDidDisconnect(ws: WebSocket, error: NSError?) {
|
||||
if let e = error {
|
||||
println("websocket is disconnected: \(e.localizedDescription)")
|
||||
print("websocket is disconnected: \(e.localizedDescription)")
|
||||
} else {
|
||||
println("websocket disconnected")
|
||||
print("websocket disconnected")
|
||||
}
|
||||
}
|
||||
|
||||
func websocketDidReceiveMessage(ws: WebSocket, text: String) {
|
||||
println("Received text: \(text)")
|
||||
print("Received text: \(text)")
|
||||
}
|
||||
|
||||
func websocketDidReceiveData(ws: WebSocket, data: NSData) {
|
||||
println("Received data: \(data.length)")
|
||||
print("Received data: \(data.length)")
|
||||
}
|
||||
|
||||
// MARK: Write Text Action
|
||||
|
||||
Reference in New Issue
Block a user