/*---------------------------------------------------------------------------*/
/* dtlssocket.cpp                                                            */
/* copyright (c) innovaphone 2017                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#include <platform.h>

#include "sdk/common/os/iomux.h"
#include "sdk/common/interface/socket.h"
#include "sdk/common/interface/dtls.h"
#include "sdk/common/interface/certificates.h"
#include "common/ilib/hash.h"

#include "sdk/common/service/openssl_shared/ssl.h"
#include "dtls.h"

#define SEND_BUFFER_BLOCK_SIZE  512
#define RECV_BUFFER_BLOCK_SIZE  65535

#define DTLS_RETRANSMISSION_TIME 1000
#define DTLS_RETRANSMISSION_TIME_FAST 20
#define DTLS_RETRANSMISSION_COUNT 4
#define DTLS_RETRANSMISSION_COUNT_FAST 9
#define DTLS_RETRANSMISSION_COUNT_FINISHED 2
#define DTLS_SERVER_ACCEPT_TIMEOUT 5000

#define DTLS_MAX_HANDSHAKE_FRAGMENT 1180
#define DTLS_MAX_FRAGMENT 1240

static inline size_t get_size_in_blocks(size_t s, size_t block)
{
    size_t tmp = s / block * block;
    if (tmp < s)
        return tmp + block;
    else
        return tmp;
}

class IDtls * IDtls::Create(class IIoMux* iomux, class UDtls* user, void* userCtx, IInstanceLog* const log)
{
    return new Dtls(iomux, user, userCtx, log);
}

/*---------------------------------------------------------------------*/
/* class DTLS                                                          */
/*---------------------------------------------------------------------*/
Dtls::Dtls(class IIoMux* iomux, class UDtls * user, void * userCtx, IInstanceLog * const log)
    : log(log),
      sendBuffer(NULL),
      sendBufferSize(SEND_BUFFER_BLOCK_SIZE),
      recvBuffer(NULL),
      recvBufferSize(RECV_BUFFER_BLOCK_SIZE)
{
    this->iomux = iomux;
    this->user = user;
    this->context = NULL;
    this->userCtx = userCtx;
    this->associated = false;
    this->associatedAddr = ip_anyaddr;
    this->associatedPort = 0;
    this->ssl = NULL;
    this->sendBuffer = (byte *)malloc(sendBufferSize);
    this->recvBuffer = (byte *)malloc(recvBufferSize);
    this->cookie = NULL;
    this->cookieLen = 0;
    this->options = 0;
    this->paused = false;
    this->userDataReceived = 0;
    this->userReadPartial = false;
    user->dtlsDisabled = false;
    retransmissionCount = 0;
    retransmissionCountMax = 0;
    retransmissionTime = 1;
    state = DTLS_STATE_IDLE;
    server = false;
    handshakeMsgRx = nullptr;
    handshakeMsgRxLen = 0;
}

Dtls::~Dtls()
{
    if(sendBuffer) free(sendBuffer);
    if(recvBuffer) free(recvBuffer);
    if(cookie) free(cookie);
    if(handshakeMsgRx) free(handshakeMsgRx);
    if(ssl) delete ssl;
    clearHandshakeSendQueue();
}

void Dtls::reset()
{
    if (context) {
        context = NULL;
        if(ssl) delete ssl;
        ssl = NULL; 
        paused = false;
        options = 0;
        associated = false;
        associatedAddr = ip_anyaddr;
        associatedPort = 0;
        if (cookie) free(cookie);
        cookie = NULL;
        if (handshakeMsgRx) free(handshakeMsgRx);
        handshakeMsgRx = nullptr;
        handshakeMsgRxLen = 0;
        clearHandshakeSendQueue();
        user->DtlsTimerStop(userCtx);
    }
    state = DTLS_STATE_IDLE;
}

void Dtls::close(int error, bool alert)
{
    if (context) {
        log->Log(LOG_DTLS, "DTLS(%p)::Close error=%d", this, error);
        //if (state == DTLS_STATE_APPLICATION_DATA && alert) send_alert(TLS_ALERT_TYPE_CLOSE_NOTIFY);
        bool callback = state != DTLS_STATE_IDLE;
        //abort_use_srtp();
        reset();
        if(callback) user->DtlsConnectionClosed(userCtx, error);
    }
}

void Dtls::ExtractSrtpKeys(byte * srtpKeys, const char * profile)
{
    log->Log(LOG_DTLS, "DTLS(%p)::ExtractSrtpKeys to user=%p", this, user);

    user->DtlsSrtpKeys(userCtx, srtpKeys, profile);
}

void Dtls::DtlsInitialize(const byte * certificateFingerprint, size_t certificateFingerprintLen)
{
    log->Log(LOG_DTLS, "DTLS(%p)::dtlsInitialize fingerprint=%s", this, certificateFingerprint != NULL ? "true" : "false");
    reset();

    if(certificateFingerprint) {
        user->DtlsInitialized(userCtx, certificateFingerprint, MIN(32, certificateFingerprintLen));
        return;
    }

    log->Log(LOG_DTLS, "DTLS(%p)::Initialized DISABLED");
    user->dtlsDisabled = true;
    user->DtlsInitialized(userCtx, 0, 0);
}

void Dtls::DtlsClose()
{
    close(DTLS_ERROR_NONE, true);
}

void Dtls::DtlsConnect(bool server, int options, const char * remoteAddress, class ISocketContext * socketContext, byte * fingerprint, byte fingerprintLen, bool paused)
{
    debug->printf("DTLS(%p)::dtlsConnect %s options=%x fingerprintLen=%d remoteAddress=%s", this, server ? "SERVER" : "CLIENT", options, fingerprintLen, remoteAddress ? remoteAddress : "");
    reset();
    this->server = server;
    this->context = socketContext;
    this->options = options;
    this->paused = paused;
    log->Log(LOG_DTLS, "DTLS(%p)::dtlsConnect ssl context %p!", this, this->context);
    this->ssl = ISSLProvider::SharedInstance()->CreateSSLInstance(log, server, remoteAddress, this->context);
    if (this->ssl == NULL) {
        log->Log(LOG_DTLS, "DTLS(%p)::dtlsConnect Failed to create ssl instance!", this);
        context = NULL;
        close(DTLS_ERROR_SSL_INSTANCE, false);
        return;
    }
    this->ssl->SetSSLContext(this);
    this->ssl->SetMtu(DTLS_MAX_HANDSHAKE_FRAGMENT);

    state = DTLS_STATE_IDLE;

    retransmissionCount = 0;
    retransmissionCountMax = DTLS_RETRANSMISSION_COUNT;
    retransmissionTime = DTLS_RETRANSMISSION_TIME;
    dataSentHs = false;

    if (server) {
        state = DTLS_STATE_HS_START;
        if (!paused) user->DtlsTimerStart(userCtx, DTLS_SERVER_ACCEPT_TIMEOUT);
    }
    else {
        if (!paused) {
            state = DTLS_STATE_HS_START;
            clearHandshakeSendQueue(true);
            this->DoSSLHandshake();
            user->DtlsTimerStart(userCtx, retransmissionTime);
        }
    }
}

void Dtls::DtlsUnpause() 
{
    if (context && paused) {
        log->Log(LOG_DTLS, "DTLS(%p)::dtlsUnpause");
        paused = false;

        if (server) {
            state = DTLS_STATE_HS_START;
            user->DtlsTimerStart(userCtx, DTLS_SERVER_ACCEPT_TIMEOUT);
        }
        else {
            state = DTLS_STATE_HS_START;
            clearHandshakeSendQueue(true);
            this->DoSSLHandshake();
        }
    }
}

void Dtls::DtlsAbortConnect() 
{
    if (context || state != DTLS_STATE_IDLE) {
        log->Log(LOG_DTLS, "DTLS(%p)::Abort connect context=%p", this, context);
        reset();
    }
}

bool Dtls::DtlsRecv(char * buf, int len, IPaddr remoteAddr, word remotePort)
{
    log->Log(LOG_DTLS, "DTLS(%p)::dtlsRecv len=%d context=%p associated=%d associatedPort=%d ssl=%p context=%p match=%d,%d", this, len, context, associated, associatedPort, ssl, context, ip_match(remoteAddr, associatedAddr), (remotePort == associatedPort));

    if (context) {
        if(!associated || (ip_match(remoteAddr, associatedAddr) && remotePort == associatedPort)) {
            // recv a UDP datagram containing one or many complete DTLS records
            int recordCount = 0;
            int packetLen = len;
            int offset = 0;
            dataSentHs = false;

            while (context && (packetLen > 13)) {
                enum tlsProtocolType type = (enum tlsProtocolType)buf[offset];
                word version = READ_WORD_NET(&buf[offset + 1]);
                word recordLen = READ_WORD_NET(&buf[offset + 11]) + 13;

                if ((type == TLS_PROTOCOL_CHANGE_CIPHER_SPEC || type == TLS_PROTOCOL_ALERT || type == TLS_PROTOCOL_HANDSHAKE || type == TLS_PROTOCOL_APPLICATION) && recordLen <= packetLen) {
                    // Proc DTLS
                    log->Log(LOG_DTLS, "DTLS(%p)::dtlsRecv proc record version=%x type=%d length=%d packetLen=%d state=%d recordCount=%d", this, version, type, recordLen, packetLen, state, recordCount);
                    //log->HexDump(LOG_DTLS, &buf[offset], recordLen);

                    recordCount++;
                    int res = ssl->WriteEncoded(&buf[offset], recordLen);
                    if (res < (int)recordLen) { // Because we user memory buffers, BIO_write always should work completely
                        log->Log(LOG_DTLS, "DTLS(%p)::dtlsRecv number of ssl->WriteEncoded() bytes is %u bytes, but should be %u", this, res, recordLen);
                        log->Log(LOG_DTLS, "DTLS(%p)::dtlsRecv closing connection because of data inconsistency", this);
                        close(DTLS_ERROR_DECODING, false);
                        return true;
                    }

                    log->Log(LOG_DTLS, "DTLS(%p)::dtlsRecv proc record len %d res %d state %d", this, recordLen, res, state);

                    if (state == DTLS_STATE_HS_START) {
                        ParseRxDtlsMessage((byte *)&buf[offset], res);
                        if (associated) {
                            associatedAddr = remoteAddr;
                            associatedPort = remotePort;
                        }
                        this->DoSSLHandshake();
                    }
                    else {
                        if (type == TLS_PROTOCOL_CHANGE_CIPHER_SPEC) this->ReadChangeCipherSpec();
                        else if (type == TLS_PROTOCOL_ALERT) this->ReadAlert((byte *)&buf[offset + 13], recordLen-13);
                        else if (type == TLS_PROTOCOL_APPLICATION) this->ReadSSLData();
                    }
                }
                else {
                    log->Log(LOG_DTLS, "DTLS(%p)::dtlsRecv Ignore record (unexpected type %d or length %d) packetLen %d", this, type, recordLen, packetLen);
                    break;
                }
                packetLen -= recordLen;
                offset += recordLen;
            }
            if (recordCount) {
                if (state == DTLS_STATE_HS_START) {
                    bool sameHandshakeMsg = false;

                    // Handshake message did not trigger a new message
                    if (!dataSentHs) {
                        log->Log(LOG_DTLS, "DTLS(%p)::dtlsRecv no data sent after handshake message handshakeMsgRxLen=%d", this, handshakeMsgRxLen);
                        if (handshakeMsgRx && (handshakeMsgRxLen == len)) sameHandshakeMsg = !memcmp(handshakeMsgRx, buf, handshakeMsgRxLen);
                        if (sameHandshakeMsg) {
                            if (sendQueue.empty() == false) {
                                class HandshakeMsg * data = sendQueue.back();
                                SendSSLDataChunks(userCtx, data->msg, data->len);
                            }
                        }
                    }
                    if (!sameHandshakeMsg) {
                        if (handshakeMsgRx) free(handshakeMsgRx);
                        handshakeMsgRx = (char*)malloc(len);
                        memcpy(handshakeMsgRx, buf, len);
                        handshakeMsgRxLen = len;
                    }
                }
                return true;
            }
        }
    }
    return false;
}

void Dtls::DtlsSendApplicationData(char * buf, int len)
{
    sendApplicationData(buf, len);
}

void Dtls::DtlsTimeout()
{
    if (context) {
        log->Log(LOG_DTLS, "DTLS(%p)::dtlsTimeout state=%d cnt=%d", this, state, retransmissionCount);
        if (state == DTLS_STATE_IDLE) {
            // can be ignored, should not happen
        }
        else if (state == DTLS_STATE_APPLICATION_DATA) {
            // do some retransmissions to make sure the remote party can receive the Finished
            if (retransmissionCount < DTLS_RETRANSMISSION_COUNT_FINISHED) {
                ++retransmissionCount;
                retransmissionTime *= 2;
                sendHandshakeFlight();
            }
        }
        else if (state == DTLS_STATE_SERVER_ACCEPT) {
            close(DTLS_ERROR_TIMEOUT, false);
        }
        else {
            if (retransmissionCount < retransmissionCountMax) {
                ++retransmissionCount;
                retransmissionTime *= 2;
                sendHandshakeFlight();
            }
            else {
                close(DTLS_ERROR_TIMEOUT, false);
            }
        }
    }
}

void Dtls::sendHandshakeFlight()
{
    class HandshakeMsg * data = sendQueue.front();
    while (data) {
        SendSSLDataChunks(userCtx, data->msg, data->len);
        data = data->goNext();
    }
    user->DtlsTimerStart(userCtx, retransmissionTime);
}

void Dtls::clearHandshakeSendQueue(bool fastconnect)
{
    while (this->sendQueue.empty() == false) delete this->sendQueue.front();

    retransmissionCount = 0;
    retransmissionCountMax = fastconnect ? DTLS_RETRANSMISSION_COUNT_FAST : DTLS_RETRANSMISSION_COUNT;
    retransmissionTime = fastconnect ? DTLS_RETRANSMISSION_TIME_FAST : DTLS_RETRANSMISSION_TIME;
}

void Dtls::queueHandshake(tlsHandshakeMessageType messageType, char * buf, int len)
{
    class HandshakeMsg * handshake = new class HandshakeMsg(buf, len);
    sendQueue.push_back(handshake);
}

void Dtls::readApplicationData(char * buf, int len)
{
    log->Log(LOG_DTLS, "DTLS(%p)::Read ApplicationData", this);
    user->DtlsRecvApplicationData(userCtx, buf, len);
}

void Dtls::sendApplicationData(char * buf, int len)
{
    if (context && state == DTLS_STATE_APPLICATION_DATA) {
        log->Log(LOG_DTLS, "DTLS(%p)::sendApplicationData Write ApplicationData", this);
        if (len == 0) {
            user->DtlsSend(userCtx, buf, 0);
        }
        else {
            int sent = ssl->WriteDecoded(buf, len);
            if (sent > 0)
                this->SendSSLData();
            else if (sent < 0)
                log->Log(LOG_TLS, "DTLS(%08x,%08x)::SSLSend ssl->WriteEncoded() failed", this, user);
        }
    }
    else {
        log->Log(LOG_DTLS, "DTLS(%p)::sendApplicationData Write ApplicationData FAILED (wrong state)", this);
    }
}

void Dtls::IoExec(void* context)
{
    user->DtlsConnected(userCtx);
}

void Dtls::DoSSLHandshake()
{
    log->Log(LOG_DTLS, "DTLS(%p)::DoSSLHandshake", this);

    switch (ssl->DoHandshake())
    {
    case OP_SSL_DONE:
        // If the socket was created because of an accept of a server, the other side initiated
        // the SSL handshake. Because of that we will still have data to send, so that the client
        // can finalize the (accepted) handshake...
        if (ssl->BytesToSend())
            this->SendSSLData();

        debug->printf("DTLS(%p)::DoSSLHandshake Done!", this); 

        state = DTLS_STATE_APPLICATION_DATA;
        if(server == false) {
            clearHandshakeSendQueue();
            user->DtlsTimerStop(userCtx); 
        }
        iomux->SetExec(this, NULL);
        this->ssl->SetMtu(DTLS_MAX_FRAGMENT);
        if (handshakeMsgRx) free(handshakeMsgRx);
        handshakeMsgRx = nullptr;
        handshakeMsgRxLen = 0;
        break;

    case OP_SSL_NEED_WRITE:
    case OP_SSL_NEED_READ:
        this->SendSSLData();
        return;

    default: // error
        close(DTLS_ERROR_HANDSHAKE, false);
    }
}

void Dtls::GetAlertDescription(char* buf, tlsAlertType type) 
{
    switch (type) {
    case TLS_ALERT_TYPE_CLOSE_NOTIFY:
        _sprintf(buf, "Close notify");
        break;
    case TLS_ALERT_TYPE_UNEXPECTED_MESSAGE:
        _sprintf(buf, "Unexpected message");
        break;
    case TLS_ALERT_TYPE_BAD_RECORD_MAC:
        _sprintf(buf, "Bad MAC");
        break;
    case TLS_ALERT_TYPE_DECRYPTION_FAILED:
        _sprintf(buf, "Decryption failed");
        break;
    case TLS_ALERT_TYPE_RECORD_OVERFLOW:
        _sprintf(buf, "Record overflow");
        break;
    case TLS_ALERT_TYPE_DECOMPRESSION_FAILURE:
        _sprintf(buf, "Decompression failure");
        break;
    case TLS_ALERT_TYPE_HANDSHAKE_FAILURE:
        _sprintf(buf, "Handshake failure");
        break;
    case TLS_ALERT_TYPE_ILLEGAL_PARAMETER:
        _sprintf(buf, "Illegal parameter");
        break;
    case TLS_ALERT_TYPE_UNKNOWN_CA:
        _sprintf(buf, "Unknown CA");
        break;
    case TLS_ALERT_TYPE_ACCESS_DENIED:
        _sprintf(buf, "Access denied");
        break;
    case TLS_ALERT_TYPE_DECODE_ERROR:
        _sprintf(buf, "Decode error");
        break;
    case TLS_ALERT_TYPE_EXPORT_RESTRICTION:
        _sprintf(buf, "Export restriction");
        break;
    case TLS_ALERT_TYPE_PROTOCOL_VERSION:
        _sprintf(buf, "Protocol version");
        break;
    case TLS_ALERT_TYPE_INSUFFICIENT_SECURITY:
        _sprintf(buf, "Insufficient security");
        break;
    case TLS_ALERT_TYPE_INTERNAL_ERROR:
        _sprintf(buf, "Internal error");
        break;
    case TLS_ALERT_TYPE_USER_CANCELLED:
        _sprintf(buf, "User cancelled");
        break;
    case TLS_ALERT_TYPE_BAD_CERTIFICATE:
        _sprintf(buf, "Bad certificate");
        break;
    default:
        _sprintf(buf, "TLS alert %u", type);
        break;
    }
}

void Dtls::ReadChangeCipherSpec()
{
    log->Log(LOG_DTLS, "DTLS(%p)::ReadChangeCipherSpec", this);
    int res = ssl->ReadDecoded(recvBuffer, recvBufferSize);

    if (res > 0) {
        if (recvBuffer[0] == 0x01) {
            log->Log(LOG_DTLS, "DTLS(%p)::ReadChangeCipherSpec (A try change cipher spec should be implemented...)", this);
            //change_cipher_spec_received = true;
            //try_change_cipher_spec();
        }
        else {
            log->Log(LOG_DTLS, "DTLS(%p)::ReadChangeCipherSpec FAILED len=%d", this, res);
        }
    }
#if 1
    else if (res == OP_SSL_NEED_READ) {
        log->Log(LOG_DTLS, "DTLS(%p)::ReadChangeCipherSpec OP_SSL_NEED_READ", this);
    }
#endif
    else if (res == OP_SSL_ERROR) {
        log->Log(LOG_DTLS, "DTLS(%p)::ReadChangeCipherSpec ssl->ReadDecoded() failed", this);
        //close(DTLS_ERROR_DECODING, false);
    }
    else if (res == OP_SSL_CLOSE) {
        log->Log(LOG_DTLS, "DTLS(%p)::ReadChangeCipherSpec OP_SSL_CLOSE", this);
        close(DTLS_ERROR_CLOSED, false);
    }
}

void Dtls::ReadAlert(byte* buf, int len)
{
    log->Log(LOG_DTLS, "DTLS(%p)::ReadAlert len:%d", this, len);

    if (len >= 2) {
        enum tlsAlertType type = (enum tlsAlertType)buf[0];
        enum tlsAlertLevel level = (enum tlsAlertLevel)buf[1];

        char description[64];
        description[0] = 0;
        GetAlertDescription(description, type);
        log->Log(LOG_DTLS, "DTLS(%p)::ReadAlert type=%s level=%s", this, description, level == TLS_ALERT_LEVEL_FATAL ? "FATAL" : "WARNING");
        if (type == TLS_ALERT_TYPE_CLOSE_NOTIFY) {
            close(DTLS_ERROR_NONE, true);
        }
        else if (level == TLS_ALERT_LEVEL_FATAL) {
            close(DTLS_ERROR_REMOTE, false);
        }
    }
    else {
        log->Log(LOG_DTLS, "DTLS(%p)::ReadAlert FAILED len=%d", this, len);
    }
#if 0
    else if (res == OP_SSL_NEED_READ) {
        log->Log(LOG_DTLS, "DTLS(%p)::ReadAlert OP_SSL_NEED_READ", this);
    }
    else if (res == OP_SSL_ERROR) {
        log->Log(LOG_DTLS, "DTLS(%p)::ReadAlert ssl->ReadDecoded() failed", this);
        //close(DTLS_ERROR_DECODING, false);
    }
    else if (res == OP_SSL_CLOSE) {
        log->Log(LOG_DTLS, "DTLS(%p)::ReadAlert OP_SSL_CLOSE", this);
        close(DTLS_ERROR_CLOSED, false);
    }
#endif
}

void Dtls::ReadSSLData()
{
    log->Log(LOG_DTLS, "DTLS(%p)::ReadSSLData", this);
    int res = ssl->ReadDecoded(recvBuffer, recvBufferSize);

    if (res > 0) {
        byte * tmp = recvBuffer;
        recvBuffer = NULL;
        user->DtlsRecvApplicationData(userCtx, (char *)tmp, res);
        recvBuffer = (byte *)malloc(RECV_BUFFER_BLOCK_SIZE);
        recvBufferSize = RECV_BUFFER_BLOCK_SIZE;
    }
#if 1 // Do nothing, we will receive another DTLS packet sometime
    else if (res == OP_SSL_NEED_READ) {
        log->Log(LOG_DTLS, "DTLS(%p)::ReadSSLData OP_SSL_NEED_READ", this);
    }
#endif
    else if (res == OP_SSL_ERROR) {
        log->Log(LOG_DTLS, "DTLS(%p)::ReadSSLData ssl->ReadDecoded() failed", this);
        close(DTLS_ERROR_DECODING, false);
    }
    else if (res == OP_SSL_CLOSE) {
        close(DTLS_ERROR_CLOSED, false);
    }
}

void Dtls::ParseRxClientHello(byte* buf, int len)
{
    if (len >= 47) {
        byte sessionIdLen = buf[46];
        byte rxCookieLen = buf[47 + sessionIdLen];
        // if dtls option == no cookie -> associated = true
        if (cookie && cookieLen == rxCookieLen && (48 + sessionIdLen + rxCookieLen <= len)) {
            if (memcmp(&buf[48 + sessionIdLen], cookie, cookieLen)) {
                log->HexDump(LOG_DTLS, cookie, cookieLen);
                log->Log(LOG_DTLS, "DTLS(%p)::ParseRxClientHello failed to check cookie cookieLen=%d", this, cookieLen);
                log->HexDump(LOG_DTLS, &buf[48 + sessionIdLen], cookieLen);
            }
            else {
                associated = true;
            }
        }
        log->Log(LOG_DTLS, "DTLS(%p)::ParseRxClientHello cookieLen=%d rxCookieLen=%d len=%d associated=%d", this, cookieLen, rxCookieLen, len, associated);
    }
}

void Dtls::ParseRxServerHelloDone()
{
    log->Log(LOG_DTLS, "DTLS(%p)::ParseRxServerHelloDone", this);

    associated = true;
}

// Get cookie sent by ssl library
void Dtls::ParseTxHelloVerifyRequest(byte* buf, int len)
{
    if (len >= 15) {
        cookieLen = buf[14];
        if (cookieLen > 0 && ((cookieLen + 15) <= len)) {
            if ((cookie = (char*)malloc(cookieLen)) != NULL) {
                memcpy(cookie, &buf[15], cookieLen);
            }
        }
        log->Log(LOG_DTLS, "DTLS(%p)::ParseTxHelloVerifyRequest cookieLen=%d len=%d", this, cookieLen, len);
    }
}

void Dtls::ParseTxHandshakeMessage(byte* buf, int len)
{
    word version = READ_WORD_NET(&buf[1]);
    word recordLen = READ_WORD_NET(&buf[11]) + 13;
    enum tlsHandshakeMessageType msg = (enum tlsHandshakeMessageType)buf[13];

    log->Log(LOG_DTLS, "DTLS(%p)::ParseTxHandshakeMessage version=%x type=%d recordLen=%d", this, version, msg, recordLen);

    switch (msg) {
    case TLS_HANDSHAKE_HELLO_VERIFY_REQUEST:
        ParseTxHelloVerifyRequest(&buf[13], len - 13);
        break;
    default:
        break;
    }
}

void Dtls::ParseTxDtlsMessage(byte* sendBuffer, int len)
{
    switch (sendBuffer[0]) {
    case TLS_PROTOCOL_HANDSHAKE:
        ParseTxHandshakeMessage(sendBuffer, len);
        break;
    default:
        break;
    }
}

void Dtls::ParseRxHandshakeMessage(byte* buf, int len)
{
    word version = READ_WORD_NET(&buf[1]);
    word recordLen = READ_WORD_NET(&buf[11]) + 13;
    enum tlsHandshakeMessageType msg = (enum tlsHandshakeMessageType)buf[13];

    log->Log(LOG_DTLS, "DTLS(%p)::ParseRxHandshakeMessage version=%x type=%d recordLen=%d", this, version, msg, recordLen);

    switch (msg) {
    case TLS_HANDSHAKE_CLIENT_HELLO:
        ParseRxClientHello(&buf[13], len - 13);
        break;
    case TLS_HANDSHAKE_SERVER_HELLO_DONE:
        ParseRxServerHelloDone();
        break;
    default:
        break;
    }
}

void Dtls::ParseRxDtlsMessage(byte* sendBuffer, int len)
{
    switch (sendBuffer[0]) {
    case TLS_PROTOCOL_HANDSHAKE:
        ParseRxHandshakeMessage(sendBuffer, len);
        break;
    default:
        break;
    }
}

int Dtls::SendSSLData()
{
    log->Log(LOG_DTLS, "DTLS(%p)::SendSSLData state=%d", this, state);
    int res = ssl->BytesToSend();
    if (res > (int)sendBufferSize) {
        free(sendBuffer);
        sendBufferSize = get_size_in_blocks(res, SEND_BUFFER_BLOCK_SIZE);
        sendBuffer = (byte *)malloc(sendBufferSize);
    }

    log->Log(LOG_DTLS, "DTLS(%p)::SendSSLData res=%d sendBufferSize=%d", this, res, sendBufferSize);

    res = ssl->ReadEncoded(sendBuffer, sendBufferSize);
    if (res > 0) {
        this->ParseTxDtlsMessage(sendBuffer, res);
        if(state == DTLS_STATE_HS_START) {
            clearHandshakeSendQueue(true);
            class HandshakeMsg * hs = new class HandshakeMsg((char *)sendBuffer, res);
            sendQueue.push_back(hs);
            dataSentHs = true;
        }
        SendSSLDataChunks(userCtx, (char*)sendBuffer, res);
    }
    else if (res == OP_SSL_ERROR) {
        log->Log(LOG_DTLS, "DTLS(%p)::SendSSLData OP_SSL_ERROR ssl->ReadEncoded() failed", this);
        close(DTLS_ERROR_ENCODING, false);
    }
    else if (res == OP_SSL_CLOSE) {
        log->Log(LOG_DTLS, "DTLS(%p)::SendSSLData OP_SSL_CLOSE", this);
        close(DTLS_ERROR_CLOSED, false);
    }
    else log->Log(LOG_DTLS, "DTLS(%p)::SendSSLData ReadEncoded %d queue %d", this, res, !sendQueue.empty());
    return res;
}

void Dtls::SendSSLDataChunks(void* ctx, char* buf, int len)
{
    if (buf[0] == TLS_PROTOCOL_HANDSHAKE) {
        if (len > DTLS_MAX_HANDSHAKE_FRAGMENT) {
            int totLen = len;
            int dataOffset = 0;
            int remLen = totLen;
            while (dataOffset < totLen) {
                if (remLen > DTLS_MAX_HANDSHAKE_FRAGMENT) {
                    int rLen = 0;
                    int dOff = dataOffset;
                    while (rLen < DTLS_MAX_HANDSHAKE_FRAGMENT) {
                        word version = READ_WORD_NET(&buf[dOff + 1]);
                        word recordLen = READ_WORD_NET(&buf[dOff + 11]) + 13;
                        enum tlsHandshakeMessageType msg = (enum tlsHandshakeMessageType)buf[dOff + 13];

                        if ((rLen + recordLen) > DTLS_MAX_HANDSHAKE_FRAGMENT) break;
                        rLen += recordLen;

                        log->Log(LOG_DTLS, "DTLS(%p)::SendSSLDataChunks version=%x type=%d dOff=%d recordLen=%d rLen=%d", this, version, msg, dOff, recordLen, rLen);

                        dOff += recordLen;
                    }

                    user->DtlsSend(userCtx, &buf[dataOffset], rLen);

                    dataOffset += rLen;
                    remLen = totLen - dataOffset;
                }
                else {
                    user->DtlsSend(userCtx, &buf[dataOffset], remLen);
                    break;
                }
            }
        }
        else user->DtlsSend(userCtx, buf, len);
        user->DtlsTimerStart(userCtx, retransmissionTime);
    }
    else user->DtlsSend(userCtx, buf, len);
}
