diff --git a/PSAutobahnServerTests/AppDelegate.m b/PSAutobahnServerTests/AppDelegate.m index d37f8f2..39ebeb7 100644 --- a/PSAutobahnServerTests/AppDelegate.m +++ b/PSAutobahnServerTests/AppDelegate.m @@ -11,45 +11,54 @@ @interface AppDelegate () -@property (nonatomic, strong) PSWebSocketServer *server; +@property(nonatomic, strong) PSWebSocketServer *server; @end @implementation AppDelegate +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Override point for customization after application launch. + // Create a local server at a random available port + self.server = [PSWebSocketServer localServer]; + self.server.delegate = self; + [self.server start]; -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // Override point for customization after application launch. - - self.server = [PSWebSocketServer serverWithHost:@"127.0.0.1" port:9001]; - self.server.delegate = self; - [self.server start]; - - return YES; + return YES; } #pragma mark - PSWebSocketServerDelegate - (void)serverDidStart:(PSWebSocketServer *)server { + NSLog(@"WebSockets Server started at port %zd", server.port); } - (void)server:(PSWebSocketServer *)server didFailWithError:(NSError *)error { - [NSException raise:NSInternalInconsistencyException format:error.localizedDescription]; + [NSException raise:NSInternalInconsistencyException + format:@"didFailWithError: %@", error.localizedDescription]; } - (void)serverDidStop:(PSWebSocketServer *)server { - [NSException raise:NSInternalInconsistencyException format:@"Server stopped unexpected."]; + [NSException raise:NSInternalInconsistencyException + format:@"Server stopped unexpected."]; } -- (void)server:(PSWebSocketServer *)server webSocketDidOpen:(PSWebSocket *)webSocket { - +- (void)server:(PSWebSocketServer *)server + webSocketDidOpen:(PSWebSocket *)webSocket { } -- (void)server:(PSWebSocketServer *)server webSocket:(PSWebSocket *)webSocket didReceiveMessage:(id)message { - [webSocket send:message]; +- (void)server:(PSWebSocketServer *)server + webSocket:(PSWebSocket *)webSocket + didReceiveMessage:(id)message { + [webSocket send:message]; } -- (void)server:(PSWebSocketServer *)server webSocket:(PSWebSocket *)webSocket didFailWithError:(NSError *)error { - +- (void)server:(PSWebSocketServer *)server + webSocket:(PSWebSocket *)webSocket + didFailWithError:(NSError *)error { } -- (void)server:(PSWebSocketServer *)server webSocket:(PSWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean { - +- (void)server:(PSWebSocketServer *)server + webSocket:(PSWebSocket *)webSocket + didCloseWithCode:(NSInteger)code + reason:(NSString *)reason + wasClean:(BOOL)wasClean { } @end diff --git a/PSAutobahnServerTests/Assets.xcassets/AppIcon.appiconset/Contents.json b/PSAutobahnServerTests/Assets.xcassets/AppIcon.appiconset/Contents.json index 118c98f..b8236c6 100644 --- a/PSAutobahnServerTests/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/PSAutobahnServerTests/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,5 +1,15 @@ { "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, { "idiom" : "iphone", "size" : "29x29", diff --git a/PocketSocket.xcodeproj/project.pbxproj b/PocketSocket.xcodeproj/project.pbxproj index 35794ff..3895c9e 100644 --- a/PocketSocket.xcodeproj/project.pbxproj +++ b/PocketSocket.xcodeproj/project.pbxproj @@ -380,6 +380,7 @@ TargetAttributes = { EE453D0E1CABF60300A2EA13 = { CreatedOnToolsVersion = 7.3; + DevelopmentTeam = Z68VHL6FZT; }; EEE5E35F18B37F8700BAE47A = { TestTargetID = EEE5E30918B37DD500BAE47A; @@ -562,6 +563,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = Z68VHL6FZT; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; @@ -585,6 +587,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = Z68VHL6FZT; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; diff --git a/PocketSocket/PSWebSocketDriver.m b/PocketSocket/PSWebSocketDriver.m index 63988da..f068703 100644 --- a/PocketSocket/PSWebSocketDriver.m +++ b/PocketSocket/PSWebSocketDriver.m @@ -12,13 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -#import "PSWebSocketDriver.h" #import "PSWebSocketBuffer.h" -#import "PSWebSocketInflater.h" -#import "PSWebSocketDeflater.h" #import "PSWebSocketBuffer.h" -#import "PSWebSocketUTF8Decoder.h" +#import "PSWebSocketDeflater.h" +#import "PSWebSocketDriver.h" +#import "PSWebSocketInflater.h" #import "PSWebSocketInternal.h" +#import "PSWebSocketUTF8Decoder.h" #if TARGET_OS_IPHONE #import #endif @@ -26,20 +26,20 @@ @interface PSWebSocketFrame : NSObject { @public - BOOL fin; - BOOL rsv1; - BOOL rsv2; - BOOL rsv3; - PSWebSocketOpCode opcode; - BOOL masked; - NSUInteger payloadLength; - BOOL control; - NSUInteger headerExtraLength; - uint8_t maskKey[4]; - uint32_t maskOffset; - NSMutableData *buffer; - NSUInteger payloadRemainingLength; - BOOL pmd; + BOOL fin; + BOOL rsv1; + BOOL rsv2; + BOOL rsv3; + PSWebSocketOpCode opcode; + BOOL masked; + NSUInteger payloadLength; + BOOL control; + NSUInteger headerExtraLength; + uint8_t maskKey[4]; + uint32_t maskOffset; + NSMutableData *buffer; + NSUInteger payloadRemainingLength; + BOOL pmd; } @end @implementation PSWebSocketFrame @@ -47,33 +47,33 @@ @implementation PSWebSocketFrame @end typedef NS_ENUM(NSInteger, PSWebSocketDriverState) { - PSWebSocketDriverStateHandshakeRequest = 0, - PSWebSocketDriverStateHandshakeResponse, - PSWebSocketDriverStateFrameHeader, - PSWebSocketDriverStateFrameHeaderExtra, - PSWebSocketDriverStateFramePayload + PSWebSocketDriverStateHandshakeRequest = 0, + PSWebSocketDriverStateHandshakeResponse, + PSWebSocketDriverStateFrameHeader, + PSWebSocketDriverStateFrameHeaderExtra, + PSWebSocketDriverStateFramePayload }; -@interface PSWebSocketDriver() { - NSURLRequest *_request; - PSWebSocketDriverState _state; - - BOOL _failed; - - NSString *_handshakeSecKey; - - NSMutableArray *_frames; - - BOOL _pmdEnabled; - NSInteger _pmdClientWindowBits; - BOOL _pmdClientNoContextTakeover; - NSInteger _pmdServerWindowBits; - BOOL _pmdServerNoContextTakeover; - PSWebSocketInflater *_inflater; - PSWebSocketDeflater *_deflater; - - uint32_t _utf8DecoderState; - uint32_t _utf8DecoderCodePoint; +@interface PSWebSocketDriver () { + NSURLRequest *_request; + PSWebSocketDriverState _state; + + BOOL _failed; + + NSString *_handshakeSecKey; + + NSMutableArray *_frames; + + BOOL _pmdEnabled; + NSInteger _pmdClientWindowBits; + BOOL _pmdClientNoContextTakeover; + NSInteger _pmdServerWindowBits; + BOOL _pmdServerNoContextTakeover; + PSWebSocketInflater *_inflater; + PSWebSocketDeflater *_deflater; + + uint32_t _utf8DecoderState; + uint32_t _utf8DecoderCodePoint; } @end @implementation PSWebSocketDriver @@ -81,879 +81,1001 @@ @implementation PSWebSocketDriver #pragma mark - Class Methods + (BOOL)isWebSocketRequest:(NSURLRequest *)request { - NSDictionary *headers = request.allHTTPHeaderFields; - - NSOrderedSet *version = PSHTTPHeaderFieldValues([headers[@"Sec-WebSocket-Version"] lowercaseString]); - NSOrderedSet *upgrade = PSHTTPHeaderFieldValues([headers[@"Upgrade"] lowercaseString]); - NSOrderedSet *connection = PSHTTPHeaderFieldValues([headers[@"Connection"] lowercaseString]); - - if(headers[@"Sec-WebSocket-Key"] && - [version containsObject:@"13"] && - [connection containsObject:@"upgrade"] && - [upgrade containsObject:@"websocket"] && - [request.HTTPMethod.lowercaseString isEqualToString:@"get"] && - request.HTTPBody.length == 0) { - return YES; - } - return NO; + NSDictionary *headers = request.allHTTPHeaderFields; + + NSOrderedSet *version = PSHTTPHeaderFieldValues( + [headers[@"Sec-WebSocket-Version"] lowercaseString]); + NSOrderedSet *upgrade = + PSHTTPHeaderFieldValues([headers[@"Upgrade"] lowercaseString]); + NSOrderedSet *connection = + PSHTTPHeaderFieldValues([headers[@"Connection"] lowercaseString]); + + if (headers[@"Sec-WebSocket-Key"] && [version containsObject:@"13"] && + [connection containsObject:@"upgrade"] && + [upgrade containsObject:@"websocket"] && + [request.HTTPMethod.lowercaseString isEqualToString:@"get"] && + request.HTTPBody.length == 0) { + return YES; + } + return NO; } #pragma mark - Initialization + (instancetype)clientDriverWithRequest:(NSURLRequest *)request { - return [[self alloc] initWithMode:PSWebSocketModeClient request:request]; + return [[self alloc] initWithMode:PSWebSocketModeClient request:request]; } + (instancetype)serverDriverWithRequest:(NSURLRequest *)request { - return [[self alloc] initWithMode:PSWebSocketModeServer request:request]; + return [[self alloc] initWithMode:PSWebSocketModeServer request:request]; } -- (instancetype)initWithMode:(PSWebSocketMode)mode request:(NSURLRequest *)request { - NSParameterAssert(request); - if((self = [super init])) { - _mode = mode; - _state = (_mode == PSWebSocketModeClient) ? PSWebSocketDriverStateHandshakeRequest : PSWebSocketDriverStateHandshakeResponse; - _request = [request mutableCopy]; - _frames = [NSMutableArray array]; - _utf8DecoderState = 0; - _utf8DecoderCodePoint = 0; - _pmdEnabled = YES; - _pmdClientWindowBits = -11; - _pmdServerWindowBits = -11; - } - return self; +- (instancetype)initWithMode:(PSWebSocketMode)mode + request:(NSURLRequest *)request { + NSParameterAssert(request); + if ((self = [super init])) { + _mode = mode; + _state = (_mode == PSWebSocketModeClient) + ? PSWebSocketDriverStateHandshakeRequest + : PSWebSocketDriverStateHandshakeResponse; + _request = [request mutableCopy]; + _frames = [NSMutableArray array]; + _utf8DecoderState = 0; + _utf8DecoderCodePoint = 0; + _pmdEnabled = YES; + _pmdClientWindowBits = -11; + _pmdServerWindowBits = -11; + } + return self; } #pragma mark - Actions - (void)start { - if(_mode == PSWebSocketModeClient) { - [self writeHandshakeRequest]; - } else { - [self writeHandshakeResponse]; - } + if (_mode == PSWebSocketModeClient) { + [self writeHandshakeRequest]; + } else { + [self writeHandshakeResponse]; + } } - (NSUInteger)execute:(void *)bytes maxLength:(NSUInteger)maxLength { - // skip if failed - if(_failed) { - return 0; - } - - // skip if 0 bytes - if(maxLength <= 0) { - return 0; - } - - NSError *error = nil; - NSInteger bytesRead = 0; - NSUInteger totalBytesRead = 0; - while(totalBytesRead < maxLength) { - bytesRead = [self readBytes:bytes maxLength:maxLength - totalBytesRead error:&error]; - if(bytesRead < 0) { - if(error) { - [self failWithError:error]; - } else { - [self failWithErrorCode:-1 reason:@"An unknown error occurred"]; - } - break; - } else if(bytesRead == 0) { - break; - } - totalBytesRead += bytesRead; - bytes += bytesRead; + // skip if failed + if (_failed) { + return 0; + } + + // skip if 0 bytes + if (maxLength <= 0) { + return 0; + } + + NSError *error = nil; + NSInteger bytesRead = 0; + NSUInteger totalBytesRead = 0; + while (totalBytesRead < maxLength) { + bytesRead = [self readBytes:bytes + maxLength:maxLength - totalBytesRead + error:&error]; + if (bytesRead < 0) { + if (error) { + [self failWithError:error]; + } else { + [self failWithErrorCode:-1 reason:@"An unknown error occurred"]; + } + break; + } else if (bytesRead == 0) { + break; } - return totalBytesRead; + totalBytesRead += bytesRead; + bytes += bytesRead; + } + return totalBytesRead; } - (void)sendText:(NSString *)text { - NSData *data = [text dataUsingEncoding:NSUTF8StringEncoding]; - [self writeMessageWithOpCode:PSWebSocketOpCodeText data:data]; + NSData *data = [text dataUsingEncoding:NSUTF8StringEncoding]; + [self writeMessageWithOpCode:PSWebSocketOpCodeText data:data]; } - (void)sendBinary:(NSData *)binary { - [self writeMessageWithOpCode:PSWebSocketOpCodeBinary data:binary]; + [self writeMessageWithOpCode:PSWebSocketOpCodeBinary data:binary]; } - (void)sendCloseCode:(NSInteger)code reason:(NSString *)reason { - NSUInteger reasonMaxLength = [reason maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding]; - NSMutableData *data = [NSMutableData dataWithLength:sizeof(uint16_t) + reasonMaxLength]; - uint8_t *dataBytes = (uint8_t *)data.mutableBytes; - ((uint16_t *)dataBytes)[0] = EndianU16_BtoN(code); - if(reason) { - NSRange remainingRange = NSMakeRange(0, 0); - NSUInteger usedLength = 0; - __unused BOOL success = [reason getBytes:dataBytes + sizeof(uint16_t) - maxLength:data.length - sizeof(uint16_t) - usedLength:&usedLength - encoding:NSUTF8StringEncoding - options:NSStringEncodingConversionExternalRepresentation - range:NSMakeRange(0, reason.length) - remainingRange:&remainingRange]; - NSAssert(success, @"Failed to write reason when sending close frame"); - NSAssert(remainingRange.length == 0, @"Failed to write reason when sending close frame"); - - data.length = usedLength + sizeof(uint16_t); - } - [self writeMessageWithOpCode:PSWebSocketOpCodeClose data:data]; + NSUInteger reasonMaxLength = + [reason maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + NSMutableData *data = + [NSMutableData dataWithLength:sizeof(uint16_t) + reasonMaxLength]; + uint8_t *dataBytes = (uint8_t *)data.mutableBytes; + ((uint16_t *)dataBytes)[0] = EndianU16_BtoN(code); + if (reason) { + NSRange remainingRange = NSMakeRange(0, 0); + NSUInteger usedLength = 0; + __unused BOOL success = + [reason getBytes:dataBytes + sizeof(uint16_t) + maxLength:data.length - sizeof(uint16_t) + usedLength:&usedLength + encoding:NSUTF8StringEncoding + options:NSStringEncodingConversionExternalRepresentation + range:NSMakeRange(0, reason.length) + remainingRange:&remainingRange]; + NSAssert(success, @"Failed to write reason when sending close frame"); + NSAssert(remainingRange.length == 0, + @"Failed to write reason when sending close frame"); + + data.length = usedLength + sizeof(uint16_t); + } + [self writeMessageWithOpCode:PSWebSocketOpCodeClose data:data]; } - (void)sendPing:(NSData *)data { - [self writeMessageWithOpCode:PSWebSocketOpCodePing data:data]; + [self writeMessageWithOpCode:PSWebSocketOpCodePing data:data]; } - (void)sendPong:(NSData *)data { - [self writeMessageWithOpCode:PSWebSocketOpCodePong data:data]; + [self writeMessageWithOpCode:PSWebSocketOpCodePong data:data]; } #pragma mark - Writing - (void)writeHandshakeRequest { - NSAssert(_mode == PSWebSocketModeClient, @"Cannot send handshake requests in non client mode"); - NSAssert(_state == PSWebSocketDriverStateHandshakeRequest, @"Cannot start a driver more than once"); - - // set handshake sec key - NSMutableData *secKeyData = [NSMutableData dataWithLength:16]; - SecRandomCopyBytes(kSecRandomDefault, secKeyData.length, secKeyData.mutableBytes); - - _handshakeSecKey = [self base64EncodedData:secKeyData]; - - NSURL *URL = _request.URL; - BOOL secure = ([URL.scheme isEqualToString:@"https"] || [URL.scheme isEqualToString:@"wss"]); - NSString *host = (URL.port) ? [NSString stringWithFormat:@"%@:%@", URL.host, URL.port] : URL.host; - NSString *origin = [NSString stringWithFormat:@"http%@://%@", (secure) ? @"s" : @"", host]; - - CFHTTPMessageRef msg = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), (__bridge CFURLRef)URL, kCFHTTPVersion1_1); - CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Host"), (__bridge CFStringRef)host); - CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Connection"), CFSTR("upgrade")); - CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Upgrade"), CFSTR("websocket")); - CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Sec-WebSocket-Version"), CFSTR("13")); - CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Sec-WebSocket-Key"), (__bridge CFStringRef)_handshakeSecKey); - CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Origin"), (__bridge CFStringRef)origin); - - [_request.allHTTPHeaderFields enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { - CFHTTPMessageSetHeaderFieldValue(msg, (__bridge CFStringRef)[NSString stringWithFormat:@"%@", key], (__bridge CFStringRef)[NSString stringWithFormat:@"%@", obj]); - }]; - - // extensions - NSMutableArray *extensionComponents = [NSMutableArray array]; - [extensionComponents addObjectsFromArray:[self pmdExtensionsHeaderComponents]]; - if(extensionComponents.count > 0) { - NSString *value = [extensionComponents componentsJoinedByString:@"; "]; - CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Sec-WebSocket-Extensions"), (__bridge CFStringRef)value); - } - - // serialize - NSData *handshakeData = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(msg)); - - // write handshake - [_delegate driver:self write:handshakeData]; - - // clean up - CFRelease(msg); - - // transition state - _state = PSWebSocketDriverStateHandshakeResponse; + NSAssert(_mode == PSWebSocketModeClient, + @"Cannot send handshake requests in non client mode"); + NSAssert(_state == PSWebSocketDriverStateHandshakeRequest, + @"Cannot start a driver more than once"); + + // set handshake sec key + NSMutableData *secKeyData = [NSMutableData dataWithLength:16]; + SecRandomCopyBytes(kSecRandomDefault, secKeyData.length, + secKeyData.mutableBytes); + + _handshakeSecKey = [self base64EncodedData:secKeyData]; + + NSURL *URL = _request.URL; + BOOL secure = ([URL.scheme isEqualToString:@"https"] || + [URL.scheme isEqualToString:@"wss"]); + NSString *host = + (URL.port) ? [NSString stringWithFormat:@"%@:%@", URL.host, URL.port] + : URL.host; + NSString *origin = + [NSString stringWithFormat:@"http%@://%@", (secure) ? @"s" : @"", host]; + + CFHTTPMessageRef msg = + CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), + (__bridge CFURLRef)URL, kCFHTTPVersion1_1); + CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Host"), + (__bridge CFStringRef)host); + CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Connection"), CFSTR("upgrade")); + CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Upgrade"), CFSTR("websocket")); + CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Sec-WebSocket-Version"), + CFSTR("13")); + CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Sec-WebSocket-Key"), + (__bridge CFStringRef)_handshakeSecKey); + CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Origin"), + (__bridge CFStringRef)origin); + + [_request.allHTTPHeaderFields + enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + CFHTTPMessageSetHeaderFieldValue( + msg, (__bridge CFStringRef)[NSString stringWithFormat:@"%@", key], + (__bridge CFStringRef)[NSString stringWithFormat:@"%@", obj]); + }]; + + // extensions + NSMutableArray *extensionComponents = [NSMutableArray array]; + [extensionComponents + addObjectsFromArray:[self pmdExtensionsHeaderComponents]]; + if (extensionComponents.count > 0) { + NSString *value = [extensionComponents componentsJoinedByString:@"; "]; + CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Sec-WebSocket-Extensions"), + (__bridge CFStringRef)value); + } + + // serialize + NSData *handshakeData = + CFBridgingRelease(CFHTTPMessageCopySerializedMessage(msg)); + + // write handshake + [_delegate driver:self write:handshakeData]; + + // clean up + CFRelease(msg); + + // transition state + _state = PSWebSocketDriverStateHandshakeResponse; } - (void)writeHandshakeResponse { - NSAssert(_state == PSWebSocketDriverStateHandshakeResponse, @"Cannot start a driver more than once"); - - // get headers - NSDictionary *headers = _request.allHTTPHeaderFields; - - // validate is websocket - if(![[self class] isWebSocketRequest:_request]) { - [self failWithErrorCode:-1 reason:@"Invalid websocket request"]; - return; - } - - // validate extensions - NSOrderedSet *extensionComponents = PSHTTPHeaderFieldValues([headers[@"Sec-WebSocket-Extensions"] lowercaseString]); - if(![self pmdConfigureWithExtensionsHeaderComponents:extensionComponents]) { - [self failWithErrorCode:PSWebSocketErrorCodeHandshakeFailed reason:@"invalid permessage-deflate extension parameters"]; - return; - } - - // set key - _handshakeSecKey = headers[@"Sec-WebSocket-Key"]; - - // create response - CFHTTPMessageRef msg = CFHTTPMessageCreateResponse(kCFAllocatorDefault, 101, CFSTR("Switching Protocols"), kCFHTTPVersion1_1); - CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Connection"), CFSTR("Upgrade")); - CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Upgrade"), CFSTR("websocket")); - CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Sec-WebSocket-Accept"), (__bridge CFStringRef)[self acceptHeaderForKey:_handshakeSecKey]); - if (_protocol) { - CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Sec-WebSocket-Protocol"), (__bridge CFStringRef)_protocol); - } - - NSMutableArray *negotiatedExtensionComponents = [NSMutableArray array]; - [negotiatedExtensionComponents addObjectsFromArray:[self pmdExtensionsHeaderComponents]]; - if(negotiatedExtensionComponents.count > 0) { - NSString *value = [negotiatedExtensionComponents componentsJoinedByString:@"; "]; - CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Sec-WebSocket-Extensions"), (__bridge CFStringRef)value); - } - - // serialize - NSData *handshakeData = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(msg)); - - // write handshake - [_delegate driver:self write:handshakeData]; - - // clean up - CFRelease(msg); - - // transition state - _state = PSWebSocketDriverStateFrameHeader; - - // open - [_delegate driverDidOpen:self]; + NSAssert(_state == PSWebSocketDriverStateHandshakeResponse, + @"Cannot start a driver more than once"); + + // get headers + NSDictionary *headers = _request.allHTTPHeaderFields; + + // validate is websocket + if (![[self class] isWebSocketRequest:_request]) { + [self failWithErrorCode:-1 reason:@"Invalid websocket request"]; + return; + } + + // validate extensions + NSOrderedSet *extensionComponents = PSHTTPHeaderFieldValues( + [headers[@"Sec-WebSocket-Extensions"] lowercaseString]); + if (![self pmdConfigureWithExtensionsHeaderComponents:extensionComponents]) { + [self failWithErrorCode:PSWebSocketErrorCodeHandshakeFailed + reason:@"invalid permessage-deflate extension parameters"]; + return; + } + + // set key + _handshakeSecKey = headers[@"Sec-WebSocket-Key"]; + + // create response + CFHTTPMessageRef msg = CFHTTPMessageCreateResponse( + kCFAllocatorDefault, 101, CFSTR("Switching Protocols"), + kCFHTTPVersion1_1); + CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Connection"), CFSTR("Upgrade")); + CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Upgrade"), CFSTR("websocket")); + CFHTTPMessageSetHeaderFieldValue( + msg, CFSTR("Sec-WebSocket-Accept"), + (__bridge CFStringRef)[self acceptHeaderForKey:_handshakeSecKey]); + if (_protocol) { + CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Sec-WebSocket-Protocol"), + (__bridge CFStringRef)_protocol); + } + + NSMutableArray *negotiatedExtensionComponents = [NSMutableArray array]; + [negotiatedExtensionComponents + addObjectsFromArray:[self pmdExtensionsHeaderComponents]]; + if (negotiatedExtensionComponents.count > 0) { + NSString *value = + [negotiatedExtensionComponents componentsJoinedByString:@"; "]; + CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Sec-WebSocket-Extensions"), + (__bridge CFStringRef)value); + } + + // serialize + NSData *handshakeData = + CFBridgingRelease(CFHTTPMessageCopySerializedMessage(msg)); + + // write handshake + [_delegate driver:self write:handshakeData]; + + // clean up + CFRelease(msg); + + // transition state + _state = PSWebSocketDriverStateFrameHeader; + + // open + [_delegate driverDidOpen:self]; } - (void)writeMessageWithOpCode:(PSWebSocketOpCode)opcode data:(NSData *)data { - // create header - NSMutableData *header = [NSMutableData dataWithLength:2]; - uint8_t *headerBytes = header.mutableBytes; - - headerBytes[0] |= PSWebSocketFinMask; - // headerBytes[0] |= (PSWebSocketRsv2Mask); - // headerBytes[0] |= (PSWebSocketRsv3Mask); - headerBytes[0] |= (PSWebSocketOpCodeMask & opcode); - - // determine payload payload - id payload = data; - - // deflate payload - if(_pmdEnabled && !PSWebSocketOpCodeIsControl(opcode) && [payload length] > 0) { - // reset deflater if needed - if((_pmdClientNoContextTakeover && _mode == PSWebSocketModeClient) || - (_pmdServerNoContextTakeover && _mode == PSWebSocketModeServer)) { - [_deflater reset]; - } - - // create deflate buffer - NSMutableData *deflated = [NSMutableData dataWithCapacity:[payload length]/4]; - - // error - NSError *error = nil; - - // begin deflater - if(![_deflater begin:deflated error:&error]) { - NSAssert(NO, error.localizedDescription); - [self failWithError:error]; - [_deflater reset]; - return; - } - - // append bytes - if(![_deflater appendBytes:[payload bytes] length:[payload length] error:&error]) { - NSAssert(NO, error.localizedDescription); - [self failWithError:error]; - [_deflater reset]; - return; - } - - // end deflater - if(![_deflater end:&error]) { - NSAssert(NO, error.localizedDescription); - [self failWithError:error]; - [_deflater reset]; - return; - } - - // reassign data - payload = deflated; - - // set rsv1 mask - headerBytes[0] |= PSWebSocketRsv1Mask; - } - - // set payload length data - if([payload length] < 126) { - headerBytes[1] |= [payload length]; - } else if([payload length] <= UINT16_MAX) { - headerBytes[1] |= 126; - uint16_t len = EndianU16_BtoN((uint16_t)[payload length]); - [header appendBytes:&len length:sizeof(len)]; - } else { - headerBytes[1] |= 127; - uint64_t len = EndianU64_BtoN((uint64_t)[payload length]); - [header appendBytes:&len length:sizeof(len)]; - } - - // set masking data - if(_mode == PSWebSocketModeClient) { - headerBytes = header.mutableBytes; // because -appendBytes may have realloced header - headerBytes[1] |= PSWebSocketMaskMask; - - uint8_t maskKey[4]; - SecRandomCopyBytes(kSecRandomDefault, sizeof(maskKey), maskKey); - [header appendBytes:maskKey length:sizeof(maskKey)]; - - // make copy if not already mutable - if(![payload isKindOfClass:[NSMutableData class]]) { - payload = [payload mutableCopy]; - } - - // mask payload inplace - uint8_t *payloadBytes = (uint8_t *)[payload mutableBytes]; - for(NSUInteger i = 0; i < [payload length]; ++i) { - payloadBytes[i] = payloadBytes[i] ^ maskKey[i % sizeof(maskKey)]; - } + // create header + NSMutableData *header = [NSMutableData dataWithLength:2]; + uint8_t *headerBytes = header.mutableBytes; + + headerBytes[0] |= PSWebSocketFinMask; + // headerBytes[0] |= (PSWebSocketRsv2Mask); + // headerBytes[0] |= (PSWebSocketRsv3Mask); + headerBytes[0] |= (PSWebSocketOpCodeMask & opcode); + + // determine payload payload + id payload = data; + + // deflate payload + if (_pmdEnabled && !PSWebSocketOpCodeIsControl(opcode) && + [payload length] > 0) { + // reset deflater if needed + if ((_pmdClientNoContextTakeover && _mode == PSWebSocketModeClient) || + (_pmdServerNoContextTakeover && _mode == PSWebSocketModeServer)) { + [_deflater reset]; + } + + // create deflate buffer + NSMutableData *deflated = + [NSMutableData dataWithCapacity:[payload length] / 4]; + + // error + NSError *error = nil; + + // begin deflater + if (![_deflater begin:deflated error:&error]) { + NSAssert(NO, error.localizedDescription); + [self failWithError:error]; + [_deflater reset]; + return; + } + + // append bytes + if (![_deflater appendBytes:[payload bytes] + length:[payload length] + error:&error]) { + NSAssert(NO, error.localizedDescription); + [self failWithError:error]; + [_deflater reset]; + return; + } + + // end deflater + if (![_deflater end:&error]) { + NSAssert(NO, error.localizedDescription); + [self failWithError:error]; + [_deflater reset]; + return; + } + + // reassign data + payload = deflated; + + // set rsv1 mask + headerBytes[0] |= PSWebSocketRsv1Mask; + } + + // set payload length data + if ([payload length] < 126) { + headerBytes[1] |= [payload length]; + } else if ([payload length] <= UINT16_MAX) { + headerBytes[1] |= 126; + uint16_t len = EndianU16_BtoN((uint16_t)[payload length]); + [header appendBytes:&len length:sizeof(len)]; + } else { + headerBytes[1] |= 127; + uint64_t len = EndianU64_BtoN((uint64_t)[payload length]); + [header appendBytes:&len length:sizeof(len)]; + } + + // set masking data + if (_mode == PSWebSocketModeClient) { + headerBytes = + header.mutableBytes; // because -appendBytes may have realloced header + headerBytes[1] |= PSWebSocketMaskMask; + + uint8_t maskKey[4]; + SecRandomCopyBytes(kSecRandomDefault, sizeof(maskKey), maskKey); + [header appendBytes:maskKey length:sizeof(maskKey)]; + + // make copy if not already mutable + if (![payload isKindOfClass:[NSMutableData class]]) { + payload = [payload mutableCopy]; + } + + // mask payload inplace + uint8_t *payloadBytes = (uint8_t *)[payload mutableBytes]; + for (NSUInteger i = 0; i < [payload length]; ++i) { + payloadBytes[i] = payloadBytes[i] ^ maskKey[i % sizeof(maskKey)]; } - - // write data to delegate - [_delegate driver:self write:header]; - [_delegate driver:self write:payload]; + } + + // write data to delegate + [header appendData:payload]; + [_delegate driver:self write:header]; } #pragma mark - Reading -- (NSInteger)readBytes:(void *)bytes maxLength:(NSUInteger)maxLength error:(NSError *__autoreleasing *)outError { +- (NSInteger)readBytes:(void *)bytes + maxLength:(NSUInteger)maxLength + error:(NSError *__autoreleasing *)outError { + NSAssert(maxLength > 0, @"Must have 1 or more bytes"); + switch (_state) { + // + // HANDSHAKE RESPONSE + // + case PSWebSocketDriverStateHandshakeResponse: { NSAssert(maxLength > 0, @"Must have 1 or more bytes"); - switch(_state) { - // - // HANDSHAKE RESPONSE - // - case PSWebSocketDriverStateHandshakeResponse: { - NSAssert(maxLength > 0, @"Must have 1 or more bytes"); - NSAssert(_state == PSWebSocketDriverStateHandshakeResponse, @"Invalid state for reading handshake response"); - - void* boundary = memmem(bytes, maxLength, "\r\n\r\n", 4); - if (boundary == NULL) { - // do not allow too much data for headers - if(maxLength >= 16384) { - PSWebSocketSetOutError(outError, PSWebSocketErrorCodeHandshakeFailed, @"HTTP headers did not finish after reading 16384 bytes"); - return -1; - } - return 0; - } - NSUInteger preBoundaryLength = boundary + 4 - bytes; - - // create handshake - CFHTTPMessageRef msg = CFHTTPMessageCreateEmpty(NULL, NO); - if (!CFHTTPMessageAppendBytes(msg, (const UInt8 *)bytes, preBoundaryLength)) { - PSWebSocketSetOutError(outError, PSWebSocketErrorCodeHandshakeFailed, @"Not a valid HTTP response"); - CFRelease(msg); - return -1; - } - - // validate complete - if(!CFHTTPMessageIsHeaderComplete(msg)) { - PSWebSocketSetOutError(outError, PSWebSocketErrorCodeHandshakeFailed, @"HTTP headers found CRLFCRLF but not complete"); - CFRelease(msg); - return -1; - } - - // get values - NSInteger statusCode = CFHTTPMessageGetResponseStatusCode(msg); - NSDictionary *headers = [CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(msg)) copy]; - CFAutorelease(msg); - - // validate status - if(statusCode != 101) { - if(outError) { - NSString* message = CFBridgingRelease(CFHTTPMessageCopyResponseStatusLine(msg)); - if (!message) - message = [NSHTTPURLResponse localizedStringForStatusCode:statusCode]; - else if ([message hasPrefix:@"HTTP/1.1 "]) - message = [message substringFromIndex:9]; - NSString* desc = [@"Handshake failed: " stringByAppendingString:message]; - NSDictionary* userInfo = @{NSLocalizedDescriptionKey: desc, - NSLocalizedFailureReasonErrorKey: message, - PSHTTPStatusErrorKey: @(statusCode), - PSHTTPResponseErrorKey: (__bridge id)msg}; - *outError = [NSError errorWithDomain:PSWebSocketErrorDomain - code:PSWebSocketErrorCodeHandshakeFailed - userInfo:userInfo]; - } - return - 1; - } - - // validate accept - if(![headers[@"Sec-WebSocket-Accept"] isEqualToString:[self acceptHeaderForKey:_handshakeSecKey]]) { - PSWebSocketSetOutError(outError, PSWebSocketErrorCodeHandshakeFailed, @"Invalid Sec-WebSocket-Accept"); - return -1; - } - - // validate version - if(headers[@"Sec-WebSocket-Version"] && ![headers[@"Sec-WebSocket-Version"] isEqualToString:@"13"]) { - PSWebSocketSetOutError(outError, PSWebSocketErrorCodeHandshakeFailed, @"Invalid Sec-WebSocket-Version"); - return -1; - } - - // validate protocol - _protocol = headers[@"Sec-WebSocket-Protocol"]; - NSString* protocolRequest = _request.allHTTPHeaderFields[@"Sec-WebSocket-Protocol"]; - if (protocolRequest) { - NSArray *protocolComponents = [protocolRequest componentsSeparatedByString:@" "]; - if(!_protocol || ![protocolComponents containsObject:_protocol]) { - PSWebSocketSetOutError(outError, PSWebSocketErrorCodeHandshakeFailed, - @"Invalid Sec-WebSocket-Protocol"); - return -1; - } - } - - // extensions - NSOrderedSet *extensionComponents = PSHTTPHeaderFieldValues([headers[@"Sec-WebSocket-Extensions"] lowercaseString]); - - // per-message deflate - if(![self pmdConfigureWithExtensionsHeaderComponents:extensionComponents]) { - PSWebSocketSetOutError(outError, PSWebSocketErrorCodeHandshakeFailed, @"permessage-deflate could not negotiate parameters"); - return -1; - } - - // transition state - _state = PSWebSocketDriverStateFrameHeader; - - [_delegate driverDidOpen:self]; - - return preBoundaryLength; - } - // - // FRAME HEADER - // - case PSWebSocketDriverStateFrameHeader: { - NSAssert(maxLength > 0, @"Must have 1 or more bytes"); - - if(maxLength < 2) { - return 0; - } - - const uint8_t *header = (const uint8_t *)bytes; - - BOOL fin = !!(header[0] & PSWebSocketFinMask); - BOOL rsv1 = !!(header[0] & PSWebSocketRsv1Mask); - BOOL rsv2 = !!(header[0] & PSWebSocketRsv2Mask); - BOOL rsv3 = !!(header[0] & PSWebSocketRsv3Mask); - PSWebSocketOpCode opcode = (header[0] & PSWebSocketOpCodeMask); - BOOL masked = !!(header[1] & PSWebSocketMaskMask); - uint64_t payloadLength = (header[1] & PSWebSocketPayloadLenMask); - uint32_t headerExtraLength = (masked) ? sizeof(uint32_t) : 0; - if(payloadLength == 126) { - headerExtraLength += sizeof(uint16_t); - } else if(payloadLength == 127) { - headerExtraLength += sizeof(uint64_t); - } - - // validate opcode - if(!PSWebSocketOpCodeIsValid(opcode)) { - PSWebSocketSetOutError(outError, PSWebSocketStatusCodeProtocolError, @"Invalid opcode"); - return -1; - } - - // determine if control frame - BOOL control = PSWebSocketOpCodeIsControl(opcode); - - // validate control frame - if(control) { - // control frames must be final - if(!fin) { - PSWebSocketSetOutError(outError, PSWebSocketStatusCodeProtocolError, @"Control frames must be final"); - return -1; - } - // control frames must not have any reserved bits set - if(rsv1 || rsv2 || rsv3) { - PSWebSocketSetOutError(outError, PSWebSocketStatusCodeProtocolError, @"Control frames must not use reserved bits"); - return -1; - } - // control frames must not have payload >= 126 - if(payloadLength >= 126) { - PSWebSocketSetOutError(outError, PSWebSocketStatusCodeProtocolError, @"Control frames payload must < 126"); - return -1; - } - } - // validate data frame - else { - // data continuation frames must follow an initial data frame - if(opcode == PSWebSocketOpCodeContinuation && _frames.count == 0) { - PSWebSocketSetOutError(outError, PSWebSocketStatusCodeProtocolError, @"Data continuation frames must follow an initial data frame"); - return -1; - } - // non data continuation frames must not follow an initial data frame - if(opcode != PSWebSocketOpCodeContinuation && _frames.count > 0) { - PSWebSocketSetOutError(outError, PSWebSocketStatusCodeProtocolError, @"Data frames must not follow an initial data frame unless continuations"); - return -1; - } - // rsv1 only set if permessage-deflate is enabled - if(rsv1 && !_pmdEnabled) { - PSWebSocketSetOutError(outError, PSWebSocketStatusCodeProtocolError, @"Data frames must only use rsv1 bit if permessage-deflate extension is on"); - return -1; - } - // rsv2 and rsv3 must never be set - if(rsv2 || rsv3) { - PSWebSocketSetOutError(outError, PSWebSocketStatusCodeProtocolError, @"Data frames must never use rsv2 or rsv3 bits"); - return -1; - } - } - - // ensure not masked if client mode - if(masked && _mode == PSWebSocketModeClient) { - PSWebSocketSetOutError(outError, PSWebSocketStatusCodeProtocolError, @"Frames must never be masked from server"); - return -1; - } - // ensure masked if server mode - if(!masked && _mode == PSWebSocketModeServer) { - PSWebSocketSetOutError(outError, PSWebSocketStatusCodeProtocolError, @"Frames must always be masked from client"); - return -1; - } - - // create frame - PSWebSocketFrame *frame = [[PSWebSocketFrame alloc] init]; - frame->fin = fin; - frame->rsv1 = rsv1; - frame->rsv2 = rsv2; - frame->rsv3 = rsv3; - frame->opcode = opcode; - frame->masked = masked; - frame->payloadLength = (NSUInteger)payloadLength; - frame->payloadRemainingLength = (NSUInteger)payloadLength; - frame->headerExtraLength = headerExtraLength; - frame->control = control; - frame->pmd = (_pmdEnabled && !control && rsv1); - - if(!frame->control && _frames.count > 0) { - PSWebSocketFrame *lastFrame = [_frames lastObject]; - frame->pmd = (_pmdEnabled && (rsv1 || lastFrame->pmd)); - frame->opcode = lastFrame->opcode; - frame->buffer = lastFrame->buffer; - } else { - frame->buffer = [NSMutableData data]; - } - [_frames addObject:frame]; - - if(headerExtraLength > 0) { - _state = PSWebSocketDriverStateFrameHeaderExtra; - } else if(payloadLength > 0) { - _state = PSWebSocketDriverStateFramePayload; - } else { - _state = PSWebSocketDriverStateFrameHeader; - if(![self processFramesAndDelegate:outError]) { - return -1; - } - } - return 2; + NSAssert(_state == PSWebSocketDriverStateHandshakeResponse, + @"Invalid state for reading handshake response"); + + void *boundary = memmem(bytes, maxLength, "\r\n\r\n", 4); + if (boundary == NULL) { + // do not allow too much data for headers + if (maxLength >= 16384) { + PSWebSocketSetOutError( + outError, PSWebSocketErrorCodeHandshakeFailed, + @"HTTP headers did not finish after reading 16384 bytes"); + return -1; + } + return 0; + } + NSUInteger preBoundaryLength = boundary + 4 - bytes; + + // create handshake + CFHTTPMessageRef msg = CFHTTPMessageCreateEmpty(NULL, NO); + if (!CFHTTPMessageAppendBytes(msg, (const UInt8 *)bytes, + preBoundaryLength)) { + PSWebSocketSetOutError(outError, PSWebSocketErrorCodeHandshakeFailed, + @"Not a valid HTTP response"); + CFRelease(msg); + return -1; + } + + // validate complete + if (!CFHTTPMessageIsHeaderComplete(msg)) { + PSWebSocketSetOutError(outError, PSWebSocketErrorCodeHandshakeFailed, + @"HTTP headers found CRLFCRLF but not complete"); + CFRelease(msg); + return -1; + } + + // get values + NSInteger statusCode = CFHTTPMessageGetResponseStatusCode(msg); + NSDictionary *headers = + [CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(msg)) copy]; + CFAutorelease(msg); + + // validate status + if (statusCode != 101) { + if (outError) { + NSString *message = + CFBridgingRelease(CFHTTPMessageCopyResponseStatusLine(msg)); + if (!message) + message = [NSHTTPURLResponse localizedStringForStatusCode:statusCode]; + else if ([message hasPrefix:@"HTTP/1.1 "]) + message = [message substringFromIndex:9]; + NSString *desc = + [@"Handshake failed: " stringByAppendingString:message]; + NSDictionary *userInfo = @{ + NSLocalizedDescriptionKey : desc, + NSLocalizedFailureReasonErrorKey : message, + PSHTTPStatusErrorKey : @(statusCode), + PSHTTPResponseErrorKey : (__bridge id)msg + }; + *outError = [NSError errorWithDomain:PSWebSocketErrorDomain + code:PSWebSocketErrorCodeHandshakeFailed + userInfo:userInfo]; + } + return -1; + } + + // validate accept + if (![headers[@"Sec-WebSocket-Accept"] + isEqualToString:[self acceptHeaderForKey:_handshakeSecKey]]) { + PSWebSocketSetOutError(outError, PSWebSocketErrorCodeHandshakeFailed, + @"Invalid Sec-WebSocket-Accept"); + return -1; + } + + // validate version + if (headers[@"Sec-WebSocket-Version"] && + ![headers[@"Sec-WebSocket-Version"] isEqualToString:@"13"]) { + PSWebSocketSetOutError(outError, PSWebSocketErrorCodeHandshakeFailed, + @"Invalid Sec-WebSocket-Version"); + return -1; + } + + // validate protocol + _protocol = headers[@"Sec-WebSocket-Protocol"]; + NSString *protocolRequest = + _request.allHTTPHeaderFields[@"Sec-WebSocket-Protocol"]; + if (protocolRequest) { + NSArray *protocolComponents = + [protocolRequest componentsSeparatedByString:@" "]; + if (!_protocol || ![protocolComponents containsObject:_protocol]) { + PSWebSocketSetOutError(outError, PSWebSocketErrorCodeHandshakeFailed, + @"Invalid Sec-WebSocket-Protocol"); + return -1; + } + } + + // extensions + NSOrderedSet *extensionComponents = PSHTTPHeaderFieldValues( + [headers[@"Sec-WebSocket-Extensions"] lowercaseString]); + + // per-message deflate + if (![self + pmdConfigureWithExtensionsHeaderComponents:extensionComponents]) { + PSWebSocketSetOutError( + outError, PSWebSocketErrorCodeHandshakeFailed, + @"permessage-deflate could not negotiate parameters"); + return -1; + } + + // transition state + _state = PSWebSocketDriverStateFrameHeader; + + [_delegate driverDidOpen:self]; + + return preBoundaryLength; + } + // + // FRAME HEADER + // + case PSWebSocketDriverStateFrameHeader: { + NSAssert(maxLength > 0, @"Must have 1 or more bytes"); + + if (maxLength < 2) { + return 0; + } + + const uint8_t *header = (const uint8_t *)bytes; + + BOOL fin = !!(header[0] & PSWebSocketFinMask); + BOOL rsv1 = !!(header[0] & PSWebSocketRsv1Mask); + BOOL rsv2 = !!(header[0] & PSWebSocketRsv2Mask); + BOOL rsv3 = !!(header[0] & PSWebSocketRsv3Mask); + PSWebSocketOpCode opcode = (header[0] & PSWebSocketOpCodeMask); + BOOL masked = !!(header[1] & PSWebSocketMaskMask); + uint64_t payloadLength = (header[1] & PSWebSocketPayloadLenMask); + uint32_t headerExtraLength = (masked) ? sizeof(uint32_t) : 0; + if (payloadLength == 126) { + headerExtraLength += sizeof(uint16_t); + } else if (payloadLength == 127) { + headerExtraLength += sizeof(uint64_t); + } + + // validate opcode + if (!PSWebSocketOpCodeIsValid(opcode)) { + PSWebSocketSetOutError(outError, PSWebSocketStatusCodeProtocolError, + @"Invalid opcode"); + return -1; + } + + // determine if control frame + BOOL control = PSWebSocketOpCodeIsControl(opcode); + + // validate control frame + if (control) { + // control frames must be final + if (!fin) { + PSWebSocketSetOutError(outError, PSWebSocketStatusCodeProtocolError, + @"Control frames must be final"); + return -1; + } + // control frames must not have any reserved bits set + if (rsv1 || rsv2 || rsv3) { + PSWebSocketSetOutError(outError, PSWebSocketStatusCodeProtocolError, + @"Control frames must not use reserved bits"); + return -1; + } + // control frames must not have payload >= 126 + if (payloadLength >= 126) { + PSWebSocketSetOutError(outError, PSWebSocketStatusCodeProtocolError, + @"Control frames payload must < 126"); + return -1; + } + } + // validate data frame + else { + // data continuation frames must follow an initial data frame + if (opcode == PSWebSocketOpCodeContinuation && _frames.count == 0) { + PSWebSocketSetOutError( + outError, PSWebSocketStatusCodeProtocolError, + @"Data continuation frames must follow an initial data frame"); + return -1; + } + // non data continuation frames must not follow an initial data frame + if (opcode != PSWebSocketOpCodeContinuation && _frames.count > 0) { + PSWebSocketSetOutError(outError, PSWebSocketStatusCodeProtocolError, + @"Data frames must not follow an initial data " + @"frame unless continuations"); + return -1; + } + // rsv1 only set if permessage-deflate is enabled + if (rsv1 && !_pmdEnabled) { + PSWebSocketSetOutError(outError, PSWebSocketStatusCodeProtocolError, + @"Data frames must only use rsv1 bit if " + @"permessage-deflate extension is on"); + return -1; + } + // rsv2 and rsv3 must never be set + if (rsv2 || rsv3) { + PSWebSocketSetOutError(outError, PSWebSocketStatusCodeProtocolError, + @"Data frames must never use rsv2 or rsv3 bits"); + return -1; + } + } + + // ensure not masked if client mode + if (masked && _mode == PSWebSocketModeClient) { + PSWebSocketSetOutError(outError, PSWebSocketStatusCodeProtocolError, + @"Frames must never be masked from server"); + return -1; + } + // ensure masked if server mode + if (!masked && _mode == PSWebSocketModeServer) { + PSWebSocketSetOutError(outError, PSWebSocketStatusCodeProtocolError, + @"Frames must always be masked from client"); + return -1; + } + + // create frame + PSWebSocketFrame *frame = [[PSWebSocketFrame alloc] init]; + frame->fin = fin; + frame->rsv1 = rsv1; + frame->rsv2 = rsv2; + frame->rsv3 = rsv3; + frame->opcode = opcode; + frame->masked = masked; + frame->payloadLength = (NSUInteger)payloadLength; + frame->payloadRemainingLength = (NSUInteger)payloadLength; + frame->headerExtraLength = headerExtraLength; + frame->control = control; + frame->pmd = (_pmdEnabled && !control && rsv1); + + if (!frame->control && _frames.count > 0) { + PSWebSocketFrame *lastFrame = [_frames lastObject]; + frame->pmd = (_pmdEnabled && (rsv1 || lastFrame->pmd)); + frame->opcode = lastFrame->opcode; + frame->buffer = lastFrame->buffer; + } else { + frame->buffer = [NSMutableData data]; + } + [_frames addObject:frame]; + + if (headerExtraLength > 0) { + _state = PSWebSocketDriverStateFrameHeaderExtra; + } else if (payloadLength > 0) { + _state = PSWebSocketDriverStateFramePayload; + } else { + _state = PSWebSocketDriverStateFrameHeader; + if (![self processFramesAndDelegate:outError]) { + return -1; + } + } + return 2; + } + // + // FRAME HEADER EXTRA + // + case PSWebSocketDriverStateFrameHeaderExtra: { + NSAssert(maxLength > 0, @"Must have 1 or more bytes"); + + // get current frame + PSWebSocketFrame *frame = [_frames lastObject]; + + // we need at least current frames header extra length + if (maxLength < frame->headerExtraLength) { + return 0; + } + + uint64_t payloadLength = frame->payloadLength; + if (payloadLength == 126) { + payloadLength = EndianU16_BtoN(*(uint16_t *)bytes); + } else if (payloadLength == 127) { + payloadLength = EndianU64_BtoN(*(uint64_t *)bytes); + } + frame->payloadLength = (NSUInteger)payloadLength; + frame->payloadRemainingLength = (NSUInteger)payloadLength; + + if (frame->masked) { + memcpy(frame->maskKey, + (uint8_t *)bytes + (frame->headerExtraLength - sizeof(uint32_t)), + sizeof(uint32_t)); + frame->maskOffset = 0; + } + + if (frame->payloadLength > 0) { + _state = PSWebSocketDriverStateFramePayload; + } else { + _state = PSWebSocketDriverStateFrameHeader; + if (![self processFramesAndDelegate:outError]) { + return -1; + } + } + return frame->headerExtraLength; + } + // + // FRAME PAYLOAD + // + case PSWebSocketDriverStateFramePayload: { + NSAssert(maxLength > 0, @"Must have 1 or more bytes"); + + // get current frame + PSWebSocketFrame *frame = [_frames lastObject]; + + NSUInteger consumeLength = MIN(frame->payloadRemainingLength, maxLength); + NSUInteger offset = frame->buffer.length; + + // unmask bytes if client -> server + if (_mode == PSWebSocketModeServer) { + uint8_t *unmaskedBytes = (uint8_t *)bytes; + uint8_t *maskKey = frame->maskKey; + for (NSInteger i = 0; i < consumeLength; ++i) { + unmaskedBytes[i] = + unmaskedBytes[i] ^ maskKey[frame->maskOffset++ % sizeof(uint32_t)]; + } + } + + // inflate if necessary + if (frame->pmd) { + // reset inflater if we need to + if ((_pmdClientNoContextTakeover && _mode == PSWebSocketModeServer) || + (_pmdServerNoContextTakeover && _mode == PSWebSocketModeClient)) { + [_inflater reset]; + } + + // begin the inflater + if (frame->payloadLength == frame->payloadRemainingLength) { + if (![_inflater begin:frame->buffer error:outError]) { + return -1; } - // - // FRAME HEADER EXTRA - // - case PSWebSocketDriverStateFrameHeaderExtra: { - NSAssert(maxLength > 0, @"Must have 1 or more bytes"); - - // get current frame - PSWebSocketFrame *frame = [_frames lastObject]; - - // we need at least current frames header extra length - if(maxLength < frame->headerExtraLength) { - return 0; - } - - uint64_t payloadLength = frame->payloadLength; - if(payloadLength == 126) { - payloadLength = EndianU16_BtoN(*(uint16_t *)bytes); - } else if(payloadLength == 127) { - payloadLength = EndianU64_BtoN(*(uint64_t *)bytes); - } - frame->payloadLength = (NSUInteger)payloadLength; - frame->payloadRemainingLength = (NSUInteger)payloadLength; - - if(frame->masked) { - memcpy(frame->maskKey, (uint8_t *)bytes + (frame->headerExtraLength - sizeof(uint32_t)), sizeof(uint32_t)); - frame->maskOffset = 0; - } - - if(frame->payloadLength > 0) { - _state = PSWebSocketDriverStateFramePayload; - } else { - _state = PSWebSocketDriverStateFrameHeader; - if(![self processFramesAndDelegate:outError]) { - return -1; - } - } - return frame->headerExtraLength; + } + + // inflate bytes + if (![_inflater appendBytes:bytes length:consumeLength error:outError]) { + return -1; + } + + // end inflater + if (frame->fin && frame->payloadRemainingLength == consumeLength) { + if (![_inflater end:outError]) { + return -1; } - // - // FRAME PAYLOAD - // - case PSWebSocketDriverStateFramePayload: { - NSAssert(maxLength > 0, @"Must have 1 or more bytes"); - - // get current frame - PSWebSocketFrame *frame = [_frames lastObject]; - - NSUInteger consumeLength = MIN(frame->payloadRemainingLength, maxLength); - NSUInteger offset = frame->buffer.length; - - // unmask bytes if client -> server - if(_mode == PSWebSocketModeServer) { - uint8_t *unmaskedBytes = (uint8_t *)bytes; - uint8_t *maskKey = frame->maskKey; - for(NSInteger i = 0; i < consumeLength; ++i) { - unmaskedBytes[i] = unmaskedBytes[i] ^ maskKey[frame->maskOffset++ % sizeof(uint32_t)]; - } - } - - // inflate if necessary - if(frame->pmd) { - // reset inflater if we need to - if((_pmdClientNoContextTakeover && _mode == PSWebSocketModeServer) || - (_pmdServerNoContextTakeover && _mode == PSWebSocketModeClient)) { - [_inflater reset]; - } - - // begin the inflater - if(frame->payloadLength == frame->payloadRemainingLength) { - if(![_inflater begin:frame->buffer error:outError]) { - return -1; - } - } - - // inflate bytes - if(![_inflater appendBytes:bytes length:consumeLength error:outError]) { - return -1; - } - - // end inflater - if(frame->fin && frame->payloadRemainingLength == consumeLength) { - if(![_inflater end:outError]) { - return -1; - } - } - } - // otherwise append - else { - [frame->buffer appendBytes:bytes length:consumeLength]; - } - - // validate utf-8 if necessary - if(frame->opcode == PSWebSocketOpCodeText) { - uint8_t *bytes = (uint8_t *)(frame->buffer.bytes + offset); - for(NSUInteger i = 0; i < frame->buffer.length - offset; ++i) { - // get validation result - PSWebSocketUTF8DecoderDecode(&_utf8DecoderState, &_utf8DecoderCodePoint, *(bytes + i)); - - // read bad code point - if(_utf8DecoderState == PSWebSocketUTF8DecoderReject) { - PSWebSocketSetOutError(outError, PSWebSocketStatusCodeInvalidUTF8, @"Invalid UTF-8"); - return -1; - } - } - - // need more bytes & no data will be left - if(_utf8DecoderState > 1 && frame->fin && frame->payloadRemainingLength - consumeLength == 0) { - PSWebSocketSetOutError(outError, PSWebSocketStatusCodeInvalidUTF8, @"Invalid UTF-8"); - return -1; - } - } - - // remove consumed length from remaining payload length - frame->payloadRemainingLength -= consumeLength; - - if(frame->payloadRemainingLength == 0) { - _state = PSWebSocketDriverStateFrameHeader; - if(![self processFramesAndDelegate:outError]) { - return -1; - } - } - return consumeLength; + } + } + // otherwise append + else { + [frame->buffer appendBytes:bytes length:consumeLength]; + } + + // validate utf-8 if necessary + if (frame->opcode == PSWebSocketOpCodeText) { + uint8_t *bytes = (uint8_t *)(frame->buffer.bytes + offset); + for (NSUInteger i = 0; i < frame->buffer.length - offset; ++i) { + // get validation result + PSWebSocketUTF8DecoderDecode(&_utf8DecoderState, &_utf8DecoderCodePoint, + *(bytes + i)); + + // read bad code point + if (_utf8DecoderState == PSWebSocketUTF8DecoderReject) { + PSWebSocketSetOutError(outError, PSWebSocketStatusCodeInvalidUTF8, + @"Invalid UTF-8"); + return -1; } - default: - return 0; + } + + // need more bytes & no data will be left + if (_utf8DecoderState > 1 && frame->fin && + frame->payloadRemainingLength - consumeLength == 0) { + PSWebSocketSetOutError(outError, PSWebSocketStatusCodeInvalidUTF8, + @"Invalid UTF-8"); + return -1; + } + } + + // remove consumed length from remaining payload length + frame->payloadRemainingLength -= consumeLength; + + if (frame->payloadRemainingLength == 0) { + _state = PSWebSocketDriverStateFrameHeader; + if (![self processFramesAndDelegate:outError]) { + return -1; + } } + return consumeLength; + } + default: return 0; + } + return 0; } - (BOOL)processFramesAndDelegate:(NSError *__autoreleasing *)outError { - // get current frame - PSWebSocketFrame *frame = [_frames lastObject]; - - // skip if not final - if(!frame->fin) { - return YES; - } - - // close off pmd for zero-length frames that have a buffer otherwise they are orphaned - if (frame->pmd && frame->payloadLength == 0 && frame->buffer.length > 0) { - if (![_inflater end:outError]) { - return -1; - } + // get current frame + PSWebSocketFrame *frame = [_frames lastObject]; + + // skip if not final + if (!frame->fin) { + return YES; + } + + // close off pmd for zero-length frames that have a buffer otherwise they are + // orphaned + if (frame->pmd && frame->payloadLength == 0 && frame->buffer.length > 0) { + if (![_inflater end:outError]) { + return -1; } - - // remove frames - if(frame->control) { - [_frames removeLastObject]; - } else { - [_frames removeAllObjects]; - _utf8DecoderState = 0; - _utf8DecoderCodePoint = 0; - } - - switch(frame->opcode) { - case PSWebSocketOpCodeBinary: - [_delegate driver:self didReceiveMessage:frame->buffer]; - break; - case PSWebSocketOpCodePong: - [_delegate driver:self didReceivePong:frame->buffer]; - break; - case PSWebSocketOpCodePing: { - [_delegate driver:self didReceivePing:frame->buffer]; - break; - } - case PSWebSocketOpCodeText: { - NSString *utf8 = [[NSString alloc] initWithData:frame->buffer encoding:NSUTF8StringEncoding]; - if(!utf8) { - PSWebSocketSetOutError(outError, PSWebSocketStatusCodeInvalidUTF8, @"Invalid UTF-8"); - return NO; - } - [_delegate driver:self didReceiveMessage:utf8]; - break; + } + + // remove frames + if (frame->control) { + [_frames removeLastObject]; + } else { + [_frames removeAllObjects]; + _utf8DecoderState = 0; + _utf8DecoderCodePoint = 0; + } + + switch (frame->opcode) { + case PSWebSocketOpCodeBinary: + [_delegate driver:self didReceiveMessage:frame->buffer]; + break; + case PSWebSocketOpCodePong: + [_delegate driver:self didReceivePong:frame->buffer]; + break; + case PSWebSocketOpCodePing: { + [_delegate driver:self didReceivePing:frame->buffer]; + break; + } + case PSWebSocketOpCodeText: { + NSString *utf8 = [[NSString alloc] initWithData:frame->buffer + encoding:NSUTF8StringEncoding]; + if (!utf8) { + PSWebSocketSetOutError(outError, PSWebSocketStatusCodeInvalidUTF8, + @"Invalid UTF-8"); + return NO; + } + [_delegate driver:self didReceiveMessage:utf8]; + break; + } + case PSWebSocketOpCodeClose: + if (frame->buffer.length >= 2) { + uint16_t closeCode = 0; + [frame->buffer getBytes:&closeCode length:sizeof(closeCode)]; + closeCode = EndianU16_BtoN(closeCode); + if (!PSWebSocketCloseCodeIsValid(closeCode)) { + PSWebSocketSetOutError(outError, PSWebSocketStatusCodeProtocolError, + @"Invalid close code"); + return NO; + } + NSString *reason = nil; + if (frame->buffer.length > 2) { + reason = [[NSString alloc] + initWithBytes:frame->buffer.mutableBytes + sizeof(uint16_t) + length:frame->buffer.length - sizeof(uint16_t) + encoding:NSUTF8StringEncoding]; + if (!reason) { + PSWebSocketSetOutError(outError, PSWebSocketStatusCodeProtocolError, + @"Invalid close reason; must be UTF-8"); + return NO; } - case PSWebSocketOpCodeClose: - if(frame->buffer.length >= 2) { - uint16_t closeCode = 0; - [frame->buffer getBytes:&closeCode length:sizeof(closeCode)]; - closeCode = EndianU16_BtoN(closeCode); - if(!PSWebSocketCloseCodeIsValid(closeCode)) { - PSWebSocketSetOutError(outError, PSWebSocketStatusCodeProtocolError, @"Invalid close code"); - return NO; - } - NSString *reason = nil; - if(frame->buffer.length > 2) { - reason = [[NSString alloc] initWithBytes:frame->buffer.mutableBytes + sizeof(uint16_t) - length:frame->buffer.length - sizeof(uint16_t) - encoding:NSUTF8StringEncoding]; - if(!reason) { - PSWebSocketSetOutError(outError, PSWebSocketStatusCodeProtocolError, @"Invalid close reason; must be UTF-8"); - return NO; - } - } - [_delegate driver:self didCloseWithCode:closeCode reason:reason]; - } else if(frame->buffer.length >= 1) { - PSWebSocketSetOutError(outError, PSWebSocketStatusCodeProtocolError, @"Invalid close payload"); - return NO; - } else { - [_delegate driver:self didCloseWithCode:PSWebSocketStatusCodeNoStatusReceived reason:nil]; - } - break; - case PSWebSocketOpCodeContinuation: - return YES; - } - + } + [_delegate driver:self didCloseWithCode:closeCode reason:reason]; + } else if (frame->buffer.length >= 1) { + PSWebSocketSetOutError(outError, PSWebSocketStatusCodeProtocolError, + @"Invalid close payload"); + return NO; + } else { + [_delegate driver:self + didCloseWithCode:PSWebSocketStatusCodeNoStatusReceived + reason:nil]; + } + break; + case PSWebSocketOpCodeContinuation: return YES; + } + + return YES; } #pragma mark - Erroring -+ (NSError*)errorWithCode:(NSInteger)code reason:(NSString *)reason { - if (reason == nil) { - static NSString* const kStatusNames[] = { - @"Normal", - @"Going Away", - @"Protocol Error", - @"Unhandled Type", - nil,// 1004 reserved - @"No Status Received", - nil,// 1006 reserved - @"Invalid UTF-8", - @"Policy Violated", - @"Message Too Big" - }; - if (code >= PSWebSocketStatusCodeNormal && code <= PSWebSocketStatusCodeMessageTooBig) { - reason = kStatusNames[code - PSWebSocketStatusCodeNormal]; - } ++ (NSError *)errorWithCode:(NSInteger)code reason:(NSString *)reason { + if (reason == nil) { + static NSString *const kStatusNames[] = { + @"Normal", + @"Going Away", + @"Protocol Error", + @"Unhandled Type", + nil, // 1004 reserved + @"No Status Received", + nil, // 1006 reserved + @"Invalid UTF-8", + @"Policy Violated", + @"Message Too Big" + }; + if (code >= PSWebSocketStatusCodeNormal && + code <= PSWebSocketStatusCodeMessageTooBig) { + reason = kStatusNames[code - PSWebSocketStatusCodeNormal]; } - NSDictionary *userInfo = reason ? @{NSLocalizedDescriptionKey: reason} : nil; - return [NSError errorWithDomain:PSWebSocketErrorDomain code:code userInfo:userInfo]; + } + NSDictionary *userInfo = reason ? @{NSLocalizedDescriptionKey : reason} : nil; + return [NSError errorWithDomain:PSWebSocketErrorDomain + code:code + userInfo:userInfo]; } - (void)failWithErrorCode:(NSInteger)code reason:(NSString *)reason { - [self failWithError: [[self class] errorWithCode:code reason:reason]]; + [self failWithError:[[self class] errorWithCode:code reason:reason]]; } - (void)failWithError:(NSError *)error { - NSParameterAssert(error); - _failed = YES; - [_delegate driver:self didFailWithError:error]; + NSParameterAssert(error); + _failed = YES; + [_delegate driver:self didFailWithError:error]; } #pragma mark - permessage-deflate - (NSArray *)pmdExtensionsHeaderComponents { - if(_pmdEnabled) { - NSMutableArray *components = [NSMutableArray arrayWithObject:@"permessage-deflate"]; - - // client mode - if(_mode == PSWebSocketModeClient) { - // say we'll take whatever window bits the server gives us - [components addObject:@"client_max_window_bits"]; - } - // server mode - else if(_mode == PSWebSocketModeServer) { - // set the window bits the client must use - [components addObject:[NSString stringWithFormat:@"client_max_window_bits=%@", @(-_pmdClientWindowBits)]]; - - // set the window bits the server will use - [components addObject:[NSString stringWithFormat:@"server_max_window_bits=%@", @(-_pmdServerWindowBits)]]; - } - return components; + if (_pmdEnabled) { + NSMutableArray *components = + [NSMutableArray arrayWithObject:@"permessage-deflate"]; + + // client mode + if (_mode == PSWebSocketModeClient) { + // say we'll take whatever window bits the server gives us + [components addObject:@"client_max_window_bits"]; } - return @[]; + // server mode + else if (_mode == PSWebSocketModeServer) { + // set the window bits the client must use + [components + addObject:[NSString stringWithFormat:@"client_max_window_bits=%@", + @(-_pmdClientWindowBits)]]; + + // set the window bits the server will use + [components + addObject:[NSString stringWithFormat:@"server_max_window_bits=%@", + @(-_pmdServerWindowBits)]]; + } + return components; + } + return @[]; } - (BOOL)pmdConfigureWithExtensionsHeaderComponents:(NSOrderedSet *)components { - _pmdEnabled = NO; - _pmdClientWindowBits = -15; - _pmdClientNoContextTakeover = NO; - _pmdServerWindowBits = -15; - _pmdServerNoContextTakeover = NO; - - for(NSString *component in components) { - // split to key & value - NSArray *subcomponents = [component componentsSeparatedByString:@"="]; - - if([subcomponents[0] isEqualToString:@"permessage-deflate"]) { - _pmdEnabled = YES; - } else if([subcomponents[0] isEqualToString:@"client_max_window_bits"] && subcomponents.count > 1) { - _pmdClientWindowBits = -[subcomponents[1] integerValue]; - } else if([subcomponents[0] isEqualToString:@"server_max_window_bits"] && subcomponents.count > 1) { - _pmdServerWindowBits = -[subcomponents[1] integerValue]; - } else if([subcomponents[0] isEqualToString:@"client_no_context_takeover"] && _mode == PSWebSocketModeClient) { - _pmdClientNoContextTakeover = YES; - } else if([subcomponents[0] isEqualToString:@"server_no_context_takeover"] && _mode == PSWebSocketModeClient) { - _pmdServerNoContextTakeover = YES; - } - } - - if(_pmdClientWindowBits > -8 || _pmdClientWindowBits < -15) { - return NO; - } - if(_pmdServerWindowBits > -8 || _pmdServerWindowBits < -15) { - return NO; + _pmdEnabled = NO; + _pmdClientWindowBits = -15; + _pmdClientNoContextTakeover = NO; + _pmdServerWindowBits = -15; + _pmdServerNoContextTakeover = NO; + + for (NSString *component in components) { + // split to key & value + NSArray *subcomponents = [component componentsSeparatedByString:@"="]; + + if ([subcomponents[0] isEqualToString:@"permessage-deflate"]) { + _pmdEnabled = YES; + } else if ([subcomponents[0] isEqualToString:@"client_max_window_bits"] && + subcomponents.count > 1) { + _pmdClientWindowBits = -[subcomponents[1] integerValue]; + } else if ([subcomponents[0] isEqualToString:@"server_max_window_bits"] && + subcomponents.count > 1) { + _pmdServerWindowBits = -[subcomponents[1] integerValue]; + } else if ([subcomponents[0] + isEqualToString:@"client_no_context_takeover"] && + _mode == PSWebSocketModeClient) { + _pmdClientNoContextTakeover = YES; + } else if ([subcomponents[0] + isEqualToString:@"server_no_context_takeover"] && + _mode == PSWebSocketModeClient) { + _pmdServerNoContextTakeover = YES; } - - if (_pmdEnabled) { - if(_mode == PSWebSocketModeClient) { - _inflater = [[PSWebSocketInflater alloc] initWithWindowBits:_pmdServerWindowBits]; - _deflater = [[PSWebSocketDeflater alloc] initWithWindowBits:_pmdClientWindowBits memoryLevel:8]; - } else { - _inflater = [[PSWebSocketInflater alloc] initWithWindowBits:_pmdClientWindowBits]; - _deflater = [[PSWebSocketDeflater alloc] initWithWindowBits:_pmdServerWindowBits memoryLevel:8]; - } + } + + if (_pmdClientWindowBits > -8 || _pmdClientWindowBits < -15) { + return NO; + } + if (_pmdServerWindowBits > -8 || _pmdServerWindowBits < -15) { + return NO; + } + + if (_pmdEnabled) { + if (_mode == PSWebSocketModeClient) { + _inflater = + [[PSWebSocketInflater alloc] initWithWindowBits:_pmdServerWindowBits]; + _deflater = + [[PSWebSocketDeflater alloc] initWithWindowBits:_pmdClientWindowBits + memoryLevel:8]; + } else { + _inflater = + [[PSWebSocketInflater alloc] initWithWindowBits:_pmdClientWindowBits]; + _deflater = + [[PSWebSocketDeflater alloc] initWithWindowBits:_pmdServerWindowBits + memoryLevel:8]; } - - return YES; + } + + return YES; } #pragma mark - Utilities - (NSString *)acceptHeaderForKey:(NSString *)key { - NSParameterAssert(key); - NSString *combined = [key stringByAppendingString:PSWebSocketGUID]; - NSData *data = [combined dataUsingEncoding:NSUTF8StringEncoding]; - unsigned char sha1[CC_SHA1_DIGEST_LENGTH]; - CC_SHA1(data.bytes, (CC_LONG)data.length, sha1); - data = [NSData dataWithBytes:sha1 length:CC_SHA1_DIGEST_LENGTH]; - return [self base64EncodedData:data]; + NSParameterAssert(key); + NSString *combined = [key stringByAppendingString:PSWebSocketGUID]; + NSData *data = [combined dataUsingEncoding:NSUTF8StringEncoding]; + unsigned char sha1[CC_SHA1_DIGEST_LENGTH]; + CC_SHA1(data.bytes, (CC_LONG)data.length, sha1); + data = [NSData dataWithBytes:sha1 length:CC_SHA1_DIGEST_LENGTH]; + return [self base64EncodedData:data]; } - (NSString *)base64EncodedData:(NSData *)data { - // if we're targeting deployment before OS X 10.9 or IOS 7 the public methods for base 64 encoding didn't exist - // however, a more basic private API ( which is now also public but deprecated ) did exist so we'll use it instead -#if (TARGET_OS_MAC && __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_9) || (TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0) - if(![data respondsToSelector:@selector(base64EncodedStringWithOptions:)]) { +// if we're targeting deployment before OS X 10.9 or IOS 7 the public methods +// for base 64 encoding didn't exist +// however, a more basic private API ( which is now also public but deprecated ) +// did exist so we'll use it instead +#if (TARGET_OS_MAC && __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_9) || \ + (TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0) + if (![data respondsToSelector:@selector(base64EncodedStringWithOptions:)]) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [data base64Encoding]; + return [data base64Encoding]; #pragma clang diagnostic pop - } + } #endif - return [data base64EncodedStringWithOptions:0]; + return [data base64EncodedStringWithOptions:0]; } @end diff --git a/PocketSocket/PSWebSocketServer.h b/PocketSocket/PSWebSocketServer.h index 7e1e452..8fecc07 100644 --- a/PocketSocket/PSWebSocketServer.h +++ b/PocketSocket/PSWebSocketServer.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#import #import "PSWebSocket.h" +#import @class PSWebSocketServer; @@ -25,29 +25,55 @@ - (void)server:(PSWebSocketServer *)server didFailWithError:(NSError *)error; - (void)serverDidStop:(PSWebSocketServer *)server; -- (void)server:(PSWebSocketServer *)server webSocketDidOpen:(PSWebSocket *)webSocket; -- (void)server:(PSWebSocketServer *)server webSocket:(PSWebSocket *)webSocket didReceiveMessage:(id)message; -- (void)server:(PSWebSocketServer *)server webSocket:(PSWebSocket *)webSocket didFailWithError:(NSError *)error; -- (void)server:(PSWebSocketServer *)server webSocket:(PSWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean; +- (void)server:(PSWebSocketServer *)server + webSocketDidOpen:(PSWebSocket *)webSocket; +- (void)server:(PSWebSocketServer *)server + webSocket:(PSWebSocket *)webSocket + didReceiveMessage:(id)message; +- (void)server:(PSWebSocketServer *)server + webSocket:(PSWebSocket *)webSocket + didFailWithError:(NSError *)error; +- (void)server:(PSWebSocketServer *)server + webSocket:(PSWebSocket *)webSocket + didCloseWithCode:(NSInteger)code + reason:(NSString *)reason + wasClean:(BOOL)wasClean; @optional -- (void)server:(PSWebSocketServer *)server webSocketDidFlushInput:(PSWebSocket *)webSocket; -- (void)server:(PSWebSocketServer *)server webSocketDidFlushOutput:(PSWebSocket *)webSocket; -- (BOOL)server:(PSWebSocketServer *)server acceptWebSocketWithRequest:(NSURLRequest *)request; -- (BOOL)server:(PSWebSocketServer *)server acceptWebSocketWithRequest:(NSURLRequest *)request address:(NSData *)address trust:(SecTrustRef)trust response:(NSHTTPURLResponse **)response; +- (void)server:(PSWebSocketServer *)server + webSocketDidFlushInput:(PSWebSocket *)webSocket; +- (void)server:(PSWebSocketServer *)server + webSocketDidFlushOutput:(PSWebSocket *)webSocket; +- (BOOL)server:(PSWebSocketServer *)server + acceptWebSocketWithRequest:(NSURLRequest *)request; +- (BOOL)server:(PSWebSocketServer *)server + acceptWebSocketWithRequest:(NSURLRequest *)request + address:(NSData *)address + trust:(SecTrustRef)trust + response:(NSHTTPURLResponse **)response; @end @interface PSWebSocketServer : NSObject #pragma mark - Properties -@property (nonatomic, weak) id delegate; -@property (nonatomic, strong) dispatch_queue_t delegateQueue; +@property(nonatomic, weak) id delegate; +@property(nonatomic) dispatch_queue_t delegateQueue; +/// Expose the current port if the server started with a dynamic port +@property(nonatomic, readonly) NSInteger port; #pragma mark - Initialization - -+ (instancetype)serverWithHost:(NSString *)host port:(NSUInteger)port; -+ (instancetype)serverWithHost:(NSString *)host port:(NSUInteger)port SSLCertificates:(NSArray *)SSLCertificates; +/** + Returns a local server with a dynamic port assigned by the sytem + */ ++ (instancetype)localServer; +/** + Set port to 0 to use a dynamic port assigned by the system. + */ ++ (instancetype)serverWithHost:(NSString *)host port:(NSInteger)port; ++ (instancetype)serverWithHost:(NSString *)host + port:(NSInteger)port + SSLCertificates:(NSArray *)SSLCertificates; #pragma mark - Actions diff --git a/PocketSocket/PSWebSocketServer.m b/PocketSocket/PSWebSocketServer.m index c8ae95b..60657b4 100644 --- a/PocketSocket/PSWebSocketServer.m +++ b/PocketSocket/PSWebSocketServer.m @@ -12,682 +12,803 @@ // See the License for the specific language governing permissions and // limitations under the License. -#import "PSWebSocketServer.h" -#import "PSwebSocket.h" +#import "PSWebSocketBuffer.h" #import "PSWebSocketDriver.h" #import "PSWebSocketInternal.h" -#import "PSWebSocketBuffer.h" #import "PSWebSocketNetworkThread.h" +#import "PSWebSocketServer.h" +#import "PSwebSocket.h" #import +#import +#import +#import #import #import -#import -#import #import -#import -#import +#import typedef NS_ENUM(NSInteger, PSWebSocketServerConnectionReadyState) { - PSWebSocketServerConnectionReadyStateConnecting = 0, - PSWebSocketServerConnectionReadyStateOpen, - PSWebSocketServerConnectionReadyStateClosing, - PSWebSocketServerConnectionReadyStateClosed + PSWebSocketServerConnectionReadyStateConnecting = 0, + PSWebSocketServerConnectionReadyStateOpen, + PSWebSocketServerConnectionReadyStateClosing, + PSWebSocketServerConnectionReadyStateClosed }; @interface PSWebSocketServerConnection : NSObject -@property (nonatomic, strong, readonly) NSString *identifier; -@property (nonatomic, assign) PSWebSocketServerConnectionReadyState readyState; -@property (nonatomic, strong) NSInputStream *inputStream; -@property (nonatomic, strong) NSOutputStream *outputStream; -@property (nonatomic, assign) BOOL inputStreamOpenCompleted; -@property (nonatomic, assign) BOOL outputStreamOpenCompleted; -@property (nonatomic, strong) PSWebSocketBuffer *inputBuffer; -@property (nonatomic, strong) PSWebSocketBuffer *outputBuffer; +@property(nonatomic, strong, readonly) NSString *identifier; +@property(nonatomic, assign) PSWebSocketServerConnectionReadyState readyState; +@property(nonatomic, strong) NSInputStream *inputStream; +@property(nonatomic, strong) NSOutputStream *outputStream; +@property(nonatomic, assign) BOOL inputStreamOpenCompleted; +@property(nonatomic, assign) BOOL outputStreamOpenCompleted; +@property(nonatomic, strong) PSWebSocketBuffer *inputBuffer; +@property(nonatomic, strong) PSWebSocketBuffer *outputBuffer; @end @implementation PSWebSocketServerConnection - (instancetype)init { - if((self = [super init])) { - _identifier = [[NSProcessInfo processInfo] globallyUniqueString]; - _readyState = PSWebSocketServerConnectionReadyStateConnecting; - _inputBuffer = [[PSWebSocketBuffer alloc] init]; - _outputBuffer = [[PSWebSocketBuffer alloc] init]; - } - return self; + if ((self = [super init])) { + _identifier = [[NSProcessInfo processInfo] globallyUniqueString]; + _readyState = PSWebSocketServerConnectionReadyStateConnecting; + _inputBuffer = [[PSWebSocketBuffer alloc] init]; + _outputBuffer = [[PSWebSocketBuffer alloc] init]; + } + return self; } @end +void PSWebSocketServerAcceptCallback(CFSocketRef s, CFSocketCallBackType type, + CFDataRef address, const void *data, + void *info); -void PSWebSocketServerAcceptCallback(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info); - -@interface PSWebSocketServer() { - dispatch_queue_t _workQueue; - - NSArray *_SSLCertificates; - BOOL _secure; - - NSData *_addrData; - CFSocketContext _socketContext; - - BOOL _running; - CFSocketRef _socket; - CFRunLoopSourceRef _socketRunLoopSource; - - NSMutableSet *_connections; - NSMapTable *_connectionsByStreams; - - NSMutableSet *_webSockets; -} +@interface PSWebSocketServer () +@property(nonatomic) dispatch_queue_t workQueue; +@property(nonatomic) NSData *addrData; +@property(nonatomic) CFSocketContext socketContext; +@property(nonatomic) NSArray *SSLCertificates; + +@property(nonatomic) BOOL running; +@property(nonatomic) BOOL secure; +@property(nonatomic) CFSocketRef socket; +@property(nonatomic) CFRunLoopSourceRef socketRunLoopSource; + +@property(nonatomic) NSMutableSet *connections; +@property(nonatomic) NSMapTable *connectionsByStreams; + +@property(nonatomic) NSMutableSet *webSockets; + +@property(nonatomic) NSInteger port; @end + @implementation PSWebSocketServer #pragma mark - Properties - (NSRunLoop *)runLoop { - return [[PSWebSocketNetworkThread sharedNetworkThread] runLoop]; + return [[PSWebSocketNetworkThread sharedNetworkThread] runLoop]; } #pragma mark - Initialization - -+ (instancetype)serverWithHost:(NSString *)host port:(NSUInteger)port { - return [[self alloc] initWithHost:host port:port SSLCertificates:nil]; -} -+ (instancetype)serverWithHost:(NSString *)host port:(NSUInteger)port SSLCertificates:(NSArray *)SSLCertificates { - return [[self alloc] initWithHost:host port:port SSLCertificates:SSLCertificates]; -} -- (instancetype)initWithHost:(NSString *)host port:(NSUInteger)port SSLCertificates:(NSArray *)SSLCertificates { - NSParameterAssert(port); - if((self = [super init])) { - _workQueue = dispatch_queue_create(nil, nil); - - // copy SSL certificates - _SSLCertificates = [SSLCertificates copy]; - _secure = (_SSLCertificates != nil); - - // create addr data - struct sockaddr_in addr; - memset(&addr, 0, sizeof(addr)); - addr.sin_len = sizeof(addr); - addr.sin_family = AF_INET; - if(host && ![host isEqualToString:@"0.0.0.0"]) { - addr.sin_addr.s_addr = inet_addr(host.UTF8String); - if(!addr.sin_addr.s_addr) { - [NSException raise:@"Invalid host" format:@"Could not formulate internet address from host: %@", host]; - return nil; - } - } else { - addr.sin_addr.s_addr = htonl(INADDR_ANY); - } - addr.sin_port = htons(port); - _addrData = [NSData dataWithBytes:&addr length:sizeof(addr)]; - - // create socket context - _socketContext = (CFSocketContext){0, (__bridge void *)self, NULL, NULL, NULL}; - - _connections = [NSMutableSet set]; - _connectionsByStreams = [NSMapTable weakToWeakObjectsMapTable]; - - _webSockets = [NSMutableSet set]; - ++ (instancetype)localServer { + return [self.class serverWithHost:@"127.0.0.1" port:0]; +} + ++ (instancetype)serverWithHost:(NSString *)host port:(NSInteger)port { + return [[self alloc] initWithHost:host port:port SSLCertificates:nil]; +} + ++ (instancetype)serverWithHost:(NSString *)host + port:(NSInteger)port + SSLCertificates:(NSArray *)SSLCertificates { + return [[self alloc] initWithHost:host + port:port + SSLCertificates:SSLCertificates]; +} +- (instancetype)initWithHost:(NSString *)host + port:(NSInteger)port + SSLCertificates:(NSArray *)SSLCertificates { + if ((self = [super init])) { + _workQueue = dispatch_queue_create(nil, nil); + + // copy SSL certificates + _SSLCertificates = [SSLCertificates copy]; + _secure = (_SSLCertificates != nil); + _port = port; + // create addr data + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_len = sizeof(addr); + addr.sin_family = AF_INET; + if (host && host.length && ![host isEqualToString:@"0.0.0.0"]) { + addr.sin_addr.s_addr = inet_addr(host.UTF8String); + if (!addr.sin_addr.s_addr) { + [NSException + raise:@"Invalid host" + format:@"Could not formulate internet address from host: %@", host]; + return nil; + } + } else { + addr.sin_addr.s_addr = htonl(INADDR_ANY); } - return self; + addr.sin_port = htons(port); + _addrData = [NSData dataWithBytes:&addr length:sizeof(addr)]; + + // create socket context + _socketContext = + (CFSocketContext){0, (__bridge void *)self, NULL, NULL, NULL}; + + _connections = [NSMutableSet set]; + _connectionsByStreams = [NSMapTable weakToWeakObjectsMapTable]; + + _webSockets = [NSMutableSet set]; + } + return self; } #pragma mark - Actions - (void)start { - [self executeWork:^{ - [self connect:NO]; - }]; + __weak typeof(self) wself = self; + [self executeWork:^{ + __strong typeof(self) sself = wself; + [sself connect:NO]; + }]; } - (void)stop { - [self executeWork:^{ - [self disconnectGracefully:NO]; - }]; + __weak typeof(self) wself = self; + [self executeWork:^{ + __strong typeof(self) sself = wself; + [sself disconnectGracefully:NO]; + }]; } #pragma mark - Connection - (void)connect:(BOOL)silent { - if(_running) { - return; + if (_running) { + return; + } + + // create socket + _socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, + IPPROTO_TCP, kCFSocketAcceptCallBack, + PSWebSocketServerAcceptCallback, &_socketContext); + // configure socket + int yes = 1; + setsockopt(CFSocketGetNative(_socket), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, + sizeof(yes)); + + // bind + CFSocketError err = + CFSocketSetAddress(_socket, (__bridge CFDataRef)_addrData); + if (err == kCFSocketError) { + if (!silent) { + [self notifyDelegateFailedToStart:[NSError + errorWithDomain:NSPOSIXErrorDomain + code:errno + userInfo:nil]]; } - - // create socket - _socket = CFSocketCreate(kCFAllocatorDefault, - PF_INET, - SOCK_STREAM, - IPPROTO_TCP, - kCFSocketAcceptCallBack, - PSWebSocketServerAcceptCallback, - &_socketContext); - // configure socket - int yes = 1; - setsockopt(CFSocketGetNative(_socket), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes)); - - // bind - CFSocketError err = CFSocketSetAddress(_socket, (__bridge CFDataRef)_addrData); - if(err == kCFSocketError) { - if(!silent) { - [self notifyDelegateFailedToStart:[NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]]; - } - return; - } else if(err == kCFSocketTimeout) { - if(!silent) { - [self notifyDelegateFailedToStart:[NSError errorWithDomain:NSPOSIXErrorDomain code:ETIME userInfo:nil]]; - } - return; + return; + } else if (err == kCFSocketTimeout) { + if (!silent) { + [self notifyDelegateFailedToStart:[NSError + errorWithDomain:NSPOSIXErrorDomain + code:ETIME + userInfo:nil]]; } - - // schedule - _socketRunLoopSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socket, 0); - - CFRunLoopRef runLoop = [[self runLoop] getCFRunLoop]; - CFRunLoopAddSource(runLoop, _socketRunLoopSource, kCFRunLoopDefaultMode); - - _running = YES; - - if(!silent) { - [self notifyDelegateDidStart]; + return; + } + + // Get socket port in case we use a dynamic port + if (self.port == 0) { + struct sockaddr_in sin; + bzero(&sin, sizeof(struct sockaddr_in)); + int addrlen = sizeof(sin); + if (getsockname(CFSocketGetNative(_socket), (struct sockaddr *)&sin, + &addrlen) == 0) { + self.port = ntohs(sin.sin_port); } + } + + // schedule + _socketRunLoopSource = + CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socket, 0); + + CFRunLoopRef runLoop = [[self runLoop] getCFRunLoop]; + CFRunLoopAddSource(runLoop, _socketRunLoopSource, kCFRunLoopDefaultMode); + + _running = YES; + + if (!silent) { + [self notifyDelegateDidStart]; + } } - (void)disconnectGracefully:(BOOL)silent { - if(!_running) { - return; - } - - for(PSWebSocketServerConnection *connection in _connections.allObjects) { - [self disconnectConnectionGracefully:connection statusCode:500 description:@"Service Going Away" headers: nil]; - } - for(PSWebSocket *webSocket in _webSockets.allObjects) { - [webSocket close]; - } - - [self pumpOutput]; - - // disconnect - [self executeWork:^{ - [self disconnect:silent]; - }]; - - _running = NO; + if (!_running) { + return; + } + + for (PSWebSocketServerConnection *connection in _connections.allObjects) { + [self disconnectConnectionGracefully:connection + statusCode:500 + description:@"Service Going Away" + headers:nil]; + } + for (PSWebSocket *webSocket in _webSockets.allObjects) { + [webSocket close]; + } + + [self pumpOutput]; + + // disconnect + __weak typeof(self) wself = self; + [self executeWork:^{ + __strong typeof(self) sself = wself; + [sself disconnect:silent]; + }]; + + _running = NO; } - (void)disconnect:(BOOL)silent { - if(_socketRunLoopSource) { - CFRunLoopRef runLoop = [[self runLoop] getCFRunLoop]; - CFRunLoopRemoveSource(runLoop, _socketRunLoopSource, kCFRunLoopDefaultMode); - CFRelease(_socketRunLoopSource); - _socketRunLoopSource = nil; - } - - if(_socket) { - if(CFSocketIsValid(_socket)) { - CFSocketInvalidate(_socket); - } - CFRelease(_socket); - _socket = nil; - } - - _running = NO; - - if(!silent) { - [self notifyDelegateDidStop]; + if (_socketRunLoopSource) { + CFRunLoopRef runLoop = [[self runLoop] getCFRunLoop]; + CFRunLoopRemoveSource(runLoop, _socketRunLoopSource, kCFRunLoopDefaultMode); + CFRelease(_socketRunLoopSource); + _socketRunLoopSource = nil; + } + + if (_socket) { + if (CFSocketIsValid(_socket)) { + CFSocketInvalidate(_socket); } + CFRelease(_socket); + _socket = nil; + } + + _running = NO; + + if (!silent) { + [self notifyDelegateDidStop]; + } } #pragma mark - Accepting - (void)accept:(CFSocketNativeHandle)handle { - [self executeWork:^{ - // create streams - CFReadStreamRef readStream = nil; - CFWriteStreamRef writeStream = nil; - CFStreamCreatePairWithSocket(kCFAllocatorDefault, handle, &readStream, &writeStream); - - // fail if we couldn't get streams - if(!readStream || !writeStream) { - return; - } - - // configure streams - CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); - CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); - - // enable SSL - if(_secure) { - NSMutableDictionary *opts = [NSMutableDictionary dictionary]; - - opts[(__bridge id)kCFStreamSSLIsServer] = @YES; - opts[(__bridge id)kCFStreamSSLCertificates] = _SSLCertificates; - opts[(__bridge id)kCFStreamSSLValidatesCertificateChain] = @NO; // i.e. client certs - - CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, (__bridge CFDictionaryRef)opts); - CFWriteStreamSetProperty(writeStream, kCFStreamPropertySSLSettings, (__bridge CFDictionaryRef)opts); - - SSLContextRef context = (SSLContextRef)CFWriteStreamCopyProperty(writeStream, kCFStreamPropertySSLContext); - SSLSetClientSideAuthenticate(context, kTryAuthenticate); - CFRelease(context); - } - - // create connection - PSWebSocketServerConnection *connection = [[PSWebSocketServerConnection alloc] init]; - connection.inputStream = CFBridgingRelease(readStream); - connection.outputStream = CFBridgingRelease(writeStream); - - // attach connection - [self attachConnection:connection]; - - // open - [connection.inputStream open]; - [connection.outputStream open]; - - }]; + __weak typeof(self) wself = self; + [self executeWork:^{ + __strong typeof(self) sself = wself; + // create streams + CFReadStreamRef readStream = nil; + CFWriteStreamRef writeStream = nil; + CFStreamCreatePairWithSocket(kCFAllocatorDefault, handle, &readStream, + &writeStream); + + // fail if we couldn't get streams + if (!readStream || !writeStream) { + return; + } + + // configure streams + CFReadStreamSetProperty( + readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); + CFWriteStreamSetProperty( + writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); + + // enable SSL + if (sself.secure) { + NSMutableDictionary *opts = [NSMutableDictionary dictionary]; + + opts[(__bridge id)kCFStreamSSLIsServer] = @YES; + opts[(__bridge id)kCFStreamSSLCertificates] = _SSLCertificates; + opts[(__bridge id)kCFStreamSSLValidatesCertificateChain] = + @NO; // i.e. client certs + + CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, + (__bridge CFDictionaryRef)opts); + CFWriteStreamSetProperty(writeStream, kCFStreamPropertySSLSettings, + (__bridge CFDictionaryRef)opts); + + SSLContextRef context = (SSLContextRef)CFWriteStreamCopyProperty( + writeStream, kCFStreamPropertySSLContext); + SSLSetClientSideAuthenticate(context, kTryAuthenticate); + CFRelease(context); + } + + // create connection + PSWebSocketServerConnection *connection = + [[PSWebSocketServerConnection alloc] init]; + connection.inputStream = CFBridgingRelease(readStream); + connection.outputStream = CFBridgingRelease(writeStream); + + // attach connection + [sself attachConnection:connection]; + + // open + [connection.inputStream open]; + [connection.outputStream open]; + + }]; } #pragma mark - WebSockets - (void)attachWebSocket:(PSWebSocket *)webSocket { - if([_webSockets containsObject:webSocket]) { - return; - } - [_webSockets addObject:webSocket]; - webSocket.delegate = self; - webSocket.delegateQueue = _workQueue; + if ([_webSockets containsObject:webSocket]) { + return; + } + [_webSockets addObject:webSocket]; + webSocket.delegate = self; + webSocket.delegateQueue = _workQueue; } - (void)detachWebSocket:(PSWebSocket *)webSocket { - if(![_webSockets containsObject:webSocket]) { - return; - } - [_webSockets removeObject:webSocket]; - webSocket.delegate = nil; + if (![_webSockets containsObject:webSocket]) { + return; + } + [_webSockets removeObject:webSocket]; + webSocket.delegate = nil; } #pragma mark - PSWebSocketDelegate - (void)webSocketDidOpen:(PSWebSocket *)webSocket { - [self notifyDelegateWebSocketDidOpen:webSocket]; + [self notifyDelegateWebSocketDidOpen:webSocket]; } - (void)webSocket:(PSWebSocket *)webSocket didReceiveMessage:(id)message { - [self notifyDelegateWebSocket:webSocket didReceiveMessage:message]; + [self notifyDelegateWebSocket:webSocket didReceiveMessage:message]; } - (void)webSocket:(PSWebSocket *)webSocket didFailWithError:(NSError *)error { - [self detachWebSocket:webSocket]; - [self notifyDelegateWebSocket:webSocket didFailWithError:error]; -} -- (void)webSocket:(PSWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean { - [self detachWebSocket:webSocket]; - [self notifyDelegateWebSocket:webSocket didCloseWithCode:code reason:reason wasClean:wasClean]; + [self detachWebSocket:webSocket]; + [self notifyDelegateWebSocket:webSocket didFailWithError:error]; +} +- (void)webSocket:(PSWebSocket *)webSocket + didCloseWithCode:(NSInteger)code + reason:(NSString *)reason + wasClean:(BOOL)wasClean { + [self detachWebSocket:webSocket]; + [self notifyDelegateWebSocket:webSocket + didCloseWithCode:code + reason:reason + wasClean:wasClean]; } - (void)webSocketDidFlushInput:(PSWebSocket *)webSocket { - [self notifyDelegateWebSocketDidFlushInput:webSocket]; + [self notifyDelegateWebSocketDidFlushInput:webSocket]; } - (void)webSocketDidFlushOutput:(PSWebSocket *)webSocket { - [self notifyDelegateWebSocketDidFlushOutput:webSocket]; + [self notifyDelegateWebSocketDidFlushOutput:webSocket]; } #pragma mark - Connections - (void)attachConnection:(PSWebSocketServerConnection *)connection { - if([_connections containsObject:connection]) { - return; - } - [_connections addObject:connection]; - [_connectionsByStreams setObject:connection forKey:connection.inputStream]; - [_connectionsByStreams setObject:connection forKey:connection.outputStream]; - connection.inputStream.delegate = self; - connection.outputStream.delegate = self; - [connection.inputStream scheduleInRunLoop:[self runLoop] forMode:NSRunLoopCommonModes]; - [connection.outputStream scheduleInRunLoop:[self runLoop] forMode:NSRunLoopCommonModes]; + if ([_connections containsObject:connection]) { + return; + } + [_connections addObject:connection]; + [_connectionsByStreams setObject:connection forKey:connection.inputStream]; + [_connectionsByStreams setObject:connection forKey:connection.outputStream]; + connection.inputStream.delegate = self; + connection.outputStream.delegate = self; + [connection.inputStream scheduleInRunLoop:[self runLoop] + forMode:NSRunLoopCommonModes]; + [connection.outputStream scheduleInRunLoop:[self runLoop] + forMode:NSRunLoopCommonModes]; } - (void)detatchConnection:(PSWebSocketServerConnection *)connection { - if(![_connections containsObject:connection]) { - return; - } - [_connections removeObject:connection]; - [_connectionsByStreams removeObjectForKey:connection.inputStream]; - [_connectionsByStreams removeObjectForKey:connection.outputStream]; - [connection.inputStream removeFromRunLoop:[self runLoop] forMode:NSRunLoopCommonModes]; - [connection.outputStream removeFromRunLoop:[self runLoop] forMode:NSRunLoopCommonModes]; - connection.inputStream.delegate = nil; - connection.outputStream.delegate = nil; + if (![_connections containsObject:connection]) { + return; + } + [_connections removeObject:connection]; + [_connectionsByStreams removeObjectForKey:connection.inputStream]; + [_connectionsByStreams removeObjectForKey:connection.outputStream]; + [connection.inputStream removeFromRunLoop:[self runLoop] + forMode:NSRunLoopCommonModes]; + [connection.outputStream removeFromRunLoop:[self runLoop] + forMode:NSRunLoopCommonModes]; + connection.inputStream.delegate = nil; + connection.outputStream.delegate = nil; } - (void)disconnectConnectionGracefully:(PSWebSocketServerConnection *)connection statusCode:(NSInteger)statusCode description:(NSString *)description - headers:(NSDictionary*)headers -{ - if(connection.readyState >= PSWebSocketServerConnectionReadyStateClosing) { - return; - } - connection.readyState = PSWebSocketServerConnectionReadyStateClosing; - if (!description) - description = [NSHTTPURLResponse localizedStringForStatusCode:statusCode]; - CFHTTPMessageRef msg = CFHTTPMessageCreateResponse(kCFAllocatorDefault, statusCode, (__bridge CFStringRef)description, kCFHTTPVersion1_1); - for (NSString* name in headers) { - CFHTTPMessageSetHeaderFieldValue(msg, (__bridge CFStringRef)name, - (__bridge CFStringRef)headers[name]); - } - CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Connection"), CFSTR("Close")); - CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Content-Length"), CFSTR("0")); - NSData *data = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(msg)); - CFRelease(msg); - [connection.outputBuffer appendData:data]; - [self pumpOutput]; - __weak typeof(self)weakSelf = self; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC), _workQueue, ^{ - __strong typeof(weakSelf)strongSelf = weakSelf; - if(strongSelf) { - [strongSelf disconnectConnection:connection]; - } - }); + headers:(NSDictionary *)headers { + if (connection.readyState >= PSWebSocketServerConnectionReadyStateClosing) { + return; + } + connection.readyState = PSWebSocketServerConnectionReadyStateClosing; + if (!description) + description = [NSHTTPURLResponse localizedStringForStatusCode:statusCode]; + CFHTTPMessageRef msg = CFHTTPMessageCreateResponse( + kCFAllocatorDefault, statusCode, (__bridge CFStringRef)description, + kCFHTTPVersion1_1); + for (NSString *name in headers) { + CFHTTPMessageSetHeaderFieldValue(msg, (__bridge CFStringRef)name, + (__bridge CFStringRef)headers[name]); + } + CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Connection"), CFSTR("Close")); + CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Content-Length"), CFSTR("0")); + NSData *data = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(msg)); + CFRelease(msg); + [connection.outputBuffer appendData:data]; + [self pumpOutput]; + __weak typeof(self) weakSelf = self; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC), _workQueue, + ^{ + __strong typeof(weakSelf) strongSelf = weakSelf; + if (strongSelf) { + [strongSelf disconnectConnection:connection]; + } + }); } - (void)disconnectConnection:(PSWebSocketServerConnection *)connection { - if(connection.readyState == PSWebSocketServerConnectionReadyStateClosed) { - return; - } - connection.readyState = PSWebSocketServerConnectionReadyStateClosed; - [self detatchConnection:connection]; - [connection.inputStream close]; - [connection.outputStream close]; + if (connection.readyState == PSWebSocketServerConnectionReadyStateClosed) { + return; + } + connection.readyState = PSWebSocketServerConnectionReadyStateClosed; + [self detatchConnection:connection]; + [connection.inputStream close]; + [connection.outputStream close]; } #pragma mark - Pumping - (void)pumpInput { - uint8_t chunkBuffer[4096]; - for(PSWebSocketServerConnection *connection in _connections.allObjects) { - if(connection.readyState != PSWebSocketServerConnectionReadyStateOpen || - !connection.inputStream.hasBytesAvailable) { - continue; - } - - while(connection.inputStream.hasBytesAvailable) { - NSInteger readLength = [connection.inputStream read:chunkBuffer maxLength:sizeof(chunkBuffer)]; - if(readLength > 0) { - [connection.inputBuffer appendBytes:chunkBuffer length:readLength]; - } else if(readLength < 0) { - [self disconnectConnection:connection]; - } - if(readLength < sizeof(chunkBuffer)) { - break; - } + uint8_t chunkBuffer[4096]; + for (PSWebSocketServerConnection *connection in _connections.allObjects) { + if (connection.readyState != PSWebSocketServerConnectionReadyStateOpen || + !connection.inputStream.hasBytesAvailable) { + continue; + } + + while (connection.inputStream.hasBytesAvailable) { + NSInteger readLength = [connection.inputStream read:chunkBuffer + maxLength:sizeof(chunkBuffer)]; + if (readLength > 0) { + [connection.inputBuffer appendBytes:chunkBuffer length:readLength]; + } else if (readLength < 0) { + [self disconnectConnection:connection]; + } + if (readLength < sizeof(chunkBuffer)) { + break; + } + } + + if (connection.inputBuffer.bytesAvailable > 4) { + void *boundary = + memmem(connection.inputBuffer.bytes, + connection.inputBuffer.bytesAvailable, "\r\n\r\n", 4); + if (boundary == NULL) { + // Haven't reached end of HTTP headers yet + if (connection.inputBuffer.bytesAvailable >= 16384) { + [self disconnectConnection:connection]; } - - if(connection.inputBuffer.bytesAvailable > 4) { - void* boundary = memmem(connection.inputBuffer.bytes, - connection.inputBuffer.bytesAvailable, - "\r\n\r\n", 4); - if (boundary == NULL) { - // Haven't reached end of HTTP headers yet - if(connection.inputBuffer.bytesAvailable >= 16384) { - [self disconnectConnection:connection]; - } - continue; - } - NSUInteger boundaryOffset = boundary + 4 - connection.inputBuffer.bytes; - - CFHTTPMessageRef msg = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, YES); - CFHTTPMessageAppendBytes(msg, connection.inputBuffer.bytes, connection.inputBuffer.bytesAvailable); - if(!CFHTTPMessageIsHeaderComplete(msg)) { - [self disconnectConnection:connection]; - CFRelease(msg); - continue; - } - - // move input buffer - connection.inputBuffer.offset += boundaryOffset; - if(connection.inputBuffer.hasBytesAvailable) { - [self disconnectConnection:connection]; - CFRelease(msg); - continue; - } - - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:CFBridgingRelease(CFHTTPMessageCopyRequestURL(msg))]; - request.HTTPMethod = CFBridgingRelease(CFHTTPMessageCopyRequestMethod(msg)); - - NSDictionary *headers = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(msg)); - [headers enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { - [request setValue:obj forHTTPHeaderField:key]; - }]; - - if(![PSWebSocket isWebSocketRequest:request]) { - [self disconnectConnectionGracefully:connection - statusCode:501 description:@"WebSockets only, please" - headers:nil]; - CFRelease(msg); - continue; - } - - NSString* protocol = nil; - if(_delegate) { - NSHTTPURLResponse* response = nil; - if (![self askDelegateShouldAcceptConnection:connection - request:request - response:&response]) { - [self disconnectConnectionGracefully:connection - statusCode:(response.statusCode ?: 403) - description:nil - headers:response.allHeaderFields]; - CFRelease(msg); - continue; - } - protocol = response.allHeaderFields[@"Sec-WebSocket-Protocol"]; - } - - // detach connection - [self detatchConnection:connection]; - - // create webSocket - PSWebSocket *webSocket = [PSWebSocket serverSocketWithRequest:request inputStream:connection.inputStream outputStream:connection.outputStream]; - webSocket.delegateQueue = _workQueue; - - // attach webSocket - [self attachWebSocket:webSocket]; - - // open webSocket - [webSocket open]; - - // clean up - CFRelease(msg); + continue; + } + NSUInteger boundaryOffset = boundary + 4 - connection.inputBuffer.bytes; + + CFHTTPMessageRef msg = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, YES); + CFHTTPMessageAppendBytes(msg, connection.inputBuffer.bytes, + connection.inputBuffer.bytesAvailable); + if (!CFHTTPMessageIsHeaderComplete(msg)) { + [self disconnectConnection:connection]; + CFRelease(msg); + continue; + } + + // move input buffer + connection.inputBuffer.offset += boundaryOffset; + if (connection.inputBuffer.hasBytesAvailable) { + [self disconnectConnection:connection]; + CFRelease(msg); + continue; + } + + NSMutableURLRequest *request = [NSMutableURLRequest + requestWithURL:CFBridgingRelease(CFHTTPMessageCopyRequestURL(msg))]; + request.HTTPMethod = + CFBridgingRelease(CFHTTPMessageCopyRequestMethod(msg)); + + NSDictionary *headers = + CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(msg)); + [headers enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + [request setValue:obj forHTTPHeaderField:key]; + }]; + + if (![PSWebSocket isWebSocketRequest:request]) { + [self disconnectConnectionGracefully:connection + statusCode:501 + description:@"WebSockets only, please" + headers:nil]; + CFRelease(msg); + continue; + } + + NSString *protocol = nil; + if (_delegate) { + NSHTTPURLResponse *response = nil; + if (![self askDelegateShouldAcceptConnection:connection + request:request + response:&response]) { + [self disconnectConnectionGracefully:connection + statusCode:(response.statusCode ?: 403) + description:nil + headers:response.allHeaderFields]; + CFRelease(msg); + continue; } + protocol = response.allHeaderFields[@"Sec-WebSocket-Protocol"]; + } + + // detach connection + [self detatchConnection:connection]; + + // create webSocket + PSWebSocket *webSocket = + [PSWebSocket serverSocketWithRequest:request + inputStream:connection.inputStream + outputStream:connection.outputStream]; + webSocket.delegateQueue = _workQueue; + + // attach webSocket + [self attachWebSocket:webSocket]; + + // open webSocket + [webSocket open]; + + // clean up + CFRelease(msg); } + } } - (void)pumpOutput { - for(PSWebSocketServerConnection *connection in _connections.allObjects) { - if(connection.readyState != PSWebSocketServerConnectionReadyStateOpen && - connection.readyState != PSWebSocketServerConnectionReadyStateClosing) { - continue; - } - - while(connection.outputStream.hasSpaceAvailable && connection.outputBuffer.hasBytesAvailable) { - NSInteger writeLength = [connection.outputStream write:connection.outputBuffer.bytes maxLength:connection.outputBuffer.bytesAvailable]; - if(writeLength > 0) { - connection.outputBuffer.offset += writeLength; - } else if(writeLength < 0) { - [self disconnectConnection:connection]; - break; - } - - if(writeLength == 0) { - break; - } - } - - if(connection.readyState == PSWebSocketServerConnectionReadyStateClosing && - !connection.outputBuffer.hasBytesAvailable) { - [self disconnectConnection:connection]; - } + for (PSWebSocketServerConnection *connection in _connections.allObjects) { + if (connection.readyState != PSWebSocketServerConnectionReadyStateOpen && + connection.readyState != PSWebSocketServerConnectionReadyStateClosing) { + continue; + } + + while (connection.outputStream.hasSpaceAvailable && + connection.outputBuffer.hasBytesAvailable) { + NSInteger writeLength = [connection.outputStream + write:connection.outputBuffer.bytes + maxLength:connection.outputBuffer.bytesAvailable]; + if (writeLength > 0) { + connection.outputBuffer.offset += writeLength; + } else if (writeLength < 0) { + [self disconnectConnection:connection]; + break; + } + + if (writeLength == 0) { + break; + } + } + + if (connection.readyState == PSWebSocketServerConnectionReadyStateClosing && + !connection.outputBuffer.hasBytesAvailable) { + [self disconnectConnection:connection]; } + } } #pragma mark - NSStreamDelegate - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)event { - [self executeWork:^{ - if(stream.delegate != self) { - [stream.delegate stream:stream handleEvent:event]; - return; - } - - PSWebSocketServerConnection *connection = [_connectionsByStreams objectForKey:stream]; - NSAssert(connection, @"Connection should not be nil"); - - if(event == NSStreamEventOpenCompleted) { - if(stream == connection.inputStream) { - connection.inputStreamOpenCompleted = YES; - } else if(stream == connection.outputStream) { - connection.outputStreamOpenCompleted = YES; - } - } - if(!connection.inputStreamOpenCompleted || !connection.outputStreamOpenCompleted) { - return; - } - - switch(event) { - case NSStreamEventOpenCompleted: { - if(connection.readyState == PSWebSocketServerConnectionReadyStateConnecting) { - connection.readyState = PSWebSocketServerConnectionReadyStateOpen; - } - [self pumpInput]; - [self pumpOutput]; - break; - } - case NSStreamEventErrorOccurred: { - [self disconnectConnection:connection]; - break; - } - case NSStreamEventEndEncountered: { - [self disconnectConnection:connection]; - break; - } - case NSStreamEventHasBytesAvailable: { - [self pumpInput]; - break; - } - case NSStreamEventHasSpaceAvailable: { - [self pumpOutput]; - break; - } - default: - break; - } - }]; + __weak typeof(self) wself = self; + [self executeWork:^{ + __strong typeof(self) sself = self; + + if (stream.delegate != sself) { + [stream.delegate stream:stream handleEvent:event]; + return; + } + + PSWebSocketServerConnection *connection = + [sself.connectionsByStreams objectForKey:stream]; + NSAssert(connection, @"Connection should not be nil"); + + if (event == NSStreamEventOpenCompleted) { + if (stream == connection.inputStream) { + connection.inputStreamOpenCompleted = YES; + } else if (stream == connection.outputStream) { + connection.outputStreamOpenCompleted = YES; + } + } + if (!connection.inputStreamOpenCompleted || + !connection.outputStreamOpenCompleted) { + return; + } + + switch (event) { + case NSStreamEventOpenCompleted: { + if (connection.readyState == + PSWebSocketServerConnectionReadyStateConnecting) { + connection.readyState = PSWebSocketServerConnectionReadyStateOpen; + } + [sself pumpInput]; + [sself pumpOutput]; + break; + } + case NSStreamEventErrorOccurred: { + [sself disconnectConnection:connection]; + break; + } + case NSStreamEventEndEncountered: { + [sself disconnectConnection:connection]; + break; + } + case NSStreamEventHasBytesAvailable: { + [sself pumpInput]; + break; + } + case NSStreamEventHasSpaceAvailable: { + [sself pumpOutput]; + break; + } + default: + break; + } + }]; } #pragma mark - Delegation - (void)notifyDelegateDidStart { - [self executeDelegate:^{ - [_delegate serverDidStart:self]; - }]; + __weak typeof(self) wself = self; + [self executeDelegate:^{ + __strong typeof(self) sself = wself; + [sself.delegate serverDidStart:self]; + }]; } - (void)notifyDelegateFailedToStart:(NSError *)error { - [self executeDelegate:^{ - [_delegate server:self didFailWithError:error]; - }]; + __weak typeof(self) wself = self; + [self executeDelegate:^{ + __strong typeof(self) sself = wself; + [sself.delegate server:self didFailWithError:error]; + }]; } - (void)notifyDelegateDidStop { - [self executeDelegate:^{ - [_delegate serverDidStop:self]; - }]; + __weak typeof(self) wself = self; + [self executeDelegate:^{ + __strong typeof(self) sself = wself; + [sself.delegate serverDidStop:self]; + }]; } - (void)notifyDelegateWebSocketDidOpen:(PSWebSocket *)webSocket { - [self executeDelegate:^{ - [_delegate server:self webSocketDidOpen:webSocket]; - }]; -} -- (void)notifyDelegateWebSocket:(PSWebSocket *)webSocket didReceiveMessage:(id)message { - [self executeDelegate:^{ - [_delegate server:self webSocket:webSocket didReceiveMessage:message]; - }]; -} - -- (void)notifyDelegateWebSocket:(PSWebSocket *)webSocket didFailWithError:(NSError *)error { - [self executeDelegate:^{ - [_delegate server:self webSocket:webSocket didFailWithError:error]; - }]; -} -- (void)notifyDelegateWebSocket:(PSWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean { - [self executeDelegate:^{ - [_delegate server:self webSocket:webSocket didCloseWithCode:code reason:reason wasClean:wasClean]; - }]; + __weak typeof(self) wself = self; + [self executeDelegate:^{ + __strong typeof(self) sself = wself; + [sself.delegate server:self webSocketDidOpen:webSocket]; + }]; +} +- (void)notifyDelegateWebSocket:(PSWebSocket *)webSocket + didReceiveMessage:(id)message { + __weak typeof(self) wself = self; + [self executeDelegate:^{ + __strong typeof(self) sself = wself; + [sself.delegate server:self webSocket:webSocket didReceiveMessage:message]; + }]; +} + +- (void)notifyDelegateWebSocket:(PSWebSocket *)webSocket + didFailWithError:(NSError *)error { + __weak typeof(self) wself = self; + [self executeDelegate:^{ + __strong typeof(self) sself = wself; + [sself.delegate server:self webSocket:webSocket didFailWithError:error]; + }]; +} +- (void)notifyDelegateWebSocket:(PSWebSocket *)webSocket + didCloseWithCode:(NSInteger)code + reason:(NSString *)reason + wasClean:(BOOL)wasClean { + __weak typeof(self) wself = self; + [self executeDelegate:^{ + __strong typeof(self) sself = wself; + [sself.delegate server:self + webSocket:webSocket + didCloseWithCode:code + reason:reason + wasClean:wasClean]; + }]; } - (void)notifyDelegateWebSocketDidFlushInput:(PSWebSocket *)webSocket { - [self executeDelegate:^{ - if ([_delegate respondsToSelector: @selector(server:webSocketDidFlushInput:)]) { - [_delegate server:self webSocketDidFlushInput:webSocket]; - }; - }]; + __weak typeof(self) wself = self; + [self executeDelegate:^{ + __strong typeof(self) sself = wself; + if ([sself.delegate + respondsToSelector:@selector(server:webSocketDidFlushInput:)]) { + [sself.delegate server:self webSocketDidFlushInput:webSocket]; + }; + }]; } - (void)notifyDelegateWebSocketDidFlushOutput:(PSWebSocket *)webSocket { - [self executeDelegate:^{ - if ([_delegate respondsToSelector: @selector(server:webSocketDidFlushOutput:)]) { - [_delegate server:self webSocketDidFlushOutput:webSocket]; - } - }]; + __weak typeof(self) wself = self; + [self executeDelegate:^{ + __strong typeof(self) sself = wself; + if ([sself.delegate + respondsToSelector:@selector(server:webSocketDidFlushOutput:)]) { + [sself.delegate server:self webSocketDidFlushOutput:webSocket]; + } + }]; } -- (BOOL)askDelegateShouldAcceptConnection:(PSWebSocketServerConnection *)connection - request: (NSURLRequest *)request +- (BOOL)askDelegateShouldAcceptConnection: + (PSWebSocketServerConnection *)connection + request:(NSURLRequest *)request response:(NSHTTPURLResponse **)outResponse { - __block BOOL accept; - __block NSHTTPURLResponse* response = nil; - [self executeDelegateAndWait:^{ - if([_delegate respondsToSelector:@selector(server:acceptWebSocketWithRequest:address:trust:response:)]) { - NSData* address = PSPeerAddressOfInputStream(connection.inputStream); - SecTrustRef trust = (SecTrustRef)CFReadStreamCopyProperty( - (__bridge CFReadStreamRef)connection.inputStream, - kCFStreamPropertySSLPeerTrust); - accept = [_delegate server:self - acceptWebSocketWithRequest:request - address:address - trust:trust - response:&response]; - if(trust) { - CFRelease(trust); - } - } else if([_delegate respondsToSelector:@selector(server:acceptWebSocketWithRequest:)]) { - accept = [_delegate server:self acceptWebSocketWithRequest:request]; - } else { - accept = YES; - } - }]; - *outResponse = response; - return accept; + __block BOOL accept; + __block NSHTTPURLResponse *response = nil; + __weak typeof(self) wself = self; + [self executeDelegateAndWait:^{ + __strong typeof(self) sself = wself; + if ([sself.delegate respondsToSelector:@selector(server: + acceptWebSocketWithRequest: + address: + trust: + response:)]) { + NSData *address = PSPeerAddressOfInputStream(connection.inputStream); + SecTrustRef trust = (SecTrustRef)CFReadStreamCopyProperty( + (__bridge CFReadStreamRef)connection.inputStream, + kCFStreamPropertySSLPeerTrust); + accept = [sself.delegate server:self + acceptWebSocketWithRequest:request + address:address + trust:trust + response:&response]; + if (trust) { + CFRelease(trust); + } + } else if ([sself.delegate + respondsToSelector:@selector(server: + acceptWebSocketWithRequest:)]) { + accept = [sself.delegate server:self acceptWebSocketWithRequest:request]; + } else { + accept = YES; + } + }]; + *outResponse = response; + return accept; } #pragma mark - Queueing - (void)executeWork:(void (^)(void))work { - NSParameterAssert(work); - dispatch_async(_workQueue, work); + NSParameterAssert(work); + dispatch_async(_workQueue, work); } - (void)executeWorkAndWait:(void (^)(void))work { - NSParameterAssert(work); - dispatch_sync(_workQueue, work); + NSParameterAssert(work); + dispatch_sync(_workQueue, work); } - (void)executeDelegate:(void (^)(void))work { - NSParameterAssert(work); - dispatch_async((_delegateQueue) ? _delegateQueue : dispatch_get_main_queue(), work); + NSParameterAssert(work); + dispatch_async((_delegateQueue) ? _delegateQueue : dispatch_get_main_queue(), + work); } - (void)executeDelegateAndWait:(void (^)(void))work { - NSParameterAssert(work); - dispatch_sync((_delegateQueue) ? _delegateQueue : dispatch_get_main_queue(), work); + NSParameterAssert(work); + dispatch_sync((_delegateQueue) ? _delegateQueue : dispatch_get_main_queue(), + work); } #pragma mark - Dealloc - (void)dealloc { - [self executeWorkAndWait:^{ - [self disconnect:YES]; - }]; + __weak typeof(self) wself = self; + [self executeWorkAndWait:^{ + __strong typeof(self) sself = wself; + [sself disconnect:YES]; + }]; } @end -void PSWebSocketServerAcceptCallback(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) { - [(__bridge PSWebSocketServer *)info accept:*(CFSocketNativeHandle *)data]; +void PSWebSocketServerAcceptCallback(CFSocketRef s, CFSocketCallBackType type, + CFDataRef address, const void *data, + void *info) { + [(__bridge PSWebSocketServer *)info accept:*(CFSocketNativeHandle *)data]; }