diff --git a/.gitignore b/.gitignore index b9353b017..b07e3754f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,21 @@ build/ nohup.out .DS_Store xcuserdata/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +*.xcworkspace +!default.xcworkspace +*xcuserdata +*.xccheckout +profile +*.moved-aside +DerivedData +extern/ + +*.pyc diff --git a/.gitmodules b/.gitmodules index ee0c676ad..a0188c0a6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "pages"] - path = pages - url = git://github.com/square/SocketRocket.git +[submodule "Vendor/xctoolchain"] + path = Vendor/xctoolchain + url = https://github.com/nlutsenko/xctoolchain.git diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 000000000..2bf1c1ccf --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.3.1 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..527e4b4ba --- /dev/null +++ b/.travis.yml @@ -0,0 +1,43 @@ +branches: + only: + - master +language: objective-c +os: osx +osx_image: xcode7.3 +env: + matrix: + - TEST_TYPE=iOS + - TEST_TYPE=OSX + - TEST_TYPE=tvOS + - TEST_TYPE=CocoaPods + - TEST_TYPE=Carthage +before_install: +- | + if [ "$TEST_TYPE" = iOS ] || [ "$TEST_TYPE" = OSX ] || [ "$TEST_TYPE" = tvOS ]; then + bundle install + elif [ "$TEST_TYPE" = Carthage ]; then + brew update + brew install carthage || brew upgrade carthage + fi +install: +- | + if [ "$TEST_TYPE" = iOS ]; then + ./TestSupport/setup_env.sh .env + fi +script: +- | + if [ "$TEST_TYPE" = iOS ]; then + set -o pipefail + xcodebuild -project SocketRocket.xcodeproj -scheme "SocketRocket-iOS" -sdk iphonesimulator build test + elif [ "$TEST_TYPE" = OSX ]; then + set -o pipefail + xcodebuild -project SocketRocket.xcodeproj -scheme "SocketRocket-macOS" -sdk macosx build | xcpretty -c + elif [ "$TEST_TYPE" = tvOS ]; then + set -o pipefail + xcodebuild -project SocketRocket.xcodeproj -scheme "SocketRocket-tvOS" -sdk appletvsimulator build | xcpretty -c + elif [ "$TEST_TYPE" = CocoaPods ]; then + pod lib lint SocketRocket.podspec + pod lib lint --use-libraries SocketRocket.podspec + elif [ "$TEST_TYPE" = Carthage ]; then + carthage build --no-skip-current + fi diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..ac27d8a51 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,2 @@ +# Code of Conduct +Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please [read the full text](https://code.fb.com/codeofconduct) so that you can understand what actions will and will not be tolerated. \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..1fa59e7ca --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,33 @@ +# Contributing to SocketRocket +We want to make contributing to this project as easy and transparent as possible. + +## Pull Requests +We actively welcome your pull requests. + +1. Fork the repo and create your branch from `master`. +2. If you've added code that should be tested, add tests. +3. If you've changed APIs, update the documentation. +4. Ensure the test suite passes. +5. Make sure your code lints. +6. If you haven't already, complete the Contributor License Agreement ("CLA"). + +## Contributor License Agreement ("CLA") +In order to accept your pull request, we need you to submit a CLA. +You only need to do this once to work on any of Facebook's open source projects. + +Complete your CLA here: + +## Issues +We use GitHub issues to track public bugs. Please ensure your description is +clear and has sufficient instructions to be able to reproduce the issue. + +Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe +disclosure of security bugs. In those cases, please go through the process +outlined on that page and do not file a public issue. + +## Coding Style +* Most importantly, match the existing code style as much as possible. +* Try to keep lines under 140 characters, if possible. + +## License +By contributing to SocketRocket, you agree that your contributions will be licensed under its BSD license. \ No newline at end of file diff --git a/Configurations/Shared b/Configurations/Shared new file mode 120000 index 000000000..818aee708 --- /dev/null +++ b/Configurations/Shared @@ -0,0 +1 @@ +../Vendor/xctoolchain/Configurations/ \ No newline at end of file diff --git a/Configurations/SocketRocket-iOS-Dynamic.xcconfig b/Configurations/SocketRocket-iOS-Dynamic.xcconfig new file mode 100644 index 000000000..c40c6bb4b --- /dev/null +++ b/Configurations/SocketRocket-iOS-Dynamic.xcconfig @@ -0,0 +1,17 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#include "Shared/Platform/iOS.xcconfig" +#include "Shared/Product/DynamicFramework.xcconfig" + +PRODUCT_NAME = SocketRocket +PRODUCT_BUNDLE_IDENTIFIER = com.facebook.socketrocket.ios +IPHONEOS_DEPLOYMENT_TARGET = 11.0 + +INFOPLIST_FILE = $(SRCROOT)/SocketRocket/Resources/Info.plist diff --git a/Configurations/SocketRocket-iOS.xcconfig b/Configurations/SocketRocket-iOS.xcconfig new file mode 100644 index 000000000..98bca92c5 --- /dev/null +++ b/Configurations/SocketRocket-iOS.xcconfig @@ -0,0 +1,20 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#include "Shared/Platform/iOS.xcconfig" +#include "Shared/Product/StaticFramework.xcconfig" + +PRODUCT_NAME = SocketRocket +PRODUCT_BUNDLE_IDENTIFIER = com.facebook.socketrocket.ios +IPHONEOS_DEPLOYMENT_TARGET = 11.0 + +INFOPLIST_FILE = $(SRCROOT)/SocketRocket/Resources/Info.plist + +OTHER_CFLAGS[sdk=iphoneos9.*] = $(inherited) -fembed-bitcode +OTHER_LDFLAGS = $(inherited) -Licucore diff --git a/Configurations/SocketRocket-macOS.xcconfig b/Configurations/SocketRocket-macOS.xcconfig new file mode 100644 index 000000000..de53c6250 --- /dev/null +++ b/Configurations/SocketRocket-macOS.xcconfig @@ -0,0 +1,17 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#include "Shared/Platform/macOS.xcconfig" +#include "Shared/Product/DynamicFramework.xcconfig" + +PRODUCT_NAME = SocketRocket +PRODUCT_BUNDLE_IDENTIFIER = com.facebook.socketrocket.macos +MACOSX_DEPLOYMENT_TARGET = 10.13 + +INFOPLIST_FILE = $(SRCROOT)/SocketRocket/Resources/Info.plist diff --git a/Configurations/SocketRocket-tvOS.xcconfig b/Configurations/SocketRocket-tvOS.xcconfig new file mode 100644 index 000000000..59abe9372 --- /dev/null +++ b/Configurations/SocketRocket-tvOS.xcconfig @@ -0,0 +1,17 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#include "Shared/Platform/tvOS.xcconfig" +#include "Shared/Product/DynamicFramework.xcconfig" + +PRODUCT_NAME = SocketRocket +PRODUCT_BUNDLE_IDENTIFIER = com.facebook.socketrocket.tvos +TVOS_DEPLOYMENT_TARGET = 11.0 + +INFOPLIST_FILE = $(SRCROOT)/SocketRocket/Resources/Info.plist diff --git a/Configurations/SocketRocketTests-iOS.xcconfig b/Configurations/SocketRocketTests-iOS.xcconfig new file mode 100644 index 000000000..cfb48767a --- /dev/null +++ b/Configurations/SocketRocketTests-iOS.xcconfig @@ -0,0 +1,19 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#include "Shared/Platform/iOS.xcconfig" +#include "Shared/Product/LogicTests.xcconfig" + +PRODUCT_NAME = SocketRocketTests-iOS +PRODUCT_MODULE_NAME = SocketRocketTests +PRODUCT_BUNDLE_IDENTIFIER = com.facebook.socketrocket.tests.ios + +IPHONEOS_DEPLOYMENT_TARGET = 11.0 + +INFOPLIST_FILE = $(SRCROOT)/Tests/Resources/Info.plist diff --git a/Configurations/TestChat-iOS.xcconfig b/Configurations/TestChat-iOS.xcconfig new file mode 100644 index 000000000..fc9ad3797 --- /dev/null +++ b/Configurations/TestChat-iOS.xcconfig @@ -0,0 +1,19 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#include "Shared/Platform/iOS.xcconfig" +#include "Shared/Product/Application.xcconfig" + +PRODUCT_NAME = TestChat +PRODUCT_MODULE_NAME = TestChat +PRODUCT_BUNDLE_IDENTIFIER = com.facebook.socketrocket.testchat + +IPHONEOS_DEPLOYMENT_TARGET = 11.0 + +INFOPLIST_FILE = $(SRCROOT)/TestChat/TestChat-Info.plist diff --git a/Gemfile b/Gemfile new file mode 100644 index 000000000..43c41f975 --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +gem 'cocoapods' +gem 'xcpretty' diff --git a/LICENSE b/LICENSE index c01a79c3b..032b20673 100644 --- a/LICENSE +++ b/LICENSE @@ -1,15 +1,30 @@ +BSD License - Copyright 2012 Square Inc. +For SocketRocket software - 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 +Copyright (c) 2016-present, Facebook, Inc. All rights reserved. - http://www.apache.org/licenses/LICENSE-2.0 +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: - 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. + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/LICENSE-examples b/LICENSE-examples new file mode 100644 index 000000000..1de781305 --- /dev/null +++ b/LICENSE-examples @@ -0,0 +1,11 @@ +Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + +The examples provided by Facebook are for non-commercial testing and evaluation +purposes only. Facebook reserves all rights not expressly granted. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile index e28774fa9..20e5e9b14 100644 --- a/Makefile +++ b/Makefile @@ -1,20 +1,30 @@ TEST_SCENARIOS="[1-8]*" TEST_URL='ws://localhost:9001/' -test: +all: + $(MAKE) -C SocketRocket + +clean: + $(MAKE) -C SocketRocket clean + +.env: + + ./TestSupport/setup_env.sh .env + +test: .env mkdir -p pages/results - bash ./TestSupport/run_test.sh $(TEST_SCENARIOS) $(TEST_URL) Debug || open pages/results/index.html && false + bash ./TestSupport/run_test_server.sh $(TEST_SCENARIOS) $(TEST_URL) Debug || open pages/results/index.html && false open pages/results/index.html -test_all: +test_all: .env mkdir -p pages/results - bash ./TestSupport/run_test.sh '*' $(TEST_URL) Debug || open pages/results/index.html && false + bash ./TestSupport/run_test_server.sh '*' $(TEST_URL) Debug || open pages/results/index.html && false open pages/results/index.html -test_perf: +test_perf: .env mkdir -p pages/results - bash ./TestSupport/run_test.sh '9.*' $(TEST_URL) Release || open pages/results/index.html && false + bash ./TestSupport/run_test_server.sh '9.*' $(TEST_URL) Release || open pages/results/index.html && false open pages/results/index.html diff --git a/PATENTS b/PATENTS new file mode 100644 index 000000000..6c116ef9f --- /dev/null +++ b/PATENTS @@ -0,0 +1,33 @@ +Additional Grant of Patent Rights Version 2 + +"Software" means the SocketRocket software distributed by Facebook, Inc. + +Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software +("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable +(subject to the termination provision below) license under any Necessary +Claims, to make, have made, use, sell, offer to sell, import, and otherwise +transfer the Software. For avoidance of doubt, no license is granted under +Facebook’s rights in any patent claims that are infringed by (i) modifications +to the Software made by you or any third party or (ii) the Software in +combination with any software or other technology. + +The license granted hereunder will terminate, automatically and without notice, +if you (or any of your subsidiaries, corporate affiliates or agents) initiate +directly or indirectly, or take a direct financial interest in, any Patent +Assertion: (i) against Facebook or any of its subsidiaries or corporate +affiliates, (ii) against any party if such Patent Assertion arises in whole or +in part from any software, technology, product or service of Facebook or any of +its subsidiaries or corporate affiliates, or (iii) against any party relating +to the Software. Notwithstanding the foregoing, if Facebook or any of its +subsidiaries or corporate affiliates files a lawsuit alleging patent +infringement against you in the first instance, and you respond by filing a +patent infringement counterclaim in that lawsuit against that party that is +unrelated to the Software, the license granted hereunder will not terminate +under section (i) of this paragraph due to such counterclaim. + +A "Necessary Claim" is a claim of a patent owned by Facebook that is +necessarily infringed by the Software standing alone. + +A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, +or contributory infringement or inducement to infringe any patent, including a +cross-claim or counterclaim. diff --git a/README.md b/README.md new file mode 100644 index 000000000..94697582a --- /dev/null +++ b/README.md @@ -0,0 +1,214 @@ +# SocketRocket + +![Platforms][platforms-svg] +[![License][license-svg]][license-link] + +[![Podspec][podspec-svg]][podspec-link] +[![Carthage Compatible][carthage-svg]](carthage-link) + +[![Build Status][build-status-svg]][build-status-link] + +A conforming WebSocket ([RFC 6455](https://tools.ietf.org/html/rfc6455>)) client library for iOS, macOS, tvOS and visionOS. + +Test results for SocketRocket [here](http://facebook.github.io/SocketRocket/results/). +You can compare to what modern browsers look like [here](http://autobahn.ws/testsuite/reports/clients/index.html). + +SocketRocket currently conforms to all core ~300 of [Autobahn](http://autobahn.ws/testsuite/>)'s fuzzing tests +(aside from two UTF-8 ones where it is merely *non-strict* tests 6.4.2 and 6.4.4). + +## Features/Design + +- TLS (wss) support, including self-signed certificates. +- Seems to perform quite well. +- Supports HTTP Proxies. +- Supports IPv4/IPv6. +- Supports SSL certificate pinning. +- Sends `ping` and can process `pong` events. +- Asynchronous and non-blocking. Most of the work is done on a background thread. +- Supports iOS, macOS, tvOS. + +## Installing + +There are a few options. Choose one, or just figure it out: + +- **[CocoaPods](https://cocoapods.org)** + + Add the following line to your Podfile: + ```ruby + pod 'SocketRocket' + ``` + Run `pod install`, and you are all set. + +- **[Carthage](https://github.com/carthage/carthage)** + + Add the following line to your Cartfile: + ``` + github "facebook/SocketRocket" + ``` + Run `carthage update`, and you should now have the latest version of `SocketRocket` in your `Carthage` folder. + +- **Using SocketRocket as a sub-project** + + You can also include `SocketRocket` as a subproject inside of your application if you'd prefer, although we do not recommend this, as it will increase your indexing time significantly. To do so, just drag and drop the `SocketRocket.xcodeproj` file into your workspace. + +## API + +### `SRWebSocket` + +The Web Socket. + +#### Note: + +`SRWebSocket` will retain itself between `-(void)open` and when it closes, errors, or fails. +This is similar to how `NSURLConnection` behaves (unlike `NSURLConnection`, `SRWebSocket` won't retain the delegate). + +#### Interface + +```objective-c +@interface SRWebSocket : NSObject + +// Make it with this +- (instancetype)initWithURLRequest:(NSURLRequest *)request; + +// Set this before opening +@property (nonatomic, weak) id delegate; + +// Open with this +- (void)open; + +// Close it with this +- (void)close; + +// Send a Data +- (void)sendData:(nullable NSData *)data error:(NSError **)error; + +// Send a UTF8 String +- (void)sendString:(NSString *)string error:(NSError **)error; + +@end +``` + +### `SRWebSocketDelegate` + +You implement this + +```objective-c +@protocol SRWebSocketDelegate + +@optional + +- (void)webSocketDidOpen:(SRWebSocket *)webSocket; + +- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessageWithString:(NSString *)string; +- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessageWithData:(NSData *)data; + +- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error; +- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(nullable NSString *)reason wasClean:(BOOL)wasClean; + +@end +``` + +## Testing + +Included are setup scripts for the python testing environment. +It comes packaged with vitualenv so all the dependencies are installed in userland. + +To run the short test from the command line, run: +```bash + make test +``` + +To run all the tests, run: +```bash + make test_all +``` + +The short tests don't include the performance tests +(the test harness is actually the bottleneck, not SocketRocket). + +The first time this is run, it may take a while to install the dependencies. It will be smooth sailing after that. + +You can also run tests inside Xcode, which runs the same thing, but makes it easier to debug. + +- Choose the `SocketRocketTests` target +- Make sure your running destination is either your Mac or any Simulator +- Run the test action (`⌘+U`) + +### TestChat Demo Application + +SocketRocket includes a demo app, TestChat. +It will "chat" with a listening websocket on port 9900. + +#### TestChat Server + +The sever takes a message and broadcasts it to all other connected clients. + +It requires some dependencies though to run. +We also want to reuse the virtualenv we made when we ran the tests. +If you haven't run the tests yet, go into the SocketRocket root directory and type: + +```bash +make test +``` + +This will set up your [virtualenv](https://pypi.python.org/pypi/virtualenv). + +Now, in your terminal: + +```bash +source .env/bin/activate +pip install git+https://github.com/tornadoweb/tornado.git +``` + +In the same terminal session, start the chatroom server: + +```bash +python TestChatServer/py/chatroom.py +``` + +There's also a Go implementation (with the latest weekly) where you can: + +```bash +cd TestChatServer/go +go run chatroom.go +``` + +#### Chatting + +Now, start TestChat.app (just run the target in the Xcode project). +If you had it started already you can hit the refresh button to reconnect. +It should say "Connected!" on top. + +To talk with the app, open up your browser to [http://localhost:9000](http://localhost:9000) and start chatting. + + +## WebSocket Server Implementation Recommendations + +SocketRocket has been used with the following libraries: + +- [Tornado](https://github.com/tornadoweb/tornado) +- Go's [WebSocket package](https://godoc.org/golang.org/x/net/websocket) or Gorilla's [version](http://www.gorillatoolkit.org/pkg/websocket). +- [Autobahn](http://autobahn.ws/testsuite/) (using its fuzzing client). + +The Tornado one is dirt simple and works like a charm. +([IPython notebook](http://ipython.org/ipython-doc/dev/interactive/htmlnotebook.html) uses it too). +It's much easier to configure handlers and routes than in Autobahn/twisted. + +## Contributing + +We’re glad you’re interested in SocketRocket, and we’d love to see where you take it. +Please read our [contributing guidelines](https://github.com/facebook/SocketRocket/blob/master/CONTRIBUTING.md) prior to submitting a Pull Request. + + [build-status-svg]: https://img.shields.io/travis/facebook/SocketRocket/master.svg + [build-status-link]: https://travis-ci.org/facebook/SocketRocket/branches + + [license-svg]: https://img.shields.io/badge/license-BSD-lightgrey.svg + [license-link]: https://github.com/facebook/SocketRocket/blob/master/LICENSE + + [podspec-svg]: https://img.shields.io/cocoapods/v/SocketRocket.svg + [podspec-link]: https://cocoapods.org/pods/SocketRocket + + [carthage-svg]: https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat + [carthage-link]: https://github.com/carthage/carthage + + [platforms-svg]: http://img.shields.io/cocoapods/p/SocketRocket.svg?style=flat diff --git a/README.rst b/README.rst deleted file mode 100644 index 25f302621..000000000 --- a/README.rst +++ /dev/null @@ -1,257 +0,0 @@ -SocketRocket Objective-C WebSocket Client (beta) -================================================ -A conforming WebSocket (`RFC 6455 `_) -client library. - -`Test results for SocketRocket here `_. -You can compare to what `modern browsers look like here -`_. - -SocketRocket currently conforms to all ~300 of `Autobahn -`_'s fuzzing tests (aside from -two UTF-8 ones where it is merely *non-strict*. tests 6.4.2 and 6.4.4) - -Features/Design ---------------- -- TLS (wss) support. It uses CFStream so we get this for *free* -- Uses NSStream/CFNetworking. Earlier implementations used ``dispatch_io``, - however, this proved to be make TLS nearly impossible. Also I wanted this to - work in iOS 4.x. (SocketRocket only supports 5.0 and above now) -- Uses ARC. It uses the 4.0 compatible subset (no weak refs). -- Seems to perform quite well -- Parallel architecture. Most of the work is done in background worker queues. -- Delegate-based. Had older versions that could use blocks too, but I felt it - didn't blend well with retain cycles and just objective C in general. - -Changes -------- - -v0.3.1-beta2 - 2013-01-12 -````````````````````````` - -- Stability fix for ``closeWithCode:reason:`` (Thanks @michaelpetrov!) -- Actually clean up the NSStreams and remove them from their runloops -- ``_SRRunLoopThread``'s ``main`` wasn't correctly wrapped with - ``@autoreleasepool`` - -v0.3.1-beta1 - 2013-01-12 -````````````````````````` - -- Cleaned up GCD so OS_OBJECT_USE_OBJC_RETAIN_RELEASE is optional -- Removed deprecated ``dispatch_get_current_queue`` in favor of ``dispatch_queue_set_specific`` and ``dispatch_get_specific`` -- Dropping support for iOS 4.0 (it may still work) - - -Installing (iOS) ----------------- -There's a few options. Choose one, or just figure it out - -- You can copy all the files in the SocketRocket group into your app. -- Include SocketRocket as a subproject and use libSocketRocket - - If you do this, you must add -ObjC to your "other linker flags" option - -- For OS X you will have to repackage make a .framework target. I will take - contributions. Message me if you are interested. - - -Depending on how you configure your project you may need to ``#import`` either -```` or ``"SRSocketRocket.h"`` - -Framework Dependencies -`````````````````````` -Your .app must be linked against the following frameworks/dylibs - -- libicucore.dylib -- CFNetwork.framework -- Security.framework -- Foundation.framework - -Installing (OS X) ------------------ -SocketRocket now has (64-bit only) OS X support. ``SocketRocket.framework`` -inside Xcode project is for OS X only. It should be identical in function aside -from the unicode validation. ICU isn't shipped with OS X which is what the -original implementation used for unicode validation. The workaround is much -more rhudimentary and less robust. - -1. Add SocketRocket.xcodeproj as either a subproject of your app or in your workspace. -2. Add ``SocketRocket.framework`` to the link libraries -3. If you don't have a "copy files" step for ``Framework``, create one -4. Add ``SocketRocket.framework`` to the "copy files" step. - -API ---- -The classes - -``SRWebSocket`` -``````````````` -The Web Socket. - -.. note:: ``SRWebSocket`` will retain itself between ``-(void)open`` and when it - closes, errors, or fails. This is similar to how ``NSURLConnection`` behaves. - (unlike ``NSURLConnection``, ``SRWebSocket`` won't retain the delegate) - -What you need to know - -.. code-block:: objective-c - - @interface SRWebSocket : NSObject - - // Make it with this - - (id)initWithURLRequest:(NSURLRequest *)request; - - // Set this before opening - @property (nonatomic, assign) id delegate; - - - (void)open; - - // Close it with this - - (void)close; - - // Send a UTF8 String or Data - - (void)send:(id)data; - - @end - -``SRWebSocketDelegate`` -``````````````````````` -You implement this - -.. code-block:: objective-c - - @protocol SRWebSocketDelegate - - - (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message; - - @optional - - - (void)webSocketDidOpen:(SRWebSocket *)webSocket; - - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error; - - (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean; - - @end - -Known Issues/Server Todo's --------------------------- -- Needs auth delegates (like in NSURLConnection) -- Move the streams off the main runloop (most of the work is backgrounded uses - GCD, but I just haven't gotten around to moving it off the main loop since I - converted it from dispatch_io) -- Re-implement server. I removed an existing implementation as well because it - wasn't being used and I wasn't super happy with the interface. Will revisit - this. -- Separate framer and client logic. This will make it nicer when having a - server. - -Testing -------- -Included are setup scripts for the python testing environment. It comes -packaged with vitualenv so all the dependencies are installed in userland. - -To run the short test from the command line, run:: - - make test - -To run all the tests, run:: - - make test_all - -The short tests don't include the performance tests. (the test harness is -actually the bottleneck, not SocketRocket). - -The first time this is run, it may take a while to install the dependencies. It -will be smooth sailing after that. After the test runs the makefile will open -the results page in your browser. If nothing comes up, you failed. Working on -making this interface a bit nicer. - -To run from the app, choose the ``SocketRocket`` target and run the test action -(``cmd+u``). It runs the same thing, but makes it easier to debug. There is -some serious pre/post hooks in the Test action. You can edit it to customize -behavior. - -.. note:: Xcode only up to version 4.4 is currently supported for the test - harness - -TestChat Demo Application -------------------------- -SocketRocket includes a demo app, TestChat. It will "chat" with a listening -websocket on port 9900. - -It's a simple project. Uses storyboard. Storyboard is sweet. - - -TestChat Server -``````````````` -We've included a small server for the chat app. It has a simple function. -It will take a message and broadcast it to all other connected clients. - -We have to get some dependencies. We also want to reuse the virtualenv we made -when we ran the tests. If you haven't run the tests yet, go into the -SocketRocket root directory and type:: - - make test - -This will set up your `virtualenv `_. -Now, in your terminal:: - - source .env/bin/activate - pip install git+https://github.com/facebook/tornado.git - -In the same terminal session, start the chatroom server:: - - python TestChatServer/py/chatroom.py - -There's also a Go implementation (with the latest weekly) where you can:: - - cd TestChatServer/go - go run chatroom.go - -Chatting -```````` -Now, start TestChat.app (just run the target in the XCode project). If you had -it started already you can hit the refresh button to reconnect. It should say -"Connected!" on top. - -To talk with the app, open up your browser to `http://localhost:9000 `_ and -start chatting. - - -WebSocket Server Implementation Recommendations ------------------------------------------------ -SocketRocket has been used with the following libraries: - -- `Tornado `_ -- Go's `weekly build `_ (the official release has an - outdated protocol, so you may have to use weekly until `Go 1 - `_ is released) -- `Autobahn `_ (using its fuzzing - client) - -The Tornado one is dirt simple and works like a charm. (`IPython notebook -`_ uses it -too). It's much easier to configure handlers and routes than in -Autobahn/twisted. - -As far as Go's goes, it works in my limited testing. I much prefer go's -concurrency model as well. Try it! You may like it. -It could use some more control over things such as pings, etc., but I -am sure it will come in time. - -Autobahn is a great test suite. The Python server code is good, and conforms -well (obviously). Hovever, for me, twisted would be a deal-breaker for writing -something new. I find it a bit too complex and heavy for a simple service. If -you are already using twisted though, Autobahn is probably for you. - -Contributing ------------- -Any contributors to the master SocketRocket repository must sign the `Individual -Contributor License Agreement -(CLA) -`_. -It's a short form that covers our bases and makes sure you're eligible to -contribute. - -When you have a change you'd like to see in the master repository, `send a pull -request `_. Before we merge your -request, we'll make sure you're in the list of people who have signed a CLA. diff --git a/SRWebSocketTests/SRTAutobahnTests.m b/SRWebSocketTests/SRTAutobahnTests.m deleted file mode 100644 index 764b3bd87..000000000 --- a/SRWebSocketTests/SRTAutobahnTests.m +++ /dev/null @@ -1,343 +0,0 @@ -// -// Copyright 2012 Square Inc. -// -// 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 -#import -#import "SRWebSocket.h" -#import "SRTWebSocketOperation.h" -#import "SenTestCase+SRTAdditions.h" - -#define SRLogDebug(format, ...) -//#define SRLogDebug(format, ...) NSLog(format, __VA_ARGS__) - -@interface SRTAutobahnTests : SenTestCase -@end - -@interface TestOperation : SRTWebSocketOperation -- (id)initWithBaseURL:(NSURL *)url testNumber:(NSInteger)testNumber agent:(NSString *)agent; -@end - - -@interface CaseGetterOperation : SRTWebSocketOperation -- (id)initWithBaseURL:(NSURL *)url; - -@property (nonatomic, readonly) NSInteger caseCount; - -@end - -@interface NSInvocation (SRTBlockInvocation) - -+ (NSInvocation *)invocationWithBlock:(dispatch_block_t)block; - -@end - -@interface SRTBlockInvoker - -- (id)initWithBlock:(dispatch_block_t)block; - -- (void)invoke; - -@end - -@interface UpdateOperation : SRTWebSocketOperation - -- (id)initWithBaseURL:(NSURL *)url agent:(NSString *)agent; - -@end - -@interface TestInfoOperation : SRTWebSocketOperation - -@property (nonatomic) NSDictionary *info; - -- (id)initWithBaseURL:(NSURL *)url caseNumber:(NSInteger)caseNumber; - -@end - -@interface TestResultsOperation : SRTWebSocketOperation - -@property (nonatomic) NSDictionary *info; - -- (id)initWithBaseURL:(NSURL *)url caseNumber:(NSInteger)caseNumber agent:(NSString *)agent; - -@end - -@implementation SRTAutobahnTests { - SRWebSocket *_curWebSocket; - NSInteger _testCount; - NSInteger _curTest; - NSMutableArray *_sockets; - NSString *_testURLString; - NSURL *_prefixURL; - NSString *_agent; - NSString *_description; -} - -- (id)initWithInvocation:(NSInvocation *)anInvocation description:(NSString *)description; -{ - self = [self initWithInvocation:anInvocation]; - if (self) { - _description = description; - } - return self; -} - -- (id)initWithInvocation:(NSInvocation *)anInvocation; -{ - self = [super initWithInvocation:anInvocation]; - if (self) { - [self raiseAfterFailure]; - _testURLString = [[NSProcessInfo processInfo].environment objectForKey:@"SR_TEST_URL"]; - _prefixURL = [NSURL URLWithString:_testURLString]; - _agent = [NSBundle bundleForClass:[self class]].bundleIdentifier; - } - return self; -} - -- (unsigned int)testCaseCount; -{ - if (self.invocation) { - return [super testCaseCount]; - } - - CaseGetterOperation *caseGetter = [[CaseGetterOperation alloc] initWithBaseURL:_prefixURL]; - - [caseGetter start]; - - [self runCurrentRunLoopUntilTestPasses:^BOOL{ - return caseGetter.isFinished; - } timeout:20.0]; - - STAssertNil(caseGetter.error, @"CaseGetter should have successfully returned the number of testCases. Instead got error %@", caseGetter.error); - - NSInteger caseCount = caseGetter.caseCount; - - return caseCount; -} - -- (BOOL)isEmpty; -{ - return NO; -} - -- (void)performTest:(SenTestCaseRun *) aRun -{ - if (self.invocation) { - [super performTest:aRun]; - return; - } - [aRun start]; - for (NSUInteger i = 1; i <= aRun.test.testCaseCount; i++) { - SEL sel = @selector(performTestWithNumber:); - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[[self class] instanceMethodSignatureForSelector:sel]]; - - invocation.selector = sel; - invocation.target = self; - - [invocation setArgument:&i atIndex:2]; - - NSString *description = [self caseDescriptionForCaseNumber:i]; - - SenTestCase *testCase = [[[self class] alloc] initWithInvocation:invocation description:description]; - - SenTestCaseRun *run = [[SenTestCaseRun alloc] initWithTest:testCase]; - - [testCase performTest:run]; - - for (NSException *e in run.exceptions) { - [aRun addException:e]; - } - } - [aRun stop]; - - [self updateReports]; -} - -- (NSInteger)testNum; -{ - NSInteger i; - [self.invocation getArgument:&i atIndex:2]; - return i; -} - -- (NSString *)caseDescriptionForCaseNumber:(NSInteger)caseNumber; -{ - TestInfoOperation *testInfoOperation = [[TestInfoOperation alloc] initWithBaseURL:_prefixURL caseNumber:caseNumber]; - - [testInfoOperation start]; - - [self runCurrentRunLoopUntilTestPasses:^BOOL{ - return testInfoOperation.isFinished; - } timeout:60 * 60]; - - STAssertNil(testInfoOperation.error, @"Updating the report should not have errored"); - - return [NSString stringWithFormat:@"%@ - %@", [testInfoOperation.info objectForKey:@"id"], [testInfoOperation.info objectForKey:@"description"]]; -} - -- (NSString *)description; -{ - if (_description) { - return _description; - } else { - return @"Autobahn Test Harness"; - } -} - -+ (id) defaultTestSuite -{ - return [[[self class] alloc] init]; -} - -- (void)performTestWithNumber:(NSInteger)testNumber; -{ - NSOperationQueue *testQueue = [[NSOperationQueue alloc] init]; - - testQueue.maxConcurrentOperationCount = 1; - - TestOperation *testOp = [[TestOperation alloc] initWithBaseURL:_prefixURL testNumber:testNumber agent:_agent]; - [testQueue addOperation:testOp]; - - TestResultsOperation *resultOp = [[TestResultsOperation alloc] initWithBaseURL:_prefixURL caseNumber:testNumber agent:_agent]; - [resultOp addDependency:testOp]; - [testQueue addOperation:resultOp]; - - testQueue.suspended = NO; - - [self runCurrentRunLoopUntilTestPasses:^BOOL{ - return resultOp.isFinished; - } timeout:60 * 60]; - - STAssertTrue(!testOp.error, @"Test operation should not have failed"); - STAssertEqualObjects(@"OK", [resultOp.info objectForKey:@"behavior"], @"Test behavior should be OK"); -} - -- (void)updateReports; -{ - UpdateOperation *updateReportOperation = [[UpdateOperation alloc] initWithBaseURL:_prefixURL agent:_agent]; - - [updateReportOperation start]; - - [self runCurrentRunLoopUntilTestPasses:^BOOL{ - return updateReportOperation.isFinished; - } timeout:60 * 60]; - - STAssertNil(updateReportOperation.error, @"Updating the report should not have errored"); -} - -@end - -@implementation TestOperation { - NSInteger _testNumber; -} - -- (id)initWithBaseURL:(NSURL *)url testNumber:(NSInteger)testNumber agent:(NSString *)agent; -{ - - NSString *path = [[url URLByAppendingPathComponent:@"runCase"] absoluteString]; - path = [path stringByAppendingFormat:@"?case=%d&agent=%@", testNumber, agent]; - - self = [super initWithURL:[NSURL URLWithString:path]]; - if (self) { - _testNumber = testNumber; - } - return self; -} - -- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message; -{ - [webSocket send:message]; -} - -@end - - -@implementation CaseGetterOperation - -@synthesize caseCount = _caseCount; - -- (id)initWithBaseURL:(NSURL *)url; -{ - self = [super initWithURL:[url URLByAppendingPathComponent:@"getCaseCount"]]; - if (self) { - } - return self; -} - -- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message; -{ - _caseCount = [message integerValue]; -} - -@end - - -@implementation UpdateOperation - -- (id)initWithBaseURL:(NSURL *)url agent:(NSString *)agent; -{ - NSString *path = [[url URLByAppendingPathComponent:@"updateReports"] absoluteString]; - path = [path stringByAppendingFormat:@"?agent=%@", agent]; - - return [super initWithURL:[NSURL URLWithString:path]]; -} - -- (void)start; -{ - [super start]; - NSLog(@"Updating Reports!"); -} - -@end - - -@implementation TestInfoOperation - -@synthesize info = _info; - -- (id)initWithBaseURL:(NSURL *)url caseNumber:(NSInteger)caseNumber; -{ - NSString *path = [[url URLByAppendingPathComponent:@"getCaseInfo"] absoluteString]; - path = [path stringByAppendingFormat:@"?case=%d", caseNumber]; - - return [super initWithURL:[NSURL URLWithString:path]]; -} - -- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(NSString *)message; -{ - self.info = [NSJSONSerialization JSONObjectWithData:[message dataUsingEncoding:NSUTF8StringEncoding] options:0 error:NULL]; -} - -@end - - -@implementation TestResultsOperation - -@synthesize info = _info; - -- (id)initWithBaseURL:(NSURL *)url caseNumber:(NSInteger)caseNumber agent:(NSString *)agent; -{ - NSString *path = [[url URLByAppendingPathComponent:@"getCaseStatus"] absoluteString]; - path = [path stringByAppendingFormat:@"?case=%d&agent=%@", caseNumber, agent]; - - return [super initWithURL:[NSURL URLWithString:path]]; -} - -- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(NSString *)message; -{ - self.info = [NSJSONSerialization JSONObjectWithData:[message dataUsingEncoding:NSUTF8StringEncoding] options:0 error:NULL]; -} - -@end diff --git a/SRWebSocketTests/SRTWebSocketOperation.h b/SRWebSocketTests/SRTWebSocketOperation.h deleted file mode 100644 index 4be22715c..000000000 --- a/SRWebSocketTests/SRTWebSocketOperation.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// SRTWebSocketOperation.h -// SocketRocket -// -// Created by Mike Lewis on 1/28/12. -// Copyright (c) 2012 __MyCompanyName__. All rights reserved. -// - -#import - -#import "SRWebSocket.h" - -@interface SRTWebSocketOperation : NSOperation - -- (id)initWithURL:(NSURL *)URL; - -@property (nonatomic) BOOL isFinished; -@property (nonatomic) BOOL isExecuting; - -@property (nonatomic, readonly, retain) NSError *error; - -// We override these methods. Please call super -- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean; -- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error; - -@end diff --git a/SRWebSocketTests/SRWebSocketTests-Prefix.pch b/SRWebSocketTests/SRWebSocketTests-Prefix.pch deleted file mode 100644 index 677477f2f..000000000 --- a/SRWebSocketTests/SRWebSocketTests-Prefix.pch +++ /dev/null @@ -1,21 +0,0 @@ -// -// Copyright 2012 Square Inc. -// -// 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. -// - -#ifdef __OBJC__ - #import - #import - #import "SenTestCase+SRTAdditions.h" -#endif diff --git a/SRWebSocketTests/SenTestCase+SRTAdditions.h b/SRWebSocketTests/SenTestCase+SRTAdditions.h deleted file mode 100644 index a9c056363..000000000 --- a/SRWebSocketTests/SenTestCase+SRTAdditions.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright 2012 Square Inc. -// -// 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 - - -typedef BOOL (^PXPredicateBlock)(); - - -@interface SenTest (PXAdditions) - -- (void)runCurrentRunLoopUntilTestPasses:(PXPredicateBlock)predicate timeout:(NSTimeInterval)timeout; - -@end diff --git a/SRWebSocketTests/SenTestCase+SRTAdditions.m b/SRWebSocketTests/SenTestCase+SRTAdditions.m deleted file mode 100644 index de373fd95..000000000 --- a/SRWebSocketTests/SenTestCase+SRTAdditions.m +++ /dev/null @@ -1,38 +0,0 @@ - // -// Copyright 2012 Square Inc. -// -// 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 "SenTestCase+SRTAdditions.h" - - -@implementation SenTestCase (SRTAdditions) - -- (void)runCurrentRunLoopUntilTestPasses:(PXPredicateBlock)predicate timeout:(NSTimeInterval)timeout; -{ - NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeout]; - - NSTimeInterval timeoutTime = [timeoutDate timeIntervalSinceReferenceDate]; - NSTimeInterval currentTime; - - for (currentTime = [NSDate timeIntervalSinceReferenceDate]; - !predicate() && currentTime < timeoutTime; - currentTime = [NSDate timeIntervalSinceReferenceDate]) { - [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - } - - STAssertTrue(currentTime <= timeoutTime, @"Timed out"); -} - -@end diff --git a/SRWebSocketTests/en.lproj/InfoPlist.strings b/SRWebSocketTests/en.lproj/InfoPlist.strings deleted file mode 100644 index 477b28ff8..000000000 --- a/SRWebSocketTests/en.lproj/InfoPlist.strings +++ /dev/null @@ -1,2 +0,0 @@ -/* Localized versions of Info.plist keys */ - diff --git a/SRWebSocketTests/foo.mm b/SRWebSocketTests/foo.mm deleted file mode 100644 index 3232008cc..000000000 --- a/SRWebSocketTests/foo.mm +++ /dev/null @@ -1,8 +0,0 @@ -// -// foo.m -// SocketRocket -// -// Created by Mike Lewis on 10/31/11. -// Copyright (c) 2011 __MyCompanyName__. All rights reserved. -// - diff --git a/SocketRocket.podspec b/SocketRocket.podspec index 55da48904..af8c3cf1e 100644 --- a/SocketRocket.podspec +++ b/SocketRocket.podspec @@ -1,15 +1,23 @@ Pod::Spec.new do |s| - s.name = "SocketRocket" - s.version = '0.3.1-beta2' - s.summary = 'A conforming WebSocket (RFC 6455) client library.' - s.homepage = 'https://github.com/square/SocketRocket' - s.authors = 'Square' - s.license = 'Apache License, Version 2.0' - s.source = { :git => 'https://github.com/square/SocketRocket.git', :commit => '82c9f8938f8b9b7aa578866cb7ce56bc11e52ced' } - s.source_files = 'SocketRocket/*.{h,m,c}' + s.name = 'SocketRocket' + s.version = '0.7.1' + s.summary = 'A conforming WebSocket (RFC 6455) client library for iOS, macOS and tvOS.' + s.homepage = 'https://github.com/facebook/SocketRocket' + s.authors = { 'Nikita Lutsenko' => 'nlutsenko@me.com', 'Dan Federman' => 'federman@squareup.com', 'Mike Lewis' => 'mikelikespie@gmail.com' } + s.license = 'BSD' + s.source = { :git => 'https://github.com/facebook/SocketRocket.git', :tag => s.version.to_s } s.requires_arc = true - s.ios.frameworks = %w{CFNetwork Security} - s.osx.frameworks = %w{CoreServices Security} - s.osx.compiler_flags = '-Wno-format' - s.libraries = "icucore" + + s.source_files = 'SocketRocket/**/*.{h,m}' + s.public_header_files = 'SocketRocket/*.h' + + s.ios.deployment_target = '11.0' + s.osx.deployment_target = '10.13' + s.tvos.deployment_target = '11.0' + s.visionos.deployment_target = '1.0' + + s.ios.frameworks = 'CFNetwork', 'Security' + s.osx.frameworks = 'CoreServices', 'Security' + s.tvos.frameworks = 'CFNetwork', 'Security' + s.libraries = 'icucore' end diff --git a/SocketRocket.xcodeproj/project.pbxproj b/SocketRocket.xcodeproj/project.pbxproj index dad7c9b7e..d97fb0d78 100644 --- a/SocketRocket.xcodeproj/project.pbxproj +++ b/SocketRocket.xcodeproj/project.pbxproj @@ -7,63 +7,268 @@ objects = { /* Begin PBXBuildFile section */ + 2D42277F1BB4365C000C1A6C /* SRWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = F6A12CCF145119B700C1D980 /* SRWebSocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D4227801BB43693000C1A6C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6B208301450F597009315AF /* Foundation.framework */; }; + 2D4227831BB436B1000C1A6C /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6A12CD3145122FC00C1D980 /* Security.framework */; }; + 2D4227851BB43734000C1A6C /* SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = F6A12CD0145119B700C1D980 /* SRWebSocket.m */; }; + 3345DC841C52ACD70083CCB8 /* SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = F6A12CD0145119B700C1D980 /* SRWebSocket.m */; }; + 3345DC871C52ACD70083CCB8 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6A12CD3145122FC00C1D980 /* Security.framework */; }; + 3345DC881C52ACD70083CCB8 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6B208301450F597009315AF /* Foundation.framework */; }; + 3345DC8A1C52ACD70083CCB8 /* SRWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = F6A12CCF145119B700C1D980 /* SRWebSocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 454A02D61D0FAD010060DFB2 /* SRSecurityPolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = 454A02D41D0FAD010060DFB2 /* SRSecurityPolicy.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 454A02D71D0FAD010060DFB2 /* SRSecurityPolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = 454A02D41D0FAD010060DFB2 /* SRSecurityPolicy.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 454A02D81D0FAD010060DFB2 /* SRSecurityPolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = 454A02D41D0FAD010060DFB2 /* SRSecurityPolicy.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 454FEA7D1D2570F600073768 /* SRPinningSecurityPolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = 454FEA791D2570D400073768 /* SRPinningSecurityPolicy.h */; }; + 454FEA7E1D2570F600073768 /* SRPinningSecurityPolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = 454FEA7A1D2570D400073768 /* SRPinningSecurityPolicy.m */; }; + 454FEA7F1D2570F800073768 /* SRPinningSecurityPolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = 454FEA791D2570D400073768 /* SRPinningSecurityPolicy.h */; }; + 454FEA801D2570F800073768 /* SRPinningSecurityPolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = 454FEA7A1D2570D400073768 /* SRPinningSecurityPolicy.m */; }; + 454FEA811D2570F900073768 /* SRPinningSecurityPolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = 454FEA791D2570D400073768 /* SRPinningSecurityPolicy.h */; }; + 454FEA821D2570F900073768 /* SRPinningSecurityPolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = 454FEA7A1D2570D400073768 /* SRPinningSecurityPolicy.m */; }; + 454FEA851D25719900073768 /* SRSecurityPolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = 454FEA831D25717C00073768 /* SRSecurityPolicy.m */; }; + 454FEA861D25719A00073768 /* SRSecurityPolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = 454FEA831D25717C00073768 /* SRSecurityPolicy.m */; }; + 454FEA871D25719A00073768 /* SRSecurityPolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = 454FEA831D25717C00073768 /* SRSecurityPolicy.m */; }; + 8105E4801CDD67B400AA12DB /* SRAutobahnTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8105E47A1CDD679A00AA12DB /* SRAutobahnTests.m */; }; + 8105E4821CDD67BD00AA12DB /* SRTWebSocketOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 8105E4771CDD679A00AA12DB /* SRTWebSocketOperation.m */; }; + 8105E4AE1CDD6E6200AA12DB /* SRAutobahnOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 8105E4AD1CDD6E6200AA12DB /* SRAutobahnOperation.m */; }; + 8105E5281CDD98E100AA12DB /* autobahn_configuration.json in Resources */ = {isa = PBXBuildFile; fileRef = 8105E5271CDD98E100AA12DB /* autobahn_configuration.json */; }; + 8117C4241D3076DF00784D79 /* NSURLRequest+SRWebSocketPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 8117C4221D3076DF00784D79 /* NSURLRequest+SRWebSocketPrivate.h */; }; + 8117C4251D3076DF00784D79 /* NSURLRequest+SRWebSocketPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 8117C4221D3076DF00784D79 /* NSURLRequest+SRWebSocketPrivate.h */; }; + 8117C4261D3076DF00784D79 /* NSURLRequest+SRWebSocketPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 8117C4221D3076DF00784D79 /* NSURLRequest+SRWebSocketPrivate.h */; }; + 8117C4311D30779900784D79 /* NSRunLoop+SRWebSocketPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 8117C42F1D30779900784D79 /* NSRunLoop+SRWebSocketPrivate.h */; }; + 8117C4321D30779900784D79 /* NSRunLoop+SRWebSocketPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 8117C42F1D30779900784D79 /* NSRunLoop+SRWebSocketPrivate.h */; }; + 8117C4331D30779900784D79 /* NSRunLoop+SRWebSocketPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 8117C42F1D30779900784D79 /* NSRunLoop+SRWebSocketPrivate.h */; }; + 811934BC1CDAF725003AB243 /* SocketRocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 555E0EB11C51E56D00E6BB92 /* SocketRocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 811934BE1CDAF725003AB243 /* SocketRocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 555E0EB11C51E56D00E6BB92 /* SocketRocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 811934C01CDAF726003AB243 /* SocketRocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 555E0EB11C51E56D00E6BB92 /* SocketRocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 813364001D091E170062E28D /* SRProxyConnect.h in Headers */ = {isa = PBXBuildFile; fileRef = 4861E7731D022211002FAB1D /* SRProxyConnect.h */; }; + 813364041D091E170062E28D /* SRProxyConnect.h in Headers */ = {isa = PBXBuildFile; fileRef = 4861E7731D022211002FAB1D /* SRProxyConnect.h */; }; + 813364081D091E180062E28D /* SRProxyConnect.h in Headers */ = {isa = PBXBuildFile; fileRef = 4861E7731D022211002FAB1D /* SRProxyConnect.h */; }; + 8133640C1D091E1B0062E28D /* SRProxyConnect.m in Sources */ = {isa = PBXBuildFile; fileRef = 4861E7741D022211002FAB1D /* SRProxyConnect.m */; }; + 8133640E1D091E1B0062E28D /* SRProxyConnect.m in Sources */ = {isa = PBXBuildFile; fileRef = 4861E7741D022211002FAB1D /* SRProxyConnect.m */; }; + 8133640F1D091E1C0062E28D /* SRProxyConnect.m in Sources */ = {isa = PBXBuildFile; fileRef = 4861E7741D022211002FAB1D /* SRProxyConnect.m */; }; + 815FE7271D497D720085FDA5 /* SRConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 815FE7241D497D720085FDA5 /* SRConstants.h */; }; + 815FE7281D497D720085FDA5 /* SRConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 815FE7241D497D720085FDA5 /* SRConstants.h */; }; + 815FE7291D497D720085FDA5 /* SRConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 815FE7241D497D720085FDA5 /* SRConstants.h */; }; + 815FE72B1D497D720085FDA5 /* SRConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 815FE7251D497D720085FDA5 /* SRConstants.m */; }; + 815FE72C1D497D720085FDA5 /* SRConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 815FE7251D497D720085FDA5 /* SRConstants.m */; }; + 815FE72D1D497D720085FDA5 /* SRConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 815FE7251D497D720085FDA5 /* SRConstants.m */; }; + 817491A91D1C8C33006E09DF /* SRMutex.h in Headers */ = {isa = PBXBuildFile; fileRef = 817491A61D1C8C33006E09DF /* SRMutex.h */; }; + 817491AA1D1C8C33006E09DF /* SRMutex.h in Headers */ = {isa = PBXBuildFile; fileRef = 817491A61D1C8C33006E09DF /* SRMutex.h */; }; + 817491AB1D1C8C33006E09DF /* SRMutex.h in Headers */ = {isa = PBXBuildFile; fileRef = 817491A61D1C8C33006E09DF /* SRMutex.h */; }; + 817491AD1D1C8C33006E09DF /* SRMutex.m in Sources */ = {isa = PBXBuildFile; fileRef = 817491A71D1C8C33006E09DF /* SRMutex.m */; }; + 817491AE1D1C8C33006E09DF /* SRMutex.m in Sources */ = {isa = PBXBuildFile; fileRef = 817491A71D1C8C33006E09DF /* SRMutex.m */; }; + 817491AF1D1C8C33006E09DF /* SRMutex.m in Sources */ = {isa = PBXBuildFile; fileRef = 817491A71D1C8C33006E09DF /* SRMutex.m */; }; + 817995871CE139700084DA37 /* SRDelegateController.h in Headers */ = {isa = PBXBuildFile; fileRef = 817995841CE139700084DA37 /* SRDelegateController.h */; }; + 817995881CE139700084DA37 /* SRDelegateController.h in Headers */ = {isa = PBXBuildFile; fileRef = 817995841CE139700084DA37 /* SRDelegateController.h */; }; + 817995891CE139700084DA37 /* SRDelegateController.h in Headers */ = {isa = PBXBuildFile; fileRef = 817995841CE139700084DA37 /* SRDelegateController.h */; }; + 8179958B1CE139700084DA37 /* SRDelegateController.m in Sources */ = {isa = PBXBuildFile; fileRef = 817995851CE139700084DA37 /* SRDelegateController.m */; }; + 8179958C1CE139700084DA37 /* SRDelegateController.m in Sources */ = {isa = PBXBuildFile; fileRef = 817995851CE139700084DA37 /* SRDelegateController.m */; }; + 8179958D1CE139700084DA37 /* SRDelegateController.m in Sources */ = {isa = PBXBuildFile; fileRef = 817995851CE139700084DA37 /* SRDelegateController.m */; }; + 817996801CE184F40084DA37 /* SRAutobahnUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 8179967F1CE184F40084DA37 /* SRAutobahnUtilities.m */; }; + 81900A4D1D18C9CC0015A290 /* SRLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 81900A4A1D18C9CC0015A290 /* SRLog.h */; }; + 81900A4E1D18C9CC0015A290 /* SRLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 81900A4A1D18C9CC0015A290 /* SRLog.h */; }; + 81900A4F1D18C9CC0015A290 /* SRLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 81900A4A1D18C9CC0015A290 /* SRLog.h */; }; + 81900A511D18C9CC0015A290 /* SRLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 81900A4B1D18C9CC0015A290 /* SRLog.m */; }; + 81900A521D18C9CC0015A290 /* SRLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 81900A4B1D18C9CC0015A290 /* SRLog.m */; }; + 81900A531D18C9CC0015A290 /* SRLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 81900A4B1D18C9CC0015A290 /* SRLog.m */; }; + 81AFCD661D4C431C00B3AFC9 /* libicucore.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 81AFCD651D4C431C00B3AFC9 /* libicucore.tbd */; }; + 81B22EC61CE42D7E0073C636 /* SRError.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B22EC31CE42D7E0073C636 /* SRError.h */; }; + 81B22EC71CE42D7E0073C636 /* SRError.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B22EC31CE42D7E0073C636 /* SRError.h */; }; + 81B22EC81CE42D7E0073C636 /* SRError.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B22EC31CE42D7E0073C636 /* SRError.h */; }; + 81B22ECA1CE42D7E0073C636 /* SRError.m in Sources */ = {isa = PBXBuildFile; fileRef = 81B22EC41CE42D7E0073C636 /* SRError.m */; }; + 81B22ECB1CE42D7E0073C636 /* SRError.m in Sources */ = {isa = PBXBuildFile; fileRef = 81B22EC41CE42D7E0073C636 /* SRError.m */; }; + 81B22ECC1CE42D7E0073C636 /* SRError.m in Sources */ = {isa = PBXBuildFile; fileRef = 81B22EC41CE42D7E0073C636 /* SRError.m */; }; + 81B22EE51CE43ECC0073C636 /* SRURLUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B22EE21CE43ECC0073C636 /* SRURLUtilities.h */; }; + 81B22EE61CE43ECC0073C636 /* SRURLUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B22EE21CE43ECC0073C636 /* SRURLUtilities.h */; }; + 81B22EE71CE43ECC0073C636 /* SRURLUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B22EE21CE43ECC0073C636 /* SRURLUtilities.h */; }; + 81B22EE91CE43ECC0073C636 /* SRURLUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 81B22EE31CE43ECC0073C636 /* SRURLUtilities.m */; }; + 81B22EEA1CE43ECC0073C636 /* SRURLUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 81B22EE31CE43ECC0073C636 /* SRURLUtilities.m */; }; + 81B22EEB1CE43ECC0073C636 /* SRURLUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 81B22EE31CE43ECC0073C636 /* SRURLUtilities.m */; }; + 81B31C151CDC404100D86D43 /* SRIOConsumer.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B31C0F1CDC404100D86D43 /* SRIOConsumer.h */; }; + 81B31C161CDC404100D86D43 /* SRIOConsumer.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B31C0F1CDC404100D86D43 /* SRIOConsumer.h */; }; + 81B31C171CDC404100D86D43 /* SRIOConsumer.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B31C0F1CDC404100D86D43 /* SRIOConsumer.h */; }; + 81B31C191CDC404100D86D43 /* SRIOConsumer.m in Sources */ = {isa = PBXBuildFile; fileRef = 81B31C101CDC404100D86D43 /* SRIOConsumer.m */; }; + 81B31C1A1CDC404100D86D43 /* SRIOConsumer.m in Sources */ = {isa = PBXBuildFile; fileRef = 81B31C101CDC404100D86D43 /* SRIOConsumer.m */; }; + 81B31C1B1CDC404100D86D43 /* SRIOConsumer.m in Sources */ = {isa = PBXBuildFile; fileRef = 81B31C101CDC404100D86D43 /* SRIOConsumer.m */; }; + 81B31C1D1CDC404100D86D43 /* SRIOConsumerPool.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B31C111CDC404100D86D43 /* SRIOConsumerPool.h */; }; + 81B31C1E1CDC404100D86D43 /* SRIOConsumerPool.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B31C111CDC404100D86D43 /* SRIOConsumerPool.h */; }; + 81B31C1F1CDC404100D86D43 /* SRIOConsumerPool.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B31C111CDC404100D86D43 /* SRIOConsumerPool.h */; }; + 81B31C211CDC404100D86D43 /* SRIOConsumerPool.m in Sources */ = {isa = PBXBuildFile; fileRef = 81B31C121CDC404100D86D43 /* SRIOConsumerPool.m */; }; + 81B31C221CDC404100D86D43 /* SRIOConsumerPool.m in Sources */ = {isa = PBXBuildFile; fileRef = 81B31C121CDC404100D86D43 /* SRIOConsumerPool.m */; }; + 81B31C231CDC404100D86D43 /* SRIOConsumerPool.m in Sources */ = {isa = PBXBuildFile; fileRef = 81B31C121CDC404100D86D43 /* SRIOConsumerPool.m */; }; + 81B31C2E1CDC406B00D86D43 /* SRHash.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B31C2B1CDC406B00D86D43 /* SRHash.h */; }; + 81B31C2F1CDC406B00D86D43 /* SRHash.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B31C2B1CDC406B00D86D43 /* SRHash.h */; }; + 81B31C301CDC406B00D86D43 /* SRHash.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B31C2B1CDC406B00D86D43 /* SRHash.h */; }; + 81B31C321CDC406B00D86D43 /* SRHash.m in Sources */ = {isa = PBXBuildFile; fileRef = 81B31C2C1CDC406B00D86D43 /* SRHash.m */; }; + 81B31C331CDC406B00D86D43 /* SRHash.m in Sources */ = {isa = PBXBuildFile; fileRef = 81B31C2C1CDC406B00D86D43 /* SRHash.m */; }; + 81B31C341CDC406B00D86D43 /* SRHash.m in Sources */ = {isa = PBXBuildFile; fileRef = 81B31C2C1CDC406B00D86D43 /* SRHash.m */; }; + 81B31C601CDC444900D86D43 /* SRRunLoopThread.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B31C5D1CDC444900D86D43 /* SRRunLoopThread.h */; }; + 81B31C611CDC444900D86D43 /* SRRunLoopThread.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B31C5D1CDC444900D86D43 /* SRRunLoopThread.h */; }; + 81B31C621CDC444900D86D43 /* SRRunLoopThread.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B31C5D1CDC444900D86D43 /* SRRunLoopThread.h */; }; + 81B31C641CDC444900D86D43 /* SRRunLoopThread.m in Sources */ = {isa = PBXBuildFile; fileRef = 81B31C5E1CDC444900D86D43 /* SRRunLoopThread.m */; }; + 81B31C651CDC444900D86D43 /* SRRunLoopThread.m in Sources */ = {isa = PBXBuildFile; fileRef = 81B31C5E1CDC444900D86D43 /* SRRunLoopThread.m */; }; + 81B31C661CDC444900D86D43 /* SRRunLoopThread.m in Sources */ = {isa = PBXBuildFile; fileRef = 81B31C5E1CDC444900D86D43 /* SRRunLoopThread.m */; }; + 81C22BC31D124168007BFDDF /* SRHTTPConnectMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = 81C22BC01D124168007BFDDF /* SRHTTPConnectMessage.h */; }; + 81C22BC41D124168007BFDDF /* SRHTTPConnectMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = 81C22BC01D124168007BFDDF /* SRHTTPConnectMessage.h */; }; + 81C22BC51D124168007BFDDF /* SRHTTPConnectMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = 81C22BC01D124168007BFDDF /* SRHTTPConnectMessage.h */; }; + 81C22BC71D124168007BFDDF /* SRHTTPConnectMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 81C22BC11D124168007BFDDF /* SRHTTPConnectMessage.m */; }; + 81C22BC81D124168007BFDDF /* SRHTTPConnectMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 81C22BC11D124168007BFDDF /* SRHTTPConnectMessage.m */; }; + 81C22BC91D124168007BFDDF /* SRHTTPConnectMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 81C22BC11D124168007BFDDF /* SRHTTPConnectMessage.m */; }; + 81C22BF91D1256E1007BFDDF /* SRRandom.h in Headers */ = {isa = PBXBuildFile; fileRef = 81C22BF61D1256E1007BFDDF /* SRRandom.h */; }; + 81C22BFA1D1256E1007BFDDF /* SRRandom.h in Headers */ = {isa = PBXBuildFile; fileRef = 81C22BF61D1256E1007BFDDF /* SRRandom.h */; }; + 81C22BFB1D1256E1007BFDDF /* SRRandom.h in Headers */ = {isa = PBXBuildFile; fileRef = 81C22BF61D1256E1007BFDDF /* SRRandom.h */; }; + 81C22BFD1D1256E1007BFDDF /* SRRandom.m in Sources */ = {isa = PBXBuildFile; fileRef = 81C22BF71D1256E1007BFDDF /* SRRandom.m */; }; + 81C22BFE1D1256E1007BFDDF /* SRRandom.m in Sources */ = {isa = PBXBuildFile; fileRef = 81C22BF71D1256E1007BFDDF /* SRRandom.m */; }; + 81C22BFF1D1256E1007BFDDF /* SRRandom.m in Sources */ = {isa = PBXBuildFile; fileRef = 81C22BF71D1256E1007BFDDF /* SRRandom.m */; }; + 81C68CD41D2CBE0A00A1D005 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81C68CD31D2CBE0A00A1D005 /* CFNetwork.framework */; }; + 81C68CDF1D2CBE1900A1D005 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81C68CDE1D2CBE1900A1D005 /* CFNetwork.framework */; }; + 81C68CEE1D2CBE9400A1D005 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6A12CD51451231B00C1D980 /* CFNetwork.framework */; }; + 81C68CF11D2CBE9F00A1D005 /* libicucore.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 81C68CF01D2CBE9F00A1D005 /* libicucore.tbd */; }; + 81C68CF61D2CBED100A1D005 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6A12CD3145122FC00C1D980 /* Security.framework */; }; + 81C68D071D2CBF6A00A1D005 /* libicucore.A.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 81C68D061D2CBF6A00A1D005 /* libicucore.A.tbd */; }; + 81C68D0E1D2CBFA800A1D005 /* libicucore.A.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 81C68D0D1D2CBFA800A1D005 /* libicucore.A.tbd */; }; + 81CD05D81CEEC47300497F47 /* NSURLRequest+SRWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CD05D51CEEC47300497F47 /* NSURLRequest+SRWebSocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 81CD05D91CEEC47300497F47 /* NSURLRequest+SRWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CD05D51CEEC47300497F47 /* NSURLRequest+SRWebSocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 81CD05DA1CEEC47300497F47 /* NSURLRequest+SRWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CD05D51CEEC47300497F47 /* NSURLRequest+SRWebSocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 81CD05DC1CEEC47300497F47 /* NSURLRequest+SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 81CD05D61CEEC47300497F47 /* NSURLRequest+SRWebSocket.m */; }; + 81CD05DD1CEEC47300497F47 /* NSURLRequest+SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 81CD05D61CEEC47300497F47 /* NSURLRequest+SRWebSocket.m */; }; + 81CD05DE1CEEC47300497F47 /* NSURLRequest+SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 81CD05D61CEEC47300497F47 /* NSURLRequest+SRWebSocket.m */; }; + 81CD05FE1CEEC65D00497F47 /* NSRunLoop+SRWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CD05FB1CEEC65D00497F47 /* NSRunLoop+SRWebSocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 81CD05FF1CEEC65D00497F47 /* NSRunLoop+SRWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CD05FB1CEEC65D00497F47 /* NSRunLoop+SRWebSocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 81CD06001CEEC65D00497F47 /* NSRunLoop+SRWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CD05FB1CEEC65D00497F47 /* NSRunLoop+SRWebSocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 81CD06021CEEC65D00497F47 /* NSRunLoop+SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 81CD05FC1CEEC65D00497F47 /* NSRunLoop+SRWebSocket.m */; }; + 81CD06031CEEC65D00497F47 /* NSRunLoop+SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 81CD05FC1CEEC65D00497F47 /* NSRunLoop+SRWebSocket.m */; }; + 81CD06041CEEC65D00497F47 /* NSRunLoop+SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 81CD05FC1CEEC65D00497F47 /* NSRunLoop+SRWebSocket.m */; }; + 81DCD1241D2D9235002501A2 /* libicucore.A.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 81C68D0D1D2CBFA800A1D005 /* libicucore.A.tbd */; }; + F5391CBF1D2F4B4700606A81 /* SRSIMDHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = F5391CBC1D2F4B4700606A81 /* SRSIMDHelpers.h */; }; + F5391CC01D2F4B4700606A81 /* SRSIMDHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = F5391CBC1D2F4B4700606A81 /* SRSIMDHelpers.h */; }; + F5391CC11D2F4B4700606A81 /* SRSIMDHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = F5391CBC1D2F4B4700606A81 /* SRSIMDHelpers.h */; }; + F5391CC31D2F4B4700606A81 /* SRSIMDHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = F5391CBD1D2F4B4700606A81 /* SRSIMDHelpers.m */; }; + F5391CC41D2F4B4700606A81 /* SRSIMDHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = F5391CBD1D2F4B4700606A81 /* SRSIMDHelpers.m */; }; + F5391CC51D2F4B4700606A81 /* SRSIMDHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = F5391CBD1D2F4B4700606A81 /* SRSIMDHelpers.m */; }; F6016C8814620EC70037BB3D /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6A12CD3145122FC00C1D980 /* Security.framework */; }; - F6016C8914620ECC0037BB3D /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6A12CD3145122FC00C1D980 /* Security.framework */; }; - F6016C8A1462143C0037BB3D /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6A12CD51451231B00C1D980 /* CFNetwork.framework */; }; - F60CC2A114D4EA0500A005E4 /* SRTWebSocketOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = F60CC2A014D4EA0500A005E4 /* SRTWebSocketOperation.m */; }; F61A0DC81625F44D00365EBD /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F61A0DC71625F44D00365EBD /* Default-568h@2x.png */; }; F62417E614D52F3C003CE997 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F62417E514D52F3C003CE997 /* UIKit.framework */; }; F62417E714D52F3C003CE997 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6B208301450F597009315AF /* Foundation.framework */; }; - F62417E914D52F3C003CE997 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F62417E814D52F3C003CE997 /* CoreGraphics.framework */; }; F62417EF14D52F3C003CE997 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = F62417ED14D52F3C003CE997 /* InfoPlist.strings */; }; F62417F114D52F3C003CE997 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = F62417F014D52F3C003CE997 /* main.m */; }; F62417F514D52F3C003CE997 /* TCAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = F62417F414D52F3C003CE997 /* TCAppDelegate.m */; }; F62417F814D52F3C003CE997 /* MainStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F62417F614D52F3C003CE997 /* MainStoryboard.storyboard */; }; F62417FB14D52F3C003CE997 /* TCViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F62417FA14D52F3C003CE997 /* TCViewController.m */; }; F624180114D5300C003CE997 /* TCChatCell.m in Sources */ = {isa = PBXBuildFile; fileRef = F624180014D5300C003CE997 /* TCChatCell.m */; }; - F624180214D532E0003CE997 /* libSocketRocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F6B2082D1450F597009315AF /* libSocketRocket.a */; }; F624180314D53449003CE997 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6A12CD51451231B00C1D980 /* CFNetwork.framework */; }; F624180414D53449003CE997 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6A12CD3145122FC00C1D980 /* Security.framework */; }; - F624180614D53451003CE997 /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F6C41C95145F7C4700641356 /* libicucore.dylib */; }; F6396B86153E67EC00345B5E /* SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = F6A12CD0145119B700C1D980 /* SRWebSocket.m */; }; - F668C899153E923C0044DBAC /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6396BA4153E6D7400345B5E /* CoreServices.framework */; }; - F668C89A153E923C0044DBAC /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6396BA1153E6D4800345B5E /* Foundation.framework */; }; - F668C89B153E923C0044DBAC /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6396B9F153E6D3700345B5E /* Security.framework */; }; F668C8AA153E92F90044DBAC /* SRWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = F6A12CCF145119B700C1D980 /* SRWebSocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; - F6A12CD1145119B700C1D980 /* SRWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = F6A12CCF145119B700C1D980 /* SRWebSocket.h */; }; - F6A12CD2145119B700C1D980 /* SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = F6A12CD0145119B700C1D980 /* SRWebSocket.m */; }; - F6AE451D145906A70022AF3C /* libSocketRocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F6B2082D1450F597009315AF /* libSocketRocket.a */; }; - F6AE4520145906B20022AF3C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6B208301450F597009315AF /* Foundation.framework */; }; F6AE45241459071C0022AF3C /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6A12CD51451231B00C1D980 /* CFNetwork.framework */; }; - F6AE4528145907D30022AF3C /* SenTestCase+SRTAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = F6AE4527145907D30022AF3C /* SenTestCase+SRTAdditions.m */; }; - F6BDA804145900D200FE3253 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6BDA803145900D200FE3253 /* SenTestingKit.framework */; }; F6BDA806145900D200FE3253 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6B208301450F597009315AF /* Foundation.framework */; }; - F6BDA80C145900D200FE3253 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = F6BDA80A145900D200FE3253 /* InfoPlist.strings */; }; - F6BDA8161459016900FE3253 /* SRTAutobahnTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F6BDA8151459016900FE3253 /* SRTAutobahnTests.m */; }; - F6C41C98145F7C6100641356 /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F6C41C95145F7C4700641356 /* libicucore.dylib */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - F62417D514D50869003CE997 /* PBXContainerItemProxy */ = { + 81E8A6A01D4C41DA00916C7E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F6B208241450F597009315AF /* Project object */; proxyType = 1; - remoteGlobalIDString = F6B2082C1450F597009315AF; - remoteInfo = SocketRocket; + remoteGlobalIDString = 2D4227611BB4358C000C1A6C; + remoteInfo = "SocketRocket-iOS-Dynamic"; }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + 81E8A6A61D4C41E000916C7E /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ - F60CC29F14D4EA0500A005E4 /* SRTWebSocketOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRTWebSocketOperation.h; sourceTree = ""; }; - F60CC2A014D4EA0500A005E4 /* SRTWebSocketOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRTWebSocketOperation.m; sourceTree = ""; }; + 2D4227621BB4358C000C1A6C /* SocketRocket.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SocketRocket.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3345DC901C52ACD70083CCB8 /* SocketRocket.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SocketRocket.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 454A02D41D0FAD010060DFB2 /* SRSecurityPolicy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SRSecurityPolicy.h; path = SocketRocket/SRSecurityPolicy.h; sourceTree = SOURCE_ROOT; }; + 454FEA791D2570D400073768 /* SRPinningSecurityPolicy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRPinningSecurityPolicy.h; sourceTree = ""; }; + 454FEA7A1D2570D400073768 /* SRPinningSecurityPolicy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRPinningSecurityPolicy.m; sourceTree = ""; }; + 454FEA831D25717C00073768 /* SRSecurityPolicy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRSecurityPolicy.m; sourceTree = ""; }; + 4861E7731D022211002FAB1D /* SRProxyConnect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRProxyConnect.h; sourceTree = ""; }; + 4861E7741D022211002FAB1D /* SRProxyConnect.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRProxyConnect.m; sourceTree = ""; }; + 555E0EB11C51E56D00E6BB92 /* SocketRocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SocketRocket.h; sourceTree = ""; }; + 8105E4761CDD679A00AA12DB /* SRTWebSocketOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SRTWebSocketOperation.h; sourceTree = ""; }; + 8105E4771CDD679A00AA12DB /* SRTWebSocketOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SRTWebSocketOperation.m; sourceTree = ""; }; + 8105E4791CDD679A00AA12DB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 8105E47A1CDD679A00AA12DB /* SRAutobahnTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SRAutobahnTests.m; sourceTree = ""; }; + 8105E4AC1CDD6E6200AA12DB /* SRAutobahnOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRAutobahnOperation.h; sourceTree = ""; }; + 8105E4AD1CDD6E6200AA12DB /* SRAutobahnOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRAutobahnOperation.m; sourceTree = ""; }; + 8105E5271CDD98E100AA12DB /* autobahn_configuration.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = autobahn_configuration.json; sourceTree = ""; }; + 8117C4221D3076DF00784D79 /* NSURLRequest+SRWebSocketPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSURLRequest+SRWebSocketPrivate.h"; path = "Internal/NSURLRequest+SRWebSocketPrivate.h"; sourceTree = ""; }; + 8117C42F1D30779900784D79 /* NSRunLoop+SRWebSocketPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSRunLoop+SRWebSocketPrivate.h"; path = "Internal/NSRunLoop+SRWebSocketPrivate.h"; sourceTree = ""; }; + 811934B11CDAF711003AB243 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 815FE7241D497D720085FDA5 /* SRConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SRConstants.h; path = SocketRocket/Internal/SRConstants.h; sourceTree = SOURCE_ROOT; }; + 815FE7251D497D720085FDA5 /* SRConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SRConstants.m; path = SocketRocket/Internal/SRConstants.m; sourceTree = SOURCE_ROOT; }; + 817491A61D1C8C33006E09DF /* SRMutex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRMutex.h; sourceTree = ""; }; + 817491A71D1C8C33006E09DF /* SRMutex.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRMutex.m; sourceTree = ""; }; + 817995841CE139700084DA37 /* SRDelegateController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRDelegateController.h; sourceTree = ""; }; + 817995851CE139700084DA37 /* SRDelegateController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRDelegateController.m; sourceTree = ""; }; + 8179967E1CE184F40084DA37 /* SRAutobahnUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRAutobahnUtilities.h; sourceTree = ""; }; + 8179967F1CE184F40084DA37 /* SRAutobahnUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRAutobahnUtilities.m; sourceTree = ""; }; + 81900A4A1D18C9CC0015A290 /* SRLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRLog.h; sourceTree = ""; }; + 81900A4B1D18C9CC0015A290 /* SRLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRLog.m; sourceTree = ""; }; + 81AFCD651D4C431C00B3AFC9 /* libicucore.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libicucore.tbd; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.0.sdk/usr/lib/libicucore.tbd; sourceTree = DEVELOPER_DIR; }; + 81B22EC31CE42D7E0073C636 /* SRError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRError.h; sourceTree = ""; }; + 81B22EC41CE42D7E0073C636 /* SRError.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRError.m; sourceTree = ""; }; + 81B22EE21CE43ECC0073C636 /* SRURLUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRURLUtilities.h; sourceTree = ""; }; + 81B22EE31CE43ECC0073C636 /* SRURLUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRURLUtilities.m; sourceTree = ""; }; + 81B31C0F1CDC404100D86D43 /* SRIOConsumer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRIOConsumer.h; sourceTree = ""; }; + 81B31C101CDC404100D86D43 /* SRIOConsumer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRIOConsumer.m; sourceTree = ""; }; + 81B31C111CDC404100D86D43 /* SRIOConsumerPool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRIOConsumerPool.h; sourceTree = ""; }; + 81B31C121CDC404100D86D43 /* SRIOConsumerPool.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRIOConsumerPool.m; sourceTree = ""; }; + 81B31C2B1CDC406B00D86D43 /* SRHash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRHash.h; sourceTree = ""; }; + 81B31C2C1CDC406B00D86D43 /* SRHash.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRHash.m; sourceTree = ""; }; + 81B31C5D1CDC444900D86D43 /* SRRunLoopThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRRunLoopThread.h; sourceTree = ""; }; + 81B31C5E1CDC444900D86D43 /* SRRunLoopThread.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRRunLoopThread.m; sourceTree = ""; }; + 81C22BC01D124168007BFDDF /* SRHTTPConnectMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRHTTPConnectMessage.h; sourceTree = ""; }; + 81C22BC11D124168007BFDDF /* SRHTTPConnectMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRHTTPConnectMessage.m; sourceTree = ""; }; + 81C22BF61D1256E1007BFDDF /* SRRandom.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRRandom.h; sourceTree = ""; }; + 81C22BF71D1256E1007BFDDF /* SRRandom.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRRandom.m; sourceTree = ""; }; + 81C68CD31D2CBE0A00A1D005 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/CFNetwork.framework; sourceTree = DEVELOPER_DIR; }; + 81C68CDE1D2CBE1900A1D005 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/System/Library/Frameworks/CFNetwork.framework; sourceTree = DEVELOPER_DIR; }; + 81C68CF01D2CBE9F00A1D005 /* libicucore.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libicucore.tbd; path = usr/lib/libicucore.tbd; sourceTree = SDKROOT; }; + 81C68D061D2CBF6A00A1D005 /* libicucore.A.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libicucore.A.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/lib/libicucore.A.tbd; sourceTree = DEVELOPER_DIR; }; + 81C68D0D1D2CBFA800A1D005 /* libicucore.A.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libicucore.A.tbd; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib/libicucore.A.tbd; sourceTree = DEVELOPER_DIR; }; + 81CD05D51CEEC47300497F47 /* NSURLRequest+SRWebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURLRequest+SRWebSocket.h"; sourceTree = ""; }; + 81CD05D61CEEC47300497F47 /* NSURLRequest+SRWebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURLRequest+SRWebSocket.m"; sourceTree = ""; }; + 81CD05FB1CEEC65D00497F47 /* NSRunLoop+SRWebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSRunLoop+SRWebSocket.h"; sourceTree = ""; }; + 81CD05FC1CEEC65D00497F47 /* NSRunLoop+SRWebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSRunLoop+SRWebSocket.m"; sourceTree = ""; }; + 81D6474A1D2CA6A100690609 /* Common.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Common.xcconfig; sourceTree = ""; }; + 81D6474C1D2CA6A100690609 /* iOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = iOS.xcconfig; sourceTree = ""; }; + 81D6474D1D2CA6A100690609 /* macOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = macOS.xcconfig; sourceTree = ""; }; + 81D6474E1D2CA6A100690609 /* tvOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = tvOS.xcconfig; sourceTree = ""; }; + 81D6474F1D2CA6A100690609 /* watchOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = watchOS.xcconfig; sourceTree = ""; }; + 81D647511D2CA6A100690609 /* Application.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Application.xcconfig; sourceTree = ""; }; + 81D647521D2CA6A100690609 /* DynamicFramework.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DynamicFramework.xcconfig; sourceTree = ""; }; + 81D647531D2CA6A100690609 /* LogicTests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = LogicTests.xcconfig; sourceTree = ""; }; + 81D647541D2CA6A100690609 /* StaticFramework.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = StaticFramework.xcconfig; sourceTree = ""; }; + 81D647561D2CA6A100690609 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + 81D647571D2CA6A100690609 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 81D647581D2CA6A100690609 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 81D647591D2CA6A100690609 /* SocketRocket-iOS-Dynamic.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "SocketRocket-iOS-Dynamic.xcconfig"; sourceTree = ""; }; + 81D6475A1D2CA6A100690609 /* SocketRocket-iOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "SocketRocket-iOS.xcconfig"; sourceTree = ""; }; + 81D6475B1D2CA6A100690609 /* SocketRocket-macOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "SocketRocket-macOS.xcconfig"; sourceTree = ""; }; + 81D6475C1D2CA6A100690609 /* SocketRocket-tvOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "SocketRocket-tvOS.xcconfig"; sourceTree = ""; }; + 81D6475D1D2CA6A100690609 /* SocketRocketTests-iOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "SocketRocketTests-iOS.xcconfig"; sourceTree = ""; }; + 81E8A69A1D4C417A00916C7E /* TestChat-iOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "TestChat-iOS.xcconfig"; sourceTree = ""; }; + F5391CBC1D2F4B4700606A81 /* SRSIMDHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRSIMDHelpers.h; sourceTree = ""; }; + F5391CBD1D2F4B4700606A81 /* SRSIMDHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRSIMDHelpers.m; sourceTree = ""; }; F61A0DC71625F44D00365EBD /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "en.lproj/Default-568h@2x.png"; sourceTree = ""; }; F62417E314D52F3C003CE997 /* TestChat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestChat.app; sourceTree = BUILT_PRODUCTS_DIR; }; - F62417E514D52F3C003CE997 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; - F62417E814D52F3C003CE997 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + F62417E514D52F3C003CE997 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; F62417EC14D52F3C003CE997 /* TestChat-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "TestChat-Info.plist"; sourceTree = ""; }; F62417EE14D52F3C003CE997 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; F62417F014D52F3C003CE997 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - F62417F214D52F3C003CE997 /* TestChat-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TestChat-Prefix.pch"; sourceTree = ""; }; F62417F314D52F3C003CE997 /* TCAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TCAppDelegate.h; sourceTree = ""; }; F62417F414D52F3C003CE997 /* TCAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TCAppDelegate.m; sourceTree = ""; }; F62417F714D52F3C003CE997 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/MainStoryboard.storyboard; sourceTree = ""; }; @@ -71,64 +276,57 @@ F62417FA14D52F3C003CE997 /* TCViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TCViewController.m; sourceTree = ""; }; F62417FF14D5300C003CE997 /* TCChatCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TCChatCell.h; sourceTree = ""; }; F624180014D5300C003CE997 /* TCChatCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TCChatCell.m; sourceTree = ""; }; - F6396B9F153E6D3700345B5E /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/System/Library/Frameworks/Security.framework; sourceTree = DEVELOPER_DIR; }; - F6396BA1153E6D4800345B5E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; - F6396BA4153E6D7400345B5E /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/System/Library/Frameworks/CoreServices.framework; sourceTree = DEVELOPER_DIR; }; F668C880153E91210044DBAC /* SocketRocket.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SocketRocket.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F668C884153E91210044DBAC /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; - F668C885153E91210044DBAC /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; - F668C886153E91210044DBAC /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; - F668C889153E91210044DBAC /* SocketRocketOSX-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SocketRocketOSX-Info.plist"; sourceTree = ""; }; F6A12CCF145119B700C1D980 /* SRWebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRWebSocket.h; sourceTree = ""; }; F6A12CD0145119B700C1D980 /* SRWebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRWebSocket.m; sourceTree = ""; }; F6A12CD3145122FC00C1D980 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; F6A12CD51451231B00C1D980 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; - F6AE4526145907D30022AF3C /* SenTestCase+SRTAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SenTestCase+SRTAdditions.h"; sourceTree = ""; }; - F6AE4527145907D30022AF3C /* SenTestCase+SRTAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SenTestCase+SRTAdditions.m"; sourceTree = ""; }; - F6B2082D1450F597009315AF /* libSocketRocket.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libSocketRocket.a; sourceTree = BUILT_PRODUCTS_DIR; }; F6B208301450F597009315AF /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; - F6B208341450F597009315AF /* SocketRocket-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SocketRocket-Prefix.pch"; sourceTree = ""; }; - F6BDA802145900D200FE3253 /* SRWebSocketTests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SRWebSocketTests.octest; sourceTree = BUILT_PRODUCTS_DIR; }; - F6BDA803145900D200FE3253 /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; }; - F6BDA809145900D200FE3253 /* SRWebSocketTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SRWebSocketTests-Info.plist"; sourceTree = ""; }; - F6BDA80B145900D200FE3253 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; - F6BDA810145900D200FE3253 /* SRWebSocketTests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SRWebSocketTests-Prefix.pch"; sourceTree = ""; }; - F6BDA8151459016900FE3253 /* SRTAutobahnTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRTAutobahnTests.m; sourceTree = ""; }; - F6C41C95145F7C4700641356 /* libicucore.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libicucore.dylib; path = usr/lib/libicucore.dylib; sourceTree = SDKROOT; }; + F6BDA802145900D200FE3253 /* SocketRocketTests-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SocketRocketTests-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - F62417E014D52F3C003CE997 /* Frameworks */ = { + 2D42275E1BB4358C000C1A6C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F62417E614D52F3C003CE997 /* UIKit.framework in Frameworks */, - F62417E714D52F3C003CE997 /* Foundation.framework in Frameworks */, - F62417E914D52F3C003CE997 /* CoreGraphics.framework in Frameworks */, - F624180214D532E0003CE997 /* libSocketRocket.a in Frameworks */, - F624180314D53449003CE997 /* CFNetwork.framework in Frameworks */, - F624180414D53449003CE997 /* Security.framework in Frameworks */, - F624180614D53451003CE997 /* libicucore.dylib in Frameworks */, + 81C68D0E1D2CBFA800A1D005 /* libicucore.A.tbd in Frameworks */, + 81C68CD41D2CBE0A00A1D005 /* CFNetwork.framework in Frameworks */, + 2D4227831BB436B1000C1A6C /* Security.framework in Frameworks */, + 2D4227801BB43693000C1A6C /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - F668C87C153E91210044DBAC /* Frameworks */ = { + 3345DC851C52ACD70083CCB8 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 81C68D071D2CBF6A00A1D005 /* libicucore.A.tbd in Frameworks */, + 81C68CDF1D2CBE1900A1D005 /* CFNetwork.framework in Frameworks */, + 3345DC871C52ACD70083CCB8 /* Security.framework in Frameworks */, + 3345DC881C52ACD70083CCB8 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F62417E014D52F3C003CE997 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F668C899153E923C0044DBAC /* CoreServices.framework in Frameworks */, - F668C89A153E923C0044DBAC /* Foundation.framework in Frameworks */, - F668C89B153E923C0044DBAC /* Security.framework in Frameworks */, + 81AFCD661D4C431C00B3AFC9 /* libicucore.tbd in Frameworks */, + F62417E614D52F3C003CE997 /* UIKit.framework in Frameworks */, + F62417E714D52F3C003CE997 /* Foundation.framework in Frameworks */, + F624180314D53449003CE997 /* CFNetwork.framework in Frameworks */, + F624180414D53449003CE997 /* Security.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - F6B2082A1450F597009315AF /* Frameworks */ = { + F668C87C153E91210044DBAC /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F6AE4520145906B20022AF3C /* Foundation.framework in Frameworks */, - F6016C8914620ECC0037BB3D /* Security.framework in Frameworks */, - F6016C8A1462143C0037BB3D /* CFNetwork.framework in Frameworks */, + 81C68CEE1D2CBE9400A1D005 /* CFNetwork.framework in Frameworks */, + 81C68CF61D2CBED100A1D005 /* Security.framework in Frameworks */, + 81C68CF11D2CBE9F00A1D005 /* libicucore.tbd in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -136,18 +334,237 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F6C41C98145F7C6100641356 /* libicucore.dylib in Frameworks */, - F6BDA804145900D200FE3253 /* SenTestingKit.framework in Frameworks */, F6BDA806145900D200FE3253 /* Foundation.framework in Frameworks */, - F6AE451D145906A70022AF3C /* libSocketRocket.a in Frameworks */, F6AE45241459071C0022AF3C /* CFNetwork.framework in Frameworks */, F6016C8814620EC70037BB3D /* Security.framework in Frameworks */, + 81DCD1241D2D9235002501A2 /* libicucore.A.tbd in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 4861E7721D022211002FAB1D /* Proxy */ = { + isa = PBXGroup; + children = ( + 4861E7731D022211002FAB1D /* SRProxyConnect.h */, + 4861E7741D022211002FAB1D /* SRProxyConnect.m */, + ); + path = Proxy; + sourceTree = ""; + }; + 8105E4741CDD679A00AA12DB /* Tests */ = { + isa = PBXGroup; + children = ( + 8105E47A1CDD679A00AA12DB /* SRAutobahnTests.m */, + 8105E4751CDD679A00AA12DB /* Operations */, + 8105E47C1CDD679A00AA12DB /* Utilities */, + 8105E4781CDD679A00AA12DB /* Resources */, + ); + path = Tests; + sourceTree = ""; + }; + 8105E4751CDD679A00AA12DB /* Operations */ = { + isa = PBXGroup; + children = ( + 8105E4761CDD679A00AA12DB /* SRTWebSocketOperation.h */, + 8105E4771CDD679A00AA12DB /* SRTWebSocketOperation.m */, + 8105E4AC1CDD6E6200AA12DB /* SRAutobahnOperation.h */, + 8105E4AD1CDD6E6200AA12DB /* SRAutobahnOperation.m */, + ); + path = Operations; + sourceTree = ""; + }; + 8105E4781CDD679A00AA12DB /* Resources */ = { + isa = PBXGroup; + children = ( + 8105E5271CDD98E100AA12DB /* autobahn_configuration.json */, + 8105E4791CDD679A00AA12DB /* Info.plist */, + ); + path = Resources; + sourceTree = ""; + }; + 8105E47C1CDD679A00AA12DB /* Utilities */ = { + isa = PBXGroup; + children = ( + 8179967E1CE184F40084DA37 /* SRAutobahnUtilities.h */, + 8179967F1CE184F40084DA37 /* SRAutobahnUtilities.m */, + ); + path = Utilities; + sourceTree = ""; + }; + 811934B01CDAF711003AB243 /* Resources */ = { + isa = PBXGroup; + children = ( + 811934B11CDAF711003AB243 /* Info.plist */, + ); + path = Resources; + sourceTree = ""; + }; + 817995831CE139540084DA37 /* Delegate */ = { + isa = PBXGroup; + children = ( + 817995841CE139700084DA37 /* SRDelegateController.h */, + 817995851CE139700084DA37 /* SRDelegateController.m */, + ); + path = Delegate; + sourceTree = ""; + }; + 8186892C1D08EF3C004F94C8 /* Security */ = { + isa = PBXGroup; + children = ( + 454FEA791D2570D400073768 /* SRPinningSecurityPolicy.h */, + 454FEA7A1D2570D400073768 /* SRPinningSecurityPolicy.m */, + ); + path = Security; + sourceTree = ""; + }; + 81B31C0D1CDC404100D86D43 /* Internal */ = { + isa = PBXGroup; + children = ( + 8186892C1D08EF3C004F94C8 /* Security */, + 4861E7721D022211002FAB1D /* Proxy */, + 817995831CE139540084DA37 /* Delegate */, + 81B31C0E1CDC404100D86D43 /* IOConsumer */, + 81B31C5C1CDC443A00D86D43 /* RunLoop */, + 81B31C131CDC404100D86D43 /* Utilities */, + ); + path = Internal; + sourceTree = ""; + }; + 81B31C0E1CDC404100D86D43 /* IOConsumer */ = { + isa = PBXGroup; + children = ( + 81B31C0F1CDC404100D86D43 /* SRIOConsumer.h */, + 81B31C101CDC404100D86D43 /* SRIOConsumer.m */, + 81B31C111CDC404100D86D43 /* SRIOConsumerPool.h */, + 81B31C121CDC404100D86D43 /* SRIOConsumerPool.m */, + ); + path = IOConsumer; + sourceTree = ""; + }; + 81B31C131CDC404100D86D43 /* Utilities */ = { + isa = PBXGroup; + children = ( + 81B22EC31CE42D7E0073C636 /* SRError.h */, + 81B22EC41CE42D7E0073C636 /* SRError.m */, + 81B31C2B1CDC406B00D86D43 /* SRHash.h */, + 81B31C2C1CDC406B00D86D43 /* SRHash.m */, + 81C22BC01D124168007BFDDF /* SRHTTPConnectMessage.h */, + 81C22BC11D124168007BFDDF /* SRHTTPConnectMessage.m */, + 81900A4A1D18C9CC0015A290 /* SRLog.h */, + 81900A4B1D18C9CC0015A290 /* SRLog.m */, + 817491A61D1C8C33006E09DF /* SRMutex.h */, + 817491A71D1C8C33006E09DF /* SRMutex.m */, + 81C22BF61D1256E1007BFDDF /* SRRandom.h */, + 81C22BF71D1256E1007BFDDF /* SRRandom.m */, + 815FE7241D497D720085FDA5 /* SRConstants.h */, + 815FE7251D497D720085FDA5 /* SRConstants.m */, + 81B22EE21CE43ECC0073C636 /* SRURLUtilities.h */, + 81B22EE31CE43ECC0073C636 /* SRURLUtilities.m */, + F5391CBC1D2F4B4700606A81 /* SRSIMDHelpers.h */, + F5391CBD1D2F4B4700606A81 /* SRSIMDHelpers.m */, + ); + path = Utilities; + sourceTree = ""; + }; + 81B31C5C1CDC443A00D86D43 /* RunLoop */ = { + isa = PBXGroup; + children = ( + 81B31C5D1CDC444900D86D43 /* SRRunLoopThread.h */, + 81B31C5E1CDC444900D86D43 /* SRRunLoopThread.m */, + ); + name = RunLoop; + path = SocketRocket/Internal/RunLoop; + sourceTree = SOURCE_ROOT; + }; + 81C68CF41D2CBEBD00A1D005 /* macOS */ = { + isa = PBXGroup; + children = ( + F6B208301450F597009315AF /* Foundation.framework */, + F6A12CD3145122FC00C1D980 /* Security.framework */, + F6A12CD51451231B00C1D980 /* CFNetwork.framework */, + 81C68CF01D2CBE9F00A1D005 /* libicucore.tbd */, + ); + name = macOS; + sourceTree = ""; + }; + 81C68CFD1D2CBF1800A1D005 /* tvOS */ = { + isa = PBXGroup; + children = ( + 81C68CDE1D2CBE1900A1D005 /* CFNetwork.framework */, + 81C68D061D2CBF6A00A1D005 /* libicucore.A.tbd */, + ); + name = tvOS; + sourceTree = ""; + }; + 81C68CFE1D2CBF2100A1D005 /* iOS */ = { + isa = PBXGroup; + children = ( + F62417E514D52F3C003CE997 /* UIKit.framework */, + 81C68CD31D2CBE0A00A1D005 /* CFNetwork.framework */, + 81C68D0D1D2CBFA800A1D005 /* libicucore.A.tbd */, + ); + name = iOS; + sourceTree = ""; + }; + 81D647481D2CA6A100690609 /* Configurations */ = { + isa = PBXGroup; + children = ( + 81D6475A1D2CA6A100690609 /* SocketRocket-iOS.xcconfig */, + 81D647591D2CA6A100690609 /* SocketRocket-iOS-Dynamic.xcconfig */, + 81D6475B1D2CA6A100690609 /* SocketRocket-macOS.xcconfig */, + 81D6475C1D2CA6A100690609 /* SocketRocket-tvOS.xcconfig */, + 81D6475D1D2CA6A100690609 /* SocketRocketTests-iOS.xcconfig */, + 81E8A69A1D4C417A00916C7E /* TestChat-iOS.xcconfig */, + 81D647491D2CA6A100690609 /* Shared */, + ); + path = Configurations; + sourceTree = ""; + }; + 81D647491D2CA6A100690609 /* Shared */ = { + isa = PBXGroup; + children = ( + 81D6474A1D2CA6A100690609 /* Common.xcconfig */, + 81D6474B1D2CA6A100690609 /* Platform */, + 81D647501D2CA6A100690609 /* Product */, + 81D647551D2CA6A100690609 /* Project */, + 81D647581D2CA6A100690609 /* Warnings.xcconfig */, + ); + path = Shared; + sourceTree = ""; + }; + 81D6474B1D2CA6A100690609 /* Platform */ = { + isa = PBXGroup; + children = ( + 81D6474C1D2CA6A100690609 /* iOS.xcconfig */, + 81D6474D1D2CA6A100690609 /* macOS.xcconfig */, + 81D6474E1D2CA6A100690609 /* tvOS.xcconfig */, + 81D6474F1D2CA6A100690609 /* watchOS.xcconfig */, + ); + path = Platform; + sourceTree = ""; + }; + 81D647501D2CA6A100690609 /* Product */ = { + isa = PBXGroup; + children = ( + 81D647511D2CA6A100690609 /* Application.xcconfig */, + 81D647521D2CA6A100690609 /* DynamicFramework.xcconfig */, + 81D647531D2CA6A100690609 /* LogicTests.xcconfig */, + 81D647541D2CA6A100690609 /* StaticFramework.xcconfig */, + ); + path = Product; + sourceTree = ""; + }; + 81D647551D2CA6A100690609 /* Project */ = { + isa = PBXGroup; + children = ( + 81D647561D2CA6A100690609 /* Debug.xcconfig */, + 81D647571D2CA6A100690609 /* Release.xcconfig */, + ); + path = Project; + sourceTree = ""; + }; F62417EA14D52F3C003CE997 /* TestChat */ = { isa = PBXGroup; children = ( @@ -170,46 +587,17 @@ F62417EC14D52F3C003CE997 /* TestChat-Info.plist */, F62417ED14D52F3C003CE997 /* InfoPlist.strings */, F62417F014D52F3C003CE997 /* main.m */, - F62417F214D52F3C003CE997 /* TestChat-Prefix.pch */, ); name = "Supporting Files"; sourceTree = ""; }; - F6396BA3153E6D4D00345B5E /* OSX Frameworks */ = { - isa = PBXGroup; - children = ( - F6396BA4153E6D7400345B5E /* CoreServices.framework */, - F6396BA1153E6D4800345B5E /* Foundation.framework */, - F6396B9F153E6D3700345B5E /* Security.framework */, - ); - name = "OSX Frameworks"; - sourceTree = ""; - }; - F668C883153E91210044DBAC /* Other Frameworks */ = { - isa = PBXGroup; - children = ( - F668C884153E91210044DBAC /* AppKit.framework */, - F668C885153E91210044DBAC /* CoreData.framework */, - F668C886153E91210044DBAC /* Foundation.framework */, - ); - name = "Other Frameworks"; - sourceTree = ""; - }; - F668C887153E91210044DBAC /* SocketRocketOSX */ = { - isa = PBXGroup; - children = ( - F668C889153E91210044DBAC /* SocketRocketOSX-Info.plist */, - ); - path = SocketRocketOSX; - sourceTree = ""; - }; F6B208221450F597009315AF = { isa = PBXGroup; children = ( + 81D647481D2CA6A100690609 /* Configurations */, F6B208321450F597009315AF /* SocketRocket */, - F6BDA807145900D200FE3253 /* SRWebSocketTests */, + 8105E4741CDD679A00AA12DB /* Tests */, F62417EA14D52F3C003CE997 /* TestChat */, - F668C887153E91210044DBAC /* SocketRocketOSX */, F6B2082F1450F597009315AF /* Frameworks */, F6B2082E1450F597009315AF /* Products */, ); @@ -220,10 +608,11 @@ F6B2082E1450F597009315AF /* Products */ = { isa = PBXGroup; children = ( - F6B2082D1450F597009315AF /* libSocketRocket.a */, - F6BDA802145900D200FE3253 /* SRWebSocketTests.octest */, + F6BDA802145900D200FE3253 /* SocketRocketTests-iOS.xctest */, F62417E314D52F3C003CE997 /* TestChat.app */, F668C880153E91210044DBAC /* SocketRocket.framework */, + 2D4227621BB4358C000C1A6C /* SocketRocket.framework */, + 3345DC901C52ACD70083CCB8 /* SocketRocket.framework */, ); name = Products; sourceTree = ""; @@ -231,15 +620,10 @@ F6B2082F1450F597009315AF /* Frameworks */ = { isa = PBXGroup; children = ( - F6396BA3153E6D4D00345B5E /* OSX Frameworks */, - F6C41C95145F7C4700641356 /* libicucore.dylib */, - F6A12CD51451231B00C1D980 /* CFNetwork.framework */, - F6A12CD3145122FC00C1D980 /* Security.framework */, - F6B208301450F597009315AF /* Foundation.framework */, - F6BDA803145900D200FE3253 /* SenTestingKit.framework */, - F62417E514D52F3C003CE997 /* UIKit.framework */, - F62417E814D52F3C003CE997 /* CoreGraphics.framework */, - F668C883153E91210044DBAC /* Other Frameworks */, + 81AFCD651D4C431C00B3AFC9 /* libicucore.tbd */, + 81C68CF41D2CBEBD00A1D005 /* macOS */, + 81C68CFD1D2CBF1800A1D005 /* tvOS */, + 81C68CFE1D2CBF2100A1D005 /* iOS */, ); name = Frameworks; sourceTree = ""; @@ -247,66 +631,150 @@ F6B208321450F597009315AF /* SocketRocket */ = { isa = PBXGroup; children = ( - F6B208331450F597009315AF /* Supporting Files */, + 81B31C0D1CDC404100D86D43 /* Internal */, + 555E0EB11C51E56D00E6BB92 /* SocketRocket.h */, + 454A02D41D0FAD010060DFB2 /* SRSecurityPolicy.h */, + 454FEA831D25717C00073768 /* SRSecurityPolicy.m */, F6A12CCF145119B700C1D980 /* SRWebSocket.h */, F6A12CD0145119B700C1D980 /* SRWebSocket.m */, + 81CD05D51CEEC47300497F47 /* NSURLRequest+SRWebSocket.h */, + 8117C4221D3076DF00784D79 /* NSURLRequest+SRWebSocketPrivate.h */, + 81CD05D61CEEC47300497F47 /* NSURLRequest+SRWebSocket.m */, + 81CD05FB1CEEC65D00497F47 /* NSRunLoop+SRWebSocket.h */, + 8117C42F1D30779900784D79 /* NSRunLoop+SRWebSocketPrivate.h */, + 81CD05FC1CEEC65D00497F47 /* NSRunLoop+SRWebSocket.m */, + 811934B01CDAF711003AB243 /* Resources */, ); path = SocketRocket; sourceTree = ""; }; - F6B208331450F597009315AF /* Supporting Files */ = { - isa = PBXGroup; - children = ( - F6B208341450F597009315AF /* SocketRocket-Prefix.pch */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - F6BDA807145900D200FE3253 /* SRWebSocketTests */ = { - isa = PBXGroup; - children = ( - F6BDA808145900D200FE3253 /* Supporting Files */, - F6BDA8151459016900FE3253 /* SRTAutobahnTests.m */, - F6BDA810145900D200FE3253 /* SRWebSocketTests-Prefix.pch */, - F6AE4526145907D30022AF3C /* SenTestCase+SRTAdditions.h */, - F6AE4527145907D30022AF3C /* SenTestCase+SRTAdditions.m */, - F60CC29F14D4EA0500A005E4 /* SRTWebSocketOperation.h */, - F60CC2A014D4EA0500A005E4 /* SRTWebSocketOperation.m */, - ); - path = SRWebSocketTests; - sourceTree = ""; - }; - F6BDA808145900D200FE3253 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - F6BDA809145900D200FE3253 /* SRWebSocketTests-Info.plist */, - F6BDA80A145900D200FE3253 /* InfoPlist.strings */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ - F668C87D153E91210044DBAC /* Headers */ = { + 2D42275F1BB4358C000C1A6C /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - F668C8AA153E92F90044DBAC /* SRWebSocket.h in Headers */, + 81B22EE51CE43ECC0073C636 /* SRURLUtilities.h in Headers */, + 454FEA7F1D2570F800073768 /* SRPinningSecurityPolicy.h in Headers */, + 81B31C151CDC404100D86D43 /* SRIOConsumer.h in Headers */, + 8117C4241D3076DF00784D79 /* NSURLRequest+SRWebSocketPrivate.h in Headers */, + 81CD05FE1CEEC65D00497F47 /* NSRunLoop+SRWebSocket.h in Headers */, + 815FE7271D497D720085FDA5 /* SRConstants.h in Headers */, + 454A02D61D0FAD010060DFB2 /* SRSecurityPolicy.h in Headers */, + 81CD05D81CEEC47300497F47 /* NSURLRequest+SRWebSocket.h in Headers */, + 81900A4D1D18C9CC0015A290 /* SRLog.h in Headers */, + 81B31C1D1CDC404100D86D43 /* SRIOConsumerPool.h in Headers */, + 813364001D091E170062E28D /* SRProxyConnect.h in Headers */, + 2D42277F1BB4365C000C1A6C /* SRWebSocket.h in Headers */, + 81B31C2E1CDC406B00D86D43 /* SRHash.h in Headers */, + 811934BE1CDAF725003AB243 /* SocketRocket.h in Headers */, + 81C22BF91D1256E1007BFDDF /* SRRandom.h in Headers */, + 8117C4311D30779900784D79 /* NSRunLoop+SRWebSocketPrivate.h in Headers */, + 81C22BC31D124168007BFDDF /* SRHTTPConnectMessage.h in Headers */, + 817995871CE139700084DA37 /* SRDelegateController.h in Headers */, + 817491A91D1C8C33006E09DF /* SRMutex.h in Headers */, + 81B22EC61CE42D7E0073C636 /* SRError.h in Headers */, + 81B31C601CDC444900D86D43 /* SRRunLoopThread.h in Headers */, + F5391CBF1D2F4B4700606A81 /* SRSIMDHelpers.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; - F6B2082B1450F597009315AF /* Headers */ = { + 3345DC891C52ACD70083CCB8 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - F6A12CD1145119B700C1D980 /* SRWebSocket.h in Headers */, + 81B22EE71CE43ECC0073C636 /* SRURLUtilities.h in Headers */, + 454FEA811D2570F900073768 /* SRPinningSecurityPolicy.h in Headers */, + 81B31C171CDC404100D86D43 /* SRIOConsumer.h in Headers */, + 8117C4261D3076DF00784D79 /* NSURLRequest+SRWebSocketPrivate.h in Headers */, + 81CD06001CEEC65D00497F47 /* NSRunLoop+SRWebSocket.h in Headers */, + 815FE7291D497D720085FDA5 /* SRConstants.h in Headers */, + 454A02D81D0FAD010060DFB2 /* SRSecurityPolicy.h in Headers */, + 81CD05DA1CEEC47300497F47 /* NSURLRequest+SRWebSocket.h in Headers */, + 81900A4F1D18C9CC0015A290 /* SRLog.h in Headers */, + 81B31C1F1CDC404100D86D43 /* SRIOConsumerPool.h in Headers */, + 813364081D091E180062E28D /* SRProxyConnect.h in Headers */, + 3345DC8A1C52ACD70083CCB8 /* SRWebSocket.h in Headers */, + 81B31C301CDC406B00D86D43 /* SRHash.h in Headers */, + 811934C01CDAF726003AB243 /* SocketRocket.h in Headers */, + 81C22BFB1D1256E1007BFDDF /* SRRandom.h in Headers */, + 8117C4331D30779900784D79 /* NSRunLoop+SRWebSocketPrivate.h in Headers */, + 81C22BC51D124168007BFDDF /* SRHTTPConnectMessage.h in Headers */, + 817995891CE139700084DA37 /* SRDelegateController.h in Headers */, + 817491AB1D1C8C33006E09DF /* SRMutex.h in Headers */, + 81B22EC81CE42D7E0073C636 /* SRError.h in Headers */, + 81B31C621CDC444900D86D43 /* SRRunLoopThread.h in Headers */, + F5391CC11D2F4B4700606A81 /* SRSIMDHelpers.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F668C87D153E91210044DBAC /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 81B22EE61CE43ECC0073C636 /* SRURLUtilities.h in Headers */, + 454FEA7D1D2570F600073768 /* SRPinningSecurityPolicy.h in Headers */, + 81B31C161CDC404100D86D43 /* SRIOConsumer.h in Headers */, + 8117C4251D3076DF00784D79 /* NSURLRequest+SRWebSocketPrivate.h in Headers */, + 81CD05FF1CEEC65D00497F47 /* NSRunLoop+SRWebSocket.h in Headers */, + 815FE7281D497D720085FDA5 /* SRConstants.h in Headers */, + 454A02D71D0FAD010060DFB2 /* SRSecurityPolicy.h in Headers */, + 81CD05D91CEEC47300497F47 /* NSURLRequest+SRWebSocket.h in Headers */, + 81900A4E1D18C9CC0015A290 /* SRLog.h in Headers */, + 81B31C1E1CDC404100D86D43 /* SRIOConsumerPool.h in Headers */, + 813364041D091E170062E28D /* SRProxyConnect.h in Headers */, + F668C8AA153E92F90044DBAC /* SRWebSocket.h in Headers */, + 81B31C2F1CDC406B00D86D43 /* SRHash.h in Headers */, + 811934BC1CDAF725003AB243 /* SocketRocket.h in Headers */, + 81C22BFA1D1256E1007BFDDF /* SRRandom.h in Headers */, + 8117C4321D30779900784D79 /* NSRunLoop+SRWebSocketPrivate.h in Headers */, + 81C22BC41D124168007BFDDF /* SRHTTPConnectMessage.h in Headers */, + 817995881CE139700084DA37 /* SRDelegateController.h in Headers */, + 817491AA1D1C8C33006E09DF /* SRMutex.h in Headers */, + 81B22EC71CE42D7E0073C636 /* SRError.h in Headers */, + 81B31C611CDC444900D86D43 /* SRRunLoopThread.h in Headers */, + F5391CC01D2F4B4700606A81 /* SRSIMDHelpers.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 2D4227611BB4358C000C1A6C /* SocketRocket-iOS-Dynamic */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2D4227671BB4358C000C1A6C /* Build configuration list for PBXNativeTarget "SocketRocket-iOS-Dynamic" */; + buildPhases = ( + 2D42275D1BB4358C000C1A6C /* Sources */, + 2D42275E1BB4358C000C1A6C /* Frameworks */, + 2D42275F1BB4358C000C1A6C /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "SocketRocket-iOS-Dynamic"; + productName = "SocketRocket-iOS"; + productReference = 2D4227621BB4358C000C1A6C /* SocketRocket.framework */; + productType = "com.apple.product-type.framework"; + }; + 3345DC821C52ACD70083CCB8 /* SocketRocket-tvOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3345DC8D1C52ACD70083CCB8 /* Build configuration list for PBXNativeTarget "SocketRocket-tvOS" */; + buildPhases = ( + 3345DC831C52ACD70083CCB8 /* Sources */, + 3345DC851C52ACD70083CCB8 /* Frameworks */, + 3345DC891C52ACD70083CCB8 /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "SocketRocket-tvOS"; + productName = "SocketRocket-iOS"; + productReference = 3345DC901C52ACD70083CCB8 /* SocketRocket.framework */; + productType = "com.apple.product-type.framework"; + }; F62417E214D52F3C003CE997 /* TestChat */ = { isa = PBXNativeTarget; buildConfigurationList = F62417FC14D52F3C003CE997 /* Build configuration list for PBXNativeTarget "TestChat" */; @@ -314,69 +782,51 @@ F62417DF14D52F3C003CE997 /* Sources */, F62417E014D52F3C003CE997 /* Frameworks */, F62417E114D52F3C003CE997 /* Resources */, + 81E8A6A61D4C41E000916C7E /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( + 81E8A6A11D4C41DA00916C7E /* PBXTargetDependency */, ); name = TestChat; productName = TestChat; productReference = F62417E314D52F3C003CE997 /* TestChat.app */; productType = "com.apple.product-type.application"; }; - F668C87F153E91210044DBAC /* SocketRocketOSX */ = { + F668C87F153E91210044DBAC /* SocketRocket-macOS */ = { isa = PBXNativeTarget; - buildConfigurationList = F668C891153E91210044DBAC /* Build configuration list for PBXNativeTarget "SocketRocketOSX" */; + buildConfigurationList = F668C891153E91210044DBAC /* Build configuration list for PBXNativeTarget "SocketRocket-macOS" */; buildPhases = ( F6396B85153E67EC00345B5E /* Sources */, F668C87C153E91210044DBAC /* Frameworks */, F668C87D153E91210044DBAC /* Headers */, - F668C87E153E91210044DBAC /* Resources */, ); buildRules = ( ); dependencies = ( ); - name = SocketRocketOSX; + name = "SocketRocket-macOS"; productName = SocketRocketOSX; productReference = F668C880153E91210044DBAC /* SocketRocket.framework */; productType = "com.apple.product-type.framework"; }; - F6B2082C1450F597009315AF /* SocketRocket */ = { - isa = PBXNativeTarget; - buildConfigurationList = F6B2083A1450F597009315AF /* Build configuration list for PBXNativeTarget "SocketRocket" */; - buildPhases = ( - F6B208291450F597009315AF /* Sources */, - F6B2082A1450F597009315AF /* Frameworks */, - F6B2082B1450F597009315AF /* Headers */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = SocketRocket; - productName = SocketRocket; - productReference = F6B2082D1450F597009315AF /* libSocketRocket.a */; - productType = "com.apple.product-type.library.static"; - }; - F6BDA801145900D200FE3253 /* SRWebSocketTests */ = { + F6BDA801145900D200FE3253 /* SocketRocketTests-iOS */ = { isa = PBXNativeTarget; - buildConfigurationList = F6BDA813145900D200FE3253 /* Build configuration list for PBXNativeTarget "SRWebSocketTests" */; + buildConfigurationList = F6BDA813145900D200FE3253 /* Build configuration list for PBXNativeTarget "SocketRocketTests-iOS" */; buildPhases = ( F6BDA7FD145900D200FE3253 /* Sources */, F6BDA7FE145900D200FE3253 /* Frameworks */, F6BDA7FF145900D200FE3253 /* Resources */, - F6BDA800145900D200FE3253 /* ShellScript */, ); buildRules = ( ); dependencies = ( - F62417D614D50869003CE997 /* PBXTargetDependency */, ); - name = SRWebSocketTests; + name = "SocketRocketTests-iOS"; productName = SRWebSocketTests; - productReference = F6BDA802145900D200FE3253 /* SRWebSocketTests.octest */; - productType = "com.apple.product-type.bundle"; + productReference = F6BDA802145900D200FE3253 /* SocketRocketTests-iOS.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ @@ -384,13 +834,15 @@ F6B208241450F597009315AF /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0510; + LastTestingUpgradeCheck = 0640; + LastUpgradeCheck = 0800; }; buildConfigurationList = F6B208271450F597009315AF /* Build configuration list for PBXProject "SocketRocket" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = F6B208221450F597009315AF; @@ -398,9 +850,10 @@ projectDirPath = ""; projectRoot = ""; targets = ( - F6B2082C1450F597009315AF /* SocketRocket */, - F668C87F153E91210044DBAC /* SocketRocketOSX */, - F6BDA801145900D200FE3253 /* SRWebSocketTests */, + 2D4227611BB4358C000C1A6C /* SocketRocket-iOS-Dynamic */, + F668C87F153E91210044DBAC /* SocketRocket-macOS */, + 3345DC821C52ACD70083CCB8 /* SocketRocket-tvOS */, + F6BDA801145900D200FE3253 /* SocketRocketTests-iOS */, F62417E214D52F3C003CE997 /* TestChat */, ); }; @@ -417,40 +870,69 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - F668C87E153E91210044DBAC /* Resources */ = { + F6BDA7FF145900D200FE3253 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 8105E5281CDD98E100AA12DB /* autobahn_configuration.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; - F6BDA7FF145900D200FE3253 /* Resources */ = { - isa = PBXResourcesBuildPhase; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 2D42275D1BB4358C000C1A6C /* Sources */ = { + isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - F6BDA80C145900D200FE3253 /* InfoPlist.strings in Resources */, + 81CD05DC1CEEC47300497F47 /* NSURLRequest+SRWebSocket.m in Sources */, + F5391CC31D2F4B4700606A81 /* SRSIMDHelpers.m in Sources */, + 81B22ECA1CE42D7E0073C636 /* SRError.m in Sources */, + 81B31C191CDC404100D86D43 /* SRIOConsumer.m in Sources */, + 81C22BC71D124168007BFDDF /* SRHTTPConnectMessage.m in Sources */, + 454FEA801D2570F800073768 /* SRPinningSecurityPolicy.m in Sources */, + 454FEA851D25719900073768 /* SRSecurityPolicy.m in Sources */, + 815FE72B1D497D720085FDA5 /* SRConstants.m in Sources */, + 81CD06021CEEC65D00497F47 /* NSRunLoop+SRWebSocket.m in Sources */, + 2D4227851BB43734000C1A6C /* SRWebSocket.m in Sources */, + 81C22BFD1D1256E1007BFDDF /* SRRandom.m in Sources */, + 81B31C211CDC404100D86D43 /* SRIOConsumerPool.m in Sources */, + 81B22EE91CE43ECC0073C636 /* SRURLUtilities.m in Sources */, + 8133640C1D091E1B0062E28D /* SRProxyConnect.m in Sources */, + 817491AD1D1C8C33006E09DF /* SRMutex.m in Sources */, + 81B31C641CDC444900D86D43 /* SRRunLoopThread.m in Sources */, + 81900A511D18C9CC0015A290 /* SRLog.m in Sources */, + 81B31C321CDC406B00D86D43 /* SRHash.m in Sources */, + 8179958B1CE139700084DA37 /* SRDelegateController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - F6BDA800145900D200FE3253 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; + 3345DC831C52ACD70083CCB8 /* Sources */ = { + isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - ); - inputPaths = ( - ); - outputPaths = ( + 81CD05DE1CEEC47300497F47 /* NSURLRequest+SRWebSocket.m in Sources */, + F5391CC51D2F4B4700606A81 /* SRSIMDHelpers.m in Sources */, + 81B22ECC1CE42D7E0073C636 /* SRError.m in Sources */, + 81B31C1B1CDC404100D86D43 /* SRIOConsumer.m in Sources */, + 81C22BC91D124168007BFDDF /* SRHTTPConnectMessage.m in Sources */, + 454FEA821D2570F900073768 /* SRPinningSecurityPolicy.m in Sources */, + 454FEA871D25719A00073768 /* SRSecurityPolicy.m in Sources */, + 815FE72D1D497D720085FDA5 /* SRConstants.m in Sources */, + 81CD06041CEEC65D00497F47 /* NSRunLoop+SRWebSocket.m in Sources */, + 3345DC841C52ACD70083CCB8 /* SRWebSocket.m in Sources */, + 81C22BFF1D1256E1007BFDDF /* SRRandom.m in Sources */, + 81B31C231CDC404100D86D43 /* SRIOConsumerPool.m in Sources */, + 81B22EEB1CE43ECC0073C636 /* SRURLUtilities.m in Sources */, + 8133640F1D091E1C0062E28D /* SRProxyConnect.m in Sources */, + 817491AF1D1C8C33006E09DF /* SRMutex.m in Sources */, + 81B31C661CDC444900D86D43 /* SRRunLoopThread.m in Sources */, + 81900A531D18C9CC0015A290 /* SRLog.m in Sources */, + 81B31C341CDC406B00D86D43 /* SRHash.m in Sources */, + 8179958D1CE139700084DA37 /* SRDelegateController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n"; }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ F62417DF14D52F3C003CE997 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -466,15 +948,25 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 81CD05DD1CEEC47300497F47 /* NSURLRequest+SRWebSocket.m in Sources */, + F5391CC41D2F4B4700606A81 /* SRSIMDHelpers.m in Sources */, + 81B22ECB1CE42D7E0073C636 /* SRError.m in Sources */, + 81B31C1A1CDC404100D86D43 /* SRIOConsumer.m in Sources */, + 81C22BC81D124168007BFDDF /* SRHTTPConnectMessage.m in Sources */, + 454FEA7E1D2570F600073768 /* SRPinningSecurityPolicy.m in Sources */, + 454FEA861D25719A00073768 /* SRSecurityPolicy.m in Sources */, + 815FE72C1D497D720085FDA5 /* SRConstants.m in Sources */, + 81CD06031CEEC65D00497F47 /* NSRunLoop+SRWebSocket.m in Sources */, F6396B86153E67EC00345B5E /* SRWebSocket.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - F6B208291450F597009315AF /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - F6A12CD2145119B700C1D980 /* SRWebSocket.m in Sources */, + 81C22BFE1D1256E1007BFDDF /* SRRandom.m in Sources */, + 81B31C221CDC404100D86D43 /* SRIOConsumerPool.m in Sources */, + 81B22EEA1CE43ECC0073C636 /* SRURLUtilities.m in Sources */, + 8133640E1D091E1B0062E28D /* SRProxyConnect.m in Sources */, + 817491AE1D1C8C33006E09DF /* SRMutex.m in Sources */, + 81B31C651CDC444900D86D43 /* SRRunLoopThread.m in Sources */, + 81900A521D18C9CC0015A290 /* SRLog.m in Sources */, + 81B31C331CDC406B00D86D43 /* SRHash.m in Sources */, + 8179958C1CE139700084DA37 /* SRDelegateController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -482,19 +974,20 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - F6BDA8161459016900FE3253 /* SRTAutobahnTests.m in Sources */, - F6AE4528145907D30022AF3C /* SenTestCase+SRTAdditions.m in Sources */, - F60CC2A114D4EA0500A005E4 /* SRTWebSocketOperation.m in Sources */, + 8105E4AE1CDD6E6200AA12DB /* SRAutobahnOperation.m in Sources */, + 817996801CE184F40084DA37 /* SRAutobahnUtilities.m in Sources */, + 8105E4801CDD67B400AA12DB /* SRAutobahnTests.m in Sources */, + 8105E4821CDD67BD00AA12DB /* SRTWebSocketOperation.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - F62417D614D50869003CE997 /* PBXTargetDependency */ = { + 81E8A6A11D4C41DA00916C7E /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = F6B2082C1450F597009315AF /* SocketRocket */; - targetProxy = F62417D514D50869003CE997 /* PBXContainerItemProxy */; + target = 2D4227611BB4358C000C1A6C /* SocketRocket-iOS-Dynamic */; + targetProxy = 81E8A6A01D4C41DA00916C7E /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ @@ -515,266 +1008,122 @@ name = MainStoryboard.storyboard; sourceTree = ""; }; - F6BDA80A145900D200FE3253 /* InfoPlist.strings */ = { - isa = PBXVariantGroup; - children = ( - F6BDA80B145900D200FE3253 /* en */, - ); - name = InfoPlist.strings; - sourceTree = ""; - }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ - F62417FD14D52F3C003CE997 /* Debug */ = { + 2D4227681BB4358C000C1A6C /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 81D647591D2CA6A100690609 /* SocketRocket-iOS-Dynamic.xcconfig */; buildSettings = { - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "\"$(DEVELOPER_FRAMEWORKS_DIR)\"", - ); - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "TestChat/TestChat-Prefix.pch"; - INFOPLIST_FILE = "TestChat/TestChat-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 5.0; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = "$(TARGET_NAME)"; - TARGETED_DEVICE_FAMILY = "1,2"; - WRAPPER_EXTENSION = app; + MTL_ENABLE_DEBUG_INFO = YES; }; name = Debug; }; - F62417FE14D52F3C003CE997 /* Release */ = { + 2D4227691BB4358C000C1A6C /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 81D647591D2CA6A100690609 /* SocketRocket-iOS-Dynamic.xcconfig */; buildSettings = { - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "\"$(DEVELOPER_FRAMEWORKS_DIR)\"", - ); - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "TestChat/TestChat-Prefix.pch"; - INFOPLIST_FILE = "TestChat/TestChat-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 5.0; - OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = "$(TARGET_NAME)"; - TARGETED_DEVICE_FAMILY = "1,2"; - WRAPPER_EXTENSION = app; + MTL_ENABLE_DEBUG_INFO = NO; }; name = Release; }; - F668C892153E91210044DBAC /* Debug */ = { + 3345DC8E1C52ACD70083CCB8 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 81D6475C1D2CA6A100690609 /* SocketRocket-tvOS.xcconfig */; buildSettings = { - COMBINE_HIDPI_IMAGES = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "\"$(SYSTEM_APPS_DIR)/Xcode.app/Contents/Developer/Library/Frameworks\"", - ); - FRAMEWORK_VERSION = A; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "SocketRocket/SocketRocket-Prefix.pch"; - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - INFOPLIST_FILE = "SocketRocketOSX/SocketRocketOSX-Info.plist"; - LD_DYLIB_INSTALL_NAME = "@executable_path/../Frameworks/$(EXECUTABLE_PATH)"; - ONLY_ACTIVE_ARCH = YES; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = SocketRocket; - SDKROOT = macosx; - WRAPPER_EXTENSION = framework; + MTL_ENABLE_DEBUG_INFO = YES; + TARGETED_DEVICE_FAMILY = 3; }; name = Debug; }; - F668C893153E91210044DBAC /* Release */ = { + 3345DC8F1C52ACD70083CCB8 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 81D6475C1D2CA6A100690609 /* SocketRocket-tvOS.xcconfig */; buildSettings = { - COMBINE_HIDPI_IMAGES = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "\"$(SYSTEM_APPS_DIR)/Xcode.app/Contents/Developer/Library/Frameworks\"", - ); - FRAMEWORK_VERSION = A; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "SocketRocket/SocketRocket-Prefix.pch"; - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - INFOPLIST_FILE = "SocketRocketOSX/SocketRocketOSX-Info.plist"; - LD_DYLIB_INSTALL_NAME = "@executable_path/../Frameworks/$(EXECUTABLE_PATH)"; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = SocketRocket; - SDKROOT = macosx; - WRAPPER_EXTENSION = framework; + MTL_ENABLE_DEBUG_INFO = NO; + TARGETED_DEVICE_FAMILY = 3; }; name = Release; }; - F6B208381450F597009315AF /* Debug */ = { + F62417FD14D52F3C003CE997 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 81E8A69A1D4C417A00916C7E /* TestChat-iOS.xcconfig */; buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - GCC_DYNAMIC_NO_PIC = NO; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_VERSION = com.apple.compilers.llvm.clang.1_0; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_MISSING_NEWLINE = YES; - GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_SIGN_COMPARE = YES; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 5.1; - ONLY_ACTIVE_ARCH = NO; - RUN_CLANG_STATIC_ANALYZER = YES; - SDKROOT = iphoneos; }; name = Debug; }; - F6B208391450F597009315AF /* Release */ = { + F62417FE14D52F3C003CE997 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 81E8A69A1D4C417A00916C7E /* TestChat-iOS.xcconfig */; buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = YES; - GCC_PREPROCESSOR_DEFINITIONS = ( - NDEBUG, - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = YES; - GCC_VERSION = com.apple.compilers.llvm.clang.1_0; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_MISSING_NEWLINE = YES; - GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_SIGN_COMPARE = YES; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 5.1; - RUN_CLANG_STATIC_ANALYZER = YES; - SDKROOT = iphoneos; - VALIDATE_PRODUCT = YES; }; name = Release; }; - F6B2083B1450F597009315AF /* Debug */ = { + F668C892153E91210044DBAC /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 81D6475B1D2CA6A100690609 /* SocketRocket-macOS.xcconfig */; buildSettings = { - CLANG_ENABLE_OBJC_ARC = YES; - DSTROOT = /tmp/SocketRocket.dst; - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "SocketRocket/SocketRocket-Prefix.pch"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "\"$(DEVELOPER_DIR)/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.1.sdk/usr/lib/system\"", - "\"$(DEVELOPER_DIR)/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.1.sdk/usr/lib\"", - ); - OTHER_LDFLAGS = "-Licucore"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; + COMBINE_HIDPI_IMAGES = YES; }; name = Debug; }; - F6B2083C1450F597009315AF /* Release */ = { + F668C893153E91210044DBAC /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 81D6475B1D2CA6A100690609 /* SocketRocket-macOS.xcconfig */; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + }; + name = Release; + }; + F6B208381450F597009315AF /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 81D647561D2CA6A100690609 /* Debug.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + F6B208391450F597009315AF /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 81D647571D2CA6A100690609 /* Release.xcconfig */; buildSettings = { - CLANG_ENABLE_OBJC_ARC = YES; - DSTROOT = /tmp/SocketRocket.dst; - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "SocketRocket/SocketRocket-Prefix.pch"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "\"$(DEVELOPER_DIR)/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.1.sdk/usr/lib/system\"", - "\"$(DEVELOPER_DIR)/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.1.sdk/usr/lib\"", - ); - OTHER_LDFLAGS = "-Licucore"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; }; name = Release; }; F6BDA811145900D200FE3253 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 81D6475D1D2CA6A100690609 /* SocketRocketTests-iOS.xcconfig */; buildSettings = { - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(DEVELOPER_LIBRARY_DIR)/Frameworks", - "\"$(DEVELOPER_DIR)/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator5.0.sdk/System/Library/Frameworks\"", - ); - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "SRWebSocketTests/SRWebSocketTests-Prefix.pch"; - INFOPLIST_FILE = "SRWebSocketTests/SRWebSocketTests-Info.plist"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "\"$(DEVELOPER_DIR)/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator5.0.sdk/usr/lib/system\"", - "\"$(DEVELOPER_DIR)/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator5.0.sdk/usr/lib\"", - ); - OTHER_LDFLAGS = ( - "-all_load", - "-ObjC", - ); - PRODUCT_NAME = "$(TARGET_NAME)"; - WRAPPER_EXTENSION = octest; }; name = Debug; }; F6BDA812145900D200FE3253 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 81D6475D1D2CA6A100690609 /* SocketRocketTests-iOS.xcconfig */; buildSettings = { - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(DEVELOPER_LIBRARY_DIR)/Frameworks", - "\"$(DEVELOPER_DIR)/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator5.0.sdk/System/Library/Frameworks\"", - ); - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "SRWebSocketTests/SRWebSocketTests-Prefix.pch"; - INFOPLIST_FILE = "SRWebSocketTests/SRWebSocketTests-Info.plist"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "\"$(DEVELOPER_DIR)/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator5.0.sdk/usr/lib/system\"", - "\"$(DEVELOPER_DIR)/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator5.0.sdk/usr/lib\"", - ); - OTHER_LDFLAGS = ( - "-all_load", - "-ObjC", - ); - PRODUCT_NAME = "$(TARGET_NAME)"; - WRAPPER_EXTENSION = octest; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 2D4227671BB4358C000C1A6C /* Build configuration list for PBXNativeTarget "SocketRocket-iOS-Dynamic" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2D4227681BB4358C000C1A6C /* Debug */, + 2D4227691BB4358C000C1A6C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 3345DC8D1C52ACD70083CCB8 /* Build configuration list for PBXNativeTarget "SocketRocket-tvOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3345DC8E1C52ACD70083CCB8 /* Debug */, + 3345DC8F1C52ACD70083CCB8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; F62417FC14D52F3C003CE997 /* Build configuration list for PBXNativeTarget "TestChat" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -784,7 +1133,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - F668C891153E91210044DBAC /* Build configuration list for PBXNativeTarget "SocketRocketOSX" */ = { + F668C891153E91210044DBAC /* Build configuration list for PBXNativeTarget "SocketRocket-macOS" */ = { isa = XCConfigurationList; buildConfigurations = ( F668C892153E91210044DBAC /* Debug */, @@ -802,16 +1151,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - F6B2083A1450F597009315AF /* Build configuration list for PBXNativeTarget "SocketRocket" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - F6B2083B1450F597009315AF /* Debug */, - F6B2083C1450F597009315AF /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - F6BDA813145900D200FE3253 /* Build configuration list for PBXNativeTarget "SRWebSocketTests" */ = { + F6BDA813145900D200FE3253 /* Build configuration list for PBXNativeTarget "SocketRocketTests-iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( F6BDA811145900D200FE3253 /* Debug */, diff --git a/SocketRocket.xcodeproj/xcshareddata/xcschemes/SocketRocket-iOS-Dynamic.xcscheme b/SocketRocket.xcodeproj/xcshareddata/xcschemes/SocketRocket-iOS-Dynamic.xcscheme new file mode 100644 index 000000000..5895fd73b --- /dev/null +++ b/SocketRocket.xcodeproj/xcshareddata/xcschemes/SocketRocket-iOS-Dynamic.xcscheme @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SocketRocket.xcodeproj/xcshareddata/xcschemes/SocketRocket.xcscheme b/SocketRocket.xcodeproj/xcshareddata/xcschemes/SocketRocket-iOS.xcscheme similarity index 58% rename from SocketRocket.xcodeproj/xcshareddata/xcschemes/SocketRocket.xcscheme rename to SocketRocket.xcodeproj/xcshareddata/xcschemes/SocketRocket-iOS.xcscheme index 212a79c37..572074155 100644 --- a/SocketRocket.xcodeproj/xcshareddata/xcschemes/SocketRocket.xcscheme +++ b/SocketRocket.xcodeproj/xcshareddata/xcschemes/SocketRocket-iOS.xcscheme @@ -1,6 +1,6 @@ @@ -25,35 +25,34 @@ buildForRunning = "NO" buildForProfiling = "NO" buildForArchiving = "NO" - buildForAnalyzing = "NO"> + buildForAnalyzing = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES"> + scriptText = "PIDFILE=$TMPDIR/sr_test_server.pid if [ -r $PIDFILE ]; then EXISTING_PID=`cat $PIDFILE` echo "Killing Dangling Autobahn Server PID:" $EXISTING_PID kill $EXISTING_PID || true rm $PIDFILE fi pushd $PROJECT_DIR source .env/bin/activate nohup ./TestSupport/run_test_server.sh & echo $! > $PIDFILE popd "> @@ -65,87 +64,76 @@ ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction"> + scriptText = "PIDFILE=$TMPDIR/sr_test_server.pid if [ -r $PIDFILE ]; then EXISTING_PID=`cat $PIDFILE` echo "Killing SR TestServer PID:" $EXISTING_PID kill $EXISTING_PID rm $PIDFILE fi "> + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + diff --git a/SocketRocket.xcodeproj/xcshareddata/xcschemes/SocketRocket-macOS.xcscheme b/SocketRocket.xcodeproj/xcshareddata/xcschemes/SocketRocket-macOS.xcscheme new file mode 100644 index 000000000..5cce30b69 --- /dev/null +++ b/SocketRocket.xcodeproj/xcshareddata/xcschemes/SocketRocket-macOS.xcscheme @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SocketRocket.xcodeproj/xcshareddata/xcschemes/SocketRocketOSX.xcscheme b/SocketRocket.xcodeproj/xcshareddata/xcschemes/SocketRocket-tvOS.xcscheme similarity index 73% rename from SocketRocket.xcodeproj/xcshareddata/xcschemes/SocketRocketOSX.xcscheme rename to SocketRocket.xcodeproj/xcshareddata/xcschemes/SocketRocket-tvOS.xcscheme index 43c8438ff..b7abda550 100644 --- a/SocketRocket.xcodeproj/xcshareddata/xcschemes/SocketRocketOSX.xcscheme +++ b/SocketRocket.xcodeproj/xcshareddata/xcschemes/SocketRocket-tvOS.xcscheme @@ -1,6 +1,6 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> - - + + + + + shouldUseLaunchSchemeArgsEnv = "NO"> + + + + + + @@ -73,10 +85,10 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -38,17 +38,21 @@ ReferencedContainer = "container:SocketRocket.xcodeproj"> + + - + @@ -81,12 +85,13 @@ - + + +#import + +NS_ASSUME_NONNULL_BEGIN + +#if OBJC_BOOL_IS_BOOL + +struct SRDelegateAvailableMethods { + BOOL didReceiveMessage : 1; + BOOL didReceiveMessageWithString : 1; + BOOL didReceiveMessageWithData : 1; + BOOL didOpen : 1; + BOOL didFailWithError : 1; + BOOL didCloseWithCode : 1; + BOOL didReceivePing : 1; + BOOL didReceivePong : 1; + BOOL shouldConvertTextFrameToString : 1; +}; + +#else + +struct SRDelegateAvailableMethods { + BOOL didReceiveMessage; + BOOL didReceiveMessageWithString; + BOOL didReceiveMessageWithData; + BOOL didOpen; + BOOL didFailWithError; + BOOL didCloseWithCode; + BOOL didReceivePing; + BOOL didReceivePong; + BOOL shouldConvertTextFrameToString; +}; + +#endif + +typedef struct SRDelegateAvailableMethods SRDelegateAvailableMethods; + +typedef void(^SRDelegateBlock)(id _Nullable delegate, SRDelegateAvailableMethods availableMethods); + +@interface SRDelegateController : NSObject + +@property (nonatomic, weak) id delegate; +@property (atomic, readonly) SRDelegateAvailableMethods availableDelegateMethods; + +@property (nullable, nonatomic, strong) dispatch_queue_t dispatchQueue; +@property (nullable, nonatomic, strong) NSOperationQueue *operationQueue; + +///-------------------------------------- +#pragma mark - Perform +///-------------------------------------- + +- (void)performDelegateBlock:(SRDelegateBlock)block; +- (void)performDelegateQueueBlock:(dispatch_block_t)block; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/Internal/Delegate/SRDelegateController.m b/SocketRocket/Internal/Delegate/SRDelegateController.m new file mode 100644 index 000000000..f20c9d504 --- /dev/null +++ b/SocketRocket/Internal/Delegate/SRDelegateController.m @@ -0,0 +1,138 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "SRDelegateController.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface SRDelegateController () + +@property (nonatomic, strong, readonly) dispatch_queue_t accessQueue; + +@property (atomic, assign, readwrite) SRDelegateAvailableMethods availableDelegateMethods; + +@end + +@implementation SRDelegateController + +@synthesize delegate = _delegate; +@synthesize dispatchQueue = _dispatchQueue; +@synthesize operationQueue = _operationQueue; + +///-------------------------------------- +#pragma mark - Init +///-------------------------------------- + +- (instancetype)init +{ + self = [super init]; + if (!self) return self; + + _accessQueue = dispatch_queue_create("com.facebook.socketrocket.delegate.access", DISPATCH_QUEUE_CONCURRENT); + _dispatchQueue = dispatch_get_main_queue(); + + return self; +} + +///-------------------------------------- +#pragma mark - Accessors +///-------------------------------------- + +- (void)setDelegate:(id _Nullable)delegate +{ + dispatch_barrier_async(self.accessQueue, ^{ + self->_delegate = delegate; + + self.availableDelegateMethods = (SRDelegateAvailableMethods){ + .didReceiveMessage = [delegate respondsToSelector:@selector(webSocket:didReceiveMessage:)], + .didReceiveMessageWithString = [delegate respondsToSelector:@selector(webSocket:didReceiveMessageWithString:)], + .didReceiveMessageWithData = [delegate respondsToSelector:@selector(webSocket:didReceiveMessageWithData:)], + .didOpen = [delegate respondsToSelector:@selector(webSocketDidOpen:)], + .didFailWithError = [delegate respondsToSelector:@selector(webSocket:didFailWithError:)], + .didCloseWithCode = [delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)], + .didReceivePing = [delegate respondsToSelector:@selector(webSocket:didReceivePingWithData:)], + .didReceivePong = [delegate respondsToSelector:@selector(webSocket:didReceivePong:)], + .shouldConvertTextFrameToString = [delegate respondsToSelector:@selector(webSocketShouldConvertTextFrameToString:)] + }; + }); +} + +- (id _Nullable)delegate +{ + __block id delegate = nil; + dispatch_sync(self.accessQueue, ^{ + delegate = self->_delegate; + }); + return delegate; +} + +- (void)setDispatchQueue:(dispatch_queue_t _Nullable)queue +{ + dispatch_barrier_async(self.accessQueue, ^{ + self->_dispatchQueue = queue ?: dispatch_get_main_queue(); + self->_operationQueue = nil; + }); +} + +- (dispatch_queue_t _Nullable)dispatchQueue +{ + __block dispatch_queue_t queue = nil; + dispatch_sync(self.accessQueue, ^{ + queue = self->_dispatchQueue; + }); + return queue; +} + +- (void)setOperationQueue:(NSOperationQueue *_Nullable)queue +{ + dispatch_barrier_async(self.accessQueue, ^{ + self->_dispatchQueue = queue ? nil : dispatch_get_main_queue(); + self->_operationQueue = queue; + }); +} + +- (NSOperationQueue *_Nullable)operationQueue +{ + __block NSOperationQueue *queue = nil; + dispatch_sync(self.accessQueue, ^{ + queue = self->_operationQueue; + }); + return queue; +} + +///-------------------------------------- +#pragma mark - Perform +///-------------------------------------- + +- (void)performDelegateBlock:(SRDelegateBlock)block +{ + __block __strong id delegate = nil; + __block SRDelegateAvailableMethods availableMethods; + dispatch_sync(self.accessQueue, ^{ + delegate = self->_delegate; // Not `OK` to go through `self`, since queue sync. + availableMethods = self.availableDelegateMethods; // `OK` to call through `self`, since no queue sync. + }); + [self performDelegateQueueBlock:^{ + block(delegate, availableMethods); + }]; +} + +- (void)performDelegateQueueBlock:(dispatch_block_t)block +{ + dispatch_queue_t dispatchQueue = self.dispatchQueue; + if (dispatchQueue) { + dispatch_async(dispatchQueue, block); + } else { + [self.operationQueue addOperationWithBlock:block]; + } +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/Internal/IOConsumer/SRIOConsumer.h b/SocketRocket/Internal/IOConsumer/SRIOConsumer.h new file mode 100644 index 000000000..6b02a3b1d --- /dev/null +++ b/SocketRocket/Internal/IOConsumer/SRIOConsumer.h @@ -0,0 +1,40 @@ +// +// Copyright 2012 Square Inc. +// Portions Copyright (c) 2016-present, Facebook, Inc. +// +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +@class SRWebSocket; // TODO: (nlutsenko) Remove dependency on SRWebSocket here. + +// Returns number of bytes consumed. Returning 0 means you didn't match. +// Sends bytes to callback handler; +typedef size_t (^stream_scanner)(NSData *collected_data); +typedef void (^data_callback)(SRWebSocket *webSocket, NSData *data); + +@interface SRIOConsumer : NSObject { + stream_scanner _scanner; + data_callback _handler; + size_t _bytesNeeded; + BOOL _readToCurrentFrame; + BOOL _unmaskBytes; +} +@property (nonatomic, copy, readonly) stream_scanner consumer; +@property (nonatomic, copy, readonly) data_callback handler; +@property (nonatomic, assign) size_t bytesNeeded; +@property (nonatomic, assign, readonly) BOOL readToCurrentFrame; +@property (nonatomic, assign, readonly) BOOL unmaskBytes; + +- (void)resetWithScanner:(stream_scanner)scanner + handler:(data_callback)handler + bytesNeeded:(size_t)bytesNeeded + readToCurrentFrame:(BOOL)readToCurrentFrame + unmaskBytes:(BOOL)unmaskBytes; + +@end diff --git a/SocketRocket/Internal/IOConsumer/SRIOConsumer.m b/SocketRocket/Internal/IOConsumer/SRIOConsumer.m new file mode 100644 index 000000000..8b17e3e8d --- /dev/null +++ b/SocketRocket/Internal/IOConsumer/SRIOConsumer.m @@ -0,0 +1,36 @@ +// +// Copyright 2012 Square Inc. +// Portions Copyright (c) 2016-present, Facebook, Inc. +// +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "SRIOConsumer.h" + +@implementation SRIOConsumer + +@synthesize bytesNeeded = _bytesNeeded; +@synthesize consumer = _scanner; +@synthesize handler = _handler; +@synthesize readToCurrentFrame = _readToCurrentFrame; +@synthesize unmaskBytes = _unmaskBytes; + +- (void)resetWithScanner:(stream_scanner)scanner + handler:(data_callback)handler + bytesNeeded:(size_t)bytesNeeded + readToCurrentFrame:(BOOL)readToCurrentFrame + unmaskBytes:(BOOL)unmaskBytes +{ + _scanner = [scanner copy]; + _handler = [handler copy]; + _bytesNeeded = bytesNeeded; + _readToCurrentFrame = readToCurrentFrame; + _unmaskBytes = unmaskBytes; + assert(_scanner || _bytesNeeded); +} + +@end diff --git a/SocketRocket/Internal/IOConsumer/SRIOConsumerPool.h b/SocketRocket/Internal/IOConsumer/SRIOConsumerPool.h new file mode 100644 index 000000000..1e7ad3202 --- /dev/null +++ b/SocketRocket/Internal/IOConsumer/SRIOConsumerPool.h @@ -0,0 +1,28 @@ +// +// Copyright 2012 Square Inc. +// Portions Copyright (c) 2016-present, Facebook, Inc. +// +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +#import "SRIOConsumer.h" // TODO: (nlutsenko) Convert to @class and constants file for block types + +// This class is not thread-safe, and is expected to always be run on the same queue. +@interface SRIOConsumerPool : NSObject + +- (instancetype)initWithBufferCapacity:(NSUInteger)poolSize; + +- (SRIOConsumer *)consumerWithScanner:(stream_scanner)scanner + handler:(data_callback)handler + bytesNeeded:(size_t)bytesNeeded + readToCurrentFrame:(BOOL)readToCurrentFrame + unmaskBytes:(BOOL)unmaskBytes; +- (void)returnConsumer:(SRIOConsumer *)consumer; + +@end diff --git a/SocketRocket/Internal/IOConsumer/SRIOConsumerPool.m b/SocketRocket/Internal/IOConsumer/SRIOConsumerPool.m new file mode 100644 index 000000000..33a4af9f2 --- /dev/null +++ b/SocketRocket/Internal/IOConsumer/SRIOConsumerPool.m @@ -0,0 +1,64 @@ +// +// Copyright 2012 Square Inc. +// Portions Copyright (c) 2016-present, Facebook, Inc. +// +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "SRIOConsumerPool.h" + +@implementation SRIOConsumerPool { + NSUInteger _poolSize; + NSMutableArray *_bufferedConsumers; +} + +- (instancetype)initWithBufferCapacity:(NSUInteger)poolSize +{ + self = [super init]; + if (self) { + _poolSize = poolSize; + _bufferedConsumers = [NSMutableArray arrayWithCapacity:poolSize]; + } + return self; +} + +- (instancetype)init +{ + return [self initWithBufferCapacity:8]; +} + +- (SRIOConsumer *)consumerWithScanner:(stream_scanner)scanner + handler:(data_callback)handler + bytesNeeded:(size_t)bytesNeeded + readToCurrentFrame:(BOOL)readToCurrentFrame + unmaskBytes:(BOOL)unmaskBytes +{ + SRIOConsumer *consumer = nil; + if (_bufferedConsumers.count) { + consumer = [_bufferedConsumers lastObject]; + [_bufferedConsumers removeLastObject]; + } else { + consumer = [[SRIOConsumer alloc] init]; + } + + [consumer resetWithScanner:scanner + handler:handler + bytesNeeded:bytesNeeded + readToCurrentFrame:readToCurrentFrame + unmaskBytes:unmaskBytes]; + + return consumer; +} + +- (void)returnConsumer:(SRIOConsumer *)consumer +{ + if (_bufferedConsumers.count < _poolSize) { + [_bufferedConsumers addObject:consumer]; + } +} + +@end diff --git a/SocketRocket/Internal/NSRunLoop+SRWebSocketPrivate.h b/SocketRocket/Internal/NSRunLoop+SRWebSocketPrivate.h new file mode 100644 index 000000000..098f7a818 --- /dev/null +++ b/SocketRocket/Internal/NSRunLoop+SRWebSocketPrivate.h @@ -0,0 +1,13 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +// Empty function that force links the object file for the category. +extern void import_NSRunLoop_SRWebSocket(void); diff --git a/SocketRocket/Internal/NSURLRequest+SRWebSocketPrivate.h b/SocketRocket/Internal/NSURLRequest+SRWebSocketPrivate.h new file mode 100644 index 000000000..b09dde420 --- /dev/null +++ b/SocketRocket/Internal/NSURLRequest+SRWebSocketPrivate.h @@ -0,0 +1,13 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +// Empty function that force links the object file for the category. +extern void import_NSURLRequest_SRWebSocket(void); diff --git a/SocketRocket/Internal/Proxy/SRProxyConnect.h b/SocketRocket/Internal/Proxy/SRProxyConnect.h new file mode 100644 index 000000000..e947c4825 --- /dev/null +++ b/SocketRocket/Internal/Proxy/SRProxyConnect.h @@ -0,0 +1,26 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef void(^SRProxyConnectCompletion)(NSError *_Nullable error, + NSInputStream *_Nullable readStream, + NSOutputStream *_Nullable writeStream); + +@interface SRProxyConnect : NSObject + +- (instancetype)initWithURL:(NSURL *)url; + +- (void)openNetworkStreamWithCompletion:(SRProxyConnectCompletion)completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/Internal/Proxy/SRProxyConnect.m b/SocketRocket/Internal/Proxy/SRProxyConnect.m new file mode 100644 index 000000000..fcb4c866e --- /dev/null +++ b/SocketRocket/Internal/Proxy/SRProxyConnect.m @@ -0,0 +1,485 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "SRProxyConnect.h" + +#import "NSRunLoop+SRWebSocket.h" +#import "SRConstants.h" +#import "SRError.h" +#import "SRLog.h" +#import "SRURLUtilities.h" + +@interface SRProxyConnect() + +@property (nonatomic, strong) NSURL *url; +@property (nonatomic, strong) NSInputStream *inputStream; +@property (nonatomic, strong) NSOutputStream *outputStream; + +@end + +@implementation SRProxyConnect +{ + SRProxyConnectCompletion _completion; + + NSString *_httpProxyHost; + uint32_t _httpProxyPort; + + CFHTTPMessageRef _receivedHTTPHeaders; + + NSString *_socksProxyHost; + uint32_t _socksProxyPort; + NSString *_socksProxyUsername; + NSString *_socksProxyPassword; + + BOOL _connectionRequiresSSL; + + NSMutableArray *_inputQueue; + dispatch_queue_t _writeQueue; +} + +///-------------------------------------- +#pragma mark - Init +///-------------------------------------- + +-(instancetype)initWithURL:(NSURL *)url +{ + self = [super init]; + if (!self) return self; + + _url = url; + _connectionRequiresSSL = SRURLRequiresSSL(url); + + _writeQueue = dispatch_queue_create("com.facebook.socketrocket.proxyconnect.write", DISPATCH_QUEUE_SERIAL); + _inputQueue = [NSMutableArray arrayWithCapacity:2]; + + return self; +} + +- (void)dealloc +{ + // If we get deallocated before the socket open finishes - we need to cleanup everything. + + [self.inputStream removeFromRunLoop:[NSRunLoop SR_networkRunLoop] forMode:NSDefaultRunLoopMode]; + self.inputStream.delegate = nil; + [self.inputStream close]; + self.inputStream = nil; + + self.outputStream.delegate = nil; + [self.outputStream close]; + self.outputStream = nil; +} + +///-------------------------------------- +#pragma mark - Open +///-------------------------------------- + +- (void)openNetworkStreamWithCompletion:(SRProxyConnectCompletion)completion +{ + _completion = completion; + [self _configureProxy]; +} + +///-------------------------------------- +#pragma mark - Flow +///-------------------------------------- + +- (void)_didConnect +{ + SRDebugLog(@"_didConnect, return streams"); + if (_connectionRequiresSSL) { + if (_httpProxyHost) { + // Must set the real peer name before turning on SSL + SRDebugLog(@"proxy set peer name to real host %@", self.url.host); + [self.outputStream setProperty:self.url.host forKey:@"_kCFStreamPropertySocketPeerName"]; + } + } + if (_receivedHTTPHeaders) { + CFRelease(_receivedHTTPHeaders); + _receivedHTTPHeaders = NULL; + } + + NSInputStream *inputStream = self.inputStream; + NSOutputStream *outputStream = self.outputStream; + + self.inputStream = nil; + self.outputStream = nil; + + [inputStream removeFromRunLoop:[NSRunLoop SR_networkRunLoop] forMode:NSDefaultRunLoopMode]; + inputStream.delegate = nil; + outputStream.delegate = nil; + + _completion(nil, inputStream, outputStream); +} + +- (void)_failWithError:(NSError *)error +{ + SRDebugLog(@"_failWithError, return error"); + if (!error) { + error = SRHTTPErrorWithCodeDescription(500, 2132,@"Proxy Error"); + } + + if (_receivedHTTPHeaders) { + CFRelease(_receivedHTTPHeaders); + _receivedHTTPHeaders = NULL; + } + + self.inputStream.delegate = nil; + self.outputStream.delegate = nil; + + [self.inputStream removeFromRunLoop:[NSRunLoop SR_networkRunLoop] + forMode:NSDefaultRunLoopMode]; + [self.inputStream close]; + [self.outputStream close]; + self.inputStream = nil; + self.outputStream = nil; + _completion(error, nil, nil); +} + +// get proxy setting from device setting +- (void)_configureProxy +{ + SRDebugLog(@"configureProxy"); + NSDictionary *proxySettings = CFBridgingRelease(CFNetworkCopySystemProxySettings()); + + // CFNetworkCopyProxiesForURL doesn't understand ws:// or wss:// + NSURL *httpURL; + if (_connectionRequiresSSL) { + httpURL = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@", _url.host]]; + } else { + httpURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@", _url.host]]; + } + + NSArray *proxies = CFBridgingRelease(CFNetworkCopyProxiesForURL((__bridge CFURLRef)httpURL, (__bridge CFDictionaryRef)proxySettings)); + if (proxies.count == 0) { + SRDebugLog(@"configureProxy no proxies"); + [self _openConnection]; + return; // no proxy + } + NSDictionary *settings = [proxies objectAtIndex:0]; + NSString *proxyType = settings[(NSString *)kCFProxyTypeKey]; + if ([proxyType isEqualToString:(NSString *)kCFProxyTypeAutoConfigurationURL]) { + NSURL *pacURL = settings[(NSString *)kCFProxyAutoConfigurationURLKey]; + if (pacURL) { + [self _fetchPAC:pacURL withProxySettings:proxySettings]; + return; + } + } + if ([proxyType isEqualToString:(__bridge NSString *)kCFProxyTypeAutoConfigurationJavaScript]) { + NSString *script = settings[(__bridge NSString *)kCFProxyAutoConfigurationJavaScriptKey]; + if (script) { + [self _runPACScript:script withProxySettings:proxySettings]; + return; + } + } + [self _readProxySettingWithType:proxyType settings:settings]; + + [self _openConnection]; +} + +- (void)_readProxySettingWithType:(NSString *)proxyType settings:(NSDictionary *)settings +{ + if ([proxyType isEqualToString:(NSString *)kCFProxyTypeHTTP] || + [proxyType isEqualToString:(NSString *)kCFProxyTypeHTTPS]) { + _httpProxyHost = settings[(NSString *)kCFProxyHostNameKey]; + NSNumber *portValue = settings[(NSString *)kCFProxyPortNumberKey]; + if (portValue) { + _httpProxyPort = [portValue intValue]; + } + } + if ([proxyType isEqualToString:(NSString *)kCFProxyTypeSOCKS]) { + _socksProxyHost = settings[(NSString *)kCFProxyHostNameKey]; + NSNumber *portValue = settings[(NSString *)kCFProxyPortNumberKey]; + if (portValue) + _socksProxyPort = [portValue intValue]; + _socksProxyUsername = settings[(NSString *)kCFProxyUsernameKey]; + _socksProxyPassword = settings[(NSString *)kCFProxyPasswordKey]; + } + if (_httpProxyHost) { + SRDebugLog(@"Using http proxy %@:%u", _httpProxyHost, _httpProxyPort); + } else if (_socksProxyHost) { + SRDebugLog(@"Using socks proxy %@:%u", _socksProxyHost, _socksProxyPort); + } else { + SRDebugLog(@"configureProxy no proxies"); + } +} + +- (void)_fetchPAC:(NSURL *)PACurl withProxySettings:(NSDictionary *)proxySettings +{ + SRDebugLog(@"SRWebSocket fetchPAC:%@", PACurl); + + if ([PACurl isFileURL]) { + NSError *error = nil; + NSString *script = [NSString stringWithContentsOfURL:PACurl + usedEncoding:NULL + error:&error]; + + if (error) { + [self _openConnection]; + } else { + [self _runPACScript:script withProxySettings:proxySettings]; + } + return; + } + + NSString *scheme = [PACurl.scheme lowercaseString]; + if (![scheme isEqualToString:@"http"] && ![scheme isEqualToString:@"https"]) { + // Don't know how to read data from this URL, we'll have to give up + // We'll simply assume no proxies, and start the request as normal + [self _openConnection]; + return; + } + __weak typeof(self) wself = self; + NSURLRequest *request = [NSURLRequest requestWithURL:PACurl]; + NSURLSession *session = [NSURLSession sharedSession]; + [[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + __strong typeof(wself) sself = wself; + if (!error) { + NSString *script = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + [sself _runPACScript:script withProxySettings:proxySettings]; + } else { + [sself _openConnection]; + } + }] resume]; +} + +- (void)_runPACScript:(NSString *)script withProxySettings:(NSDictionary *)proxySettings +{ + if (!script) { + [self _openConnection]; + return; + } + SRDebugLog(@"runPACScript"); + // From: http://developer.apple.com/samplecode/CFProxySupportTool/listing1.html + // Work around . This dummy call to + // CFNetworkCopyProxiesForURL initialise some state within CFNetwork + // that is required by CFNetworkCopyProxiesForAutoConfigurationScript. + CFBridgingRelease(CFNetworkCopyProxiesForURL((__bridge CFURLRef)_url, (__bridge CFDictionaryRef)proxySettings)); + + // Obtain the list of proxies by running the autoconfiguration script + CFErrorRef err = NULL; + + // CFNetworkCopyProxiesForAutoConfigurationScript doesn't understand ws:// or wss:// + NSURL *httpURL; + if (_connectionRequiresSSL) + httpURL = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@", _url.host]]; + else + httpURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@", _url.host]]; + + NSArray *proxies = CFBridgingRelease(CFNetworkCopyProxiesForAutoConfigurationScript((__bridge CFStringRef)script,(__bridge CFURLRef)httpURL, &err)); + if (!err && [proxies count] > 0) { + NSDictionary *settings = [proxies objectAtIndex:0]; + NSString *proxyType = settings[(NSString *)kCFProxyTypeKey]; + [self _readProxySettingWithType:proxyType settings:settings]; + } + [self _openConnection]; +} + +- (void)_openConnection +{ + [self _initializeStreams]; + + [self.inputStream scheduleInRunLoop:[NSRunLoop SR_networkRunLoop] + forMode:NSDefaultRunLoopMode]; + //[self.outputStream scheduleInRunLoop:[NSRunLoop SR_networkRunLoop] + // forMode:NSDefaultRunLoopMode]; + [self.outputStream open]; + [self.inputStream open]; +} + +- (void)_initializeStreams +{ + assert(_url.port.unsignedIntValue <= UINT32_MAX); + uint32_t port = _url.port.unsignedIntValue; + if (port == 0) { + port = (_connectionRequiresSSL ? 443 : 80); + } + NSString *host = _url.host; + + if (_httpProxyHost) { + host = _httpProxyHost; + port = (_httpProxyPort ?: 80); + } + + CFReadStreamRef readStream = NULL; + CFWriteStreamRef writeStream = NULL; + + SRDebugLog(@"ProxyConnect connect stream to %@:%u", host, port); + CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream); + + self.outputStream = CFBridgingRelease(writeStream); + self.inputStream = CFBridgingRelease(readStream); + + if (_socksProxyHost) { + SRDebugLog(@"ProxyConnect set sock property stream to %@:%u user %@ password %@", _socksProxyHost, _socksProxyPort, _socksProxyUsername, _socksProxyPassword); + NSMutableDictionary *settings = [NSMutableDictionary dictionaryWithCapacity:4]; + settings[NSStreamSOCKSProxyHostKey] = _socksProxyHost; + if (_socksProxyPort) { + settings[NSStreamSOCKSProxyPortKey] = @(_socksProxyPort); + } + if (_socksProxyUsername) { + settings[NSStreamSOCKSProxyUserKey] = _socksProxyUsername; + } + if (_socksProxyPassword) { + settings[NSStreamSOCKSProxyPasswordKey] = _socksProxyPassword; + } + [self.inputStream setProperty:settings forKey:NSStreamSOCKSProxyConfigurationKey]; + [self.outputStream setProperty:settings forKey:NSStreamSOCKSProxyConfigurationKey]; + } + self.inputStream.delegate = self; + self.outputStream.delegate = self; +} + +- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode +{ + SRDebugLog(@"stream handleEvent %u", eventCode); + switch (eventCode) { + case NSStreamEventOpenCompleted: { + if (aStream == self.inputStream) { + if (_httpProxyHost) { + [self _proxyDidConnect]; + } else { + [self _didConnect]; + } + } + break; + } + case NSStreamEventErrorOccurred: { + [self _failWithError:aStream.streamError]; + break; + } + case NSStreamEventEndEncountered: { + [self _failWithError:aStream.streamError]; + break; + } + case NSStreamEventHasBytesAvailable: { + if (aStream == _inputStream) { + [self _processInputStream]; + } + break; + } + case NSStreamEventHasSpaceAvailable: + case NSStreamEventNone: + SRDebugLog(@"(default) %@", aStream); + break; + } +} + +- (void)_proxyDidConnect +{ + SRDebugLog(@"Proxy Connected"); + uint32_t port = _url.port.unsignedIntValue; + if (port == 0) { + port = (_connectionRequiresSSL ? 443 : 80); + } + // Send HTTP CONNECT Request + NSString *connectRequestStr = [NSString stringWithFormat:@"CONNECT %@:%u HTTP/1.1\r\nHost: %@\r\nConnection: keep-alive\r\nProxy-Connection: keep-alive\r\n\r\n", _url.host, port, _url.host]; + + NSData *message = [connectRequestStr dataUsingEncoding:NSUTF8StringEncoding]; + SRDebugLog(@"Proxy sending %@", connectRequestStr); + + [self _writeData:message]; +} + +///handles the incoming bytes and sending them to the proper processing method +- (void)_processInputStream +{ + NSMutableData *buf = [NSMutableData dataWithCapacity:SRDefaultBufferSize()]; + uint8_t *buffer = buf.mutableBytes; + NSInteger length = [_inputStream read:buffer maxLength:SRDefaultBufferSize()]; + + if (length <= 0) { + return; + } + + BOOL process = (_inputQueue.count == 0); + [_inputQueue addObject:[NSData dataWithBytes:buffer length:length]]; + + if (process) { + [self _dequeueInput]; + } +} + +// dequeue the incoming input so it is processed in order + +- (void)_dequeueInput +{ + while (_inputQueue.count > 0) { + NSData *data = _inputQueue.firstObject; + [_inputQueue removeObjectAtIndex:0]; + + // No need to process any data further, we got the full header data. + if ([self _proxyProcessHTTPResponseWithData:data]) { + break; + } + } +} +//handle checking the proxy connection status +- (BOOL)_proxyProcessHTTPResponseWithData:(NSData *)data +{ + if (_receivedHTTPHeaders == NULL) { + _receivedHTTPHeaders = CFHTTPMessageCreateEmpty(NULL, NO); + } + + CFHTTPMessageAppendBytes(_receivedHTTPHeaders, (const UInt8 *)data.bytes, data.length); + if (CFHTTPMessageIsHeaderComplete(_receivedHTTPHeaders)) { + SRDebugLog(@"Finished reading headers %@", CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(_receivedHTTPHeaders))); + [self _proxyHTTPHeadersDidFinish]; + return YES; + } + + return NO; +} + +- (void)_proxyHTTPHeadersDidFinish +{ + NSInteger responseCode = CFHTTPMessageGetResponseStatusCode(_receivedHTTPHeaders); + + if (responseCode >= 299) { + SRDebugLog(@"Connect to Proxy Request failed with response code %d", responseCode); + NSError *error = SRHTTPErrorWithCodeDescription(responseCode, 2132, + [NSString stringWithFormat:@"Received bad response code from proxy server: %d.", + (int)responseCode]); + [self _failWithError:error]; + return; + } + SRDebugLog(@"proxy connect return %d, call socket connect", responseCode); + [self _didConnect]; +} + +static NSTimeInterval const SRProxyConnectWriteTimeout = 5.0; + +- (void)_writeData:(NSData *)data +{ + const uint8_t * bytes = data.bytes; + __block NSInteger timeout = (NSInteger)(SRProxyConnectWriteTimeout * 1000000); // wait timeout before giving up + __weak typeof(self) wself = self; + dispatch_async(_writeQueue, ^{ + __strong typeof(wself) sself = self; + if (!sself) { + return; + } + NSOutputStream *outStream = sself.outputStream; + if (!outStream) { + return; + } + while (![outStream hasSpaceAvailable]) { + usleep(100); //wait until the socket is ready + timeout -= 100; + if (timeout < 0) { + NSError *error = SRHTTPErrorWithCodeDescription(408, 2132, @"Proxy timeout"); + [sself _failWithError:error]; + } else if (outStream.streamError != nil) { + [sself _failWithError:outStream.streamError]; + } + } + [outStream write:bytes maxLength:data.length]; + }); +} + +@end diff --git a/SocketRocket/Internal/RunLoop/SRRunLoopThread.h b/SocketRocket/Internal/RunLoop/SRRunLoopThread.h new file mode 100644 index 000000000..380cfa0e5 --- /dev/null +++ b/SocketRocket/Internal/RunLoop/SRRunLoopThread.h @@ -0,0 +1,24 @@ +// +// Copyright 2012 Square Inc. +// Portions Copyright (c) 2016-present, Facebook, Inc. +// +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SRRunLoopThread : NSThread + +@property (nonatomic, strong, readonly) NSRunLoop *runLoop; + ++ (instancetype)sharedThread; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/Internal/RunLoop/SRRunLoopThread.m b/SocketRocket/Internal/RunLoop/SRRunLoopThread.m new file mode 100644 index 000000000..c692ae6f7 --- /dev/null +++ b/SocketRocket/Internal/RunLoop/SRRunLoopThread.m @@ -0,0 +1,84 @@ +// +// Copyright 2012 Square Inc. +// Portions Copyright (c) 2016-present, Facebook, Inc. +// +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "SRRunLoopThread.h" + +@interface SRRunLoopThread () +{ + dispatch_group_t _waitGroup; +} + +@property (nonatomic, strong, readwrite) NSRunLoop *runLoop; + +@end + +@implementation SRRunLoopThread + ++ (instancetype)sharedThread +{ + static SRRunLoopThread *thread; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + thread = [[SRRunLoopThread alloc] init]; + thread.name = @"com.facebook.SocketRocket.NetworkThread"; + thread.qualityOfService = NSQualityOfServiceUserInitiated; + [thread start]; + }); + return thread; +} + +- (instancetype)init +{ + self = [super init]; + if (self) { + _waitGroup = dispatch_group_create(); + dispatch_group_enter(_waitGroup); + } + return self; +} + +- (void)main +{ + @autoreleasepool { + _runLoop = [NSRunLoop currentRunLoop]; + dispatch_group_leave(_waitGroup); + + // Add an empty run loop source to prevent runloop from spinning. + CFRunLoopSourceContext sourceCtx = { + .version = 0, + .info = NULL, + .retain = NULL, + .release = NULL, + .copyDescription = NULL, + .equal = NULL, + .hash = NULL, + .schedule = NULL, + .cancel = NULL, + .perform = NULL + }; + CFRunLoopSourceRef source = CFRunLoopSourceCreate(NULL, 0, &sourceCtx); + CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode); + CFRelease(source); + + while ([_runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) { + + } + assert(NO); + } +} + +- (NSRunLoop *)runLoop +{ + dispatch_group_wait(_waitGroup, DISPATCH_TIME_FOREVER); + return _runLoop; +} + +@end diff --git a/SocketRocket/Internal/SRConstants.h b/SocketRocket/Internal/SRConstants.h new file mode 100644 index 000000000..38dd009db --- /dev/null +++ b/SocketRocket/Internal/SRConstants.h @@ -0,0 +1,26 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +typedef NS_ENUM(uint8_t, SROpCode) +{ + SROpCodeTextFrame = 0x1, + SROpCodeBinaryFrame = 0x2, + // 3-7 reserved. + SROpCodeConnectionClose = 0x8, + SROpCodePing = 0x9, + SROpCodePong = 0xA, + // B-F reserved. +}; + +/** + Default buffer size that is used for reading/writing to streams. + */ +extern size_t SRDefaultBufferSize(void); diff --git a/SocketRocket/Internal/SRConstants.m b/SocketRocket/Internal/SRConstants.m new file mode 100644 index 000000000..1dbd774db --- /dev/null +++ b/SocketRocket/Internal/SRConstants.m @@ -0,0 +1,19 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "SRConstants.h" + +size_t SRDefaultBufferSize(void) { + static size_t size; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + size = getpagesize(); + }); + return size; +} diff --git a/SocketRocket/Internal/Security/SRPinningSecurityPolicy.h b/SocketRocket/Internal/Security/SRPinningSecurityPolicy.h new file mode 100644 index 000000000..0d498b211 --- /dev/null +++ b/SocketRocket/Internal/Security/SRPinningSecurityPolicy.h @@ -0,0 +1,27 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * NOTE: While publicly, SocketRocket does not support configuring the security policy with pinned certificates, + * it is still possible to manually construct a security policy of this class. If you do this, note that you may + * be open to MitM attacks, and we will not support any issues you may have. Dive at your own risk. + */ +@interface SRPinningSecurityPolicy : SRSecurityPolicy + +- (instancetype)initWithCertificates:(NSArray *)pinnedCertificates; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/Internal/Security/SRPinningSecurityPolicy.m b/SocketRocket/Internal/Security/SRPinningSecurityPolicy.m new file mode 100644 index 000000000..c12dffa00 --- /dev/null +++ b/SocketRocket/Internal/Security/SRPinningSecurityPolicy.m @@ -0,0 +1,71 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "SRPinningSecurityPolicy.h" + +#import + +#import "SRLog.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface SRPinningSecurityPolicy () + +@property (nonatomic, copy, readonly) NSArray *pinnedCertificates; + +@end + +@implementation SRPinningSecurityPolicy + +- (instancetype)initWithCertificates:(NSArray *)pinnedCertificates +{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated" + // Do not validate certificate chain since we're pinning to specific certificates. + self = [super initWithCertificateChainValidationEnabled:NO]; +#pragma clang diagnostic pop + + if (!self) { return self; } + + if (pinnedCertificates.count == 0) { + @throw [NSException exceptionWithName:@"Creating security policy failed." + reason:@"Must specify at least one certificate when creating a pinning policy." + userInfo:nil]; + } + _pinnedCertificates = [pinnedCertificates copy]; + + return self; +} + +- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain +{ + SRDebugLog(@"Pinned cert count: %d", self.pinnedCertificates.count); + NSUInteger requiredCertCount = self.pinnedCertificates.count; + + NSUInteger validatedCertCount = 0; + CFIndex serverCertCount = SecTrustGetCertificateCount(serverTrust); + for (CFIndex i = 0; i < serverCertCount; i++) { + SecCertificateRef cert = SecTrustGetCertificateAtIndex(serverTrust, i); + NSData *data = CFBridgingRelease(SecCertificateCopyData(cert)); + for (id ref in self.pinnedCertificates) { + SecCertificateRef trustedCert = (__bridge SecCertificateRef)ref; + // TODO: (nlutsenko) Add caching, so we don't copy the data for every pinned cert all the time. + NSData *trustedCertData = CFBridgingRelease(SecCertificateCopyData(trustedCert)); + if ([trustedCertData isEqualToData:data]) { + validatedCertCount++; + break; + } + } + } + return (requiredCertCount == validatedCertCount); +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/Internal/Utilities/SRError.h b/SocketRocket/Internal/Utilities/SRError.h new file mode 100644 index 000000000..7e13a8205 --- /dev/null +++ b/SocketRocket/Internal/Utilities/SRError.h @@ -0,0 +1,20 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +extern NSError *SRErrorWithDomainCodeDescription(NSString *domain, NSInteger code, NSString *description); +extern NSError *SRErrorWithCodeDescription(NSInteger code, NSString *description); +extern NSError *SRErrorWithCodeDescriptionUnderlyingError(NSInteger code, NSString *description, NSError *underlyingError); + +extern NSError *SRHTTPErrorWithCodeDescription(NSInteger httpCode, NSInteger errorCode, NSString *description); + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/Internal/Utilities/SRError.m b/SocketRocket/Internal/Utilities/SRError.m new file mode 100644 index 000000000..eeabe0321 --- /dev/null +++ b/SocketRocket/Internal/Utilities/SRError.m @@ -0,0 +1,42 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "SRError.h" + +#import "SRWebSocket.h" + +NS_ASSUME_NONNULL_BEGIN + +NSError *SRErrorWithDomainCodeDescription(NSString *domain, NSInteger code, NSString *description) +{ + return [NSError errorWithDomain:domain code:code userInfo:@{ NSLocalizedDescriptionKey: description }]; +} + +NSError *SRErrorWithCodeDescription(NSInteger code, NSString *description) +{ + return SRErrorWithDomainCodeDescription(SRWebSocketErrorDomain, code, description); +} + +NSError *SRErrorWithCodeDescriptionUnderlyingError(NSInteger code, NSString *description, NSError *underlyingError) +{ + return [NSError errorWithDomain:SRWebSocketErrorDomain + code:code + userInfo:@{ NSLocalizedDescriptionKey: description, + NSUnderlyingErrorKey: underlyingError }]; +} + +NSError *SRHTTPErrorWithCodeDescription(NSInteger httpCode, NSInteger errorCode, NSString *description) +{ + return [NSError errorWithDomain:SRWebSocketErrorDomain + code:errorCode + userInfo:@{ NSLocalizedDescriptionKey: description, + SRHTTPResponseErrorKey: @(httpCode) }]; +} + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/Internal/Utilities/SRHTTPConnectMessage.h b/SocketRocket/Internal/Utilities/SRHTTPConnectMessage.h new file mode 100644 index 000000000..b67f3d533 --- /dev/null +++ b/SocketRocket/Internal/Utilities/SRHTTPConnectMessage.h @@ -0,0 +1,21 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +extern CFHTTPMessageRef SRHTTPConnectMessageCreate(NSURLRequest *request, + NSString *securityKey, + uint8_t webSocketProtocolVersion, + NSArray *_Nullable cookies, + NSArray *_Nullable requestedProtocols); + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/Internal/Utilities/SRHTTPConnectMessage.m b/SocketRocket/Internal/Utilities/SRHTTPConnectMessage.m new file mode 100644 index 000000000..b2f9760cc --- /dev/null +++ b/SocketRocket/Internal/Utilities/SRHTTPConnectMessage.m @@ -0,0 +1,73 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "SRHTTPConnectMessage.h" + +#import "SRURLUtilities.h" + +NS_ASSUME_NONNULL_BEGIN + +static NSString *_SRHTTPConnectMessageHost(NSURL *url) +{ + NSString *host = url.host; + if (url.port) { + host = [host stringByAppendingFormat:@":%@", url.port]; + } + return host; +} + +CFHTTPMessageRef SRHTTPConnectMessageCreate(NSURLRequest *request, + NSString *securityKey, + uint8_t webSocketProtocolVersion, + NSArray *_Nullable cookies, + NSArray *_Nullable requestedProtocols) +{ + NSURL *url = request.URL; + + CFHTTPMessageRef message = CFHTTPMessageCreateRequest(NULL, (__bridge CFStringRef)request.HTTPMethod, (__bridge CFURLRef)url, kCFHTTPVersion1_1); + + // Set host first so it defaults + CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Host"), (__bridge CFStringRef)_SRHTTPConnectMessageHost(url)); + + // Apply cookies if any have been provided + if (cookies) { + NSDictionary *messageCookies = [NSHTTPCookie requestHeaderFieldsWithCookies:(NSArray *_Nonnull)cookies]; + [messageCookies enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull obj, BOOL * _Nonnull stop) { + if (key.length && obj.length) { + CFHTTPMessageSetHeaderFieldValue(message, (__bridge CFStringRef)key, (__bridge CFStringRef)obj); + } + }]; + } + + // set header for http basic auth + NSString *basicAuthorizationString = SRBasicAuthorizationHeaderFromURL(url); + if (basicAuthorizationString) { + CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Authorization"), (__bridge CFStringRef)basicAuthorizationString); + } + + CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Upgrade"), CFSTR("websocket")); + CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Connection"), CFSTR("Upgrade")); + CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Sec-WebSocket-Key"), (__bridge CFStringRef)securityKey); + CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Sec-WebSocket-Version"), (__bridge CFStringRef)@(webSocketProtocolVersion).stringValue); + + CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Origin"), (__bridge CFStringRef)SRURLOrigin(url)); + + if (requestedProtocols.count) { + CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Sec-WebSocket-Protocol"), + (__bridge CFStringRef)[requestedProtocols componentsJoinedByString:@", "]); + } + + [request.allHTTPHeaderFields enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + CFHTTPMessageSetHeaderFieldValue(message, (__bridge CFStringRef)key, (__bridge CFStringRef)obj); + }]; + + return message; +} + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/Internal/Utilities/SRHash.h b/SocketRocket/Internal/Utilities/SRHash.h new file mode 100644 index 000000000..3db14de48 --- /dev/null +++ b/SocketRocket/Internal/Utilities/SRHash.h @@ -0,0 +1,19 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +extern NSData *SRSHA1HashFromString(NSString *string); +extern NSData *SRSHA1HashFromBytes(const char *bytes, size_t length); + +extern NSString *SRBase64EncodedStringFromData(NSData *data); + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/Internal/Utilities/SRHash.m b/SocketRocket/Internal/Utilities/SRHash.m new file mode 100644 index 000000000..a150ecc79 --- /dev/null +++ b/SocketRocket/Internal/Utilities/SRHash.m @@ -0,0 +1,47 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "SRHash.h" + +#import + +NS_ASSUME_NONNULL_BEGIN + +NSData *SRSHA1HashFromString(NSString *string) +{ + const char *utf8String = string.UTF8String; + if (!utf8String) { + return [NSData data]; + } + size_t length = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + return SRSHA1HashFromBytes(utf8String, length); +} + +NSData *SRSHA1HashFromBytes(const char *bytes, size_t length) +{ + uint8_t outputLength = CC_SHA1_DIGEST_LENGTH; + unsigned char output[outputLength]; + CC_SHA1(bytes, (CC_LONG)length, output); + + return [NSData dataWithBytes:output length:outputLength]; +} + +NSString *SRBase64EncodedStringFromData(NSData *data) +{ + if ([data respondsToSelector:@selector(base64EncodedStringWithOptions:)]) { + return [data base64EncodedStringWithOptions:0]; + } + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + return [data base64Encoding]; +#pragma clang diagnostic pop +} + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/Internal/Utilities/SRLog.h b/SocketRocket/Internal/Utilities/SRLog.h new file mode 100644 index 000000000..99689efa3 --- /dev/null +++ b/SocketRocket/Internal/Utilities/SRLog.h @@ -0,0 +1,20 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +// Uncomment this line to enable debug logging +//#define SR_DEBUG_LOG_ENABLED + +extern void SRErrorLog(NSString *format, ...); +extern void SRDebugLog(NSString *format, ...); + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/Internal/Utilities/SRLog.m b/SocketRocket/Internal/Utilities/SRLog.m new file mode 100644 index 000000000..459601781 --- /dev/null +++ b/SocketRocket/Internal/Utilities/SRLog.m @@ -0,0 +1,33 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "SRLog.h" + +NS_ASSUME_NONNULL_BEGIN + +extern void SRErrorLog(NSString *format, ...) +{ + __block va_list arg_list; + va_start (arg_list, format); + + NSString *formattedString = [[NSString alloc] initWithFormat:format arguments:arg_list]; + + va_end(arg_list); + + NSLog(@"[SocketRocket] %@", formattedString); +} + +extern void SRDebugLog(NSString *format, ...) +{ +#ifdef SR_DEBUG_LOG_ENABLED + SRErrorLog(tag, format); +#endif +} + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/Internal/Utilities/SRMutex.h b/SocketRocket/Internal/Utilities/SRMutex.h new file mode 100644 index 000000000..8226ce621 --- /dev/null +++ b/SocketRocket/Internal/Utilities/SRMutex.h @@ -0,0 +1,22 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef __attribute__((capability("mutex"))) pthread_mutex_t *SRMutex; + +extern SRMutex SRMutexInitRecursive(void); +extern void SRMutexDestroy(SRMutex mutex); + +extern void SRMutexLock(SRMutex mutex) __attribute__((acquire_capability(mutex))); +extern void SRMutexUnlock(SRMutex mutex) __attribute__((release_capability(mutex))); + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/Internal/Utilities/SRMutex.m b/SocketRocket/Internal/Utilities/SRMutex.m new file mode 100644 index 000000000..03b5939cd --- /dev/null +++ b/SocketRocket/Internal/Utilities/SRMutex.m @@ -0,0 +1,47 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "SRMutex.h" + +#import + +NS_ASSUME_NONNULL_BEGIN + +SRMutex SRMutexInitRecursive(void) +{ + pthread_mutex_t *mutex = malloc(sizeof(pthread_mutex_t)); + pthread_mutexattr_t attributes; + + pthread_mutexattr_init(&attributes); + pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(mutex, &attributes); + pthread_mutexattr_destroy(&attributes); + + return mutex; +} + +void SRMutexDestroy(SRMutex mutex) +{ + pthread_mutex_destroy(mutex); + free(mutex); +} + +__attribute__((no_thread_safety_analysis)) +void SRMutexLock(SRMutex mutex) +{ + pthread_mutex_lock(mutex); +} + +__attribute__((no_thread_safety_analysis)) +void SRMutexUnlock(SRMutex mutex) +{ + pthread_mutex_unlock(mutex); +} + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/Internal/Utilities/SRRandom.h b/SocketRocket/Internal/Utilities/SRRandom.h new file mode 100644 index 000000000..9b116cfa1 --- /dev/null +++ b/SocketRocket/Internal/Utilities/SRRandom.h @@ -0,0 +1,16 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +extern NSData *SRRandomData(NSUInteger length); + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/Internal/Utilities/SRRandom.m b/SocketRocket/Internal/Utilities/SRRandom.m new file mode 100644 index 000000000..acfbfe958 --- /dev/null +++ b/SocketRocket/Internal/Utilities/SRRandom.m @@ -0,0 +1,29 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "SRRandom.h" + +#import + +NS_ASSUME_NONNULL_BEGIN + +NSData *SRRandomData(NSUInteger length) +{ + NSMutableData *_Nullable data = [NSMutableData dataWithLength:length]; + if (data == nil) { + [NSException raise:NSInternalInconsistencyException format:@"Failed to allocate random data"]; + } + int result = SecRandomCopyBytes(kSecRandomDefault, data.length, ((NSMutableData *_Nonnull)data).mutableBytes); + if (result != errSecSuccess) { + [NSException raise:NSInternalInconsistencyException format:@"Failed to generate random bytes with OSStatus: %d", result]; + } + return (NSMutableData *_Nonnull)data; +} + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/Internal/Utilities/SRSIMDHelpers.h b/SocketRocket/Internal/Utilities/SRSIMDHelpers.h new file mode 100644 index 000000000..8291cc71d --- /dev/null +++ b/SocketRocket/Internal/Utilities/SRSIMDHelpers.h @@ -0,0 +1,19 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +/** + Unmask bytes using XOR via SIMD. + + @param bytes The bytes to unmask. + @param length The number of bytes to unmask. + @param maskKey The mask to XOR with MUST be of length sizeof(uint32_t). + */ +void SRMaskBytesSIMD(uint8_t *bytes, size_t length, uint8_t *maskKey); diff --git a/SocketRocket/Internal/Utilities/SRSIMDHelpers.m b/SocketRocket/Internal/Utilities/SRSIMDHelpers.m new file mode 100644 index 000000000..97e6fc157 --- /dev/null +++ b/SocketRocket/Internal/Utilities/SRSIMDHelpers.m @@ -0,0 +1,73 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "SRSIMDHelpers.h" + +typedef uint8_t uint8x32_t __attribute__((vector_size(32))); + +static void SRMaskBytesManual(uint8_t *bytes, size_t length, uint8_t *maskKey) { + for (size_t i = 0; i < length; i++) { + bytes[i] = bytes[i] ^ maskKey[i % sizeof(uint32_t)]; + } +} + +/** + Right-shift the elements of a vector, circularly. + + @param vector The vector to circular shift. + @param by The number of elements to shift by. + + @return A shifted vector. + */ +static uint8x32_t SRShiftVector(uint8x32_t vector, size_t by) { + uint8x32_t vectorCopy = vector; + by = by % _Alignof(uint8x32_t); + + uint8_t *vectorPointer = (uint8_t *)&vector; + uint8_t *vectorCopyPointer = (uint8_t *)&vectorCopy; + + memmove(vectorPointer + by, vectorPointer, sizeof(vector) - by); + memcpy(vectorPointer, vectorCopyPointer + (sizeof(vector) - by), by); + + return vector; +} + +void SRMaskBytesSIMD(uint8_t *bytes, size_t length, uint8_t *maskKey) { + size_t alignmentBytes = _Alignof(uint8x32_t) - ((uintptr_t)bytes % _Alignof(uint8x32_t)); + if (alignmentBytes == _Alignof(uint8x32_t)) { + alignmentBytes = 0; + } + + // If the number of bytes that can be processed after aligning is + // less than the number of bytes we can put into a vector, + // then there's no work to do with SIMD, just call the manual version. + if (alignmentBytes > length || (length - alignmentBytes) < sizeof(uint8x32_t)) { + SRMaskBytesManual(bytes, length, maskKey); + return; + } + + size_t vectorLength = (length - alignmentBytes) / sizeof(uint8x32_t); + size_t manualStartOffset = alignmentBytes + (vectorLength * sizeof(uint8x32_t)); + size_t manualLength = length - manualStartOffset; + + uint8x32_t *vector = (uint8x32_t *)(bytes + alignmentBytes); + uint8x32_t maskVector; + + memset_pattern4(&maskVector, maskKey, sizeof(uint8x32_t)); + maskVector = SRShiftVector(maskVector, alignmentBytes); + + SRMaskBytesManual(bytes, alignmentBytes, maskKey); + + for (size_t vectorIndex = 0; vectorIndex < vectorLength; vectorIndex++) { + vector[vectorIndex] = vector[vectorIndex] ^ maskVector; + } + + // Use the shifted mask for the final manual part. + SRMaskBytesManual(bytes + manualStartOffset, manualLength, (uint8_t *) &maskVector); +} diff --git a/SocketRocket/Internal/Utilities/SRURLUtilities.h b/SocketRocket/Internal/Utilities/SRURLUtilities.h new file mode 100644 index 000000000..a9ec78215 --- /dev/null +++ b/SocketRocket/Internal/Utilities/SRURLUtilities.h @@ -0,0 +1,26 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +// The origin isn't really applicable for a native application. +// So instead, just map ws -> http and wss -> https. +extern NSString *SRURLOrigin(NSURL *url); + +extern BOOL SRURLRequiresSSL(NSURL *_Nullable url); + +// Extracts `user` and `password` from url (if available) into `Basic base64(user:password)`. +extern NSString *_Nullable SRBasicAuthorizationHeaderFromURL(NSURL *url); + +// Returns a valid value for `NSStreamNetworkServiceType` or `nil`. +extern NSString *_Nullable SRStreamNetworkServiceTypeFromURLRequest(NSURLRequest *request); + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/Internal/Utilities/SRURLUtilities.m b/SocketRocket/Internal/Utilities/SRURLUtilities.m new file mode 100644 index 000000000..e8d5e45b6 --- /dev/null +++ b/SocketRocket/Internal/Utilities/SRURLUtilities.m @@ -0,0 +1,87 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "SRURLUtilities.h" + +#import "SRHash.h" + +NS_ASSUME_NONNULL_BEGIN + +NSString *SRURLOrigin(NSURL *url) +{ + NSMutableString *origin = [NSMutableString string]; + + NSString *scheme = url.scheme.lowercaseString; + if ([scheme isEqualToString:@"wss"]) { + scheme = @"https"; + } else if ([scheme isEqualToString:@"ws"]) { + scheme = @"http"; + } + [origin appendFormat:@"%@://%@", scheme, url.host]; + + NSNumber *port = url.port; + BOOL portIsDefault = (!port || + ([scheme isEqualToString:@"http"] && port.integerValue == 80) || + ([scheme isEqualToString:@"https"] && port.integerValue == 443)); + if (!portIsDefault) { + [origin appendFormat:@":%@", port.stringValue]; + } + return origin; +} + +extern BOOL SRURLRequiresSSL(NSURL *_Nullable url) +{ + NSString *scheme = url.scheme.lowercaseString; + return ([scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]); +} + +extern NSString *_Nullable SRBasicAuthorizationHeaderFromURL(NSURL *url) +{ + if (!url.user || !url.password) { + return nil; + } + + NSData *data = [[NSString stringWithFormat:@"%@:%@", url.user, url.password] dataUsingEncoding:NSUTF8StringEncoding]; + return [NSString stringWithFormat:@"Basic %@", SRBase64EncodedStringFromData(data)]; +} + +extern NSString *_Nullable SRStreamNetworkServiceTypeFromURLRequest(NSURLRequest *request) +{ + NSString *networkServiceType = nil; + switch (request.networkServiceType) { + case NSURLNetworkServiceTypeDefault: + case NSURLNetworkServiceTypeResponsiveData: + case NSURLNetworkServiceTypeAVStreaming: + case NSURLNetworkServiceTypeResponsiveAV: + break; + case NSURLNetworkServiceTypeVoIP: + networkServiceType = NSStreamNetworkServiceTypeVoIP; + break; + case NSURLNetworkServiceTypeVideo: + networkServiceType = NSStreamNetworkServiceTypeVideo; + break; + case NSURLNetworkServiceTypeBackground: + networkServiceType = NSStreamNetworkServiceTypeBackground; + break; + case NSURLNetworkServiceTypeVoice: + networkServiceType = NSStreamNetworkServiceTypeVoice; + break; +#if (__MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 || __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000 || __TV_OS_VERSION_MAX_ALLOWED >= 100000 || __WATCH_OS_VERSION_MAX_ALLOWED >= 30000) + case NSURLNetworkServiceTypeCallSignaling: { + if (@available(iOS 10.0, tvOS 10.0, macOS 10.12, *)) { + networkServiceType = NSStreamNetworkServiceTypeCallSignaling; + } + break; + } +#endif + } + return networkServiceType; +} + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/NSRunLoop+SRWebSocket.h b/SocketRocket/NSRunLoop+SRWebSocket.h new file mode 100644 index 000000000..8f419e31c --- /dev/null +++ b/SocketRocket/NSRunLoop+SRWebSocket.h @@ -0,0 +1,27 @@ +// +// Copyright 2012 Square Inc. +// Portions Copyright (c) 2016-present, Facebook, Inc. +// +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSRunLoop (SRWebSocket) + +/** + Default run loop that will be used to schedule all instances of `SRWebSocket`. + + @return An instance of `NSRunLoop`. + */ ++ (NSRunLoop *)SR_networkRunLoop; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/NSRunLoop+SRWebSocket.m b/SocketRocket/NSRunLoop+SRWebSocket.m new file mode 100644 index 000000000..30eb608b2 --- /dev/null +++ b/SocketRocket/NSRunLoop+SRWebSocket.m @@ -0,0 +1,27 @@ +// +// Copyright 2012 Square Inc. +// Portions Copyright (c) 2016-present, Facebook, Inc. +// +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "NSRunLoop+SRWebSocket.h" +#import "NSRunLoop+SRWebSocketPrivate.h" + +#import "SRRunLoopThread.h" + +// Required for object file to always be linked. +void import_NSRunLoop_SRWebSocket(void) { } + +@implementation NSRunLoop (SRWebSocket) + ++ (NSRunLoop *)SR_networkRunLoop +{ + return [SRRunLoopThread sharedThread].runLoop; +} + +@end diff --git a/SocketRocket/NSURLRequest+SRWebSocket.h b/SocketRocket/NSURLRequest+SRWebSocket.h new file mode 100644 index 000000000..4ccffd513 --- /dev/null +++ b/SocketRocket/NSURLRequest+SRWebSocket.h @@ -0,0 +1,38 @@ +// +// Copyright 2012 Square Inc. +// Portions Copyright (c) 2016-present, Facebook, Inc. +// +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSURLRequest (SRWebSocket) + +/** + An array of pinned `SecCertificateRef` SSL certificates that `SRWebSocket` will use for validation. + */ +@property (nullable, nonatomic, copy, readonly) NSArray *SR_SSLPinnedCertificates + DEPRECATED_MSG_ATTRIBUTE("Using pinned certificates is neither secure nor supported in SocketRocket, " + "and leads to security issues. Please use a proper, trust chain validated certificate."); + +@end + +@interface NSMutableURLRequest (SRWebSocket) + +/** + An array of pinned `SecCertificateRef` SSL certificates that `SRWebSocket` will use for validation. + */ +@property (nullable, nonatomic, copy) NSArray *SR_SSLPinnedCertificates + DEPRECATED_MSG_ATTRIBUTE("Using pinned certificates is neither secure nor supported in SocketRocket, " + "and leads to security issues. Please use a proper, trust chain validated certificate."); + +@end + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/NSURLRequest+SRWebSocket.m b/SocketRocket/NSURLRequest+SRWebSocket.m new file mode 100644 index 000000000..1fd6f98a9 --- /dev/null +++ b/SocketRocket/NSURLRequest+SRWebSocket.m @@ -0,0 +1,40 @@ +// +// Copyright 2012 Square Inc. +// Portions Copyright (c) 2016-present, Facebook, Inc. +// +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "NSURLRequest+SRWebSocket.h" +#import "NSURLRequest+SRWebSocketPrivate.h" + +// Required for object file to always be linked. +void import_NSURLRequest_SRWebSocket(void) { } + +NS_ASSUME_NONNULL_BEGIN + +@implementation NSURLRequest (SRWebSocket) + +- (nullable NSArray *)SR_SSLPinnedCertificates +{ + return nil; +} + +@end + +@implementation NSMutableURLRequest (SRWebSocket) + +- (void)setSR_SSLPinnedCertificates:(nullable NSArray *)SR_SSLPinnedCertificates +{ + [NSException raise:NSInvalidArgumentException + format:@"Using pinned certificates is neither secure nor supported in SocketRocket, " + "and leads to security issues. Please use a proper, trust chain validated certificate."]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SocketRocketOSX/SocketRocketOSX-Info.plist b/SocketRocket/Resources/Info.plist similarity index 64% rename from SocketRocketOSX/SocketRocketOSX-Info.plist rename to SocketRocket/Resources/Info.plist index afef9ed0b..d3de8eefb 100644 --- a/SocketRocketOSX/SocketRocketOSX-Info.plist +++ b/SocketRocket/Resources/Info.plist @@ -3,17 +3,15 @@ CFBundleDevelopmentRegion - English + en CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIconFile - + $(EXECUTABLE_NAME) CFBundleIdentifier - com.squareup.${PRODUCT_NAME:rfc1034identifier} + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName - ${PRODUCT_NAME} + $(PRODUCT_NAME) CFBundlePackageType FMWK CFBundleShortVersionString @@ -21,9 +19,7 @@ CFBundleSignature ???? CFBundleVersion - 1 - NSHumanReadableCopyright - Copyright © 2012 Square, Inc. All rights reserved. + $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/SocketRocket/SRSecurityPolicy.h b/SocketRocket/SRSecurityPolicy.h new file mode 100644 index 000000000..3fbd80933 --- /dev/null +++ b/SocketRocket/SRSecurityPolicy.h @@ -0,0 +1,72 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SRSecurityPolicy : NSObject + +/** + A default `SRSecurityPolicy` implementation specifies socket security and + validates the certificate chain. + + Use a subclass of `SRSecurityPolicy` for more fine grained customization. + */ ++ (instancetype)defaultPolicy; + +/** + Specifies socket security and provider certificate pinning, disregarding certificate + chain validation. + + @param pinnedCertificates Array of `SecCertificateRef` SSL certificates to use for validation. + */ ++ (instancetype)pinnningPolicyWithCertificates:(NSArray *)pinnedCertificates + DEPRECATED_MSG_ATTRIBUTE("Using pinned certificates is neither secure nor supported in SocketRocket, " + "and leads to security issues. Please use a proper, trust chain validated certificate."); + +/** + Specifies socket security and optional certificate chain validation. + + @param enabled Whether or not to validate the SSL certificate chain. If you + are considering using this method because your certificate was not issued by a + recognized certificate authority, consider using `pinningPolicyWithCertificates` instead. + */ +- (instancetype)initWithCertificateChainValidationEnabled:(BOOL)enabled + DEPRECATED_MSG_ATTRIBUTE("Disabling certificate chain validation is unsafe. " + "Please use a proper Certificate Authority to issue your TLS certificates.") + NS_DESIGNATED_INITIALIZER; + +/** + Updates all the security options for input and output streams, for example you + can set your socket security level here. + + @param stream Stream to update the options in. + */ +- (void)updateSecurityOptionsInStream:(NSStream *)stream; + +/** + Whether or not the specified server trust should be accepted, based on the security policy. + + This method should be used when responding to an authentication challenge from + a server. In the default implemenation, no further validation is done here, but + you're free to override it in a subclass. See `SRPinningSecurityPolicy.h` for + an example. + + @param serverTrust The X.509 certificate trust of the server. + @param domain The domain of serverTrust. + + @return Whether or not to trust the server. + */ +- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/SRSecurityPolicy.m b/SocketRocket/SRSecurityPolicy.m new file mode 100644 index 000000000..3759d26e4 --- /dev/null +++ b/SocketRocket/SRSecurityPolicy.m @@ -0,0 +1,75 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "SRSecurityPolicy.h" +#import "SRPinningSecurityPolicy.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface SRSecurityPolicy () + +@property (nonatomic, assign, readonly) BOOL certificateChainValidationEnabled; + +@end + +@implementation SRSecurityPolicy + ++ (instancetype)defaultPolicy +{ + return [self new]; +} + ++ (instancetype)pinnningPolicyWithCertificates:(NSArray *)pinnedCertificates +{ + [NSException raise:NSInvalidArgumentException + format:@"Using pinned certificates is neither secure nor supported in SocketRocket, " + "and leads to security issues. Please use a proper, trust chain validated certificate."]; + + return nil; +} + +- (instancetype)initWithCertificateChainValidationEnabled:(BOOL)enabled +{ + self = [super init]; + if (!self) { return self; } + + _certificateChainValidationEnabled = enabled; + + return self; +} + +- (instancetype)init +{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated" + + return [self initWithCertificateChainValidationEnabled:YES]; + +#pragma clang diagnostic pop +} + +- (void)updateSecurityOptionsInStream:(NSStream *)stream +{ + // Enforce TLS 1.2 + [stream setProperty:(__bridge id)CFSTR("kCFStreamSocketSecurityLevelTLSv1_2") forKey:(__bridge id)kCFStreamPropertySocketSecurityLevel]; + + // Validate certificate chain for this stream if enabled. + NSDictionary *sslOptions = @{ (__bridge NSString *)kCFStreamSSLValidatesCertificateChain : @(self.certificateChainValidationEnabled) }; + [stream setProperty:sslOptions forKey:(__bridge NSString *)kCFStreamPropertySSLSettings]; +} + +- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain +{ + // No further evaluation happens in the default policy. + return YES; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/SRWebSocket.h b/SocketRocket/SRWebSocket.h index 2d40bb1de..f7d96cd33 100644 --- a/SocketRocket/SRWebSocket.h +++ b/SocketRocket/SRWebSocket.h @@ -1,114 +1,418 @@ // -// Copyright 2012 Square Inc. +// Copyright 2012 Square Inc. +// Portions Copyright (c) 2016-present, Facebook, Inc. // -// 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 +// All rights reserved. // -// 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. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. // #import -#import -typedef enum { +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSInteger, SRReadyState) { SR_CONNECTING = 0, SR_OPEN = 1, SR_CLOSING = 2, SR_CLOSED = 3, -} SRReadyState; +}; + +typedef NS_ENUM(NSInteger, SRStatusCode) { + // 0-999: Reserved and not used. + SRStatusCodeNormal = 1000, + SRStatusCodeGoingAway = 1001, + SRStatusCodeProtocolError = 1002, + SRStatusCodeUnhandledType = 1003, + // 1004 reserved. + SRStatusCodeNoStatusReceived = 1005, + SRStatusNoStatusReceived __deprecated_enum_msg("Use SRStatusCodeNoStatusReceived") = SRStatusCodeNoStatusReceived, + SRStatusCodeAbnormal = 1006, + SRStatusCodeInvalidUTF8 = 1007, + SRStatusCodePolicyViolated = 1008, + SRStatusCodeMessageTooBig = 1009, + SRStatusCodeMissingExtension = 1010, + SRStatusCodeInternalError = 1011, + SRStatusCodeServiceRestart = 1012, + SRStatusCodeTryAgainLater = 1013, + // 1014: Reserved for future use by the WebSocket standard. + SRStatusCodeTLSHandshake = 1015, + // 1016-1999: Reserved for future use by the WebSocket standard. + // 2000-2999: Reserved for use by WebSocket extensions. + // 3000-3999: Available for use by libraries and frameworks. May not be used by applications. Available for registration at the IANA via first-come, first-serve. + // 4000-4999: Available for use by applications. +}; @class SRWebSocket; +@class SRSecurityPolicy; +/** + Error domain used for errors reported by SRWebSocket. + */ extern NSString *const SRWebSocketErrorDomain; -#pragma mark - SRWebSocketDelegate +/** + Key used for HTTP status code if bad response was received from the server. + */ +extern NSString *const SRHTTPResponseErrorKey; @protocol SRWebSocketDelegate; +///-------------------------------------- #pragma mark - SRWebSocket +///-------------------------------------- +/** + A `SRWebSocket` object lets you connect, send and receive data to a remote Web Socket. + */ @interface SRWebSocket : NSObject -@property (nonatomic, assign) id delegate; +/** + The delegate of the web socket. + + The web socket delegate is notified on all state changes that happen to the web socket. + */ +@property (nonatomic, weak) id delegate; + +/** + A dispatch queue for scheduling the delegate calls. The queue doesn't need be a serial queue. -@property (nonatomic, readonly) SRReadyState readyState; -@property (nonatomic, readonly, retain) NSURL *url; + If `nil` and `delegateOperationQueue` is `nil`, the socket uses main queue for performing all delegate method calls. + */ +@property (nullable, nonatomic, strong) dispatch_queue_t delegateDispatchQueue; -// This returns the negotiated protocol. -// It will be nil until after the handshake completes. -@property (nonatomic, readonly, copy) NSString *protocol; +/** + An operation queue for scheduling the delegate calls. -// Protocols should be an array of strings that turn into Sec-WebSocket-Protocol. -- (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols; -- (id)initWithURLRequest:(NSURLRequest *)request; + If `nil` and `delegateOperationQueue` is `nil`, the socket uses main queue for performing all delegate method calls. + */ +@property (nullable, nonatomic, strong) NSOperationQueue *delegateOperationQueue; -// Some helper constructors. -- (id)initWithURL:(NSURL *)url protocols:(NSArray *)protocols; -- (id)initWithURL:(NSURL *)url; +/** + Current ready state of the socket. Default: `SR_CONNECTING`. -// Delegate queue will be dispatch_main_queue by default. -// You cannot set both OperationQueue and dispatch_queue. -- (void)setDelegateOperationQueue:(NSOperationQueue*) queue; -- (void)setDelegateDispatchQueue:(dispatch_queue_t) queue; + This property is Key-Value Observable and fully thread-safe. + */ +@property (atomic, assign, readonly) SRReadyState readyState; -// By default, it will schedule itself on +[NSRunLoop SR_networkRunLoop] using defaultModes. -- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; -- (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; - -// SRWebSockets are intended for one-time-use only. Open should be called once and only once. +/** + An instance of `NSURL` that this socket connects to. + */ +@property (nullable, nonatomic, strong, readonly) NSURL *url; + +/** + All HTTP headers that were received by socket or `nil` if none were received so far. + */ +@property (nullable, nonatomic, assign, readonly) CFHTTPMessageRef receivedHTTPHeaders; + +/** + Array of `NSHTTPCookie` cookies to apply to the connection. + */ +@property (nullable, nonatomic, copy) NSArray *requestCookies; + +/** + The negotiated web socket protocol or `nil` if handshake did not yet complete. + */ +@property (nullable, nonatomic, copy, readonly) NSString *protocol; + +/** + A boolean value indicating whether this socket will allow connection without SSL trust chain evaluation. + For DEBUG builds this flag is ignored, and SSL connections are allowed regardless of the certificate trust configuration + */ +@property (nonatomic, assign, readonly) BOOL allowsUntrustedSSLCertificates; + +///-------------------------------------- +#pragma mark - Constructors +///-------------------------------------- + +/** + Initializes a web socket with a given `NSURLRequest`. + + @param request Request to initialize with. + */ +- (instancetype)initWithURLRequest:(NSURLRequest *)request; + +/** + Initializes a web socket with a given `NSURLRequest`, specifying a transport security policy (e.g. SSL configuration). + + @param request Request to initialize with. + @param securityPolicy Policy object describing transport security behavior. + */ +- (instancetype)initWithURLRequest:(NSURLRequest *)request securityPolicy:(SRSecurityPolicy *)securityPolicy; + +/** + Initializes a web socket with a given `NSURLRequest` and list of sub-protocols. + + @param request Request to initialize with. + @param protocols An array of strings that turn into `Sec-WebSocket-Protocol`. Default: `nil`. + */ +- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(nullable NSArray *)protocols; + +/** + Initializes a web socket with a given `NSURLRequest`, list of sub-protocols and whether untrusted SSL certificates are allowed. + + @param request Request to initialize with. + @param protocols An array of strings that turn into `Sec-WebSocket-Protocol`. Default: `nil`. + @param allowsUntrustedSSLCertificates Boolean value indicating whether untrusted SSL certificates are allowed. Default: `false`. + */ +- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(nullable NSArray *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates + DEPRECATED_MSG_ATTRIBUTE("Disabling certificate chain validation is unsafe. " + "Please use a proper Certificate Authority to issue your TLS certificates."); + +/** + Initializes a web socket with a given `NSURLRequest`, list of sub-protocols and whether untrusted SSL certificates are allowed. + + @param request Request to initialize with. + @param protocols An array of strings that turn into `Sec-WebSocket-Protocol`. Default: `nil`. + @param securityPolicy Policy object describing transport security behavior. + */ +- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(nullable NSArray *)protocols securityPolicy:(SRSecurityPolicy *)securityPolicy NS_DESIGNATED_INITIALIZER; + +/** + Initializes a web socket with a given `NSURL`. + + @param url URL to initialize with. + */ +- (instancetype)initWithURL:(NSURL *)url; + +/** + Initializes a web socket with a given `NSURL` and list of sub-protocols. + + @param url URL to initialize with. + @param protocols An array of strings that turn into `Sec-WebSocket-Protocol`. Default: `nil`. + */ +- (instancetype)initWithURL:(NSURL *)url protocols:(nullable NSArray *)protocols; + +/** + Initializes a web socket with a given `NSURL`, specifying a transport security policy (e.g. SSL configuration). + + @param url URL to initialize with. + @param securityPolicy Policy object describing transport security behavior. + */ +- (instancetype)initWithURL:(NSURL *)url securityPolicy:(SRSecurityPolicy *)securityPolicy; + +/** + Initializes a web socket with a given `NSURL`, list of sub-protocols and whether untrusted SSL certificates are allowed. + + @param url URL to initialize with. + @param protocols An array of strings that turn into `Sec-WebSocket-Protocol`. Default: `nil`. + @param allowsUntrustedSSLCertificates Boolean value indicating whether untrusted SSL certificates are allowed. Default: `false`. + */ +- (instancetype)initWithURL:(NSURL *)url protocols:(nullable NSArray *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates + DEPRECATED_MSG_ATTRIBUTE("Disabling certificate chain validation is unsafe. " + "Please use a proper Certificate Authority to issue your TLS certificates."); + +/** + Unavailable initializer. Please use any other initializer. + */ +- (instancetype)init NS_UNAVAILABLE; + +/** + Unavailable constructor. Please use any other initializer. + */ ++ (instancetype)new NS_UNAVAILABLE; + +///-------------------------------------- +#pragma mark - Schedule +///-------------------------------------- + +/** + Schedules a received on a given run loop in a given mode. + By default, a web socket will schedule itself on `+[NSRunLoop SR_networkRunLoop]` using `NSDefaultRunLoopMode`. + + @param runLoop The run loop on which to schedule the receiver. + @param mode The mode for the run loop. + */ +- (void)scheduleInRunLoop:(NSRunLoop *)runLoop forMode:(NSString *)mode NS_SWIFT_NAME(schedule(in:forMode:)); + +/** + Removes the receiver from a given run loop running in a given mode. + + @param runLoop The run loop on which the receiver was scheduled. + @param mode The mode for the run loop. + */ +- (void)unscheduleFromRunLoop:(NSRunLoop *)runLoop forMode:(NSString *)mode NS_SWIFT_NAME(unschedule(from:forMode:)); + +///-------------------------------------- +#pragma mark - Open / Close +///-------------------------------------- + +/** + Opens web socket, which will trigger connection, authentication and start receiving/sending events. + An instance of `SRWebSocket` is intended for one-time-use only. This method should be called once and only once. + */ - (void)open; +/** + Closes a web socket using `SRStatusCodeNormal` code and no reason. + */ - (void)close; -- (void)closeWithCode:(NSInteger)code reason:(NSString *)reason; -// Send a UTF8 String or Data. -- (void)send:(id)data; +/** + Closes a web socket using a given code and reason. + + @param code Code to close the socket with. + @param reason Reason to send to the server or `nil`. + */ +- (void)closeWithCode:(NSInteger)code reason:(nullable NSString *)reason; + +///-------------------------------------- +#pragma mark Send +///-------------------------------------- + +/** + Send a UTF-8 string or binary data to the server. + + @param message UTF-8 String or Data to send. + + @deprecated Please use `sendString:` or `sendData` instead. + */ +- (void)send:(nullable id)message __attribute__((deprecated("Please use `sendString:error:` or `sendData:error:` instead."))); + +/** + Send a UTF-8 String to the server. + + @param string String to send. + @param error On input, a pointer to variable for an `NSError` object. + If an error occurs, this pointer is set to an `NSError` object containing information about the error. + You may specify `nil` to ignore the error information. + + @return `YES` if the string was scheduled to send, otherwise - `NO`. + */ +- (BOOL)sendString:(NSString *)string error:(NSError **)error NS_SWIFT_NAME(send(string:)); + +/** + Send binary data to the server. + + @param data Data to send. + @param error On input, a pointer to variable for an `NSError` object. + If an error occurs, this pointer is set to an `NSError` object containing information about the error. + You may specify `nil` to ignore the error information. + + @return `YES` if the string was scheduled to send, otherwise - `NO`. + */ +- (BOOL)sendData:(nullable NSData *)data error:(NSError **)error NS_SWIFT_NAME(send(data:)); + +/** + Send binary data to the server, without making a defensive copy of it first. + + @param data Data to send. + @param error On input, a pointer to variable for an `NSError` object. + If an error occurs, this pointer is set to an `NSError` object containing information about the error. + You may specify `nil` to ignore the error information. + + @return `YES` if the string was scheduled to send, otherwise - `NO`. + */ +- (BOOL)sendDataNoCopy:(nullable NSData *)data error:(NSError **)error NS_SWIFT_NAME(send(dataNoCopy:)); + +/** + Send Ping message to the server with optional data. + + @param data Instance of `NSData` or `nil`. + @param error On input, a pointer to variable for an `NSError` object. + If an error occurs, this pointer is set to an `NSError` object containing information about the error. + You may specify `nil` to ignore the error information. + + @return `YES` if the string was scheduled to send, otherwise - `NO`. + */ +- (BOOL)sendPing:(nullable NSData *)data error:(NSError **)error NS_SWIFT_NAME(sendPing(_:)); @end +///-------------------------------------- #pragma mark - SRWebSocketDelegate +///-------------------------------------- +/** + The `SRWebSocketDelegate` protocol describes the methods that `SRWebSocket` objects + call on their delegates to handle status and messsage events. + */ @protocol SRWebSocketDelegate -// message will either be an NSString if the server is using text -// or NSData if the server is using binary. +@optional + +#pragma mark Receive Messages + +/** + Called when any message was received from a web socket. + This method is suboptimal and might be deprecated in a future release. + + @param webSocket An instance of `SRWebSocket` that received a message. + @param message Received message. Either a `String` or `NSData`. + */ - (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message; -@optional +/** + Called when a frame was received from a web socket. -- (void)webSocketDidOpen:(SRWebSocket *)webSocket; -- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error; -- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean; + @param webSocket An instance of `SRWebSocket` that received a message. + @param string Received text in a form of UTF-8 `String`. + */ +- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessageWithString:(NSString *)string; -@end +/** + Called when a frame was received from a web socket. -#pragma mark - NSURLRequest (CertificateAdditions) + @param webSocket An instance of `SRWebSocket` that received a message. + @param data Received data in a form of `NSData`. + */ +- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessageWithData:(NSData *)data; -@interface NSURLRequest (CertificateAdditions) +#pragma mark Status & Connection -@property (nonatomic, retain, readonly) NSArray *SR_SSLPinnedCertificates; +/** + Called when a given web socket was open and authenticated. -@end + @param webSocket An instance of `SRWebSocket` that was open. + */ +- (void)webSocketDidOpen:(SRWebSocket *)webSocket; -#pragma mark - NSMutableURLRequest (CertificateAdditions) +/** + Called when a given web socket encountered an error. -@interface NSMutableURLRequest (CertificateAdditions) + @param webSocket An instance of `SRWebSocket` that failed with an error. + @param error An instance of `NSError`. + */ +- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error; -@property (nonatomic, retain) NSArray *SR_SSLPinnedCertificates; +/** + Called when a given web socket was closed. -@end + @param webSocket An instance of `SRWebSocket` that was closed. + @param code Code reported by the server. + @param reason Reason in a form of a String that was reported by the server or `nil`. + @param wasClean Boolean value indicating whether a socket was closed in a clean state. + */ +- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(nullable NSString *)reason wasClean:(BOOL)wasClean; + +/** + Called on receive of a ping message from the server. -#pragma mark - NSRunLoop (SRWebSocket) + @param webSocket An instance of `SRWebSocket` that received a ping frame. + @param data Payload that was received or `nil` if there was no payload. + */ +- (void)webSocket:(SRWebSocket *)webSocket didReceivePingWithData:(nullable NSData *)data; -@interface NSRunLoop (SRWebSocket) +/** + Called when a pong data was received in response to ping. -+ (NSRunLoop *)SR_networkRunLoop; + @param webSocket An instance of `SRWebSocket` that received a pong frame. + @param pongData Payload that was received or `nil` if there was no payload. + */ +- (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(nullable NSData *)pongData; + +/** + Sent before reporting a text frame to be able to configure if it shuold be convert to a UTF-8 String or passed as `NSData`. + If the method is not implemented - it will always convert text frames to String. + + @param webSocket An instance of `SRWebSocket` that received a text frame. + + @return `YES` if text frame should be converted to UTF-8 String, otherwise - `NO`. Default: `YES`. + */ +- (BOOL)webSocketShouldConvertTextFrameToString:(SRWebSocket *)webSocket NS_SWIFT_NAME(webSocketShouldConvertTextFrameToString(_:)); @end + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/SRWebSocket.m b/SocketRocket/SRWebSocket.m index ad6e9461c..698990e4a 100644 --- a/SocketRocket/SRWebSocket.m +++ b/SocketRocket/SRWebSocket.m @@ -1,23 +1,17 @@ // -// Copyright 2012 Square Inc. +// Copyright 2012 Square Inc. +// Portions Copyright (c) 2016-present, Facebook, Inc. // -// 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 +// All rights reserved. // -// http://www.apache.org/licenses/LICENSE-2.0 +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. // -// 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 "SRWebSocket.h" -#if TARGET_OS_IPHONE +#if __has_include() #define HAS_ICU #endif @@ -25,58 +19,42 @@ #import #endif -#if TARGET_OS_IPHONE -#import -#else -#import -#endif - -#import -#import - -#if OS_OBJECT_USE_OBJC_RETAIN_RELEASE -#define sr_dispatch_retain(x) -#define sr_dispatch_release(x) -#define maybe_bridge(x) ((__bridge void *) x) -#else -#define sr_dispatch_retain(x) dispatch_retain(x) -#define sr_dispatch_release(x) dispatch_release(x) -#define maybe_bridge(x) (x) -#endif - -#if !__has_feature(objc_arc) -#error SocketRocket muust be compiled with ARC enabled +#import + +#import "SRDelegateController.h" +#import "SRIOConsumer.h" +#import "SRIOConsumerPool.h" +#import "SRHash.h" +#import "SRURLUtilities.h" +#import "SRError.h" +#import "NSURLRequest+SRWebSocket.h" +#import "NSRunLoop+SRWebSocket.h" +#import "SRProxyConnect.h" +#import "SRSecurityPolicy.h" +#import "SRHTTPConnectMessage.h" +#import "SRRandom.h" +#import "SRLog.h" +#import "SRMutex.h" +#import "SRSIMDHelpers.h" +#import "NSURLRequest+SRWebSocketPrivate.h" +#import "NSRunLoop+SRWebSocketPrivate.h" +#import "SRConstants.h" + +#if !__has_feature(objc_arc) +#error SocketRocket must be compiled with ARC enabled #endif - -typedef enum { - SROpCodeTextFrame = 0x1, - SROpCodeBinaryFrame = 0x2, - // 3-7 reserved. - SROpCodeConnectionClose = 0x8, - SROpCodePing = 0x9, - SROpCodePong = 0xA, - // B-F reserved. -} SROpCode; - -typedef enum { - SRStatusCodeNormal = 1000, - SRStatusCodeGoingAway = 1001, - SRStatusCodeProtocolError = 1002, - SRStatusCodeUnhandledType = 1003, - // 1004 reserved. - SRStatusNoStatusReceived = 1005, - // 1004-1006 reserved. - SRStatusCodeInvalidUTF8 = 1007, - SRStatusCodePolicyViolated = 1008, - SRStatusCodeMessageTooBig = 1009, -} SRStatusCode; +__attribute__((used)) static void importCategories(void) +{ + import_NSURLRequest_SRWebSocket(); + import_NSRunLoop_SRWebSocket(); +} typedef struct { BOOL fin; -// BOOL rsv1; -// BOOL rsv2; -// BOOL rsv3; + // BOOL rsv1; + // BOOL rsv2; + // BOOL rsv3; uint8_t opcode; BOOL masked; uint64_t payload_length; @@ -85,153 +63,43 @@ static NSString *const SRWebSocketAppendToSecKeyString = @"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; static inline int32_t validate_dispatch_data_partial_string(NSData *data); -static inline void SRFastLog(NSString *format, ...); - -@interface NSData (SRWebSocket) - -- (NSString *)stringBySHA1ThenBase64Encoding; - -@end - - -@interface NSString (SRWebSocket) - -- (NSString *)stringBySHA1ThenBase64Encoding; - -@end - - -@interface NSURL (SRWebSocket) - -// The origin isn't really applicable for a native application. -// So instead, just map ws -> http and wss -> https. -- (NSString *)SR_origin; - -@end - - -@interface _SRRunLoopThread : NSThread - -@property (nonatomic, readonly) NSRunLoop *runLoop; - -@end - - -static NSString *newSHA1String(const char *bytes, size_t length) { - uint8_t md[CC_SHA1_DIGEST_LENGTH]; - assert(length >= 0); - assert(length <= UINT32_MAX); - CC_SHA1(bytes, (CC_LONG)length, md); - - return [[NSData dataWithBytes:md length:CC_SHA1_DIGEST_LENGTH] base64Encoding]; -} +static uint8_t const SRWebSocketProtocolVersion = 13; -@implementation NSData (SRWebSocket) - -- (NSString *)stringBySHA1ThenBase64Encoding; -{ - return newSHA1String(self.bytes, self.length); -} - -@end - - -@implementation NSString (SRWebSocket) - -- (NSString *)stringBySHA1ThenBase64Encoding; -{ - return newSHA1String(self.UTF8String, self.length); -} - -@end +// Max frame payload length for all frames is 256MB, which is reasonable max. +static const uint32_t SRWebSocketMaxFramePayloadLength = 256 * 1024 * 1024; NSString *const SRWebSocketErrorDomain = @"SRWebSocketErrorDomain"; - -// Returns number of bytes consumed. Returning 0 means you didn't match. -// Sends bytes to callback handler; -typedef size_t (^stream_scanner)(NSData *collected_data); - -typedef void (^data_callback)(SRWebSocket *webSocket, NSData *data); - -@interface SRIOConsumer : NSObject { - stream_scanner _scanner; - data_callback _handler; - size_t _bytesNeeded; - BOOL _readToCurrentFrame; - BOOL _unmaskBytes; -} -@property (nonatomic, copy, readonly) stream_scanner consumer; -@property (nonatomic, copy, readonly) data_callback handler; -@property (nonatomic, assign) size_t bytesNeeded; -@property (nonatomic, assign, readonly) BOOL readToCurrentFrame; -@property (nonatomic, assign, readonly) BOOL unmaskBytes; - -@end - -// This class is not thread-safe, and is expected to always be run on the same queue. -@interface SRIOConsumerPool : NSObject - -- (id)initWithBufferCapacity:(NSUInteger)poolSize; - -- (SRIOConsumer *)consumerWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; -- (void)returnConsumer:(SRIOConsumer *)consumer; - -@end +NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode"; @interface SRWebSocket () -- (void)_writeData:(NSData *)data; -- (void)_closeWithProtocolError:(NSString *)message; -- (void)_failWithError:(NSError *)error; - -- (void)_disconnect; +@property (atomic, assign, readwrite) SRReadyState readyState; -- (void)_readFrameNew; -- (void)_readFrameContinue; +// Specifies whether SSL trust chain should NOT be evaluated. +// By default this flag is set to NO, meaning only secure SSL connections are allowed. +// For DEBUG builds this flag is ignored, and SSL connections are allowed regardless +// of the certificate trust configuration +@property (nonatomic, assign, readwrite) BOOL allowsUntrustedSSLCertificates; -- (void)_pumpScanner; - -- (void)_pumpWriting; - -- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback; -- (void)_addConsumerWithDataLength:(size_t)dataLength callback:(data_callback)callback readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; -- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback dataLength:(size_t)dataLength; -- (void)_readUntilBytes:(const void *)bytes length:(size_t)length callback:(data_callback)dataHandler; -- (void)_readUntilHeaderCompleteWithCallback:(data_callback)dataHandler; - -- (void)_sendFrameWithOpcode:(SROpCode)opcode data:(id)data; - -- (BOOL)_checkHandshake:(CFHTTPMessageRef)httpMessage; -- (void)_SR_commonInit; - -- (void)_initializeStreams; -- (void)_connect; - -@property (nonatomic) SRReadyState readyState; - -@property (nonatomic) NSOperationQueue *delegateOperationQueue; -@property (nonatomic) dispatch_queue_t delegateDispatchQueue; +@property (nonatomic, strong, readonly) SRDelegateController *delegateController; @end - @implementation SRWebSocket { - NSInteger _webSocketVersion; - - NSOperationQueue *_delegateOperationQueue; - dispatch_queue_t _delegateDispatchQueue; - + SRMutex _kvoLock; + os_unfair_lock _propertyLock; + dispatch_queue_t _workQueue; - NSMutableArray *_consumers; + NSMutableArray *_consumers; NSInputStream *_inputStream; NSOutputStream *_outputStream; - - NSMutableData *_readBuffer; + + dispatch_data_t _readBuffer; NSUInteger _readBufferOffset; - - NSMutableData *_outputBuffer; + + dispatch_data_t _outputBuffer; NSUInteger _outputBufferOffset; uint8_t _currentFrameOpcode; @@ -239,128 +107,154 @@ @implementation SRWebSocket { size_t _readOpCount; uint32_t _currentStringScanPosition; NSMutableData *_currentFrameData; - + NSString *_closeReason; - + NSString *_secKey; - - BOOL _pinnedCertFound; - + + SRSecurityPolicy *_securityPolicy; + BOOL _requestRequiresSSL; + BOOL _streamSecurityValidated; + uint8_t _currentReadMaskKey[4]; size_t _currentReadMaskOffset; - BOOL _consumerStopped; - BOOL _closeWhenFinishedWriting; BOOL _failed; - BOOL _secure; NSURLRequest *_urlRequest; - CFHTTPMessageRef _receivedHTTPHeaders; - BOOL _sentClose; BOOL _didFail; + BOOL _cleanupScheduled; int _closeCode; - + BOOL _isPumping; - - NSMutableSet *_scheduledRunloops; - + + NSMutableSet *_scheduledRunloops; // Set<[RunLoop, Mode]>. TODO: (nlutsenko) Fix clowntown + // We use this to retain ourselves. __strong SRWebSocket *_selfRetain; - - NSArray *_requestedProtocols; + + NSArray *_requestedProtocols; SRIOConsumerPool *_consumerPool; + + // proxy support + SRProxyConnect *_proxyConnect; } -@synthesize delegate = _delegate; -@synthesize url = _url; @synthesize readyState = _readyState; -@synthesize protocol = _protocol; -static __strong NSData *CRLFCRLF; +///-------------------------------------- +#pragma mark - Init +///-------------------------------------- -+ (void)initialize; +- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols securityPolicy:(SRSecurityPolicy *)securityPolicy { - CRLFCRLF = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4]; + self = [super init]; + if (!self) return self; + + assert(request.URL); + _url = request.URL; + _urlRequest = request; + _requestedProtocols = [protocols copy]; + _securityPolicy = securityPolicy; + _requestRequiresSSL = SRURLRequiresSSL(_url); + + _readyState = SR_CONNECTING; + + _propertyLock = OS_UNFAIR_LOCK_INIT; + _kvoLock = SRMutexInitRecursive(); + _workQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); + + // Going to set a specific on the queue so we can validate we're on the work queue + dispatch_queue_set_specific(_workQueue, (__bridge void *)self, (__bridge void *)(_workQueue), NULL); + + _delegateController = [[SRDelegateController alloc] init]; + + _readBuffer = dispatch_data_empty; + _outputBuffer = dispatch_data_empty; + + _currentFrameData = [[NSMutableData alloc] init]; + + _consumers = [[NSMutableArray alloc] init]; + + _consumerPool = [[SRIOConsumerPool alloc] init]; + + _scheduledRunloops = [[NSMutableSet alloc] init]; + + return self; } -- (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols; +- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates { - self = [super init]; - if (self) { - assert(request.URL); - _url = request.URL; - _urlRequest = request; - - _requestedProtocols = [protocols copy]; - - [self _SR_commonInit]; + SRSecurityPolicy *securityPolicy; + NSArray *pinnedCertificates = request.SR_SSLPinnedCertificates; + if (pinnedCertificates) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated" + securityPolicy = [SRSecurityPolicy pinnningPolicyWithCertificates:pinnedCertificates]; +#pragma clang diagnostic pop + } else { + BOOL certificateChainValidationEnabled = !allowsUntrustedSSLCertificates; + securityPolicy = [[SRSecurityPolicy alloc] initWithCertificateChainValidationEnabled:certificateChainValidationEnabled]; } - - return self; + + return [self initWithURLRequest:request protocols:protocols securityPolicy:securityPolicy]; +} + +- (instancetype)initWithURLRequest:(NSURLRequest *)request securityPolicy:(SRSecurityPolicy *)securityPolicy +{ + return [self initWithURLRequest:request protocols:nil securityPolicy:securityPolicy]; +} + +- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols +{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated" + return [self initWithURLRequest:request protocols:protocols allowsUntrustedSSLCertificates:NO]; +#pragma clang diagnostic pop } -- (id)initWithURLRequest:(NSURLRequest *)request; +- (instancetype)initWithURLRequest:(NSURLRequest *)request { return [self initWithURLRequest:request protocols:nil]; } -- (id)initWithURL:(NSURL *)url; +- (instancetype)initWithURL:(NSURL *)url { return [self initWithURL:url protocols:nil]; } -- (id)initWithURL:(NSURL *)url protocols:(NSArray *)protocols; +- (instancetype)initWithURL:(NSURL *)url protocols:(NSArray *)protocols { - NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; - return [self initWithURLRequest:request protocols:protocols]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated" + return [self initWithURL:url protocols:protocols allowsUntrustedSSLCertificates:NO]; +#pragma clang diagnostic pop } -- (void)_SR_commonInit; +- (instancetype)initWithURL:(NSURL *)url securityPolicy:(SRSecurityPolicy *)securityPolicy { - - NSString *scheme = _url.scheme.lowercaseString; - assert([scheme isEqualToString:@"ws"] || [scheme isEqualToString:@"http"] || [scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]); - - if ([scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]) { - _secure = YES; - } - - _readyState = SR_CONNECTING; - _consumerStopped = YES; - _webSocketVersion = 13; - - _workQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); - - // Going to set a specific on the queue so we can validate we're on the work queue - dispatch_queue_set_specific(_workQueue, (__bridge void *)self, maybe_bridge(_workQueue), NULL); - - _delegateDispatchQueue = dispatch_get_main_queue(); - sr_dispatch_retain(_delegateDispatchQueue); - - _readBuffer = [[NSMutableData alloc] init]; - _outputBuffer = [[NSMutableData alloc] init]; - - _currentFrameData = [[NSMutableData alloc] init]; + NSURLRequest *request = [NSURLRequest requestWithURL:url]; + return [self initWithURLRequest:request protocols:nil securityPolicy:securityPolicy]; +} - _consumers = [[NSMutableArray alloc] init]; - - _consumerPool = [[SRIOConsumerPool alloc] init]; - - _scheduledRunloops = [[NSMutableSet alloc] init]; - - [self _initializeStreams]; - - // default handlers +- (instancetype)initWithURL:(NSURL *)url protocols:(NSArray *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates +{ + NSURLRequest *request = [NSURLRequest requestWithURL:url]; + return [self initWithURLRequest:request protocols:protocols allowsUntrustedSSLCertificates:allowsUntrustedSSLCertificates]; } -- (void)assertOnWorkQueue; +- (void)assertOnWorkQueue { - assert(dispatch_get_specific((__bridge void *)self) == maybe_bridge(_workQueue)); + assert(dispatch_get_specific((__bridge void *)self) == (__bridge void *)_workQueue); } +///-------------------------------------- +#pragma mark - Dealloc +///-------------------------------------- + - (void)dealloc { _inputStream.delegate = nil; @@ -368,285 +262,297 @@ - (void)dealloc [_inputStream close]; [_outputStream close]; - - sr_dispatch_release(_workQueue); - _workQueue = NULL; - + if (_receivedHTTPHeaders) { CFRelease(_receivedHTTPHeaders); _receivedHTTPHeaders = NULL; } - - if (_delegateDispatchQueue) { - sr_dispatch_release(_delegateDispatchQueue); - _delegateDispatchQueue = NULL; - } + + SRMutexDestroy(_kvoLock); } -#ifndef NDEBUG +///-------------------------------------- +#pragma mark - Accessors +///-------------------------------------- + +#pragma mark readyState -- (void)setReadyState:(SRReadyState)aReadyState; +- (void)setReadyState:(SRReadyState)readyState { - [self willChangeValueForKey:@"readyState"]; - assert(aReadyState > _readyState); - _readyState = aReadyState; - [self didChangeValueForKey:@"readyState"]; + @try { + SRMutexLock(_kvoLock); + if (_readyState != readyState) { + [self willChangeValueForKey:@"readyState"]; + os_unfair_lock_lock(&_propertyLock); + _readyState = readyState; + os_unfair_lock_unlock(&_propertyLock); + [self didChangeValueForKey:@"readyState"]; + } + } + @finally { + SRMutexUnlock(_kvoLock); + } } -#endif - -- (void)open; +- (SRReadyState)readyState { - assert(_url); - NSAssert(_readyState == SR_CONNECTING, @"Cannot call -(void)open on SRWebSocket more than once"); + SRReadyState state = 0; + os_unfair_lock_lock(&_propertyLock); + state = _readyState; + os_unfair_lock_unlock(&_propertyLock); + return state; +} - _selfRetain = self; - - [self _connect]; ++ (BOOL)automaticallyNotifiesObserversOfReadyState { + return NO; } -// Calls block on delegate queue -- (void)_performDelegateBlock:(dispatch_block_t)block; +///-------------------------------------- +#pragma mark - Open / Close +///-------------------------------------- + +- (void)open { - if (_delegateOperationQueue) { - [_delegateOperationQueue addOperationWithBlock:block]; - } else { - assert(_delegateDispatchQueue); - dispatch_async(_delegateDispatchQueue, block); + NSURL* const url = _url; + if (!url) { + NSError *error = SRErrorWithDomainCodeDescription(NSURLErrorDomain, NSURLErrorBadURL, @"Unable to open socket with emtpy URL."); + [self _failWithError:error]; + return; } + NSAssert(self.readyState == SR_CONNECTING, @"Cannot call -(void)open on SRWebSocket more than once."); + + _selfRetain = self; + + if (_urlRequest.timeoutInterval > 0) { + dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_urlRequest.timeoutInterval * NSEC_PER_SEC)); + __weak typeof(self) wself = self; + dispatch_after(popTime, dispatch_get_main_queue(), ^{ + __strong SRWebSocket *sself = wself; + if (!sself) { + return; + } + if (sself.readyState == SR_CONNECTING) { + NSError *error = SRErrorWithDomainCodeDescription(NSURLErrorDomain, NSURLErrorTimedOut, @"Timed out connecting to server."); + [sself _failWithError:error]; + } + }); + } + + _proxyConnect = [[SRProxyConnect alloc] initWithURL:url]; + + __weak typeof(self) wself = self; + [_proxyConnect openNetworkStreamWithCompletion:^(NSError *error, NSInputStream *readStream, NSOutputStream *writeStream) { + [wself _connectionDoneWithError:error readStream:readStream writeStream:writeStream]; + }]; } -- (void)setDelegateDispatchQueue:(dispatch_queue_t)queue; +- (void)_connectionDoneWithError:(NSError *)error readStream:(NSInputStream *)readStream writeStream:(NSOutputStream *)writeStream { - if (queue) { - sr_dispatch_retain(queue); - } - - if (_delegateDispatchQueue) { - sr_dispatch_release(_delegateDispatchQueue); + if (error != nil) { + [self _failWithError:error]; + } else { + _outputStream = writeStream; + _inputStream = readStream; + + _inputStream.delegate = self; + _outputStream.delegate = self; + [self _updateSecureStreamOptions]; + + if (!_scheduledRunloops.count) { + [self scheduleInRunLoop:[NSRunLoop SR_networkRunLoop] forMode:NSDefaultRunLoopMode]; + } + + // If we don't require SSL validation - consider that we connected. + // Otherwise `didConnect` is called when SSL validation finishes. + if (!_requestRequiresSSL) { + dispatch_async(_workQueue, ^{ + [self didConnect]; + }); + } } - - _delegateDispatchQueue = queue; + // Schedule to run on a work queue, to make sure we don't run this inline and deallocate `self` inside `SRProxyConnect`. + // TODO: (nlutsenko) Find a better structure for this, maybe Bolts Tasks? + dispatch_async(_workQueue, ^{ + self->_proxyConnect = nil; + }); } -- (BOOL)_checkHandshake:(CFHTTPMessageRef)httpMessage; +- (BOOL)_checkHandshake:(CFHTTPMessageRef)httpMessage { NSString *acceptHeader = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(httpMessage, CFSTR("Sec-WebSocket-Accept"))); if (acceptHeader == nil) { return NO; } - + NSString *concattedString = [_secKey stringByAppendingString:SRWebSocketAppendToSecKeyString]; - NSString *expectedAccept = [concattedString stringBySHA1ThenBase64Encoding]; - + NSData *hashedString = SRSHA1HashFromString(concattedString); + NSString *expectedAccept = SRBase64EncodedStringFromData(hashedString); return [acceptHeader isEqualToString:expectedAccept]; } -- (void)_HTTPHeadersDidFinish; +- (void)_HTTPHeadersDidFinish:(CFHTTPMessageRef)httpMessage { - NSInteger responseCode = CFHTTPMessageGetResponseStatusCode(_receivedHTTPHeaders); - + NSInteger responseCode = CFHTTPMessageGetResponseStatusCode(httpMessage); if (responseCode >= 400) { - SRFastLog(@"Request failed with response code %d", responseCode); - [self _failWithError:[NSError errorWithDomain:@"org.lolrus.SocketRocket" code:2132 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"received bad response code from server %ld", (long)responseCode] forKey:NSLocalizedDescriptionKey]]]; + SRDebugLog(@"Request failed with response code %d", responseCode); + NSError *error = SRHTTPErrorWithCodeDescription(responseCode, 2132, + [NSString stringWithFormat:@"Received bad response code from server: %d.", + (int)responseCode]); + [self _failWithError:error]; return; - } - - if(![self _checkHandshake:_receivedHTTPHeaders]) { - [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain code:2133 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Invalid Sec-WebSocket-Accept response"] forKey:NSLocalizedDescriptionKey]]]; + + if(![self _checkHandshake:httpMessage]) { + NSError *error = SRErrorWithCodeDescription(2133, @"Invalid Sec-WebSocket-Accept response."); + [self _failWithError:error]; return; } - - NSString *negotiatedProtocol = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(_receivedHTTPHeaders, CFSTR("Sec-WebSocket-Protocol"))); + + NSString *negotiatedProtocol = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(httpMessage, CFSTR("Sec-WebSocket-Protocol"))); if (negotiatedProtocol) { // Make sure we requested the protocol if ([_requestedProtocols indexOfObject:negotiatedProtocol] == NSNotFound) { - [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain code:2133 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Server specified Sec-WebSocket-Protocol that wasn't requested"] forKey:NSLocalizedDescriptionKey]]]; + NSError *error = SRErrorWithCodeDescription(2133, @"Server specified Sec-WebSocket-Protocol that wasn't requested."); + [self _failWithError:error]; return; } - + _protocol = negotiatedProtocol; } - + self.readyState = SR_OPEN; - + if (!_didFail) { [self _readFrameNew]; } - [self _performDelegateBlock:^{ - if ([self.delegate respondsToSelector:@selector(webSocketDidOpen:)]) { - [self.delegate webSocketDidOpen:self]; - }; + [self.delegateController performDelegateBlock:^(id _Nullable delegate, SRDelegateAvailableMethods availableMethods) { + if (availableMethods.didOpen) { + [delegate webSocketDidOpen:self]; + } }]; } -- (void)_readHTTPHeader; +- (void)_readHTTPHeader { if (_receivedHTTPHeaders == NULL) { _receivedHTTPHeaders = CFHTTPMessageCreateEmpty(NULL, NO); } - - [self _readUntilHeaderCompleteWithCallback:^(SRWebSocket *self, NSData *data) { - CFHTTPMessageAppendBytes(_receivedHTTPHeaders, (const UInt8 *)data.bytes, data.length); - - if (CFHTTPMessageIsHeaderComplete(_receivedHTTPHeaders)) { - SRFastLog(@"Finished reading headers %@", CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(_receivedHTTPHeaders))); - [self _HTTPHeadersDidFinish]; + + [self _readUntilHeaderCompleteWithCallback:^(SRWebSocket *socket, NSData *data) { + if (!socket) { + return; + } + CFHTTPMessageRef receivedHTTPHeaders = socket->_receivedHTTPHeaders; + + CFHTTPMessageAppendBytes(receivedHTTPHeaders, (const UInt8 *)data.bytes, data.length); + + if (CFHTTPMessageIsHeaderComplete(receivedHTTPHeaders)) { + SRDebugLog(@"Finished reading headers %@", CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(receivedHTTPHeaders))); + [socket _HTTPHeadersDidFinish:receivedHTTPHeaders]; } else { - [self _readHTTPHeader]; + [socket _readHTTPHeader]; } }]; } - (void)didConnect { - SRFastLog(@"Connected"); - CFHTTPMessageRef request = CFHTTPMessageCreateRequest(NULL, CFSTR("GET"), (__bridge CFURLRef)_url, kCFHTTPVersion1_1); - - // Set host first so it defaults - CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Host"), (__bridge CFStringRef)(_url.port ? [NSString stringWithFormat:@"%@:%@", _url.host, _url.port] : _url.host)); - - NSMutableData *keyBytes = [[NSMutableData alloc] initWithLength:16]; - SecRandomCopyBytes(kSecRandomDefault, keyBytes.length, keyBytes.mutableBytes); - _secKey = keyBytes.base64Encoding; + SRDebugLog(@"Connected"); + + _secKey = SRBase64EncodedStringFromData(SRRandomData(16)); assert([_secKey length] == 24); - - CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Upgrade"), CFSTR("websocket")); - CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Connection"), CFSTR("Upgrade")); - CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Key"), (__bridge CFStringRef)_secKey); - CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Version"), (__bridge CFStringRef)[NSString stringWithFormat:@"%ld", (long)_webSocketVersion]); - - CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Origin"), (__bridge CFStringRef)_url.SR_origin); - - if (_requestedProtocols) { - CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Protocol"), (__bridge CFStringRef)[_requestedProtocols componentsJoinedByString:@", "]); - } - [_urlRequest.allHTTPHeaderFields enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { - CFHTTPMessageSetHeaderFieldValue(request, (__bridge CFStringRef)key, (__bridge CFStringRef)obj); - }]; - - NSData *message = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(request)); - - CFRelease(request); + CFHTTPMessageRef message = SRHTTPConnectMessageCreate(_urlRequest, + _secKey, + SRWebSocketProtocolVersion, + self.requestCookies, + _requestedProtocols); + + NSData *messageData = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(message)); - [self _writeData:message]; + CFRelease(message); + + [self _writeData:messageData]; [self _readHTTPHeader]; } -- (void)_initializeStreams; +- (void)_updateSecureStreamOptions { - assert(_url.port.unsignedIntValue <= UINT32_MAX); - uint32_t port = _url.port.unsignedIntValue; - if (port == 0) { - if (!_secure) { - port = 80; - } else { - port = 443; - } - } - NSString *host = _url.host; - - CFReadStreamRef readStream = NULL; - CFWriteStreamRef writeStream = NULL; - - CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream); - - _outputStream = CFBridgingRelease(writeStream); - _inputStream = CFBridgingRelease(readStream); - - - if (_secure) { - NSMutableDictionary *SSLOptions = [[NSMutableDictionary alloc] init]; - - [_outputStream setProperty:(__bridge id)kCFStreamSocketSecurityLevelNegotiatedSSL forKey:(__bridge id)kCFStreamPropertySocketSecurityLevel]; - - // If we're using pinned certs, don't validate the certificate chain - if ([_urlRequest SR_SSLPinnedCertificates].count) { - [SSLOptions setValue:[NSNumber numberWithBool:NO] forKey:(__bridge id)kCFStreamSSLValidatesCertificateChain]; - } - -#if DEBUG - [SSLOptions setValue:[NSNumber numberWithBool:NO] forKey:(__bridge id)kCFStreamSSLValidatesCertificateChain]; - NSLog(@"SocketRocket: In debug mode. Allowing connection to any root cert"); -#endif - - [_outputStream setProperty:SSLOptions - forKey:(__bridge id)kCFStreamPropertySSLSettings]; + if (_requestRequiresSSL) { + SRDebugLog(@"Setting up security for streams."); + [_securityPolicy updateSecurityOptionsInStream:_inputStream]; + [_securityPolicy updateSecurityOptionsInStream:_outputStream]; } - - _inputStream.delegate = self; - _outputStream.delegate = self; -} -- (void)_connect; -{ - if (!_scheduledRunloops.count) { - [self scheduleInRunLoop:[NSRunLoop SR_networkRunLoop] forMode:NSDefaultRunLoopMode]; + NSString *networkServiceType = SRStreamNetworkServiceTypeFromURLRequest(_urlRequest); + if (networkServiceType != nil) { + [_inputStream setProperty:networkServiceType forKey:NSStreamNetworkServiceType]; + [_outputStream setProperty:networkServiceType forKey:NSStreamNetworkServiceType]; } - - - [_outputStream open]; - [_inputStream open]; } -- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; +- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode { [_outputStream scheduleInRunLoop:aRunLoop forMode:mode]; [_inputStream scheduleInRunLoop:aRunLoop forMode:mode]; - + [_scheduledRunloops addObject:@[aRunLoop, mode]]; } -- (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; +- (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode { [_outputStream removeFromRunLoop:aRunLoop forMode:mode]; [_inputStream removeFromRunLoop:aRunLoop forMode:mode]; - + [_scheduledRunloops removeObject:@[aRunLoop, mode]]; } -- (void)close; +- (void)close { - [self closeWithCode:-1 reason:nil]; + [self closeWithCode:SRStatusCodeNormal reason:nil]; } -- (void)closeWithCode:(NSInteger)code reason:(NSString *)reason; +- (void)closeWithCode:(NSInteger)code reason:(NSString *)reason { assert(code); + __weak typeof(self) wself = self; dispatch_async(_workQueue, ^{ - if (self.readyState == SR_CLOSING || self.readyState == SR_CLOSED) { + __strong SRWebSocket *sself = wself; + if (!sself) { + return; + } + if (sself.readyState == SR_CLOSING || sself.readyState == SR_CLOSED) { return; } - - BOOL wasConnecting = self.readyState == SR_CONNECTING; - - self.readyState = SR_CLOSING; - - SRFastLog(@"Closing with code %d reason %@", code, reason); - + + BOOL wasConnecting = sself.readyState == SR_CONNECTING; + + sself.readyState = SR_CLOSING; + + SRDebugLog(@"Closing with code %d reason %@", code, reason); + if (wasConnecting) { - [self _disconnect]; + [sself closeConnection]; return; } size_t maxMsgSize = [reason maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding]; NSMutableData *mutablePayload = [[NSMutableData alloc] initWithLength:sizeof(uint16_t) + maxMsgSize]; NSData *payload = mutablePayload; - - ((uint16_t *)mutablePayload.mutableBytes)[0] = EndianU16_BtoN(code); - + + ((uint16_t *)mutablePayload.mutableBytes)[0] = CFSwapInt16BigToHost((uint16_t)code); + if (reason) { NSRange remainingRange = {0}; - + NSUInteger usedLength = 0; - + BOOL success = [reason getBytes:(char *)mutablePayload.mutableBytes + sizeof(uint16_t) maxLength:payload.length - sizeof(uint16_t) usedLength:&usedLength encoding:NSUTF8StringEncoding options:NSStringEncodingConversionExternalRepresentation range:NSMakeRange(0, reason.length) remainingRange:&remainingRange]; - +#pragma unused (success) + assert(success); assert(remainingRange.length == 0); @@ -654,92 +560,157 @@ - (void)closeWithCode:(NSInteger)code reason:(NSString *)reason; payload = [payload subdataWithRange:NSMakeRange(0, usedLength + sizeof(uint16_t))]; } } - - - [self _sendFrameWithOpcode:SROpCodeConnectionClose data:payload]; + + + [sself _sendFrameWithOpcode:SROpCodeConnectionClose data:payload]; }); } -- (void)_closeWithProtocolError:(NSString *)message; +- (void)_closeWithProtocolError:(NSString *)message { - // Need to shunt this on the _callbackQueue first to see if they received any messages - [self _performDelegateBlock:^{ + // Need to shunt this on the _callbackQueue first to see if they received any messages + [self.delegateController performDelegateQueueBlock:^{ [self closeWithCode:SRStatusCodeProtocolError reason:message]; - dispatch_async(_workQueue, ^{ - [self _disconnect]; + dispatch_async(self->_workQueue, ^{ + [self closeConnection]; }); }]; } -- (void)_failWithError:(NSError *)error; +- (void)_failWithError:(NSError *)error { dispatch_async(_workQueue, ^{ if (self.readyState != SR_CLOSED) { - _failed = YES; - [self _performDelegateBlock:^{ - if ([self.delegate respondsToSelector:@selector(webSocket:didFailWithError:)]) { - [self.delegate webSocket:self didFailWithError:error]; + self->_failed = YES; + [self.delegateController performDelegateBlock:^(id _Nullable delegate, SRDelegateAvailableMethods availableMethods) { + if (availableMethods.didFailWithError) { + [delegate webSocket:self didFailWithError:error]; } }]; self.readyState = SR_CLOSED; - _selfRetain = nil; - SRFastLog(@"Failing with error %@", error.localizedDescription); - - [self _disconnect]; + SRDebugLog(@"Failing with error %@", error.localizedDescription); + + [self closeConnection]; + [self _scheduleCleanup]; } }); } -- (void)_writeData:(NSData *)data; -{ +- (void)_writeData:(NSData *)data +{ [self assertOnWorkQueue]; if (_closeWhenFinishedWriting) { - return; + return; } - [_outputBuffer appendData:data]; + + __block NSData *strongData = data; + dispatch_data_t newData = dispatch_data_create(data.bytes, data.length, nil, ^{ + strongData = nil; + }); + (void)strongData; + _outputBuffer = dispatch_data_create_concat(_outputBuffer, newData); [self _pumpWriting]; } -- (void)send:(id)data; + +- (void)send:(nullable id)message +{ + if (!message) { + [self sendData:nil error:nil]; // Send Data, but it doesn't matter since we are going to send the same text frame with 0 length. + } else if ([message isKindOfClass:[NSString class]]) { + [self sendString:(NSString *_Nonnull)message error:nil]; + } else if ([message isKindOfClass:[NSData class]]) { + [self sendData:message error:nil]; + } else { + NSAssert(NO, @"Unrecognized message. Not able to send anything other than a String or NSData."); + } +} + +- (BOOL)sendString:(NSString *)string error:(NSError **)error +{ + if (self.readyState != SR_OPEN) { + NSString *message = @"Invalid State: Cannot call `sendString:error:` until connection is open."; + if (error) { + *error = SRErrorWithCodeDescription(2134, message); + } + SRDebugLog(message); + return NO; + } + + string = [string copy]; + dispatch_async(_workQueue, ^{ + [self _sendFrameWithOpcode:SROpCodeTextFrame data:[string dataUsingEncoding:NSUTF8StringEncoding]]; + }); + return YES; +} + +- (BOOL)sendData:(nullable NSData *)data error:(NSError **)error { - NSAssert(self.readyState != SR_CONNECTING, @"Invalid State: Cannot call send: until connection is open"); - // TODO: maybe not copy this for performance data = [data copy]; + return [self sendDataNoCopy:data error:error]; +} + +- (BOOL)sendDataNoCopy:(nullable NSData *)data error:(NSError **)error +{ + if (self.readyState != SR_OPEN) { + NSString *message = @"Invalid State: Cannot call `sendDataNoCopy:error:` until connection is open."; + if (error) { + *error = SRErrorWithCodeDescription(2134, message); + } + SRDebugLog(message); + return NO; + } + dispatch_async(_workQueue, ^{ - if ([data isKindOfClass:[NSString class]]) { - [self _sendFrameWithOpcode:SROpCodeTextFrame data:[(NSString *)data dataUsingEncoding:NSUTF8StringEncoding]]; - } else if ([data isKindOfClass:[NSData class]]) { + if (data) { [self _sendFrameWithOpcode:SROpCodeBinaryFrame data:data]; - } else if (data == nil) { - [self _sendFrameWithOpcode:SROpCodeTextFrame data:data]; } else { - assert(NO); + [self _sendFrameWithOpcode:SROpCodeTextFrame data:nil]; } }); + return YES; } -- (void)handlePing:(NSData *)pingData; +- (BOOL)sendPing:(nullable NSData *)data error:(NSError **)error { - // Need to pingpong this off _callbackQueue first to make sure messages happen in order - [self _performDelegateBlock:^{ - dispatch_async(_workQueue, ^{ - [self _sendFrameWithOpcode:SROpCodePong data:pingData]; - }); - }]; + if (self.readyState != SR_OPEN) { + NSString *message = @"Invalid State: Cannot call `sendPing:error:` until connection is open."; + if (error) { + *error = SRErrorWithCodeDescription(2134, message); + } + SRDebugLog(message); + return NO; + } + + data = [data copy] ?: [NSData data]; // It's okay for a ping to be empty + dispatch_async(_workQueue, ^{ + [self _sendFrameWithOpcode:SROpCodePing data:data]; + }); + return YES; } -- (void)handlePong; +- (void)_handlePingWithData:(nullable NSData *)data { - // NOOP + // Need to pingpong this off _callbackQueue first to make sure messages happen in order + [self.delegateController performDelegateBlock:^(id _Nullable delegate, SRDelegateAvailableMethods availableMethods) { + if (availableMethods.didReceivePing) { + [delegate webSocket:self didReceivePingWithData:data]; + } + dispatch_async(self->_workQueue, ^{ + [self _sendFrameWithOpcode:SROpCodePong data:data]; + }); + }]; } -- (void)_handleMessage:(id)message +- (void)handlePong:(NSData *)pongData { - SRFastLog(@"Received message"); - [self _performDelegateBlock:^{ - [self.delegate webSocket:self didReceiveMessage:message]; + SRDebugLog(@"Received pong"); + [self.delegateController performDelegateBlock:^(id _Nullable delegate, SRDelegateAvailableMethods availableMethods) { + if (availableMethods.didReceivePong) { + [delegate webSocket:self didReceivePong:pongData]; + } }]; } @@ -748,7 +719,7 @@ static inline BOOL closeCodeIsValid(int closeCode) { if (closeCode < 1000) { return NO; } - + if (closeCode >= 1000 && closeCode <= 1011) { if (closeCode == 1004 || closeCode == 1005 || @@ -757,11 +728,11 @@ static inline BOOL closeCodeIsValid(int closeCode) { } return YES; } - + if (closeCode >= 3000 && closeCode <= 3999) { return YES; } - + if (closeCode >= 4000 && closeCode <= 4999) { return YES; } @@ -778,20 +749,20 @@ static inline BOOL closeCodeIsValid(int closeCode) { // encoded data with value /reason/, the interpretation of which is not // defined by this specification. -- (void)handleCloseWithData:(NSData *)data; +- (void)handleCloseWithData:(NSData *)data { size_t dataSize = data.length; __block uint16_t closeCode = 0; - - SRFastLog(@"Received close frame"); - + + SRDebugLog(@"Received close frame"); + if (dataSize == 1) { // TODO handle error [self _closeWithProtocolError:@"Payload for close must be larger than 2 bytes"]; return; } else if (dataSize >= 2) { [data getBytes:&closeCode length:sizeof(closeCode)]; - _closeCode = EndianU16_BtoN(closeCode); + _closeCode = CFSwapInt16BigToHost(closeCode); if (!closeCodeIsValid(_closeCode)) { [self _closeWithProtocolError:[NSString stringWithFormat:@"Cannot have close code of %d", _closeCode]]; return; @@ -804,65 +775,95 @@ - (void)handleCloseWithData:(NSData *)data; } } } else { - _closeCode = SRStatusNoStatusReceived; + _closeCode = SRStatusCodeNoStatusReceived; } - + [self assertOnWorkQueue]; - + if (self.readyState == SR_OPEN) { [self closeWithCode:1000 reason:nil]; } dispatch_async(_workQueue, ^{ - [self _disconnect]; + [self closeConnection]; }); } -- (void)_disconnect; +- (void)closeConnection { [self assertOnWorkQueue]; - SRFastLog(@"Trying to disconnect"); + SRDebugLog(@"Trying to disconnect"); _closeWhenFinishedWriting = YES; [self _pumpWriting]; } -- (void)_handleFrameWithData:(NSData *)frameData opCode:(NSInteger)opcode; -{ +- (void)_handleFrameWithData:(NSData *)frameData opCode:(SROpCode)opcode +{ // Check that the current data is valid UTF8 - + BOOL isControlFrame = (opcode == SROpCodePing || opcode == SROpCodePong || opcode == SROpCodeConnectionClose); - if (!isControlFrame) { - [self _readFrameNew]; - } else { + if (isControlFrame) { + //frameData will be copied before passing to handlers + //otherwise there can be misbehaviours when value at the pointer is changed + frameData = [frameData copy]; + dispatch_async(_workQueue, ^{ [self _readFrameContinue]; }); + } else { + [self _readFrameNew]; } - + switch (opcode) { case SROpCodeTextFrame: { - NSString *str = [[NSString alloc] initWithData:frameData encoding:NSUTF8StringEncoding]; - if (str == nil && frameData) { - [self closeWithCode:SRStatusCodeInvalidUTF8 reason:@"Text frames must be valid UTF-8"]; + NSString *string = [[NSString alloc] initWithData:frameData encoding:NSUTF8StringEncoding]; + if (!string && frameData) { + [self closeWithCode:SRStatusCodeInvalidUTF8 reason:@"Text frames must be valid UTF-8."]; dispatch_async(_workQueue, ^{ - [self _disconnect]; + [self closeConnection]; }); - return; } - [self _handleMessage:str]; + SRDebugLog(@"Received text message."); + [self.delegateController performDelegateBlock:^(id _Nullable delegate, SRDelegateAvailableMethods availableMethods) { + // Don't convert into string - iff `delegate` tells us not to. Otherwise - create UTF8 string and handle that. + if (availableMethods.shouldConvertTextFrameToString && ![delegate webSocketShouldConvertTextFrameToString:self]) { + if (availableMethods.didReceiveMessage) { + [delegate webSocket:self didReceiveMessage:frameData]; + } + if (availableMethods.didReceiveMessageWithData) { + [delegate webSocket:self didReceiveMessageWithData:frameData]; + } + } else { + if (availableMethods.didReceiveMessage) { + [delegate webSocket:self didReceiveMessage:string]; + } + if (availableMethods.didReceiveMessageWithString) { + [delegate webSocket:self didReceiveMessageWithString:string]; + } + } + }]; break; } - case SROpCodeBinaryFrame: - [self _handleMessage:[frameData copy]]; + case SROpCodeBinaryFrame: { + SRDebugLog(@"Received data message."); + [self.delegateController performDelegateBlock:^(id _Nullable delegate, SRDelegateAvailableMethods availableMethods) { + if (availableMethods.didReceiveMessage) { + [delegate webSocket:self didReceiveMessage:frameData]; + } + if (availableMethods.didReceiveMessageWithData) { + [delegate webSocket:self didReceiveMessageWithData:frameData]; + } + }]; + } break; case SROpCodeConnectionClose: [self handleCloseWithData:frameData]; break; case SROpCodePing: - [self handlePing:frameData]; + [self _handlePingWithData:frameData]; break; case SROpCodePong: - [self handlePong]; + [self handlePong:frameData]; break; default: [self _closeWithProtocolError:[NSString stringWithFormat:@"Unknown opcode %ld", (long)opcode]]; @@ -871,32 +872,32 @@ - (void)_handleFrameWithData:(NSData *)frameData opCode:(NSInteger)opcode; } } -- (void)_handleFrameHeader:(frame_header)frame_header curData:(NSData *)curData; +- (void)_handleFrameHeader:(frame_header)frame_header curData:(NSData *)curData { assert(frame_header.opcode != 0); - - if (self.readyState != SR_OPEN) { + + if (self.readyState == SR_CLOSED) { return; } - - + + BOOL isControlFrame = (frame_header.opcode == SROpCodePing || frame_header.opcode == SROpCodePong || frame_header.opcode == SROpCodeConnectionClose); - + if (isControlFrame && !frame_header.fin) { [self _closeWithProtocolError:@"Fragmented control frames not allowed"]; return; } - + if (isControlFrame && frame_header.payload_length >= 126) { [self _closeWithProtocolError:@"Control frames cannot have payloads larger than 126 bytes"]; return; } - + if (!isControlFrame) { _currentFrameOpcode = frame_header.opcode; _currentFrameCount += 1; } - + if (frame_header.payload_length == 0) { if (isControlFrame) { [self _handleFrameWithData:curData opCode:frame_header.opcode]; @@ -909,18 +910,20 @@ - (void)_handleFrameHeader:(frame_header)frame_header curData:(NSData *)curData; } } } else { - assert(frame_header.payload_length <= SIZE_T_MAX); - [self _addConsumerWithDataLength:(size_t)frame_header.payload_length callback:^(SRWebSocket *self, NSData *newData) { + if (frame_header.payload_length > SRWebSocketMaxFramePayloadLength) { + [self _closeWithProtocolError:@"Payload length too large."]; + return; + } + [self _addConsumerWithDataLength:(size_t)frame_header.payload_length callback:^(SRWebSocket *sself, NSData *newData) { if (isControlFrame) { - [self _handleFrameWithData:newData opCode:frame_header.opcode]; + [sself _handleFrameWithData:newData opCode:frame_header.opcode]; } else { if (frame_header.fin) { - [self _handleFrameWithData:self->_currentFrameData opCode:frame_header.opcode]; + [sself _handleFrameWithData:sself->_currentFrameData opCode:frame_header.opcode]; } else { // TODO add assert that opcode is not a control; - [self _readFrameContinue]; + [sself _readFrameContinue]; } - } } readToCurrentFrame:!isControlFrame unmaskBytes:frame_header.masked]; } @@ -955,188 +958,252 @@ - (void)_handleFrameHeader:(frame_header)frame_header curData:(NSData *)curData; static const uint8_t SRPayloadLenMask = 0x7F; -- (void)_readFrameContinue; +- (void)_readFrameContinue { assert((_currentFrameCount == 0 && _currentFrameOpcode == 0) || (_currentFrameCount > 0 && _currentFrameOpcode > 0)); - [self _addConsumerWithDataLength:2 callback:^(SRWebSocket *self, NSData *data) { + [self _addConsumerWithDataLength:2 callback:^(SRWebSocket *sself, NSData *data) { __block frame_header header = {0}; - + const uint8_t *headerBuffer = data.bytes; assert(data.length >= 2); - + if (headerBuffer[0] & SRRsvMask) { - [self _closeWithProtocolError:@"Server used RSV bits"]; + [sself _closeWithProtocolError:@"Server used RSV bits"]; return; } - + uint8_t receivedOpcode = (SROpCodeMask & headerBuffer[0]); - + BOOL isControlFrame = (receivedOpcode == SROpCodePing || receivedOpcode == SROpCodePong || receivedOpcode == SROpCodeConnectionClose); - - if (!isControlFrame && receivedOpcode != 0 && self->_currentFrameCount > 0) { - [self _closeWithProtocolError:@"all data frames after the initial data frame must have opcode 0"]; + + if (!isControlFrame && receivedOpcode != 0 && sself->_currentFrameCount > 0) { + [sself _closeWithProtocolError:@"all data frames after the initial data frame must have opcode 0"]; return; } - - if (receivedOpcode == 0 && self->_currentFrameCount == 0) { - [self _closeWithProtocolError:@"cannot continue a message"]; + + if (receivedOpcode == 0 && sself->_currentFrameCount == 0) { + [sself _closeWithProtocolError:@"cannot continue a message"]; return; } - - header.opcode = receivedOpcode == 0 ? self->_currentFrameOpcode : receivedOpcode; - + + header.opcode = receivedOpcode == 0 ? sself->_currentFrameOpcode : receivedOpcode; + header.fin = !!(SRFinMask & headerBuffer[0]); - - + + header.masked = !!(SRMaskMask & headerBuffer[1]); header.payload_length = SRPayloadLenMask & headerBuffer[1]; - + headerBuffer = NULL; - + if (header.masked) { - [self _closeWithProtocolError:@"Client must receive unmasked data"]; + [sself _closeWithProtocolError:@"Client must receive unmasked data"]; + return; } - - size_t extra_bytes_needed = header.masked ? sizeof(_currentReadMaskKey) : 0; - + + size_t extra_bytes_needed = header.masked ? sizeof(sself->_currentReadMaskKey) : 0; + if (header.payload_length == 126) { extra_bytes_needed += sizeof(uint16_t); } else if (header.payload_length == 127) { extra_bytes_needed += sizeof(uint64_t); } - + if (extra_bytes_needed == 0) { - [self _handleFrameHeader:header curData:self->_currentFrameData]; + [sself _handleFrameHeader:header curData:sself->_currentFrameData]; } else { - [self _addConsumerWithDataLength:extra_bytes_needed callback:^(SRWebSocket *self, NSData *data) { - size_t mapped_size = data.length; - const void *mapped_buffer = data.bytes; + [sself _addConsumerWithDataLength:extra_bytes_needed callback:^(SRWebSocket *eself, NSData *edata) { + size_t mapped_size = edata.length; +#pragma unused (mapped_size) + const void *mapped_buffer = edata.bytes; size_t offset = 0; - + if (header.payload_length == 126) { assert(mapped_size >= sizeof(uint16_t)); - uint16_t newLen = EndianU16_BtoN(*(uint16_t *)(mapped_buffer)); - header.payload_length = newLen; + uint16_t payloadLength = 0; + memcpy(&payloadLength, mapped_buffer, sizeof(uint16_t)); + payloadLength = CFSwapInt16BigToHost(payloadLength); + + header.payload_length = payloadLength; offset += sizeof(uint16_t); } else if (header.payload_length == 127) { assert(mapped_size >= sizeof(uint64_t)); - header.payload_length = EndianU64_BtoN(*(uint64_t *)(mapped_buffer)); + uint64_t payloadLength = 0; + memcpy(&payloadLength, mapped_buffer, sizeof(uint64_t)); + payloadLength = CFSwapInt64BigToHost(payloadLength); + + header.payload_length = payloadLength; offset += sizeof(uint64_t); } else { assert(header.payload_length < 126 && header.payload_length >= 0); } - - + if (header.masked) { - assert(mapped_size >= sizeof(_currentReadMaskOffset) + offset); - memcpy(self->_currentReadMaskKey, ((uint8_t *)mapped_buffer) + offset, sizeof(self->_currentReadMaskKey)); + assert(mapped_size >= sizeof(eself->_currentReadMaskOffset) + offset); + memcpy(eself->_currentReadMaskKey, ((uint8_t *)mapped_buffer) + offset, sizeof(eself->_currentReadMaskKey)); } - - [self _handleFrameHeader:header curData:self->_currentFrameData]; + + [eself _handleFrameHeader:header curData:eself->_currentFrameData]; } readToCurrentFrame:NO unmaskBytes:NO]; } } readToCurrentFrame:NO unmaskBytes:NO]; } -- (void)_readFrameNew; +- (void)_readFrameNew { dispatch_async(_workQueue, ^{ - [_currentFrameData setLength:0]; - - _currentFrameOpcode = 0; - _currentFrameCount = 0; - _readOpCount = 0; - _currentStringScanPosition = 0; - + // Don't reset the length, since Apple doesn't guarantee that this will free the memory (and in tests on + // some platforms, it doesn't seem to, effectively causing a leak the size of the biggest frame so far). + self->_currentFrameData = [[NSMutableData alloc] init]; + + self->_currentFrameOpcode = 0; + self->_currentFrameCount = 0; + self->_readOpCount = 0; + self->_currentStringScanPosition = 0; + [self _readFrameContinue]; }); } -- (void)_pumpWriting; +- (void)_pumpWriting { [self assertOnWorkQueue]; - - NSUInteger dataLength = _outputBuffer.length; + + NSUInteger dataLength = dispatch_data_get_size(_outputBuffer); if (dataLength - _outputBufferOffset > 0 && _outputStream.hasSpaceAvailable) { - NSInteger bytesWritten = [_outputStream write:_outputBuffer.bytes + _outputBufferOffset maxLength:dataLength - _outputBufferOffset]; - if (bytesWritten == -1) { - [self _failWithError:[NSError errorWithDomain:@"org.lolrus.SocketRocket" code:2145 userInfo:[NSDictionary dictionaryWithObject:@"Error writing to stream" forKey:NSLocalizedDescriptionKey]]]; - return; + __block NSInteger bytesWritten = 0; + __block BOOL streamFailed = NO; + + dispatch_data_t dataToSend = dispatch_data_create_subrange(_outputBuffer, _outputBufferOffset, dataLength - _outputBufferOffset); + dispatch_data_apply(dataToSend, ^bool(dispatch_data_t region, size_t offset, const void *buffer, size_t size) { + NSInteger sentLength = [_outputStream write:buffer maxLength:size]; + if (sentLength == -1) { + streamFailed = YES; + return false; + } + bytesWritten += sentLength; + return (sentLength >= (NSInteger)size); // If we can't write all the data into the stream - bail-out early. + }); + if (streamFailed) { + NSInteger code = 2145; + NSString *description = @"Error writing to stream."; + NSError *streamError = _outputStream.streamError; + NSError *error = streamError ? SRErrorWithCodeDescriptionUnderlyingError(code, description, streamError) : SRErrorWithCodeDescription(code, description); + [self _failWithError:error]; + return; } - + _outputBufferOffset += bytesWritten; - - if (_outputBufferOffset > 4096 && _outputBufferOffset > (_outputBuffer.length >> 1)) { - _outputBuffer = [[NSMutableData alloc] initWithBytes:(char *)_outputBuffer.bytes + _outputBufferOffset length:_outputBuffer.length - _outputBufferOffset]; + + if (_outputBufferOffset > SRDefaultBufferSize() && _outputBufferOffset > dataLength / 2) { + _outputBuffer = dispatch_data_create_subrange(_outputBuffer, _outputBufferOffset, dataLength - _outputBufferOffset); _outputBufferOffset = 0; } } - - if (_closeWhenFinishedWriting && - _outputBuffer.length - _outputBufferOffset == 0 && + + if (_closeWhenFinishedWriting && + (dispatch_data_get_size(_outputBuffer) - _outputBufferOffset) == 0 && (_inputStream.streamStatus != NSStreamStatusNotOpen && _inputStream.streamStatus != NSStreamStatusClosed) && !_sentClose) { _sentClose = YES; - - [_outputStream close]; - [_inputStream close]; - - - for (NSArray *runLoop in [_scheduledRunloops copy]) { - [self unscheduleFromRunLoop:[runLoop objectAtIndex:0] forMode:[runLoop objectAtIndex:1]]; + + @synchronized(self) { + [_outputStream close]; + [_inputStream close]; + + + for (NSArray *runLoop in [_scheduledRunloops copy]) { + [self unscheduleFromRunLoop:[runLoop objectAtIndex:0] forMode:[runLoop objectAtIndex:1]]; + } } - + if (!_failed) { - [self _performDelegateBlock:^{ - if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) { - [self.delegate webSocket:self didCloseWithCode:_closeCode reason:_closeReason wasClean:YES]; + self.readyState = SR_CLOSED; + [self.delegateController performDelegateBlock:^(id _Nullable delegate, SRDelegateAvailableMethods availableMethods) { + if (availableMethods.didCloseWithCode) { + [delegate webSocket:self didCloseWithCode:self->_closeCode reason:self->_closeReason wasClean:YES]; } }]; } - - _selfRetain = nil; + + [self _scheduleCleanup]; } } -- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback; +- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback { [self assertOnWorkQueue]; [self _addConsumerWithScanner:consumer callback:callback dataLength:0]; } -- (void)_addConsumerWithDataLength:(size_t)dataLength callback:(data_callback)callback readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; -{ +- (void)_addConsumerWithDataLength:(size_t)dataLength callback:(data_callback)callback readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes +{ [self assertOnWorkQueue]; assert(dataLength); - + [_consumers addObject:[_consumerPool consumerWithScanner:nil handler:callback bytesNeeded:dataLength readToCurrentFrame:readToCurrentFrame unmaskBytes:unmaskBytes]]; [self _pumpScanner]; } -- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback dataLength:(size_t)dataLength; -{ +- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback dataLength:(size_t)dataLength +{ [self assertOnWorkQueue]; [_consumers addObject:[_consumerPool consumerWithScanner:consumer handler:callback bytesNeeded:dataLength readToCurrentFrame:NO unmaskBytes:NO]]; [self _pumpScanner]; } +- (void)_scheduleCleanup +{ + @synchronized(self) { + if (_cleanupScheduled) { + return; + } + + _cleanupScheduled = YES; + + // Cleanup NSStream delegate's in the same RunLoop used by the streams themselves: + // This way we'll prevent race conditions between handleEvent and SRWebsocket's dealloc + NSTimer *timer = [NSTimer timerWithTimeInterval:(0.0f) target:self selector:@selector(_cleanupSelfReference:) userInfo:nil repeats:NO]; + [[NSRunLoop SR_networkRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; + } +} + +- (void)_cleanupSelfReference:(NSTimer *)timer +{ + @synchronized(self) { + // Nuke NSStream delegate's + _inputStream.delegate = nil; + _outputStream.delegate = nil; + + // Remove the streams, right now, from the networkRunLoop + [_inputStream close]; + [_outputStream close]; + } + + // Cleanup selfRetain in the same GCD queue as usual + dispatch_async(_workQueue, ^{ + self->_selfRetain = nil; + }); +} + + static const char CRLFCRLFBytes[] = {'\r', '\n', '\r', '\n'}; -- (void)_readUntilHeaderCompleteWithCallback:(data_callback)dataHandler; +- (void)_readUntilHeaderCompleteWithCallback:(data_callback)dataHandler { [self _readUntilBytes:CRLFCRLFBytes length:sizeof(CRLFCRLFBytes) callback:dataHandler]; } -- (void)_readUntilBytes:(const void *)bytes length:(size_t)length callback:(data_callback)dataHandler; +- (void)_readUntilBytes:(const void *)bytes length:(size_t)length callback:(data_callback)dataHandler { // TODO optimize so this can continue from where we last searched stream_scanner consumer = ^size_t(NSData *data) { __block size_t found_size = 0; __block size_t match_count = 0; - + size_t size = data.length; const unsigned char *buffer = data.bytes; for (size_t i = 0; i < size; i++ ) { @@ -1158,30 +1225,32 @@ - (void)_readUntilBytes:(const void *)bytes length:(size_t)length callback:(data // Returns true if did work - (BOOL)_innerPumpScanner { - + BOOL didWork = NO; - - if (self.readyState >= SR_CLOSING) { + + if (self.readyState >= SR_CLOSED) { return didWork; } - + + size_t readBufferSize = dispatch_data_get_size(_readBuffer); + if (!_consumers.count) { return didWork; } - - size_t curSize = _readBuffer.length - _readBufferOffset; + + size_t curSize = readBufferSize - _readBufferOffset; if (!curSize) { return didWork; } - + SRIOConsumer *consumer = [_consumers objectAtIndex:0]; - + size_t bytesNeeded = consumer.bytesNeeded; - + size_t foundSize = 0; if (consumer.consumer) { - NSData *tempView = [NSData dataWithBytesNoCopy:(char *)_readBuffer.bytes + _readBufferOffset length:_readBuffer.length - _readBufferOffset freeWhenDone:NO]; - foundSize = consumer.consumer(tempView); + NSData *subdata = (NSData *)dispatch_data_create_subrange(_readBuffer, _readBufferOffset, readBufferSize - _readBufferOffset); + foundSize = consumer.consumer(subdata); } else { assert(consumer.bytesNeeded); if (curSize >= bytesNeeded) { @@ -1190,63 +1259,67 @@ - (BOOL)_innerPumpScanner { foundSize = curSize; } } - - NSData *slice = nil; + if (consumer.readToCurrentFrame || foundSize) { - NSRange sliceRange = NSMakeRange(_readBufferOffset, foundSize); - slice = [_readBuffer subdataWithRange:sliceRange]; - + dispatch_data_t slice = dispatch_data_create_subrange(_readBuffer, _readBufferOffset, foundSize); + _readBufferOffset += foundSize; - - if (_readBufferOffset > 4096 && _readBufferOffset > (_readBuffer.length >> 1)) { - _readBuffer = [[NSMutableData alloc] initWithBytes:(char *)_readBuffer.bytes + _readBufferOffset length:_readBuffer.length - _readBufferOffset]; _readBufferOffset = 0; + + if (_readBufferOffset > SRDefaultBufferSize() && _readBufferOffset > readBufferSize / 2) { + _readBuffer = dispatch_data_create_subrange(_readBuffer, _readBufferOffset, readBufferSize - _readBufferOffset); + _readBufferOffset = 0; } - + if (consumer.unmaskBytes) { - NSMutableData *mutableSlice = [slice mutableCopy]; - + __block NSMutableData *mutableSlice = [slice mutableCopy]; + NSUInteger len = mutableSlice.length; uint8_t *bytes = mutableSlice.mutableBytes; - + for (NSUInteger i = 0; i < len; i++) { bytes[i] = bytes[i] ^ _currentReadMaskKey[_currentReadMaskOffset % sizeof(_currentReadMaskKey)]; _currentReadMaskOffset += 1; } - - slice = mutableSlice; + + slice = dispatch_data_create(bytes, len, nil, ^{ + mutableSlice = nil; + }); } - + if (consumer.readToCurrentFrame) { - [_currentFrameData appendData:slice]; - + dispatch_data_apply(slice, ^bool(dispatch_data_t region, size_t offset, const void *buffer, size_t size) { + [_currentFrameData appendBytes:buffer length:size]; + return true; + }); + _readOpCount += 1; - + if (_currentFrameOpcode == SROpCodeTextFrame) { // Validate UTF8 stuff. size_t currentDataSize = _currentFrameData.length; if (_currentFrameOpcode == SROpCodeTextFrame && currentDataSize > 0) { // TODO: Optimize the crap out of this. Don't really have to copy all the data each time - + size_t scanSize = currentDataSize - _currentStringScanPosition; - + NSData *scan_data = [_currentFrameData subdataWithRange:NSMakeRange(_currentStringScanPosition, scanSize)]; int32_t valid_utf8_size = validate_dispatch_data_partial_string(scan_data); - + if (valid_utf8_size == -1) { [self closeWithCode:SRStatusCodeInvalidUTF8 reason:@"Text frames must be valid UTF-8"]; dispatch_async(_workQueue, ^{ - [self _disconnect]; + [self closeConnection]; }); return didWork; } else { _currentStringScanPosition += valid_utf8_size; } - } - + } + } - + consumer.bytesNeeded -= foundSize; - + if (consumer.bytesNeeded == 0) { [_consumers removeObjectAtIndex:0]; consumer.handler(self, nil); @@ -1255,7 +1328,7 @@ - (BOOL)_innerPumpScanner { } } else if (foundSize) { [_consumers removeObjectAtIndex:0]; - consumer.handler(self, slice); + consumer.handler(self, (NSData *)slice); [_consumerPool returnConsumer:consumer]; didWork = YES; } @@ -1263,20 +1336,20 @@ - (BOOL)_innerPumpScanner { return didWork; } --(void)_pumpScanner; +-(void)_pumpScanner { [self assertOnWorkQueue]; - + if (!_isPumping) { _isPumping = YES; } else { return; } - + while ([self _innerPumpScanner]) { - + } - + _isPumping = NO; } @@ -1284,331 +1357,243 @@ -(void)_pumpScanner; static const size_t SRFrameHeaderOverhead = 32; -- (void)_sendFrameWithOpcode:(SROpCode)opcode data:(id)data; +- (void)_sendFrameWithOpcode:(SROpCode)opCode data:(NSData *)data { [self assertOnWorkQueue]; - - NSAssert(data == nil || [data isKindOfClass:[NSData class]] || [data isKindOfClass:[NSString class]], @"Function expects nil, NSString or NSData"); - - size_t payloadLength = [data isKindOfClass:[NSString class]] ? [(NSString *)data lengthOfBytesUsingEncoding:NSUTF8StringEncoding] : [data length]; - - NSMutableData *frame = [[NSMutableData alloc] initWithLength:payloadLength + SRFrameHeaderOverhead]; - if (!frame) { + + if (!data) { + return; + } + + size_t payloadLength = data.length; + + NSMutableData *frameData = [[NSMutableData alloc] initWithLength:payloadLength + SRFrameHeaderOverhead]; + if (!frameData) { [self closeWithCode:SRStatusCodeMessageTooBig reason:@"Message too big"]; return; } - uint8_t *frame_buffer = (uint8_t *)[frame mutableBytes]; - + uint8_t *frameBuffer = (uint8_t *)frameData.mutableBytes; + // set fin - frame_buffer[0] = SRFinMask | opcode; - - BOOL useMask = YES; -#ifdef NOMASK - useMask = NO; -#endif - - if (useMask) { + frameBuffer[0] = SRFinMask | opCode; + // set the mask and header - frame_buffer[1] |= SRMaskMask; - } - - size_t frame_buffer_size = 2; - - const uint8_t *unmasked_payload = NULL; - if ([data isKindOfClass:[NSData class]]) { - unmasked_payload = (uint8_t *)[data bytes]; - } else if ([data isKindOfClass:[NSString class]]) { - unmasked_payload = (const uint8_t *)[data UTF8String]; - } else { - assert(NO); - } - + frameBuffer[1] |= SRMaskMask; + + size_t frameBufferSize = 2; + if (payloadLength < 126) { - frame_buffer[1] |= payloadLength; - } else if (payloadLength <= UINT16_MAX) { - frame_buffer[1] |= 126; - *((uint16_t *)(frame_buffer + frame_buffer_size)) = EndianU16_BtoN((uint16_t)payloadLength); - frame_buffer_size += sizeof(uint16_t); + frameBuffer[1] |= payloadLength; } else { - frame_buffer[1] |= 127; - *((uint64_t *)(frame_buffer + frame_buffer_size)) = EndianU64_BtoN((uint64_t)payloadLength); - frame_buffer_size += sizeof(uint64_t); - } - - if (!useMask) { - for (size_t i = 0; i < payloadLength; i++) { - frame_buffer[frame_buffer_size] = unmasked_payload[i]; - frame_buffer_size += 1; - } - } else { - uint8_t *mask_key = frame_buffer + frame_buffer_size; - SecRandomCopyBytes(kSecRandomDefault, sizeof(uint32_t), (uint8_t *)mask_key); - frame_buffer_size += sizeof(uint32_t); - - // TODO: could probably optimize this with SIMD - for (size_t i = 0; i < payloadLength; i++) { - frame_buffer[frame_buffer_size] = unmasked_payload[i] ^ mask_key[i % sizeof(uint32_t)]; - frame_buffer_size += 1; + uint64_t declaredPayloadLength = 0; + size_t declaredPayloadLengthSize = 0; + + if (payloadLength <= UINT16_MAX) { + frameBuffer[1] |= 126; + + declaredPayloadLength = CFSwapInt16BigToHost((uint16_t)payloadLength); + declaredPayloadLengthSize = sizeof(uint16_t); + } else { + frameBuffer[1] |= 127; + + declaredPayloadLength = CFSwapInt64BigToHost((uint64_t)payloadLength); + declaredPayloadLengthSize = sizeof(uint64_t); } + + memcpy((frameBuffer + frameBufferSize), &declaredPayloadLength, declaredPayloadLengthSize); + frameBufferSize += declaredPayloadLengthSize; } - assert(frame_buffer_size <= [frame length]); - frame.length = frame_buffer_size; - - [self _writeData:frame]; + const uint8_t *unmaskedPayloadBuffer = (uint8_t *)data.bytes; + uint8_t *maskKey = frameBuffer + frameBufferSize; + + size_t randomBytesSize = sizeof(uint32_t); + NSData *randomData = SRRandomData(randomBytesSize); + [randomData getBytes:maskKey range:NSMakeRange(0, randomBytesSize)]; + frameBufferSize += randomBytesSize; + + // Copy and unmask the buffer + uint8_t *frameBufferPayloadPointer = frameBuffer + frameBufferSize; + + memcpy(frameBufferPayloadPointer, unmaskedPayloadBuffer, payloadLength); + SRMaskBytesSIMD(frameBufferPayloadPointer, payloadLength, maskKey); + frameBufferSize += payloadLength; + + assert(frameBufferSize <= frameData.length); + frameData.length = frameBufferSize; + + [self _writeData:frameData]; } -- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode; +- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode { - if (_secure && !_pinnedCertFound && (eventCode == NSStreamEventHasBytesAvailable || eventCode == NSStreamEventHasSpaceAvailable)) { - - NSArray *sslCerts = [_urlRequest SR_SSLPinnedCertificates]; - if (sslCerts) { - SecTrustRef secTrust = (__bridge SecTrustRef)[aStream propertyForKey:(__bridge id)kCFStreamPropertySSLPeerTrust]; - if (secTrust) { - NSInteger numCerts = SecTrustGetCertificateCount(secTrust); - for (NSInteger i = 0; i < numCerts && !_pinnedCertFound; i++) { - SecCertificateRef cert = SecTrustGetCertificateAtIndex(secTrust, i); - NSData *certData = CFBridgingRelease(SecCertificateCopyData(cert)); - - for (id ref in sslCerts) { - SecCertificateRef trustedCert = (__bridge SecCertificateRef)ref; - NSData *trustedCertData = CFBridgingRelease(SecCertificateCopyData(trustedCert)); - - if ([trustedCertData isEqualToData:certData]) { - _pinnedCertFound = YES; - break; - } - } - } - } - - if (!_pinnedCertFound) { + __weak typeof(self) wself = self; + + if (_requestRequiresSSL && !_streamSecurityValidated && + (eventCode == NSStreamEventHasBytesAvailable || eventCode == NSStreamEventHasSpaceAvailable)) { + SecTrustRef trust = (__bridge SecTrustRef)[aStream propertyForKey:(__bridge id)kCFStreamPropertySSLPeerTrust]; + if (trust) { + NSString *const host = _urlRequest.URL.host; + if (!host || host.length == 0) { dispatch_async(_workQueue, ^{ - [self _failWithError:[NSError errorWithDomain:@"org.lolrus.SocketRocket" code:23556 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Invalid server cert"] forKey:NSLocalizedDescriptionKey]]]; + NSError *error = SRErrorWithDomainCodeDescription(NSURLErrorDomain, + NSURLErrorBadURL, + @"Unable to validate certificate for empty host."); + [wself _failWithError:error]; }); return; } + _streamSecurityValidated = [_securityPolicy evaluateServerTrust:trust forDomain:host]; } + if (!_streamSecurityValidated) { + dispatch_async(_workQueue, ^{ + NSError *error = SRErrorWithDomainCodeDescription(NSURLErrorDomain, + NSURLErrorClientCertificateRejected, + @"Invalid server certificate."); + [wself _failWithError:error]; + }); + return; + } + dispatch_async(_workQueue, ^{ + [self didConnect]; + }); } - dispatch_async(_workQueue, ^{ - switch (eventCode) { - case NSStreamEventOpenCompleted: { - SRFastLog(@"NSStreamEventOpenCompleted %@", aStream); - if (self.readyState >= SR_CLOSING) { - return; - } - assert(_readBuffer); - - if (self.readyState == SR_CONNECTING && aStream == _inputStream) { - [self didConnect]; - } - [self _pumpWriting]; - [self _pumpScanner]; - break; + [wself safeHandleEvent:eventCode stream:aStream]; + }); +} + +- (void)safeHandleEvent:(NSStreamEvent)eventCode stream:(NSStream *)aStream +{ + switch (eventCode) { + case NSStreamEventOpenCompleted: { + SRDebugLog(@"NSStreamEventOpenCompleted %@", aStream); + if (self.readyState >= SR_CLOSING) { + return; } - - case NSStreamEventErrorOccurred: { - SRFastLog(@"NSStreamEventErrorOccurred %@ %@", aStream, [[aStream streamError] copy]); - /// TODO specify error better! - [self _failWithError:aStream.streamError]; - _readBufferOffset = 0; - [_readBuffer setLength:0]; - break; - + assert(_readBuffer); + + if (!_requestRequiresSSL && self.readyState == SR_CONNECTING && aStream == _inputStream) { + [self didConnect]; } - - case NSStreamEventEndEncountered: { - [self _pumpScanner]; - SRFastLog(@"NSStreamEventEndEncountered %@", aStream); - if (aStream.streamError) { - [self _failWithError:aStream.streamError]; - } else { + + [self _pumpWriting]; + [self _pumpScanner]; + + break; + } + + case NSStreamEventErrorOccurred: { + SRDebugLog(@"NSStreamEventErrorOccurred %@ %@", aStream, [[aStream streamError] copy]); + /// TODO specify error better! + [self _failWithError:aStream.streamError]; + _readBufferOffset = 0; + _readBuffer = dispatch_data_empty; + break; + + } + + case NSStreamEventEndEncountered: { + [self _pumpScanner]; + SRDebugLog(@"NSStreamEventEndEncountered %@", aStream); + if (aStream.streamError) { + [self _failWithError:aStream.streamError]; + } else { + dispatch_async(_workQueue, ^{ if (self.readyState != SR_CLOSED) { self.readyState = SR_CLOSED; - _selfRetain = nil; + [self _scheduleCleanup]; } - if (!_sentClose && !_failed) { - _sentClose = YES; + if (!self->_sentClose && !self->_failed) { + self->_sentClose = YES; // If we get closed in this state it's probably not clean because we should be sending this when we send messages - [self _performDelegateBlock:^{ - if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) { - [self.delegate webSocket:self didCloseWithCode:0 reason:@"Stream end encountered" wasClean:NO]; + [self.delegateController performDelegateBlock:^(id _Nullable delegate, SRDelegateAvailableMethods availableMethods) { + if (availableMethods.didCloseWithCode) { + [delegate webSocket:self + didCloseWithCode:SRStatusCodeGoingAway + reason:@"Stream end encountered" + wasClean:NO]; } }]; } - } - - break; + }); } - - case NSStreamEventHasBytesAvailable: { - SRFastLog(@"NSStreamEventHasBytesAvailable %@", aStream); - const int bufferSize = 2048; - uint8_t buffer[bufferSize]; - - while (_inputStream.hasBytesAvailable) { - NSInteger bytes_read = [_inputStream read:buffer maxLength:bufferSize]; - - if (bytes_read > 0) { - [_readBuffer appendBytes:buffer length:bytes_read]; - } else if (bytes_read < 0) { - [self _failWithError:_inputStream.streamError]; - } - - if (bytes_read != bufferSize) { - break; + + break; + } + + case NSStreamEventHasBytesAvailable: { + SRDebugLog(@"NSStreamEventHasBytesAvailable %@", aStream); + uint8_t buffer[SRDefaultBufferSize()]; + + while (_inputStream.hasBytesAvailable) { + NSInteger bytesRead = [_inputStream read:buffer maxLength:SRDefaultBufferSize()]; + if (bytesRead > 0) { + dispatch_data_t data = dispatch_data_create(buffer, bytesRead, nil, DISPATCH_DATA_DESTRUCTOR_DEFAULT); + if (!data) { + NSError *error = SRErrorWithCodeDescription(SRStatusCodeMessageTooBig, + @"Unable to allocate memory to read from socket."); + [self _failWithError:error]; + return; } - }; - [self _pumpScanner]; - break; - } - - case NSStreamEventHasSpaceAvailable: { - SRFastLog(@"NSStreamEventHasSpaceAvailable %@", aStream); - [self _pumpWriting]; - break; + _readBuffer = dispatch_data_create_concat(_readBuffer, data); + } else if (bytesRead == -1) { + [self _failWithError:_inputStream.streamError]; + } } - - default: - SRFastLog(@"(default) %@", aStream); - break; + [self _pumpScanner]; + break; } - }); -} - -@end - - -@implementation SRIOConsumer - -@synthesize bytesNeeded = _bytesNeeded; -@synthesize consumer = _scanner; -@synthesize handler = _handler; -@synthesize readToCurrentFrame = _readToCurrentFrame; -@synthesize unmaskBytes = _unmaskBytes; - -- (void)setupWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; -{ - _scanner = [scanner copy]; - _handler = [handler copy]; - _bytesNeeded = bytesNeeded; - _readToCurrentFrame = readToCurrentFrame; - _unmaskBytes = unmaskBytes; - assert(_scanner || _bytesNeeded); -} - -@end - - -@implementation SRIOConsumerPool { - NSUInteger _poolSize; - NSMutableArray *_bufferedConsumers; -} + case NSStreamEventHasSpaceAvailable: { + SRDebugLog(@"NSStreamEventHasSpaceAvailable %@", aStream); + [self _pumpWriting]; + break; + } -- (id)initWithBufferCapacity:(NSUInteger)poolSize; -{ - self = [super init]; - if (self) { - _poolSize = poolSize; - _bufferedConsumers = [[NSMutableArray alloc] initWithCapacity:poolSize]; + case NSStreamEventNone: + SRDebugLog(@"(default) %@", aStream); + break; } - return self; } -- (id)init -{ - return [self initWithBufferCapacity:8]; -} +///-------------------------------------- +#pragma mark - Delegate +///-------------------------------------- -- (SRIOConsumer *)consumerWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +- (id _Nullable)delegate { - SRIOConsumer *consumer = nil; - if (_bufferedConsumers.count) { - consumer = [_bufferedConsumers lastObject]; - [_bufferedConsumers removeLastObject]; - } else { - consumer = [[SRIOConsumer alloc] init]; - } - - [consumer setupWithScanner:scanner handler:handler bytesNeeded:bytesNeeded readToCurrentFrame:readToCurrentFrame unmaskBytes:unmaskBytes]; - - return consumer; + return self.delegateController.delegate; } -- (void)returnConsumer:(SRIOConsumer *)consumer; +- (void)setDelegate:(id _Nullable)delegate { - if (_bufferedConsumers.count < _poolSize) { - [_bufferedConsumers addObject:consumer]; - } + self.delegateController.delegate = delegate; } -@end - - -@implementation NSURLRequest (CertificateAdditions) - -- (NSArray *)SR_SSLPinnedCertificates; +- (void)setDelegateDispatchQueue:(dispatch_queue_t _Nullable)queue { - return [NSURLProtocol propertyForKey:@"SR_SSLPinnedCertificates" inRequest:self]; + self.delegateController.dispatchQueue = queue; } -@end - -@implementation NSMutableURLRequest (CertificateAdditions) - -- (NSArray *)SR_SSLPinnedCertificates; +- (dispatch_queue_t _Nullable)delegateDispatchQueue { - return [NSURLProtocol propertyForKey:@"SR_SSLPinnedCertificates" inRequest:self]; + return self.delegateController.dispatchQueue; } -- (void)setSR_SSLPinnedCertificates:(NSArray *)SR_SSLPinnedCertificates; +- (void)setDelegateOperationQueue:(NSOperationQueue *_Nullable)queue { - [NSURLProtocol setProperty:SR_SSLPinnedCertificates forKey:@"SR_SSLPinnedCertificates" inRequest:self]; + self.delegateController.operationQueue = queue; } -@end - -@implementation NSURL (SRWebSocket) - -- (NSString *)SR_origin; +- (NSOperationQueue *_Nullable)delegateOperationQueue { - NSString *scheme = [self.scheme lowercaseString]; - - if ([scheme isEqualToString:@"wss"]) { - scheme = @"https"; - } else if ([scheme isEqualToString:@"ws"]) { - scheme = @"http"; - } - - if (self.port) { - return [NSString stringWithFormat:@"%@://%@:%@/", scheme, self.host, self.port]; - } else { - return [NSString stringWithFormat:@"%@://%@/", scheme, self.host]; - } + return self.delegateController.operationQueue; } @end -//#define SR_ENABLE_LOG - -static inline void SRFastLog(NSString *format, ...) { -#ifdef SR_ENABLE_LOG - __block va_list arg_list; - va_start (arg_list, format); - - NSString *formattedString = [[NSString alloc] initWithFormat:format arguments:arg_list]; - - va_end(arg_list); - - NSLog(@"[SR] %@", formattedString); -#endif -} - - #ifdef HAS_ICU static inline int32_t validate_dispatch_data_partial_string(NSData *data) { @@ -1621,7 +1606,7 @@ static inline int32_t validate_dispatch_data_partial_string(NSData *data) { const void * contents = [data bytes]; const uint8_t *str = (const uint8_t *)contents; - + UChar32 codepoint = 1; int32_t offset = 0; int32_t lastOffset = 0; @@ -1629,32 +1614,32 @@ static inline int32_t validate_dispatch_data_partial_string(NSData *data) { lastOffset = offset; U8_NEXT(str, offset, size, codepoint); } - + if (codepoint == -1) { // Check to see if the last byte is valid or whether it was just continuing if (!U8_IS_LEAD(str[lastOffset]) || U8_COUNT_TRAIL_BYTES(str[lastOffset]) + lastOffset < (int32_t)size) { - + size = -1; } else { uint8_t leadByte = str[lastOffset]; U8_MASK_LEAD_BYTE(leadByte, U8_COUNT_TRAIL_BYTES(leadByte)); - + for (int i = lastOffset + 1; i < offset; i++) { if (U8_IS_SINGLE(str[i]) || U8_IS_LEAD(str[i]) || !U8_IS_TRAIL(str[i])) { size = -1; } } - + if (size != -1) { size = lastOffset; } } } - + if (size != -1 && ![[NSString alloc] initWithBytesNoCopy:(char *)[data bytes] length:size encoding:NSUTF8StringEncoding freeWhenDone:NO]) { size = -1; } - + return size; } @@ -1663,80 +1648,15 @@ static inline int32_t validate_dispatch_data_partial_string(NSData *data) { // This is a hack, and probably not optimal static inline int32_t validate_dispatch_data_partial_string(NSData *data) { static const int maxCodepointSize = 3; - + for (int i = 0; i < maxCodepointSize; i++) { NSString *str = [[NSString alloc] initWithBytesNoCopy:(char *)data.bytes length:data.length - i encoding:NSUTF8StringEncoding freeWhenDone:NO]; if (str) { - return data.length - i; + return (int32_t)data.length - i; } } - + return -1; } #endif - -static _SRRunLoopThread *networkThread = nil; -static NSRunLoop *networkRunLoop = nil; - -@implementation NSRunLoop (SRWebSocket) - -+ (NSRunLoop *)SR_networkRunLoop { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - networkThread = [[_SRRunLoopThread alloc] init]; - networkThread.name = @"com.squareup.SocketRocket.NetworkThread"; - [networkThread start]; - networkRunLoop = networkThread.runLoop; - }); - - return networkRunLoop; -} - -@end - - -@implementation _SRRunLoopThread { - dispatch_group_t _waitGroup; -} - -@synthesize runLoop = _runLoop; - -- (void)dealloc -{ - sr_dispatch_release(_waitGroup); -} - -- (id)init -{ - self = [super init]; - if (self) { - _waitGroup = dispatch_group_create(); - dispatch_group_enter(_waitGroup); - } - return self; -} - -- (void)main; -{ - @autoreleasepool { - _runLoop = [NSRunLoop currentRunLoop]; - dispatch_group_leave(_waitGroup); - - NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate distantFuture] interval:0.0 target:nil selector:nil userInfo:nil repeats:NO]; - [_runLoop addTimer:timer forMode:NSDefaultRunLoopMode]; - - while ([_runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) { - - } - assert(NO); - } -} - -- (NSRunLoop *)runLoop; -{ - dispatch_group_wait(_waitGroup, DISPATCH_TIME_FOREVER); - return _runLoop; -} - -@end diff --git a/SocketRocket/SocketRocket-Prefix.pch b/SocketRocket/SocketRocket-Prefix.pch deleted file mode 100644 index 8c32c82c9..000000000 --- a/SocketRocket/SocketRocket-Prefix.pch +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright 2012 Square Inc. -// -// 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. -// - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __OBJC__ - #import -#endif - -#ifdef __cplusplus -} -#endif diff --git a/SocketRocket/SocketRocket.h b/SocketRocket/SocketRocket.h new file mode 100644 index 000000000..c7ab0622e --- /dev/null +++ b/SocketRocket/SocketRocket.h @@ -0,0 +1,15 @@ +// +// Copyright 2012 Square Inc. +// Portions Copyright (c) 2016-present, Facebook, Inc. +// +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import +#import +#import +#import diff --git a/TestChat/TCAppDelegate.h b/TestChat/TCAppDelegate.h index 279588f79..040664d30 100644 --- a/TestChat/TCAppDelegate.h +++ b/TestChat/TCAppDelegate.h @@ -1,15 +1,16 @@ // -// TCAppDelegate.h -// TestChat +// Copyright 2012 Square Inc. +// Portions Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. // -// Created by Mike Lewis on 1/28/12. -// Copyright (c) 2012 __MyCompanyName__. All rights reserved. +// This source code is licensed under the license found in the +// LICENSE-examples file in the root directory of this source tree. // #import @interface TCAppDelegate : UIResponder -@property (strong, nonatomic) UIWindow *window; +@property (nonatomic, strong) UIWindow *window; @end diff --git a/TestChat/TCAppDelegate.m b/TestChat/TCAppDelegate.m index 2c788a06c..643a12215 100644 --- a/TestChat/TCAppDelegate.m +++ b/TestChat/TCAppDelegate.m @@ -1,61 +1,19 @@ // -// TCAppDelegate.m -// TestChat +// Copyright 2012 Square Inc. +// Portions Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. // -// Created by Mike Lewis on 1/28/12. -// Copyright (c) 2012 __MyCompanyName__. All rights reserved. +// This source code is licensed under the license found in the +// LICENSE-examples file in the root directory of this source tree. // #import "TCAppDelegate.h" - @implementation TCAppDelegate -@synthesize window = _window; - - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // Override point for customization after application launch. return YES; } - -- (void)applicationWillResignActive:(UIApplication *)application -{ - /* - Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. - */ -} - -- (void)applicationDidEnterBackground:(UIApplication *)application -{ - /* - Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. - */ -} - -- (void)applicationWillEnterForeground:(UIApplication *)application -{ - /* - Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. - */ -} - -- (void)applicationDidBecomeActive:(UIApplication *)application -{ - /* - Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - */ -} - -- (void)applicationWillTerminate:(UIApplication *)application -{ - /* - Called when the application is about to terminate. - Save data if appropriate. - See also applicationDidEnterBackground:. - */ -} @end diff --git a/TestChat/TCChatCell.h b/TestChat/TCChatCell.h index ce9d2d4b2..7aa5a599d 100644 --- a/TestChat/TCChatCell.h +++ b/TestChat/TCChatCell.h @@ -1,9 +1,10 @@ // -// TCChatCell.h -// SocketRocket +// Copyright 2012 Square Inc. +// Portions Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. // -// Created by Mike Lewis on 1/28/12. -// Copyright (c) 2012 __MyCompanyName__. All rights reserved. +// This source code is licensed under the license found in the +// LICENSE-examples file in the root directory of this source tree. // #import diff --git a/TestChat/TCChatCell.m b/TestChat/TCChatCell.m index 6652316b8..e7277db3c 100644 --- a/TestChat/TCChatCell.m +++ b/TestChat/TCChatCell.m @@ -1,9 +1,10 @@ // -// TCChatCell.m -// SocketRocket +// Copyright 2012 Square Inc. +// Portions Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. // -// Created by Mike Lewis on 1/28/12. -// Copyright (c) 2012 __MyCompanyName__. All rights reserved. +// This source code is licensed under the license found in the +// LICENSE-examples file in the root directory of this source tree. // #import "TCChatCell.h" @@ -13,7 +14,7 @@ @implementation TCChatCell @synthesize nameLabel = _nameLabel; @synthesize textView = _textView; -- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier +- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; if (self) { diff --git a/TestChat/TCViewController.h b/TestChat/TCViewController.h index 72f0929d5..f94cdf3fc 100644 --- a/TestChat/TCViewController.h +++ b/TestChat/TCViewController.h @@ -1,17 +1,19 @@ // -// TCViewController.h -// TestChat +// Copyright 2012 Square Inc. +// Portions Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. // -// Created by Mike Lewis on 1/28/12. -// Copyright (c) 2012 __MyCompanyName__. All rights reserved. +// This source code is licensed under the license found in the +// LICENSE-examples file in the root directory of this source tree. // #import @interface TCViewController : UITableViewController -@property (nonatomic, retain) IBOutlet UITextView *inputView; +@property (nonatomic, strong) IBOutlet UITextView *inputView; - (IBAction)reconnect:(id)sender; +- (IBAction)sendPing:(id)sender; @end diff --git a/TestChat/TCViewController.m b/TestChat/TCViewController.m index cce04b287..1f79f7e55 100644 --- a/TestChat/TCViewController.m +++ b/TestChat/TCViewController.m @@ -1,110 +1,144 @@ // -// TCViewController.m -// TestChat +// Copyright 2012 Square Inc. +// Portions Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. // -// Created by Mike Lewis on 1/28/12. -// Copyright (c) 2012 __MyCompanyName__. All rights reserved. +// This source code is licensed under the license found in the +// LICENSE-examples file in the root directory of this source tree. // #import "TCViewController.h" -#import "SRWebSocket.h" + +#import + #import "TCChatCell.h" @interface TCMessage : NSObject -- (id)initWithMessage:(NSString *)message fromMe:(BOOL)fromMe; +- (instancetype)initWithMessage:(NSString *)message incoming:(BOOL)incoming; -@property (nonatomic, retain, readonly) NSString *message; -@property (nonatomic, readonly) BOOL fromMe; +@property (nonatomic, copy, readonly) NSString *message; +@property (nonatomic, assign, readonly, getter=isIncoming) BOOL incoming; @end +@implementation TCMessage -@interface TCViewController () +- (instancetype)initWithMessage:(NSString *)message incoming:(BOOL)incoming +{ + self = [super init]; + if (!self) return self; + + _incoming = incoming; + _message = message; + + return self; +} @end -@implementation TCViewController { + +@interface TCViewController () +{ SRWebSocket *_webSocket; - NSMutableArray *_messages; + NSMutableArray *_messages; } -@synthesize inputView = _inputView; +@end + +@implementation TCViewController -#pragma mark - View lifecycle +///-------------------------------------- +#pragma mark - View +///-------------------------------------- - (void)viewDidLoad; { [super viewDidLoad]; - _messages = [[NSMutableArray alloc] init]; - - [self.tableView reloadData]; -} - -- (void)_reconnect; -{ - _webSocket.delegate = nil; - [_webSocket close]; - - _webSocket = [[SRWebSocket alloc] initWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"ws://localhost:9000/chat"]]]; - _webSocket.delegate = self; - - self.title = @"Opening Connection..."; - [_webSocket open]; + _messages = [[NSMutableArray alloc] init]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - [self _reconnect]; -} -- (void)reconnect:(id)sender; -{ - [self _reconnect]; + [self reconnect:nil]; } -- (void)viewDidAppear:(BOOL)animated; +- (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; - + [_inputView becomeFirstResponder]; } - (void)viewDidDisappear:(BOOL)animated { - [super viewDidDisappear:animated]; - - _webSocket.delegate = nil; + [super viewDidDisappear:animated]; + [_webSocket close]; _webSocket = nil; } -#pragma mark - UITableViewController +///-------------------------------------- +#pragma mark - Actions +///-------------------------------------- + +- (IBAction)reconnect:(id)sender +{ + _webSocket.delegate = nil; + [_webSocket close]; + _webSocket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:@"wss://echo.websocket.org"]]; + _webSocket.delegate = self; -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section; + self.title = @"Opening Connection..."; + [_webSocket open]; +} + +- (void)sendPing:(id)sender; { - return _messages.count; + [_webSocket sendPing:nil error:NULL]; } -- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath; +///-------------------------------------- +#pragma mark - Messages +///-------------------------------------- + +- (void)_addMessage:(TCMessage *)message { - TCChatCell *chatCell = (id)cell; - TCMessage *message = [_messages objectAtIndex:indexPath.row]; - chatCell.textView.text = message.message; - chatCell.nameLabel.text = message.fromMe ? @"Me" : @"Other"; + [_messages addObject:message]; + [self.tableView insertRowsAtIndexPaths:@[ [NSIndexPath indexPathForRow:_messages.count - 1 inSection:0] ] + withRowAnimation:UITableViewRowAnimationNone]; + [self.tableView scrollRectToVisible:self.tableView.tableFooterView.frame animated:YES]; } -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath; +///-------------------------------------- +#pragma mark - UITableViewController +///-------------------------------------- + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - TCMessage *message = [_messages objectAtIndex:indexPath.row]; + return _messages.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + TCMessage *message = _messages[indexPath.row]; - return [self.tableView dequeueReusableCellWithIdentifier:message.fromMe ? @"SentCell" : @"ReceivedCell"]; + TCChatCell *cell = [self.tableView dequeueReusableCellWithIdentifier:message.incoming ? @"ReceivedCell" : @"SentCell" + forIndexPath:indexPath]; + + cell.textView.text = message.message; + cell.nameLabel.text = message.incoming ? @"Other" : @"Me"; + + return cell; } +///-------------------------------------- #pragma mark - SRWebSocketDelegate +///-------------------------------------- - (void)webSocketDidOpen:(SRWebSocket *)webSocket; { @@ -115,17 +149,15 @@ - (void)webSocketDidOpen:(SRWebSocket *)webSocket; - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error; { NSLog(@":( Websocket Failed With Error %@", error); - + self.title = @"Connection Failed! (see logs)"; _webSocket = nil; } -- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message; +- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessageWithString:(nonnull NSString *)string { - NSLog(@"Received \"%@\"", message); - [_messages addObject:[[TCMessage alloc] initWithMessage:message fromMe:NO]]; - [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:_messages.count - 1 inSection:0]] withRowAnimation:UITableViewRowAnimationNone]; - [self.tableView scrollRectToVisible:self.tableView.tableFooterView.frame animated:YES]; + NSLog(@"Received \"%@\"", string); + [self _addMessage:[[TCMessage alloc] initWithMessage:string incoming:YES]]; } - (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean; @@ -135,43 +167,29 @@ - (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reas _webSocket = nil; } -- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text; +- (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload; { - if ([text rangeOfString:@"\n"].location != NSNotFound) { - NSString *message = [[textView.text stringByReplacingCharactersInRange:range withString:text] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - [_webSocket send:message]; - [_messages addObject:[[TCMessage alloc] initWithMessage:message fromMe:YES]]; - - [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:_messages.count - 1 inSection:0]] withRowAnimation:UITableViewRowAnimationNone]; - [self.tableView scrollRectToVisible:self.tableView.tableFooterView.frame animated:YES]; - - textView.text = @""; - return NO; - } - return YES; + NSLog(@"WebSocket received pong"); } -- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation; -{ - return YES; -} +///-------------------------------------- +#pragma mark - UITextViewDelegate +///-------------------------------------- -@end +- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text +{ + if ([text rangeOfString:@"\n"].location != NSNotFound) { + NSString *message = [textView.text stringByReplacingCharactersInRange:range withString:text]; + message = [message stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; -@implementation TCMessage + [_webSocket sendString:message error:NULL]; -@synthesize message = _message; -@synthesize fromMe = _fromMe; + [self _addMessage:[[TCMessage alloc] initWithMessage:message incoming:NO]]; -- (id)initWithMessage:(NSString *)message fromMe:(BOOL)fromMe; -{ - self = [super init]; - if (self) { - _fromMe = fromMe; - _message = message; + textView.text = nil; + return NO; } - - return self; + return YES; } @end diff --git a/TestChat/TestChat-Info.plist b/TestChat/TestChat-Info.plist index f337f63ee..41b47bab7 100644 --- a/TestChat/TestChat-Info.plist +++ b/TestChat/TestChat-Info.plist @@ -11,7 +11,7 @@ CFBundleIconFiles CFBundleIdentifier - com.squareup.${PRODUCT_NAME:rfc1034identifier} + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/TestChat/TestChat-Prefix.pch b/TestChat/TestChat-Prefix.pch deleted file mode 100644 index f7623c9bf..000000000 --- a/TestChat/TestChat-Prefix.pch +++ /dev/null @@ -1,14 +0,0 @@ -// -// Prefix header for all source files of the 'TestChat' target in the 'TestChat' project -// - -#import - -#ifndef __IPHONE_5_0 -#warning "This project uses features only available in iOS SDK 5.0 and later." -#endif - -#ifdef __OBJC__ - #import - #import -#endif diff --git a/TestChat/en.lproj/MainStoryboard.storyboard b/TestChat/en.lproj/MainStoryboard.storyboard index a241c57f8..9c0200b56 100644 --- a/TestChat/en.lproj/MainStoryboard.storyboard +++ b/TestChat/en.lproj/MainStoryboard.storyboard @@ -1,21 +1,20 @@ - + - - + + - - - + + - + @@ -27,9 +26,9 @@ - + - + @@ -45,17 +44,16 @@ - - + - + - + @@ -71,8 +69,7 @@ - - + @@ -85,9 +82,14 @@ + + + + + - + @@ -95,13 +97,13 @@ + - @@ -113,29 +115,14 @@ + - - - - - - - - - - - - - - - - - \ No newline at end of file + diff --git a/TestChat/main.m b/TestChat/main.m index d1a030744..a3b4c8c11 100644 --- a/TestChat/main.m +++ b/TestChat/main.m @@ -1,9 +1,10 @@ // -// main.m -// TestChat +// Copyright 2012 Square Inc. +// Portions Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. // -// Created by Mike Lewis on 1/28/12. -// Copyright (c) 2012 __MyCompanyName__. All rights reserved. +// This source code is licensed under the license found in the +// LICENSE-examples file in the root directory of this source tree. // #import diff --git a/TestChatServer/go/chatroom.go b/TestChatServer/go/chatroom.go index 66bd522c1..bc3a9c6d1 100644 --- a/TestChatServer/go/chatroom.go +++ b/TestChatServer/go/chatroom.go @@ -1,60 +1,70 @@ +// +// Copyright 2012 Square Inc. +// Portions Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the license found in the +// LICENSE-examples file in the root directory of this source tree. +// + package main import ( - "code.google.com/p/go.net/websocket" - "net/http" + "code.google.com/p/go.net/websocket" + "net/http" ) +// Msg stores both the message and the connection type Msg struct { - sender *websocket.Conn - msg string + sender *websocket.Conn + msg string } func run(reg chan *websocket.Conn, unreg chan *websocket.Conn, msg chan Msg) { - conns := make(map[*websocket.Conn]int) - for { - select { - case c := <-reg: - conns[c] = 1 - case c := <-unreg: - delete(conns, c) - case msg := <-msg: - for c := range conns { - if c != msg.sender { - websocket.Message.Send(c, msg.msg) - } - } - } - } + conns := make(map[*websocket.Conn]int) + for { + select { + case c := <-reg: + conns[c] = 1 + case c := <-unreg: + delete(conns, c) + case msg := <-msg: + for c := range conns { + if c != msg.sender { + websocket.Message.Send(c, msg.msg) + } + } + } + } } func newChatServer(reg chan *websocket.Conn, unreg chan *websocket.Conn, msg chan Msg) websocket.Handler { - return func(ws *websocket.Conn) { - reg <- ws - for { - var message string - err := websocket.Message.Receive(ws, &message) - if err != nil { - unreg <- ws - break - } - msg <- Msg{ws, message} - } - } + return func(ws *websocket.Conn) { + reg <- ws + for { + var message string + err := websocket.Message.Receive(ws, &message) + if err != nil { + unreg <- ws + break + } + msg <- Msg{ws, message} + } + } } func main() { - reg := make(chan *websocket.Conn) - unreg := make(chan *websocket.Conn) - msg := make(chan Msg) + reg := make(chan *websocket.Conn) + unreg := make(chan *websocket.Conn) + msg := make(chan Msg) - http.Handle("/chat", websocket.Handler(newChatServer(reg, unreg, msg))) - http.Handle("/", http.FileServer(http.Dir("../static"))) + http.Handle("/chat", websocket.Handler(newChatServer(reg, unreg, msg))) + http.Handle("/", http.FileServer(http.Dir("../static"))) - go run(reg, unreg, msg) + go run(reg, unreg, msg) - err := http.ListenAndServe(":9000", nil) - if err != nil { - panic("ListenAndServe: " + err.Error()) - } + err := http.ListenAndServe(":9000", nil) + if err != nil { + panic("ListenAndServe: " + err.Error()) + } } diff --git a/TestChatServer/py/chatroom.py b/TestChatServer/py/chatroom.py index 24000548b..b30472676 100755 --- a/TestChatServer/py/chatroom.py +++ b/TestChatServer/py/chatroom.py @@ -1,4 +1,13 @@ #!/usr/bin/env python +# +# Copyright 2012 Square Inc. +# Portions Copyright (c) 2016-present, Facebook, Inc. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE-examples file in the root directory of this source tree. +# + import tornado import tornado.web import tornado.websocket diff --git a/TestChatServer/static/proxy.js b/TestChatServer/static/proxy.js index 4385d7407..fc89382e6 100644 --- a/TestChatServer/static/proxy.js +++ b/TestChatServer/static/proxy.js @@ -1,3 +1,11 @@ +// +// Copyright 2012 Square Inc. +// Portions Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the license found in the +// LICENSE-examples file in the root directory of this source tree. +// function SocketClient () { this.list_elem = document.getElementById('client_list'); diff --git a/TestSupport/autobahn_fuzzingserver.json b/TestSupport/autobahn_fuzzingserver.json new file mode 100644 index 000000000..6bfc65121 --- /dev/null +++ b/TestSupport/autobahn_fuzzingserver.json @@ -0,0 +1,7 @@ +{ + "url": "ws://127.0.0.1:9001", + "outdir": "./pages/results", + "cases": ["*"], + "exclude-cases": [], + "exclude-agent-cases": {} +} diff --git a/TestSupport/ensure_virtualenv.sh b/TestSupport/ensure_virtualenv.sh deleted file mode 100644 index bcf476201..000000000 --- a/TestSupport/ensure_virtualenv.sh +++ /dev/null @@ -1,11 +0,0 @@ -VIRTUALENV_PATH=$1 - -if [ -d "$VIRTUALENV_PATH" ]; then - echo "Virtual Env already installed" -else - python extern/virtualenv/virtualenv.py $VIRTUALENV_PATH - source $VIRTUALENV_PATH/bin/activate - pushd TestSupport/sr-testharness/ - python setup.py develop - popd -fi diff --git a/TestSupport/run_test.sh b/TestSupport/run_test.sh deleted file mode 100644 index 1327a70cc..000000000 --- a/TestSupport/run_test.sh +++ /dev/null @@ -1,28 +0,0 @@ -TEST_SCENARIOS=$1 -TEST_URL=$2 -CONFIGURATION=$3 - - -export SR_TEST_URL=$TEST_URL - -bash TestSupport/ensure_virtualenv.sh .env - -pushd TestSupport/sr-testharness/ -python setup.py develop -popd - -source .env/bin/activate -sr-testharness -i '' -c "$TEST_SCENARIOS" & - -CHILD_PID=$! - -extra_opts="VALID_ARCHS=i386 ARCH=i386" - -SHARED_ARGS="-arch i386 -configuration $CONFIGURATION -sdk iphonesimulator" - -xcodebuild -scheme SocketRocketTests $SHARED_ARGS TEST_AFTER_BUILD=YES clean build $extra_opts -RESULT=$? - -kill $CHILD_PID - -exit $RESULT diff --git a/TestSupport/run_test_server.sh b/TestSupport/run_test_server.sh new file mode 100755 index 000000000..bd0265c41 --- /dev/null +++ b/TestSupport/run_test_server.sh @@ -0,0 +1,18 @@ +# +# Copyright (c) 2016-present, Facebook, Inc. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE-examples file in the root directory of this source tree. +# + +PYENV_PATH=$(pwd)/.env + +echo $PYENV_PATH +if [ -d "$PYENV_PATH" ]; then + source $PYENV_PATH/bin/activate + $PYENV_PATH/bin/wstest -m fuzzingserver -s TestSupport/autobahn_fuzzingserver.json +else + echo "Python Virtualenv not set up. Please run './TestSupport/setup_env.sh .env' first." +fi + diff --git a/TestSupport/setup_env.sh b/TestSupport/setup_env.sh new file mode 100755 index 000000000..542a4f992 --- /dev/null +++ b/TestSupport/setup_env.sh @@ -0,0 +1,30 @@ +# +# Copyright (c) 2016-present, Facebook, Inc. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE-examples file in the root directory of this source tree. +# + +VIRTUALENV_PATH=$1 + +if [ -d "$VIRTUALENV_PATH" ]; then + echo "Virtual Env already installed" +elif [ -z "$VIRTUALENV_PATH" ]; then + echo "Usage: ./setup_env.sh " +else + mkdir $VIRTUALENV_PATH + + pushd $VIRTUALENV_PATH + + curl -L -o virtualenv.pyz https://bootstrap.pypa.io/virtualenv.pyz + + popd + + python $VIRTUALENV_PATH/virtualenv.pyz $VIRTUALENV_PATH + + source $VIRTUALENV_PATH/bin/activate + pip install autobahntestsuite + + echo "Environment succesfully set up in $VIRTUALENV_PATH." +fi diff --git a/TestSupport/sr-testharness/setup.cfg b/TestSupport/sr-testharness/setup.cfg deleted file mode 100644 index 01bb95449..000000000 --- a/TestSupport/sr-testharness/setup.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[egg_info] -tag_build = dev -tag_svn_revision = true diff --git a/TestSupport/sr-testharness/setup.py b/TestSupport/sr-testharness/setup.py deleted file mode 100644 index d97f8a4b9..000000000 --- a/TestSupport/sr-testharness/setup.py +++ /dev/null @@ -1,30 +0,0 @@ -from setuptools import setup, find_packages -import sys, os - -version = '0.0' - -setup(name='srtestharness', - version=version, - description="", - long_description="""\ -""", - classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers - keywords='', - author='', - author_email='', - url='', - license='', - packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), - include_package_data=True, - zip_safe=True, - install_requires=[ - # -*- Extra requirements: -*- - 'autobahntestsuite', - 'autobahn', - ], - entry_points=""" - # -*- Entry points: -*- - [console_scripts] - sr-testharness = srtestharness.runner:main - """, - ) diff --git a/TestSupport/sr-testharness/srtestharness/__init__.py b/TestSupport/sr-testharness/srtestharness/__init__.py deleted file mode 100644 index 792d60054..000000000 --- a/TestSupport/sr-testharness/srtestharness/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# diff --git a/TestSupport/sr-testharness/srtestharness/runner.py b/TestSupport/sr-testharness/srtestharness/runner.py deleted file mode 100644 index d889d4e74..000000000 --- a/TestSupport/sr-testharness/srtestharness/runner.py +++ /dev/null @@ -1,75 +0,0 @@ -import argparse -import json -import sys -import subprocess -import urlparse - -from twisted.python import log -from twisted.internet import reactor -from twisted.web.server import Site -from twisted.web.static import File -from autobahntestsuite.fuzzing import FuzzingServerFactory -from autobahn.websocket import listenWS - -class jsondict(dict): - def __init__(self, json_value): - if isinstance(json_value, dict): - dict.__init__(self, json_value) - else: - dict.__init__(self, json.loads(json_value)) - - def append(self, other): - self.update(jsondict(other)) - - def __repr__(self): - return "'%s'" % json.dumps(self) - -def parse_opts(s): - return dict(s.split('=')) - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('-u', '--url', default='ws://localhost:9001', help='Listen URL [default: %(default)s]') - parser.add_argument('-O', '--options', default=jsondict({'failByDrop':False}), type=jsondict, action='append', help='extra options (overwrites existing) [default: %(default)s]') - - parser.add_argument('-p', '--webport', default=9090, type=int) - parser.add_argument('-i', '--listen-interface', default='localhost', help='interface to listen on') - parser.add_argument('-w', '--webdir', default='.') - parser.add_argument('-d', '--debug', default=False, action='store_true', help='Debug Mode [default: %(default)s]') - - parser.add_argument('-o', '--outdir', default='./pages/results', metavar='DIR', help='Output Directory [default: %(default)s]') - - parser.add_argument('-c', '--cases', default=['*'], nargs='+', help='test cases [default: %(default)s]') - parser.add_argument('-x', '--exclude-cases', default=[], nargs='+', help='test cases to exclude [default: %(default)s]') - - parser.add_argument('-l', '--logfile', type=argparse.FileType('w'), default=sys.stdout, help='logging file [default: stdout]') - - parser.add_argument('-t', '--exit-timeout', metavar='SECONDS', default=None, type=float, help='Will automatically exit after %(metavar)s seconds [default: %(default)s]') - - args = parser.parse_args() - - spec = args.__dict__ - - log.startLogging(args.logfile) - - ## fuzzing server - fuzzer = FuzzingServerFactory(spec) - listenWS(fuzzer, interface=args.listen_interface) - - ## web server - webdir = File(args.webdir) - web = Site(webdir) - reactor.listenTCP(args.webport, web, interface=args.listen_interface) - - log.msg("Using Twisted reactor class %s" % str(reactor.__class__)) - - if args.exit_timeout: - def exit_callback(): - log.msg("Exiting due to timeout (--exit-timeout/-t)") - reactor.fireSystemEvent('shutdown') - #reactor.stop() - sys.exit(12) - - reactor.callLater(args.exit_timeout, exit_callback) - - reactor.run() diff --git a/Tests/Operations/SRAutobahnOperation.h b/Tests/Operations/SRAutobahnOperation.h new file mode 100644 index 000000000..37f3f90f8 --- /dev/null +++ b/Tests/Operations/SRAutobahnOperation.h @@ -0,0 +1,41 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "SRTWebSocketOperation.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef void(^SRAutobahnSocketTextMessageHandler)(SRWebSocket *socket, NSString * _Nullable message); +typedef void(^SRAutobahnSocketDataMessageHandler)(SRWebSocket *socket, NSData * _Nullable message); + +@interface SRAutobahnOperation : SRTWebSocketOperation + +- (instancetype)initWithServerURL:(NSURL *)url + testCommandPath:(NSString *)path + caseNumber:(nullable NSNumber *)caseNumber + agent:(nullable NSString *)agent + textMessageHandler:(nullable SRAutobahnSocketTextMessageHandler)textMessageHandler + dataMessageHandler:(nullable SRAutobahnSocketDataMessageHandler)dataMessageHandler; + +@end + +extern SRAutobahnOperation *SRAutobahnTestOperation(NSURL *serverURL, NSInteger caseNumber, NSString *agent); + +typedef void(^SRAutobahnTestResultHandler)(NSDictionary *_Nullable result); +extern SRAutobahnOperation *SRAutobahnTestResultOperation(NSURL *serverURL, NSInteger caseNumber, NSString *agent, SRAutobahnTestResultHandler handler); + +typedef void(^SRAutobahnTestCaseInfoHandler)(NSDictionary *_Nullable caseInfo); +extern SRAutobahnOperation *SRAutobahnTestCaseInfoOperation(NSURL *serverURL, NSInteger caseNumber, SRAutobahnTestCaseInfoHandler handler); + +typedef void(^SRAutobahnTestCaseCountHandler)(NSInteger caseCount); +extern SRAutobahnOperation *SRAutobahnTestCaseCountOperation(NSURL *serverURL, NSString *agent, SRAutobahnTestCaseCountHandler handler); + +extern SRAutobahnOperation *SRAutobahnTestUpdateReportsOperation(NSURL *serverURL, NSString *agent); + +NS_ASSUME_NONNULL_END diff --git a/Tests/Operations/SRAutobahnOperation.m b/Tests/Operations/SRAutobahnOperation.m new file mode 100644 index 000000000..43d07e92a --- /dev/null +++ b/Tests/Operations/SRAutobahnOperation.m @@ -0,0 +1,131 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "SRAutobahnOperation.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface SRAutobahnOperation () + +@property (nullable, nonatomic, copy, readonly) SRAutobahnSocketTextMessageHandler textMessageHandler; +@property (nullable, nonatomic, copy, readonly) SRAutobahnSocketDataMessageHandler dataMessageHandler; + +@end + +@implementation SRAutobahnOperation + +- (instancetype)initWithServerURL:(NSURL *)url + testCommandPath:(NSString *)path + caseNumber:(nullable NSNumber *)caseNumber + agent:(nullable NSString *)agent + textMessageHandler:(nullable SRAutobahnSocketTextMessageHandler)textMessageHandler + dataMessageHandler:(nullable SRAutobahnSocketDataMessageHandler)dataMessageHandler +{ + NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; + components.path = (components.path ? [components.path stringByAppendingPathComponent:path] : path); + + NSMutableArray *queryItems = [NSMutableArray arrayWithCapacity:2]; + if (caseNumber) { + [queryItems addObject:[NSURLQueryItem queryItemWithName:@"case" value:caseNumber.stringValue]]; + } + if (agent) { + [queryItems addObject:[NSURLQueryItem queryItemWithName:@"agent" value:agent]]; + } + components.queryItems = queryItems; + self = [self initWithURL:components.URL]; + if (!self) return self; + + _textMessageHandler = [textMessageHandler copy]; + _dataMessageHandler = [dataMessageHandler copy]; + + return self; +} + +- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessageWithString:(NSString *)string +{ + if (self.textMessageHandler) { + self.textMessageHandler(webSocket, string); + } +} + +- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessageWithData:(NSData *)data +{ + if (self.dataMessageHandler) { + self.dataMessageHandler(webSocket, data); + } +} + +@end + +SRAutobahnOperation *SRAutobahnTestOperation(NSURL *serverURL, NSInteger caseNumber, NSString *agent) +{ + return [[SRAutobahnOperation alloc] initWithServerURL:serverURL + testCommandPath:@"/runCase" + caseNumber:@(caseNumber) + agent:agent + textMessageHandler:^(SRWebSocket * _Nonnull socket, NSString * _Nullable message) { + [socket sendString:message error:nil]; + } + dataMessageHandler:^(SRWebSocket * _Nonnull socket, NSData * _Nullable message) { + [socket sendData:message error:nil]; + }]; +} + +extern SRAutobahnOperation *SRAutobahnTestResultOperation(NSURL *serverURL, NSInteger caseNumber, NSString *agent, SRAutobahnTestResultHandler handler) +{ + return [[SRAutobahnOperation alloc] initWithServerURL:serverURL + testCommandPath:@"/getCaseStatus" + caseNumber:@(caseNumber) + agent:agent + textMessageHandler:^(SRWebSocket * _Nonnull socket, NSString * _Nullable message) { + NSData *messageData = [message dataUsingEncoding:NSUTF8StringEncoding]; + NSDictionary *result = [NSJSONSerialization JSONObjectWithData:messageData options:0 error:NULL]; + handler(result); + } + dataMessageHandler:nil]; +} + +extern SRAutobahnOperation *SRAutobahnTestCaseInfoOperation(NSURL *serverURL, NSInteger caseNumber, SRAutobahnTestCaseInfoHandler handler) +{ + return [[SRAutobahnOperation alloc] initWithServerURL:serverURL + testCommandPath:@"/getCaseInfo" + caseNumber:@(caseNumber) + agent:nil + textMessageHandler:^(SRWebSocket * _Nonnull socket, NSString * _Nullable message) { + NSData *messageData = [message dataUsingEncoding:NSUTF8StringEncoding]; + NSDictionary *result = [NSJSONSerialization JSONObjectWithData:messageData options:0 error:NULL]; + handler(result); + } + dataMessageHandler:nil]; +} + +extern SRAutobahnOperation *SRAutobahnTestCaseCountOperation(NSURL *serverURL, NSString *agent, SRAutobahnTestCaseCountHandler handler) +{ + return [[SRAutobahnOperation alloc] initWithServerURL:serverURL + testCommandPath:@"/getCaseCount" + caseNumber:nil + agent:agent + textMessageHandler:^(SRWebSocket * _Nonnull socket, NSString * _Nullable message) { + NSInteger count = [message integerValue]; + handler(count); + } + dataMessageHandler:nil]; +} + +extern SRAutobahnOperation *SRAutobahnTestUpdateReportsOperation(NSURL *serverURL, NSString *agent) +{ + return [[SRAutobahnOperation alloc] initWithServerURL:serverURL + testCommandPath:@"/updateReports" + caseNumber:nil + agent:agent + textMessageHandler:nil + dataMessageHandler:nil]; +} + +NS_ASSUME_NONNULL_END diff --git a/Tests/Operations/SRTWebSocketOperation.h b/Tests/Operations/SRTWebSocketOperation.h new file mode 100644 index 000000000..c4d53c2c3 --- /dev/null +++ b/Tests/Operations/SRTWebSocketOperation.h @@ -0,0 +1,29 @@ +// +// Copyright 2012 Square Inc. +// Portions Copyright (c) 2016-present, Facebook, Inc. +// +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +@interface SRTWebSocketOperation : NSOperation + +@property (nonatomic) BOOL isFinished; +@property (nonatomic) BOOL isExecuting; + +@property (nonatomic, strong, readonly) NSError *error; + +- (instancetype)initWithURL:(NSURL *)URL; + +// We override these methods. Please call super +- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean NS_REQUIRES_SUPER; +- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error NS_REQUIRES_SUPER; + +- (BOOL)waitUntilFinishedWithTimeout:(NSTimeInterval)timeout; + +@end diff --git a/SRWebSocketTests/SRTWebSocketOperation.m b/Tests/Operations/SRTWebSocketOperation.m similarity index 63% rename from SRWebSocketTests/SRTWebSocketOperation.m rename to Tests/Operations/SRTWebSocketOperation.m index a29bf9bf0..8ad9337e6 100644 --- a/SRWebSocketTests/SRTWebSocketOperation.m +++ b/Tests/Operations/SRTWebSocketOperation.m @@ -1,13 +1,17 @@ // -// SRTWebSocketOperation.m -// SocketRocket +// Copyright 2012 Square Inc. +// Portions Copyright (c) 2016-present, Facebook, Inc. // -// Created by Mike Lewis on 1/28/12. -// Copyright (c) 2012 __MyCompanyName__. All rights reserved. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. // #import "SRTWebSocketOperation.h" -#import "SRWebSocket.h" + +#import "SRAutobahnUtilities.h" @interface SRTWebSocketOperation () @@ -23,7 +27,7 @@ @implementation SRTWebSocketOperation { @synthesize isExecuting = _isExecuting; @synthesize error = _error; -- (id)initWithURL:(NSURL *)URL; +- (instancetype)initWithURL:(NSURL *)URL; { self = [super init]; if (self) { @@ -42,9 +46,10 @@ - (BOOL)isConcurrent; - (void)start; { dispatch_async(dispatch_get_main_queue(), ^{ - _webSocket = [[SRWebSocket alloc] initWithURLRequest:[NSURLRequest requestWithURL:_url]]; - _webSocket.delegate = self; - [_webSocket open]; + SRWebSocket *socket = [[SRWebSocket alloc] initWithURLRequest:[NSURLRequest requestWithURL:self->_url]]; + socket.delegate = self; + self->_webSocket = socket; + [socket open]; }); self.isExecuting = YES; } @@ -61,11 +66,6 @@ - (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reas _webSocket = nil; } -- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message; -{ - NSAssert(NO, @"Not implemented"); -} - - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error; { _error = error; @@ -79,4 +79,14 @@ - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error; _webSocket = nil; } +- (BOOL)waitUntilFinishedWithTimeout:(NSTimeInterval)timeout +{ + if (self.isFinished) { + return YES; + } + return SRRunLoopRunUntil(^BOOL{ + return self.isFinished; + }, timeout); +} + @end diff --git a/SRWebSocketTests/SRWebSocketTests-Info.plist b/Tests/Resources/Info.plist similarity index 90% rename from SRWebSocketTests/SRWebSocketTests-Info.plist rename to Tests/Resources/Info.plist index 51aed13f3..169b6f710 100644 --- a/SRWebSocketTests/SRWebSocketTests-Info.plist +++ b/Tests/Resources/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier - com.squareup.${PRODUCT_NAME:rfc1034identifier} + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType diff --git a/Tests/Resources/autobahn_configuration.json b/Tests/Resources/autobahn_configuration.json new file mode 100644 index 000000000..e5a1d9ae3 --- /dev/null +++ b/Tests/Resources/autobahn_configuration.json @@ -0,0 +1,15 @@ +{ + "UNIMPLEMENTED": [ + "12", + "13" + ], + "NON-STRICT": [ + "6.4.2", + "6.4.4", + ], + "INFORMATIONAL": [ + "7.1.6", + "7.13.1", + "7.13.2" + ] +} \ No newline at end of file diff --git a/Tests/SRAutobahnTests.m b/Tests/SRAutobahnTests.m new file mode 100644 index 000000000..cc9fc17f7 --- /dev/null +++ b/Tests/SRAutobahnTests.m @@ -0,0 +1,146 @@ +// +// Copyright 2012 Square Inc. +// Portions Copyright (c) 2016-present, Facebook, Inc. +// +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +@import XCTest; +@import ObjectiveC; + +#import + +#import "SRTWebSocketOperation.h" +#import "SRAutobahnOperation.h" +#import "SRAutobahnUtilities.h" + +@interface SRAutobahnTests : XCTestCase +@end + +@implementation SRAutobahnTests + +///-------------------------------------- +#pragma mark - Init +///-------------------------------------- + +/** + This method is called if Xcode is targeting a specific test or a set of them. + If you change this method - please make sure you test this behavior in Xcode by running all tests, then running 1+ test. + */ ++ (instancetype)testCaseWithSelector:(SEL)selector +{ + NSArray *invocations = [self testInvocations]; + for (NSInvocation *invocation in invocations) { + if (invocation.selector == selector) { + return [super testCaseWithSelector:selector]; + } + } + return nil; +} + +///-------------------------------------- +#pragma mark - Setup +///-------------------------------------- + +/** + This method is called by xctest to figure out all the tests that are available. + All the selector names are also reported back to Xcode and displayed in Test Navigator/Console. + */ ++ (NSArray *)testInvocations +{ + __block NSArray *array = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSMutableArray *invocations = [NSMutableArray array]; + for (NSUInteger i = 1; i <= SRAutobahnTestCaseCount(); i++) { + NSDictionary *caseInfo = SRAutobahnTestCaseInfo(i); + NSString *identifier = caseInfo[@"id"]; + + NSInvocation *invocation = [self invocationWithCaseNumber:i identifier:identifier]; + [invocations addObject:invocation]; + } + + array = [invocations sortedArrayUsingComparator:^NSComparisonResult(NSInvocation *_Nonnull obj1, NSInvocation *_Nonnull obj2) { + return [NSStringFromSelector(obj1.selector) compare:NSStringFromSelector(obj2.selector) options:NSNumericSearch]; + }]; + }); + return array; +} + ++ (NSInvocation *)invocationWithCaseNumber:(NSUInteger)caseNumber identifier:(NSString *)identifier +{ + SEL selector = [self addInstanceMethodForTestCaseNumber:caseNumber identifier:identifier]; + NSMethodSignature *signature = [self instanceMethodSignatureForSelector:selector]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; + invocation.selector = selector; + return invocation; +} + ++ (SEL)addInstanceMethodForTestCaseNumber:(NSInteger)caseNumber identifier:(NSString *)identifier +{ + NSString *selectorName = [NSString stringWithFormat:@"Case #%@", identifier]; + SEL selector = NSSelectorFromString(selectorName); + + IMP implementation = imp_implementationWithBlock(^(SRAutobahnTests *sself) { + [sself performTestWithCaseNumber:caseNumber identifier:identifier]; + }); + NSString *typeString = [NSString stringWithFormat:@"%s%s%s", @encode(id), @encode(id), @encode(SEL)]; + class_addMethod(self, selector, implementation, typeString.UTF8String); + + return selector; +} + +///-------------------------------------- +#pragma mark - Teardown +///-------------------------------------- + ++ (void)tearDown +{ + [self updateReports]; + [super tearDown]; +} + ++ (void)updateReports +{ + SRAutobahnOperation *operation = SRAutobahnTestUpdateReportsOperation(SRAutobahnTestServerURL(), SRAutobahnTestAgentName()); + [operation start]; + + NSAssert([operation waitUntilFinishedWithTimeout:60], @"Timed out on updating reports."); + NSAssert(!operation.error, @"Updating the report should not have errored %@", operation.error); +} + +///-------------------------------------- +#pragma mark - Test +///-------------------------------------- + +- (void)performTestWithCaseNumber:(NSInteger)caseNumber identifier:(NSString *)identifier +{ + NSURL *serverURL = SRAutobahnTestServerURL(); + NSString *agent = SRAutobahnTestAgentName(); + + NSOperationQueue *testQueue = [[NSOperationQueue alloc] init]; + testQueue.maxConcurrentOperationCount = 1; + + SRAutobahnOperation *testOp = SRAutobahnTestOperation(serverURL, caseNumber, agent); + [testQueue addOperation:testOp]; + + __block NSDictionary *resultInfo = nil; + SRAutobahnOperation *resultOp = SRAutobahnTestResultOperation(serverURL, caseNumber, agent, ^(NSDictionary * _Nullable result) { + resultInfo = result; + }); + [resultOp addDependency:testOp]; + [testQueue addOperation:resultOp]; + + + XCTAssertTrue([resultOp waitUntilFinishedWithTimeout:60 * 5], @"Test operation timed out."); + XCTAssertTrue(!testOp.error, @"Test operation should not have failed"); + if (!SRAutobahnIsValidResultBehavior(identifier, resultInfo[@"behavior"])) { + XCTFail(@"Invalid test behavior %@ for %@.", resultInfo[@"behavior"], identifier); + } +} + +@end diff --git a/Tests/Utilities/SRAutobahnUtilities.h b/Tests/Utilities/SRAutobahnUtilities.h new file mode 100644 index 000000000..4a5ff2f66 --- /dev/null +++ b/Tests/Utilities/SRAutobahnUtilities.h @@ -0,0 +1,41 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +@import Foundation; + +NS_ASSUME_NONNULL_BEGIN + +///-------------------------------------- +#pragma mark - Test Configuration +///-------------------------------------- + +extern NSString *SRAutobahnTestAgentName(void); +extern NSURL *SRAutobahnTestServerURL(void); + +///-------------------------------------- +#pragma mark - Validation +///-------------------------------------- + +extern NSDictionary *SRAutobahnTestConfiguration(void); +extern BOOL SRAutobahnIsValidResultBehavior(NSString *caseIdentifier, NSString *behavior); + +///-------------------------------------- +#pragma mark - Utilities +///-------------------------------------- + +extern BOOL SRRunLoopRunUntil(BOOL (^predicate)(), NSTimeInterval timeout); + +///-------------------------------------- +#pragma mark - Setup +///-------------------------------------- + +extern NSUInteger SRAutobahnTestCaseCount(void); +extern NSDictionary *SRAutobahnTestCaseInfo(NSInteger caseNumber); + +NS_ASSUME_NONNULL_END diff --git a/Tests/Utilities/SRAutobahnUtilities.m b/Tests/Utilities/SRAutobahnUtilities.m new file mode 100644 index 000000000..3b52616c3 --- /dev/null +++ b/Tests/Utilities/SRAutobahnUtilities.m @@ -0,0 +1,120 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "SRAutobahnUtilities.h" + +#import "SRAutobahnOperation.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface SRAutobahnUtilities : NSObject @end +@implementation SRAutobahnUtilities @end + +///-------------------------------------- +#pragma mark - Test Configuration +///-------------------------------------- + +NSString *SRAutobahnTestAgentName(void) +{ + return [NSBundle bundleForClass:[SRAutobahnUtilities class]].bundleIdentifier; +} + +NSURL *SRAutobahnTestServerURL(void) +{ + return [NSURL URLWithString:@"ws://localhost:9001"]; +} + +///-------------------------------------- +#pragma mark - Validation +///-------------------------------------- + +NSDictionary *SRAutobahnTestConfiguration(void) +{ + static NSDictionary *configuration; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSURL *configurationURL = [[NSBundle bundleForClass:[SRAutobahnUtilities class]] URLForResource:@"autobahn_configuration" + withExtension:@"json"]; + NSInputStream *readStream = [NSInputStream inputStreamWithURL:configurationURL]; + [readStream open]; + configuration = [NSJSONSerialization JSONObjectWithStream:readStream options:0 error:nil]; + [readStream close]; + }); + return configuration; +} + +BOOL SRAutobahnIsValidResultBehavior(NSString *caseIdentifier, NSString *behavior) +{ + if ([behavior isEqualToString:@"OK"]) { + return YES; + } + + NSArray *cases = SRAutobahnTestConfiguration()[behavior]; + for (NSString *caseId in cases) { + if ([caseIdentifier hasPrefix:caseId]) { + return YES; + } + } + return NO; +} + +///-------------------------------------- +#pragma mark - Utilities +///-------------------------------------- + +BOOL SRRunLoopRunUntil(BOOL (^predicate)(), NSTimeInterval timeout) +{ + NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeout]; + + NSTimeInterval timeoutTime = [timeoutDate timeIntervalSinceReferenceDate]; + NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate]; + + while (!predicate() && currentTime < timeoutTime) { + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + currentTime = [NSDate timeIntervalSinceReferenceDate]; + } + return (currentTime <= timeoutTime); +} + +///-------------------------------------- +#pragma mark - Setup +///-------------------------------------- + +NSUInteger SRAutobahnTestCaseCount(void) +{ + static NSUInteger count; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + SRAutobahnOperation *operation = SRAutobahnTestCaseCountOperation(SRAutobahnTestServerURL(), + SRAutobahnTestAgentName(), + ^(NSInteger caseCount) { + count = caseCount; + }); + [operation start]; + + NSCAssert([operation waitUntilFinishedWithTimeout:10], @"Timed out fetching test case count."); + NSCAssert(!operation.error, @"CaseGetter should have successfully returned the number of testCases. Instead got error %@", operation.error); + }); + return count; +} + +NSDictionary *SRAutobahnTestCaseInfo(NSInteger caseNumber) +{ + __block NSDictionary *caseInfo = nil; + SRAutobahnOperation *operation = SRAutobahnTestCaseInfoOperation(SRAutobahnTestServerURL(), caseNumber, ^(NSDictionary * _Nullable info) { + caseInfo = info; + }); + [operation start]; + + NSCAssert([operation waitUntilFinishedWithTimeout:10], @"Timed out fetching test case info %ld.", (long)caseNumber); + NSCAssert(!operation.error, @"Updating the report should not have errored"); + return caseInfo; +} + +NS_ASSUME_NONNULL_END diff --git a/Vendor/xctoolchain b/Vendor/xctoolchain new file mode 160000 index 000000000..4e0bdfffe --- /dev/null +++ b/Vendor/xctoolchain @@ -0,0 +1 @@ +Subproject commit 4e0bdfffe539a69f2b63a7bb05a94018a6d4b07f diff --git a/extern/virtualenv/LICENSE.txt b/extern/virtualenv/LICENSE.txt deleted file mode 100644 index 2628d9368..000000000 --- a/extern/virtualenv/LICENSE.txt +++ /dev/null @@ -1,22 +0,0 @@ -Copyright (c) 2007 Ian Bicking and Contributors -Copyright (c) 2009 Ian Bicking, The Open Planning Project -Copyright (c) 2011 The virtualenv developers - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/extern/virtualenv/virtualenv.py b/extern/virtualenv/virtualenv.py deleted file mode 100755 index 9372da34e..000000000 --- a/extern/virtualenv/virtualenv.py +++ /dev/null @@ -1,2246 +0,0 @@ -#!/usr/bin/env python -"""Create a "virtual" Python installation -""" - -# If you change the version here, change it in setup.py -# and docs/conf.py as well. -virtualenv_version = "1.7" - -import base64 -import sys -import os -import optparse -import re -import shutil -import logging -import tempfile -import zlib -import errno -import distutils.sysconfig -from distutils.util import strtobool - -try: - import subprocess -except ImportError: - if sys.version_info <= (2, 3): - print('ERROR: %s' % sys.exc_info()[1]) - print('ERROR: this script requires Python 2.4 or greater; or at least the subprocess module.') - print('If you copy subprocess.py from a newer version of Python this script will probably work') - sys.exit(101) - else: - raise -try: - set -except NameError: - from sets import Set as set -try: - basestring -except NameError: - basestring = str - -try: - import ConfigParser -except ImportError: - import configparser as ConfigParser - -join = os.path.join -py_version = 'python%s.%s' % (sys.version_info[0], sys.version_info[1]) - -is_jython = sys.platform.startswith('java') -is_pypy = hasattr(sys, 'pypy_version_info') -is_win = (sys.platform == 'win32') -abiflags = getattr(sys, 'abiflags', '') - -user_dir = os.path.expanduser('~') -if sys.platform == 'win32': - user_dir = os.environ.get('APPDATA', user_dir) # Use %APPDATA% for roaming - default_storage_dir = os.path.join(user_dir, 'virtualenv') -else: - default_storage_dir = os.path.join(user_dir, '.virtualenv') -default_config_file = os.path.join(default_storage_dir, 'virtualenv.ini') - -if is_pypy: - expected_exe = 'pypy' -elif is_jython: - expected_exe = 'jython' -else: - expected_exe = 'python' - - -REQUIRED_MODULES = ['os', 'posix', 'posixpath', 'nt', 'ntpath', 'genericpath', - 'fnmatch', 'locale', 'encodings', 'codecs', - 'stat', 'UserDict', 'readline', 'copy_reg', 'types', - 're', 'sre', 'sre_parse', 'sre_constants', 'sre_compile', - 'zlib'] - -REQUIRED_FILES = ['lib-dynload', 'config'] - -majver, minver = sys.version_info[:2] -if majver == 2: - if minver >= 6: - REQUIRED_MODULES.extend(['warnings', 'linecache', '_abcoll', 'abc']) - if minver >= 7: - REQUIRED_MODULES.extend(['_weakrefset']) - if minver <= 3: - REQUIRED_MODULES.extend(['sets', '__future__']) -elif majver == 3: - # Some extra modules are needed for Python 3, but different ones - # for different versions. - REQUIRED_MODULES.extend(['_abcoll', 'warnings', 'linecache', 'abc', 'io', - '_weakrefset', 'copyreg', 'tempfile', 'random', - '__future__', 'collections', 'keyword', 'tarfile', - 'shutil', 'struct', 'copy']) - if minver >= 2: - REQUIRED_FILES[-1] = 'config-%s' % majver - if minver == 3: - # The whole list of 3.3 modules is reproduced below - the current - # uncommented ones are required for 3.3 as of now, but more may be - # added as 3.3 development continues. - REQUIRED_MODULES.extend([ - #"aifc", - #"antigravity", - #"argparse", - #"ast", - #"asynchat", - #"asyncore", - "base64", - #"bdb", - #"binhex", - "bisect", - #"calendar", - #"cgi", - #"cgitb", - #"chunk", - #"cmd", - #"codeop", - #"code", - #"colorsys", - #"_compat_pickle", - #"compileall", - #"concurrent", - #"configparser", - #"contextlib", - #"cProfile", - #"crypt", - #"csv", - #"ctypes", - #"curses", - #"datetime", - #"dbm", - #"decimal", - #"difflib", - #"dis", - #"doctest", - #"dummy_threading", - "_dummy_thread", - #"email", - #"filecmp", - #"fileinput", - #"formatter", - #"fractions", - #"ftplib", - #"functools", - #"getopt", - #"getpass", - #"gettext", - #"glob", - #"gzip", - "hashlib", - "heapq", - "hmac", - #"html", - #"http", - #"idlelib", - #"imaplib", - #"imghdr", - #"importlib", - #"inspect", - #"json", - #"lib2to3", - #"logging", - #"macpath", - #"macurl2path", - #"mailbox", - #"mailcap", - #"_markupbase", - #"mimetypes", - #"modulefinder", - #"multiprocessing", - #"netrc", - #"nntplib", - #"nturl2path", - #"numbers", - #"opcode", - #"optparse", - #"os2emxpath", - #"pdb", - #"pickle", - #"pickletools", - #"pipes", - #"pkgutil", - #"platform", - #"plat-linux2", - #"plistlib", - #"poplib", - #"pprint", - #"profile", - #"pstats", - #"pty", - #"pyclbr", - #"py_compile", - #"pydoc_data", - #"pydoc", - #"_pyio", - #"queue", - #"quopri", - "reprlib", - "rlcompleter", - #"runpy", - #"sched", - #"shelve", - #"shlex", - #"smtpd", - #"smtplib", - #"sndhdr", - #"socket", - #"socketserver", - #"sqlite3", - #"ssl", - #"stringprep", - #"string", - #"_strptime", - #"subprocess", - #"sunau", - #"symbol", - #"symtable", - #"sysconfig", - #"tabnanny", - #"telnetlib", - #"test", - #"textwrap", - #"this", - #"_threading_local", - #"threading", - #"timeit", - #"tkinter", - #"tokenize", - #"token", - #"traceback", - #"trace", - #"tty", - #"turtledemo", - #"turtle", - #"unittest", - #"urllib", - #"uuid", - #"uu", - #"wave", - "weakref", - #"webbrowser", - #"wsgiref", - #"xdrlib", - #"xml", - #"xmlrpc", - #"zipfile", - ]) - -if is_pypy: - # these are needed to correctly display the exceptions that may happen - # during the bootstrap - REQUIRED_MODULES.extend(['traceback', 'linecache']) - -class Logger(object): - - """ - Logging object for use in command-line script. Allows ranges of - levels, to avoid some redundancy of displayed information. - """ - - DEBUG = logging.DEBUG - INFO = logging.INFO - NOTIFY = (logging.INFO+logging.WARN)/2 - WARN = WARNING = logging.WARN - ERROR = logging.ERROR - FATAL = logging.FATAL - - LEVELS = [DEBUG, INFO, NOTIFY, WARN, ERROR, FATAL] - - def __init__(self, consumers): - self.consumers = consumers - self.indent = 0 - self.in_progress = None - self.in_progress_hanging = False - - def debug(self, msg, *args, **kw): - self.log(self.DEBUG, msg, *args, **kw) - def info(self, msg, *args, **kw): - self.log(self.INFO, msg, *args, **kw) - def notify(self, msg, *args, **kw): - self.log(self.NOTIFY, msg, *args, **kw) - def warn(self, msg, *args, **kw): - self.log(self.WARN, msg, *args, **kw) - def error(self, msg, *args, **kw): - self.log(self.WARN, msg, *args, **kw) - def fatal(self, msg, *args, **kw): - self.log(self.FATAL, msg, *args, **kw) - def log(self, level, msg, *args, **kw): - if args: - if kw: - raise TypeError( - "You may give positional or keyword arguments, not both") - args = args or kw - rendered = None - for consumer_level, consumer in self.consumers: - if self.level_matches(level, consumer_level): - if (self.in_progress_hanging - and consumer in (sys.stdout, sys.stderr)): - self.in_progress_hanging = False - sys.stdout.write('\n') - sys.stdout.flush() - if rendered is None: - if args: - rendered = msg % args - else: - rendered = msg - rendered = ' '*self.indent + rendered - if hasattr(consumer, 'write'): - consumer.write(rendered+'\n') - else: - consumer(rendered) - - def start_progress(self, msg): - assert not self.in_progress, ( - "Tried to start_progress(%r) while in_progress %r" - % (msg, self.in_progress)) - if self.level_matches(self.NOTIFY, self._stdout_level()): - sys.stdout.write(msg) - sys.stdout.flush() - self.in_progress_hanging = True - else: - self.in_progress_hanging = False - self.in_progress = msg - - def end_progress(self, msg='done.'): - assert self.in_progress, ( - "Tried to end_progress without start_progress") - if self.stdout_level_matches(self.NOTIFY): - if not self.in_progress_hanging: - # Some message has been printed out since start_progress - sys.stdout.write('...' + self.in_progress + msg + '\n') - sys.stdout.flush() - else: - sys.stdout.write(msg + '\n') - sys.stdout.flush() - self.in_progress = None - self.in_progress_hanging = False - - def show_progress(self): - """If we are in a progress scope, and no log messages have been - shown, write out another '.'""" - if self.in_progress_hanging: - sys.stdout.write('.') - sys.stdout.flush() - - def stdout_level_matches(self, level): - """Returns true if a message at this level will go to stdout""" - return self.level_matches(level, self._stdout_level()) - - def _stdout_level(self): - """Returns the level that stdout runs at""" - for level, consumer in self.consumers: - if consumer is sys.stdout: - return level - return self.FATAL - - def level_matches(self, level, consumer_level): - """ - >>> l = Logger([]) - >>> l.level_matches(3, 4) - False - >>> l.level_matches(3, 2) - True - >>> l.level_matches(slice(None, 3), 3) - False - >>> l.level_matches(slice(None, 3), 2) - True - >>> l.level_matches(slice(1, 3), 1) - True - >>> l.level_matches(slice(2, 3), 1) - False - """ - if isinstance(level, slice): - start, stop = level.start, level.stop - if start is not None and start > consumer_level: - return False - if stop is not None and stop <= consumer_level: - return False - return True - else: - return level >= consumer_level - - #@classmethod - def level_for_integer(cls, level): - levels = cls.LEVELS - if level < 0: - return levels[0] - if level >= len(levels): - return levels[-1] - return levels[level] - - level_for_integer = classmethod(level_for_integer) - -# create a silent logger just to prevent this from being undefined -# will be overridden with requested verbosity main() is called. -logger = Logger([(Logger.LEVELS[-1], sys.stdout)]) - -def mkdir(path): - if not os.path.exists(path): - logger.info('Creating %s', path) - os.makedirs(path) - else: - logger.info('Directory %s already exists', path) - -def copyfileordir(src, dest): - if os.path.isdir(src): - shutil.copytree(src, dest, True) - else: - shutil.copy2(src, dest) - -def copyfile(src, dest, symlink=True): - if not os.path.exists(src): - # Some bad symlink in the src - logger.warn('Cannot find file %s (bad symlink)', src) - return - if os.path.exists(dest): - logger.debug('File %s already exists', dest) - return - if not os.path.exists(os.path.dirname(dest)): - logger.info('Creating parent directories for %s' % os.path.dirname(dest)) - os.makedirs(os.path.dirname(dest)) - if not os.path.islink(src): - srcpath = os.path.abspath(src) - else: - srcpath = os.readlink(src) - if symlink and hasattr(os, 'symlink') and not is_win: - logger.info('Symlinking %s', dest) - try: - os.symlink(srcpath, dest) - except (OSError, NotImplementedError): - logger.info('Symlinking failed, copying to %s', dest) - copyfileordir(src, dest) - else: - logger.info('Copying to %s', dest) - copyfileordir(src, dest) - -def writefile(dest, content, overwrite=True): - if not os.path.exists(dest): - logger.info('Writing %s', dest) - f = open(dest, 'wb') - f.write(content.encode('utf-8')) - f.close() - return - else: - f = open(dest, 'rb') - c = f.read() - f.close() - if c != content: - if not overwrite: - logger.notify('File %s exists with different content; not overwriting', dest) - return - logger.notify('Overwriting %s with new content', dest) - f = open(dest, 'wb') - f.write(content.encode('utf-8')) - f.close() - else: - logger.info('Content %s already in place', dest) - -def rmtree(dir): - if os.path.exists(dir): - logger.notify('Deleting tree %s', dir) - shutil.rmtree(dir) - else: - logger.info('Do not need to delete %s; already gone', dir) - -def make_exe(fn): - if hasattr(os, 'chmod'): - oldmode = os.stat(fn).st_mode & 0xFFF # 0o7777 - newmode = (oldmode | 0x16D) & 0xFFF # 0o555, 0o7777 - os.chmod(fn, newmode) - logger.info('Changed mode of %s to %s', fn, oct(newmode)) - -def _find_file(filename, dirs): - for dir in reversed(dirs): - if os.path.exists(join(dir, filename)): - return join(dir, filename) - return filename - -def _install_req(py_executable, unzip=False, distribute=False, - search_dirs=None, never_download=False): - - if search_dirs is None: - search_dirs = file_search_dirs() - - if not distribute: - setup_fn = 'setuptools-0.6c11-py%s.egg' % sys.version[:3] - project_name = 'setuptools' - bootstrap_script = EZ_SETUP_PY - source = None - else: - setup_fn = None - source = 'distribute-0.6.24.tar.gz' - project_name = 'distribute' - bootstrap_script = DISTRIBUTE_SETUP_PY - - if setup_fn is not None: - setup_fn = _find_file(setup_fn, search_dirs) - - if source is not None: - source = _find_file(source, search_dirs) - - if is_jython and os._name == 'nt': - # Jython's .bat sys.executable can't handle a command line - # argument with newlines - fd, ez_setup = tempfile.mkstemp('.py') - os.write(fd, bootstrap_script) - os.close(fd) - cmd = [py_executable, ez_setup] - else: - cmd = [py_executable, '-c', bootstrap_script] - if unzip: - cmd.append('--always-unzip') - env = {} - remove_from_env = [] - if logger.stdout_level_matches(logger.DEBUG): - cmd.append('-v') - - old_chdir = os.getcwd() - if setup_fn is not None and os.path.exists(setup_fn): - logger.info('Using existing %s egg: %s' % (project_name, setup_fn)) - cmd.append(setup_fn) - if os.environ.get('PYTHONPATH'): - env['PYTHONPATH'] = setup_fn + os.path.pathsep + os.environ['PYTHONPATH'] - else: - env['PYTHONPATH'] = setup_fn - else: - # the source is found, let's chdir - if source is not None and os.path.exists(source): - logger.info('Using existing %s egg: %s' % (project_name, source)) - os.chdir(os.path.dirname(source)) - # in this case, we want to be sure that PYTHONPATH is unset (not - # just empty, really unset), else CPython tries to import the - # site.py that it's in virtualenv_support - remove_from_env.append('PYTHONPATH') - else: - if never_download: - logger.fatal("Can't find any local distributions of %s to install " - "and --never-download is set. Either re-run virtualenv " - "without the --never-download option, or place a %s " - "distribution (%s) in one of these " - "locations: %r" % (project_name, project_name, - setup_fn or source, - search_dirs)) - sys.exit(1) - - logger.info('No %s egg found; downloading' % project_name) - cmd.extend(['--always-copy', '-U', project_name]) - logger.start_progress('Installing %s...' % project_name) - logger.indent += 2 - cwd = None - if project_name == 'distribute': - env['DONT_PATCH_SETUPTOOLS'] = 'true' - - def _filter_ez_setup(line): - return filter_ez_setup(line, project_name) - - if not os.access(os.getcwd(), os.W_OK): - cwd = tempfile.mkdtemp() - if source is not None and os.path.exists(source): - # the current working dir is hostile, let's copy the - # tarball to a temp dir - target = os.path.join(cwd, os.path.split(source)[-1]) - shutil.copy(source, target) - try: - call_subprocess(cmd, show_stdout=False, - filter_stdout=_filter_ez_setup, - extra_env=env, - remove_from_env=remove_from_env, - cwd=cwd) - finally: - logger.indent -= 2 - logger.end_progress() - if os.getcwd() != old_chdir: - os.chdir(old_chdir) - if is_jython and os._name == 'nt': - os.remove(ez_setup) - -def file_search_dirs(): - here = os.path.dirname(os.path.abspath(__file__)) - dirs = ['.', here, - join(here, 'virtualenv_support')] - if os.path.splitext(os.path.dirname(__file__))[0] != 'virtualenv': - # Probably some boot script; just in case virtualenv is installed... - try: - import virtualenv - except ImportError: - pass - else: - dirs.append(os.path.join(os.path.dirname(virtualenv.__file__), 'virtualenv_support')) - return [d for d in dirs if os.path.isdir(d)] - -def install_setuptools(py_executable, unzip=False, - search_dirs=None, never_download=False): - _install_req(py_executable, unzip, - search_dirs=search_dirs, never_download=never_download) - -def install_distribute(py_executable, unzip=False, - search_dirs=None, never_download=False): - _install_req(py_executable, unzip, distribute=True, - search_dirs=search_dirs, never_download=never_download) - -_pip_re = re.compile(r'^pip-.*(zip|tar.gz|tar.bz2|tgz|tbz)$', re.I) -def install_pip(py_executable, search_dirs=None, never_download=False): - if search_dirs is None: - search_dirs = file_search_dirs() - - filenames = [] - for dir in search_dirs: - filenames.extend([join(dir, fn) for fn in os.listdir(dir) - if _pip_re.search(fn)]) - filenames = [(os.path.basename(filename).lower(), i, filename) for i, filename in enumerate(filenames)] - filenames.sort() - filenames = [filename for basename, i, filename in filenames] - if not filenames: - filename = 'pip' - else: - filename = filenames[-1] - easy_install_script = 'easy_install' - if sys.platform == 'win32': - easy_install_script = 'easy_install-script.py' - cmd = [join(os.path.dirname(py_executable), easy_install_script), filename] - if sys.platform == 'win32': - cmd.insert(0, py_executable) - if filename == 'pip': - if never_download: - logger.fatal("Can't find any local distributions of pip to install " - "and --never-download is set. Either re-run virtualenv " - "without the --never-download option, or place a pip " - "source distribution (zip/tar.gz/tar.bz2) in one of these " - "locations: %r" % search_dirs) - sys.exit(1) - logger.info('Installing pip from network...') - else: - logger.info('Installing existing %s distribution: %s' % ( - os.path.basename(filename), filename)) - logger.start_progress('Installing pip...') - logger.indent += 2 - def _filter_setup(line): - return filter_ez_setup(line, 'pip') - try: - call_subprocess(cmd, show_stdout=False, - filter_stdout=_filter_setup) - finally: - logger.indent -= 2 - logger.end_progress() - -def filter_ez_setup(line, project_name='setuptools'): - if not line.strip(): - return Logger.DEBUG - if project_name == 'distribute': - for prefix in ('Extracting', 'Now working', 'Installing', 'Before', - 'Scanning', 'Setuptools', 'Egg', 'Already', - 'running', 'writing', 'reading', 'installing', - 'creating', 'copying', 'byte-compiling', 'removing', - 'Processing'): - if line.startswith(prefix): - return Logger.DEBUG - return Logger.DEBUG - for prefix in ['Reading ', 'Best match', 'Processing setuptools', - 'Copying setuptools', 'Adding setuptools', - 'Installing ', 'Installed ']: - if line.startswith(prefix): - return Logger.DEBUG - return Logger.INFO - - -class UpdatingDefaultsHelpFormatter(optparse.IndentedHelpFormatter): - """ - Custom help formatter for use in ConfigOptionParser that updates - the defaults before expanding them, allowing them to show up correctly - in the help listing - """ - def expand_default(self, option): - if self.parser is not None: - self.parser.update_defaults(self.parser.defaults) - return optparse.IndentedHelpFormatter.expand_default(self, option) - - -class ConfigOptionParser(optparse.OptionParser): - """ - Custom option parser which updates its defaults by by checking the - configuration files and environmental variables - """ - def __init__(self, *args, **kwargs): - self.config = ConfigParser.RawConfigParser() - self.files = self.get_config_files() - self.config.read(self.files) - optparse.OptionParser.__init__(self, *args, **kwargs) - - def get_config_files(self): - config_file = os.environ.get('VIRTUALENV_CONFIG_FILE', False) - if config_file and os.path.exists(config_file): - return [config_file] - return [default_config_file] - - def update_defaults(self, defaults): - """ - Updates the given defaults with values from the config files and - the environ. Does a little special handling for certain types of - options (lists). - """ - # Then go and look for the other sources of configuration: - config = {} - # 1. config files - config.update(dict(self.get_config_section('virtualenv'))) - # 2. environmental variables - config.update(dict(self.get_environ_vars())) - # Then set the options with those values - for key, val in config.items(): - key = key.replace('_', '-') - if not key.startswith('--'): - key = '--%s' % key # only prefer long opts - option = self.get_option(key) - if option is not None: - # ignore empty values - if not val: - continue - # handle multiline configs - if option.action == 'append': - val = val.split() - else: - option.nargs = 1 - if option.action in ('store_true', 'store_false', 'count'): - val = strtobool(val) - try: - val = option.convert_value(key, val) - except optparse.OptionValueError: - e = sys.exc_info()[1] - print("An error occured during configuration: %s" % e) - sys.exit(3) - defaults[option.dest] = val - return defaults - - def get_config_section(self, name): - """ - Get a section of a configuration - """ - if self.config.has_section(name): - return self.config.items(name) - return [] - - def get_environ_vars(self, prefix='VIRTUALENV_'): - """ - Returns a generator with all environmental vars with prefix VIRTUALENV - """ - for key, val in os.environ.items(): - if key.startswith(prefix): - yield (key.replace(prefix, '').lower(), val) - - def get_default_values(self): - """ - Overridding to make updating the defaults after instantiation of - the option parser possible, update_defaults() does the dirty work. - """ - if not self.process_default_values: - # Old, pre-Optik 1.5 behaviour. - return optparse.Values(self.defaults) - - defaults = self.update_defaults(self.defaults.copy()) # ours - for option in self._get_all_options(): - default = defaults.get(option.dest) - if isinstance(default, basestring): - opt_str = option.get_opt_string() - defaults[option.dest] = option.check_value(opt_str, default) - return optparse.Values(defaults) - - -def main(): - parser = ConfigOptionParser( - version=virtualenv_version, - usage="%prog [OPTIONS] DEST_DIR", - formatter=UpdatingDefaultsHelpFormatter()) - - parser.add_option( - '-v', '--verbose', - action='count', - dest='verbose', - default=0, - help="Increase verbosity") - - parser.add_option( - '-q', '--quiet', - action='count', - dest='quiet', - default=0, - help='Decrease verbosity') - - parser.add_option( - '-p', '--python', - dest='python', - metavar='PYTHON_EXE', - help='The Python interpreter to use, e.g., --python=python2.5 will use the python2.5 ' - 'interpreter to create the new environment. The default is the interpreter that ' - 'virtualenv was installed with (%s)' % sys.executable) - - parser.add_option( - '--clear', - dest='clear', - action='store_true', - help="Clear out the non-root install and start from scratch") - - parser.add_option( - '--no-site-packages', - dest='no_site_packages', - action='store_true', - help="Don't give access to the global site-packages dir to the " - "virtual environment") - - parser.add_option( - '--system-site-packages', - dest='system_site_packages', - action='store_true', - help="Give access to the global site-packages dir to the " - "virtual environment") - - parser.add_option( - '--unzip-setuptools', - dest='unzip_setuptools', - action='store_true', - help="Unzip Setuptools or Distribute when installing it") - - parser.add_option( - '--relocatable', - dest='relocatable', - action='store_true', - help='Make an EXISTING virtualenv environment relocatable. ' - 'This fixes up scripts and makes all .pth files relative') - - parser.add_option( - '--distribute', - dest='use_distribute', - action='store_true', - help='Use Distribute instead of Setuptools. Set environ variable ' - 'VIRTUALENV_DISTRIBUTE to make it the default ') - - default_search_dirs = file_search_dirs() - parser.add_option( - '--extra-search-dir', - dest="search_dirs", - action="append", - default=default_search_dirs, - help="Directory to look for setuptools/distribute/pip distributions in. " - "You can add any number of additional --extra-search-dir paths.") - - parser.add_option( - '--never-download', - dest="never_download", - action="store_true", - help="Never download anything from the network. Instead, virtualenv will fail " - "if local distributions of setuptools/distribute/pip are not present.") - - parser.add_option( - '--prompt=', - dest='prompt', - help='Provides an alternative prompt prefix for this environment') - - if 'extend_parser' in globals(): - extend_parser(parser) - - options, args = parser.parse_args() - - global logger - - if 'adjust_options' in globals(): - adjust_options(options, args) - - verbosity = options.verbose - options.quiet - logger = Logger([(Logger.level_for_integer(2-verbosity), sys.stdout)]) - - if options.python and not os.environ.get('VIRTUALENV_INTERPRETER_RUNNING'): - env = os.environ.copy() - interpreter = resolve_interpreter(options.python) - if interpreter == sys.executable: - logger.warn('Already using interpreter %s' % interpreter) - else: - logger.notify('Running virtualenv with interpreter %s' % interpreter) - env['VIRTUALENV_INTERPRETER_RUNNING'] = 'true' - file = __file__ - if file.endswith('.pyc'): - file = file[:-1] - popen = subprocess.Popen([interpreter, file] + sys.argv[1:], env=env) - raise SystemExit(popen.wait()) - - # Force --distribute on Python 3, since setuptools is not available. - if majver > 2: - options.use_distribute = True - - if os.environ.get('PYTHONDONTWRITEBYTECODE') and not options.use_distribute: - print( - "The PYTHONDONTWRITEBYTECODE environment variable is " - "not compatible with setuptools. Either use --distribute " - "or unset PYTHONDONTWRITEBYTECODE.") - sys.exit(2) - if not args: - print('You must provide a DEST_DIR') - parser.print_help() - sys.exit(2) - if len(args) > 1: - print('There must be only one argument: DEST_DIR (you gave %s)' % ( - ' '.join(args))) - parser.print_help() - sys.exit(2) - - home_dir = args[0] - - if os.environ.get('WORKING_ENV'): - logger.fatal('ERROR: you cannot run virtualenv while in a workingenv') - logger.fatal('Please deactivate your workingenv, then re-run this script') - sys.exit(3) - - if 'PYTHONHOME' in os.environ: - logger.warn('PYTHONHOME is set. You *must* activate the virtualenv before using it') - del os.environ['PYTHONHOME'] - - if options.relocatable: - make_environment_relocatable(home_dir) - return - - if options.no_site_packages: - logger.warn('The --no-site-packages flag is deprecated; it is now ' - 'the default behavior.') - - create_environment(home_dir, - site_packages=options.system_site_packages, - clear=options.clear, - unzip_setuptools=options.unzip_setuptools, - use_distribute=options.use_distribute, - prompt=options.prompt, - search_dirs=options.search_dirs, - never_download=options.never_download) - if 'after_install' in globals(): - after_install(options, home_dir) - -def call_subprocess(cmd, show_stdout=True, - filter_stdout=None, cwd=None, - raise_on_returncode=True, extra_env=None, - remove_from_env=None): - cmd_parts = [] - for part in cmd: - if len(part) > 45: - part = part[:20]+"..."+part[-20:] - if ' ' in part or '\n' in part or '"' in part or "'" in part: - part = '"%s"' % part.replace('"', '\\"') - if hasattr(part, 'decode'): - try: - part = part.decode(sys.getdefaultencoding()) - except UnicodeDecodeError: - part = part.decode(sys.getfilesystemencoding()) - cmd_parts.append(part) - cmd_desc = ' '.join(cmd_parts) - if show_stdout: - stdout = None - else: - stdout = subprocess.PIPE - logger.debug("Running command %s" % cmd_desc) - if extra_env or remove_from_env: - env = os.environ.copy() - if extra_env: - env.update(extra_env) - if remove_from_env: - for varname in remove_from_env: - env.pop(varname, None) - else: - env = None - try: - proc = subprocess.Popen( - cmd, stderr=subprocess.STDOUT, stdin=None, stdout=stdout, - cwd=cwd, env=env) - except Exception: - e = sys.exc_info()[1] - logger.fatal( - "Error %s while executing command %s" % (e, cmd_desc)) - raise - all_output = [] - if stdout is not None: - stdout = proc.stdout - encoding = sys.getdefaultencoding() - fs_encoding = sys.getfilesystemencoding() - while 1: - line = stdout.readline() - try: - line = line.decode(encoding) - except UnicodeDecodeError: - line = line.decode(fs_encoding) - if not line: - break - line = line.rstrip() - all_output.append(line) - if filter_stdout: - level = filter_stdout(line) - if isinstance(level, tuple): - level, line = level - logger.log(level, line) - if not logger.stdout_level_matches(level): - logger.show_progress() - else: - logger.info(line) - else: - proc.communicate() - proc.wait() - if proc.returncode: - if raise_on_returncode: - if all_output: - logger.notify('Complete output from command %s:' % cmd_desc) - logger.notify('\n'.join(all_output) + '\n----------------------------------------') - raise OSError( - "Command %s failed with error code %s" - % (cmd_desc, proc.returncode)) - else: - logger.warn( - "Command %s had error code %s" - % (cmd_desc, proc.returncode)) - - -def create_environment(home_dir, site_packages=False, clear=False, - unzip_setuptools=False, use_distribute=False, - prompt=None, search_dirs=None, never_download=False): - """ - Creates a new environment in ``home_dir``. - - If ``site_packages`` is true, then the global ``site-packages/`` - directory will be on the path. - - If ``clear`` is true (default False) then the environment will - first be cleared. - """ - home_dir, lib_dir, inc_dir, bin_dir = path_locations(home_dir) - - py_executable = os.path.abspath(install_python( - home_dir, lib_dir, inc_dir, bin_dir, - site_packages=site_packages, clear=clear)) - - install_distutils(home_dir) - - # use_distribute also is True if VIRTUALENV_DISTRIBUTE env var is set - # we also check VIRTUALENV_USE_DISTRIBUTE for backwards compatibility - if use_distribute or os.environ.get('VIRTUALENV_USE_DISTRIBUTE'): - install_distribute(py_executable, unzip=unzip_setuptools, - search_dirs=search_dirs, never_download=never_download) - else: - install_setuptools(py_executable, unzip=unzip_setuptools, - search_dirs=search_dirs, never_download=never_download) - - install_pip(py_executable, search_dirs=search_dirs, never_download=never_download) - - install_activate(home_dir, bin_dir, prompt) - -def path_locations(home_dir): - """Return the path locations for the environment (where libraries are, - where scripts go, etc)""" - # XXX: We'd use distutils.sysconfig.get_python_inc/lib but its - # prefix arg is broken: http://bugs.python.org/issue3386 - if sys.platform == 'win32': - # Windows has lots of problems with executables with spaces in - # the name; this function will remove them (using the ~1 - # format): - mkdir(home_dir) - if ' ' in home_dir: - try: - import win32api - except ImportError: - print('Error: the path "%s" has a space in it' % home_dir) - print('To handle these kinds of paths, the win32api module must be installed:') - print(' http://sourceforge.net/projects/pywin32/') - sys.exit(3) - home_dir = win32api.GetShortPathName(home_dir) - lib_dir = join(home_dir, 'Lib') - inc_dir = join(home_dir, 'Include') - bin_dir = join(home_dir, 'Scripts') - elif is_jython: - lib_dir = join(home_dir, 'Lib') - inc_dir = join(home_dir, 'Include') - bin_dir = join(home_dir, 'bin') - elif is_pypy: - lib_dir = home_dir - inc_dir = join(home_dir, 'include') - bin_dir = join(home_dir, 'bin') - else: - lib_dir = join(home_dir, 'lib', py_version) - inc_dir = join(home_dir, 'include', py_version + abiflags) - bin_dir = join(home_dir, 'bin') - return home_dir, lib_dir, inc_dir, bin_dir - - -def change_prefix(filename, dst_prefix): - prefixes = [sys.prefix] - - if sys.platform == "darwin": - prefixes.extend(( - os.path.join("/Library/Python", sys.version[:3], "site-packages"), - os.path.join(sys.prefix, "Extras", "lib", "python"), - os.path.join("~", "Library", "Python", sys.version[:3], "site-packages"))) - - if hasattr(sys, 'real_prefix'): - prefixes.append(sys.real_prefix) - prefixes = list(map(os.path.abspath, prefixes)) - filename = os.path.abspath(filename) - for src_prefix in prefixes: - if filename.startswith(src_prefix): - _, relpath = filename.split(src_prefix, 1) - assert relpath[0] == os.sep - relpath = relpath[1:] - return join(dst_prefix, relpath) - assert False, "Filename %s does not start with any of these prefixes: %s" % \ - (filename, prefixes) - -def copy_required_modules(dst_prefix): - import imp - # If we are running under -p, we need to remove the current - # directory from sys.path temporarily here, so that we - # definitely get the modules from the site directory of - # the interpreter we are running under, not the one - # virtualenv.py is installed under (which might lead to py2/py3 - # incompatibility issues) - _prev_sys_path = sys.path - if os.environ.get('VIRTUALENV_INTERPRETER_RUNNING'): - sys.path = sys.path[1:] - try: - for modname in REQUIRED_MODULES: - if modname in sys.builtin_module_names: - logger.info("Ignoring built-in bootstrap module: %s" % modname) - continue - try: - f, filename, _ = imp.find_module(modname) - except ImportError: - logger.info("Cannot import bootstrap module: %s" % modname) - else: - if f is not None: - f.close() - dst_filename = change_prefix(filename, dst_prefix) - copyfile(filename, dst_filename) - if filename.endswith('.pyc'): - pyfile = filename[:-1] - if os.path.exists(pyfile): - copyfile(pyfile, dst_filename[:-1]) - finally: - sys.path = _prev_sys_path - -def install_python(home_dir, lib_dir, inc_dir, bin_dir, site_packages, clear): - """Install just the base environment, no distutils patches etc""" - if sys.executable.startswith(bin_dir): - print('Please use the *system* python to run this script') - return - - if clear: - rmtree(lib_dir) - ## FIXME: why not delete it? - ## Maybe it should delete everything with #!/path/to/venv/python in it - logger.notify('Not deleting %s', bin_dir) - - if hasattr(sys, 'real_prefix'): - logger.notify('Using real prefix %r' % sys.real_prefix) - prefix = sys.real_prefix - else: - prefix = sys.prefix - mkdir(lib_dir) - fix_lib64(lib_dir) - stdlib_dirs = [os.path.dirname(os.__file__)] - if sys.platform == 'win32': - stdlib_dirs.append(join(os.path.dirname(stdlib_dirs[0]), 'DLLs')) - elif sys.platform == 'darwin': - stdlib_dirs.append(join(stdlib_dirs[0], 'site-packages')) - if hasattr(os, 'symlink'): - logger.info('Symlinking Python bootstrap modules') - else: - logger.info('Copying Python bootstrap modules') - logger.indent += 2 - try: - # copy required files... - for stdlib_dir in stdlib_dirs: - if not os.path.isdir(stdlib_dir): - continue - for fn in os.listdir(stdlib_dir): - bn = os.path.splitext(fn)[0] - if fn != 'site-packages' and bn in REQUIRED_FILES: - copyfile(join(stdlib_dir, fn), join(lib_dir, fn)) - # ...and modules - copy_required_modules(home_dir) - finally: - logger.indent -= 2 - mkdir(join(lib_dir, 'site-packages')) - import site - site_filename = site.__file__ - if site_filename.endswith('.pyc'): - site_filename = site_filename[:-1] - elif site_filename.endswith('$py.class'): - site_filename = site_filename.replace('$py.class', '.py') - site_filename_dst = change_prefix(site_filename, home_dir) - site_dir = os.path.dirname(site_filename_dst) - writefile(site_filename_dst, SITE_PY) - writefile(join(site_dir, 'orig-prefix.txt'), prefix) - site_packages_filename = join(site_dir, 'no-global-site-packages.txt') - if not site_packages: - writefile(site_packages_filename, '') - else: - if os.path.exists(site_packages_filename): - logger.info('Deleting %s' % site_packages_filename) - os.unlink(site_packages_filename) - - if is_pypy or is_win: - stdinc_dir = join(prefix, 'include') - else: - stdinc_dir = join(prefix, 'include', py_version + abiflags) - if os.path.exists(stdinc_dir): - copyfile(stdinc_dir, inc_dir) - else: - logger.debug('No include dir %s' % stdinc_dir) - - # pypy never uses exec_prefix, just ignore it - if sys.exec_prefix != prefix and not is_pypy: - if sys.platform == 'win32': - exec_dir = join(sys.exec_prefix, 'lib') - elif is_jython: - exec_dir = join(sys.exec_prefix, 'Lib') - else: - exec_dir = join(sys.exec_prefix, 'lib', py_version) - for fn in os.listdir(exec_dir): - copyfile(join(exec_dir, fn), join(lib_dir, fn)) - - if is_jython: - # Jython has either jython-dev.jar and javalib/ dir, or just - # jython.jar - for name in 'jython-dev.jar', 'javalib', 'jython.jar': - src = join(prefix, name) - if os.path.exists(src): - copyfile(src, join(home_dir, name)) - # XXX: registry should always exist after Jython 2.5rc1 - src = join(prefix, 'registry') - if os.path.exists(src): - copyfile(src, join(home_dir, 'registry'), symlink=False) - copyfile(join(prefix, 'cachedir'), join(home_dir, 'cachedir'), - symlink=False) - - mkdir(bin_dir) - py_executable = join(bin_dir, os.path.basename(sys.executable)) - if 'Python.framework' in prefix: - if re.search(r'/Python(?:-32|-64)*$', py_executable): - # The name of the python executable is not quite what - # we want, rename it. - py_executable = os.path.join( - os.path.dirname(py_executable), 'python') - - logger.notify('New %s executable in %s', expected_exe, py_executable) - pcbuild_dir = os.path.dirname(sys.executable) - pyd_pth = os.path.join(lib_dir, 'site-packages', 'virtualenv_builddir_pyd.pth') - if is_win and os.path.exists(os.path.join(pcbuild_dir, 'build.bat')): - logger.notify('Detected python running from build directory %s', pcbuild_dir) - logger.notify('Writing .pth file linking to build directory for *.pyd files') - writefile(pyd_pth, pcbuild_dir) - else: - pcbuild_dir = None - if os.path.exists(pyd_pth): - logger.info('Deleting %s (not Windows env or not build directory python)' % pyd_pth) - os.unlink(pyd_pth) - - if sys.executable != py_executable: - ## FIXME: could I just hard link? - executable = sys.executable - if sys.platform == 'cygwin' and os.path.exists(executable + '.exe'): - # Cygwin misreports sys.executable sometimes - executable += '.exe' - py_executable += '.exe' - logger.info('Executable actually exists in %s' % executable) - shutil.copyfile(executable, py_executable) - make_exe(py_executable) - if sys.platform == 'win32' or sys.platform == 'cygwin': - pythonw = os.path.join(os.path.dirname(sys.executable), 'pythonw.exe') - if os.path.exists(pythonw): - logger.info('Also created pythonw.exe') - shutil.copyfile(pythonw, os.path.join(os.path.dirname(py_executable), 'pythonw.exe')) - # we need to copy the DLL to enforce that windows will load the correct one. - # may not exist if we are cygwin. - py_executable_dll = 'python%s%s.dll' % ( - sys.version_info[0], sys.version_info[1]) - pythondll = os.path.join(os.path.dirname(sys.executable), py_executable_dll) - if os.path.exists(pythondll): - logger.info('Also created %s' % py_executable_dll) - shutil.copyfile(pythondll, os.path.join(os.path.dirname(py_executable), py_executable_dll)) - if is_pypy: - # make a symlink python --> pypy-c - python_executable = os.path.join(os.path.dirname(py_executable), 'python') - logger.info('Also created executable %s' % python_executable) - copyfile(py_executable, python_executable) - - if os.path.splitext(os.path.basename(py_executable))[0] != expected_exe: - secondary_exe = os.path.join(os.path.dirname(py_executable), - expected_exe) - py_executable_ext = os.path.splitext(py_executable)[1] - if py_executable_ext == '.exe': - # python2.4 gives an extension of '.4' :P - secondary_exe += py_executable_ext - if os.path.exists(secondary_exe): - logger.warn('Not overwriting existing %s script %s (you must use %s)' - % (expected_exe, secondary_exe, py_executable)) - else: - logger.notify('Also creating executable in %s' % secondary_exe) - shutil.copyfile(sys.executable, secondary_exe) - make_exe(secondary_exe) - - if 'Python.framework' in prefix: - logger.debug('MacOSX Python framework detected') - - # Make sure we use the the embedded interpreter inside - # the framework, even if sys.executable points to - # the stub executable in ${sys.prefix}/bin - # See http://groups.google.com/group/python-virtualenv/ - # browse_thread/thread/17cab2f85da75951 - original_python = os.path.join( - prefix, 'Resources/Python.app/Contents/MacOS/Python') - shutil.copy(original_python, py_executable) - - # Copy the framework's dylib into the virtual - # environment - virtual_lib = os.path.join(home_dir, '.Python') - - if os.path.exists(virtual_lib): - os.unlink(virtual_lib) - copyfile( - os.path.join(prefix, 'Python'), - virtual_lib) - - # And then change the install_name of the copied python executable - try: - call_subprocess( - ["install_name_tool", "-change", - os.path.join(prefix, 'Python'), - '@executable_path/../.Python', - py_executable]) - except: - logger.fatal( - "Could not call install_name_tool -- you must have Apple's development tools installed") - raise - - # Some tools depend on pythonX.Y being present - py_executable_version = '%s.%s' % ( - sys.version_info[0], sys.version_info[1]) - if not py_executable.endswith(py_executable_version): - # symlinking pythonX.Y > python - pth = py_executable + '%s.%s' % ( - sys.version_info[0], sys.version_info[1]) - if os.path.exists(pth): - os.unlink(pth) - os.symlink('python', pth) - else: - # reverse symlinking python -> pythonX.Y (with --python) - pth = join(bin_dir, 'python') - if os.path.exists(pth): - os.unlink(pth) - os.symlink(os.path.basename(py_executable), pth) - - if sys.platform == 'win32' and ' ' in py_executable: - # There's a bug with subprocess on Windows when using a first - # argument that has a space in it. Instead we have to quote - # the value: - py_executable = '"%s"' % py_executable - cmd = [py_executable, '-c', """ -import sys -prefix = sys.prefix -if sys.version_info[0] == 3: - prefix = prefix.encode('utf8') -if hasattr(sys.stdout, 'detach'): - sys.stdout = sys.stdout.detach() -elif hasattr(sys.stdout, 'buffer'): - sys.stdout = sys.stdout.buffer -sys.stdout.write(prefix) -"""] - logger.info('Testing executable with %s %s "%s"' % tuple(cmd)) - try: - proc = subprocess.Popen(cmd, - stdout=subprocess.PIPE) - proc_stdout, proc_stderr = proc.communicate() - except OSError: - e = sys.exc_info()[1] - if e.errno == errno.EACCES: - logger.fatal('ERROR: The executable %s could not be run: %s' % (py_executable, e)) - sys.exit(100) - else: - raise e - - proc_stdout = proc_stdout.strip().decode("utf-8") - proc_stdout = os.path.normcase(os.path.abspath(proc_stdout)) - norm_home_dir = os.path.normcase(os.path.abspath(home_dir)) - if hasattr(norm_home_dir, 'decode'): - norm_home_dir = norm_home_dir.decode(sys.getfilesystemencoding()) - if proc_stdout != norm_home_dir: - logger.fatal( - 'ERROR: The executable %s is not functioning' % py_executable) - logger.fatal( - 'ERROR: It thinks sys.prefix is %r (should be %r)' - % (proc_stdout, norm_home_dir)) - logger.fatal( - 'ERROR: virtualenv is not compatible with this system or executable') - if sys.platform == 'win32': - logger.fatal( - 'Note: some Windows users have reported this error when they ' - 'installed Python for "Only this user" or have multiple ' - 'versions of Python installed. Copying the appropriate ' - 'PythonXX.dll to the virtualenv Scripts/ directory may fix ' - 'this problem.') - sys.exit(100) - else: - logger.info('Got sys.prefix result: %r' % proc_stdout) - - pydistutils = os.path.expanduser('~/.pydistutils.cfg') - if os.path.exists(pydistutils): - logger.notify('Please make sure you remove any previous custom paths from ' - 'your %s file.' % pydistutils) - ## FIXME: really this should be calculated earlier - - fix_local_scheme(home_dir) - - return py_executable - -def install_activate(home_dir, bin_dir, prompt=None): - home_dir = os.path.abspath(home_dir) - if sys.platform == 'win32' or is_jython and os._name == 'nt': - files = { - 'activate.bat': ACTIVATE_BAT, - 'deactivate.bat': DEACTIVATE_BAT, - 'activate.ps1': ACTIVATE_PS, - } - - # MSYS needs paths of the form /c/path/to/file - drive, tail = os.path.splitdrive(home_dir.replace(os.sep, '/')) - home_dir_msys = (drive and "/%s%s" or "%s%s") % (drive[:1], tail) - - # Run-time conditional enables (basic) Cygwin compatibility - home_dir_sh = ("""$(if [ "$OSTYPE" == "cygwin" ]; then cygpath -u '%s'; else echo '%s'; fi;)""" % - (home_dir, home_dir_msys)) - files['activate'] = ACTIVATE_SH.replace('__VIRTUAL_ENV__', home_dir_sh) - - else: - files = {'activate': ACTIVATE_SH} - - # suppling activate.fish in addition to, not instead of, the - # bash script support. - files['activate.fish'] = ACTIVATE_FISH - - # same for csh/tcsh support... - files['activate.csh'] = ACTIVATE_CSH - - files['activate_this.py'] = ACTIVATE_THIS - if hasattr(home_dir, 'decode'): - home_dir = home_dir.decode(sys.getfilesystemencoding()) - vname = os.path.basename(home_dir) - for name, content in files.items(): - content = content.replace('__VIRTUAL_PROMPT__', prompt or '') - content = content.replace('__VIRTUAL_WINPROMPT__', prompt or '(%s)' % vname) - content = content.replace('__VIRTUAL_ENV__', home_dir) - content = content.replace('__VIRTUAL_NAME__', vname) - content = content.replace('__BIN_NAME__', os.path.basename(bin_dir)) - writefile(os.path.join(bin_dir, name), content) - -def install_distutils(home_dir): - distutils_path = change_prefix(distutils.__path__[0], home_dir) - mkdir(distutils_path) - ## FIXME: maybe this prefix setting should only be put in place if - ## there's a local distutils.cfg with a prefix setting? - home_dir = os.path.abspath(home_dir) - ## FIXME: this is breaking things, removing for now: - #distutils_cfg = DISTUTILS_CFG + "\n[install]\nprefix=%s\n" % home_dir - writefile(os.path.join(distutils_path, '__init__.py'), DISTUTILS_INIT) - writefile(os.path.join(distutils_path, 'distutils.cfg'), DISTUTILS_CFG, overwrite=False) - -def fix_local_scheme(home_dir): - """ - Platforms that use the "posix_local" install scheme (like Ubuntu with - Python 2.7) need to be given an additional "local" location, sigh. - """ - try: - import sysconfig - except ImportError: - pass - else: - if sysconfig._get_default_scheme() == 'posix_local': - local_path = os.path.join(home_dir, 'local') - if not os.path.exists(local_path): - os.mkdir(local_path) - for subdir_name in os.listdir(home_dir): - if subdir_name == 'local': - continue - os.symlink(os.path.abspath(os.path.join(home_dir, subdir_name)), \ - os.path.join(local_path, subdir_name)) - -def fix_lib64(lib_dir): - """ - Some platforms (particularly Gentoo on x64) put things in lib64/pythonX.Y - instead of lib/pythonX.Y. If this is such a platform we'll just create a - symlink so lib64 points to lib - """ - if [p for p in distutils.sysconfig.get_config_vars().values() - if isinstance(p, basestring) and 'lib64' in p]: - logger.debug('This system uses lib64; symlinking lib64 to lib') - assert os.path.basename(lib_dir) == 'python%s' % sys.version[:3], ( - "Unexpected python lib dir: %r" % lib_dir) - lib_parent = os.path.dirname(lib_dir) - assert os.path.basename(lib_parent) == 'lib', ( - "Unexpected parent dir: %r" % lib_parent) - copyfile(lib_parent, os.path.join(os.path.dirname(lib_parent), 'lib64')) - -def resolve_interpreter(exe): - """ - If the executable given isn't an absolute path, search $PATH for the interpreter - """ - if os.path.abspath(exe) != exe: - paths = os.environ.get('PATH', '').split(os.pathsep) - for path in paths: - if os.path.exists(os.path.join(path, exe)): - exe = os.path.join(path, exe) - break - if not os.path.exists(exe): - logger.fatal('The executable %s (from --python=%s) does not exist' % (exe, exe)) - raise SystemExit(3) - if not is_executable(exe): - logger.fatal('The executable %s (from --python=%s) is not executable' % (exe, exe)) - raise SystemExit(3) - return exe - -def is_executable(exe): - """Checks a file is executable""" - return os.access(exe, os.X_OK) - -############################################################ -## Relocating the environment: - -def make_environment_relocatable(home_dir): - """ - Makes the already-existing environment use relative paths, and takes out - the #!-based environment selection in scripts. - """ - home_dir, lib_dir, inc_dir, bin_dir = path_locations(home_dir) - activate_this = os.path.join(bin_dir, 'activate_this.py') - if not os.path.exists(activate_this): - logger.fatal( - 'The environment doesn\'t have a file %s -- please re-run virtualenv ' - 'on this environment to update it' % activate_this) - fixup_scripts(home_dir) - fixup_pth_and_egg_link(home_dir) - ## FIXME: need to fix up distutils.cfg - -OK_ABS_SCRIPTS = ['python', 'python%s' % sys.version[:3], - 'activate', 'activate.bat', 'activate_this.py'] - -def fixup_scripts(home_dir): - # This is what we expect at the top of scripts: - shebang = '#!%s/bin/python' % os.path.normcase(os.path.abspath(home_dir)) - # This is what we'll put: - new_shebang = '#!/usr/bin/env python%s' % sys.version[:3] - activate = "import os; activate_this=os.path.join(os.path.dirname(os.path.realpath(__file__)), 'activate_this.py'); execfile(activate_this, dict(__file__=activate_this)); del os, activate_this" - if sys.platform == 'win32': - bin_suffix = 'Scripts' - else: - bin_suffix = 'bin' - bin_dir = os.path.join(home_dir, bin_suffix) - home_dir, lib_dir, inc_dir, bin_dir = path_locations(home_dir) - for filename in os.listdir(bin_dir): - filename = os.path.join(bin_dir, filename) - if not os.path.isfile(filename): - # ignore subdirs, e.g. .svn ones. - continue - f = open(filename, 'rb') - lines = f.readlines() - f.close() - if not lines: - logger.warn('Script %s is an empty file' % filename) - continue - if not lines[0].strip().startswith(shebang): - if os.path.basename(filename) in OK_ABS_SCRIPTS: - logger.debug('Cannot make script %s relative' % filename) - elif lines[0].strip() == new_shebang: - logger.info('Script %s has already been made relative' % filename) - else: - logger.warn('Script %s cannot be made relative (it\'s not a normal script that starts with %s)' - % (filename, shebang)) - continue - logger.notify('Making script %s relative' % filename) - lines = [new_shebang+'\n', activate+'\n'] + lines[1:] - f = open(filename, 'wb') - f.writelines(lines) - f.close() - -def fixup_pth_and_egg_link(home_dir, sys_path=None): - """Makes .pth and .egg-link files use relative paths""" - home_dir = os.path.normcase(os.path.abspath(home_dir)) - if sys_path is None: - sys_path = sys.path - for path in sys_path: - if not path: - path = '.' - if not os.path.isdir(path): - continue - path = os.path.normcase(os.path.abspath(path)) - if not path.startswith(home_dir): - logger.debug('Skipping system (non-environment) directory %s' % path) - continue - for filename in os.listdir(path): - filename = os.path.join(path, filename) - if filename.endswith('.pth'): - if not os.access(filename, os.W_OK): - logger.warn('Cannot write .pth file %s, skipping' % filename) - else: - fixup_pth_file(filename) - if filename.endswith('.egg-link'): - if not os.access(filename, os.W_OK): - logger.warn('Cannot write .egg-link file %s, skipping' % filename) - else: - fixup_egg_link(filename) - -def fixup_pth_file(filename): - lines = [] - prev_lines = [] - f = open(filename) - prev_lines = f.readlines() - f.close() - for line in prev_lines: - line = line.strip() - if (not line or line.startswith('#') or line.startswith('import ') - or os.path.abspath(line) != line): - lines.append(line) - else: - new_value = make_relative_path(filename, line) - if line != new_value: - logger.debug('Rewriting path %s as %s (in %s)' % (line, new_value, filename)) - lines.append(new_value) - if lines == prev_lines: - logger.info('No changes to .pth file %s' % filename) - return - logger.notify('Making paths in .pth file %s relative' % filename) - f = open(filename, 'w') - f.write('\n'.join(lines) + '\n') - f.close() - -def fixup_egg_link(filename): - f = open(filename) - link = f.read().strip() - f.close() - if os.path.abspath(link) != link: - logger.debug('Link in %s already relative' % filename) - return - new_link = make_relative_path(filename, link) - logger.notify('Rewriting link %s in %s as %s' % (link, filename, new_link)) - f = open(filename, 'w') - f.write(new_link) - f.close() - -def make_relative_path(source, dest, dest_is_directory=True): - """ - Make a filename relative, where the filename is dest, and it is - being referred to from the filename source. - - >>> make_relative_path('/usr/share/something/a-file.pth', - ... '/usr/share/another-place/src/Directory') - '../another-place/src/Directory' - >>> make_relative_path('/usr/share/something/a-file.pth', - ... '/home/user/src/Directory') - '../../../home/user/src/Directory' - >>> make_relative_path('/usr/share/a-file.pth', '/usr/share/') - './' - """ - source = os.path.dirname(source) - if not dest_is_directory: - dest_filename = os.path.basename(dest) - dest = os.path.dirname(dest) - dest = os.path.normpath(os.path.abspath(dest)) - source = os.path.normpath(os.path.abspath(source)) - dest_parts = dest.strip(os.path.sep).split(os.path.sep) - source_parts = source.strip(os.path.sep).split(os.path.sep) - while dest_parts and source_parts and dest_parts[0] == source_parts[0]: - dest_parts.pop(0) - source_parts.pop(0) - full_parts = ['..']*len(source_parts) + dest_parts - if not dest_is_directory: - full_parts.append(dest_filename) - if not full_parts: - # Special case for the current directory (otherwise it'd be '') - return './' - return os.path.sep.join(full_parts) - - - -############################################################ -## Bootstrap script creation: - -def create_bootstrap_script(extra_text, python_version=''): - """ - Creates a bootstrap script, which is like this script but with - extend_parser, adjust_options, and after_install hooks. - - This returns a string that (written to disk of course) can be used - as a bootstrap script with your own customizations. The script - will be the standard virtualenv.py script, with your extra text - added (your extra text should be Python code). - - If you include these functions, they will be called: - - ``extend_parser(optparse_parser)``: - You can add or remove options from the parser here. - - ``adjust_options(options, args)``: - You can change options here, or change the args (if you accept - different kinds of arguments, be sure you modify ``args`` so it is - only ``[DEST_DIR]``). - - ``after_install(options, home_dir)``: - - After everything is installed, this function is called. This - is probably the function you are most likely to use. An - example would be:: - - def after_install(options, home_dir): - subprocess.call([join(home_dir, 'bin', 'easy_install'), - 'MyPackage']) - subprocess.call([join(home_dir, 'bin', 'my-package-script'), - 'setup', home_dir]) - - This example immediately installs a package, and runs a setup - script from that package. - - If you provide something like ``python_version='2.4'`` then the - script will start with ``#!/usr/bin/env python2.4`` instead of - ``#!/usr/bin/env python``. You can use this when the script must - be run with a particular Python version. - """ - filename = __file__ - if filename.endswith('.pyc'): - filename = filename[:-1] - f = open(filename, 'rb') - content = f.read() - f.close() - py_exe = 'python%s' % python_version - content = (('#!/usr/bin/env %s\n' % py_exe) - + '## WARNING: This file is generated\n' - + content) - return content.replace('##EXT' 'END##', extra_text) - -##EXTEND## - -def convert(s): - b = base64.b64decode(s.encode('ascii')) - return zlib.decompress(b).decode('utf-8') - -##file site.py -SITE_PY = convert(""" -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 -Q7wQmAIMcman2gOwRiH4P2ts6wE= -""") - -##file ez_setup.py -EZ_SETUP_PY = convert(""" -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 -BDaonX65d/fwEjNqlDjLVIvM9X+XVxF7 -""") - -##file distribute_setup.py -DISTRIBUTE_SETUP_PY = convert(""" -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 -""") - -##file activate.sh -ACTIVATE_SH = convert(""" -eJytVU1v4jAQPW9+xTT0ANVS1GsrDlRFAqmFqmG72m0rY5IJsRRslDiktNr/vuMQ8tFQpNU2B4I9 -H36eeW/SglkgYvBFiLBKYg0LhCRGD1KhA7BjlUQuwkLIHne12HCNNpz5kVrBgsfBmdWCrUrA5VIq -DVEiQWjwRISuDreW5eE+CtodeLeAnhZEGKMGFXqAciMiJVcoNWx4JPgixDjzEj48QVeCfcqmtzfs -cfww+zG4ZfeD2ciGF7gCHaDMPM1jtvuHXAsPfF2rSGeOxV4iDY5GUGb3xVEYv2aj6WQ0vRseAlMY -G5DKsAawwnQUXt2LQOYlzZoYByqhonqoqfxZf4BLD97i4DukgXADCPgGgdOLTK5arYxZB1xnrc9T -EQFcHoZEAa1gSQioo/TPV5FZrDlxJA+NzwF+Ek1UonOzFnKZp6k5mgLBqSkuuAGXS4whJb5xz/xs -wXCHjiVerAk5eh9Kfz1wqOldtVv9dkbscfjgjKeTA8XPrtaNauX5rInOxaHuOReNtpFjo1/OxdFG -5eY9hJ3L3jqcPJbATggXAemDLZX0MNZRYjSDH7C1wMHQh73DyYfTu8a0F9v+6D8W6XNnF1GEIXW/ -JrSKPOtnW1YFat9mrLJkzLbyIlTvYzV0RGXcaTBfVLx7jF2PJ2wyuBsydpm7VSVa4C4Zb6pFO2TR -huypCEPwuQjNftUrNl6GsYZzuFrrLdC9iJjQ3omAPBbcI2lsU77tUD43kw1NPZhTrnZWzuQKLomx -Rd4OXM1ByExVVkmoTwfBJ7Lt10Iq1Kgo23Bmd8Ib1KrGbsbO4Pp2yO4fpnf3s6MnZiwuiJuls1/L -Pu4yUCvhpA+vZaJvWWDTr0yFYYyVnHMqCEq+QniuYX225xmnzRENjbXACF3wkCYNVZ1mBwxoR9Iw -WAo3/36oSOTfgjwEEQKt15e9Xpqm52+oaXxszmnE9GLl65RH2OMmS6+u5acKxDmlPgj2eT5/gQOX -LLK0j1y0Uwbmn438VZkVpqlfNKa/YET/53j+99G8H8tUhr9ZSXs2 -""") - -##file activate.fish -ACTIVATE_FISH = convert(""" -eJydVm1v4jgQ/s6vmA1wBxUE7X2stJVYlVWR2lK13d6d9laRk0yIr8HmbIe0++tvnIQQB9pbXT5A -Ys/LM55nZtyHx5RrSHiGsMm1gRAh1xhDwU0Kng8hFzMWGb5jBv2E69SDs0TJDdj3MxilxmzPZzP7 -pVPMMl+q9bjXh1eZQ8SEkAZULoAbiLnCyGSvvV6SC7IoBcS4Nw0wjcFbvJDcjiuTswzFDpiIQaHJ -lQAjQUi1YRmUboC2uZJig8J4PaCnT5IaDcgsbm/CjinOwgx1KcUTMEhhTgV4g2B1fRk8Le8fv86v -g7v545UHpZB9rKnp+gXsMhxLunIIpwVQxP/l9c/Hq9Xt1epm4R27bva6AJqN92G4YhbMG2i+LB+u -grv71c3dY7B6WtzfLy9bePbp0taDTXSwJQJszUnnp0y57mvpPcrF7ZODyhswtd59+/jdgw+fwBNS -xLSscksUPIDqwwNmCez3PpxGeyBYg6HE0YdcWBxcKczYzuVJi5Wu915vn5oWePCCoPUZBN5B7IgV -MCi54ZDLG7TUZ0HweXkb3M5vFmSpFm/gthhBx0UrveoPpv9AJ9unIbQYdUoe21bKg2q48sPFGVwu -H+afrxd1qvclaNlRFyh1EQ2sSccEuNAGWQwysfVpz1tPajUqbqJUnEcIJkWo6OXDaodK8ZiLdbmM -L1wb+9H0D+pcyPSrX5u5kgWSygRYXCnJUi/KKcuU4cqsAyTKZBiissLc7NFwizvjxtieKBVCIdWz -fzilzPaYyljZN0cGN1v7NnaIPNCGmVy3GKuJaQ6iVjE1Qfm+36hglErwmnAD8hu0dDy4uICBA8ZV -pQr/q/+O0KFW2kjelu9Dgb9SDBsWV4F4x5CswgS0zBVlk5tDMP5bVtUGpslbm81Lu2sdKq7uNMGh -MVQ4fy9xhogC1lS5guhISa0DlBWv0O8odT6/LP+4WZzDV6FzIkEqC0uolGZSZoMnlpxplmD2euaT -O4hkTpPnbztDccey0bhjDaBIqaWQa0uwEtQEwtyU56i4fq54F9IE3ORR6mKriODM4XOYZwaVYLYz -7SPbKkz4i7VkB6/Ot1upDE3znNqYKpM8raa0Bx8vfvntJ32UENsM4aI6gJL+jJwhxhh3jVIDOcpi -m0r2hmEtS8XXXNBk71QCDXTBNhhPiHX2LtHkrVIlhoEshH/EZgdq53Eirqs5iFKMnkOmqZTtr3Xq -djvPTWZT4S3NT5aVLgurMPUWI07BRVYqkQrmtCKohNY8qu9EdACoT6ki0a66XxVF4f9AQ3W38yO5 -mWmZmIIpnDFrbXakvKWeZhLwhvrbUH8fahhqD0YUcBDJjEBMQwiznE4y5QbHrbhHBOnUAYzb2tVN -jJa65e+eE2Ya30E2GurxUP8ssA6e/wOnvo3V78d3vTcvMB3n7l3iX1JXWqk= -""") - -##file activate.csh -ACTIVATE_CSH = convert(""" -eJx9U11vmzAUffevOCVRu+UB9pws29Kl0iq1aVWllaZlcgxciiViItsQdb9+xiQp+dh4QOB7Pu49 -XHqY59IgkwVhVRmLmFAZSrGRNkdgykonhFiqSCRW1sJSmJg8wCDT5QrucRCyHn6WFRKhVGmhKwVp -kUpNiS3emup3TY6XIn7DVNQyJUwlrgthJD6n/iCNv72uhCzCpFx9CRkThRQGKe08cWXJ9db/yh/u -pvzl9mn+PLnjj5P5D1yM8QmXlzBkSdXwZ0H/BBc0mEo5FE5qI2jKhclHOOvy9HD/OO/6YO1mX9vx -sY0H/tPIV0dtqel0V7iZvWyNg8XFcBA0ToEqVeqOdNUEQFvN41SumAv32VtJrakQNSmLWmgp4oJM -yDoBHgoydtoEAs47r5wHHnUal5vbJ8oOI+9wI86vb2d8Nrm/4Xy4RZ8R85E4uTZPB5EZPnTaaAGu -E59J8BE2J8XgrkbLeXMlVoQxznEYFYY8uFFdxsKQRx90Giwx9vSueHP1YNaUSFG4vTaErNSYuBOF -lXiVyXa9Sy3JdClEyK1dD6Nos9mEf8iKlOpmqSNTZnYjNEWiUYn2pKNB3ttcLJ3HmYYXy6Un76f7 -r8rRsC1TpTJj7f19m5sUf/V3Ir+x/yjtLu8KjLX/CmN/AcVGUUo= -""") - -##file activate.bat -ACTIVATE_BAT = convert(""" -eJyFUkEKgzAQvAfyhz0YaL9QEWpRqlSjWGspFPZQTevFHOr/adQaU1GaUzI7Mzu7ZF89XhKkEJS8 -qxaKMMsvboQ+LxxE44VICSW1gEa2UFaibqoS0iyJ0xw2lIA6nX5AHCu1jpRsv5KRjknkac9VLVug -sX9mtzxIeJDE/mg4OGp47qoLo3NHX2jsMB3AiDht5hryAUOEifoTdCXbSh7V0My2NMq/Xbh5MEjU -ZT63gpgNT9lKOJ/CtHsvT99re3pX303kydn4HeyOeAg5cjf2EW1D6HOPkg9NGKhu -""") - -##file deactivate.bat -DEACTIVATE_BAT = convert(""" -eJxzSE3OyFfIT0vj4spMU0hJTcvMS01RiPf3cYkP8wwKCXX0iQ8I8vcNCFHQ4FIAguLUEgWIgK0q -FlWqXJpcICVYpGzx2BAZ4uHv5+Hv6wq1BWINXBTdKriEKkI1DhW2QAfhttcxxANiFZCBbglQSJUL -i2dASrm4rFz9XLgAwJNbyQ== -""") - -##file activate.ps1 -ACTIVATE_PS = convert(""" -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 -QastYw== -""") - -##file distutils-init.py -DISTUTILS_INIT = convert(""" -eJytV92L4zYQf/dfMU0ottuse7RvC6FQrg8Lxz2Ugz4si9HacqKuIxlJ2ST313dG8odkO9d7aGBB -luZLv/nNjFacOqUtKJMIvzK3cXlhWgp5MDBsqK5SNYftsBAGpLLA4F1oe2Ytl+9wUvW55TswCi4c -KibhbFDSglXQCFmDPXIwtm7FawLRbwtPzg2T9gf4gupKv4GS0N262w7V0NvpbCy8cvTo3eAus6C5 -ETU3ICQZX1hFTw/dzR6V/AW1RCN4/XAtbsVXqIXmlVX6liS4lOzEYY9QFB2zx6LfoSNjz1a0pqT9 -QOIfJWQ2E888NEVZNqLlZZnvIB0NpHkimlFdKn2iRRY7yGG/CCJb6Iz280d34SFXBS2yEYPNF0Q7 -yM7oCjpWvbEDQmnhRwOs6zjThpKE8HogwRAgraqYFZgGZvzmzVh+mgz9vskT3hruwyjdFcqyENJw -bbMPO5jdzonxK68QKT7B57CMRRG5shRSWDTX3dI8LzRndZbnSWL1zfvriUmK4TcGWSnZiEPCrxXv -bM+sP7VW2is2WgWXCO3sAu3Rzysz3FiNCA8WPyM4gb1JAAmCiyTZbhFjWx3h9SzauuRXC9MFoVbc -yNTCm1QXOOIfIn/g1kGMhDUBN72hI5XCBQtIXQw8UEEdma6Jaz4vJIJ51Orc15hzzmu6TdFp3ogr -Aof0c98tsw1SiaiWotHffk3XYCkqdToxWRfTFXqgpg2khcLluOHMVC0zZhLKIomesfSreUNNgbXi -Ky9VRzwzkBneNoGQyyvGjbsFQqOZvpWIjqH281lJ/jireFgR3cPzSyTGWzQpDNIU+03Fs4XKLkhp -/n0uFnuF6VphB44b3uWRneSbBoMSioqE8oeF0JY+qTvYfEK+bPLYdoR4McfYQ7wMZj39q0kfP8q+ -FfsymO0GzNlPh644Jje06ulqHpOEQqdJUfoidI2O4CWx4qOglLye6RrFQirpCRXvhoRqXH3sYdVJ -AItvc+VUsLO2v2hVAWrNIfVGtkG351cUMNncbh/WdowtSPtCdkzYFv6mwYc9o2Jt68ud6wectBr8 -hYAulPSlgzH44YbV3ikjrulEaNJxt+/H3wZ7bXSXje/YY4tfVVrVmUstaDwwOBLMg6iduDB0lMVC -UyzYx7Ab4kjCqdViEJmDcdk/SKbgsjYXgfMznUWcrtS4z4fmJ/XOM1LPk/iIpqass5XwNbdnLb1Y -8h3ERXSWZI6rZJxKs1LBqVH65w0Oy4ra0CBYxEeuOMbDmV5GI6E0Ha/wgVTtkX0+OXvqsD02CKLf -XHbeft85D7tTCMYy2Njp4DJP7gWJr6paVWXZ1+/6YXLv/iE0M90FktiI7yFJD9e7SOLhEkkaMTUO -azq9i2woBNR0/0eoF1HFMf0H8ChxH/jgcB34GZIz3Qn4/vid+VEamQrOVqAPTrOfmD4MPdVh09tb -8dLLjvh/61lEP4yW5vJaH4vHcevG8agXvzPGoOhhXNncpTr99PTHx6e/UvffFLaxUSjuSeP286Dw -gtEMcW1xKr/he4/6IQ6FUXP+0gkioHY5iwC9Eyx3HKO7af0zPPe+XyLn7fAY78k4aiR387bCr5XT -5C4rFgwLGfMvJuAMew== -""") - -##file distutils.cfg -DISTUTILS_CFG = convert(""" -eJxNj00KwkAMhfc9xYNuxe4Ft57AjYiUtDO1wXSmNJnK3N5pdSEEAu8nH6lxHVlRhtDHMPATA4uH -xJ4EFmGbvfJiicSHFRzUSISMY6hq3GLCRLnIvSTnEefN0FIjw5tF0Hkk9Q5dRunBsVoyFi24aaLg -9FDOlL0FPGluf4QjcInLlxd6f6rqkgPu/5nHLg0cXCscXoozRrP51DRT3j9QNl99AP53T2Q= -""") - -##file activate_this.py -ACTIVATE_THIS = convert(""" -eJyNU01v2zAMvetXEB4K21jmDOstQA4dMGCHbeihlyEIDMWmG62yJEiKE//7kXKdpN2KzYBt8euR -fKSyLPs8wiEo8wh4wqZTGou4V6Hm0wJa1cSiTkJdr8+GsoTRHuCotBayiWqQEYGtMCgfD1KjGYBe -5a3p0cRKiAe2NtLADikftnDco0ko/SFEVgEZ8aRC5GLux7i3BpSJ6J1H+i7A2CjiHq9z7JRZuuQq -siwTIvpxJYCeuWaBpwZdhB+yxy/eWz+ZvVSU8C4E9FFZkyxFsvCT/ZzL8gcz9aXVE14Yyp2M+2W0 -y7n5mp0qN+avKXvbsyyzUqjeWR8hjGE+2iCE1W1tQ82hsCZN9UzlJr+/e/iab8WfqsmPI6pWeUPd -FrMsd4H/55poeO9n54COhUs+sZNEzNtg/wanpjpuqHJaxs76HtZryI/K3H7KJ/KDIhqcbJ7kI4ar -XL+sMgXnX0D+Te2Iy5xdP8yueSlQB/x/ED2BTAtyE3K4SYUN6AMNfbO63f4lBW3bUJPbTL+mjSxS -PyRfJkZRgj+VbFv+EzHFi5pKwUEepa4JslMnwkowSRCXI+m5XvEOvtuBrxHdhLalG0JofYBok6qj -YdN2dEngUlbC4PG60M1WEN0piu7Nq7on0mgyyUw3iV1etLo6r/81biWdQ9MWHFaePWZYaq+nmp+t -s3az+sj7eA0jfgPfeoN1 -""") - -if __name__ == '__main__': - main() - -## TODO: -## Copy python.exe.manifest -## Monkeypatch distutils.sysconfig diff --git a/pages b/pages deleted file mode 160000 index f103cccb1..000000000 --- a/pages +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f103cccb1d138c74a02404dfefcb875b540286a1