
/*---------------------------------------------------------------------------*/
/* websocket.cpp                                                             */
/* copyright (c) innovaphone 2015                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#include <platform.h>
#include "sdk/common/os/iomux.h"

#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#include "common/ilib/hash.h"
#include "common/ilib/base64.h"
#include "common/ilib/str.h"
#include "common/ilib/uri.h"

#include "sdk/common/lib/httplib.h"

#include "sdk/common/interface/random.h"
#include "sdk/common/interface/dns.h"
#include "sdk/common/interface/socket.h"
#include "sdk/common/interface/network.h"
#include "sdk/common/interface/websocket_client.h"

#include "websocketframe.h"
#include "websocketclient.h"

#ifdef OVERRIDE_WEBSOCKET_MAX_PAYLOAD_SIZE
#ifdef WEBSOCKET_MAX_PAYLOAD_SIZE
#undef WEBSOCKET_MAX_PAYLOAD_SIZE
#endif
#define WEBSOCKET_MAX_PAYLOAD_SIZE OVERRIDE_WEBSOCKET_MAX_PAYLOAD_SIZE
#endif

#define WEBSOCKET_SOCKET_CONNECT_TIMEOUT 3000    // ~3 seconds
#define WEBSOCKET_HANDSHAKE_TIMEOUT      60000   // ~60 seconds
#define WEBSOCKET_CLOSE_TIMEOUT          5000    // ~5 seconds

const char * CLOSE_REASON_NORMAL        = "Normal shutdown";
const char * CLOSE_REASON_PROTOCOL      = "Handshake protocol mismatch";
const char * CLOSE_REASON_EXT_LEN       = "Received ext datalength 0";
const char * CLOSE_REASON_MSG_SIZE      = "Received message is too long";
const char * CLOSE_REASON_SRV_MSG_MASK  = "Received masked frame from server";
const char * CLOSE_REASON_MSG_MASK      = "Received an unmasked frame from client";
const char * CLOSE_REASON_CLOSE_RECV    = "Received close message";
const char * CLOSE_REASON_FRAME_ERR     = "Frame / protocol error";
const char * CLOSE_REASON_CONNECT       = "Connection failed";

const char * Trim(const char* in)
{
    if (in != nullptr) {
        while (*in != '\0' && (*in == ' ' || *in == '\t')) ++in;
    }
    return in;
}


//void __debug_frame(WebsocketFrame * frame, byte * data)
//{
//    const char * opcodeStr = "";
//    opcode_t opcode = frame->opcode();
//    switch (opcode) {
//    case 0: opcodeStr = "Continous Frame"; break;
//    case 1: opcodeStr = "Text Frame"; break;
//    case 2: opcodeStr = "Binary Frame"; break;
//    case 8: opcodeStr = "Close"; break;
//    case 9: opcodeStr = "Ping"; break;
//    case 0xA: opcodeStr = "Pong"; break;
//    };
//
//    debug->printf("-- Websocket-Frame data -------------------------");
//    debug->printf("    finbit  :  %s", frame->buffer()[0] & 0x80 ? "1" : "0");
//    debug->printf("    opcode  :  %u (%s)", opcode, opcodeStr);
//    debug->printf("    maskbit :  %s", frame->buffer()[1] & 0x80 ? "1" : "0");
//    byte plen = frame->buffer()[1] & 0x7F;
//    debug->printf("    plen    :  %u", plen);
//    if (plen == 126)
//        debug->printf("    ext-len :  16bit (%u)", (word)frame->dataLength());
//    else if (plen == 127)
//        debug->printf("    ext-len :  64bit (%llu)", frame->dataLength());
//
//    if (frame->maskBit()) {
//        int ms = frame->headerSize() - 4;
//        debug->printf("    mask    :  0x%02x 0x%02x 0x%02x 0x%02x",
//            frame->buffer()[ms], frame->buffer()[ms + 1],
//            frame->buffer()[ms + 2], frame->buffer()[ms + 3]);
//    }
//    debug->printf("-------------------------------------------------");
//    printf("Raw-Data ");
//    for (int i = 0; i < (int)frame->headerSize(); i++)
//        printf("0x%02x ", frame->buffer()[i]);
//
//    if (data)
//        for (int i = 0; i < (int)frame->dataLength(); i++)
//            printf("0x%02x ", data[i]);
//    printf("\n\n");
//
//}


IWebsocketClient * IWebsocketClient::Create(IIoMux * const iomux,
	                                        UWebsocketClient * const user,
											ISocketProvider * const tcpSocketProvider,
											ISocketProvider * const tlsSocketProvider,
                                            IDns * const dns,
                                            IInstanceLog * const log,
                                            unsigned pingIntervalSec)
{
	return new WebsocketClient(iomux, user, tcpSocketProvider, tlsSocketProvider, dns, log, pingIntervalSec);
}


/*---------------------------------------------------------------------------*/
/* class Websocketclient                                                     */
/*---------------------------------------------------------------------------*/

WebsocketClient::WebsocketClient(IIoMux * const iomux,
								 UWebsocketClient * const user,
								 ISocketProvider * const tcpSocketProvider,
								 ISocketProvider * const tlsSocketProvider,
                                 IDns * const dns,
                                 IInstanceLog * const log,
                                 unsigned pingIntervalSec)
	: iomux(iomux),
	  user(user),
	  tcpSocketProvider(tcpSocketProvider),
	  tlsSocketProvider(tlsSocketProvider),
      dns(dns),
      log(log),
      pingIntervalSec(pingIntervalSec)
{
    log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::WebsocketClient", this, user);
    this->socket = NULL;
    this->recvFrame = new WebsocketFrame;
    this->sendFrame = new WebsocketFrame;
    this->httpParser = nullptr;
    this->state = WSCS_OFFLINE;
    this->recvCalled = false;
    this->hostName = NULL;
    this->hostPath = NULL;
    this->hostIPAddress[0] = '\0';
    this->proxyIPAddress[0] = '\0';
    this->proxies = nullptr;
    this->proxy = nullptr;
    this->retryAuthenticated = false;
    this->dnsRetried = false;
    this->useTLS = false;
    this->port = 80;
    this->wsServerSecKey = NULL;
    this->userClosingReason = UWebsocketClient::WSCR_NORMAL_CLOSE;
    this->closingReason = 1000;
    this->closingReasonMsg = NULL;
    this->userRecvBuffer = NULL;
    this->userRecvBufferLen = 0;
    this->socketRecvBuffer = malloc(1);
    this->recvDataIsText = false;
    this->recvDataIsFragmented = false;
    this->isClosing = false;
    this->closeFrameSent = false;
    this->srCompleteCnt = 0;
    this->srUserCnt = 0;
    this->isServerMode = false;
    this->hasPendingPing = false;
    this->handshakeTimer = iomux ? (new ITimer(iomux, this)) : NULL;
}


WebsocketClient::~WebsocketClient()
{
    log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::~WebsocketClient", this, user);
    delete sendFrame;
    delete recvFrame;
    delete httpParser;
    if (!isServerMode) delete socket;
    if (handshakeTimer) delete handshakeTimer;
	free(hostName);
    free(hostPath);
	free(wsServerSecKey);
    free(closingReasonMsg);
    free(socketRecvBuffer);
    if (this->proxies) delete this->proxies;
}


void WebsocketClient::DnsGetHostByNameResult(const char * addr, bool isIPv6)
{
    if (isClosing) {
        this->FinalizeClose();
    }
    else if (!addr) {
        if (this->proxy) {
            this->proxy = this->proxy->goNext();
            ConnectProxy();
            return;
        }
        userClosingReason = UWebsocketClient::WSCR_CONNECTION_FAILED;
        free(closingReasonMsg);
        closingReasonMsg = _strdup(CLOSE_REASON_CONNECT);
        log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::DnsGetHostByNameResult DNS lookup for \"%s\" failed", this, user, hostName);
        this->FinalizeClose();
    }
    else {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::DnsGetHostByNameResult hostname \"%s\" resolved to %s", this, user, hostName, addr);
        strcpy(this->proxy ? this->proxyIPAddress : hostIPAddress, addr);
        this->ConnectSocket();
    }
}


void WebsocketClient::IoExec(void * execContext)
{
    // For now the IoExec function only will be used to call WebsocketClosed
    // with the Error URI invalid (see WebsocketClient::Connect()).
    UserWebsocketCloseComplete(UWebsocketClient::closereason_t(uintptr_t(execContext)));
}


void WebsocketClient::TimerOnTimeout(ITimer * timer)
{
    if ((state == WSCS_SOCKET_CONNECTING) || (state == WSCS_CONNECT_TUNNEL)) {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::TimerOnTimeout aborting connect", this, user);
        socket->Shutdown();
    }
    else if (hasPendingPing || isClosing || (state == WSCS_HANDSHAKE_STARTED)) {
        userClosingReason = isClosing || (state == WSCS_HANDSHAKE_STARTED) ? UWebsocketClient::WSCR_CONNECTION_FAILED : UWebsocketClient::WSCR_SERVER_CLOSED;
        free(closingReasonMsg);
        closingReasonMsg = _strdup(CLOSE_REASON_CONNECT);
        log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::TimerOnTimeout %s", this, user, (isClosing || (state == WSCS_HANDSHAKE_STARTED)) ? "handshake failed" : "ping timeout");
        isClosing = true;
        if (socket) {
            socket->Shutdown();
        }
        else {
            this->FinalizeClose();
        }
    }
    else if (pingIntervalSec != 0) {
        sendFrame->EncodePingFrame();
        this->SendFrame();
        hasPendingPing = true;
        handshakeTimer->Start(pingIntervalSec * 1000);
    }
}


void WebsocketClient::ConnectProxy()
{
    while (this->proxy && (this->proxy->type != HTTP_PROXY_TYPE_HTTP) && (this->proxy->type != HTTP_PROXY_TYPE_HTTPS)) {
        this->proxy = this->proxy->goNext();
    }
    if (this->proxy) {
        char * p = 0;
        str::to_ip(this->proxy->host, &p, /*prefix*/0);
        if (p != this->proxy->host) {
            memcpy(this->proxyIPAddress, this->proxy->host, p - this->proxy->host);
            this->proxyIPAddress[p - this->proxy->host] = 0;
        }
    }
    if ((this->proxy && this->proxyIPAddress[0]) || (!this->proxy && this->hostIPAddress[0])) {
        this->dnsRetried = true;
        this->ConnectSocket();
    }
    else {
        this->dnsRetried = false;
        this->ResolveDns();
    }
}


void WebsocketClient::ResolveDns()
{
    this->userClosingReason = UWebsocketClient::WSCR_NORMAL_CLOSE;
    this->dns->GetHostByName(this->proxy ? this->proxy->host : this->hostName, this, this->log, DNS_DEFAULT_TIMEOUT, false, this->proxy ? this->proxy->port : this->port);
    state = WSCS_RESOLVE_NAME;
}


void WebsocketClient::ConnectSocket()
{
    char socketAddress[100];

    this->userClosingReason = UWebsocketClient::WSCR_NORMAL_CLOSE;
    if (this->proxy) {
	    // Port is removed, now check IP type. IPv6 has colons...
	    bool useIPv6 = strstr(proxyIPAddress, ":") != 0;

        ASSERT(!socket, "WebsocketClient::ConnectSocket - socket already exists");
        socket = tcpSocketProvider->CreateSocket(iomux, this, this->log, useIPv6);

        // 100 Bytes should be more than enough for the IP Address. Must just for the case still use
        // snprintf(). If something goes wrong, only the IP Address is wrong and the connect will fail.
        _snprintf(socketAddress, 100, useIPv6 ? "[%s]:%u" : "%s:%u", this->proxyIPAddress, (this->proxy->port != 0) ? this->proxy->port : 80);
	    state = WSCS_CONNECT_TUNNEL;
    }
    else {
	    // Port is removed, now check IP type. IPv6 has colons...
	    bool useIPv6 = strstr(hostIPAddress, ":") != 0;

        ASSERT(!socket, "WebsocketClient::ConnectSocket - socket already exists");
        socket = (useTLS ? tlsSocketProvider : tcpSocketProvider)->CreateSocket(iomux, this, this->log, useIPv6);

        // 100 Bytes should be more than enough for the IP Address. Must just for the case still use
        // snprintf(). If something goes wrong, only the IP Address is wrong and the connect will fail.
        _snprintf(socketAddress, 100, useIPv6 ? "[%s]:%u" : "%s:%u", hostIPAddress, port);
	    state = WSCS_SOCKET_CONNECTING;
    }
    socket->Connect(socketAddress, hostName);
    if (handshakeTimer) handshakeTimer->Start(WEBSOCKET_SOCKET_CONNECT_TIMEOUT);
}


void WebsocketClient::Connect(const char * uri)
{
    log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::Connect uri:%s state:%i", this, user, uri, state);
    ASSERT(state == WSCS_OFFLINE, "WebsocketClient::Connect(): Websocket already connected");

    // The innovaphone webserver needs a websocket server. After the handshake the only difference
    // between a websocket client and websocket server is, that the server will receive encrypted
    // data and send plain text, whil it will be the opposite for the client. So this class can
    // also be used as a server so by changing the direction for encryption. To do this, the
    // innoivaphone webserver send __ACTIVATE_SERVER_MODE as uri to let us now that we should
    // act as a server. However, with this the webserver is responsible for doing the websocket
    // handshake and giving a ISocketProvider instance that gives a ISocket instance to send
    // and receive the data. Without this, this class cannot act as s server (because no server
    // init function is implemented). For more details look at the innovaphone webserver project,
    // httpconnection.cpp and search for __ACTIVATE_SERVER_MODE.
    if (strcmp(uri, "__ACTIVATE_SERVER_MODE") == 0) {
        isServerMode = true;
        socket = tcpSocketProvider->CreateSocket(NULL, this, this->log, false);
        state = WSCS_LISTENING;
        sendFrame->SetServerMode(true);
        recvFrame->SetServerMode(true);
        socket->Recv(recvFrame->buffer(), 2); // Waiting for the base part of an frame header...
        UserWebsocketConnectComplete(); // Well, theoretically we don't need that...
        if (pingIntervalSec != 0) {
            handshakeTimer->Start(pingIntervalSec * 1000);
        }
        return;
    }

    // If the URI is wrong, we will call an SetExec of the iomux object to inform the
    // user later of that error. WIth this we will keep the asynchron design.
	uri_dissector uriDisp;
	if (!uriDisp.dissect_uri((char *)uri)) {
        iomux->SetExec(this, (void *)UWebsocketClient::WSCR_URI_INVALID);
		return;
	}

    if (!uriDisp.scheme || (uriDisp.e_scheme - uriDisp.scheme) > 3) {
        iomux->SetExec(this, (void *)UWebsocketClient::WSCR_URI_INVALID);
        return;
    }

	if (!(uriDisp.scheme[0] == 'w' || uriDisp.scheme[0] == 'W') ||
		!(uriDisp.scheme[1] == 's' || uriDisp.scheme[1] == 'S')) {
        iomux->SetExec(this, (void *)UWebsocketClient::WSCR_URI_INVALID);
        return;
	}

    if (!uriDisp.hier || (uriDisp.e_hier - uriDisp.hier) != 2) {
        iomux->SetExec(this, (void *)UWebsocketClient::WSCR_URI_INVALID);
        return;
    }

    if (!uriDisp.ip && !uriDisp.fqdn) {
        iomux->SetExec(this, (void *)UWebsocketClient::WSCR_URI_INVALID);
        return;
    }

	if (uriDisp.scheme[2] == 's' || uriDisp.scheme[2] == 'S') {
		useTLS = true;
		port = 443;
	}
	if (uriDisp.port) port = atoi(uriDisp.port);

    free(hostPath);
    int lenPath = (int)(uriDisp.e_path - uriDisp.path);
    int lenFile = (int)(uriDisp.e_file - uriDisp.file);
    int len = lenPath + lenFile;
    if (len > 0) {
        hostPath = (char *)malloc(len + 1);
        if (uriDisp.path) memcpy(hostPath, uriDisp.path, lenPath);
        if (uriDisp.file) memcpy(hostPath + lenPath, uriDisp.file, lenFile);
    }
    else {
        hostPath = (char *)malloc(2);
        hostPath[0] = '/';
        len++;
    }
    hostPath[len] = 0;

	// uri_despense has an HostName or an IP Address, never both of them...
	free(hostName);
    hostIPAddress[0] = '\0';
    proxyIPAddress[0] = '\0';
	if (uriDisp.ip) {
		char *ipStart = uriDisp.ip;
        int len = (int)(uriDisp.e_ip - uriDisp.ip);
		if (*ipStart == '[') {
			ipStart++;
			len -= 2;
		}
		hostName = (char *)malloc(len + 1);
		memcpy(hostName, ipStart, len);
		memcpy(hostIPAddress, ipStart, len);
		hostIPAddress[len] = hostName[len] = 0;
	}
	else {
        int len = (int)(uriDisp.e_fqdn - uriDisp.fqdn);
		hostName = (char *)malloc(len + 1);
		memcpy(hostName, uriDisp.fqdn, len);
		hostName[len] = 0;
	}
    if (this->proxies) delete(this->proxies);
    this->proxies = INetwork::GetHTTPProxiesForURL(uri);
    this->proxy = this->proxies->httpProxies.empty() ? nullptr : this->proxies->httpProxies.front();
    ConnectProxy();
}


inline void WebsocketClient::SendFrame()
{
    if (sendFrame->isServerMode()) {
        socket->Send(sendFrame->buffer(), sendFrame->headerSize());
        socket->Send(sendFrame->dataBuffer(), size_t(sendFrame->dataLength()));
        srCompleteCnt += 2;
    }
    else {
        socket->Send(sendFrame->buffer(), sendFrame->frameSize());
        srCompleteCnt++;
    }
}


void WebsocketClient::Close()
{
    log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::Close state:%i isClosing:%i", this, user, state, isClosing);
    if (state == WSCS_OFFLINE) {
        iomux->SetExec(this, (void *)UWebsocketClient::WSCR_NORMAL_CLOSE);
        return;
    }

    if (isClosing) return;

    this->InitializeClose(true);
}


void WebsocketClient::Cancel()
{
    log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::Cancel state:%i isClosing:%i", this, user, state, isClosing);
    if (state == WSCS_OFFLINE) {
        iomux->SetExec(this, (void *)UWebsocketClient::WSCR_NORMAL_CLOSE);
        return;
    }

    this->InitializeClose(false);
}


void WebsocketClient::Ping()
{
    if (isClosing) return;
    sendFrame->EncodePingFrame();
    this->SendFrame();
}


void WebsocketClient::Send(const void * buf, size_t len, bool text)
{
    log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::Send state:%i len:%i text:%i", this, user, state, len, text);
    log->HexDump(LOG_WEBSOCKET_CLIENT, buf, len);
    ASSERT(state >= WSCS_CONNECTED, "WebsocketClient::Send() called while client is offline");

    if (isClosing) return;

    frame_t ftype = WSF_UNDEFINED;
    if (!text) ftype = frame_t(ftype | WSF_BINARY);

    if (len > WEBSOCKET_MAX_PAYLOAD_SIZE) {
        byte * buffer = (byte *)buf;
        sendFrame->EncodeDataFrame(frame_t(ftype | WSF_FRAGMENTED_START), buffer, WEBSOCKET_MAX_PAYLOAD_SIZE);
        this->SendFrame();

        bool cont = true;
        while (cont) {
            len -= WEBSOCKET_MAX_PAYLOAD_SIZE;
            buffer += WEBSOCKET_MAX_PAYLOAD_SIZE;

            if (len <= WEBSOCKET_MAX_PAYLOAD_SIZE) {
                sendFrame->EncodeDataFrame(frame_t(ftype | WSF_FRAGMENTED_END), buffer, len);
                this->SendFrame();
                cont = false;
            }
            else {
                sendFrame->EncodeDataFrame(frame_t(ftype | WSF_FRAGMENTED_CONTINOUS), buffer, WEBSOCKET_MAX_PAYLOAD_SIZE);
                this->SendFrame();
            }
        }
    }
    else {
        sendFrame->EncodeDataFrame(frame_t(ftype | WSF_SINGLE), (byte *)buf, len);
        this->SendFrame();
    }

    srUserCnt++;
}

void WebsocketClient::ReadPendingData(byte * newBuf, size_t len)
{
    size_t bufSizeNeeded = (size_t)recvFrame->dataLength();

    if (bufSizeNeeded == 0) { // Well - should not happen...
        const char * opcodeStr = "";
        switch (recvFrame->opcode()) {
            case 0: opcodeStr = "Continous Frame"; break;
            case 1: opcodeStr = "Text Frame"; break;
            case 2: opcodeStr = "Binary Frame"; break;
            case 8: opcodeStr = "Close"; break;
            case 9: opcodeStr = "Ping"; break;
            case 0xA:opcodeStr = "Pong"; break;
        };

        log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::ReadPendingData received message with data length 0 (opcode = 0x%02x, %s) - will handle directly", this, user, recvFrame->opcode(), opcodeStr);
        this->ScanWebsocketFrame(NULL, 0);
        return;
    }

    if (!newBuf) {
        newBuf = (byte *)UserWebsocketRecvBuffer(bufSizeNeeded);
        len = bufSizeNeeded;
        ASSERT(newBuf != NULL, "WebsocketClient::Recv(): userRecvBuffer / newBuf == NULL");
        userRecvBuffer = newBuf;
        userRecvBufferLen = len;
    }
    
    if (bufSizeNeeded > len) {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::ReadPendingData receive buffer too small (size = %u, should be %u)", this, user, userRecvBufferLen, bufSizeNeeded);
        closingReason = 1009;
        free(closingReasonMsg);
        closingReasonMsg = _strdup(CLOSE_REASON_MSG_SIZE);
        userClosingReason = UWebsocketClient::WSCR_BUFFER_OVERFLOW;
        this->InitializeClose(false);
        return;
    }

    state = WSCS_READ_DATA;
    socket->Recv(newBuf, bufSizeNeeded);
}


void WebsocketClient::Recv(void * buf, size_t len)
{
    log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::Recv state:%i len:%i", this, user, state, len);
    if (recvCalled) {
        if (this->isServerMode) {
            this->userClosingReason = UWebsocketClient::WSCR_DOUBLE_RECV_CALL;
            this->Close();
        }
        else {
            ASSERT(false, "WebsocketClient::Recv() called again before handling the result of a previous call");
        }
    }

    recvCalled = true;
    userRecvBuffer = (byte *)buf;
    userRecvBufferLen = len;
    if (state == WSCS_WAIT_FOR_USER_BUFFER) {
        this->ReadPendingData((byte *)buf, len);
    }
}


void WebsocketClient::ScanTunnelResponse(char * buf, size_t len)
{
    if (this->httpParser == nullptr) {
        this->httpParser = new HTTPParser();
    }

    httpParser->ParseData(buf, len);
    switch (httpParser->GetParseResult()) {
        case HTTPParser::parseresult_t::HTTP_PARSE_CANCEL:
            // Nothing to do here, because the case won't happen...
            break;

        case HTTPParser::parseresult_t::HTTP_PARSE_ERROR:
            log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::ScanTunnelResponse: HTTPParser::ParseData() reutnred an error.", this, user);
            socket->Shutdown();
            break;

        case HTTPParser::parseresult_t::HTTP_PARSE_NEED_DATA:
            socket->Recv(headerBuffer, sizeof(headerBuffer), true);
            return;

        case HTTPParser::parseresult_t::HTTP_PARSE_DONE:
            if ((this->httpParser->GetHTTPResultCode()->code >= 200) && (this->httpParser->GetHTTPResultCode()->code < 300)) {
                this->state = WSCS_SOCKET_CONNECTING;
                this->retryAuthenticated = false;
                if (this->useTLS) {
                    this->socket = this->tlsSocketProvider->UpgradeToTLSSocket(this->socket, this->iomux, this, this->log, NULL);
                }
                else {
                    this->SocketConnectComplete(this->socket);
                }
            }
            else if ((this->httpParser->GetHTTPResultCode()->code == 407) && !this->retryAuthenticated && proxy->usr && proxy->pwd) {
                this->retryAuthenticated = true;
                char tmp[1024];
                char tmp2[1024];
                int l = _sprintf(tmp, "%s:%s", proxy->usr, proxy->pwd);
                encode_base64((byte *)tmp2, tmp, l);
                bool useIPv6 = strchr(this->hostName, ':') != nullptr;
                l = snprintf(tmp, sizeof(tmp), useIPv6 ? "CONNECT [%s]:%u HTTP/1.1\r\nHost: [%s]:%u\r\nProxy-Authorization: Basic %s\r\n\r\n" :
                    "CONNECT %s:%u HTTP/1.1\r\nHost: %s:%u\r\nProxy-Authorization: Basic %s\r\n\r\n", this->hostName, this->port, this->hostName, this->port, tmp2);
                this->socket->Send(tmp, l);
                srCompleteCnt++;
                socket->Recv(headerBuffer, sizeof(headerBuffer), true);
            }
            else {
                this->socket->Shutdown();
            }
            break;
    }

    delete this->httpParser;
    this->httpParser = nullptr;
}


void WebsocketClient::SendHandshake()
{
	state = WSCS_HANDSHAKE_STARTED;

	// Key for handshake need to be an 16 byte random value, base64 encoded...
	dword rndVal[4];
	rndVal[0] = IRandom::GetRandom();
    rndVal[1] = IRandom::GetRandom();
    rndVal[2] = IRandom::GetRandom();
    rndVal[3] = IRandom::GetRandom();

	char wsClientSecKey[25];
	int r = encode_base64((const byte *)rndVal, wsClientSecKey, 16);
	wsClientSecKey[r] = 0;

	// Server-Response = wsClientSecKey + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11
	char tmp[61];
	_sprintf(tmp, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", wsClientSecKey);
	
    hash sha1;
    sha1.init(HASH_SHA1);

    // sha1.size() will return 20, but Visual C++ only accepts constants for array sizes.
    unsigned char shaBuf[20];
    sha1.update(tmp, 60);
    sha1.final(shaBuf);
	
	free(wsServerSecKey);
	wsServerSecKey = (char *)malloc(29);
	r = encode_base64((const byte *)shaBuf, wsServerSecKey, 20);
	wsServerSecKey[r] = 0;
	
	char buffer[1024];
    
    size_t len = _snprintf(buffer, 1024, "GET %s HTTP/1.1\r\n"
                                         "Host: %s\r\n"
                                         "Upgrade: websocket\r\n"
                                         "Connection: Upgrade\r\n"
                                         "Sec-WebSocket-Key: %s\r\n"
                                         "Sec-WebSocket-Version: 13\r\n\r\n",
                                          hostPath, hostName, wsClientSecKey);

    // Note that snprintf will return the number of bytes the result can have, not the number of bytes written to the buffer (in the
    // case, that the buffer is not big enough). So check, if everything is fine - if not, adjust the len to not send to much. This
    // will lead to an error, but that will be resolved later...
    if (len >= sizeof(buffer)) len = sizeof(buffer) - 1;

//	msg = "Sec-WebSocket-Protocol: chat, superchat\r\n";
    socket->Send(buffer, len);
    srCompleteCnt++;
    if (handshakeTimer) handshakeTimer->Start(WEBSOCKET_HANDSHAKE_TIMEOUT);
}


void WebsocketClient::ScanHandshakeResponse(char * buf, size_t len)
{
    if (this->httpParser == nullptr) {
        this->httpParser = new HTTPParser();
    }

    httpParser->ParseData(buf, len);
    switch (httpParser->GetParseResult()) {
        case HTTPParser::parseresult_t::HTTP_PARSE_CANCEL:
            // Nothing to do here, because the case won't happen...
            break;

        case HTTPParser::parseresult_t::HTTP_PARSE_ERROR:
            log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::ScanHandshakeResponse: HTTPParser::ParseData() reutnred an error.", this, user);
            this->HandshakeFailed();
            break;

        case HTTPParser::parseresult_t::HTTP_PARSE_NEED_DATA:
            socket->Recv(headerBuffer, sizeof(headerBuffer), true);
            return;

        case HTTPParser::parseresult_t::HTTP_PARSE_DONE:
            if (handshakeTimer && handshakeTimer->IsRunning()) handshakeTimer->Cancel();
            if (!this->IsRedirectResponse()) {
                if (!this->ValidateHandshake()) this->HandshakeFailed();
            }
            break;
    }

    delete this->httpParser;
    this->httpParser = nullptr;
}


bool WebsocketClient::IsRedirectResponse()
{
    bool result = false;
    const HTTPLib::http_result_t& res = *this->httpParser->GetHTTPResultCode();

    if (res == HTTPLib::HTTP_301 || res == HTTPLib::HTTP_302 || res == HTTPLib::HTTP_307 || res == HTTPLib::HTTP_308) {
        result = true;
        const char * location = this->GetHandshakeHeaderField("Location", nullptr);

        if (location == nullptr || *location == '\0') {
            this->log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::IsRedirectResponse(): HTTP resonse requests redirection, but location field is not set or empty", this, user);
            this->HandshakeFailed();
        }
        else {
            free(this->hostName);
            this->hostName = _strdup(location);
            this->state = WSCS_REDIRECTING;
            this->socket->Shutdown();
        }
    }

    return result;
}


const char * WebsocketClient::GetHandshakeHeaderField(const char * headerField, const char * neededValue)
{
    const char * result = nullptr;
    const HTTPHeaderField * field = this->httpParser->GetHeaderField(headerField);

    if (field == nullptr) {
        this->log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::GetHandshakeHeaderField(): Headerfield '%s' not found or empty.", this, user, headerField);
        return nullptr;
    }

    if (neededValue == nullptr) {
        result = Trim(field->GetValue(0));
        if (result == nullptr || *result == '\0') {
            this->log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::GetHandshakeHeaderField(): Headerfield '%s' doesn't contain a value.", this, user, headerField);
            return nullptr;
        }
    }
    else {
        for (size_t i = 0; i < field->GetValueCount(); ++i) {
            result = Trim(field->GetValue(i));
            if (result != nullptr && *result != '\0' && strcasecmp(result, neededValue) == 0) {
                break;
            }
            result = nullptr;
        }
    }

    // Result can onlky be nullptr here, if neededValue is not nullptr. Else, if result becomes nullptr, the function already would have returned.
    if (result == nullptr) { 
        this->log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::GetHandshakeHeaderField(): Headerfield '%s' expected value is '%s', but '%s' was found.", this, user, headerField, neededValue, result);
    }

    return result;
}


bool WebsocketClient::ValidateHandshake()
{
    // The minimal Response has to be:
    //
    // HTTP/1.1 101 Switching Protocols
    // Upgrade: websocket
    // Connection: Upgrade
    // Sec-WebSocket-Accept: <Value-Of-wsServerSecKey>

    if (this->httpParser->GetMajorVersion() != 1 || this->httpParser->GetMinorVersion() != 1) {
        this->log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::ValidateHandshake(): HTTP version expected is 1.1, but version %u.%u received.", this, user, this->httpParser->GetMajorVersion(), this->httpParser->GetMinorVersion());
        return false;
    }

    const HTTPLib::http_result_t & httpRes = *this->httpParser->GetHTTPResultCode();
    if (httpRes != HTTPLib::HTTP_101) {
        this->log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::ValidateHandshake(): Expected HTTP response 101, but got %u (%s).", this, user, httpRes.code, httpRes.description);
        return false;
    }

    const char* val;
    if ((val = this->GetHandshakeHeaderField("connection", "upgrade")) == nullptr) return false;
    if ((val = this->GetHandshakeHeaderField("upgrade", "websocket")) == nullptr) return false;
    if ((val = this->GetHandshakeHeaderField("sec-websocket-accept", nullptr)) == nullptr) return false;
    if (strncmp(this->wsServerSecKey, val, strlen(this->wsServerSecKey)) != 0) {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::ValidateHandshake Sec-WebSocket-Accept key missmatch received:%s expected:%s", this, user, val, wsServerSecKey);
        return false;
    }

    state = WSCS_LISTENING;
    socket->Recv(recvFrame->buffer(), 2); // Waiting for the base part of an frame header...
    UserWebsocketConnectComplete();
    if (pingIntervalSec != 0) {
        handshakeTimer->Start(pingIntervalSec * 1000);
    }

    return true;
}


void WebsocketClient::HandshakeFailed()
{
    log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::HandshakeFailed() - that's bad...", this, user);
    closingReason = 1002; // Protocoll - Error
    free(closingReasonMsg);
    closingReasonMsg = _strdup(CLOSE_REASON_PROTOCOL);
    userClosingReason = UWebsocketClient::WSCR_CONNECTION_FAILED;
    // Set the state to connected, to let InitializeClose() work proberly. But also prevent from sending
    // a close frame, because the websocket connection itself hasn't be established.
    closeFrameSent = true;
    state = WSCS_CONNECTED;
    this->InitializeClose(false);
}


void WebsocketClient::StartReadData()
{
    if (recvFrame->dataLength() > 0xFFFFFFFF) {
        log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::StartReadData frame payload length to high (max: 0xFFFFFFFF, length: 0x%llX)", this, user, recvFrame->dataLength());
        recvCalled = false;
        closingReason = 1009;
        free(closingReasonMsg);
        closingReasonMsg = _strdup(CLOSE_REASON_MSG_SIZE);
        userClosingReason = UWebsocketClient::WSCR_BUFFER_OVERFLOW;
        this->InitializeClose(false);
        return;
    }

    state = WSCS_READ_DATA;

    if (recvFrame->opcode() == WSOC_CLOSE || recvFrame->opcode() == WSOC_PING || recvFrame->opcode() == WSOC_PONG || isClosing) {
        size_t dlen = (size_t)recvFrame->dataLength();
        if (dlen > 0) {
            free(socketRecvBuffer);
            socketRecvBuffer = malloc(dlen);
            socket->Recv(socketRecvBuffer, dlen);
        }
        else this->ScanWebsocketFrame(NULL, 0);
        return;
    }

    if (!recvCalled) {
        state = WSCS_WAIT_FOR_USER_BUFFER;
        return;
    }

    this->ReadPendingData(userRecvBuffer, userRecvBufferLen);
}


void WebsocketClient::ScanWebsocketFrame(void * buffer, size_t dataLen)
{
    byte * buf = static_cast<byte *>(buffer);

    switch (recvFrame->opcode())
    {
    case WSOC_TEXT_FRAME:
        // If the frame is a text frame, save that fact for the case the fragments will be received.
        // The flag recvDataIsText will then be used for the following fragments, too.
        recvDataIsText = true;
        // Because the rest of the processing will be the same for WSOC_TEXT_FRAME, WSOC_BINARY_FRAME
        // and WSOC_CONTINOUS_FRAME, there is not break here!

    case WSOC_BINARY_FRAME:
    case WSOC_CONTINOUS_FRAME:
        if (recvCalled && (buffer != socketRecvBuffer)) {
            recvCalled = false;
            userRecvBuffer = NULL;
            userRecvBufferLen = 0;

            if (recvFrame->isServerMode()) recvFrame->MaskData(buf, buf, dataLen);

            recvDataIsFragmented = !recvFrame->finBit();
            UserWebsocketRecvResult(buf, dataLen, recvDataIsText, recvDataIsFragmented);
            if (!recvDataIsFragmented) recvDataIsText = false;
        }
        break;

    case WSOC_CLOSE:
        if (!closeFrameSent) {
            closingReason = 1000;
            free(closingReasonMsg);
            closingReasonMsg = _strdup(CLOSE_REASON_CLOSE_RECV);
            this->InitializeClose(false);
        }
        else {
            if (dataLen > 0) {
                closingReason = READ_WORD_NET((word *)buf);
                if (dataLen > 2) {
                    free(closingReasonMsg);
                    closingReasonMsg = (char *)malloc(dataLen-1);
                    memcpy(closingReasonMsg, &buf[2], dataLen-2);
                    closingReasonMsg[dataLen-2] = 0;
                    //log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::ScanWebsocketFrame closingReasonMsg (%s)", this, user, closingReasonMsg);
                }
            }
            if (handshakeTimer && handshakeTimer->IsRunning()) handshakeTimer->Cancel();
            socket->Shutdown();
        }
        return; // No need to set the buffer at the end of the function again...

    case WSOC_PING:
        sendFrame->EncodePongFrame();
        this->SendFrame();
        break;

    case WSOC_PONG:
        if (hasPendingPing) {
            hasPendingPing = false;
        }
        else {
            this->user->WebsocketPongReceived(this);
        }
        break;
    };

    // Wait for the next Frame...
    state = WSCS_LISTENING;
    socket->Recv(recvFrame->buffer(), 2);
}


void WebsocketClient::InitializeClose(bool waitForServerResponse)
{
    bool needSendCloseMsg = state >= WSCS_CONNECTED && !closeFrameSent;
    isClosing = true;
    
    if (closingReasonMsg == NULL) {
        closingReason = 1000;
        closingReasonMsg = _strdup(CLOSE_REASON_NORMAL);
    }
    if (state == WSCS_WAIT_FOR_USER_BUFFER) {
        size_t dLen = (size_t)recvFrame->dataLength();
        free(socketRecvBuffer);
        socketRecvBuffer = malloc(dLen);
        this->ReadPendingData((byte *)socketRecvBuffer, dLen);
    }
    if (needSendCloseMsg) {
        sendFrame->EncodeCloseFrame(closingReason, closingReasonMsg);
        this->SendFrame();
        closeFrameSent = true;
    }
    
    // If the WebsocketClient is not yet connected, return for now. Because of the isClosing flag set,
    // closing will be continued later.
    if (state < WSCS_CONNECTED) return;

    if (waitForServerResponse) {
        if (handshakeTimer) handshakeTimer->Start(WEBSOCKET_CLOSE_TIMEOUT);
    }
    else {
        if (handshakeTimer && handshakeTimer->IsRunning()) handshakeTimer->Cancel();
        socket->Shutdown();
    }
}


void WebsocketClient::FinalizeClose()
{
    state = WSCS_OFFLINE;

    free(hostName);
    hostName = NULL;

    if (proxies) {
        delete proxies;
        proxies = NULL;
    }

    useTLS = false;
    port = 80;

    free(wsServerSecKey);
    wsServerSecKey = NULL;

    free(closingReasonMsg);
    closingReasonMsg = NULL;
    closingReason = 0;

    recvCalled = false;
    if (userRecvBuffer) {
        UserWebsocketRecvCanceled(userRecvBuffer);
        userRecvBuffer = NULL;
        userRecvBufferLen = 0;
    }
    recvDataIsText = false;

    isClosing = false;
    closeFrameSent = false;

    srUserCnt = srCompleteCnt = 0;

    // Make a copy of the close-reason and reinit it before calling UserWebsocketCloseComplete,
    // because it could be that in that handler the instance will be deleted. In that case
    // reintitializing userClosingReason after that will write to an laready releasd memory area.
    UWebsocketClient::closereason_t tmpCR = userClosingReason;
    userClosingReason = UWebsocketClient::WSCR_NORMAL_CLOSE; // Reinit with default
    UserWebsocketCloseComplete(tmpCR);
}


void WebsocketClient::SocketShutdown(ISocket * const socket, shutdownreason_t reason)
{
    if (handshakeTimer && handshakeTimer->IsRunning()) handshakeTimer->Cancel();
    // Only release the socket, if the Websocket Client is not in server mode, used by the webserver.
    // In that mode, sockerProvider->CreateSocket() only returns a copy of a pointer, but not a new
    // project.
    if (!isServerMode) delete this->socket;
    this->socket = NULL;

    if (this->state == WSCS_REDIRECTING) {
        // The redirect location is stored in the hostName. Since reconnecting will free the hostName (see Connect()), there is no need to release it here.
        char * location = _strdup(this->hostName);
        this->state = WSCS_OFFLINE;
        this->Connect(location);
        free(location);
        return;
    }
    
    if (!isClosing) {
        if (state < WSCS_CONNECTED) {
            const char * host = this->proxy ? this->proxy->host : this->hostName;
            if (host
             && ((this->proxy ? this->proxyIPAddress : this->hostIPAddress)[0] != 0)) {
                char * p = 0;
                str::to_ip(host, &p, /*prefix*/0);
                if ((p == host) && this->dns) {
                    this->dns->UnsuitableAddress(host, this->log, this->proxy ? this->proxyIPAddress : this->hostIPAddress, this->proxy ? ((this->proxy->port != 0) ? this->proxy->port : 80) : this->port);
                    if (!this->dnsRetried) { // just retry DNS resolution once, as we otherwise may run into an infinite loop, as the DNS module unmarks unsuitable addresses if no more addresses can be resolved
                        // mark current address as unsuitable, as the IP address was not available at all and retry DNS resolution to see if there is another address
                        // this helps e.g. if an IPv6 address is resolved first but can't be used (ARM gateway) so a second IPv4 resolved address may work then
                        this->dnsRetried = true;
                        this->ResolveDns();
                        return;
                    }
                }
                if (this->proxy) {
                    this->proxy = this->proxy->goNext();
                    ConnectProxy();
                    return;
                }
            }
            userClosingReason = UWebsocketClient::WSCR_CONNECTION_FAILED;
        }
        else {
            userClosingReason = UWebsocketClient::WSCR_SOCKET_LOST;
        }
    }

    this->FinalizeClose();
}


void WebsocketClient::SocketConnectComplete(ISocket * const socket)
{
    if (handshakeTimer && handshakeTimer->IsRunning()) handshakeTimer->Cancel();
    if (isClosing) {
        socket->Shutdown();
    }
    else {
        delete this->httpParser;
        this->httpParser = nullptr;
        if (this->state == WSCS_CONNECT_TUNNEL) {
            char buf[1024];
            bool useIPv6 = strchr(this->hostName, ':') != nullptr;
            int len = snprintf(buf, sizeof(buf), useIPv6 ? "CONNECT [%s]:%u HTTP/1.1\r\nHost: [%s]:%u\r\n\r\n" : "CONNECT %s:%u HTTP/1.1\r\nHost: %s:%u\r\n\r\n", this->hostName, this->port, this->hostName, this->port);
            this->socket->Send(buf, len);
            srCompleteCnt++;
        }
        else {
            this->SendHandshake();
        }
        socket->Recv(headerBuffer, sizeof(headerBuffer), true);
    }
}


void WebsocketClient::SocketUpgradeToTLSComplete(ISocket* const socket)
{
    SocketConnectComplete(socket);
}


void WebsocketClient::SocketSendResult(ISocket * const socket)
{
    // Only inform the user if this is a send result of a message sent by the user
    if (srCompleteCnt == srUserCnt) {
        UserWebsocketSendResult();
        srUserCnt--;
    }
    srCompleteCnt--;
}


void WebsocketClient::SocketRecvResult(ISocket * const socket, void * buf, size_t len)
{
    switch (state) {
    case WSCS_CONNECT_TUNNEL:
        if (isClosing) {
            socket->Shutdown();
        }
        else {
            this->ScanTunnelResponse((char *)buf, len);
        }
        break;

    case WSCS_HANDSHAKE_STARTED:
        if (isClosing) {
            if (handshakeTimer && handshakeTimer->IsRunning()) handshakeTimer->Cancel();
            socket->Shutdown();
        }
        else {
            this->ScanHandshakeResponse((char *)buf, len);
        }
        break;

    case WSCS_LISTENING:
        recvFrame->DecodeHeaderBase();
        if (recvFrame->maskBit() && !recvFrame->isServerMode()) {
            log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::SocketRecvResult client received masked frame", this, user);
            closingReason = 1002;
            free(closingReasonMsg);
            closingReasonMsg = _strdup(CLOSE_REASON_SRV_MSG_MASK);
            userClosingReason = UWebsocketClient::WSCR_PROTOCOL_ERROR;
            this->InitializeClose(false);
            return;
        }
        else if (!recvFrame->maskBit() && recvFrame->isServerMode()) {
            log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::SocketRecvResult received an unmasked frame while in server mode", this, user);
            closingReason = 1002;
            free(closingReasonMsg);
            closingReasonMsg = _strdup(CLOSE_REASON_MSG_MASK);
            userClosingReason = UWebsocketClient::WSCR_PROTOCOL_ERROR;
            this->InitializeClose(false);
            return;
        }

        if (recvDataIsFragmented && recvFrame->opcode() != WSOC_CONTINOUS_FRAME) {
            log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::SocketRecvResult didn't receive expected continuous frame", this, user);
            closingReason = 1002;
            free(closingReasonMsg);
            closingReasonMsg = _strdup(CLOSE_REASON_FRAME_ERR);
            userClosingReason = UWebsocketClient::WSCR_PROTOCOL_ERROR;
            this->InitializeClose(false);
            return;
        }

        if (!recvDataIsFragmented && recvFrame->opcode() == WSOC_CONTINOUS_FRAME) {
            log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::SocketRecvResult received unexpected continuous frame", this, user);
            closingReason = 1002;
            free(closingReasonMsg);
            closingReasonMsg = _strdup(CLOSE_REASON_FRAME_ERR);
            userClosingReason = UWebsocketClient::WSCR_PROTOCOL_ERROR;
            this->InitializeClose(false);
            return;
        }

        if (recvFrame->headerSizeExt() > 0) {
            state = WSCS_READ_HEADER_EXT;
            socket->Recv(&recvFrame->buffer()[2], recvFrame->headerSizeExt());
        }
        else if (recvFrame->dataLength() > 0) this->StartReadData();
        else this->ScanWebsocketFrame(NULL, 0);
        break;

    case WSCS_READ_HEADER_EXT:
        recvFrame->DecodeHeaderExt();
        if (recvFrame->dataLength() > MESSAGE_SIZE_MAX) {
            closingReason = 1009;
            free(closingReasonMsg);
            closingReasonMsg = _strdup(CLOSE_REASON_MSG_SIZE);
            userClosingReason = UWebsocketClient::WSCR_PROTOCOL_ERROR;
            this->InitializeClose(false);
            return;
        }
        //if (!recvFrame->isServerMode() && recvFrame->dataLength() == 0) {
        //    debug->printf("WebsocketClient::ScanHeaderExt(): Websocket-Frame error, datalength = 0");
        //    closingReason = 1002;
        //    closingReasonMsg = _strdup(CLOSE_REASON_EXT_LEN);
        //    userClosingReason = UWebsocketClient::WSCR_PROTOCOL_ERROR;
        //    this->InitializeClose(false);
        //}
        this->StartReadData();
        break;

    case WSCS_READ_DATA:
        this->ScanWebsocketFrame(buf, len);
        break;

    default:
        debug->printf("WebsocketClient(%08x,%08x)::SocketRecvResult unhandled state (%u)", this, user, state);
        ASSERT(false, "WebsocketClient::SocketRecvResult unhandled state");
        break;
    }
}

void WebsocketClient::SocketRecvCanceled(ISocket * const socket, void * buf)
{
}

void WebsocketClient::UserWebsocketConnectComplete()
{
    log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::WebsocketConnectComplete", this, user);
    this->user->WebsocketConnectComplete(this);
}

void WebsocketClient::UserWebsocketCloseComplete(UWebsocketClient::closereason_t reason)
{
    log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::WebsocketCloseComplete reason:%i", this, user, reason);
    this->user->WebsocketCloseComplete(this, reason);
}

void WebsocketClient::UserWebsocketSendResult()
{
    log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::WebsocketSendResult", this, user);
    this->user->WebsocketSendResult(this);
}

void WebsocketClient::UserWebsocketRecvResult(void * buf, size_t len, bool text, bool isFragment)
{
    log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::WebsocketRecvResult len:%i text:%i isFragment:%i", this, user, len, text, isFragment);
    log->HexDump(LOG_WEBSOCKET_CLIENT, buf, len);
    this->user->WebsocketRecvResult(this, buf, len, text, isFragment);
}

void * WebsocketClient::UserWebsocketRecvBuffer(size_t len)
{
    log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::WebsocketRecvBuffer len:%i", this, user, len);
    return this->user->WebsocketRecvBuffer(len);
}

void WebsocketClient::UserWebsocketRecvCanceled(void * buf)
{
    log->Log(LOG_WEBSOCKET_CLIENT, "WebsocketClient(%08x,%08x)::UserWebsocketRecvCanceled", this, user);
    this->user->WebsocketRecvCanceled(this, buf);
}
