123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791 |
- #import "WebSocket.h"
- #import "HTTPMessage.h"
- #import "GCDAsyncSocket.h"
- #import "DDNumber.h"
- #import "DDData.h"
- #import "HTTPLogging.h"
- #if ! __has_feature(objc_arc)
- #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
- #endif
- // Log levels: off, error, warn, info, verbose
- // Other flags : trace
- static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE;
- #define TIMEOUT_NONE -1
- #define TIMEOUT_REQUEST_BODY 10
- #define TAG_HTTP_REQUEST_BODY 100
- #define TAG_HTTP_RESPONSE_HEADERS 200
- #define TAG_HTTP_RESPONSE_BODY 201
- #define TAG_PREFIX 300
- #define TAG_MSG_PLUS_SUFFIX 301
- #define TAG_MSG_WITH_LENGTH 302
- #define TAG_MSG_MASKING_KEY 303
- #define TAG_PAYLOAD_PREFIX 304
- #define TAG_PAYLOAD_LENGTH 305
- #define TAG_PAYLOAD_LENGTH16 306
- #define TAG_PAYLOAD_LENGTH64 307
- #define WS_OP_CONTINUATION_FRAME 0
- #define WS_OP_TEXT_FRAME 1
- #define WS_OP_BINARY_FRAME 2
- #define WS_OP_CONNECTION_CLOSE 8
- #define WS_OP_PING 9
- #define WS_OP_PONG 10
- static inline BOOL WS_OP_IS_FINAL_FRAGMENT(UInt8 frame)
- {
- return (frame & 0x80) ? YES : NO;
- }
- static inline BOOL WS_PAYLOAD_IS_MASKED(UInt8 frame)
- {
- return (frame & 0x80) ? YES : NO;
- }
- static inline NSUInteger WS_PAYLOAD_LENGTH(UInt8 frame)
- {
- return frame & 0x7F;
- }
- @interface WebSocket (PrivateAPI)
- - (void)readRequestBody;
- - (void)sendResponseBody;
- - (void)sendResponseHeaders;
- @end
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark -
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- @implementation WebSocket
- {
- BOOL isRFC6455;
- BOOL nextFrameMasked;
- NSUInteger nextOpCode;
- NSData *maskingKey;
- }
- + (BOOL)isWebSocketRequest:(HTTPMessage *)request
- {
- // Request (Draft 75):
- //
- // GET /demo HTTP/1.1
- // Upgrade: WebSocket
- // Connection: Upgrade
- // Host: example.com
- // Origin: http://example.com
- // WebSocket-Protocol: sample
- //
- //
- // Request (Draft 76):
- //
- // GET /demo HTTP/1.1
- // Upgrade: WebSocket
- // Connection: Upgrade
- // Host: example.com
- // Origin: http://example.com
- // Sec-WebSocket-Protocol: sample
- // Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5
- // Sec-WebSocket-Key2: 12998 5 Y3 1 .P00
- //
- // ^n:ds[4U
-
- // Look for Upgrade: and Connection: headers.
- // If we find them, and they have the proper value,
- // we can safely assume this is a websocket request.
-
- NSString *upgradeHeaderValue = [request headerField:@"Upgrade"];
- NSString *connectionHeaderValue = [request headerField:@"Connection"];
-
- BOOL isWebSocket = YES;
-
- if (!upgradeHeaderValue || !connectionHeaderValue) {
- isWebSocket = NO;
- }
- else if (![upgradeHeaderValue caseInsensitiveCompare:@"WebSocket"] == NSOrderedSame) {
- isWebSocket = NO;
- }
- else if ([connectionHeaderValue rangeOfString:@"Upgrade" options:NSCaseInsensitiveSearch].location == NSNotFound) {
- isWebSocket = NO;
- }
-
- HTTPLogTrace2(@"%@: %@ - %@", THIS_FILE, THIS_METHOD, (isWebSocket ? @"YES" : @"NO"));
-
- return isWebSocket;
- }
- + (BOOL)isVersion76Request:(HTTPMessage *)request
- {
- NSString *key1 = [request headerField:@"Sec-WebSocket-Key1"];
- NSString *key2 = [request headerField:@"Sec-WebSocket-Key2"];
-
- BOOL isVersion76;
-
- if (!key1 || !key2) {
- isVersion76 = NO;
- }
- else {
- isVersion76 = YES;
- }
-
- HTTPLogTrace2(@"%@: %@ - %@", THIS_FILE, THIS_METHOD, (isVersion76 ? @"YES" : @"NO"));
-
- return isVersion76;
- }
- + (BOOL)isRFC6455Request:(HTTPMessage *)request
- {
- NSString *key = [request headerField:@"Sec-WebSocket-Key"];
- BOOL isRFC6455 = (key != nil);
- HTTPLogTrace2(@"%@: %@ - %@", THIS_FILE, THIS_METHOD, (isRFC6455 ? @"YES" : @"NO"));
- return isRFC6455;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark Setup and Teardown
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- @synthesize websocketQueue;
- - (id)initWithRequest:(HTTPMessage *)aRequest socket:(GCDAsyncSocket *)socket
- {
- HTTPLogTrace();
-
- if (aRequest == nil)
- {
- return nil;
- }
-
- if ((self = [super init]))
- {
- if (HTTP_LOG_VERBOSE)
- {
- NSData *requestHeaders = [aRequest messageData];
-
- NSString *temp = [[NSString alloc] initWithData:requestHeaders encoding:NSUTF8StringEncoding];
- HTTPLogVerbose(@"%@[%p] Request Headers:\n%@", THIS_FILE, self, temp);
- }
-
- websocketQueue = dispatch_queue_create("WebSocket", NULL);
- request = aRequest;
-
- asyncSocket = socket;
- [asyncSocket setDelegate:self delegateQueue:websocketQueue];
-
- isOpen = NO;
- isVersion76 = [[self class] isVersion76Request:request];
- isRFC6455 = [[self class] isRFC6455Request:request];
-
- term = [[NSData alloc] initWithBytes:"\xFF" length:1];
- }
- return self;
- }
- - (void)dealloc
- {
- HTTPLogTrace();
-
- #if !OS_OBJECT_USE_OBJC
- dispatch_release(websocketQueue);
- #endif
-
- [asyncSocket setDelegate:nil delegateQueue:NULL];
- [asyncSocket disconnect];
- }
- - (id)delegate
- {
- __block id result = nil;
-
- dispatch_sync(websocketQueue, ^{
- result = delegate;
- });
-
- return result;
- }
- - (void)setDelegate:(id)newDelegate
- {
- dispatch_async(websocketQueue, ^{
- delegate = newDelegate;
- });
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark Start and Stop
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- /**
- * Starting point for the WebSocket after it has been fully initialized (including subclasses).
- * This method is called by the HTTPConnection it is spawned from.
- **/
- - (void)start
- {
- // This method is not exactly designed to be overriden.
- // Subclasses are encouraged to override the didOpen method instead.
-
- dispatch_async(websocketQueue, ^{ @autoreleasepool {
-
- if (isStarted) return;
- isStarted = YES;
-
- if (isVersion76)
- {
- [self readRequestBody];
- }
- else
- {
- [self sendResponseHeaders];
- [self didOpen];
- }
- }});
- }
- /**
- * This method is called by the HTTPServer if it is asked to stop.
- * The server, in turn, invokes stop on each WebSocket instance.
- **/
- - (void)stop
- {
- // This method is not exactly designed to be overriden.
- // Subclasses are encouraged to override the didClose method instead.
-
- dispatch_async(websocketQueue, ^{ @autoreleasepool {
-
- [asyncSocket disconnect];
- }});
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark HTTP Response
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- - (void)readRequestBody
- {
- HTTPLogTrace();
-
- NSAssert(isVersion76, @"WebSocket version 75 doesn't contain a request body");
-
- [asyncSocket readDataToLength:8 withTimeout:TIMEOUT_NONE tag:TAG_HTTP_REQUEST_BODY];
- }
- - (NSString *)originResponseHeaderValue
- {
- HTTPLogTrace();
-
- NSString *origin = [request headerField:@"Origin"];
-
- if (origin == nil)
- {
- NSString *port = [NSString stringWithFormat:@"%hu", [asyncSocket localPort]];
-
- return [NSString stringWithFormat:@"http://localhost:%@", port];
- }
- else
- {
- return origin;
- }
- }
- - (NSString *)locationResponseHeaderValue
- {
- HTTPLogTrace();
-
- NSString *location;
-
- NSString *scheme = [asyncSocket isSecure] ? @"wss" : @"ws";
- NSString *host = [request headerField:@"Host"];
-
- NSString *requestUri = [[request url] relativeString];
-
- if (host == nil)
- {
- NSString *port = [NSString stringWithFormat:@"%hu", [asyncSocket localPort]];
-
- location = [NSString stringWithFormat:@"%@://localhost:%@%@", scheme, port, requestUri];
- }
- else
- {
- location = [NSString stringWithFormat:@"%@://%@%@", scheme, host, requestUri];
- }
-
- return location;
- }
- - (NSString *)secWebSocketKeyResponseHeaderValue {
- NSString *key = [request headerField: @"Sec-WebSocket-Key"];
- NSString *guid = @"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
- return [[key stringByAppendingString: guid] dataUsingEncoding: NSUTF8StringEncoding].sha1Digest.base64Encoded;
- }
- - (void)sendResponseHeaders
- {
- HTTPLogTrace();
-
- // Request (Draft 75):
- //
- // GET /demo HTTP/1.1
- // Upgrade: WebSocket
- // Connection: Upgrade
- // Host: example.com
- // Origin: http://example.com
- // WebSocket-Protocol: sample
- //
- //
- // Request (Draft 76):
- //
- // GET /demo HTTP/1.1
- // Upgrade: WebSocket
- // Connection: Upgrade
- // Host: example.com
- // Origin: http://example.com
- // Sec-WebSocket-Protocol: sample
- // Sec-WebSocket-Key2: 12998 5 Y3 1 .P00
- // Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5
- //
- // ^n:ds[4U
-
- // Response (Draft 75):
- //
- // HTTP/1.1 101 Web Socket Protocol Handshake
- // Upgrade: WebSocket
- // Connection: Upgrade
- // WebSocket-Origin: http://example.com
- // WebSocket-Location: ws://example.com/demo
- // WebSocket-Protocol: sample
- //
- //
- // Response (Draft 76):
- //
- // HTTP/1.1 101 WebSocket Protocol Handshake
- // Upgrade: WebSocket
- // Connection: Upgrade
- // Sec-WebSocket-Origin: http://example.com
- // Sec-WebSocket-Location: ws://example.com/demo
- // Sec-WebSocket-Protocol: sample
- //
- // 8jKS'y:G*Co,Wxa-
-
- HTTPMessage *wsResponse = [[HTTPMessage alloc] initResponseWithStatusCode:101
- description:@"Web Socket Protocol Handshake"
- version:HTTPVersion1_1];
-
- [wsResponse setHeaderField:@"Upgrade" value:@"WebSocket"];
- [wsResponse setHeaderField:@"Connection" value:@"Upgrade"];
-
- // Note: It appears that WebSocket-Origin and WebSocket-Location
- // are required for Google's Chrome implementation to work properly.
- //
- // If we don't send either header, Chrome will never report the WebSocket as open.
- // If we only send one of the two, Chrome will immediately close the WebSocket.
- //
- // In addition to this it appears that Chrome's implementation is very picky of the values of the headers.
- // They have to match exactly with what Chrome sent us or it will close the WebSocket.
-
- NSString *originValue = [self originResponseHeaderValue];
- NSString *locationValue = [self locationResponseHeaderValue];
-
- NSString *originField = isVersion76 ? @"Sec-WebSocket-Origin" : @"WebSocket-Origin";
- NSString *locationField = isVersion76 ? @"Sec-WebSocket-Location" : @"WebSocket-Location";
-
- [wsResponse setHeaderField:originField value:originValue];
- [wsResponse setHeaderField:locationField value:locationValue];
-
- NSString *acceptValue = [self secWebSocketKeyResponseHeaderValue];
- if (acceptValue) {
- [wsResponse setHeaderField: @"Sec-WebSocket-Accept" value: acceptValue];
- }
- NSData *responseHeaders = [wsResponse messageData];
-
-
- if (HTTP_LOG_VERBOSE)
- {
- NSString *temp = [[NSString alloc] initWithData:responseHeaders encoding:NSUTF8StringEncoding];
- HTTPLogVerbose(@"%@[%p] Response Headers:\n%@", THIS_FILE, self, temp);
- }
-
- [asyncSocket writeData:responseHeaders withTimeout:TIMEOUT_NONE tag:TAG_HTTP_RESPONSE_HEADERS];
- }
- - (NSData *)processKey:(NSString *)key
- {
- HTTPLogTrace();
-
- unichar c;
- NSUInteger i;
- NSUInteger length = [key length];
-
- // Concatenate the digits into a string,
- // and count the number of spaces.
-
- NSMutableString *numStr = [NSMutableString stringWithCapacity:10];
- long long numSpaces = 0;
-
- for (i = 0; i < length; i++)
- {
- c = [key characterAtIndex:i];
-
- if (c >= '0' && c <= '9')
- {
- [numStr appendFormat:@"%C", c];
- }
- else if (c == ' ')
- {
- numSpaces++;
- }
- }
-
- long long num = strtoll([numStr UTF8String], NULL, 10);
-
- long long resultHostNum;
-
- if (numSpaces == 0)
- resultHostNum = 0;
- else
- resultHostNum = num / numSpaces;
-
- HTTPLogVerbose(@"key(%@) -> %qi / %qi = %qi", key, num, numSpaces, resultHostNum);
-
- // Convert result to 4 byte big-endian (network byte order)
- // and then convert to raw data.
-
- UInt32 result = OSSwapHostToBigInt32((uint32_t)resultHostNum);
-
- return [NSData dataWithBytes:&result length:4];
- }
- - (void)sendResponseBody:(NSData *)d3
- {
- HTTPLogTrace();
-
- NSAssert(isVersion76, @"WebSocket version 75 doesn't contain a response body");
- NSAssert([d3 length] == 8, @"Invalid requestBody length");
-
- NSString *key1 = [request headerField:@"Sec-WebSocket-Key1"];
- NSString *key2 = [request headerField:@"Sec-WebSocket-Key2"];
-
- NSData *d1 = [self processKey:key1];
- NSData *d2 = [self processKey:key2];
-
- // Concatenated d1, d2 & d3
-
- NSMutableData *d0 = [NSMutableData dataWithCapacity:(4+4+8)];
- [d0 appendData:d1];
- [d0 appendData:d2];
- [d0 appendData:d3];
-
- // Hash the data using MD5
-
- NSData *responseBody = [d0 md5Digest];
-
- [asyncSocket writeData:responseBody withTimeout:TIMEOUT_NONE tag:TAG_HTTP_RESPONSE_BODY];
-
- if (HTTP_LOG_VERBOSE)
- {
- NSString *s1 = [[NSString alloc] initWithData:d1 encoding:NSASCIIStringEncoding];
- NSString *s2 = [[NSString alloc] initWithData:d2 encoding:NSASCIIStringEncoding];
- NSString *s3 = [[NSString alloc] initWithData:d3 encoding:NSASCIIStringEncoding];
-
- NSString *s0 = [[NSString alloc] initWithData:d0 encoding:NSASCIIStringEncoding];
-
- NSString *sH = [[NSString alloc] initWithData:responseBody encoding:NSASCIIStringEncoding];
-
- HTTPLogVerbose(@"key1 result : raw(%@) str(%@)", d1, s1);
- HTTPLogVerbose(@"key2 result : raw(%@) str(%@)", d2, s2);
- HTTPLogVerbose(@"key3 passed : raw(%@) str(%@)", d3, s3);
- HTTPLogVerbose(@"key0 concat : raw(%@) str(%@)", d0, s0);
- HTTPLogVerbose(@"responseBody: raw(%@) str(%@)", responseBody, sH);
-
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark Core Functionality
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- - (void)didOpen
- {
- HTTPLogTrace();
-
- // Override me to perform any custom actions once the WebSocket has been opened.
- // This method is invoked on the websocketQueue.
- //
- // Don't forget to invoke [super didOpen] in your method.
-
- // Start reading for messages
- [asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:(isRFC6455 ? TAG_PAYLOAD_PREFIX : TAG_PREFIX)];
-
- // Notify delegate
- if ([delegate respondsToSelector:@selector(webSocketDidOpen:)])
- {
- [delegate webSocketDidOpen:self];
- }
- }
- - (void)sendMessage:(NSString *)msg
- {
- NSData *msgData = [msg dataUsingEncoding:NSUTF8StringEncoding];
- [self sendData:msgData];
- }
- - (void)sendData:(NSData *)msgData
- {
- HTTPLogTrace();
-
- NSMutableData *data = nil;
-
- if (isRFC6455)
- {
- NSUInteger length = msgData.length;
- if (length <= 125)
- {
- data = [NSMutableData dataWithCapacity:(length + 2)];
- [data appendBytes: "\x81" length:1];
- UInt8 len = (UInt8)length;
- [data appendBytes: &len length:1];
- [data appendData:msgData];
- }
- else if (length <= 0xFFFF)
- {
- data = [NSMutableData dataWithCapacity:(length + 4)];
- [data appendBytes: "\x81\x7E" length:2];
- UInt16 len = (UInt16)length;
- [data appendBytes: (UInt8[]){len >> 8, len & 0xFF} length:2];
- [data appendData:msgData];
- }
- else
- {
- data = [NSMutableData dataWithCapacity:(length + 10)];
- [data appendBytes: "\x81\x7F" length:2];
- [data appendBytes: (UInt8[]){0, 0, 0, 0, (UInt8)(length >> 24), (UInt8)(length >> 16), (UInt8)(length >> 8), length & 0xFF} length:8];
- [data appendData:msgData];
- }
- }
- else
- {
- data = [NSMutableData dataWithCapacity:([msgData length] + 2)];
-
- [data appendBytes:"\x00" length:1];
- [data appendData:msgData];
- [data appendBytes:"\xFF" length:1];
- }
-
- // Remember: GCDAsyncSocket is thread-safe
-
- [asyncSocket writeData:data withTimeout:TIMEOUT_NONE tag:0];
- }
- - (void)didReceiveMessage:(NSString *)msg
- {
- HTTPLogTrace();
-
- // Override me to process incoming messages.
- // This method is invoked on the websocketQueue.
- //
- // For completeness, you should invoke [super didReceiveMessage:msg] in your method.
-
- // Notify delegate
- if ([delegate respondsToSelector:@selector(webSocket:didReceiveMessage:)])
- {
- [delegate webSocket:self didReceiveMessage:msg];
- }
- }
- - (void)didClose
- {
- HTTPLogTrace();
-
- // Override me to perform any cleanup when the socket is closed
- // This method is invoked on the websocketQueue.
- //
- // Don't forget to invoke [super didClose] at the end of your method.
-
- // Notify delegate
- if ([delegate respondsToSelector:@selector(webSocketDidClose:)])
- {
- [delegate webSocketDidClose:self];
- }
-
- // Notify HTTPServer
- [[NSNotificationCenter defaultCenter] postNotificationName:WebSocketDidDieNotification object:self];
- }
- #pragma mark WebSocket Frame
- - (BOOL)isValidWebSocketFrame:(UInt8)frame
- {
- NSUInteger rsv = frame & 0x70;
- NSUInteger opcode = frame & 0x0F;
- if (rsv || (3 <= opcode && opcode <= 7) || (0xB <= opcode && opcode <= 0xF))
- {
- return NO;
- }
- return YES;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark AsyncSocket Delegate
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- // 0 1 2 3
- // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
- // +-+-+-+-+-------+-+-------------+-------------------------------+
- // |F|R|R|R| opcode|M| Payload len | Extended payload length |
- // |I|S|S|S| (4) |A| (7) | (16/64) |
- // |N|V|V|V| |S| | (if payload len==126/127) |
- // | |1|2|3| |K| | |
- // +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
- // | Extended payload length continued, if payload len == 127 |
- // + - - - - - - - - - - - - - - - +-------------------------------+
- // | |Masking-key, if MASK set to 1 |
- // +-------------------------------+-------------------------------+
- // | Masking-key (continued) | Payload Data |
- // +-------------------------------- - - - - - - - - - - - - - - - +
- // : Payload Data continued ... :
- // + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
- // | Payload Data continued ... |
- // +---------------------------------------------------------------+
- - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
- {
- HTTPLogTrace();
-
- if (tag == TAG_HTTP_REQUEST_BODY)
- {
- [self sendResponseHeaders];
- [self sendResponseBody:data];
- [self didOpen];
- }
- else if (tag == TAG_PREFIX)
- {
- UInt8 *pFrame = (UInt8 *)[data bytes];
- UInt8 frame = *pFrame;
-
- if (frame <= 0x7F)
- {
- [asyncSocket readDataToData:term withTimeout:TIMEOUT_NONE tag:TAG_MSG_PLUS_SUFFIX];
- }
- else
- {
- // Unsupported frame type
- [self didClose];
- }
- }
- else if (tag == TAG_PAYLOAD_PREFIX)
- {
- UInt8 *pFrame = (UInt8 *)[data bytes];
- UInt8 frame = *pFrame;
- if ([self isValidWebSocketFrame: frame])
- {
- nextOpCode = (frame & 0x0F);
- [asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_LENGTH];
- }
- else
- {
- // Unsupported frame type
- [self didClose];
- }
- }
- else if (tag == TAG_PAYLOAD_LENGTH)
- {
- UInt8 frame = *(UInt8 *)[data bytes];
- BOOL masked = WS_PAYLOAD_IS_MASKED(frame);
- NSUInteger length = WS_PAYLOAD_LENGTH(frame);
- nextFrameMasked = masked;
- maskingKey = nil;
- if (length <= 125)
- {
- if (nextFrameMasked)
- {
- [asyncSocket readDataToLength:4 withTimeout:TIMEOUT_NONE tag:TAG_MSG_MASKING_KEY];
- }
- [asyncSocket readDataToLength:length withTimeout:TIMEOUT_NONE tag:TAG_MSG_WITH_LENGTH];
- }
- else if (length == 126)
- {
- [asyncSocket readDataToLength:2 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_LENGTH16];
- }
- else
- {
- [asyncSocket readDataToLength:8 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_LENGTH64];
- }
- }
- else if (tag == TAG_PAYLOAD_LENGTH16)
- {
- UInt8 *pFrame = (UInt8 *)[data bytes];
- NSUInteger length = ((NSUInteger)pFrame[0] << 8) | (NSUInteger)pFrame[1];
- if (nextFrameMasked) {
- [asyncSocket readDataToLength:4 withTimeout:TIMEOUT_NONE tag:TAG_MSG_MASKING_KEY];
- }
- [asyncSocket readDataToLength:length withTimeout:TIMEOUT_NONE tag:TAG_MSG_WITH_LENGTH];
- }
- else if (tag == TAG_PAYLOAD_LENGTH64)
- {
- // FIXME: 64bit data size in memory?
- [self didClose];
- }
- else if (tag == TAG_MSG_WITH_LENGTH)
- {
- NSUInteger msgLength = [data length];
- if (nextFrameMasked && maskingKey) {
- NSMutableData *masked = data.mutableCopy;
- UInt8 *pData = (UInt8 *)masked.mutableBytes;
- UInt8 *pMask = (UInt8 *)maskingKey.bytes;
- for (NSUInteger i = 0; i < msgLength; i++)
- {
- pData[i] = pData[i] ^ pMask[i % 4];
- }
- data = masked;
- }
- if (nextOpCode == WS_OP_TEXT_FRAME)
- {
- NSString *msg = [[NSString alloc] initWithBytes:[data bytes] length:msgLength encoding:NSUTF8StringEncoding];
- [self didReceiveMessage:msg];
- }
- else
- {
- [self didClose];
- return;
- }
- // Read next frame
- [asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_PREFIX];
- }
- else if (tag == TAG_MSG_MASKING_KEY)
- {
- maskingKey = data.copy;
- }
- else
- {
- NSUInteger msgLength = [data length] - 1; // Excluding ending 0xFF frame
-
- NSString *msg = [[NSString alloc] initWithBytes:[data bytes] length:msgLength encoding:NSUTF8StringEncoding];
-
- [self didReceiveMessage:msg];
-
-
- // Read next message
- [asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PREFIX];
- }
- }
- - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)error
- {
- HTTPLogTrace2(@"%@[%p]: socketDidDisconnect:withError: %@", THIS_FILE, self, error);
-
- [self didClose];
- }
- @end
|