Mike Lewis 13 жил өмнө
commit
d0ce5ee63e

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+.idea/
+.env/

+ 15 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
SRTests/en.lproj/InfoPlist.strings

@@ -0,0 +1,2 @@
+/* Localized versions of Info.plist keys */
+

+ 26 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
SRWebSocketTests/en.lproj/InfoPlist.strings

@@ -0,0 +1,2 @@
+/* Localized versions of Info.plist keys */
+

+ 8 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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

+ 1275 - 0
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;
+        }
+
+        if (consumer.readToCurrentFrame) {
+            [_currentFrameData appendData:slice];
+            
+            _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];
+                        });
+                        return;
+                    } else {
+                        _currentStringScanPosition += valid_utf8_size;
+                    }
+                } 
+
+            }
+            
+            consumer.bytesNeeded -= foundSize;
+            
+            if (consumer.bytesNeeded == 0) {
+                consumer.handler(self, nil);
+                [_consumers removeObjectAtIndex:0];
+            }
+        } else if (foundSize) {
+            consumer.handler(self, slice);
+            [_consumers removeObjectAtIndex:0];
+        }
+        
+        dispatch_async(_workQueue, ^{
+            [self _pumpScanner];
+        });
+    }
+}
+
+//#define NOMASK
+
+static const size_t SRFrameHeaderOverhead = 32;
+
+- (void)_sendFrameWithOpcode:(SROpCode)opcode data:(id)data;
+{
+    assert(dispatch_get_current_queue() == _workQueue);
+    
+    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) {
+        [self closeWithCode:SRStatusCodeMessageTooBig reason:@"Message too big"];
+        return;
+    }
+    uint8_t *frame_buffer = (uint8_t *)[frame mutableBytes];
+    
+    // set fin
+    frame_buffer[0] = SRFinMask | opcode;
+    
+    BOOL useMask = YES;
+#ifdef NOMASK
+    useMask = NO;
+#endif
+    
+    if (useMask) {
+    // 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];
+    }
+    
+    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);
+    } 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 (int 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 (int i = 0; i < payloadLength; i++) {
+            frame_buffer[frame_buffer_size] = unmasked_payload[i] ^ mask_key[i % sizeof(uint32_t)];
+            frame_buffer_size += 1;
+        }
+    }
+
+    assert(frame_buffer_size <= [frame length]);
+    frame.length = frame_buffer_size;
+    
+    [self _writeData:frame];
+}
+
+- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode;
+{
+//    SRFastLog(@"%@ Got stream event %d", aStream, eventCode);
+    switch (eventCode) {
+        case NSStreamEventOpenCompleted: {
+            SRFastLog(@"NSStreamEventOpenCompleted %@", aStream);
+            if (self.readyState >= SR_CLOSING) {
+                return;
+            }
+            
+            assert(_readBuffer);
+
+            dispatch_async(_workQueue, ^{
+                if (self.readyState == SR_CONNECTING && aStream == _inputStream) {
+                    [self didConnect];
+                }
+                [self _pumpWriting];
+                [self _pumpScanner];
+            });
+            break;
+        }
+            
+        case NSStreamEventErrorOccurred: {
+            SRFastLog(@"NSStreamEventErrorOccurred %@", aStream);
+            /// TODO specify error better!
+            [self _failWithError:aStream.streamError];
+            _readBufferOffset = 0;
+            [_readBuffer setLength:0];
+            break;
+            
+        }
+            
+        case NSStreamEventEndEncountered: {
+            SRFastLog(@"NSStreamEventEndEncountered %@", aStream);
+            if (aStream.streamError) {
+                [self _failWithError:aStream.streamError];
+            }
+            
+            if (self.readyState != SR_CLOSED) {
+                self.readyState = SR_CLOSED;
+            }
+            break;
+        }
+            
+        case NSStreamEventHasBytesAvailable: {
+            dispatch_async(_workQueue, ^{
+                const int bufferSize = 2048;
+                uint8_t buffer[bufferSize];
+                
+                while (_inputStream.hasBytesAvailable) {
+                    int 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;
+                    }
+                };
+                [self _pumpScanner];
+            });
+            break;
+        }
+            
+        case NSStreamEventHasSpaceAvailable: {
+            dispatch_async(_workQueue, ^{
+                [self _pumpWriting];
+            });
+            break;
+        }
+            
+        default:
+            break;
+    }
+}
+
+@end
+
+
+@implementation SRIOConsumer
+
+@synthesize bytesNeeded = _bytesNeeded;
+@synthesize consumer = _scanner;
+@synthesize handler = _handler;
+@synthesize readToCurrentFrame = _readToCurrentFrame;
+@synthesize unmaskBytes = _unmaskBytes;
+
+- (id)initWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes;
+{
+    self = [super init];
+    if (self) {
+        _scanner = [scanner copy];
+        _handler = [handler copy];
+        _bytesNeeded = bytesNeeded;
+        _readToCurrentFrame = readToCurrentFrame;
+        _unmaskBytes = unmaskBytes;
+        assert(_scanner || _bytesNeeded);
+    }
+    return self;
+}
+
+@end
+
+
+

+ 27 - 0
SocketRocket/SocketRocket-Prefix.pch

@@ -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.
+//
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+    
+#ifdef __OBJC__
+    #import <Foundation/Foundation.h>
+#endif
+    
+#ifdef __cplusplus
+}
+#endif

+ 314 - 0
SocketRocket/base64.c

@@ -0,0 +1,314 @@
+/*	$OpenBSD: base64.c,v 1.5 2006/10/21 09:55:03 otto Exp $	*/
+
+/*
+ * Copyright (c) 1996 by Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+/*
+ * Portions Copyright (c) 1995 by International Business Machines, Inc.
+ *
+ * International Business Machines, Inc. (hereinafter called IBM) grants
+ * permission under its copyrights to use, copy, modify, and distribute this
+ * Software with or without fee, provided that the above copyright notice and
+ * all paragraphs of this notice appear in all copies, and that the name of IBM
+ * not be used in connection with the marketing of any product incorporating
+ * the Software or modifications thereof, without specific, written prior
+ * permission.
+ *
+ * To the extent it has a right to do so, IBM grants an immunity from suit
+ * under its patents, if any, for the use, sale or manufacture of products to
+ * the extent that such products are used for performing Domain Name System
+ * dynamic updates in TCP/IP networks by means of the Software.  No immunity is
+ * granted for any product per se or for any other function of any product.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", AND IBM DISCLAIMS ALL WARRANTIES,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE.  IN NO EVENT SHALL IBM BE LIABLE FOR ANY SPECIAL,
+ * DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE, EVEN
+ * IF IBM IS APPRISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ */
+
+/* OPENBSD ORIGINAL: lib/libc/net/base64.c */
+
+
+#if (!defined(HAVE_B64_NTOP) && !defined(HAVE___B64_NTOP)) || (!defined(HAVE_B64_PTON) && !defined(HAVE___B64_PTON))
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <stdio.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "base64.h"
+
+static const char Base64[] =
+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+static const char Pad64 = '=';
+
+/* (From RFC1521 and draft-ietf-dnssec-secext-03.txt)
+ The following encoding technique is taken from RFC 1521 by Borenstein
+ and Freed.  It is reproduced here in a slightly edited form for
+ convenience.
+ 
+ A 65-character subset of US-ASCII is used, enabling 6 bits to be
+ represented per printable character. (The extra 65th character, "=",
+ is used to signify a special processing function.)
+ 
+ The encoding process represents 24-bit groups of input bits as output
+ strings of 4 encoded characters. Proceeding from left to right, a
+ 24-bit input group is formed by concatenating 3 8-bit input groups.
+ These 24 bits are then treated as 4 concatenated 6-bit groups, each
+ of which is translated into a single digit in the base64 alphabet.
+ 
+ Each 6-bit group is used as an index into an array of 64 printable
+ characters. The character referenced by the index is placed in the
+ output string.
+ 
+ Table 1: The Base64 Alphabet
+ 
+ Value Encoding  Value Encoding  Value Encoding  Value Encoding
+ 0 A            17 R            34 i            51 z
+ 1 B            18 S            35 j            52 0
+ 2 C            19 T            36 k            53 1
+ 3 D            20 U            37 l            54 2
+ 4 E            21 V            38 m            55 3
+ 5 F            22 W            39 n            56 4
+ 6 G            23 X            40 o            57 5
+ 7 H            24 Y            41 p            58 6
+ 8 I            25 Z            42 q            59 7
+ 9 J            26 a            43 r            60 8
+ 10 K            27 b            44 s            61 9
+ 11 L            28 c            45 t            62 +
+ 12 M            29 d            46 u            63 /
+ 13 N            30 e            47 v
+ 14 O            31 f            48 w         (pad) =
+ 15 P            32 g            49 x
+ 16 Q            33 h            50 y
+ 
+ Special processing is performed if fewer than 24 bits are available
+ at the end of the data being encoded.  A full encoding quantum is
+ always completed at the end of a quantity.  When fewer than 24 input
+ bits are available in an input group, zero bits are added (on the
+ right) to form an integral number of 6-bit groups.  Padding at the
+ end of the data is performed using the '=' character.
+ 
+ Since all base64 input is an integral number of octets, only the
+ -------------------------------------------------                       
+ following cases can arise:
+ 
+ (1) the final quantum of encoding input is an integral
+ multiple of 24 bits; here, the final unit of encoded
+ output will be an integral multiple of 4 characters
+ with no "=" padding,
+ (2) the final quantum of encoding input is exactly 8 bits;
+ here, the final unit of encoded output will be two
+ characters followed by two "=" padding characters, or
+ (3) the final quantum of encoding input is exactly 16 bits;
+ here, the final unit of encoded output will be three
+ characters followed by one "=" padding character.
+ */
+
+#if !defined(HAVE_B64_NTOP) && !defined(HAVE___B64_NTOP) 
+int
+b64_ntop(u_char const *src, size_t srclength, char *target, size_t targsize)
+{
+	size_t datalength = 0;
+	u_char input[3];
+	u_char output[4];
+	u_int i;
+    
+	while (2 < srclength) {
+		input[0] = *src++;
+		input[1] = *src++;
+		input[2] = *src++;
+		srclength -= 3;
+        
+		output[0] = input[0] >> 2;
+		output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4);
+		output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6);
+		output[3] = input[2] & 0x3f;
+        
+		if (datalength + 4 > targsize)
+			return (-1);
+		target[datalength++] = Base64[output[0]];
+		target[datalength++] = Base64[output[1]];
+		target[datalength++] = Base64[output[2]];
+		target[datalength++] = Base64[output[3]];
+	}
+    
+	/* Now we worry about padding. */
+	if (0 != srclength) {
+		/* Get what's left. */
+		input[0] = input[1] = input[2] = '\0';
+		for (i = 0; i < srclength; i++)
+			input[i] = *src++;
+        
+		output[0] = input[0] >> 2;
+		output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4);
+		output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6);
+        
+		if (datalength + 4 > targsize)
+			return (-1);
+		target[datalength++] = Base64[output[0]];
+		target[datalength++] = Base64[output[1]];
+		if (srclength == 1)
+			target[datalength++] = Pad64;
+		else
+			target[datalength++] = Base64[output[2]];
+		target[datalength++] = Pad64;
+	}
+	if (datalength >= targsize)
+		return (-1);
+	target[datalength] = '\0';	/* Returned value doesn't count \0. */
+	return (datalength);
+}
+#endif /* !defined(HAVE_B64_NTOP) && !defined(HAVE___B64_NTOP) */
+
+#if !defined(HAVE_B64_PTON) && !defined(HAVE___B64_PTON)
+
+/* skips all whitespace anywhere.
+ converts characters, four at a time, starting at (or after)
+ src from base - 64 numbers into three 8 bit bytes in the target area.
+ it returns the number of data bytes stored at the target, or -1 on error.
+ */
+
+int
+b64_pton(char const *src, u_char *target, size_t targsize)
+{
+	u_int tarindex, state;
+	int ch;
+	char *pos;
+    
+	state = 0;
+	tarindex = 0;
+    
+	while ((ch = *src++) != '\0') {
+		if (isspace(ch))	/* Skip whitespace anywhere. */
+			continue;
+        
+		if (ch == Pad64)
+			break;
+        
+		pos = strchr(Base64, ch);
+		if (pos == 0) 		/* A non-base64 character. */
+			return (-1);
+        
+		switch (state) {
+            case 0:
+                if (target) {
+                    if (tarindex >= targsize)
+                        return (-1);
+                    target[tarindex] = (pos - Base64) << 2;
+                }
+                state = 1;
+                break;
+            case 1:
+                if (target) {
+                    if (tarindex + 1 >= targsize)
+                        return (-1);
+                    target[tarindex]   |=  (pos - Base64) >> 4;
+                    target[tarindex+1]  = ((pos - Base64) & 0x0f)
+                    << 4 ;
+                }
+                tarindex++;
+                state = 2;
+                break;
+            case 2:
+                if (target) {
+                    if (tarindex + 1 >= targsize)
+                        return (-1);
+                    target[tarindex]   |=  (pos - Base64) >> 2;
+                    target[tarindex+1]  = ((pos - Base64) & 0x03)
+                    << 6;
+                }
+                tarindex++;
+                state = 3;
+                break;
+            case 3:
+                if (target) {
+                    if (tarindex >= targsize)
+                        return (-1);
+                    target[tarindex] |= (pos - Base64);
+                }
+                tarindex++;
+                state = 0;
+                break;
+		}
+	}
+    
+	/*
+	 * We are done decoding Base-64 chars.  Let's see if we ended
+	 * on a byte boundary, and/or with erroneous trailing characters.
+	 */
+    
+	if (ch == Pad64) {		/* We got a pad char. */
+		ch = *src++;		/* Skip it, get next. */
+		switch (state) {
+            case 0:		/* Invalid = in first position */
+            case 1:		/* Invalid = in second position */
+                return (-1);
+                
+            case 2:		/* Valid, means one byte of info */
+                /* Skip any number of spaces. */
+                for (; ch != '\0'; ch = *src++)
+                    if (!isspace(ch))
+                        break;
+                /* Make sure there is another trailing = sign. */
+                if (ch != Pad64)
+                    return (-1);
+                ch = *src++;		/* Skip the = */
+                /* Fall through to "single trailing =" case. */
+                /* FALLTHROUGH */
+                
+            case 3:		/* Valid, means two bytes of info */
+                /*
+                 * We know this char is an =.  Is there anything but
+                 * whitespace after it?
+                 */
+                for (; ch != '\0'; ch = *src++)
+                    if (!isspace(ch))
+                        return (-1);
+                
+                /*
+                 * Now make sure for cases 2 and 3 that the "extra"
+                 * bits that slopped past the last full byte were
+                 * zeros.  If we don't check them, they become a
+                 * subliminal channel.
+                 */
+                if (target && target[tarindex] != 0)
+                    return (-1);
+		}
+	} else {
+		/*
+		 * We ended by seeing the end of the string.  Make sure we
+		 * have no partial bytes lying around.
+		 */
+		if (state != 0)
+			return (-1);
+	}
+    
+	return (tarindex);
+}
+
+#endif /* !defined(HAVE_B64_PTON) && !defined(HAVE___B64_PTON) */
+#endif 

+ 34 - 0
SocketRocket/base64.h

@@ -0,0 +1,34 @@
+//   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.
+//
+
+
+#ifndef SocketRocket_base64_h
+#define SocketRocket_base64_h
+
+#include <sys/types.h>
+
+extern int
+b64_ntop(u_char const *src,
+         size_t srclength,
+         char *target,
+         size_t targsize);
+
+extern int
+b64_pton(char const *src,
+         u_char *target, 
+         size_t targsize);
+
+
+#endif