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

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

#include "sdk/common/interface/socket.h"
#include "sdk/common/interface/time.h"
#include "sdk/common/interface/guuid.h"
#include "common/ilib/str.h"

#include "turn.h"
#include "turn_stun.h"

#define TURN_RETRY_TIMEOUT_MS   100
#define TURN_SLOW_SERVER_ROUNDTRIP_MS 1500
#define TURN_RETRIES_ALLOCATION 4
#define TURN_RETRIES            10

#define TURN_REFRESH_TIMEOUT_MS 5000

#define TURN_STATE_IDLE         0
#define TURN_STATE_AUTHORIZING  1
#define TURN_STATE_STUN_STARTED 2
#define TURN_STATE_ALLOCATING   3
#define TURN_STATE_ALLOCATED    4
#define TURN_STATE_CHANGING     5
#define TURN_STATE_REAUTHORIZING 6
#define TURN_STATE_REALLOCATING 7
#define TURN_STATE_BOUND        8
#define TURN_STATE_TIMEOUT      9
#define TURN_STATE_REFRESHED    10
#define TURN_STATE_REBOUND      11
#define TURN_STATE_RELEASING    12
#define TURN_STATE_CONNECTING   13

#define TURN_PEER_STATE_BIND    0
#define TURN_PEER_STATE_BOUND   1
#define TURN_PEER_STATE_FAILED  2

Turn::Turn(class IIoMux * iomux, class IInstanceLog * const log, class TurnUser *user, void * userCtx, bool rtcp) :
log(log)
{
    this->startChannel = 0x4000;
    this->rtcp = rtcp;
    this->user = user;
    this->userCtx = userCtx;
    this->iomux = iomux;
    state = TURN_STATE_IDLE;
    turnRtcp = false;
    turnTcp = false;
    retries = 0;
    user->turnUsr = NULL;
    user->turnPwd = NULL;
    sendData = NULL;
    peersAllocated = 4;
    peerTable = (turn_peer_t *)malloc(peersAllocated * sizeof(turn_peer_t));
    peerCount = 0;
    realm = NULL;
    nonce = NULL;
    turnAddr = ip_anyaddr;
    turnPort = 0;
    turnAltAddr = ip_anyaddr;
    turnAltPort = 0;
    sendAddr = ip_anyaddr;
    sendPort = 0;
    mappedAddr = ip_anyaddr;
    mappedPort = 0;
    relayedAddr = ip_anyaddr;
    relayedPort = 0;
    localAddr = ip_anyaddr;
    localPort = 0;
    alternateAddr = ip_anyaddr;
    alternatePort = 0;
}

Turn::~Turn()
{
    log->Log(LOG_TURN, "TURN::~TURN(%p) sendData=%p peerCount=%d peersAllocated=%d", this, sendData, peerCount, peersAllocated);
    if(realm) free(realm);
    if(nonce) free(nonce);
    if(peerTable) free(peerTable);
    if(sendData) {
        free(sendData);
        sendData = 0;
    }
}

void Turn::timeout()
{
    char * p = NULL;
    int len;
    unsigned int i;

    log->Log(LOG_TURN, "TURN(%p)::TimerOnTimeout", this);

    if (state == TURN_STATE_AUTHORIZING) {
        state = TURN_STATE_STUN_STARTED;
    }
    if (state == TURN_STATE_CONNECTING) {
        log->Log(LOG_TURN, "TURN(%p)::TimerOnTimeout TURN TCP failed no connect", this);
        release();
        user->TurnFailed(userCtx, Error_connection_failed, "Server not reached");
    }
    else if ((retries >= (unsigned int)((allocateFlags & TURN_FLAG_SLOW_SERVER) ? TURN_SLOW_SERVER_ROUNDTRIP_MS/TURN_RETRY_TIMEOUT_MS + TURN_RETRIES : TURN_RETRIES))
     || ((retries >= (unsigned int)((allocateFlags & TURN_FLAG_SLOW_SERVER) ? TURN_SLOW_SERVER_ROUNDTRIP_MS/TURN_RETRY_TIMEOUT_MS + TURN_RETRIES_ALLOCATION : TURN_RETRIES_ALLOCATION))
      && ((state == TURN_STATE_AUTHORIZING) || (state == TURN_STATE_STUN_STARTED) || (state == TURN_STATE_ALLOCATING)))) {
        if (state == TURN_STATE_RELEASING) {
            log->Log(LOG_TURN, "TURN(%p)::TimerOnTimeout release after timeout", this);
            release();
        }
        else {
            releaseAllocation();
            log->Log(LOG_TURN, "TURN(%p)::TimerOnTimeout allocation failed", this);
            user->TurnFailed(userCtx, Error_allocation_failed, "Allocation failed");
        }
    }
    else {
        retries++;
        if ((state != TURN_STATE_REFRESHED)) {
            if ((state == TURN_STATE_AUTHORIZING) || (state == TURN_STATE_STUN_STARTED) || (state == TURN_STATE_REAUTHORIZING)) {
                p = TurnStun::writeAllocateRequest(&id, 0, 0, 0, 0, allocateFlags, 600, reservationToken, len);
            }
            else if ((state == TURN_STATE_ALLOCATING) || (state == TURN_STATE_REALLOCATING)) {
                //printf("writeAllocateRequest(%d) %x,%x,%x,%x\n", state, user->turnUsr, user->turnPwd, realm, nonce);
                p = TurnStun::writeAllocateRequest(&id, user->turnUsr, user->turnPwd, realm, nonce, allocateFlags, 600, reservationToken, len);
            }
            else if ((state == TURN_STATE_RELEASING) || (state == TURN_STATE_CHANGING)) {
                p = TurnStun::writeRefreshRequest(&id, user->turnUsr, user->turnPwd, realm, nonce, TURN_FLAG_LIFETIME, 0, len);
            }
            else {
                if (state == TURN_STATE_BOUND) {
                    state = TURN_STATE_TIMEOUT;
                    retries = 0;
                    for (i = 0; i < peerCount; i++) {
                        if (peerTable[i].state == TURN_PEER_STATE_BOUND) {
                            peerTable[i].state = TURN_PEER_STATE_BIND;
                        }
                    }
                }
                p = TurnStun::writeRefreshRequest(&id, user->turnUsr, user->turnPwd, realm, nonce, 0, 600, len);
            }
            if(p) { 
                log->Log(LOG_TURN, "TURN(%p)::TimerOnTimeout %s after timeout len=%d", this, TurnStun::getMessageType(p), len);
                user->TurnSend(userCtx, p, len);
                free(p);
            }
        }
        if ((state == TURN_STATE_TIMEOUT) || (state == TURN_STATE_REFRESHED)) {
            for (i = 0; i < peerCount; i++) {
                if (peerTable[i].state == TURN_PEER_STATE_BIND) {
                    p = TurnStun::writeChannelBindRequest(&id, user->turnUsr, user->turnPwd, realm, nonce, startChannel + i, peerTable[i].addr, peerTable[i].port, len);
                    log->Log(LOG_TURN, "TURN(%p)::TimerOnTimeout %s after timeout %d", this, TurnStun::getMessageType(p), len);
                    user->TurnSend(userCtx, p, len);
                    free(p);
                }
            }
        }
        if ((state == TURN_STATE_ALLOCATED) || ((retries >= TURN_RETRIES) && ((state == TURN_STATE_TIMEOUT) || (state == TURN_STATE_REFRESHED) || (state == TURN_STATE_REBOUND)))) {
            retries = 0;
            user->TurnTimerStart(userCtx, TURN_REFRESH_TIMEOUT_MS);
        }
        else {
            user->TurnTimerStart(userCtx, TURN_RETRY_TIMEOUT_MS);
        }
    }
}

void Turn::startAllocation(dword flags)
{
    if ((state == TURN_STATE_IDLE) || (state == TURN_STATE_CONNECTING) || (state == TURN_STATE_AUTHORIZING)) {
        if (state != TURN_STATE_AUTHORIZING) {
            TurnStun::createId(&id);
        }
        allocateFlags = flags;

        int len;
        char * p = TurnStun::writeAllocateRequest(&id, 0, 0, 0, 0, allocateFlags, 600, reservationToken, len);
        if(p) {
            log->Log(LOG_TURN, "TURN(%p,%d,%d)::startAllocation %s len=%d", this, turnRtcp, turnTcp, TurnStun::getMessageType(p), len);
            if(turnTcp == false) {
                for (unsigned int i = 0; i < 1; i++) {
                    user->TurnSend(userCtx, p, len);
                }
            }
            user->TurnSend(userCtx, p, len);
            state = TURN_STATE_AUTHORIZING;
            retries = 0;
            free(p);
        }
        user->TurnTimerStart(userCtx, TURN_RETRY_TIMEOUT_MS);
    }
}

void Turn::release()
{
    if (state != TURN_STATE_IDLE) {
        state = TURN_STATE_IDLE;
        retries = 0;
        peerCount = 0;
        relayedAddr = ip_anyaddr;
        relayedPort = 0;
        if (sendData) {
            free(sendData);
            sendData = 0;
        }
        user->TurnTimerStop(userCtx);
        user->TurnReleased(userCtx);
    }
}

bool Turn::bind(IPaddr & addr, word port)
{
    word ch;
    unsigned int i;

    if ((state == TURN_STATE_ALLOCATED) || (state == TURN_STATE_BOUND) || (state == TURN_STATE_TIMEOUT) || (state == TURN_STATE_REFRESHED) || (state == TURN_STATE_REBOUND)) {
        ch = channelBind(addr, port);
        boundChannel = ch - startChannel;

        for (i = 0; i < boundChannel; i++) {
            peerTable[i].state = TURN_PEER_STATE_BIND;
        }
        for (i = boundChannel + 1; i < peerCount; i++) {
            peerTable[i].state = TURN_PEER_STATE_BIND;
        }
        WRITE_WORD_NET(&dataHeader.b[0], ch);
        if (state == TURN_STATE_ALLOCATED) {
            state = TURN_STATE_REFRESHED;
            retries = 0;
            user->TurnTimerStart(userCtx, TURN_RETRY_TIMEOUT_MS);
        }
        return true;
    }
    log->Log(LOG_TURN, "TURN(%p)::bind failed %d", this, state);
    return false;
}

void Turn::clearAllBinds()
{
    peerCount = 0;
    if (state == TURN_STATE_TIMEOUT) {
        state = TURN_STATE_REBOUND;
    }
    else if (state == TURN_STATE_REFRESHED) {
        state = TURN_STATE_BOUND;
        retries = 0;
        user->TurnTimerStart(userCtx, TURN_REFRESH_TIMEOUT_MS);
    }
}

void Turn::releaseAllocation()
{
    log->Log(LOG_TURN, "Turn(%p)::releaseAllocation state=%d", this, state);

    if ((state != TURN_STATE_IDLE) && (state != TURN_STATE_RELEASING)) {
        if ((state == TURN_STATE_CONNECTING) || (state == TURN_STATE_AUTHORIZING) || (state == TURN_STATE_STUN_STARTED) || (state == TURN_STATE_REAUTHORIZING)) {
            log->Log(LOG_TURN, "TURN(%p)::releaseAllocation release early", this);
            release();
        }
        else {
            TurnStun::createId(&id);

            int len;
            char * p = TurnStun::writeRefreshRequest(&id, user->turnUsr, user->turnPwd, realm, nonce, TURN_FLAG_LIFETIME, 0, len);
            log->Log(LOG_TURN, "TURN(%p)::releaseAllocation msg=%s", this, TurnStun::getMessageType(p));
            if(turnTcp == false) {
                for (unsigned int i = 0; i < 1; i++) {
                    user->TurnSend(userCtx, p, len);
                }
            }
            user->TurnSend(userCtx, p, len);
            state = TURN_STATE_RELEASING;
            retries = 0;
            user->TurnTimerStart(userCtx, TURN_RETRY_TIMEOUT_MS);
            free(p);
        }
    }
}

bool Turn::isAllocating()
{
    return ((state == TURN_STATE_CONNECTING) || (state == TURN_STATE_AUTHORIZING) || (state == TURN_STATE_STUN_STARTED) || (state == TURN_STATE_ALLOCATING));
}

bool Turn::isTurnPacket(char * data, int len)
{
    word messageType;

    if (state == TURN_STATE_IDLE) return false;
    // check head
    if (len < 4) return false;
    // length field
    if (READ_WORD_NET(data + 2) != len - 20) return false;
    // message type
    messageType = READ_WORD_NET(data);
    switch(messageType) {
    case TURN_ALLOCATE_RESPONSE:
    case TURN_ALLOCATE_ERROR:
        break;
    case TURN_REFRESH_RESPONSE:
    case TURN_REFRESH_ERROR:
        break;
    case TURN_CHANNEL_BIND_RESPONSE:
    case TURN_CHANNEL_BIND_ERROR:
        break;
    default:
        return false;
    }
    return true;
}

bool Turn::iceTurnRecv(class ISocket * socket, IPaddr srcAddr, word srcPort, IPaddr dstAddr, word dstPort, char * data, int len)
{
    //byte buf[8];
    word messageType, ch;
    dword flags;
    dword errorCode;
    dword lifetime;
    bool parsingOk;
    char * p;
    unsigned int i;

    if (state == TURN_STATE_IDLE) return false;
    // check head
    if (len < 4) return false;
    // length field
    if (READ_WORD_NET(data + 2) != len - 20) return false;
    // message type
    messageType = READ_WORD_NET(data);
    switch(messageType) {
    case TURN_ALLOCATE_RESPONSE:
    case TURN_ALLOCATE_ERROR:
        if(log->LogFlagSet(LOG_TURN)) {
            char addrSrc[128];
            str::from_ip(addrSrc, 128, &srcAddr);
            char addrDst[128];
            str::from_ip(addrDst, 128, &dstAddr);
            debug->printf("TURN(%p,%s,%d)::iceStunRecv %s:%d <- %s:%d", this, TurnStun::getMessageType(data), state, addrDst, dstPort, addrSrc, srcPort);
        }
        errorCode = 0;
        parsingOk = TurnStun::readAllocateResponse(data, len, user->turnUsr, user->turnPwd, &id, &realm, &nonce, &flags, &errorCode, &lifetime, &relayedAddr, &relayedPort, &mappedAddr, &mappedPort, reservationToken, &alternateAddr, &alternatePort);

        if(relayedPort || mappedPort || alternatePort) log->Log(LOG_TURN, "TURN(%p)::iceTurnRecv readAllocateResponse parsingOk=%d relayedPort=%d mappedPort=%d alternatePort=%d", this, parsingOk, relayedPort, mappedPort, alternatePort);

        if (!parsingOk || (flags & TURN_FLAG_ERROR)) {
            if (!parsingOk) {
                log->Log(LOG_TURN, "TURN(%p)::iceTurnRecv %s parsing error flags %x parsing %d", this, TurnStun::getMessageType(data), flags, parsingOk);
                break;
            }
            if ((((state == TURN_STATE_AUTHORIZING) || (state == TURN_STATE_STUN_STARTED) || (state == TURN_STATE_REAUTHORIZING))
              && ((errorCode == TURN_ERROR_TRY_ALTERNATE) || (errorCode == TURN_ERROR_UNAUTHORIZED)))
             || (((state == TURN_STATE_ALLOCATING) || (state == TURN_STATE_REALLOCATING))
              && (errorCode == TURN_ERROR_STALE_NONCE))) {
                if ((state == TURN_STATE_AUTHORIZING) || (state == TURN_STATE_STUN_STARTED) || (state == TURN_STATE_REAUTHORIZING)) {
                    TurnStun::createId(&id);
                }
                if (errorCode == TURN_ERROR_TRY_ALTERNATE) {
                    log->Log(LOG_TURN, "TURN(%p)::iceTurnRecv %s (%s)", this, TurnStun::getMessageType(data), TurnStun::getErrorPhrase(errorCode));
                    p = TurnStun::writeAllocateRequest(&id, 0, 0, 0, 0, allocateFlags, 600, reservationToken, len);
                }
                else {
                    log->Log(LOG_TURN, "TURN(%p)::iceTurnRecv %s (%s): re-authorizing", this, TurnStun::getMessageType(data), TurnStun::getErrorPhrase(errorCode));
                    p = TurnStun::writeAllocateRequest(&id, user->turnUsr, user->turnPwd, realm, nonce, allocateFlags, 600, reservationToken, len);
                }
                log->Log(LOG_TURN, "TURN(%p)::iceTurnRecv %s after (%s) len %d", this, TurnStun::getMessageType(data), TurnStun::getErrorPhrase(errorCode), len);
                if(turnTcp == false) {
                    for (i = 0; i < 1; i++) {
                        user->TurnSend(userCtx, p, len);
                    }
                }
                user->TurnSend(userCtx, p, len);
                free(p);
                retries = 0;
                if (((state == TURN_STATE_AUTHORIZING) || (state == TURN_STATE_STUN_STARTED) || (state == TURN_STATE_REAUTHORIZING)) && (errorCode != TURN_ERROR_TRY_ALTERNATE)) {
                    state = (state == TURN_STATE_REAUTHORIZING) ? TURN_STATE_REALLOCATING : TURN_STATE_ALLOCATING;
                    if (sendData) {
                        stunSend(sendData, sendDataLen, sendAddr, sendPort);
                    }
                }
                user->TurnTimerStart(userCtx, TURN_RETRY_TIMEOUT_MS);
            }
            else {
                log->Log(LOG_TURN, "TURN(%p)::iceTurnRecv release after allocate error %s", this, TurnStun::getErrorPhrase(errorCode));
                release();
                user->TurnFailed(userCtx, errorCode ? errorCode : Error_abnormal_release_after_allocation, errorCode ? TurnStun::getErrorPhrase(errorCode) : "Abnormal release after allocation");
            }
            break;
        }
        else {
            log->Log(LOG_TURN, "TURN(%p)::iceTurnRecv %s", this, TurnStun::getMessageType(data));
        }
        if ((state == TURN_STATE_AUTHORIZING) || (state == TURN_STATE_STUN_STARTED) || (state == TURN_STATE_ALLOCATING)) {
            state = TURN_STATE_ALLOCATED;
            localAddr = dstAddr;
            localPort = dstPort;
            if (sendData) {
                stunSend(sendData, sendDataLen, sendAddr, sendPort);
            }
            user->TurnAllocated(userCtx);
            retries = 0;
            user->TurnTimerStart(userCtx, TURN_REFRESH_TIMEOUT_MS);
        }
        else if ((state == TURN_STATE_REAUTHORIZING) || (state == TURN_STATE_REALLOCATING)) {
            TurnStun::createId(&id);
            state = TURN_STATE_ALLOCATED;
            if (sendData) {
                stunSend(sendData, sendDataLen, sendAddr, sendPort);
            }
            retries = 0;
            user->TurnTimerStart(userCtx, TURN_REFRESH_TIMEOUT_MS);
        }
        else if (state == TURN_STATE_RELEASING) {
            if ((flags & TURN_FLAG_LIFETIME) && (lifetime == 0)) {
                log->Log(LOG_TURN, "TURN(%p)::iceTurnRecv release after allocate", this);
                release();
            }
        }
        else if (state == TURN_STATE_REBOUND) {
            state = TURN_STATE_BOUND;
            retries = 0;
            user->TurnTimerStart(userCtx, TURN_REFRESH_TIMEOUT_MS);
        }
        break;
    case TURN_REFRESH_RESPONSE:
    case TURN_REFRESH_ERROR:
        errorCode = 0;
        parsingOk = TurnStun::readRefreshResponse(data, len, user->turnUsr, user->turnPwd, &id, &realm, &nonce, &flags, &errorCode, &lifetime);
        if (!parsingOk || (flags & TURN_FLAG_ERROR)) {
            if (!parsingOk) {
                log->Log(LOG_TURN, "TURN(%p)::iceTurnRecv (msg=%s) parsing error", this, TurnStun::getMessageType(data));
                break;
            }
            if (state == TURN_STATE_CHANGING) {
                log->Log(LOG_TURN, "TURN(%p)::iceTurnRecv %s %s", this, TurnStun::getMessageType(data), TurnStun::getErrorPhrase(errorCode));
            }
            else if ((state != TURN_STATE_AUTHORIZING) && (state != TURN_STATE_STUN_STARTED) && (state != TURN_STATE_ALLOCATING) && (state != TURN_STATE_REAUTHORIZING) && (state != TURN_STATE_REALLOCATING) && (errorCode == TURN_ERROR_STALE_NONCE)) {
                log->Log(LOG_TURN, "TURN(%p)::iceTurnRecv (msg=%s) (error=%s)", this, TurnStun::getMessageType(data), TurnStun::getErrorPhrase(errorCode));
                TurnStun::createId(&id);
                char * data; 
                int lenMsg;
                if (state == TURN_STATE_RELEASING) {
                    data = TurnStun::writeRefreshRequest(&id, user->turnUsr, user->turnPwd, realm, nonce, TURN_FLAG_LIFETIME, 0, lenMsg);
                }
                else {
                    data = TurnStun::writeRefreshRequest(&id, user->turnUsr, user->turnPwd, realm, nonce, 0, 600, lenMsg);
                }
                log->Log(LOG_TURN, "TURN(%p)::iceTurnRecv (msg=%s) after (error=%s)", this, TurnStun::getMessageType(data), TurnStun::getErrorPhrase(errorCode));
                if(turnTcp == false) {
                    for (i = 0; i < 1; i++) {
                        user->TurnSend(userCtx, data, lenMsg);
                    }
                }
                user->TurnSend(userCtx, data, lenMsg);
                retries = 0;
                user->TurnTimerStart(userCtx, TURN_RETRY_TIMEOUT_MS);
            }
            else {
                log->Log(LOG_TURN, "TURN(%p)::iceTurnRecv release after refresh error (error=%s)", this, TurnStun::getErrorPhrase(errorCode));
                release();
                user->TurnFailed(userCtx, errorCode ? errorCode : Error_abnormal_release_after_refresh, errorCode ? TurnStun::getErrorPhrase(errorCode) : "Abnormal release after refresh");
            }
            break;
        }
        else {
            log->Log(LOG_TURN, "TURN(%p)::iceTurnRecv %s ok", this, TurnStun::getMessageType(data), parsingOk);
        }
        if (state == TURN_STATE_CHANGING) {
            if ((flags & TURN_FLAG_LIFETIME) && (lifetime == 0)) {
                TurnStun::createId(&id);
                int lenMsg;
                char * data = TurnStun::writeAllocateRequest(&id, user->turnUsr, user->turnPwd, realm, nonce, allocateFlags, 600, reservationToken, lenMsg);
                log->Log(LOG_TURN, "TURN(%p)::iceTurnRecv %s", this, TurnStun::getMessageType(data));
                if(turnTcp == false) {
                    for(i = 0; i < 1; i++) {
                        user->TurnSend(userCtx, data, lenMsg);
                    }
                }
                user->TurnSend(userCtx, data, lenMsg);
                retries = 0;
                state = TURN_STATE_REALLOCATING;
                user->TurnTimerStart(userCtx, TURN_RETRY_TIMEOUT_MS);
            }
        }
        else if (state == TURN_STATE_TIMEOUT) {
            state = TURN_STATE_REFRESHED;
        }
        else if (state == TURN_STATE_RELEASING) {
            if ((flags & TURN_FLAG_LIFETIME) && (lifetime == 0)) {
                log->Log(LOG_TURN, "TURN(%p)::iceTurnRecv release normal", this);
                release();
            }
        }
        else if (state == TURN_STATE_REBOUND) {
            state = TURN_STATE_BOUND;
            retries = 0;
            user->TurnTimerStart(userCtx, TURN_RETRY_TIMEOUT_MS);
        }
        break;
    case TURN_CHANNEL_BIND_RESPONSE:
    case TURN_CHANNEL_BIND_ERROR:
        errorCode = 0;
        parsingOk = TurnStun::readChannelBindResponse(data, len, user->turnUsr, user->turnPwd, &id, &realm, &nonce, &ch, &flags, &errorCode);
        if (!parsingOk || (ch < startChannel) || (ch >= startChannel+peerCount) || (flags & TURN_FLAG_ERROR)) {
            if (!parsingOk) {
                log->Log(LOG_TURN, "TURN(%p)::iceTurnRecv (msg=%s) parsing error", this, TurnStun::getMessageType(data));
                break;
            }
            if ((ch < startChannel) || (ch >= startChannel+peerCount)) {
                log->Log(LOG_TURN, "TURN(%p)::iceTurnRecv (msg=%s) bad channel", this, TurnStun::getMessageType(data));
                break;
            }
            log->Log(LOG_TURN, "TURN(%p)::iceTurnRecv (msg=%s) (error=%s)", this, TurnStun::getMessageType(data), TurnStun::getErrorPhrase(errorCode));
            if (errorCode != TURN_ERROR_STALE_NONCE) {
                peerTable[ch-startChannel].state = TURN_PEER_STATE_FAILED;
            }
            if (((state == TURN_STATE_TIMEOUT) || (state == TURN_STATE_REFRESHED)) && (errorCode == TURN_ERROR_STALE_NONCE)) {
                TurnStun::createId(&id);
                int lenMsg;
                char * data = TurnStun::writeChannelBindRequest(&id, user->turnUsr, user->turnPwd, realm, nonce, ch, peerTable[ch-startChannel].addr, peerTable[ch-startChannel].port, lenMsg);
                log->Log(LOG_TURN, "TURN(%p)::iceTurnRecv (msg=%s) after (error=%s)", this, TurnStun::getMessageType(data), TurnStun::getErrorPhrase(errorCode));
                if(turnTcp == false) {
                    for (i = 0; i < 1; i++) {
                        user->TurnSend(userCtx, data, lenMsg);
                    }
                }
                user->TurnSend(userCtx, data, lenMsg);
                free(data);
                retries = 0;
                user->TurnTimerStart(userCtx, TURN_RETRY_TIMEOUT_MS);
            }
            break;
        }
        else {
            log->Log(LOG_TURN, "TURN(%p)::iceTurnRecv (msg=%s) state=%d ch=%x (%x+%x)", this, TurnStun::getMessageType(data), state, ch, startChannel, boundChannel);
        }
        peerTable[ch-startChannel].state = TURN_PEER_STATE_BOUND;
        i = 0;
        while ((i < peerCount) && (peerTable[i].state != TURN_PEER_STATE_BIND)) {
            i++;
        }
        if (i == peerCount) {
            if (state == TURN_STATE_TIMEOUT) {
                state = TURN_STATE_REBOUND;
            }
            else if (state == TURN_STATE_REFRESHED) {
                state = TURN_STATE_BOUND;
                retries = 0;
                user->TurnTimerStart(userCtx, TURN_REFRESH_TIMEOUT_MS);
            }
        }
        break;
    default:
        return false;
    }
    return true;
}

void Turn::stunSend(char * data, int len, IPaddr addr, word port)
{
    if (state != TURN_STATE_BOUND) log->Log(LOG_TURN, "TURN(%p)::stunSend %d state %d port %d relayedPort=%d", this, len, state, port, relayedPort);
    if ((state == TURN_STATE_IDLE) || (state == TURN_STATE_CONNECTING) || (state == TURN_STATE_RELEASING)) {
        if(data && sendData && (len == sendDataLen) && !memcmp(data, sendData, sendDataLen)) {
            free(sendData);
            sendData = 0;
            sendDataLen = 0;
        }
    }
    else if ((state == TURN_STATE_AUTHORIZING) || (state == TURN_STATE_STUN_STARTED) || (state == TURN_STATE_REAUTHORIZING)) {
        if (sendData) {
            free(sendData);
        }
        sendDataLen = len;
        sendData = (char *)malloc(len);
        memcpy(sendData, data, len);
        sendAddr = addr;
        sendPort = port;
    }
    else {
        int bufLen = 4 + len + (turnTcp ? (-len & 0x3) : 0);
        char * buf = (char *)malloc(bufLen);  // + padding
        word ch = channelBind(addr, port);
        WRITE_WORD_NET(&buf[0], ch);
        WRITE_WORD_NET(&buf[2], len);
        memcpy(&buf[4], data, len);
        user->TurnSend(userCtx, buf, bufLen);
        free(buf);
    }
}

word Turn::channelBind(IPaddr & addr, word port)
{
    unsigned int ch;
    turn_peer_t *peers;

    if ((state == TURN_STATE_IDLE) || (state == TURN_STATE_CONNECTING) || (state == TURN_STATE_RELEASING)) {
        return 0;
    }
    else if ((state == TURN_STATE_AUTHORIZING) || (state == TURN_STATE_STUN_STARTED) || (state == TURN_STATE_REAUTHORIZING)) {
        return 0;
    }
    else {
        ch = 0;
        while((ch < peerCount) && ((peerTable[ch].port != port) || !ip_match(peerTable[ch].addr, addr))) {
            ch++;
        }
        if (ch == peerCount) {
            if (peerCount == peersAllocated) {
                peersAllocated *= 2;
                peers = (turn_peer_t *)malloc(peersAllocated * sizeof(turn_peer_t));
                memcpy(peers, peerTable, peerCount * sizeof(turn_peer_t));
                free(peerTable);
                peerTable = peers;
            }
            peerTable[peerCount].addr = addr;
            peerTable[peerCount].port = port;
            peerTable[peerCount].state = TURN_PEER_STATE_BIND;
            peerCount++;
        }
        if (peerTable[ch].state == TURN_PEER_STATE_BIND) {
            int len;
            char *p = TurnStun::writeChannelBindRequest(&id, user->turnUsr, user->turnPwd, realm, nonce, startChannel+ch, addr, port, len);
            if(log->LogFlagSet(LOG_TURN)) {
                char addrPeer[128];
                str::from_ip(addrPeer, 128, &peerTable[ch].addr);
                debug->printf("TURN(%p,%d)::channelBind %s len=%d addr=(%s:%d) ch=0x%x ", this, peersAllocated, TurnStun::getMessageType(p), len, addrPeer, port, startChannel + ch);
            }
            if(turnTcp == false) {
                for(unsigned int i = 0; i < 1; i++) {
                    user->TurnSend(userCtx, p, len);
                }
            }
            user->TurnSend(userCtx, p, len);
            free(p);
        }
        return ((word)(startChannel+ch));
    }
}

size_t Turn::turnTcpParse(const byte * tcpData, size_t len)
{
    if (len < 4) return 0;
    size_t msgLength = 0;
    if((tcpData[0] >= 0x40) && (tcpData[0] < 0x80)) {
        int recvLen = READ_WORD_NET(&tcpData[2]);
        msgLength = (4 + recvLen + (-recvLen & 0x3));
    }
    else {
        msgLength = READ_WORD_NET(&tcpData[2]) + 20;  // no channel message, just normal
    }
    return ((len < msgLength) ? 0 : msgLength);
}

void Turn::turnTcpConnecting()
{
    turnTcp = true;
    if (state == TURN_STATE_IDLE) {
        log->Log(LOG_TURN, "TURN(%p)::turnTcpConnecting", this);
        state = TURN_STATE_CONNECTING;
        retries = 0;
        user->TurnTimerStart(userCtx, (allocateFlags & TURN_FLAG_SLOW_SERVER) ?
            TURN_SLOW_SERVER_ROUNDTRIP_MS + TURN_RETRIES_ALLOCATION * TURN_RETRY_TIMEOUT_MS :
            TURN_RETRIES_ALLOCATION * TURN_RETRY_TIMEOUT_MS);
    }
}

void Turn::turnTcpShutdown()
{
    if (state == TURN_STATE_RELEASING) {
        release();
    }
    else if (state != TURN_STATE_IDLE) {
        log->Log(LOG_TURN, "TURN(%p)::turnTcpShutdown", this);
        release();
        user->TurnFailed(userCtx, Error_abnormal_shutdown, "Abnormal shutdown");
    }
}
