Skip to content

Commit

Permalink
Merge branch 'master' of github.com:daltoniam/Starscream
Browse files Browse the repository at this point in the history
* 'master' of github.com:daltoniam/Starscream: (22 commits)
  updated podspec
  the joys of getting all the package manager to play nice
  Removed Else Clause that would sometimes give a wrong error message
  updated changelog
  updated pod spec for compression changes
  SecTrust object now is conditionally unwrapped
  spm fix
  spm fix
  package manager update
  fixes daltoniam#309, daltoniam#319, daltoniam#334
  make WSResponse values public
  Add Advanced Delegate and Custom HTTP methods
  Update README with (trite) grammar changes
  Include zlib as a module, rather than using `@_silgen_name` and redefinition.  This is less verbose and less error-prone.
  Add information about compression support to README.  Other changes based on PR feedback.
  Avoid unnecessary copying.  Confirm Autobahn fuzzing test results are comparable with the autobahn library itself.
  Fix capture memory leaks in Autobahn tester.
  Fixes to handle different windowsBits and noContextTakeover.
  Integrate compression classes.
  Add compression classes and unit tests.
  ...
  • Loading branch information
sahara108 committed Jul 18, 2017
2 parents 8ff88e6 + b0ed1de commit 31b7384
Show file tree
Hide file tree
Showing 14 changed files with 641 additions and 58 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@
All notable changes to this project will be documented in this file.
`Starscream` adheres to [Semantic Versioning](http://semver.org/).

#### [2.1.0](https://github.com/daltoniam/Starscream/tree/2.1.0)

Adds WebSocket compression. Also adds advance WebSocket delegate for extra control. Bug Fixes.

[#349](https://github.com/daltoniam/Starscream/pull/349)
[#344](https://github.com/daltoniam/Starscream/pull/344)
[#339](https://github.com/daltoniam/Starscream/pull/339)
[#337](https://github.com/daltoniam/Starscream/pull/337)
[#334](https://github.com/daltoniam/Starscream/issues/334)
[#333](https://github.com/daltoniam/Starscream/pull/333)
[#319](https://github.com/daltoniam/Starscream/issues/319)
[#309](https://github.com/daltoniam/Starscream/issues/309)
[#329](https://github.com/daltoniam/Starscream/issues/329)

#### [2.0.4](https://github.com/daltoniam/Starscream/tree/2.0.4)

SSL Pinning fix by Giuliano Galea as reported by Lukas Futera of [Centralway](https://www.centralway.com/de/).
Expand Down
9 changes: 8 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,12 @@
import PackageDescription

let package = Package(
name: "Starscream"
name: "Starscream",
dependencies: [
.Package(url: "https://github.com/daltoniam/zlib-spm.git",
majorVersion: 1),
.Package(url: "https://github.com/IBM-Swift/CommonCrypto.git",
majorVersion: 0),
],
exclude: ["Tests", "examples"]
)
89 changes: 82 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

Starscream is a conforming WebSocket ([RFC 6455](http://tools.ietf.org/html/rfc6455)) client library in Swift for iOS and OSX.

It's Objective-C counter part can be found here: [Jetfire](https://github.com/acmacalister/jetfire)
Its Objective-C counterpart can be found here: [Jetfire](https://github.com/acmacalister/jetfire)

## Features

- Conforms to all of the base [Autobahn test suite](http://autobahn.ws/testsuite/).
- Nonblocking. Everything happens in the background, thanks to GCD.
- TLS/WSS support.
- Compression Extensions support ([RFC 7692](https://tools.ietf.org/html/rfc7692))
- Simple concise codebase at just a few hundred LOC.

## Example
Expand Down Expand Up @@ -160,6 +161,21 @@ socket.headers["Sec-WebSocket-Version"] = "14"
socket.headers["My-Awesome-Header"] = "Everything is Awesome!"
```

### Custom HTTP Method

Your server may use a different HTTP method when connecting to the websocket:

```swift
socket.httpMethod = .post
```
you can use a custom string:

```swift
socket.httpMethod = .custom(value: "mycustomhttpmethod")
```



### Protocols

If you need to specify a protocol, simple add it to the init:
Expand All @@ -171,16 +187,11 @@ socket.delegate = self
socket.connect()
```

### Self Signed SSL and VOIP

There are a couple of other properties that modify the stream:
### Self Signed SSL

```swift
socket = WebSocket(url: URL(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

//set this you want to ignore SSL cert validation, so a self signed SSL certificate can be used.
socket.disableSSLCertValidation = true
```
Expand All @@ -197,6 +208,30 @@ socket.security = SSLSecurity(certs: [SSLCert(data: data)], usePublicKeys: true)
```
You load either a `Data` 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.

### SSL Cipher Suites

To use an SSL encrypted connection, you need to tell Starscream about the cipher suites your server supports.

```swift
socket = WebSocket(url: URL(string: "wss://localhost:8080/")!, protocols: ["chat","superchat"])

// Set enabled cipher suites to AES 256 and AES 128
socket.enabledSSLCipherSuites = [TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256]
```

If you don't know which cipher suites are supported by your server, you can try pointing [SSL Labs](https://www.ssllabs.com/ssltest/) at it and checking the results.

### Compression Extensions

Compression Extensions ([RFC 7692](https://tools.ietf.org/html/rfc7692)) is supported in Starscream. Compression is enabled by default, however compression will only be used if it is supported by the server as well. You may enable or disable compression via the `.enableCompression` property:

```swift
socket = WebSocket(url: URL(string: "ws://localhost:8080/")!)
socket.enableCompression = false
```

Compression should be disabled if your application is transmitting already-compressed, random, or other uncompressable data.

### Custom Queue

A custom queue can be specified when delegate methods are called. By default `DispatchQueue.main` 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.
Expand Down Expand Up @@ -286,6 +321,46 @@ Add the `Starscream.xcodeproj` to your Xcode project. Once that is complete, in

If you are running this in an OSX app or on a physical iOS device you will need to make sure you add the `Starscream.framework` to be included in your app bundle. To do this, in Xcode, navigate to the target configuration window by clicking on the blue project icon, and selecting the application target under the "Targets" heading in the sidebar. In the tab bar at the top of that window, open the "Build Phases" panel. Expand the "Link Binary with Libraries" group, and add `Starscream.framework`. Click on the + button at the top left of the panel and select "New Copy Files Phase". Rename this new phase to "Copy Frameworks", set the "Destination" to "Frameworks", and add `Starscream.framework` respectively.


## WebSocketAdvancedDelegate
The advanced delegate acts just like the simpler delegate but provides some additional information on the connection and incoming frames.

```swift
socket.advancedDelegate = self
```

In most cases you do not need the extra info and should use the normal delegate.

#### websocketDidReceiveMessage
```swift
func websocketDidReceiveMessage(socket: WebSocket, text: String, response: WebSocket.WSResponse {
print("got some text: \(text)")
print("First frame for this message arrived on \(response.firstFrame)")
}
```

#### websocketDidReceiveData
```swift
func websocketDidReceiveData(socket: WebSocket, data: Date, response: WebSocket.WSResponse) {
print("got some data it long: \(data.count)")
print("A total of \(response.frameCount) frames were used to send this data")
}
```

#### websocketHttpUpgrade
These methods are called when the HTTP upgrade request is sent and when the response returns.
```swift
func websocketHttpUpgrade(socket: WebSocket, request: CFHTTPMessage) {
print("the http request was sent we can check the raw http if we need to")
}
```

```swift
func websocketHttpUpgrade(socket: WebSocket, response: CFHTTPMessage) {
print("the http response has returned.")
}
```

## TODOs

- [ ] WatchOS?
Expand Down
177 changes: 177 additions & 0 deletions Source/Compression.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
//////////////////////////////////////////////////////////////////////////////////////////////////
//
// Compression.swift
//
// Created by Joseph Ross on 7/16/14.
// Copyright © 2017 Joseph Ross.
//
// 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.
//
//////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////
//
// Compression implementation is implemented in conformance with RFC 7692 Compression Extensions
// for WebSocket: https://tools.ietf.org/html/rfc7692
//
//////////////////////////////////////////////////////////////////////////////////////////////////

import Foundation
import CZLib

class Decompressor {
private var strm = z_stream()
private var buffer = [UInt8](repeating: 0, count: 0x2000)
private var inflateInitialized = false
private let windowBits:Int

init?(windowBits:Int) {
self.windowBits = windowBits
guard initInflate() else { return nil }
}

private func initInflate() -> Bool {
if Z_OK == inflateInit2_(&strm, -CInt(windowBits),
ZLIB_VERSION, CInt(MemoryLayout<z_stream>.size))
{
inflateInitialized = true
return true
}
return false
}

func reset() throws {
teardownInflate()
guard initInflate() else { throw NSError() }
}

func decompress(_ data: Data, finish: Bool) throws -> Data {
return try data.withUnsafeBytes { (bytes:UnsafePointer<UInt8>) -> Data in
return try decompress(bytes: bytes, count: data.count, finish: finish)
}
}

func decompress(bytes: UnsafePointer<UInt8>, count: Int, finish: Bool) throws -> Data {
var decompressed = Data()
try decompress(bytes: bytes, count: count, out: &decompressed)

if finish {
let tail:[UInt8] = [0x00, 0x00, 0xFF, 0xFF]
try decompress(bytes: tail, count: tail.count, out: &decompressed)
}

return decompressed

}

private func decompress(bytes: UnsafePointer<UInt8>, count: Int, out:inout Data) throws {
var res:CInt = 0
strm.next_in = UnsafeMutablePointer<UInt8>(mutating: bytes)
strm.avail_in = CUnsignedInt(count)

repeat {
strm.next_out = UnsafeMutablePointer<UInt8>(&buffer)
strm.avail_out = CUnsignedInt(buffer.count)

res = inflate(&strm, 0)

let byteCount = buffer.count - Int(strm.avail_out)
out.append(buffer, count: byteCount)
} while res == Z_OK && strm.avail_out == 0

guard (res == Z_OK && strm.avail_out > 0)
|| (res == Z_BUF_ERROR && Int(strm.avail_out) == buffer.count)
else {
throw NSError(domain: WebSocket.ErrorDomain, code: Int(WebSocket.InternalErrorCode.compressionError.rawValue), userInfo: nil)
}
}

private func teardownInflate() {
if inflateInitialized, Z_OK == inflateEnd(&strm) {
inflateInitialized = false
}
}

deinit {
teardownInflate()
}
}

class Compressor {
private var strm = z_stream()
private var buffer = [UInt8](repeating: 0, count: 0x2000)
private var deflateInitialized = false
private let windowBits:Int

init?(windowBits: Int) {
self.windowBits = windowBits
guard initDeflate() else { return nil }
}

private func initDeflate() -> Bool {
if Z_OK == deflateInit2_(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
-CInt(windowBits), 8, Z_DEFAULT_STRATEGY,
ZLIB_VERSION, CInt(MemoryLayout<z_stream>.size))
{
deflateInitialized = true
return true
}
return false
}

func reset() throws {
teardownDeflate()
guard initDeflate() else { throw NSError() }
}

func compress(_ data: Data) throws -> Data {
var compressed = Data()
var res:CInt = 0
data.withUnsafeBytes { (ptr:UnsafePointer<UInt8>) -> Void in
strm.next_in = UnsafeMutablePointer<UInt8>(mutating: ptr)
strm.avail_in = CUnsignedInt(data.count)

repeat {
strm.next_out = UnsafeMutablePointer<UInt8>(&buffer)
strm.avail_out = CUnsignedInt(buffer.count)

res = deflate(&strm, Z_SYNC_FLUSH)

let byteCount = buffer.count - Int(strm.avail_out)
compressed.append(buffer, count: byteCount)
}
while res == Z_OK && strm.avail_out == 0

}

guard res == Z_OK && strm.avail_out > 0
|| (res == Z_BUF_ERROR && Int(strm.avail_out) == buffer.count)
else {
throw NSError(domain: WebSocket.ErrorDomain, code: Int(WebSocket.InternalErrorCode.compressionError.rawValue), userInfo: nil)
}

compressed.removeLast(4)
return compressed
}

private func teardownDeflate() {
if deflateInitialized, Z_OK == deflateEnd(&strm) {
deflateInitialized = false
}
}

deinit {
teardownDeflate()
}
}

2 changes: 1 addition & 1 deletion Source/Info-tvOS.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.0.4</string>
<string>2.1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
Expand Down
2 changes: 1 addition & 1 deletion Source/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.0.4</string>
<string>2.1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
Expand Down
Loading

0 comments on commit 31b7384

Please sign in to comment.