Browse files

Initial Version

  • Loading branch information...
0 parents commit d0ce5ee63ef521e658e540cbba00b924fd2388a2 @mikelikespie mikelikespie committed Jan 27, 2012
2 .gitignore
@@ -0,0 +1,2 @@
+.idea/
+.env/
15 LICENSE
@@ -0,0 +1,15 @@
+
+ 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.
+
23 SRTests/SRTAppDelegate.h
@@ -0,0 +1,23 @@
+//
+// 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 <UIKit/UIKit.h>
+
+@interface SRTAppDelegate : UIResponder <UIApplicationDelegate>
+
+@property (strong, nonatomic) UIWindow *window;
+
+@end
209 SRTests/SRTAppDelegate.m
@@ -0,0 +1,209 @@
+//
+// 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 "SRTAppDelegate.h"
+#import "SRWebSocket.h"
+
+#define SRLogDebug(format, ...) //NSLog(format, __VA_ARGS__)
+
+@interface TestOperation : NSOperation <SRWebSocketDelegate>
+
+- (id)initWithTestNumber:(NSInteger)testNumber;
+
+@end
+
+
+@interface SRTAppDelegate () <SRWebSocketDelegate>
+
+@end
+
+
+@implementation SRTAppDelegate {
+ SRWebSocket *_curWebSocket;
+ NSInteger _testCount;
+ NSInteger _curTest;
+ NSMutableArray *_sockets;
+}
+
+@synthesize window = _window;
+
+//- (void)_getTests
+
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
+{
+ self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
+ // Override point for customization after application launch.
+ self.window.backgroundColor = [UIColor whiteColor];
+ [self.window makeKeyAndVisible];
+
+ _sockets = [[NSMutableArray alloc] init];
+
+#if 1
+ __unsafe_unretained SRTAppDelegate *weakself = self;
+ _curWebSocket = [[SRWebSocket alloc] initWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"wss://mcnugget.local:443/getCaseCount"]]];
+ _curWebSocket.onMessage = ^(SRWebSocket *webSocket, NSString *message) {
+ NSOperation *finishOperation = [NSBlockOperation blockOperationWithBlock:^{
+ weakself->_curWebSocket = [[SRWebSocket alloc] initWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"wss://mcnugget.local:443/updateReports?agent=socketrocket1"]]];
+
+ NSLog(@"-- Updating Reports");
+ weakself->_curWebSocket.onClose = ^(SRWebSocket *webSocket, NSInteger code, NSString *reason, BOOL wasClean) {
+ NSLog(@"-- reports updated... exiting");
+ };
+ weakself->_curWebSocket.onError = ^(SRWebSocket *webSocket, NSError *error) {
+ NSLog(@"Error updating reports %@", error.localizedDescription);
+ };
+
+ [weakself->_curWebSocket open];
+ }];
+
+
+ NSOperationQueue *testQueue = [[NSOperationQueue alloc] init];
+ testQueue.maxConcurrentOperationCount = 1;
+
+ for (int i = 0; i < [message integerValue]; i++) {
+ NSOperation *op = [[TestOperation alloc] initWithTestNumber:i + 1];
+ [finishOperation addDependency:op];
+ [testQueue addOperation:op];
+ }
+
+ [testQueue addOperation:finishOperation];
+ };
+
+ [_curWebSocket open];
+
+#else
+ double delayInSeconds = 0.1;
+ dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
+ dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
+ _listener = [[SRWebSocketListener alloc] init];
+
+ _listener.onMessage = ^(SRWebSocket *socket, id message) {
+ [socket send:message];
+
+#if 0
+ if ([message isKindOfClass:[NSString class]]) {
+ NSLog(@"Echoing String for %@", [message substringToIndex:MIN(128, [message length])]);
+ } else {
+ NSLog(@"Echoing String for %@", [message subdataWithRange:NSMakeRange(0, MIN(128, [message length]))]);
+ }
+#endif
+
+ };
+ _listener.onClose = ^(SRWebSocket *webSocket, NSInteger code, NSString *reason, BOOL wasClean) {
+ NSLog(@"closing");
+ };
+ _listener.onError = ^(SRWebSocket *webSocket, NSError *error) {
+ NSLog(@"error %@", error.localizedDescription);
+ };
+
+ [_listener startListeningOnPort:9000];
+
+ });
+
+#endif
+
+ return YES;
+}
+
+@end
+
+@interface TestOperation ()
+
+@property (nonatomic) BOOL isFinished;
+@property (nonatomic) BOOL isExecuting;
+
+@end
+
+@implementation TestOperation {
+ NSInteger _testNumber;
+ SRWebSocket *_webSocket;
+}
+
+@synthesize isFinished = _isFinished;
+@synthesize isExecuting = _isExecuting;
+
+- (id)initWithTestNumber:(NSInteger)testNumber;
+{
+ self = [super init];
+ if (self) {
+ _testNumber = testNumber;
+ _isExecuting = NO;
+ _isFinished = NO;
+ }
+ return self;
+}
+
+- (BOOL)isConcurrent;
+{
+ return YES;
+}
+
+- (void)start;
+{
+ NSLog(@"Starting test %d", _testNumber);
+ self.isExecuting = YES;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ _webSocket = [[SRWebSocket alloc] initWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"ws://localhost:9001/runCase?case=%d&agent=socketrocket1", _testNumber]]]];
+ _webSocket.delegate = self;
+ [_webSocket open];
+ });
+}
+
+- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean;
+{
+ NSLog(@"Received close for %d (%d, %@)", _testNumber, code, reason);
+
+ [self willChangeValueForKey:@"isExecuting"];
+ [self willChangeValueForKey:@"isFinished"];
+ _isFinished = YES;
+ _isExecuting = NO;
+ _webSocket = nil;
+ [self didChangeValueForKey:@"isExecuting"];
+ [self didChangeValueForKey:@"isFinished"];
+}
+
+- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(NSString *)message;
+{
+ if ([message isKindOfClass:[NSString class]]) {
+ SRLogDebug(@"Echoing String for %d %@", _testNumber, [message substringToIndex:MIN(128, [message length])]);
+ } else {
+ SRLogDebug(@"Echoing String for %d %@", _testNumber, [message subd:MIN(128, [message length])]);
+ }
+ [webSocket send:message];
+
+ double delayInSeconds = 30.0;
+ dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
+ dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
+ if (!self.isFinished) {
+ NSLog(@"Timing Out");
+ [_webSocket closeWithCode:0 reason:nil];
+ }
+ });
+}
+
+- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error;
+{
+ NSLog(@"failed with error %@", [error localizedDescription]);
+ [self willChangeValueForKey:@"isExecuting"];
+ [self willChangeValueForKey:@"isFinished"];
+ _isFinished = YES;
+ _isExecuting = NO;
+ _webSocket = nil;
+ [self didChangeValueForKey:@"isExecuting"];
+ [self didChangeValueForKey:@"isFinished"];
+}
+
+@end
47 SRTests/SRTests-Info.plist
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleDisplayName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIconFiles</key>
+ <array/>
+ <key>CFBundleIdentifier</key>
+ <string>org.lolrus.${PRODUCT_NAME:rfc1034identifier}</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1.0</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>UIRequiredDeviceCapabilities</key>
+ <array>
+ <string>armv7</string>
+ </array>
+ <key>UISupportedInterfaceOrientations</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+ <key>UISupportedInterfaceOrientations~ipad</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationPortraitUpsideDown</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+</dict>
+</plist>
35 SRTests/SRTests-Prefix.pch
@@ -0,0 +1,35 @@
+//
+// 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 <Availability.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef __IPHONE_3_0
+#warning "This project uses features only available in iOS SDK 3.0 and later."
+#endif
+
+#ifdef __OBJC__
+ #import <UIKit/UIKit.h>
+ #import <Foundation/Foundation.h>
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
2 SRTests/en.lproj/InfoPlist.strings
@@ -0,0 +1,2 @@
+/* Localized versions of Info.plist keys */
+
26 SRTests/main.m
@@ -0,0 +1,26 @@
+//
+// 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 <UIKit/UIKit.h>
+
+#import "SRTAppDelegate.h"
+
+int main(int argc, char *argv[])
+{
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([SRTAppDelegate class]));
+ }
+}
176 SRWebSocketTests/SRTAutobahnTests.m
@@ -0,0 +1,176 @@
+//
+// 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 <SenTestingKit/SenTestingKit.h>
+#import "SRWebSocket.h"
+
+#define SRLogDebug(format, ...)
+//#define SRLogDebug(format, ...) NSLog(format, __VA_ARGS__)
+
+@interface SRTAutobahnTests : SenTestCase
+@end
+
+@interface TestOperation : NSOperation <SRWebSocketDelegate>
+
+- (id)initWithTestNumber:(NSInteger)testNumber;
+
+@end
+
+@implementation SRTAutobahnTests {
+ SRWebSocket *_curWebSocket;
+ NSInteger _testCount;
+ NSInteger _curTest;
+ NSMutableArray *_sockets;
+}
+
+- (void)testFuzzer;
+{
+ _sockets = [[NSMutableArray alloc] init];
+
+ NSOperationQueue *testQueue = [[NSOperationQueue alloc] init];
+
+ __block BOOL hasFinished = NO;
+ __block BOOL hasFailed = NO;
+
+ __weak SRTAutobahnTests *weakself = self;
+ _curWebSocket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:@"ws://localhost:9001/getCaseCount"]];
+ _curWebSocket.onMessage = ^(SRWebSocket *webSocket, NSString *message) {
+ NSOperation *finishOperation = [NSBlockOperation blockOperationWithBlock:^{
+ weakself->_curWebSocket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:@"ws://localhost:9001/updateReports?agent=socketrocket"]];
+
+ NSLog(@"-- Updating Reports");
+ weakself->_curWebSocket.onClose = ^(SRWebSocket *webSocket, NSInteger code, NSString *reason, BOOL wasClean) {
+ NSLog(@"-- reports updated... exiting");
+ hasFinished = YES;
+ };
+ weakself->_curWebSocket.onError = ^(SRWebSocket *webSocket, NSError *error) {
+ NSLog(@"Error updating reports %@", error.localizedDescription);
+ hasFailed = YES;
+ hasFinished = YES;
+ };
+
+ [weakself->_curWebSocket open];
+ }];
+
+
+ testQueue.maxConcurrentOperationCount = 1;
+
+ for (int i = 0; i < [message integerValue]; i++) {
+ NSOperation *op = [[TestOperation alloc] initWithTestNumber:i + 1];
+ [finishOperation addDependency:op];
+ [testQueue addOperation:op];
+ }
+
+ [testQueue addOperation:finishOperation];
+ };
+
+ [_curWebSocket open];
+ [self runCurrentRunLoopUntilTestPasses:^BOOL{
+ return hasFinished;
+ } timeout:60 * 60];
+
+ STAssertFalse(hasFailed, @"timeout");
+}
+
+@end
+
+@interface TestOperation ()
+
+@property (nonatomic) BOOL isFinished;
+@property (nonatomic) BOOL isExecuting;
+
+@end
+
+@implementation TestOperation {
+ NSInteger _testNumber;
+ SRWebSocket *_webSocket;
+}
+
+@synthesize isFinished = _isFinished;
+@synthesize isExecuting = _isExecuting;
+
+- (id)initWithTestNumber:(NSInteger)testNumber;
+{
+ self = [super init];
+ if (self) {
+ _testNumber = testNumber;
+ _isExecuting = NO;
+ _isFinished = NO;
+ }
+ return self;
+}
+
+- (BOOL)isConcurrent;
+{
+ return YES;
+}
+
+- (void)start;
+{
+ NSLog(@"Starting test %d", _testNumber);
+ self.isExecuting = YES;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ _webSocket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"ws://localhost:9001/runCase?case=%d&agent=socketrocket", _testNumber]]];
+ _webSocket.delegate = self;
+ [_webSocket open];
+ });
+}
+
+- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean;
+{
+ NSLog(@"Received close for %d", _testNumber);
+
+ [self willChangeValueForKey:@"isExecuting"];
+ [self willChangeValueForKey:@"isFinished"];
+ _isFinished = YES;
+ _isExecuting = NO;
+ _webSocket = nil;
+ [self didChangeValueForKey:@"isExecuting"];
+ [self didChangeValueForKey:@"isFinished"];
+}
+
+- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message;
+{
+ if ([message isKindOfClass:[NSString class]]) {
+ SRLogDebug(@"Echoing String for %d %@", _testNumber, [(NSString *)message substringToIndex:MIN(128, [message length])]);
+ } else {
+ SRLogDebug(@"Echoing String for %d %@", _testNumber, [(NSData *)message subdataWithRange:NSMakeRange(0, MIN(128, ([message length])))]);
+ }
+ [webSocket send:message];
+
+ double delayInSeconds = 100.0;
+ dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
+ dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
+ if (!self.isFinished) {
+ NSLog(@"Timing Out");
+ [_webSocket closeWithCode:0 reason:nil];
+ }
+ });
+}
+
+- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error;
+{
+ NSLog(@"failed with error %@", [error localizedDescription]);
+ [self willChangeValueForKey:@"isExecuting"];
+ [self willChangeValueForKey:@"isFinished"];
+ _isFinished = YES;
+ _isExecuting = NO;
+ _webSocket = nil;
+ [self didChangeValueForKey:@"isExecuting"];
+ [self didChangeValueForKey:@"isFinished"];
+}
+
+@end
22 SRWebSocketTests/SRWebSocketTests-Info.plist
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>org.lolrus.${PRODUCT_NAME:rfc1034identifier}</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>BNDL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+</dict>
+</plist>
22 SRWebSocketTests/SRWebSocketTests-Prefix.pch
@@ -0,0 +1,22 @@
+//
+// 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 <UIKit/UIKit.h>
+ #import <Foundation/Foundation.h>
+ #import <SenTestingKit/SenTestingKit.h>
+ #import "SenTestCase+SRTAdditions.h"
+#endif
27 SRWebSocketTests/SenTestCase+SRTAdditions.h
@@ -0,0 +1,27 @@
+//
+// 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 <SenTestingKit/SenTestingKit.h>
+
+
+typedef BOOL (^PXPredicateBlock)();
+
+
+@interface SenTestCase (PXAdditions)
+
+- (void)runCurrentRunLoopUntilTestPasses:(PXPredicateBlock)predicate timeout:(NSTimeInterval)timeout;
+
+@end
38 SRWebSocketTests/SenTestCase+SRTAdditions.m
@@ -0,0 +1,38 @@
+//
+// 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
2 SRWebSocketTests/en.lproj/InfoPlist.strings
@@ -0,0 +1,2 @@
+/* Localized versions of Info.plist keys */
+
8 SRWebSocketTests/foo.mm
@@ -0,0 +1,8 @@
+//
+// foo.m
+// SocketRocket
+//
+// Created by Mike Lewis on 10/31/11.
+// Copyright (c) 2011 __MyCompanyName__. All rights reserved.
+//
+
613 SocketRocket.xcodeproj/project.pbxproj
@@ -0,0 +1,613 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ F6016C7C146124B20037BB3D /* base64.c in Sources */ = {isa = PBXBuildFile; fileRef = F6016C7B146124B20037BB3D /* base64.c */; };
+ F6016C7F146124ED0037BB3D /* base64.h in Headers */ = {isa = PBXBuildFile; fileRef = F6016C7E146124ED0037BB3D /* base64.h */; };
+ F6016C8714620EC60037BB3D /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6A12CD3145122FC00C1D980 /* Security.framework */; };
+ 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 */; };
+ F6572126146C7B6A00D6B8A9 /* NSData+SRB64Additions.m in Sources */ = {isa = PBXBuildFile; fileRef = F6572124146C7B6A00D6B8A9 /* NSData+SRB64Additions.m */; };
+ F66996FE146759FE0014B93E /* libSocketRocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F6B2082D1450F597009315AF /* libSocketRocket.a */; };
+ F6A12CD1145119B700C1D980 /* SRWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = F6A12CCF145119B700C1D980 /* SRWebSocket.h */; };
+ F6A12CD2145119B700C1D980 /* SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = F6A12CD0145119B700C1D980 /* SRWebSocket.m */; };
+ F6A12CD61451231B00C1D980 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6A12CD51451231B00C1D980 /* CFNetwork.framework */; };
+ F6AE451D145906A70022AF3C /* libSocketRocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F6B2082D1450F597009315AF /* libSocketRocket.a */; };
+ F6AE4520145906B20022AF3C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6B208301450F597009315AF /* Foundation.framework */; };
+ F6AE4521145906B20022AF3C /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6B208431450F611009315AF /* UIKit.framework */; };
+ F6AE4522145906B20022AF3C /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6B208461450F611009315AF /* CoreGraphics.framework */; };
+ F6AE4523145906C10022AF3C /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6B208461450F611009315AF /* CoreGraphics.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 */; };
+ F6B208441450F611009315AF /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6B208431450F611009315AF /* UIKit.framework */; };
+ F6B208451450F611009315AF /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6B208301450F597009315AF /* Foundation.framework */; };
+ F6B208471450F611009315AF /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6B208461450F611009315AF /* CoreGraphics.framework */; };
+ F6B2084D1450F611009315AF /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = F6B2084B1450F611009315AF /* InfoPlist.strings */; };
+ F6B2084F1450F611009315AF /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = F6B2084E1450F611009315AF /* main.m */; };
+ F6B208531450F611009315AF /* SRTAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = F6B208521450F611009315AF /* SRTAppDelegate.m */; };
+ F6BDA804145900D200FE3253 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6BDA803145900D200FE3253 /* SenTestingKit.framework */; };
+ F6BDA805145900D200FE3253 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6B208431450F611009315AF /* UIKit.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 */; };
+ F6C41C96145F7C4700641356 /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F6C41C95145F7C4700641356 /* libicucore.dylib */; };
+ F6C41C98145F7C6100641356 /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F6C41C95145F7C4700641356 /* libicucore.dylib */; };
+ F6C41C99145F7C7700641356 /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F6C41C95145F7C4700641356 /* libicucore.dylib */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ F6016C7B146124B20037BB3D /* base64.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = base64.c; sourceTree = "<group>"; };
+ F6016C7E146124ED0037BB3D /* base64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = base64.h; sourceTree = "<group>"; };
+ F6572123146C7B6A00D6B8A9 /* NSData+SRB64Additions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+SRB64Additions.h"; sourceTree = "<group>"; };
+ F6572124146C7B6A00D6B8A9 /* NSData+SRB64Additions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+SRB64Additions.m"; sourceTree = "<group>"; };
+ F6A12CCF145119B700C1D980 /* SRWebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRWebSocket.h; sourceTree = "<group>"; };
+ F6A12CD0145119B700C1D980 /* SRWebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRWebSocket.m; sourceTree = "<group>"; };
+ 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 = "<group>"; };
+ F6AE4527145907D30022AF3C /* SenTestCase+SRTAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SenTestCase+SRTAdditions.m"; sourceTree = "<group>"; };
+ 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 = "<group>"; };
+ F6B208411450F611009315AF /* SRTests.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SRTests.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ F6B208431450F611009315AF /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
+ F6B208461450F611009315AF /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
+ F6B2084A1450F611009315AF /* SRTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SRTests-Info.plist"; sourceTree = "<group>"; };
+ F6B2084C1450F611009315AF /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
+ F6B2084E1450F611009315AF /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+ F6B208501450F611009315AF /* SRTests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SRTests-Prefix.pch"; sourceTree = "<group>"; };
+ F6B208511450F611009315AF /* SRTAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SRTAppDelegate.h; sourceTree = "<group>"; };
+ F6B208521450F611009315AF /* SRTAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SRTAppDelegate.m; sourceTree = "<group>"; };
+ 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 = "<group>"; };
+ F6BDA80B145900D200FE3253 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
+ F6BDA810145900D200FE3253 /* SRWebSocketTests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SRWebSocketTests-Prefix.pch"; sourceTree = "<group>"; };
+ F6BDA8151459016900FE3253 /* SRTAutobahnTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRTAutobahnTests.m; sourceTree = "<group>"; };
+ F6C41C95145F7C4700641356 /* libicucore.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libicucore.dylib; path = usr/lib/libicucore.dylib; sourceTree = SDKROOT; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ F6B2082A1450F597009315AF /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ F6C41C96145F7C4700641356 /* libicucore.dylib in Frameworks */,
+ F6AE4520145906B20022AF3C /* Foundation.framework in Frameworks */,
+ F6AE4521145906B20022AF3C /* UIKit.framework in Frameworks */,
+ F6AE4522145906B20022AF3C /* CoreGraphics.framework in Frameworks */,
+ F6016C8914620ECC0037BB3D /* Security.framework in Frameworks */,
+ F6016C8A1462143C0037BB3D /* CFNetwork.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ F6B2083E1450F611009315AF /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ F6C41C99145F7C7700641356 /* libicucore.dylib in Frameworks */,
+ F6A12CD61451231B00C1D980 /* CFNetwork.framework in Frameworks */,
+ F6B208441450F611009315AF /* UIKit.framework in Frameworks */,
+ F6B208451450F611009315AF /* Foundation.framework in Frameworks */,
+ F6B208471450F611009315AF /* CoreGraphics.framework in Frameworks */,
+ F6016C8714620EC60037BB3D /* Security.framework in Frameworks */,
+ F66996FE146759FE0014B93E /* libSocketRocket.a in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ F6BDA7FE145900D200FE3253 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ F6C41C98145F7C6100641356 /* libicucore.dylib in Frameworks */,
+ F6BDA804145900D200FE3253 /* SenTestingKit.framework in Frameworks */,
+ F6BDA805145900D200FE3253 /* UIKit.framework in Frameworks */,
+ F6BDA806145900D200FE3253 /* Foundation.framework in Frameworks */,
+ F6AE451D145906A70022AF3C /* libSocketRocket.a in Frameworks */,
+ F6AE4523145906C10022AF3C /* CoreGraphics.framework in Frameworks */,
+ F6AE45241459071C0022AF3C /* CFNetwork.framework in Frameworks */,
+ F6016C8814620EC70037BB3D /* Security.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ F6B208221450F597009315AF = {
+ isa = PBXGroup;
+ children = (
+ F6B208321450F597009315AF /* SocketRocket */,
+ F6B208481450F611009315AF /* SRTests */,
+ F6BDA807145900D200FE3253 /* SRWebSocketTests */,
+ F6B2082F1450F597009315AF /* Frameworks */,
+ F6B2082E1450F597009315AF /* Products */,
+ );
+ sourceTree = "<group>";
+ };
+ F6B2082E1450F597009315AF /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ F6B2082D1450F597009315AF /* libSocketRocket.a */,
+ F6B208411450F611009315AF /* SRTests.app */,
+ F6BDA802145900D200FE3253 /* SRWebSocketTests.octest */,
+ );
+ name = Products;
+ sourceTree = "<group>";
+ };
+ F6B2082F1450F597009315AF /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ F6C41C95145F7C4700641356 /* libicucore.dylib */,
+ F6A12CD51451231B00C1D980 /* CFNetwork.framework */,
+ F6A12CD3145122FC00C1D980 /* Security.framework */,
+ F6B208301450F597009315AF /* Foundation.framework */,
+ F6B208431450F611009315AF /* UIKit.framework */,
+ F6B208461450F611009315AF /* CoreGraphics.framework */,
+ F6BDA803145900D200FE3253 /* SenTestingKit.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "<group>";
+ };
+ F6B208321450F597009315AF /* SocketRocket */ = {
+ isa = PBXGroup;
+ children = (
+ F6016C7B146124B20037BB3D /* base64.c */,
+ F6016C7E146124ED0037BB3D /* base64.h */,
+ F6A12CCF145119B700C1D980 /* SRWebSocket.h */,
+ F6A12CD0145119B700C1D980 /* SRWebSocket.m */,
+ F6B208331450F597009315AF /* Supporting Files */,
+ F6572123146C7B6A00D6B8A9 /* NSData+SRB64Additions.h */,
+ F6572124146C7B6A00D6B8A9 /* NSData+SRB64Additions.m */,
+ );
+ path = SocketRocket;
+ sourceTree = "<group>";
+ };
+ F6B208331450F597009315AF /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ F6B208341450F597009315AF /* SocketRocket-Prefix.pch */,
+ );
+ name = "Supporting Files";
+ sourceTree = "<group>";
+ };
+ F6B208481450F611009315AF /* SRTests */ = {
+ isa = PBXGroup;
+ children = (
+ F6B208511450F611009315AF /* SRTAppDelegate.h */,
+ F6B208521450F611009315AF /* SRTAppDelegate.m */,
+ F6B208491450F611009315AF /* Supporting Files */,
+ );
+ path = SRTests;
+ sourceTree = "<group>";
+ };
+ F6B208491450F611009315AF /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ F6B2084A1450F611009315AF /* SRTests-Info.plist */,
+ F6B2084B1450F611009315AF /* InfoPlist.strings */,
+ F6B2084E1450F611009315AF /* main.m */,
+ F6B208501450F611009315AF /* SRTests-Prefix.pch */,
+ );
+ name = "Supporting Files";
+ sourceTree = "<group>";
+ };
+ F6BDA807145900D200FE3253 /* SRWebSocketTests */ = {
+ isa = PBXGroup;
+ children = (
+ F6BDA808145900D200FE3253 /* Supporting Files */,
+ F6BDA810145900D200FE3253 /* SRWebSocketTests-Prefix.pch */,
+ F6AE4526145907D30022AF3C /* SenTestCase+SRTAdditions.h */,
+ F6AE4527145907D30022AF3C /* SenTestCase+SRTAdditions.m */,
+ F6BDA8151459016900FE3253 /* SRTAutobahnTests.m */,
+ );
+ path = SRWebSocketTests;
+ sourceTree = "<group>";
+ };
+ F6BDA808145900D200FE3253 /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ F6BDA809145900D200FE3253 /* SRWebSocketTests-Info.plist */,
+ F6BDA80A145900D200FE3253 /* InfoPlist.strings */,
+ );
+ name = "Supporting Files";
+ sourceTree = "<group>";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+ F6B2082B1450F597009315AF /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ F6A12CD1145119B700C1D980 /* SRWebSocket.h in Headers */,
+ F6016C7F146124ED0037BB3D /* base64.h in Headers */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+ 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";
+ };
+ F6B208401450F611009315AF /* SRTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = F6B208541450F611009315AF /* Build configuration list for PBXNativeTarget "SRTests" */;
+ buildPhases = (
+ F6B2083D1450F611009315AF /* Sources */,
+ F6B2083E1450F611009315AF /* Frameworks */,
+ F6B2083F1450F611009315AF /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = SRTests;
+ productName = SRTests;
+ productReference = F6B208411450F611009315AF /* SRTests.app */;
+ productType = "com.apple.product-type.application";
+ };
+ F6BDA801145900D200FE3253 /* SRWebSocketTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = F6BDA813145900D200FE3253 /* Build configuration list for PBXNativeTarget "SRWebSocketTests" */;
+ buildPhases = (
+ F6BDA7FD145900D200FE3253 /* Sources */,
+ F6BDA7FE145900D200FE3253 /* Frameworks */,
+ F6BDA7FF145900D200FE3253 /* Resources */,
+ F6BDA800145900D200FE3253 /* ShellScript */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = SRWebSocketTests;
+ productName = SRWebSocketTests;
+ productReference = F6BDA802145900D200FE3253 /* SRWebSocketTests.octest */;
+ productType = "com.apple.product-type.bundle";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ F6B208241450F597009315AF /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastUpgradeCheck = 0430;
+ };
+ buildConfigurationList = F6B208271450F597009315AF /* Build configuration list for PBXProject "SocketRocket" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ );
+ mainGroup = F6B208221450F597009315AF;
+ productRefGroup = F6B2082E1450F597009315AF /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ F6B2082C1450F597009315AF /* SocketRocket */,
+ F6B208401450F611009315AF /* SRTests */,
+ F6BDA801145900D200FE3253 /* SRWebSocketTests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ F6B2083F1450F611009315AF /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ F6B2084D1450F611009315AF /* InfoPlist.strings in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ F6BDA7FF145900D200FE3253 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ F6BDA80C145900D200FE3253 /* InfoPlist.strings in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ F6BDA800145900D200FE3253 /* ShellScript */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ outputPaths = (
+ );
+ 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 */
+ F6B208291450F597009315AF /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ F6A12CD2145119B700C1D980 /* SRWebSocket.m in Sources */,
+ F6016C7C146124B20037BB3D /* base64.c in Sources */,
+ F6572126146C7B6A00D6B8A9 /* NSData+SRB64Additions.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ F6B2083D1450F611009315AF /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ F6B2084F1450F611009315AF /* main.m in Sources */,
+ F6B208531450F611009315AF /* SRTAppDelegate.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ F6BDA7FD145900D200FE3253 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ F6BDA8161459016900FE3253 /* SRTAutobahnTests.m in Sources */,
+ F6AE4528145907D30022AF3C /* SenTestCase+SRTAdditions.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+ F6B2084B1450F611009315AF /* InfoPlist.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ F6B2084C1450F611009315AF /* en */,
+ );
+ name = InfoPlist.strings;
+ sourceTree = "<group>";
+ };
+ F6BDA80A145900D200FE3253 /* InfoPlist.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ F6BDA80B145900D200FE3253 /* en */,
+ );
+ name = InfoPlist.strings;
+ sourceTree = "<group>";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ F6B208381450F597009315AF /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ARCHS = "$(ARCHS_STANDARD_32_BIT)";
+ CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
+ CLANG_ENABLE_OBJC_ARC = YES;
+ COPY_PHASE_STRIP = NO;
+ GCC_C_LANGUAGE_STANDARD = "compiler-default";
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+ GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
+ GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 4.0;
+ SDKROOT = iphoneos;
+ };
+ name = Debug;
+ };
+ F6B208391450F597009315AF /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ARCHS = "$(ARCHS_STANDARD_32_BIT)";
+ CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
+ CLANG_ENABLE_OBJC_ARC = YES;
+ COPY_PHASE_STRIP = YES;
+ GCC_C_LANGUAGE_STANDARD = "compiler-default";
+ GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
+ GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 4.0;
+ SDKROOT = iphoneos;
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ F6B2083B1450F597009315AF /* Debug */ = {
+ isa = XCBuildConfiguration;
+ 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/iPhoneSimulator5.0.sdk/usr/lib/system\"",
+ "\"$(DEVELOPER_DIR)/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator5.0.sdk/usr/lib\"",
+ );
+ OTHER_LDFLAGS = "-ObjC";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ };
+ name = Debug;
+ };
+ F6B2083C1450F597009315AF /* Release */ = {
+ isa = XCBuildConfiguration;
+ 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/iPhoneSimulator5.0.sdk/usr/lib/system\"",
+ "\"$(DEVELOPER_DIR)/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator5.0.sdk/usr/lib\"",
+ );
+ OTHER_LDFLAGS = "-ObjC";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ };
+ name = Release;
+ };
+ F6B208551450F611009315AF /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ENABLE_OBJC_ARC = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "\"$(DEVELOPER_DIR)/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator5.0.sdk/System/Library/Frameworks\"",
+ );
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = "SRTests/SRTests-Prefix.pch";
+ INFOPLIST_FILE = "SRTests/SRTests-Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator5.0.sdk;
+ 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\"",
+ );
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TARGETED_DEVICE_FAMILY = "1,2";
+ WRAPPER_EXTENSION = app;
+ };
+ name = Debug;
+ };
+ F6B208561450F611009315AF /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ENABLE_OBJC_ARC = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "\"$(DEVELOPER_DIR)/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator5.0.sdk/System/Library/Frameworks\"",
+ );
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = "SRTests/SRTests-Prefix.pch";
+ INFOPLIST_FILE = "SRTests/SRTests-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_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TARGETED_DEVICE_FAMILY = "1,2";
+ WRAPPER_EXTENSION = app;
+ };
+ name = Release;
+ };
+ F6BDA811145900D200FE3253 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ 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\"",
+ );
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ WRAPPER_EXTENSION = octest;
+ };
+ name = Debug;
+ };
+ F6BDA812145900D200FE3253 /* Release */ = {
+ isa = XCBuildConfiguration;
+ 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\"",
+ );
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ WRAPPER_EXTENSION = octest;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ F6B208271450F597009315AF /* Build configuration list for PBXProject "SocketRocket" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ F6B208381450F597009315AF /* Debug */,
+ F6B208391450F597009315AF /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ F6B2083A1450F597009315AF /* Build configuration list for PBXNativeTarget "SocketRocket" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ F6B2083B1450F597009315AF /* Debug */,
+ F6B2083C1450F597009315AF /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ F6B208541450F611009315AF /* Build configuration list for PBXNativeTarget "SRTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ F6B208551450F611009315AF /* Debug */,
+ F6B208561450F611009315AF /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ F6BDA813145900D200FE3253 /* Build configuration list for PBXNativeTarget "SRWebSocketTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ F6BDA811145900D200FE3253 /* Debug */,
+ F6BDA812145900D200FE3253 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = F6B208241450F597009315AF /* Project object */;
+}
7 SocketRocket.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+ version = "1.0">
+ <FileRef
+ location = "self:SocketRocket.xcodeproj">
+ </FileRef>
+</Workspace>
23 SocketRocket/NSData+SRB64Additions.h
@@ -0,0 +1,23 @@
+//
+// 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 <Foundation/Foundation.h>
+
+@interface NSData (SRB64Additions)
+
+- (NSString *)SR_stringByBase64Encoding;
+
+@end
38 SocketRocket/NSData+SRB64Additions.m
@@ -0,0 +1,38 @@
+//
+// 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 "NSData+SRB64Additions.h"
+#import "base64.h"
+
+@implementation NSData (SRB64Additions)
+
+- (NSString *)SR_stringByBase64Encoding;
+{
+ size_t buffer_size = (([self length] * 3 + 2) / 2);
+
+ char *buffer = (char *)malloc(buffer_size);
+
+ int len = b64_ntop([self bytes], [self length], buffer, buffer_size);
+
+ if (len == -1) {
+ free(buffer);
+ return nil;
+ } else{
+ return [[NSString alloc] initWithBytesNoCopy:buffer length:len encoding:NSUTF8StringEncoding freeWhenDone:YES];
+ }
+}
+
+@end
73 SocketRocket/SRWebSocket.h
@@ -0,0 +1,73 @@
+//
+// 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 <Foundation/Foundation.h>
+
+typedef enum {
+ SR_CONNECTING = 0,
+ SR_OPEN = 1,
+ SR_CLOSING = 2,
+ SR_CLOSED = 3,
+
+} SRReadyState;
+
+@class SRWebSocket;
+
+typedef void (^SROnOpenHandler)(SRWebSocket *webSocket);
+typedef void (^SROnMessageHandler)(SRWebSocket *webSocket, NSString *message);
+typedef void (^SROnCloseHandler)(SRWebSocket *webSocket, NSInteger code, NSString *reason, BOOL wasClean);
+typedef void (^SROnErrorHandler)(SRWebSocket *webSocket, NSError *error);
+
+extern NSString *const SRWebSocketErrorDomain;
+
+@protocol SRWebSocketDelegate;
+
+@interface SRWebSocket : NSObject <NSStreamDelegate>
+
+@property (nonatomic, assign) id <SRWebSocketDelegate> delegate;
+
+@property (nonatomic, readonly) SRReadyState readyState;
+@property (nonatomic, readonly, strong) NSURL *url;
+
+- (id)initWithURLRequest:(NSURLRequest *)request;
+- (void)connectToHost:(NSString *)host port:(NSInteger)port;
+
+- (void)open;
+- (void)close;
+- (void)closeWithCode:(NSInteger)code reason:(NSString *)reason;
+
+- (void)failWithError:(NSError *)error;
+
+// Send a UTF8 String or Data
+- (void)send:(id)data;
+
+// Must not be set to nil
+@property (nonatomic, copy) SROnOpenHandler onOpen;
+@property (nonatomic, copy) SROnMessageHandler onMessage;
+@property (nonatomic, copy) SROnCloseHandler onClose;
+@property (nonatomic, copy) SROnErrorHandler onError;
+
+@end
+
+@protocol SRWebSocketDelegate <NSObject>
+@optional
+
+- (void)webSocketDidOpen:(SRWebSocket *)webSocket;
+- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error;
+- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(NSString *)message;
+- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean;
+
+@end
1,275 SocketRocket/SRWebSocket.m
@@ -0,0 +1,1275 @@
+//
+// 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 "SRWebSocket.h"
+
+#import <unicode/utf8.h>
+#import <endian.h>
+#import <CommonCrypto/CommonDigest.h>
+
+#import "base64.h"
+
+typedef enum {
+ SROpCodeTextFrame = 0x1,
+ SROpCodeBinaryFrame = 0x2,
+ //3-7Reserved
+ SROpCodeConnectionClose = 0x8,
+ SROpCodePing = 0x9,
+ SROpCodePong = 0xA,
+ //B-F reserved
+} SROpCode;
+
+typedef enum {
+ SRStatusCodeNormal = 1000,
+ SRStatusCodeGoingAway = 1001,
+ SRStatusCodeProtocolError = 1002,
+ SRStatusCodeUnhandledType = 1003,
+ // 1004-1006 reserved
+ SRStatusCodeInvalidUTF8 = 1007,
+ SRStatusCodePolicyViolated = 1008,
+ SRStatusCodeMessageTooBig = 1009,
+} SRStatusCode;
+
+typedef struct {
+ BOOL fin;
+// BOOL rsv1;
+// BOOL rsv2;
+// BOOL rsv3;
+ uint8_t opcode;
+ BOOL masked;
+ uint64_t payload_length;
+} frame_header;
+
+
+static inline dispatch_queue_t log_queue() {
+
+ static dispatch_queue_t queue = 0;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ queue = dispatch_queue_create("fast log queue", DISPATCH_QUEUE_SERIAL);
+ });
+
+ return queue;
+}
+
+static inline void SRFastLog(NSString *format, ...) {
+
+#if 1
+ __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
+}
+
+static NSString *const strAppendForAuth = @"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+
+static inline int32_t validate_dispatch_data_partial_string(NSData *data) {
+
+ const void * contents = [data bytes];
+ long size = [data length];
+
+ const uint8_t *str = (const uint8_t *)contents;
+
+
+ UChar32 codepoint = 1;
+ int32_t offset = 0;
+ int32_t lastOffset = 0;
+ while(offset < size && codepoint > 0) {
+ 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;
+}
+
+
+@interface NSString (DispatchDataAdditions)
+
+- (NSString *)stringBySHA1ThenBase64Encoding;
+
+@end
+
+#define CONSERVATIVE_COPY
+
+@implementation NSString (DispatchDataAdditions)
+
+- (NSString *)stringBySHA1ThenBase64Encoding;
+{
+ uint8_t md[CC_SHA1_DIGEST_LENGTH];
+
+ CC_SHA1([self UTF8String], [self lengthOfBytesUsingEncoding:NSUTF8StringEncoding], md);
+
+ size_t buffer_size = ((sizeof(md) * 3 + 2) / 2);
+
+ char *buffer = (char *)malloc(buffer_size);
+
+ int len = b64_ntop(md, CC_SHA1_DIGEST_LENGTH, buffer, buffer_size);
+ if (len == -1) {
+ free(buffer);
+ return nil;
+ } else{
+ return [[NSString alloc] initWithBytesNoCopy:buffer length:len encoding:NSASCIIStringEncoding freeWhenDone:YES];
+ }
+}
+
+@end
+
+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;
+}
+
+- (id)initWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(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
+
+
+@interface SRWebSocket () <NSStreamDelegate>
+
+- (void)_writeData:(NSData *)data;
+- (void)_closeWithProtocolError:(NSString *)message;
+- (void)_failWithError:(NSError *)error;
+
+- (void)_disconnect;
+
+- (void)_readFrameNew;
+- (void)_readFrameContinue;
+
+- (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;
+
+- (void)_checkHandshake:(NSDictionary *)headers;
+- (void)_SR_commonInit;
+
++ (dispatch_queue_t)globalReadQueue;
+
+@property (nonatomic) SRReadyState readyState;
+
+@end
+
+
+@implementation SRWebSocket {
+ NSInteger _webSocketVersion;
+ dispatch_queue_t _callbackQueue;
+ dispatch_queue_t _workQueue;
+ NSMutableArray *_consumers;
+
+ NSInputStream *_inputStream;
+ NSOutputStream *_outputStream;
+
+ NSMutableData *_readBuffer;
+ NSInteger _readBufferOffset;
+
+ NSMutableData *_outputBuffer;
+ NSInteger _outputBufferOffset;
+
+ uint8_t _currentFrameOpcode;
+ size_t _currentFrameCount;
+ size_t _readOpCount;
+ uint32_t _currentStringScanPosition;
+ NSMutableData *_currentFrameData;
+
+ uint8_t _currentReadMaskKey[4];
+ size_t _currentReadMaskOffset;
+
+ BOOL _consumerStopped;
+
+ BOOL _closeWhenFinishedWriting;
+
+ BOOL _secure;
+ NSURLRequest *_urlRequest;
+
+ __attribute__((NSObject)) CFHTTPMessageRef _receivedHTTPHeaders;
+
+ BOOL _didFail;
+ int _closeCode;
+}
+
+@synthesize delegate = _delegate;
+@synthesize url = _url;
+@synthesize readyState = _readyState;
+
+@synthesize onOpen = _onOpen;
+@synthesize onClose = _onClose;
+@synthesize onMessage = _onMessage;
+@synthesize onError = _onError;
+
+static __strong NSData *CRLFCRLF;
+
++ (void)initialize;
+{
+ CRLFCRLF = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4];
+}
+
++ (dispatch_queue_t)globalReadQueue;
+{
+ static dispatch_queue_t globalQueue = nil;
+ static dispatch_once_t token;
+
+ dispatch_once(&token, ^{
+ globalQueue = dispatch_queue_create("org.lolrus.socket.globalQueue", DISPATCH_QUEUE_SERIAL);
+ });
+
+ return globalQueue;
+}
+
+- (id)initWithURLRequest:(NSURLRequest *)request;
+{
+ self = [super init];
+ if (self) {
+
+ assert(request.URL);
+ _url = request.URL;
+ NSString *scheme = [_url scheme];
+
+ assert([scheme isEqualToString:@"ws"] || [scheme isEqualToString:@"http"] || [scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]);
+ _urlRequest = request;
+
+ if ([scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]) {
+ _secure = YES;
+ }
+
+ [self _SR_commonInit];
+ }
+
+ return self;
+}
+
+- (void)_SR_commonInit;
+{
+ _readyState = SR_CONNECTING;
+
+ _consumerStopped = YES;
+
+ _webSocketVersion = 13;
+
+ _workQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
+
+ _callbackQueue = dispatch_get_main_queue();
+ dispatch_retain(_callbackQueue);
+
+ _readBuffer = [[NSMutableData alloc] init];
+ _outputBuffer = [[NSMutableData alloc] init];
+
+ _currentFrameData = [[NSMutableData alloc] init];
+
+ _consumers = [[NSMutableArray alloc] init];
+
+ // default handlers
+ self.onError = ^(SRWebSocket *webSocket, NSError *error) {
+ if ([webSocket.delegate respondsToSelector:@selector(webSocket:didFailWithError:)]) {
+ [webSocket.delegate webSocket:webSocket didFailWithError:error];
+ }
+ };
+
+ self.onMessage = ^(SRWebSocket *webSocket, id message) {
+ if ([webSocket.delegate respondsToSelector:@selector(webSocket:didReceiveMessage:)]) {
+ [webSocket.delegate webSocket:webSocket didReceiveMessage:message];
+ }
+ };
+
+ self.onClose = ^(SRWebSocket *webSocket, NSInteger code, NSString *reason, BOOL wasClean) {
+ if ([webSocket.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) {
+ [webSocket.delegate webSocket:webSocket didCloseWithCode:code reason:reason wasClean:wasClean];
+ }
+ };
+
+ self.onOpen = ^(SRWebSocket *webSocket) {
+ if ([webSocket.delegate respondsToSelector:@selector(webSocketDidOpen:)]) {
+ [webSocket.delegate webSocketDidOpen:webSocket];
+ }
+ };
+}
+
+- (void)dealloc
+{
+ dispatch_release(_callbackQueue);
+ dispatch_release(_workQueue);
+ _inputStream.delegate = nil;
+ _outputStream.delegate = nil;
+}
+
+#ifndef NDEBUG
+
+- (void)setReadyState:(SRReadyState)aReadyState;
+{
+ [self willChangeValueForKey:@"readyState"];
+ assert(aReadyState > _readyState);
+ _readyState = aReadyState;
+ [self didChangeValueForKey:@"readyState"];
+}
+
+#endif
+
+- (void)open {
+ assert(_url);
+
+ NSInteger port = _url.port.integerValue;
+ if (port == 0) {
+ if (!_secure) {
+ port = 80;
+ } else {
+ port = 443;
+ }
+ }
+
+ [self connectToHost:_url.host port:port];
+}
+
+
+
+- (void)_checkHandshake:(NSDictionary *)headers;
+{
+ SRFastLog(@"TODO: Add handshake checking");
+}
+
+- (void)_HTTPHeadersDidFinish;
+{
+ NSDictionary *dict = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(_receivedHTTPHeaders));
+
+ [self _checkHandshake:dict];
+ NSInteger responseCode = CFHTTPMessageGetResponseStatusCode(_receivedHTTPHeaders);
+
+ 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 %d", responseCode] forKey:NSLocalizedDescriptionKey]]];
+ return;
+
+ }
+
+ self.readyState = SR_OPEN;
+
+ if (!_didFail) {
+ [self _readFrameNew];
+ }
+
+ dispatch_async(_callbackQueue, ^{
+ self.onOpen(self);
+ });
+}
+
+
+- (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];
+ } else {
+ [self _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));
+
+ [_urlRequest.allHTTPHeaderFields enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
+ CFHTTPMessageSetHeaderFieldValue(request, (__bridge CFStringRef)key, (__bridge CFStringRef)obj);
+ }];
+
+ CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Upgrade"), CFSTR("websocket"));
+ CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Connection"), CFSTR("Upgrade"));
+ CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Key"), CFSTR("/PiDVHFKG9+oB7rLAudvxw=="));
+ CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Version"), (__bridge CFStringRef)[NSString stringWithFormat:@"%d", _webSocketVersion]);
+ CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Origin"), (__bridge CFStringRef)_url.absoluteString);
+
+ NSData *message = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(request));
+
+ CFRelease(request);
+
+ [self _writeData:message];
+ [self _readHTTPHeader];
+}
+
+- (void)connectToHost:(NSString *)host port:(NSInteger)port;
+{
+ CFReadStreamRef readStream = NULL;
+ CFWriteStreamRef writeStream = NULL;
+
+ CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream);
+
+ _outputStream = CFBridgingRelease(writeStream);
+ _inputStream = CFBridgingRelease(readStream);
+
+ if (_secure) {
+ [_outputStream setProperty:(__bridge id)kCFStreamSocketSecurityLevelNegotiatedSSL forKey:(__bridge id)kCFStreamPropertySocketSecurityLevel];
+ #if DEBUG
+ NSLog(@"SocketRocket: In debug mode. Allowing connection to any root cert");
+ [_outputStream setProperty:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES]
+ forKey:(__bridge id)kCFStreamSSLAllowsAnyRoot]
+ forKey:(__bridge id)kCFStreamPropertySSLSettings];
+ #endif
+ }
+
+ _inputStream.delegate = self;
+ _outputStream.delegate = self;
+
+ // TODO schedule in a better run loop
+ [_outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
+ [_inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
+
+
+ [_outputStream open];
+ [_inputStream open];
+}
+
+- (void)close;
+{
+ [self closeWithCode:-1 reason:nil];
+}
+
+- (void)closeWithCode:(NSInteger)code reason:(NSString *)reason;
+{
+ assert(code);
+ if (self.readyState == SR_CLOSING || self.readyState == SR_CLOSED) {
+ return;
+ }
+
+ BOOL wasConnecting = self.readyState == SR_CONNECTING;
+
+ self.readyState = SR_CLOSING;
+
+ SRFastLog(@"Closing with code %d reason %@", code, reason);
+ dispatch_async(_workQueue, ^{
+ if (wasConnecting) {
+ [self _disconnect];
+ 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);
+
+ 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];
+
+ assert(success);
+ assert(remainingRange.length == 0);
+
+ if (usedLength != maxMsgSize) {
+ payload = [payload subdataWithRange:NSMakeRange(0, usedLength + sizeof(uint16_t))];
+ }
+ }
+
+
+ [self _sendFrameWithOpcode:SROpCodeConnectionClose data:payload];
+ });
+}
+
+- (void)failWithError:(NSError *)error;
+{
+ [self _failWithError:error];
+}
+
+- (void)_closeWithProtocolError:(NSString *)message;
+{
+ [self closeWithCode:SRStatusCodeProtocolError reason:message];
+ dispatch_async(_workQueue, ^{
+ [self _disconnect];
+ });
+}
+
+- (void)_failWithError:(NSError *)error;
+{
+ dispatch_async(_workQueue, ^{
+ if (self.readyState != SR_CLOSED) {
+ dispatch_async(_callbackQueue, ^{
+ _onError(self, error);
+ });
+
+ self.readyState = SR_CLOSED;
+
+ SRFastLog(@"Failing with error %@", error.localizedDescription);
+
+ [self _disconnect];
+ }
+ });
+}
+
+- (void)_writeData:(NSData *)data;
+{
+ assert(dispatch_get_current_queue() == _workQueue);
+
+ if (_closeWhenFinishedWriting) {
+ return;
+ }
+ [_outputBuffer appendData:data];
+ [self _pumpWriting];
+}
+- (void)send:(id)data;
+{
+ // TODO: maybe not copy this for performance
+ data = [data copy];
+ dispatch_async(_workQueue, ^{
+ if ([data isKindOfClass:[NSString class]]) {
+ [self _sendFrameWithOpcode:SROpCodeTextFrame data:[(NSString *)data dataUsingEncoding:NSUTF8StringEncoding]];
+ } else if ([data isKindOfClass:[NSData class]]) {
+ [self _sendFrameWithOpcode:SROpCodeBinaryFrame data:data];
+ } else if (data == nil) {
+ [self _sendFrameWithOpcode:SROpCodeTextFrame data:data];
+ } else {
+ assert(NO);
+ }
+ });
+}
+
+- (void)handlePing:(NSData *)pingData;
+{
+ [self _sendFrameWithOpcode:SROpCodePong data:pingData];
+}
+
+- (void)handlePong;
+{
+ // NOOP
+}
+
+- (void)handleMessage:(id)message
+{
+ dispatch_async(_callbackQueue, ^{
+ _onMessage(self, message);
+ });
+}
+
+// Note from RFC:
+//
+// If there is a body, the first two
+// bytes of the body MUST be a 2-byte unsigned integer (in network byte
+// order) representing a status code with value /code/ defined in
+// Section 7.4. Following the 2-byte integer the body MAY contain UTF-8
+// encoded data with value /reason/, the interpretation of which is not
+// defined by this specification.
+
+- (void)handleCloseWithData:(NSData *)data;
+{
+ size_t dataSize = data.length;
+ __block uint16_t closeCode = 0;
+
+ NSString *reason = nil;
+
+ SRFastLog(@"Received close frame");
+
+ if (dataSize == 1) {
+ // TODO handle error
+// assert(NO);
+ } else if (dataSize >= 2) {
+ [data getBytes:&closeCode length:sizeof(closeCode)];
+ closeCode = EndianU16_BtoN(closeCode);
+
+ if (dataSize > 2) {
+ reason = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(2, dataSize - 2)] encoding:NSUTF8StringEncoding];
+ }
+ }
+
+ assert(dispatch_get_current_queue() == _workQueue);
+
+ dispatch_async(_workQueue, ^{
+ if (self.readyState == SR_OPEN) {
+ [self closeWithCode:SRStatusCodeNormal reason:reason];
+ }
+ [self _disconnect];
+ });
+}
+
+- (void)_disconnect;
+{
+ SRFastLog(@"Trying to disconnect");
+ dispatch_async(_workQueue, ^{
+ _closeWhenFinishedWriting = YES;
+ [self _pumpWriting];
+ });
+}
+
+- (void)_handleFrameWithData:(NSData *)frameData opCode:(NSInteger)opcode;
+{
+ // Check that the current data is valid UTF8
+
+ BOOL isControlFrame = (opcode == SROpCodePing || opcode == SROpCodePong || opcode == SROpCodeConnectionClose);
+ if (!isControlFrame) {
+ [self _readFrameNew];
+ } else {
+ dispatch_async(_workQueue, ^{
+ [self _readFrameContinue];
+ });
+ }
+
+ 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"];
+ dispatch_async(_workQueue, ^{
+ [self _disconnect];
+ });
+
+ return;
+ }
+ [self handleMessage:str];
+ break;
+ }
+ case SROpCodeBinaryFrame:
+ [self handleMessage:[frameData copy]];
+ break;
+ case SROpCodeConnectionClose:
+ [self handleCloseWithData:frameData];
+ break;
+ case SROpCodePing:
+ [self handlePing:frameData];
+ break;
+ case SROpCodePong:
+ [self handlePong];
+ break;
+ default:
+ [self _closeWithProtocolError:[NSString stringWithFormat:@"Unknown opcode %d", opcode]];
+ // TODO: Handle invalid opcode
+ break;
+ }
+}
+
+- (void)_handleFrameHeader:(frame_header)frame_header curData:(NSData *)curData;
+{
+ assert(frame_header.opcode != 0);
+
+ if (self.readyState != SR_OPEN) {
+ 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:[NSData data] opCode:frame_header.opcode];
+ } else {
+ if (frame_header.fin) {
+// assert(_currentFrameData.length == frame_header.payload_length);
+ [self _handleFrameWithData:_currentFrameData opCode:frame_header.opcode];
+ } else {
+ // TODO add assert that opcode is not a control;
+ [self _readFrameContinue];
+ }
+ }
+ } else {
+ [self _addConsumerWithDataLength:frame_header.payload_length callback:^(SRWebSocket *self, NSData *newData) {
+ if (isControlFrame) {
+ [self _handleFrameWithData:newData opCode:frame_header.opcode];
+ } else {
+ if (frame_header.fin) {
+ [self _handleFrameWithData:_currentFrameData opCode:frame_header.opcode];
+ } else {
+ // TODO add assert that opcode is not a control;
+ [self _readFrameContinue];
+ }
+
+ }
+ } readToCurrentFrame:!isControlFrame unmaskBytes:frame_header.masked];
+ }
+}
+
+/* From RFC:
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-------+-+-------------+-------------------------------+
+ |F|R|R|R| opcode|M| Payload len | Extended payload length |
+ |I|S|S|S| (4) |A| (7) | (16/64) |
+ |N|V|V|V| |S| | (if payload len==126/127) |
+ | |1|2|3| |K| | |
+ +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
+ | Extended payload length continued, if payload len == 127 |
+ + - - - - - - - - - - - - - - - +-------------------------------+
+ | |Masking-key, if MASK set to 1 |
+ +-------------------------------+-------------------------------+
+ | Masking-key (continued) | Payload Data |
+ +-------------------------------- - - - - - - - - - - - - - - - +
+ : Payload Data continued ... :
+ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+ | Payload Data continued ... |
+ +---------------------------------------------------------------+
+ */
+
+static const uint8_t SRFinMask = 0x80;
+static const uint8_t SROpCodeMask = 0x0F;
+static const uint8_t SRRsvMask = 0x70;
+static const uint8_t SRMaskMask = 0x80;
+static const uint8_t SRPayloadLenMask = 0x7F;
+
+
+- (void)_readFrameContinue;
+{
+ assert((_currentFrameCount == 0 && _currentFrameOpcode == 0) || (_currentFrameCount > 0 && _currentFrameOpcode > 0));
+
+ [self _addConsumerWithDataLength:2 callback:^(SRWebSocket *self, NSData *data) {
+ __block frame_header header = {0};
+
+
+ const uint8_t *headerBuffer = data.bytes;
+ assert(data.length >= 2);
+
+ if (headerBuffer[0] & SRRsvMask) {
+ [(__unsafe_unretained SRWebSocket *)self _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"];
+ return;
+ }
+
+ if (receivedOpcode == 0 && _currentFrameCount == 0) {
+ [self _closeWithProtocolError:@"cannot continue a message"];
+ return;
+ }
+
+ header.opcode = receivedOpcode == 0 ? _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"];
+ }
+
+ size_t extra_bytes_needed = header.masked ? sizeof(_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:_currentFrameData];
+ } else {
+ [self _addConsumerWithDataLength:extra_bytes_needed callback:^(SRWebSocket *self, NSData *data) {
+ size_t mapped_size = data.length;
+ const void *mapped_buffer = data.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;
+ 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));
+ offset += sizeof(uint64_t);
+ } else {
+ assert(header.payload_length < 126 && header.payload_length >= 0);
+ }
+
+
+ if (header.masked) {
+ assert(mapped_size >= sizeof(_currentReadMaskOffset) + offset);
+ memcpy(_currentReadMaskKey, ((uint8_t *)mapped_buffer) + offset, sizeof(_currentReadMaskKey));
+ }
+
+ [self _handleFrameHeader:header curData:_currentFrameData];
+ } readToCurrentFrame:NO unmaskBytes:NO];
+ }
+ } readToCurrentFrame:NO unmaskBytes:NO];
+}
+
+- (void)_readFrameNew;
+{
+ dispatch_async(_workQueue, ^{
+ [_currentFrameData setLength:0];
+
+ _currentFrameOpcode = 0;
+ _currentFrameCount = 0;
+ _readOpCount = 0;
+ _currentStringScanPosition = 0;
+
+ [self _readFrameContinue];
+ });
+}
+
+- (void)_pumpWriting;
+{
+ assert(dispatch_get_current_queue() == _workQueue);
+
+ NSUInteger dataLength = _outputBuffer.length;
+ if (dataLength - _outputBufferOffset > 0 && _outputStream.hasSpaceAvailable) {
+ NSUInteger 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;
+ }
+
+ _outputBufferOffset += bytesWritten;
+
+ if (_outputBufferOffset > 4096 && _outputBufferOffset > (_outputBuffer.length >> 1)) {
+ _outputBuffer = [[NSMutableData alloc] initWithBytes:(char *)_outputBuffer.bytes + _outputBufferOffset length:_outputBuffer.length - _outputBufferOffset];
+ _outputBufferOffset = 0;
+ }
+
+ }
+
+ if (_closeWhenFinishedWriting && _outputBuffer.length - _outputBufferOffset == 0 && (_inputStream.streamStatus != NSStreamStatusNotOpen && _inputStream.streamStatus != NSStreamStatusClosed)) {
+ [_outputStream close];
+ [_inputStream close];
+
+ dispatch_async(_callbackQueue, ^{
+ _onClose(self, _closeCode, nil, YES);
+ });
+ }
+}
+
+- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback;
+{
+ [self _addConsumerWithScanner:consumer callback:callback dataLength:0];
+}
+
+- (void)_addConsumerWithDataLength:(size_t)dataLength callback:(data_callback)callback readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes;
+{
+ assert(dataLength);
+
+ dispatch_async(_workQueue, ^{
+ [_consumers addObject:[[SRIOConsumer alloc] initWithScanner:nil handler:callback bytesNeeded:dataLength readToCurrentFrame:readToCurrentFrame unmaskBytes:unmaskBytes]];
+ [self _pumpScanner];
+ });
+}
+
+- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback dataLength:(size_t)dataLength;
+{
+ dispatch_async(_workQueue, ^{
+ [_consumers addObject:[[SRIOConsumer alloc] initWithScanner:consumer handler:callback bytesNeeded:dataLength readToCurrentFrame:NO unmaskBytes:NO]];
+ [self _pumpScanner];
+ });
+}
+
+
+static const char CRLFCRLFBytes[] = {'\r', '\n', '\r', '\n'};
+
+- (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;
+{
+ // 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 (int i = 0; i < size; i++ ) {
+ if (((const unsigned char *)buffer)[i] == ((const unsigned char *)bytes)[match_count]) {
+ match_count += 1;
+ if (match_count == length) {
+ found_size = i + 1;
+ break;
+ }
+ } else {
+ match_count = 0;
+ }
+ }
+ return found_size;
+ };
+ [self _addConsumerWithScanner:consumer callback:dataHandler];
+}
+
+-(void)_pumpScanner;
+{
+ assert(dispatch_get_current_queue() == _workQueue);
+
+ if (self.readyState >= SR_CLOSING) {
+ return;
+ }
+
+ if (!_consumers.count) {
+ return;
+ }
+
+ size_t curSize = _readBuffer.length - _readBufferOffset;
+ if (!curSize) {
+ return;
+ }
+
+ 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);
+ } else {
+ assert(consumer.bytesNeeded);
+ if (curSize >= bytesNeeded) {
+ foundSize = bytesNeeded;
+ } else if (consumer.readToCurrentFrame) {
+ foundSize = curSize;
+ }
+ }
+
+ NSData *slice = nil;
+ if (consumer.readToCurrentFrame || foundSize) {
+ NSRange sliceRange = NSMakeRange(_readBufferOffset, foundSize);
+ slice = [_readBuffer subdataWithRange:sliceRange];
+
+ _readBufferOffset += foundSize;
+
+ if (_readBufferOffset > 4096 && _readBufferOffset > (_readBuffer.length >> 1)) {
+ _readBuffer = [[NSMutableData alloc] initWithBytes:(char *)_readBuffer.bytes + _readBufferOffset length:_readBuffer.length - _readBufferOffset]; _readBufferOffset = 0;
+ }
+
+ if (consumer.unmaskBytes) {
+ NSMutableData *mutableSlice = [slice mutableCopy];
+
+ NSUInteger len = mutableSlice.length;
+ uint8_t *bytes = mutableSlice.mutableBytes;
+
+ for (int i = 0; i < len; i++) {
+ bytes[i] = bytes[i] ^ _currentReadMaskKey[_currentReadMaskOffset % sizeof(_currentReadMaskKey)];
+ _currentReadMaskOffset += 1;
+ }
+
+ slice = mutableSlice;
+ }
+
+