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

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

#include "common/ilib/str.h"
#include "sdk/common/interface/socket.h"
#include "sdk/common/interface/random.h"
#include "sdk/common/interface/time.h"
#include "sdk/common/interface/guuid.h"
#include "sdk/common/interface/media.h"
#include "ice.h"
#include "ice_stun.h"

#define ICE_PREF_TYPE_HOST        126
#define ICE_PREF_TYPE_SRFLX       100
#define ICE_PREF_TYPE_PRFLX       110
#define ICE_PREF_TYPE_RELAY       0
#define ICE_PREF_LOCL_IP4_HIGH    60000
#define ICE_PREF_LOCL_IP4_LOW     10000
#define ICE_PREF_LOCL_IP6_LOCAL   30000
#define ICE_PREF_LOCL_IP6_GLOBAL  20000
#define ICE_PREF_SIG_IP4_HIGH     65000
#define ICE_PREF_SIG_IP4_LOW      15000
#define ICE_PREF_SIG_IP6_LOCAL    35000
#define ICE_PREF_COMP_RTP         255
#define ICE_PREF_COMP_RTCP        254

#define ICE_SLOW_SERVER_ROUNDTRIP 1500
#define ICE_RETRY_CHECK           100
#define ICE_RETRY_GATHER          3

#define ICE_PRFLX_MAX             4

static ulong64 calculateCheckPriority(bool controlling, dword local, dword remote)
{
    ulong64 g = controlling ? local : remote;
    ulong64 d = controlling ? remote : local;
    return (MIN(g, d) << 32) + (MAX(g, d) << 1) + (g > d ? 1 : 0);
}

Ice::Ice(class IIoMux * iomux, class IInstanceLog * const log, int interfaceIndex, int totalNumInterfaces):
log(log)
{
    this->iomux = iomux;

    iceDisabled = false;

    memset(&stun1, 0x0, sizeof(ChannelCandidate));
    memset(&stun2, 0x0, sizeof(ChannelCandidate));

    memset(&stun1RtpId, 0x0, sizeof(OS_GUID));
    memset(&stun1RtcpId, 0x0, sizeof(OS_GUID));
    memset(&stun2RtpId, 0x0, sizeof(OS_GUID));
    memset(&stun2RtcpId, 0x0, sizeof(OS_GUID));

    stun1RtpRequest = 0;
    stun1RtcpRequest = 0;
    stun2RtpRequest = 0;
    stun2RtcpRequest = 0;
    stun3RtpRequest = 0;
    stun3RtcpRequest = 0;
    stun4RtpRequest = 0;
    stun4RtcpRequest = 0;
    memset(&stun1, 0, sizeof(ChannelCandidate));
    memset(&stun2, 0, sizeof(ChannelCandidate));

    iceCheckConnectionRtp = 0;
    iceCheckConnectionRtcp = 0;
    iceCheckConnectionRtpTcp = 0;
    iceCheckPriority = 0;
    currentCheck = 0;
    nominatedCheck = 0;
    activeChecks = 0;
    prflxCheckCount = 0;
    waitLast = false;
    nominating = false;
    turnOnly = false;
    iceNoHost = false;
    iceStun1Addr = NULL;
    iceStun1Addr2 = NULL;
    iceStun2Addr = NULL;
    iceStun2Addr2 = NULL;
    iceStun1Port = 0;
    iceStun1Port2 = 0;
    iceStun2Port = 0;
    iceStun2Port2 = 0;

    iceConnectedLocalAddr = ip_anyaddr;
    iceConnectedLocalRtpPort = 0;
    iceConnectedLocalRtcpPort = 0;
    iceConnectedRemoteAddr = ip_anyaddr;
    iceConnectedRemoteRtpPort = 0;
    iceConnectedRemoteRtcpPort = 0;

    iceRtcp = true;
    iceGatheredHost = false;
    iceGatheredStun = false;
    iceGatheredTurn = false;
    iceCandidateCounter = 0;
    iceIp4Preference = true;
    iceLocalAddr = NULL;
    iceControlling = false;
    iceRoleChanged = false;
    iceTiebreaker = 0;
    iceState = idle;
    iceLocal.common.fingerprintLen = 0;
    iceLocalConnected = &iceLocal;
    iceStunDraft2 = false;

    this->interfaceIndex = interfaceIndex;
    this->totalNumInterfaces = totalNumInterfaces;
}

Ice::~Ice()
{
    log->Log(LOG_ICE, "ICE::~ICE(%p)", this);
    clearStun();
    clearChecks();
}

void Ice::clearStun()
{
    if (stun1RtpRequest) free(stun1RtpRequest);
    if (stun1RtcpRequest) free(stun1RtcpRequest);
    if (stun2RtpRequest) free(stun2RtpRequest);
    if (stun2RtcpRequest) free(stun2RtcpRequest);
    if (stun3RtpRequest) free(stun3RtpRequest);
    if (stun3RtcpRequest) free(stun3RtcpRequest);
    if (stun4RtpRequest) free(stun4RtpRequest);
    if (stun4RtcpRequest) free(stun4RtcpRequest);
    stun1RtpRequest = 0;
    stun1RtcpRequest = 0;
    stun2RtpRequest = 0;
    stun2RtcpRequest = 0;
    stun3RtpRequest = 0;
    stun3RtcpRequest = 0;
    stun4RtpRequest = 0;
    stun4RtcpRequest = 0;
    memset(&stun1, 0, sizeof(ChannelCandidate));
    memset(&stun2, 0, sizeof(ChannelCandidate));
    memset(&stun3, 0, sizeof(ChannelCandidate));
    memset(&stun4, 0, sizeof(ChannelCandidate));
    stunNumTry = 0;
    iceLocal.common.fingerprintLen = 0;
}

void Ice::clearChecks()
{
    class IceCheck * check;
    while ((check = (class IceCheck *)dynamic_cast<class IceCheckBtreeConnectionRtp *>(iceCheckConnectionRtp))) {
        removeCheck(false, check);
    }
    while ((check = (class IceCheck *)dynamic_cast<class IceCheckBtreeConnectionRtpTcp *>(iceCheckConnectionRtpTcp))) {
        removeCheck(true, check);
    }
    while (preparedChecks.front()) {
        class IceCheck * le = preparedChecks.front();
        delete le;
    }
    prflxCheckCount = 0;
    currentCheck = 0;
    nominatedCheck = 0;
    activeChecks = 0;
    nominating = false;
}

void Ice::iceAbortConnect(enum IceAbortReason disc)
{
    log->Log(LOG_ICE, "ICE(%p)::iceAbortConnect iceState=%d", this, iceState);

    if (iceState != idle && iceState != gathering && iceState != waiting) {
        if(log->LogFlagSet(LOG_ICE)) {
            if (iceState == connecting) {
                char* localAddr = NULL;
                for (int i = 0; i < iceLocalConnected->common.count; i++) {
                    if (iceLocalConnected->candidate[i].type == CHANNEL_CANDIDATE_HOST) {
                        localAddr = (char*)malloc(128);
                        str::from_ip(localAddr, 128, (IPaddr*)&iceLocalConnected->candidate[i].addr);
                        break;
                    }
                }
                char tmp[8192];
                int l = _sprintf(tmp, "ICE(%p)::Checklist for interface %s (%d/%d)", this, localAddr ? localAddr : "", interfaceIndex, totalNumInterfaces);
                if(localAddr) free(localAddr);
                class IceCheck * check = (class IceCheck *)dynamic_cast<class IceCheckBtreePriority *>(iceCheckPriority->btree_find_first_left(0));
                while (check) {
                    l += check->trace(tmp + l, sizeof(tmp)-l, "-");
                    check = (class IceCheck *)dynamic_cast<class IceCheckBtreePriority *>(iceCheckPriority->btree_find_next_left(&check->priority));
                }
                debug->printf("%s", tmp);

                l = _sprintf(tmp, "ICE(%p)::Candidates", this);
                l += ChannelIce::trace(iceLocalConnected, "LOCAL", tmp + l, sizeof(tmp)-l);
                l += ChannelIce::trace(&iceRemote, "REMOTE", tmp + l, sizeof(tmp)-l);
                debug->printf("%s", tmp);

                if(disc == IceAbortReason::ICE_PATH_FOUND)
                    debug->printf("ICE(%p)::Connection aborted (ICE working path already found)", this);
                else if (disc == IceAbortReason::CALL_TERMINATED)
                    debug->printf("ICE(%p)::Connection aborted (Call terminated)", this);
                else if (disc == IceAbortReason::RESET_ICE_PATH)
                    debug->printf("ICE(%p)::Connection aborted (Reset ICE path)", this);
                else
                    debug->printf("ICE(%p)::Connection aborted (Unknown)", this);
                debug->printf("ICE(%p)::Conclusion ABORTED", this);
#ifdef MODULE_TEST
                TEST_WATCH("iceAbortConnect");
#endif
            }
            else {
                log->Log(LOG_ICE, "ICE(%p)::Reset", this);
            }
        }
        IceTimerStop();
        clearChecks();
        iceRemote.clear();
        iceState = waiting;
        iceStartKeepAlive();
    }
}

bool Ice::iceStunRecv(class ISocket * socket, IPaddr srcAddr, word srcPort, IPaddr dstAddr, word dstPort, char * data, int len, word turnCh)
{
    if (iceDisabled || !IceStun::isStun(data, len)) return false;
    if (log->LogFlagSet(LOG_ICE)) {
        char addrSrc[128];
        str::from_ip(addrSrc, 128, &srcAddr);
        char addrDst[128];
        str::from_ip(addrDst, 128, &dstAddr);
        debug->printf("ICE(%p, %s) iceStunRecv STUN %s:%d <- %s:%d state %d turnCh = 0x%x", this, IceStun::getMessageType(data), addrDst, dstPort, addrSrc, srcPort, iceState, turnCh);
    }
    if (iceState == gathering && !iceGatheredStun) {
        IPaddr mappedAddr = ip_anyaddr;
        word mappedPort = 0;

        if (socket == rtp4()) {
            if (stun1RtpRequest && IceStun::readResponse(data, len, 0, &stun1RtpId, &mappedAddr, &mappedPort) && !stun1.rtpPort) {
                stun1.addr = mappedAddr;
                stun1.rtpPort = mappedPort;
                stun1.relatedAddr = localAddrStorage;
                stun1.relatedRtpPort = iceLocalPort;
            }
            else if (stun2RtpRequest && IceStun::readResponse(data, len, 0, &stun2RtpId, &mappedAddr, &mappedPort) && !stun2.rtpPort) {
                stun2.addr = mappedAddr;
                stun2.rtpPort = mappedPort;
                stun2.relatedAddr = localAddrStorage;
                stun2.relatedRtpPort = iceLocalPort;
            }
            else if (stun3RtpRequest && IceStun::readResponse(data, len, 0, &stun3RtpId, &mappedAddr, &mappedPort) && !stun3.rtpPort) {
                stun3.addr = mappedAddr;
                stun3.rtpPort = mappedPort;
                stun3.relatedAddr = localAddrStorage;
                stun3.relatedRtpPort = iceLocalPort;
            }
            else if (stun4RtpRequest && IceStun::readResponse(data, len, 0, &stun4RtpId, &mappedAddr, &mappedPort) && !stun4.rtpPort) {
                stun4.addr = mappedAddr;
                stun4.rtpPort = mappedPort;
                stun4.relatedAddr = localAddrStorage;
                stun4.relatedRtpPort = iceLocalPort;
            }
        }
        else if (socket == rtcp4()) {
            if (stun1RtcpRequest && IceStun::readResponse(data, len, 0, &stun1RtcpId, &mappedAddr, &mappedPort) && !stun1.rtcpPort) {
                stun1.rtcpPort = mappedPort;
                stun1.relatedRtcpPort = iceLocalRtcpPort;
            }
            else if (stun2RtcpRequest && IceStun::readResponse(data, len, 0, &stun2RtcpId, &mappedAddr, &mappedPort) && !stun2.rtcpPort) {
                stun2.rtcpPort = mappedPort;
                stun2.relatedRtcpPort = iceLocalRtcpPort;
            }
            else if (stun3RtcpRequest && IceStun::readResponse(data, len, 0, &stun3RtcpId, &mappedAddr, &mappedPort) && !stun3.rtcpPort) {
                stun3.rtcpPort = mappedPort;
                stun3.relatedRtcpPort = iceLocalRtcpPort;
            }
            else if (stun4RtcpRequest && IceStun::readResponse(data, len, 0, &stun4RtcpId, &mappedAddr, &mappedPort) && !stun4.rtcpPort) {
                stun4.rtcpPort = mappedPort;
                stun4.relatedRtcpPort = iceLocalRtcpPort;
            }
        }
        else {
            log->Log(LOG_ICE, "ICE(%p)::iceStunRecv Unexpected message", this);
        }

        bool stun1Ready = !is_anyaddr(stun1.addr) && stun1.rtpPort && (!iceRtcp || stun1.rtcpPort);
        bool stun2Ready = !is_anyaddr(stun2.addr) && stun2.rtpPort && (!iceRtcp || stun2.rtcpPort);
        bool stun3Ready = !is_anyaddr(stun3.addr) && stun3.rtpPort && (!iceRtcp || stun3.rtcpPort);
        bool stun4Ready = !is_anyaddr(stun4.addr) && stun4.rtpPort && (!iceRtcp || stun4.rtcpPort);
        bool server1Ready = (stun1Ready || stun2Ready || (!iceStun1Port && !iceStun1Port2));
        bool server2Ready = (stun3Ready || stun4Ready || (!iceStun2Port && !iceStun2Port2));
        if (server1Ready && server2Ready) {
            ChannelCandidate * media = stun1Ready ? &stun1 : stun2Ready ? &stun2 : 0;
            ChannelCandidate * media2 = stun3Ready ? &stun3 : stun4Ready ? &stun4 : 0;
            icePrepareKeepAlive(stun1Ready, stun2Ready && !stun1Ready, stun3Ready, stun4Ready && !stun3Ready);
            iceStunInitialized(media, media2);
        }
    }
    else if (iceState == connecting) {
        bool rtcp = (socket == rtcp4());
        bool tcp = (socket == rtpTcp4());
        word messageType = READ_WORD_NET(data);

        if (messageType == BINDING_REQUEST) {
            OS_GUID id;
            dword priority = 0;
            ulong64 tiebreaker = 0;
            bool controlling = false;
            bool nominated = false;
            dword microsoftImplementationVersion = 0;
            int error = IceStun::readRequest(data, len, iceLocalConnected->common.usr, iceRemote.common.usr, iceLocalConnected->common.pwd, &id, &priority, &controlling, &tiebreaker, &nominated, true, 0, &microsoftImplementationVersion);
            if ((microsoftImplementationVersion == 1 || microsoftImplementationVersion == 2) && !iceStunDraft2) {
                iceSwitchToStunDraft2();
                return true;
            }
            if (nominated) controlling = true;

            if (!error) {
                if (iceControlling && controlling) {
                    if (iceTiebreaker >= tiebreaker) {
                        error = STUN_ERROR_ROLE_CONFLICT;
                        log->Log(LOG_ICE, "ICE(%p)::iceStunRecv Role conflict detected, indicate and stay controlling", this);
                    }
                    else if (!iceRoleChanged) {
                        log->Log(LOG_ICE, "ICE(%p)::iceStunRecv Role conflict detected, try switch to controlled", this);
                        iceSwitchRole();
                    }
                }
                else if (!iceControlling && !controlling) {
                    if (iceTiebreaker<tiebreaker) {
                        error = STUN_ERROR_ROLE_CONFLICT;
                        log->Log(LOG_ICE, "ICE(%p)::iceStunRecv Role conflict detected, indicate and stay controlled", this);
                    }
                    else if (!iceRoleChanged) {
                        log->Log(LOG_ICE, "ICE(%p)::iceStunRecv Role conflict detected, try switch to controlling", this);
                        iceSwitchRole();
                    }
                }
            }

            if (error) {
                if (error != STUN_ERROR_BAD_REQUEST) {
                    int len;
                    char * data = IceStun::writeResponse(&id, error, error == STUN_ERROR_ROLE_CONFLICT ? iceLocalConnected->common.pwd : 0, len);
                    IceSendto(socket, data, len, srcAddr, srcPort, turnCh != 0);
                    free(data); 
                }
            }
            else {
                class IceCheck * check = findCheck(rtcp, tcp, dstAddr, dstPort, srcAddr, srcPort);
                if (check) {
                    debug->printf("ICE(%p)::iceStunRecv check=%p==%p iceControlling=%d nominated=%d state=%d", this, check, nominatedCheck, iceControlling, nominated, check->rtpState);
                    if (!iceControlling && nominated) {
                        if (!nominatedCheck && (check->rtpState == IceCheck::valid || check->rtcpState == IceCheck::valid)) {
                            nominatedCheck = check;
                        }
                        if (check == nominatedCheck) {
                            int len;
                            char * data = IceStun::writeResponse(&id, iceLocalConnected->common.pwd, srcAddr, srcPort, len, iceStunDraft2, 0, iceStunDraft2 ? iceRemote.common.usr : 0, iceStunDraft2 ? iceLocalConnected->common.usr : 0);
                            IceSendto(socket, data, len, srcAddr, srcPort, turnCh != 0);
                            free(data);
                            if (rtcp) {
                                check->rtcpNominated = true;
                                log->Log(LOG_ICE, "ICE(%p,%u)::iceStunRecv RTCP nominated", this, turnCh);
                            }
                            else {
                                check->rtpNominated = true;
                                log->Log(LOG_ICE, "ICE(%p,%u)::iceStunRecv RTP nominated", this, turnCh);
                                if (!check->connection.localRtcpPort) {
                                    check->rtcpNominated = true;
                                    check->rtcpState = IceCheck::valid;
                                    log->Log(LOG_ICE, "ICE(%p)::iceStunRecv RTCP implicitly nominated", this);
                                }
                            }
                        }
                        else {
                            log->Log(LOG_ICE, "ICE(%p)::iceStunRecv %s ", this, rtcp ? "RTCP nomination ignored" : "RTP nomination ignored");
                        }

                        // prepare triggered requests
                        if (check->rtpState == IceCheck::frozen || check->rtpState == IceCheck::failed) {
                            check->rtpState = IceCheck::inProgress;
                            check->rtpRetry = 0;
                            check->createStunRtp(iceLocalConnected->common.usr, iceRemote.common.usr, iceRemote.common.pwd, iceControlling, iceTiebreaker, false, iceStunDraft2);
                        }
                        if (iceRtcp && check->connection.localRtcpPort && (check->rtcpState == IceCheck::frozen || check->rtcpState == IceCheck::failed)) {
                            check->rtcpState = IceCheck::inProgress;
                            check->rtcpRetry = 0;
                            check->createStunRtcp(iceLocalConnected->common.usr, iceRemote.common.usr, iceRemote.common.pwd, iceControlling, iceTiebreaker, false, iceStunDraft2);
                        }
                        // do triggered requests
                        if (check->rtpState == IceCheck::inProgress) {
                            log->Log(LOG_ICE, "ICE(%p)::iceStunRecv RTP send triggered request", this);
                            //ice_sendfromto(check->rtpSocket, check->connection.localAddr, check->connection.local_rtp_port, check->connection.remote_addr, check->connection.remote_rtp_port, new class packet(check->stun_rtp_request));
                            IceSendto(check->rtpSocket, check->stunRtpRequest, check->stunRtpRequestLen, check->connection.remoteAddr, check->connection.remoteRtpPort, check->turn);
                        }
                        if (check->rtcpState == IceCheck::inProgress) {
                            log->Log(LOG_ICE, "ICE(%p)::iceStunRecv RTCP send triggered request", this);
                            //ice_sendfromto(check->rtcpSocket, check->connection.local_addr, check->connection.local_rtcp_port, check->connection.remote_addr, check->connection.remote_rtcp_port, new class packet(check->stun_rtcp_request));
                            IceSendto(check->rtcpSocket, check->stunRtcpRequest, check->stunRtcpRequestLen, check->connection.remoteAddr, check->connection.remoteRtcpPort, check->turn);
                        }
                        // finished?
                        if (check->rtpState == IceCheck::valid && check->rtpNominated && (!iceRtcp || (check->rtcpState == IceCheck::valid && check->rtcpNominated))) {
                            iceConnected(turnCh != 0);
                        }
                    }
                    else {
                        int len;
                        char * data = IceStun::writeResponse(&id, iceLocalConnected->common.pwd, srcAddr, srcPort, len, iceStunDraft2, 0, iceStunDraft2 ? iceRemote.common.usr : 0, iceStunDraft2 ? iceLocalConnected->common.usr : 0);
                        //ice_sendfromto((class socket *)src, dst_addr, dst_port, src_addr, src_port, ice_stun::writeResponse(&id, ice_local_connected->common.pwd, src_addr, src_port, ice_stun_draft2, 0, ice_stun_draft2 ? ice_remote.common.usr : 0, ice_stun_draft2 ? ice_local_connected->common.usr : 0));
                        IceSendto(socket, data, len, srcAddr, srcPort, turnCh != 0);
                        free(data);
                        if (iceControlling) {
                            if (!rtcp && check->rtpState == IceCheck::nominating) {
                                log->Log(LOG_ICE, "ICE(%p)::iceStunRecv RTP send triggered nomination", this);
                                //ice_sendfromto(check->rtpSocket, check->connection.localAddr, check->connection.local_rtp_port, check->connection.remote_addr, check->connection.remote_rtp_port, new class packet(check->stun_rtp_request));
                                IceSendto(check->rtpSocket, check->stunRtpRequest, check->stunRtpRequestLen, check->connection.remoteAddr, check->connection.remoteRtpPort, check->turn);
                            }
                            else if (rtcp && check->rtcpState == IceCheck::nominating) {
                                log->Log(LOG_ICE, "ICE(%p)::iceStunRecv RTCP send triggered nomination", this);
                                //ice_sendfromto(check->rtcpSocket, check->connection.localAddr, check->connection.local_rtcp_port, check->connection.remote_addr, check->connection.remote_rtcp_port, new class packet(check->stun_rtcp_request));
                                IceSendto(check->rtcpSocket, check->stunRtcpRequest, check->stunRtcpRequestLen, check->connection.remoteAddr, check->connection.remoteRtcpPort, check->turn);
                            }
                        }
                    }
                }
                else {
                    if(log->LogFlagSet(LOG_ICE)) {
                        char addrSrc[128];
                        str::from_ip(addrSrc, 128, &srcAddr);
                        char addrDst[128];
                        str::from_ip(addrDst, 128, &dstAddr);
                        debug->printf("ICE(%p,%p)::iceStunRecv Receive %s check for unexpected addresses (%s:%u <- %s:%u) turnOnly=%d noHost=%d", this, socket, rtcp ? "RTCP" : "RTP", addrDst, srcPort, addrSrc, srcPort, turnOnly, iceNoHost);
                    }
                    if (turnOnly || iceNoHost) {
                        // ignore checks from unexpected sources
                    }
                    else {
                        int len; 
                        char * data = IceStun::writeResponse(&id, iceLocalConnected->common.pwd, srcAddr, srcPort, len, iceStunDraft2, 0, iceStunDraft2 ? iceRemote.common.usr : 0, iceStunDraft2 ? iceLocalConnected->common.usr : 0);
                        //ice_sendfromto((class socket *)src, dst_addr, dst_port, src_addr, src_port, ice_stun::write_response(&id, ice_local_connected->common.pwd, src_addr, src_port, ice_stun_draft2, 0, ice_stun_draft2 ? ice_remote.common.usr : 0, ice_stun_draft2 ? ice_local_connected->common.usr : 0));
                        IceSendto(socket, data, len, srcAddr, srcPort, turnCh != 0);
                        free(data);
                        bool ip4 = is_ip4(srcAddr);
                        class IceCheck * prflx = 0;
                        // find existing prepared check
                        class IceCheck * c;
                        for (c = preparedChecks.front(); c; c = c->goNext()) {
                            if(ip_match(c->connection.remoteAddr, srcAddr) && ip_match(c->connection.localAddr, dstAddr)) {
                                prflx = c;
                                break;
                            }
                        }
                        if (!prflx && prflxCheckCount < ICE_PRFLX_MAX) {
                            prflx = new class IceCheck(0, 0, iceControlling, ip4 ? (turnCh != 0 ? tcp4() : rtp4()) : (turnCh != 0 ? tcp4() : rtp6()), iceRtcp ? (ip4 ? (turnCh != 0 ? rtcpTcp4() : rtcp4()) : (turnCh != 0 ? rtcpTcp4() : rtcp6())) : 0, turnCh != 0);
                            preparedChecks.push_back(prflx);
                            prflxCheckCount++;
                        }
                        if (prflx) {
                            if(rtcp) {
                                prflx->connection.localAddr = dstAddr;
                                prflx->connection.localRtcpPort = dstPort;
                                prflx->connection.remoteAddr = srcAddr;
                                prflx->connection.remoteRtcpPort = srcPort;
                                dword localPriority = (ICE_PREF_TYPE_PRFLX << 24) + ((ip4 ? ICE_PREF_LOCL_IP4_HIGH : ICE_PREF_LOCL_IP4_LOW) << 8) + ICE_PREF_COMP_RTCP;
                                prflx->priority.rtcp = calculateCheckPriority(iceControlling, localPriority, priority);
                                prflx->priority.rtcpReflexive = localPriority;
                                if (!iceControlling && nominated) prflx->rtcpNominated = true;
                            }
                            else {
                                prflx->connection.localAddr = dstAddr;
                                prflx->connection.localRtpPort = dstPort;
                                prflx->connection.remoteAddr = srcAddr;
                                prflx->connection.remoteRtpPort = srcPort;
                                dword localPriority = (ICE_PREF_TYPE_PRFLX << 24) + ((ip4 ? ICE_PREF_LOCL_IP4_HIGH : ICE_PREF_LOCL_IP4_LOW) << 8) + ICE_PREF_COMP_RTP;
                                prflx->priority.rtp = calculateCheckPriority(iceControlling, localPriority, priority);
                                prflx->priority.rtpReflexive = localPriority;
                                if (!iceControlling && nominated) prflx->rtpNominated = true;
                            }

                            if (prflx->connection.remoteRtpPort && (rtcpMux || prflx->connection.remoteRtcpPort)) {
                                prflx->remove();
                                prflx->rtpState = IceCheck::inProgress;
                                prflx->createStunRtp(iceLocalConnected->common.usr, iceRemote.common.usr, iceRemote.common.pwd, iceControlling, iceTiebreaker, false, iceStunDraft2);
                                addCheck(tcp, prflx);
                                log->Log(LOG_ICE, "ICE(%p,%x)::iceStunRecv RTP send request to PRFLX", this, turnCh);
                                //ice_sendfromto(prflx->rtp_socket, prflx->connection.local_addr, prflx->connection.local_rtp_port, prflx->connection.remote_addr, prflx->connection.remote_rtp_port, new class packet(prflx->stun_rtp_request));
                                IceSendto(prflx->rtpSocket, prflx->stunRtpRequest, prflx->stunRtpRequestLen, prflx->connection.remoteAddr, prflx->connection.remoteRtpPort, prflx->turn);
                            }
                        }
                    }
                }
            }
        }
        else if (messageType == BINDING_RESPONSE) {
            class IceCheck * check = findCheck(rtcp, tcp, dstAddr, dstPort, srcAddr, srcPort);

            log->Log(LOG_ICE, "ICE(%p,%x)::iceStunRecv BINDING_RESPONSE %p rtcp %d", this, turnCh, check, rtcp);

            if (check) {
                IPaddr reflexiveAddr;
                word reflexivePort = 0;

                if (IceStun::readResponse(data, len, iceRemote.common.pwd, rtcp ? &check->rtcpId : &check->rtpId, &reflexiveAddr, &reflexivePort, iceStunDraft2)) {
                    if (!rtcp) {
                        if (iceControlling) {
                            if (check->rtpState == IceCheck::inProgress) {
                                check->rtpState = IceCheck::succeeded;
                                log->Log(LOG_ICE, "ICE(%p,%x)::iceStunRecv RTP checked", this, turnCh);
                                if (iceRtcp && check->connection.localRtcpPort) {
                                    if (check->rtcpState == IceCheck::frozen) {
                                        // initial RTCP check
                                        check->rtcpState = IceCheck::inProgress;
                                        check->createStunRtcp(iceLocalConnected->common.usr, iceRemote.common.usr, iceRemote.common.pwd, iceControlling, iceTiebreaker, false, iceStunDraft2);
                                        IceSendto(check->rtcpSocket, check->stunRtcpRequest, check->stunRtcpRequestLen, check->connection.remoteAddr, check->connection.remoteRtcpPort, check->turn);
                                    }
                                }
                                else {
                                    if (IceCanNominate() && !nominating) {
                                        nominating = true;
                                        check->rtpState = IceCheck::nominating;
                                        log->Log(LOG_ICE, "ICE(%p,%x)::iceStunRecv send RTP nomination", this, turnCh);
                                        check->createStunRtp(iceLocalConnected->common.usr, iceRemote.common.usr, iceRemote.common.pwd, iceControlling, iceTiebreaker, true, iceStunDraft2);
                                        IceSendto(check->rtpSocket, check->stunRtpRequest, check->stunRtpRequestLen, check->connection.remoteAddr, check->connection.remoteRtpPort, check->turn);
                                    }
                                }
                            }
                            else if (check->rtpState == IceCheck::nominating) {
                                check->rtpState = IceCheck::nominated;
                                log->Log(LOG_ICE, "ICE(%p,%x)::iceStunRecv RTP nominated response", this, turnCh);
                                if (check->rtcpState == IceCheck::nominated || !iceRtcp || !check->connection.localRtcpPort) {
                                    // success
                                    check->rtcpState = IceCheck::nominated;
                                    iceConnected(turnCh != 0);
                                }
                            }
                        }
                        else {
                            if (check->rtpState == IceCheck::inProgress) {
                                if (iceRtcp && check->connection.localRtcpPort) {
                                    // initial RTCP check
                                    check->rtpState = IceCheck::valid;
                                    log->Log(LOG_ICE, "ICE(%p,%x)::iceStunRecv RTP valid RTCP state", this, turnCh, check->rtcpState);
                                    if (check->rtcpState == IceCheck::frozen) {
                                        check->rtcpState = IceCheck::inProgress;
                                        check->createStunRtcp(iceLocalConnected->common.usr, iceRemote.common.usr, iceRemote.common.pwd, iceControlling, iceTiebreaker, false, iceStunDraft2);
                                        IceSendto(check->rtcpSocket, check->stunRtcpRequest, check->stunRtcpRequestLen, check->connection.remoteAddr, check->connection.remoteRtcpPort, check->turn);
                                    }
                                }
                                else {
                                    // success
                                    check->rtpState = IceCheck::valid;
                                    check->rtcpState = IceCheck::valid;
                                    log->Log(LOG_ICE, "ICE(%p,%x)::iceStunRecv RTP valid (%d,%d)", this, turnCh, iceRtcp, check->connection.localRtcpPort);
                                    // wait for nomination by remote party
                                }
                            }
                            if (check->rtpState == IceCheck::valid && check->rtpNominated && (!iceRtcp || (check->rtcpState == IceCheck::valid && check->rtcpNominated))) {
                                iceConnected(turnCh != 0);
                            }
                        }
                    }
                    else {
                        if (iceControlling) {
                            if (check->rtcpState == IceCheck::inProgress) {
                                check->rtcpState = IceCheck::succeeded;
                                log->Log(LOG_ICE, "ICE(%p,%x)::iceStunRecv RTCP checked", this, turnCh);
                                if (IceCanNominate() && !nominating) {
                                    nominating = true;
                                    check->rtpState = IceCheck::nominating;
                                    log->Log(LOG_ICE, "ICE(%p,%x)::iceStunRecv send RTP nomination", this, turnCh);
                                    check->createStunRtp(iceLocalConnected->common.usr, iceRemote.common.usr, iceRemote.common.pwd, iceControlling, iceTiebreaker, true, iceStunDraft2);
                                    IceSendto(check->rtpSocket, check->stunRtpRequest, check->stunRtpRequestLen, check->connection.remoteAddr, check->connection.remoteRtpPort, check->turn);
                                    check->rtcpState = IceCheck::nominating;
                                    log->Log(LOG_ICE, "ICE(%p,%x)::iceStunRecv send RTCP nomination", this, turnCh);
                                    check->createStunRtcp(iceLocalConnected->common.usr, iceRemote.common.usr, iceRemote.common.pwd, iceControlling, iceTiebreaker, true, iceStunDraft2);
                                    IceSendto(check->rtcpSocket, check->stunRtcpRequest, check->stunRtcpRequestLen, check->connection.remoteAddr, check->connection.remoteRtcpPort, check->turn);
                                }
                            }
                            else if (check->rtcpState == IceCheck::nominating) {
                                check->rtcpState = IceCheck::nominated;
                                log->Log(LOG_ICE, "ICE(%p,%x)::iceStunRecv RTCP nominated", this, turnCh);
                                if (check->rtpState == IceCheck::nominated) {
                                    // success
                                    iceConnected(turnCh != 0);
                                }
                            }
                        }
                        else {
                            if (check->rtcpState == IceCheck::inProgress) {
                                // success
                                check->rtcpState = IceCheck::valid;
                                log->Log(LOG_ICE, "ICE(%p,%x)::iceStunRecv RTCP valid", this, turnCh);
                                // wait for nomination by remote party
                            }
                            if (check->rtpState == IceCheck::valid && check->rtpNominated && (!iceRtcp || (check->rtcpState == IceCheck::valid && check->rtcpNominated))) {
                                iceConnected(turnCh != 0);
                            }
                        }
                    }
                }
                else {
                    if(log->LogFlagSet(LOG_ICE)) {
                        char addrSrc[128];
                        str::from_ip(addrSrc, 128, &srcAddr);
                        char addrDst[128];
                        str::from_ip(addrDst, 128, &dstAddr);
                        debug->printf("ICE(%p)::iceStunRecv Failed parsing %s binding response (%s:%u <- %s:%u)", this, rtcp ? "RTCP" : "RTP", addrDst, dstPort, addrSrc, srcPort);
                    }
                }
            }
            else {
                if(log->LogFlagSet(LOG_ICE)) {
                    char addrSrc[128];
                    str::from_ip(addrSrc, 128, &srcAddr);
                    char addrDst[128];
                    str::from_ip(addrDst, 128, &dstAddr);
                    debug->printf("ICE(%p)::iceStunRecv Unexpected %s binding response (%s:%u <- %s:%u)", this, rtcp ? "RTCP" : "RTP", addrDst, dstPort, addrSrc, srcPort);
                }
            }
        }
        else if (messageType == BINDING_ERROR_RESPONSE) {
            class IceCheck * check = findCheck(rtcp, tcp, dstAddr, dstPort, srcAddr, srcPort);
            if (check) {
                OS_GUID * id = rtcp ? &check->rtcpId : &check->rtpId;
                word errorCode = 0;
                if (IceStun::readResponse(data, len, id, iceRemote.common.pwd, &errorCode)) {
                    if (errorCode == STUN_ERROR_ROLE_CONFLICT && !iceRoleChanged) {
                        log->Log(LOG_ICE, "ICE(%p)::iceStunRecv Role conflict indicated, try switch to %s", this, iceControlling ? "controlled" : "controlling");
                        iceSwitchRole();
                    }
                    else {
                        log->Log(LOG_ICE, "ICE(%p)::iceStunRecv Error code(%u), phrase(%s)%s", this, errorCode, IceStun::getErrorPhrase(errorCode), errorCode == 1025 ? " (remote endpoint might not be ready)" : "");
                    }
                }
                else {
                    log->Log(LOG_ICE, "ICE(%p)::iceStunRecv Unauthenticated error response", this);
                }
            }
        }
    }
    else {
        bool rtcp = socket == rtcp4();
        word messageType = READ_WORD_NET(data);
        if (messageType == BINDING_REQUEST) {
            OS_GUID id;
            dword priority = 0;
            ulong64 tiebreaker = 0;
            bool controlling = false;
            bool nominated = false;
            int error = IceStun::readRequest(data, len, iceLocalConnected->common.usr, iceRemote.common.usr, iceLocalConnected->common.pwd, &id, &priority, &controlling, &tiebreaker, &nominated, true);
            if (error) {
                if (error != STUN_ERROR_BAD_REQUEST) {
                    int len;
                    char * data = IceStun::writeResponse(&id, error, iceLocalConnected->common.pwd, len);
                    IceSendto(socket, data, len, srcAddr, srcPort, turnCh != 0);
                    free(data);  
                }
            }
            else {
                if (nominated) controlling = true;
                bool isConnectedPath = (iceState == completed) && ip_match(iceConnectedLocalAddr, dstAddr) && ip_match(iceConnectedRemoteAddr, srcAddr) && ((rtcp && iceConnectedRemoteRtcpPort == srcPort && iceConnectedLocalRtcpPort == dstPort) || (!rtcp && iceConnectedRemoteRtpPort == srcPort && iceConnectedLocalRtpPort == dstPort));
                if (!nominated || isConnectedPath) {
                    int len; 
                    char * data;
                    if (iceControlling != controlling) {
                        data = IceStun::writeResponse(&id, iceLocalConnected->common.pwd, srcAddr, srcPort, len, iceStunDraft2, 0, iceStunDraft2 ? iceRemote.common.usr : 0, iceStunDraft2 ? iceLocalConnected->common.usr : 0);
                    }
                    else {
                        data = IceStun::writeResponse(&id, STUN_ERROR_ROLE_CONFLICT, iceLocalConnected->common.pwd, len);
                    }
                    IceSendto(socket, data, len, srcAddr, srcPort, turnCh != 0);
                    free(data);
                }
            }
        }
    }
    return true;
}

void Ice::iceSwitchToStunDraft2() 
{
    if (iceState == connecting && !iceStunDraft2) {
        log->Log(LOG_ICE, "ICE(%p)::iceSwitchToStunDraft2 STUN version from standard to microsoft", this);
        struct ChannelIce copy;
        copy.copy(&iceRemote);
        iceConnect(&copy, iceControlling, true);
    }
}

void Ice::iceSwitchRole() 
{
    if (iceState == connecting && !iceRoleChanged) {
        log->Log(LOG_ICE, "ICE(%p)::iceSwitchRole from %s to %s", this, iceControlling ? "controlling" : "controlled", iceControlling ? "controlled" : "controlling");
        struct ChannelIce copy;
        copy.copy(&iceRemote);
        iceConnect(&copy, !iceControlling, iceStunDraft2);
        iceRoleChanged = true;
    }
}

void Ice::iceInitialize(bool useRtcp, byte * fingerprint, int fingerprintLen)
{
    //log->Log(LOG_ICE, "ICE(%p)::iceInitialize %d", this, fingerprintLen);

    IceTimerStop();
    clearChecks();
    clearStun();
    iceLocal.clear();
    if (fingerprint && fingerprintLen) {
        iceLocal.common.fingerprintLen = fingerprintLen;
        memcpy(iceLocal.common.fingerprint, fingerprint, fingerprintLen);
    }
    iceRemote.clear();
    iceLocalConnected = &iceLocal;
    iceStunDraft2 = false;

    iceConnectedLocalAddr = ip_anyaddr;
    iceConnectedLocalRtpPort = 0;
    iceConnectedLocalRtcpPort = 0;
    iceConnectedRemoteAddr = ip_anyaddr;
    iceConnectedRemoteRtpPort = 0;
    iceConnectedRemoteRtcpPort = 0;

    iceRtcp = useRtcp;
    iceState = gathering;
    iceGatheredHost = false;
    iceGatheredStun = false;
    iceGatheredTurn = false;
    iceCandidateCounter = 0;

    class ISocket * rtp = rtp4();
    class ISocket * rtcp = rtcp4();

    if (iceDisabled || !rtp || (iceRtcp && !rtcp)) {
        log->Log(LOG_ICE, "ICE(%p)::iceInitialize %d,%d,%d,%d %d,%d", this, iceDisabled, !rtp, (iceRtcp && !rtcp), turnOnly, iceRtcp, !rtcp);
        iceHostInitialized();
        iceStunInitialized(0, 0);
        iceTurnInitialized(ip_anyaddr, 0, 0, ip_anyaddr, 0, 0, ip_anyaddr, 0, 0);
    }
    else if (turnOnly) {
        log->Log(LOG_ICE, "ICE(%p)::iceInitialize turnOnly", this);
        iceHostInitialized();
        iceStunInitialized(0, 0);
    }
    else {
        // Start gathering HOST candidates
        if(!iceNoHost) {
            log->Log(LOG_ICE, "ICE(%p,%d)::iceInitialize LOCAL(%s:%d,%d) STUN1(%s:%d, (%s:%d) STUN2(%s:%d, %s:%d)", this, iceRtcp, (iceLocalAddr ? iceLocalAddr : "0.0.0.0"), iceLocalPort, iceLocalRtcpPort,
                (iceStun1Addr ? iceStun1Addr : "0.0.0.0"), iceStun1Port, (iceStun1Addr2 ? iceStun1Addr2 : "0.0.0.0"), iceStun1Port2,
                (iceStun2Addr ? iceStun2Addr : "0.0.0.0"), iceStun2Port, (iceStun2Addr2 ? iceStun2Addr2 : "0.0.0.0"), iceStun2Port2);
            addSigCandidate();
        }
        else {
            iceHostInitialized();
        }
        // Start gathering SRFLX candidates
        bool useStun = false;
        if (iceStun1Port) {
            IceStun::createId(&stun1RtpId);
            if (iceRtcp) IceStun::createId(&stun1RtcpId);
            stun1RtpRequestLen = 0;
            stun1RtpRequest = IceStun::writeRequest(&stun1RtpId, stun1RtpRequestLen);
            if (iceRtcp) {
                stun1RtcpRequestLen = 0;
                stun1RtcpRequest = IceStun::writeRequest(&stun1RtcpId, stun1RtcpRequestLen);
            }
            IceSendto(rtp, stun1RtpRequest, stun1RtpRequestLen, stun1AddrStorage, iceStun1Port, false);
            if(iceRtcp) IceSendto(rtcp, stun1RtcpRequest, stun1RtcpRequestLen, stun1AddrStorage, iceStun1Port, false);
            useStun = true;
        }
        if (iceStun1Port2) {
            IceStun::createId(&stun2RtpId);
            if (iceRtcp) IceStun::createId(&stun2RtcpId);
            stun2RtpRequestLen = 0;
            stun2RtpRequest = IceStun::writeRequest(&stun2RtpId, stun2RtpRequestLen);
            if (iceRtcp) {
                stun2RtcpRequestLen = 0;
                stun2RtcpRequest = IceStun::writeRequest(&stun2RtcpId, stun2RtcpRequestLen);
            }
            IceSendto(rtp, stun2RtpRequest, stun2RtpRequestLen, stun1Addr2Storage, iceStun1Port2, false);
            if (iceRtcp) IceSendto(rtcp, stun2RtcpRequest, stun2RtcpRequestLen, stun1Addr2Storage, iceStun1Port2, false);
            useStun = true;
        }
        if (iceStun2Port) {
            IceStun::createId(&stun3RtpId);
            if (iceRtcp) IceStun::createId(&stun3RtcpId);
            stun3RtpRequestLen = 0;
            stun3RtpRequest = IceStun::writeRequest(&stun3RtpId, stun3RtpRequestLen);
            if (iceRtcp) {
                stun3RtcpRequestLen = 0;
                stun3RtcpRequest = IceStun::writeRequest(&stun3RtcpId, stun3RtcpRequestLen);
            }
            IceSendto(rtp, stun3RtpRequest, stun3RtpRequestLen, stun2AddrStorage, iceStun2Port, false);
            if (iceRtcp) IceSendto(rtcp, stun3RtcpRequest, stun3RtcpRequestLen, stun2AddrStorage, iceStun2Port, false);
            useStun = true;
        }
        if (iceStun2Port2) {
            IceStun::createId(&stun4RtpId);
            if (iceRtcp) IceStun::createId(&stun4RtcpId);
            stun4RtpRequestLen = 0;
            stun4RtpRequest = IceStun::writeRequest(&stun4RtpId, stun4RtpRequestLen);
            if (iceRtcp) {
                stun4RtcpRequestLen = 0;
                stun4RtcpRequest = IceStun::writeRequest(&stun4RtcpId, stun4RtcpRequestLen);
            }
            IceSendto(rtp, stun4RtpRequest, stun4RtpRequestLen, stun2Addr2Storage, iceStun2Port2, false);
            if (iceRtcp) IceSendto(rtcp, stun4RtcpRequest, stun4RtcpRequestLen, stun2Addr2Storage, iceStun2Port2, false);
            useStun = true;
        }
        if (useStun) {
            IceTimerStart(100);
        }
        else {
            iceStunInitialized(0, 0);
        }
    }
}

void Ice::iceRewriteCredentials(const char* usr, const char* pwd)
{
    log->Log(LOG_ICE, "Ice(%p)::iceRewriteCredentials", this);

    strcpy(iceLocalConnected->common.usr, usr);
    strcpy(iceLocalConnected->common.pwd, pwd);
}

void Ice::iceTimeout()
{
    //log->Log(LOG_ICE, "Ice(%p)::TimerOnTimeout state=%d", this, iceState);

    if (iceState == gathering) {
        if (stunNumTry < (unsigned int)(iceSlow ? ICE_SLOW_SERVER_ROUNDTRIP / 100 + ICE_RETRY_GATHER : ICE_RETRY_GATHER)) {
            if (iceStun1Port) {
                if(stun1RtpRequest) IceSendto(rtp4(), stun1RtpRequest, stun1RtpRequestLen, stun1AddrStorage, iceStun1Port, false);
                if(stun1RtcpRequest) IceSendto(rtcp4(), stun1RtcpRequest, stun1RtcpRequestLen, stun1AddrStorage, iceStun1Port, false);
            }
            if (iceStun1Port2) {
                if(stun2RtpRequest) IceSendto(rtp4(), stun2RtpRequest, stun2RtpRequestLen, stun1Addr2Storage, iceStun1Port2, false);
                if(stun2RtcpRequest) IceSendto(rtcp4(), stun2RtcpRequest, stun2RtcpRequestLen, stun1Addr2Storage, iceStun1Port2, false);
            }
            if (iceStun2Port) {
                if(stun3RtpRequest) IceSendto(rtp4(), stun3RtpRequest, stun3RtpRequestLen, stun2AddrStorage, iceStun2Port, false);
                if(stun3RtcpRequest) IceSendto(rtcp4(), stun3RtcpRequest, stun3RtcpRequestLen, stun2AddrStorage, iceStun2Port, false);
            }
            if (iceStun2Port2) {
                if(stun4RtpRequest) IceSendto(rtp4(), stun4RtpRequest, stun4RtpRequestLen, stun2Addr2Storage, iceStun2Port2, false);
                if(stun4RtcpRequest) IceSendto(rtcp4(), stun4RtcpRequest, stun4RtcpRequestLen, stun2Addr2Storage, iceStun2Port2, false);
            }
            IceTimerStart(100); // 100ms
            stunNumTry++;
        }
        else {
            bool stun1Ready = !is_anyaddr(stun1.addr) && stun1.rtpPort && (!iceRtcp || stun1.rtcpPort);
            bool stun2Ready = !is_anyaddr(stun2.addr) && stun2.rtpPort && (!iceRtcp || stun2.rtcpPort);
            bool stun3Ready = !is_anyaddr(stun3.addr) && stun3.rtpPort && (!iceRtcp || stun3.rtcpPort);
            bool stun4Ready = !is_anyaddr(stun4.addr) && stun4.rtpPort && (!iceRtcp || stun4.rtcpPort);
            ChannelCandidate * media = stun1Ready ? &stun1 : stun2Ready ? &stun2 : 0;
            ChannelCandidate * media2 = stun3Ready ? &stun3 : stun4Ready ? &stun4 : 0;
            iceStunInitialized(media, media2);
        }
    }
    else if (iceState == connecting) {
        iceNextCheck();
    }
    else if (iceState == completed || iceState == waiting) {
        // keep-alives for SRFLX candidates
        if (stun1RtpRequest) IceSendto(rtp4(), stun1RtpRequest, stun1RtpRequestLen, stun1AddrStorage, iceStun1Port, false);
        if (stun1RtcpRequest) IceSendto(rtcp4(), stun1RtcpRequest, stun1RtcpRequestLen, stun1AddrStorage, iceStun1Port, false);
        if (stun2RtpRequest) IceSendto(rtp4(), stun2RtpRequest, stun2RtpRequestLen, stun1Addr2Storage, iceStun1Port2, false);
        if (stun2RtcpRequest) IceSendto(rtcp4(), stun2RtcpRequest, stun2RtcpRequestLen, stun1Addr2Storage, iceStun1Port2, false);
        if (stun3RtpRequest) IceSendto(rtp4(), stun3RtpRequest, stun3RtpRequestLen, stun2AddrStorage, iceStun2Port, false);
        if (stun3RtcpRequest) IceSendto(rtcp4(), stun3RtcpRequest, stun3RtcpRequestLen, stun2AddrStorage, iceStun2Port, false);
        if (stun4RtpRequest) IceSendto(rtp4(), stun4RtpRequest, stun4RtpRequestLen, stun2Addr2Storage, iceStun2Port2, false);
        if (stun4RtcpRequest) IceSendto(rtcp4(), stun4RtcpRequest, stun4RtcpRequestLen, stun2Addr2Storage, iceStun2Port2, false);
        IceTimerStart(25000);
    }
}

void Ice::addCheck(bool tcp, class IceCheck * check)
{
    if(log->LogFlagSet(LOG_ICE)) {
        char addrLocal[128];
        str::from_ip(addrLocal, 128, &check->connection.localAddr);
        char addrRemote[128];
        str::from_ip(addrRemote, 128, &check->connection.remoteAddr);
        debug->printf("ICE(%p,check=%p)::addCheck%s %s:%d,%d -> %s:%d,%d", this, check, tcp ? " tcp" : "", addrLocal, check->connection.localRtpPort, check->connection.localRtcpPort, addrRemote, check->connection.remoteRtpPort, check->connection.remoteRtcpPort);
    }
    if (tcp) {
        iceCheckConnectionRtpTcp = iceCheckConnectionRtpTcp->btree_put(dynamic_cast<class IceCheckBtreeConnectionRtpTcp *>(check));
    }
    else {
        iceCheckConnectionRtp = iceCheckConnectionRtp->btree_put(dynamic_cast<class IceCheckBtreeConnectionRtp *>(check));
        if (check->connection.localRtcpPort) iceCheckConnectionRtcp = iceCheckConnectionRtcp->btree_put(dynamic_cast<class IceCheckBtreeConnectionRtcp *>(check));
    }
    iceCheckPriority = iceCheckPriority->btree_put(dynamic_cast<class IceCheckBtreePriority *>(check));
}

void Ice::removeCheck(bool tcp, class IceCheck * check)
{
    if (tcp) {
        iceCheckConnectionRtpTcp = iceCheckConnectionRtpTcp->btree_get(dynamic_cast<class IceCheckBtreeConnectionRtpTcp *>(check));
    }
    else {
        iceCheckConnectionRtp = iceCheckConnectionRtp->btree_get(dynamic_cast<class IceCheckBtreeConnectionRtp *>(check));
        if (check->connection.localRtcpPort) iceCheckConnectionRtcp = iceCheckConnectionRtcp->btree_get(dynamic_cast<class IceCheckBtreeConnectionRtcp *>(check));
    }
    iceCheckPriority = iceCheckPriority->btree_get(dynamic_cast<class IceCheckBtreePriority *>(check));
    delete check;
}

class IceCheck * Ice::findCheck(bool rtcp, bool tcp, IPaddr localAddr, word localPort, IPaddr remoteAddr, word remotePort)
{
    IceCheck::iceCheckConnection conn;
    conn.localAddr = localAddr;
    conn.localRtpPort = localPort;
    conn.localRtcpPort = localPort;
    conn.remoteAddr = remoteAddr;
    conn.remoteRtpPort = remotePort;
    conn.remoteRtcpPort = remotePort;
    if (tcp) return (class IceCheck *)dynamic_cast<class IceCheckBtreeConnectionRtpTcp *>(iceCheckConnectionRtpTcp->btree_find(&conn));
    if (rtcp) return (class IceCheck *)dynamic_cast<class IceCheckBtreeConnectionRtcp *>(iceCheckConnectionRtcp->btree_find(&conn));
    return (class IceCheck *)dynamic_cast<class IceCheckBtreeConnectionRtp *>(iceCheckConnectionRtp->btree_find(&conn));
}

void Ice::iceNextCheck()
{
    if (!currentCheck) currentCheck = (class IceCheck *)dynamic_cast<class IceCheckBtreePriority *>(iceCheckPriority->btree_find_first_left(0));
    bool sent = false;

    log->Log(LOG_ICE, "ICE(%p)::iceNextCheck check=%p state=%d", this, currentCheck, iceState);

    while (currentCheck && !sent) {
        if (currentCheck->rtpState == IceCheck::waiting) {
            // initial RTP check
            currentCheck->rtpState = IceCheck::inProgress;
            activeChecks++;
            log->Log(LOG_ICE, "ICE(%p,%p)::iceNextCheck RTP check turn=%d", this, currentCheck, currentCheck->turn);
            currentCheck->createStunRtp(iceLocalConnected->common.usr, iceRemote.common.usr, iceRemote.common.pwd, iceControlling, iceTiebreaker, false, iceStunDraft2);
            IceSendto(currentCheck->rtpSocket, currentCheck->stunRtpRequest, currentCheck->stunRtpRequestLen, currentCheck->connection.remoteAddr, currentCheck->connection.remoteRtpPort, currentCheck->turn);
            sent = true;
        }
        else if (currentCheck->rtpState == IceCheck::inProgress) {
            if (currentCheck->rtpRetry<ICE_RETRY_CHECK) {
                // retry of RTP check
                activeChecks++;
                currentCheck->rtpRetry++;
                log->Log(LOG_ICE, "ICE(%p,%p)::iceNextCheck Retry RTP check turn=%d", this, currentCheck, currentCheck->turn);
                IceSendto(currentCheck->rtpSocket, currentCheck->stunRtpRequest, currentCheck->stunRtpRequestLen, currentCheck->connection.remoteAddr, currentCheck->connection.remoteRtpPort, currentCheck->turn);
                sent = true;
            }
            else {
                currentCheck->rtpState = IceCheck::failed;
                currentCheck->rtcpState = IceCheck::failed;
                log->Log(LOG_ICE, "ICE(%p,%p)::iceNextCheck RTP check failed", this, currentCheck);
            }
        }
        else if (currentCheck->rtcpState == IceCheck::inProgress) {
            if (currentCheck->rtcpRetry<ICE_RETRY_CHECK) {
                // retry of RTCP check
                activeChecks++;
                currentCheck->rtcpRetry++;
                log->Log(LOG_ICE, "ICE(%p,%p)::iceNextCheck Retry RTCP check turn=%d", this, currentCheck, currentCheck->turn);
                IceSendto(currentCheck->rtcpSocket, currentCheck->stunRtcpRequest, currentCheck->stunRtcpRequestLen, currentCheck->connection.remoteAddr, currentCheck->connection.remoteRtcpPort, currentCheck->turn);
                sent = true;
            }
            else {
                currentCheck->rtpState = IceCheck::failed;
                currentCheck->rtcpState = IceCheck::failed;
                log->Log(LOG_ICE, "ICE(%p,%p)::iceNextCheck RTCP check failed", this, currentCheck);
            }
        }
        else if (currentCheck->rtpState == IceCheck::nominating) {
            if (currentCheck->rtpRetry<ICE_RETRY_CHECK) {
                // retry of RTP nomination
                activeChecks++;
                currentCheck->rtpRetry++;
                log->Log(LOG_ICE, "ICE(%p,%p)::iceNextCheck Retry RTP nomination turn=%d", this, currentCheck, currentCheck->turn);
                IceSendto(currentCheck->rtpSocket, currentCheck->stunRtpRequest, currentCheck->stunRtpRequestLen, currentCheck->connection.remoteAddr, currentCheck->connection.remoteRtpPort, currentCheck->turn);
                sent = true;
                if (currentCheck->rtcpState == IceCheck::nominating) {
                    currentCheck->rtcpRetry++;
                    log->Log(LOG_ICE, "ICE(%p,%p)::iceNextCheck Retry RTCP nomination turn=%d", this, currentCheck, currentCheck->turn);
                    IceSendto(currentCheck->rtcpSocket, currentCheck->stunRtcpRequest, currentCheck->stunRtcpRequestLen, currentCheck->connection.remoteAddr, currentCheck->connection.remoteRtcpPort, currentCheck->turn);
                }
            }
            else {
                log->Log(LOG_ICE, "ICE(%p,%p)::iceNextCheck RTP check failed", this, currentCheck);
            }
        }
        else if (currentCheck->rtcpState == IceCheck::nominating) {
            if (currentCheck->rtcpRetry<ICE_RETRY_CHECK) {
                // retry of RTCP nomination
                activeChecks++;
                currentCheck->rtcpRetry++;
                sent = true;
                log->Log(LOG_ICE, "ICE(%p)::iceNextCheck Retry RTCP nomination", this);
                IceSendto(currentCheck->rtcpSocket, currentCheck->stunRtcpRequest, currentCheck->stunRtcpRequestLen, currentCheck->connection.remoteAddr, currentCheck->connection.remoteRtcpPort, currentCheck->turn);
            }
            else {
                log->Log(LOG_ICE, "ICE(%p)::iceNextCheck RTP check failed", this);
            }
        }
        currentCheck = (class IceCheck *)dynamic_cast<class IceCheckBtreePriority *>(iceCheckPriority->btree_find_next_left(&currentCheck->priority));
    }

    if (currentCheck) {
        IceTimerStart(20);
    }
    else if (activeChecks) {
        IceTimerStart((activeChecks>4 ? 1 : 6 - activeChecks)*20); // max 100ms
        activeChecks = 0;
    }
    else if (iceControlling || waitLast) {
        log->Log(LOG_ICE, "ICE(%p)::iceNextCheck Abort", this);
        iceConnected(false);
    }
    else {
        log->Log(LOG_ICE, "ICE(%p)::iceNextCheck No more checks, wait 2s", this);
        waitLast = true;
        IceTimerStart((iceSlow ? ICE_SLOW_SERVER_ROUNDTRIP / 20 + 100 : 100)*20);
    }
}

void Ice::iceConnected(bool turnCh)
{
    iceConnectedLocalAddr = ip_anyaddr;
    iceConnectedLocalRtpPort = 0;
    iceConnectedLocalRtcpPort = 0;
    iceConnectedRemoteAddr = ip_anyaddr;
    iceConnectedRemoteRtpPort = 0;
    iceConnectedRemoteRtcpPort = 0;
    class IceCheck * selected = 0;
    ulong64 selectedPriority = 0;
    byte rtpRetry = 0;
    byte rtcpRetry = 0;

    class IceCheck * check = (class IceCheck *)dynamic_cast<class IceCheckBtreePriority *>(iceCheckPriority->btree_find_first_left(0));
    while (check) {
        if (iceControlling) {
            if (check->rtpState == IceCheck::nominated && (!iceRtcp || check->rtcpState == IceCheck::nominated)) {
                selected = check;
                break;
            }
        }
        else {
            if (check->rtpState == IceCheck::valid && check->rtpNominated && (!iceRtcp || (check->rtcpState == IceCheck::valid && check->rtcpNominated)) && check->priority.rtp > selectedPriority) {
                selected = check;
            }
        }
        check = (class IceCheck *)dynamic_cast<class IceCheckBtreePriority *>(iceCheckPriority->btree_find_next_left(&check->priority));
    }

    if (selected) {
        //debug->printf("ICE(%p)::iceConnected selected (%p) %s:%d,%d %s:%d,%d", this, selected, selected->connection.localAddr, selected->connection.localRtpPort, selected->connection.localRtcpPort, selected->connection.remoteAddr, selected->connection.remoteRtpPort, selected->connection.remoteRtcpPort);
        iceConnectedLocalAddr = selected->connection.localAddr;
        iceConnectedLocalRtpPort = selected->connection.localRtpPort;
        iceConnectedLocalRtcpPort = selected->connection.localRtcpPort;
        iceConnectedRemoteAddr = selected->connection.remoteAddr;
        iceConnectedRemoteRtpPort = selected->connection.remoteRtpPort;
        iceConnectedRemoteRtcpPort = selected->connection.remoteRtcpPort;
        rtpRetry = selected->rtpRetry;
        rtcpRetry = selected->rtcpRetry;
    }

    if(log->LogFlagSet(LOG_ICE)) {
        char* localAddr = NULL;
        for (int i = 0; i < iceLocalConnected->common.count; i++) {
            if (iceLocalConnected->candidate[i].type == CHANNEL_CANDIDATE_HOST) {
                localAddr = (char*)malloc(128);
                str::from_ip(localAddr, 128, (IPaddr*)&iceLocalConnected->candidate[i].addr);
                break;
            }
        }
        char tmp[8192];
        int l = _sprintf(tmp, "ICE(%p)::Checklist for interface %s (%d/%d)", this, localAddr ? localAddr : "", interfaceIndex, totalNumInterfaces);
        if (localAddr) free(localAddr);
        class IceCheck * check = (class IceCheck *)dynamic_cast<class IceCheckBtreePriority *>(iceCheckPriority->btree_find_first_left(0));
        while (check) {
            const char * prefix = check == selected ? "+" : "-";
            l += check->trace(tmp + l, sizeof(tmp)-l, prefix);
            check = (class IceCheck *)dynamic_cast<class IceCheckBtreePriority *>(iceCheckPriority->btree_find_next_left(&check->priority));
        }
        log->Log(LOG_ICE, "ICE(%p)::%s", this, tmp);

        l = _sprintf(tmp, "ICE(%p)::Candidates", this);
        l += ChannelIce::trace(iceLocalConnected, "LOCAL", tmp + l, sizeof(tmp)-l);
        l += ChannelIce::trace(&iceRemote, "REMOTE", tmp + l, sizeof(tmp)-l);
        log->Log(LOG_ICE, "ICE(%p)::%s", this, tmp);

        char addrLocal[128];
        str::from_ip(addrLocal, 128, &iceConnectedLocalAddr);
        char addrRemote[128];
        str::from_ip(addrRemote, 128, &iceConnectedRemoteAddr);
        if(selected) debug->printf("ICE(%p)::Connected successfully RTP(%s:%u -> %s:%u, %u retries) RTCP(%s:%u -> %s:%u, %u retries)", this, addrLocal, iceConnectedLocalRtpPort, addrRemote, iceConnectedRemoteRtpPort, rtpRetry, addrLocal, iceConnectedLocalRtcpPort, addrRemote, iceConnectedRemoteRtcpPort, rtcpRetry);
        else debug->printf("ICE(%p)::Connection failed (no working network path found)", this);
        debug->printf("ICE(%p)::Conclusion %s", this, selected ? "SUCCESS" : "FAILED");
#ifdef MODULE_TEST
        TEST_WATCH_CONTENT("iceConnected", selected ? "1" : "0");
#endif
    }

    clearChecks();
    iceState = completed;
    IceTimerStop();
    iceStartKeepAlive();
    IceConnected(iceConnectedLocalAddr, iceConnectedLocalRtpPort, iceConnectedLocalRtcpPort, iceConnectedRemoteAddr, iceConnectedRemoteRtpPort, iceConnectedRemoteRtcpPort, turnCh, iceRemote.common.fingerprintLen ? iceRemote.common.fingerprint : 0, iceRemote.common.fingerprintLen, iceControlling);
}

void Ice::iceHostAddr(IPaddr addr, byte type)
{
    if(log->LogFlagSet(LOG_ICE)) {
        char addrCandidate[128];
        str::from_ip(addrCandidate, 128, &addr);
        debug->printf("ICE(%p)::iceHostAddr %s type %u", this, addrCandidate, type);
    }
    if (iceState == gathering && !iceGatheredHost) {
        addHostCandidate(addr, type);
        iceHostInitialized();
    }
}

void Ice::iceHostInitialized()
{
    if (!iceGatheredHost) {
        iceGatheredHost = true;

        if (!iceDisabled) {
            log->Log(LOG_ICE, "ICE(%p)::Gathering HOST candidates finished", this);
        }

        iceTryInitialized();
    }
}

void Ice::iceStunInitialized(ChannelCandidate * stun, ChannelCandidate* stun2)
{
    if (!iceGatheredStun) {
        iceGatheredStun = true;

        if (!iceDisabled) {
            IceTimerStop();
            addSrflxCandidate(stun);
            addSrflxCandidate(stun2);
            log->Log(LOG_ICE, "ICE(%p)::Gathering SRFLX candidates finished", this);
        }

        iceTryInitialized();
    }
}

void Ice::icePrepareKeepAlive(bool server1, bool server2, bool server3, bool server4)
{
    if (stun1RtpRequest && (!server1 || !stun1.rtpPort)) {
        free(stun1RtpRequest);
        stun1RtpRequest = 0;
    }
    if (stun1RtcpRequest && (!server1 || !stun1.rtcpPort)) {
        free(stun1RtcpRequest);
        stun1RtcpRequest = 0;
    }
    if (stun2RtpRequest && (!server2 || !stun2.rtpPort)) {
        free(stun2RtpRequest);
        stun2RtpRequest = 0;
    }
    if (stun2RtcpRequest && (!server2 || !stun2.rtcpPort)) {
        free(stun2RtcpRequest);
        stun2RtcpRequest = 0;
    }
    if (stun3RtpRequest && (!server3 || !stun3.rtpPort)) {
        free(stun3RtpRequest);
        stun3RtpRequest = 0;
    }
    if (stun3RtcpRequest && (!server3 || !stun3.rtcpPort)) {
        free(stun3RtcpRequest);
        stun3RtcpRequest = 0;
    }
    if (stun4RtpRequest && (!server4 || !stun4.rtpPort)) {
        free(stun4RtpRequest);
        stun4RtpRequest = 0;
    }
    if (stun4RtcpRequest && (!server4 || !stun4.rtcpPort)) {
        free(stun4RtcpRequest);
        stun4RtcpRequest = 0;
    }
}

void Ice::iceStartKeepAlive()
{
    if (stun1RtpRequest || stun1RtcpRequest ||
        stun2RtpRequest || stun2RtcpRequest ||
        stun3RtpRequest || stun3RtcpRequest ||
        stun4RtpRequest || stun4RtcpRequest)
    {
        IceTimerStart(10000);
    }
}

static inline dword calculatePriority(dword host, dword local, dword component, dword & counter)
{
    return (host << 24) + ((local + (counter++)) << 8) + component;
}

static inline dword zeroPriority(dword & counter)
{
    ++counter;
    return 0;
}

static ChannelCandidate * candidateExistsNonRelay(ChannelIce & ice, byte type, IPaddr addr)
{
    for (int i = 0; i<ice.common.count; i++) {
        if (ip_match(addr, ice.candidate[i].addr) && (ice.candidate[i].type != CHANNEL_CANDIDATE_RELAY) && (ChannelCandidateIsTcp(type) == ChannelCandidateIsTcp(ice.candidate[i].type))) return &ice.candidate[i];
    }
    return 0;
}

static void addCandidate(ChannelIce & ice, byte type, IPaddr addr, word rtpPort, word rtcpPort, IPaddr relatedAddr, word relatedRtpPort, word relatedRtcpPort, dword rtpPriority, dword rtcpPriority)
{
    struct ChannelCandidate * existing = candidateExistsNonRelay(ice, type, addr);
    if (existing && type != CHANNEL_CANDIDATE_RELAY) {
        if (existing->rtpPriority < rtpPriority) {
            char addrCandidate[128];
            str::from_ip(addrCandidate, 128, &addr);
            debug->printf("ICE()::addCandidate existing %u %s:%d,%d %d,%d", type, addrCandidate, rtpPort, rtcpPort, rtpPriority, rtcpPriority);
            existing->addr = addr;
            existing->rtpPort = rtpPort;
            existing->rtcpPort = rtcpPort;
            existing->type = type;
            existing->relatedAddr = relatedAddr;
            existing->relatedRtpPort = relatedRtpPort;
            existing->relatedRtcpPort = relatedRtcpPort;
            existing->rtpPriority = rtpPriority;
            existing->rtcpPriority = rtcpPriority;
        }
    }
    else if (ice.common.count < CHANNEL_MAX_CANDIDATE) {
        //debug->printf("ICE()::addCandidate(%d) new(%d) %s:%d,%d %d,%d", type, ice.common.count, addr, rtpPort, rtcpPort, rtpPriority, rtcpPriority);
        ice.candidate[ice.common.count].addr = addr;
        ice.candidate[ice.common.count].rtpPort = rtpPort;
        ice.candidate[ice.common.count].rtcpPort = rtcpPort;
        ice.candidate[ice.common.count].type = type;
        ice.candidate[ice.common.count].relatedAddr = relatedAddr;
        ice.candidate[ice.common.count].relatedRtpPort = relatedRtpPort;
        ice.candidate[ice.common.count].relatedRtcpPort = relatedRtcpPort;
        ice.candidate[ice.common.count].rtpPriority = rtpPriority;
        ice.candidate[ice.common.count].rtcpPriority = rtcpPriority;
        ice.common.count++;
    }
}

void Ice::addSigCandidate()
{
    if (iceLocalAddr) {
        log->Log(LOG_ICE, "ICE(%p)::Found HOST address %s (registration)", this, iceLocalAddr);
        bool ip4 = is_ip4(localAddrStorage);
        word rtpPort = iceLocalPort;
        word rtcpPort = !iceRtcp ? 0 : iceLocalRtcpPort;
        dword priority = ip4 ? (iceIp4Preference ? ICE_PREF_SIG_IP4_HIGH : ICE_PREF_SIG_IP4_LOW) : ICE_PREF_SIG_IP6_LOCAL;
        // Channel HOST
        addCandidate(iceLocal, CHANNEL_CANDIDATE_HOST,
            localAddrStorage, rtpPort, rtcpPort,
            ip_anyaddr, 0, 0,
            calculatePriority(ICE_PREF_TYPE_HOST, priority, ICE_PREF_COMP_RTP, iceCandidateCounter),
            iceRtcp ? calculatePriority(ICE_PREF_TYPE_HOST, priority, ICE_PREF_COMP_RTCP, iceCandidateCounter) : zeroPriority(iceCandidateCounter));
    }
}

void Ice::addHostCandidate(IPaddr addr, byte type)
{
    if(log->LogFlagSet(LOG_ICE)) {
        char addrCandidate[128];
        str::from_ip(addrCandidate, 128, &addr);
        debug->printf("ICE(%p)::Found HOST address %s (interface) type %u", this, addrCandidate, type);
    }
    word rtpPort = iceLocalPort;
    word rtcpPort = !iceRtcp ? 0 : iceLocalRtcpPort;
    dword priority = (iceIp4Preference ? ICE_PREF_LOCL_IP4_HIGH : ICE_PREF_LOCL_IP4_LOW);
    // Audio HOST
    dword rtpPrio = calculatePriority(ICE_PREF_TYPE_HOST, priority, ICE_PREF_COMP_RTP, iceCandidateCounter);
    dword rtcpPrio = iceRtcp ? calculatePriority(ICE_PREF_TYPE_HOST, priority, ICE_PREF_COMP_RTCP, iceCandidateCounter) : zeroPriority(iceCandidateCounter);
    addCandidate(iceLocal, type,
        addr, rtpPort, rtcpPort,
        ip_anyaddr, 0, 0,
        rtpPrio, rtcpPrio);
    AddIceCandidate(type, addr, rtpPort, rtcpPort, ip_anyaddr, 0, 0, rtpPrio, rtcpPrio);
}

void Ice::addSrflxCandidate(ChannelCandidate * ch)
{
    if(ch && !ip_match(ch->addr, ip_anyaddr)) {
        if(log->LogFlagSet(LOG_ICE)) {
            char addrCandidate[128];
            str::from_ip(addrCandidate, 128, &ch->addr);
            debug->printf("ICE(%p)::Found SRFLX address %s (interface)", this, addrCandidate);
        }
        // Audio/Video/AppSharing SRFLX
        dword rtpPrio = calculatePriority(ICE_PREF_TYPE_SRFLX, iceIp4Preference ? ICE_PREF_LOCL_IP4_HIGH : ICE_PREF_LOCL_IP4_LOW, ICE_PREF_COMP_RTP, iceCandidateCounter);
        dword rtcpPrio = iceRtcp ? calculatePriority(ICE_PREF_TYPE_SRFLX, iceIp4Preference ? ICE_PREF_LOCL_IP4_HIGH : ICE_PREF_LOCL_IP4_LOW, ICE_PREF_COMP_RTCP, iceCandidateCounter) : zeroPriority(iceCandidateCounter);
        if(AddIceCandidate(CHANNEL_CANDIDATE_SRFLX, localAddrStorage, iceLocalPort, iceLocalRtcpPort, ch->addr, ch->rtpPort, ch->rtcpPort, rtpPrio, rtcpPrio)) {
            addCandidate(iceLocal, CHANNEL_CANDIDATE_SRFLX,
                ch->addr, ch->rtpPort, ch->rtcpPort,
                ch->relatedAddr, iceLocalPort, iceRtcp ? iceLocalRtcpPort : 0, rtpPrio, rtcpPrio);
        }
    }
}

void Ice::addRelayCandidate(IPaddr rtpAddr, word rtpPort, word rtcpPort, IPaddr relatedRtpAddr, word relatedRtpPort, word relatedRtcpPort)
{
    if (!ip_match(rtpAddr, ip_anyaddr)) {
        if(log->LogFlagSet(LOG_ICE)) {
            char addrCandidate[128];
            str::from_ip(addrCandidate, 128, &relatedRtpAddr);
            debug->printf("ICE(%p)::Found RELAY address %s:%d,%d", this, addrCandidate, relatedRtpPort, relatedRtcpPort);
        }
        dword rtpPrio = calculatePriority(ICE_PREF_TYPE_RELAY, iceIp4Preference ? ICE_PREF_SIG_IP4_HIGH : ICE_PREF_SIG_IP4_LOW, ICE_PREF_COMP_RTP, iceCandidateCounter);
        dword rtcpPrio = iceRtcp ? calculatePriority(ICE_PREF_TYPE_RELAY, iceIp4Preference ? ICE_PREF_SIG_IP4_HIGH : ICE_PREF_SIG_IP4_LOW, ICE_PREF_COMP_RTCP, iceCandidateCounter) : zeroPriority(iceCandidateCounter);
        if(AddIceCandidate(CHANNEL_CANDIDATE_RELAY, localAddrStorage, iceLocalPort, iceRtcp ? iceLocalRtcpPort : 0, relatedRtpAddr, relatedRtpPort, iceRtcp ? relatedRtcpPort : 0, rtpPrio, rtcpPrio)) {
            addCandidate(iceLocal, CHANNEL_CANDIDATE_RELAY,
                relatedRtpAddr, relatedRtpPort, iceRtcp ? relatedRtcpPort : 0,
                rtpAddr, rtpPort, iceRtcp ? rtcpPort : 0, rtpPrio, rtcpPrio);
        }
    }
}

void Ice::iceTurnInitialized(IPaddr rtpAddr, word rtpPort, word rtcpPort, IPaddr relatedRtpAddr, word relatedRtpPort, word relatedRtcpPort, IPaddr mappedRtpAddr, word mappedRtpPort, word mappedRtcpPort)
{
    if (!iceGatheredTurn) {
        iceGatheredTurn = true;
#if 0
        if (turnOnly && rtpAddr) {
            // keep HOST and SRFLX ports hidden
            memcpy(relatedRtpAddr, rtpAddr, sizeof(IPaddr));
            memcpy(relatedRtcpAddr, rtcpAddr, sizeof(IPaddr));
        }
#endif
        if (!iceDisabled) {
            addRelayCandidate(rtpAddr, rtpPort, rtcpPort, relatedRtpAddr, relatedRtpPort, relatedRtcpPort);
            log->Log(LOG_ICE, "ICE(%p)::Gathering RELAY candidates finished", this);
        }

        iceTryInitialized();
    }
}

void Ice::showIceCandidates()
{
    if(log->LogFlagSet(LOG_ICE)) {
        if(iceLocal.common.count) {
            debug->printf("ICE(%p) Local Candidates %d RtcpMux(%d) (%s:%s) fingerprint (%d)", this, iceLocal.common.count, iceLocal.common.rtcpMux, iceLocal.common.usr, iceLocal.common.pwd, iceLocal.common.fingerprintLen);
        }
        for(int i=0; i<iceLocal.common.count; i++) {
            char addrCandidate[128];
            str::from_ip(addrCandidate, 128, &iceLocal.candidate[i].addr);
            if(iceLocal.candidate[i].type == CHANNEL_CANDIDATE_HOST)
                debug->printf("ICE(%p) [%d] %s:%d,%d", this, iceLocal.candidate[i].type, addrCandidate, iceLocal.candidate[i].rtpPort, iceLocal.candidate[i].rtcpPort);
            else {
                char addrRelay[128];
                str::from_ip(addrRelay, 128, &iceLocal.candidate[i].relatedAddr);
                debug->printf("ICE(%p) [%d] %s:%d,%d relayed (%s:%d,%d)", this, iceLocal.candidate[i].type, addrCandidate, iceLocal.candidate[i].rtpPort, iceLocal.candidate[i].rtcpPort, addrRelay, iceLocal.candidate[i].relatedRtpPort, iceLocal.candidate[i].relatedRtcpPort);
            }
        }
        if(iceRemote.common.count) {
            debug->printf("ICE(%p) Remote Candidates %d RtcpMux(%d) (%s:%s)", this, iceRemote.common.count, iceRemote.common.rtcpMux, iceRemote.common.usr, iceRemote.common.pwd);
        }
        for(int i=0; i<iceRemote.common.count; i++) {
            char addrCandidate[128];
            str::from_ip(addrCandidate, 128, &iceRemote.candidate[i].addr);
            if(iceLocal.candidate[i].type == CHANNEL_CANDIDATE_HOST)
                debug->printf("ICE(%p) [%d] %s:%d,%d", this, iceRemote.candidate[i].type, addrCandidate, iceRemote.candidate[i].rtpPort, iceRemote.candidate[i].rtcpPort);
            else {
                char addrRelay[128];
                str::from_ip(addrRelay, 128, &iceRemote.candidate[i].relatedAddr);
                debug->printf("ICE(%p) [%d] %s:%d,%d relayed (%s:%d,%d)", this, iceRemote.candidate[i].type, addrCandidate, iceRemote.candidate[i].rtpPort, iceRemote.candidate[i].rtcpPort, addrRelay, iceRemote.candidate[i].relatedRtpPort, iceRemote.candidate[i].relatedRtcpPort);
            }
        }
    }
}

void Ice::iceAbortStun()
{
    log->Log(LOG_ICE, "ICE(%p)::iceAbortStun %d,%d,%d", this, iceGatheredHost, iceGatheredStun, iceGatheredTurn);
    iceStunInitialized(NULL, NULL);
}

void Ice::iceTryInitialized()
{
    //log->Log(LOG_ICE, "ICE(%p)::iceTryInitialized %d,%d,%d", this, iceGatheredHost, iceGatheredStun, iceGatheredTurn);
    if (iceGatheredHost && iceGatheredStun && iceGatheredTurn) {
        iceState = waiting;
        iceStartKeepAlive();

        // implementation supports RTCP-Mux
        iceLocal.common.rtcpMux = true;

        log->Log(LOG_ICE, "ICE(%p)::Initialized local candidates %sRTP(%s:%d) RTCP(%s:%d)", this, iceDisabled ? "DISABLED " : "", iceLocalAddr, iceLocalPort, iceLocalAddr, iceRtcp ? iceLocalRtcpPort : 0);

        IceInitialized(iceLocal.common.count ? &iceLocal : 0);
    }
}

void Ice::iceConnect(struct ChannelIce * ice, bool controlling, bool stunDraft2)
{
    debug->printf("ICE(%p,%p)::Connect role=%s stun=%s candidates %d,%d state %d", this, ice, controlling ? "controlling" : "controlled", stunDraft2 ? "microsoft" : "standard", iceLocalConnected->common.count, ice->common.count, iceState);

    if (iceState != idle && iceState != gathering) {
        if (ice->common.lite) {
            log->Log(LOG_ICE, "ICE(%p)::Peer is a lite implementation%s", this, controlling ? "" : ", set role to controlling");
            controlling = true;
        }

        IceTimerStop();
        clearChecks();
        iceRemote.copy(ice);
        iceControlling = controlling;
        iceRoleChanged = false;
        iceTiebreaker = IRandom::GetRandom() * (ulong64)0x100000000ULL + IRandom::GetRandom();
        iceLocalConnected = &iceLocal;
        iceStunDraft2 = stunDraft2;

        iceConnectedLocalAddr = ip_anyaddr;
        iceConnectedLocalRtpPort = 0;
        iceConnectedLocalRtcpPort = 0;
        iceConnectedRemoteAddr = ip_anyaddr;
        iceConnectedRemoteRtpPort = 0;
        iceConnectedRemoteRtcpPort = 0;

        for (int i = 0; i<iceLocalConnected->common.count; i++) {
            if(ChannelCandidateIsHost(iceLocalConnected->candidate[i].type) || ChannelCandidateIsRelay(iceLocalConnected->candidate[i].type)) {
                for (int j = 0; j<iceRemote.common.count; j++) {
                    bool localIp4 = is_ip4(iceLocalConnected->candidate[i].addr);
                    bool localTcp = ChannelCandidateIsTcp(iceLocalConnected->candidate[i].type);
                    bool localTcpPassive = ChannelCandidateIsTcpPassive(iceLocalConnected->candidate[i].type);
                    bool localTcpActive = ChannelCandidateIsTcpActive(iceLocalConnected->candidate[i].type);
                    bool localTcpSO = ChannelCandidateIsTcpSO(iceLocalConnected->candidate[i].type);
                    bool remoteIp4 = is_ip4(iceRemote.candidate[j].addr);
                    bool remoteTcp = ChannelCandidateIsTcp(iceRemote.candidate[j].type);
                    bool remoteTcpPassive = ChannelCandidateIsTcpPassive(iceRemote.candidate[j].type);
                    bool remoteTcpActive = ChannelCandidateIsTcpActive(iceRemote.candidate[j].type);
                    bool remoteTcpSO = ChannelCandidateIsTcpSO(iceRemote.candidate[j].type);
                    if ((localIp4 == remoteIp4 || iceRemote.candidate[j].mDns)
                     && ((!localTcp && !remoteTcp)
                      || (localTcpPassive && remoteTcpActive)
                      || (localTcpActive && remoteTcpPassive)
                      || (localTcpSO && remoteTcpSO))) {
                        if (iceRemote.candidate[j].rtpPort != 0) {
                            class ISocket * rtpSocket = ChannelCandidateIsRelay(iceLocalConnected->candidate[i].type) ? tcp4() : rtp4();
                            class ISocket * rtcpSocket = (!iceRtcp || rtcpMux) ? 0 : (ChannelCandidateIsRelay(iceLocalConnected->candidate[i].type) ? rtcpTcp4() : rtcp4());
                            class IceCheck * check = new class IceCheck(&iceLocalConnected->candidate[i], &iceRemote.candidate[j], iceControlling, rtpSocket, rtcpSocket, (iceLocalConnected->candidate[i].type == CHANNEL_CANDIDATE_RELAY));
                            class IceCheck * old = findCheck(false, localTcp, check->connection.localAddr, check->connection.localRtpPort, check->connection.remoteAddr, check->connection.remoteRtpPort);
                            if (old) {
                                if (old->priority.rtp < check->priority.rtp) {
                                    removeCheck(localTcp, old);
                                    addCheck(localTcp, check);
                                }
                                else {
                                    delete check;
                                }
                            }
                            else {
                                class IceCheck * existingRtcp = (class IceCheck *)dynamic_cast<class IceCheckBtreeConnectionRtcp *>((dynamic_cast<class IceCheckBtreeConnectionRtcp *>(iceCheckConnectionRtcp)->btree_find(&check->connection)));
                                if (existingRtcp != 0) {
                                    log->Log(LOG_ICE, "ICE(%p)::Ignore invalid candidate pair (duplicate RTCP port)", this);
                                    delete check;
                                }
                                else {
                                    addCheck(localTcp, check);
                                }
                            }
                        }
                        else {
                            log->Log(LOG_ICE, "ICE(%p)::Ignore invalid candidate pair (RTP port is 0)", this);
                        }
                    }
                }
            }
        }
        iceState = connecting;
        activeChecks = 0;
        currentCheck = 0;
        nominatedCheck = 0;
        waitLast = false;
        if (iceControlling) IceTimerStart(0);
        else {
            log->Log(LOG_ICE, "ICE(%p)::Wait 20ms on controlled side", this);
            IceTimerStart(20);
        }
    }
    else {
        ice->clear();
    }
}

static dword _iceCheckPriorityCounter = 0xffffffff;

IceCheck::IceCheck(ChannelCandidate * local, ChannelCandidate * remote, bool controlling, class ISocket * rtpSocket, class ISocket * rtcpSocket, bool turn)
{
    if (local && remote) {
#if 0
        char localAddr[128];
        str::from_ip(localAddr, 128, &local->addr);
        char localRelated[128];
        str::from_ip(localRelated, 128, &local->relatedAddr);
        char remoteAddr[128];
        str::from_ip(remoteAddr, 128, &remote->addr);
        char remoteRelated[128];
        str::from_ip(remoteRelated, 128, &remote->relatedAddr);

        debug->printf("IceCheck::IceCheck (%d,%d) local %s:%d,%d %s:%d,%d remote %s:%d,%d %s:%d,%d sockets=%p,%p turn=%d", local->type, remote->type,
            localAddr, local->rtpPort, local->rtcpPort, localRelated, local->relatedRtpPort, local->relatedRtcpPort,
            remoteAddr, remote->rtpPort, remote->rtcpPort, remoteRelated, remote->relatedRtpPort, remote->relatedRtcpPort,
            rtpSocket, rtcpSocket, turn);
#endif
#if defined _IOS_
        this->connection.remoteAddr = remote->mDns ? (is_ip4(local->addr) ? ip_4_loopback : ip_loopback) : remote->addr;
#else
        this->connection.remoteAddr = remote->addr;
#endif
        this->connection.remoteRtpPort = remote->rtpPort;
        this->connection.remoteRtcpPort = remote->rtcpPort;
        this->connection.localAddr = local->addr;
        this->connection.localRtpPort = local->rtpPort;
        this->connection.localRtcpPort = rtcpSocket ? local->rtcpPort : 0;
        //debug->printf("IceCheck (%p) %s:%d,%d %s:%d,%d", this, this->connection.localAddr, this->connection.localRtpPort, this->connection.localRtcpPort, this->connection.remoteAddr, this->connection.remoteRtpPort, this->connection.remoteRtcpPort);
        this->priority.rtpReflexive = local->rtpPriority;
        this->priority.rtcpReflexive = local->rtcpPriority;
        this->priority.rtp = calculateCheckPriority(controlling, local->rtpPriority, remote->rtpPriority);
        this->priority.rtcp = calculateCheckPriority(controlling, local->rtcpPriority, remote->rtcpPriority);
    }
    else {
        memset(&this->connection, 0, sizeof(IceCheck::connection));
        memset(&this->priority, 0, sizeof(IceCheck::priority));
    }
    this->priority.dummy = _iceCheckPriorityCounter--;
    this->rtpState = waiting;
    this->rtcpState = frozen;
    this->rtpNominated = false;
    this->rtcpNominated = false;
    IceStun::createId(&rtpId);
    IceStun::createId(&rtcpId);
    stunRtpRequest = 0;
    stunRtpRequestLen = 0;
    stunRtcpRequest = 0;
    stunRtcpRequestLen = 0;
    rtpRetry = 0;
    rtcpRetry = 0;
    this->turn = turn;
    this->rtpSocket = rtpSocket;
    this->rtcpSocket = rtcpSocket;
}

IceCheck::~IceCheck()
{
    if(stunRtpRequest) free(stunRtpRequest);
    if(stunRtcpRequest) free(stunRtcpRequest);
}

static const char * states[9] = { "frozen", "waiting", "in-progress", "succeeded", "failed", "nominated", "valid", "nominating", "" };

void IceCheck::createStunRtp(const char * lfrag, const char * rfrag, const char * password, bool controlling, ulong64 tiebreaker, bool nominating, bool stunDraft2)
{
    rtpRetry = 0;
    IceStun::createId(&rtpId);
    if (stunRtpRequest) free(stunRtpRequest);
    stunRtpRequest = IceStun::writeRequest(&rtpId, lfrag, rfrag, password, priority.rtpReflexive, controlling, tiebreaker, nominating, stunRtpRequestLen, stunDraft2);
}

void IceCheck::createStunRtcp(const char * lfrag, const char * rfrag, const char * password, bool controlling, ulong64 tiebreaker, bool nominating, bool stunDraft2)
{
    rtcpRetry = 0;
    IceStun::createId(&rtcpId);
    if (stunRtcpRequest) delete stunRtcpRequest;
    stunRtcpRequest = IceStun::writeRequest(&rtcpId, lfrag, rfrag, password, priority.rtcpReflexive, controlling, tiebreaker, nominating, stunRtcpRequestLen, stunDraft2);
}

int IceCheck::trace(char * buffer, int len, const char * prefix)
{
    if (len < 200) return 0;
    char localAddr[128];
    str::from_ip(localAddr, 128, &connection.localAddr);
    char remoteAddr[128];
    str::from_ip(remoteAddr, 128, &connection.remoteAddr);
    int l = _sprintf(buffer, "\n     %sRTP(%s:%u -> %s:%u %s%s), RTCP(%s:%u -> %s:%u %s%s)",
        prefix,
        localAddr, connection.localRtpPort, remoteAddr, connection.remoteRtpPort, states[rtpState<8 ? rtpState : 8], rtpNominated ? " nominated" : "",
        localAddr, connection.localRtcpPort, remoteAddr, connection.remoteRtcpPort, states[rtcpState<8 ? rtcpState : 8], rtcpNominated ? " nominated" : "");
    return l;
}

IceLocalCredentials::IceLocalCredentials()
{
    memset(this, 0, sizeof(*this));
}

ChannelIce::ChannelIce()
{
    this->common.count = 0;
    clear();
}

void ChannelIce::clear()
{
    memset(this, 0, sizeof(ChannelIce));
}

void ChannelIce::copy(const struct ChannelIce * from)
{
    if (from) memcpy(this, from, sizeof(ChannelIce) - (CHANNEL_MAX_CANDIDATE - from->common.count)*sizeof(ChannelCandidate));
    else clear();
}

void ChannelIce::copy(const struct IceLocalCredentials * localCredentials)
{
    if (localCredentials) {
        str::to_str(localCredentials->usr, common.usr, sizeof(common.usr));
        str::to_str(localCredentials->pwd, common.pwd, sizeof(common.pwd));
    }
}

void ChannelIce::save(struct ChannelIce * & to)
{
    if (to) free(to);
    to = (struct ChannelIce *)malloc(sizeof(*this) - (CHANNEL_MAX_CANDIDATE - this->common.count)*sizeof(ChannelCandidate));
    memcpy(to, this, sizeof(*this) - (CHANNEL_MAX_CANDIDATE - this->common.count)*sizeof(ChannelCandidate));
}

int ChannelIce::trace(const struct ChannelIce * ice, const char * name, char * buffer, int len)
{
    static const char * types[7] = { "HOST", "SRFLX", "PRFLX", "RELAY", "" };
    int p;
    if (ice && (ice->common.count || ice->common.fingerprintLen || ice->common.usr[0] || ice->common.pwd[0]) && (len >= 200)) {
        p = _sprintf(buffer, "\n      CANDIDATES:%s,count(%d),fingerprint(%d),usr(%s),pwd(%s)%s%s",
            name,
            ice->common.count,
            ice->common.fingerprintLen,
            ice->common.usr,
            ice->common.pwd,
            ice->common.lite ? ",lite" : "",
            ice->common.rtcpMux ? ",rtcpMux" : "");
        for (int i = 0; (i < ice->common.count) && (p + 200 < len); i++) {
            char localAddr[128];
            str::from_ip(localAddr, 128, (IPaddr *)&ice->candidate[i].addr);
            p += _sprintf(buffer + p, "\n    %s:%s addr([%s]:%u/%u)",
                ice->candidate[i].foundation[0] ? ice->candidate[i].foundation : " ",
                types[ice->candidate[i].type <= 3 ? ice->candidate[i].type : 4],
                localAddr,
                ice->candidate[i].rtpPort,
                ice->candidate[i].rtcpPort);
            if(!is_anyaddr(ice->candidate[i].relatedAddr) || ice->candidate[i].relatedRtpPort || ice->candidate[i].relatedRtcpPort) {
                char relatedAddr[128];
                str::from_ip(relatedAddr, 128, (IPaddr *)&ice->candidate[i].relatedAddr);
                p += _sprintf(buffer + p, " related([%s]:%u/%u)",
                    relatedAddr,
                    ice->candidate[i].relatedRtpPort,
                    ice->candidate[i].relatedRtcpPort);
            }
            if (ice->candidate[i].rtpPriority || ice->candidate[i].rtcpPriority) {
                p += _sprintf(buffer + p, " prio(%u/%u)",
                    ice->candidate[i].rtpPriority,
                    ice->candidate[i].rtcpPriority);
            }
        }
    }
    else {
        p = 0;
        buffer[0] = 0;
    }
    return p;
}

int IceCheckBtreeConnectionRtp::btree_compare(void * key)
{
    struct IceCheck::iceCheckConnection * a = &((class IceCheck *)dynamic_cast<class IceCheckBtreeConnectionRtp *>(this))->connection;
    struct IceCheck::iceCheckConnection * b = (IceCheck::iceCheckConnection *)key;
    if(a->localAddr.q[0] > b->localAddr.q[0]) return 1;
    if(a->localAddr.q[0] < b->localAddr.q[0]) return -1;
    if(a->remoteAddr.q[0] > b->remoteAddr.q[0]) return 1;
    if(a->remoteAddr.q[0] < b->remoteAddr.q[0]) return -1;
    if(a->localAddr.q[1] > b->localAddr.q[1]) return 1;
    if(a->localAddr.q[1] < b->localAddr.q[1]) return -1;
    if(a->remoteAddr.q[1] > b->remoteAddr.q[1]) return 1;
    if(a->remoteAddr.q[1] < b->remoteAddr.q[1]) return -1;
    if(a->localRtpPort > b->localRtpPort) return 1;
    if(a->localRtpPort < b->localRtpPort) return -1;
    if(a->remoteRtpPort > b->remoteRtpPort) return 1;
    if(a->remoteRtpPort < b->remoteRtpPort) return -1;
    return 0;
}

int IceCheckBtreeConnectionRtp::btree_compare(class btree * c)
{
    struct IceCheck::iceCheckConnection * b = &((class IceCheck *)dynamic_cast<class IceCheckBtreeConnectionRtp *>(c))->connection;
    return IceCheckBtreeConnectionRtp::btree_compare(b);
}

int IceCheckBtreeConnectionRtcp::btree_compare(void * key)
{
    struct IceCheck::iceCheckConnection * a = &((class IceCheck *)dynamic_cast<class IceCheckBtreeConnectionRtcp *>(this))->connection;
    struct IceCheck::iceCheckConnection * b = (IceCheck::iceCheckConnection *)key;
    if(a->localAddr.q[0] > b->localAddr.q[0]) return 1;
    if(a->localAddr.q[0] < b->localAddr.q[0]) return -1;
    if(a->remoteAddr.q[0] > b->remoteAddr.q[0]) return 1;
    if(a->remoteAddr.q[0] < b->remoteAddr.q[0]) return -1;
    if(a->localAddr.q[1] > b->localAddr.q[1]) return 1;
    if(a->localAddr.q[1] < b->localAddr.q[1]) return -1;
    if(a->remoteAddr.q[1] > b->remoteAddr.q[1]) return 1;
    if(a->remoteAddr.q[1] < b->remoteAddr.q[1]) return -1;
    if(a->localRtcpPort > b->localRtcpPort) return 1;
    if(a->localRtcpPort < b->localRtcpPort) return -1;
    if(a->remoteRtcpPort > b->remoteRtcpPort) return 1;
    if(a->remoteRtcpPort < b->remoteRtcpPort) return -1;
    return 0;
}

int IceCheckBtreeConnectionRtcp::btree_compare(class btree * c)
{
    struct IceCheck::iceCheckConnection * b = &((class IceCheck *)dynamic_cast<class IceCheckBtreeConnectionRtcp *>(c))->connection;
    return IceCheckBtreeConnectionRtcp::btree_compare(b);
}

int IceCheckBtreeConnectionRtpTcp::btree_compare(void * key)
{
    struct IceCheck::iceCheckConnection * a = &((class IceCheck *)dynamic_cast<class IceCheckBtreeConnectionRtpTcp *>(this))->connection;
    struct IceCheck::iceCheckConnection * b = (IceCheck::iceCheckConnection *)key;
    if(a->localAddr.q[0] > b->localAddr.q[0]) return 1;
    if(a->localAddr.q[0] < b->localAddr.q[0]) return -1;
    if(a->remoteAddr.q[0] > b->remoteAddr.q[0]) return 1;
    if(a->remoteAddr.q[0] < b->remoteAddr.q[0]) return -1;
    if(a->localAddr.q[1] > b->localAddr.q[1]) return 1;
    if(a->localAddr.q[1] < b->localAddr.q[1]) return -1;
    if(a->remoteAddr.q[1] > b->remoteAddr.q[1]) return 1;
    if(a->remoteAddr.q[1] < b->remoteAddr.q[1]) return -1;
    if((a->localRtpPort != 9) && (b->localRtpPort != 9)) {
        if(a->localRtpPort > b->localRtpPort) return 1;
        if(a->localRtpPort < b->localRtpPort) return -1;
    }
    if((a->remoteRtpPort != 9) && (b->remoteRtpPort != 9)) {
        if(a->remoteRtpPort > b->remoteRtpPort) return 1;
        if(a->remoteRtpPort < b->remoteRtpPort) return -1;
    }
    return 0;
}

int IceCheckBtreeConnectionRtpTcp::btree_compare(class btree * c)
{
    struct IceCheck::iceCheckConnection * b = &((class IceCheck *)dynamic_cast<class IceCheckBtreeConnectionRtpTcp *>(c))->connection;
    return IceCheckBtreeConnectionRtpTcp::btree_compare(b);
}

int IceCheckBtreePriority::btree_compare(void * key)
{
    struct IceCheck::iceCheckPriority * a = &((class IceCheck *)dynamic_cast<class IceCheckBtreePriority *>(this))->priority;
    struct IceCheck::iceCheckPriority * b = (IceCheck::iceCheckPriority *)key;
    if (a->rtp > b->rtp) return 1;
    if (a->rtp < b->rtp) return -1;
    if (a->rtpReflexive > b->rtpReflexive) return 1;
    if (a->rtpReflexive < b->rtpReflexive) return -1;
    if (a->rtcp > b->rtcp) return 1;
    if (a->rtcp < b->rtcp) return -1;
    if (a->rtcpReflexive > b->rtcpReflexive) return 1;
    if (a->rtcpReflexive < b->rtcpReflexive) return -1;
    if (a->dummy > b->dummy) return 1;
    if (a->dummy < b->dummy) return -1;
    return 0;
}

int IceCheckBtreePriority::btree_compare(class btree * c)
{
    struct IceCheck::iceCheckPriority * b = &((class IceCheck *)dynamic_cast<class IceCheckBtreePriority *>(c))->priority;
    return IceCheckBtreePriority::btree_compare(b);
}
