#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

// Does ARC support support GCD objects?
// It does if the minimum deployment target is iOS 6+ or Mac OS X 8+

#if TARGET_OS_IPHONE

  // Compiling for iOS

  #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later
    #define NEEDS_DISPATCH_RETAIN_RELEASE 0
  #else                                         // iOS 5.X or earlier
    #define NEEDS_DISPATCH_RETAIN_RELEASE 1
  #endif

#else

  // Compiling for Mac OS X

  #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080     // Mac OS X 10.8 or later
    #define NEEDS_DISPATCH_RETAIN_RELEASE 0
  #else
    #define NEEDS_DISPATCH_RETAIN_RELEASE 1     // Mac OS X 10.7 or earlier
  #endif

#endif

// Log levels: off, error, warn, info, verbose
// Other flags : trace
static const DDLogLevel httpLogLevel = DDLogLevelWarning; // | 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 (httpLogLevel & DDLogFlagVerbose)
        {
            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 NEEDS_DISPATCH_RETAIN_RELEASE
    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 (httpLogLevel & DDLogFlagVerbose)
    {
        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 (httpLogLevel & DDLogFlagVerbose)
    {
        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
