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

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

#include "sdk/common/interface/time.h"
#include "sdk/common/interface/random.h"
#include "sdk/common/interface/task.h"
#include "sdk/common/interface/file.h"
#include "sdk/common/interface/socket.h"
#include "sdk/common/interface/dns.h"
#include "sdk/common/interface/guuid.h"
#include "common/ilib/cipher.h"
#include "common/ilib/hash.h"
#include "common/ilib/str.h"
#include "sdk/common/interface/network.h"
#include "sdk/common/interface/dtls.h"
#include "sdk/common/interface/media.h"
#include "sdk/common/interface/certificates.h"
#include "sdk/common/interface/environment.h"

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

#include "turn_stun.h"
#include "turn.h"
#include "ice.h"
#include "sctp_rtc.h"
#include "sdk/common/interface/http_client.h"
#include "recording.h"
#include "media.h"
#include "audio_endpoint.h"
#include "appsharing_endpoint.h"
#include "video_endpoint.h"

#define RTP_DTMF_FLAG_END 0x80

#define RTCP_PACKET_TYPE_SR     200 // sender report
#define RTCP_PACKET_TYPE_RR     201 // receiver report
#define RTCP_PACKET_TYPE_SDES   202 // source description
#define RTCP_PACKET_TYPE_BYE    203 // goodbye
#define RTCP_PACKET_TYPE_APP    204 // application-defined
#define RTCP_PACKET_TYPE_RTPFB  205 // Transport layer FB message (RFC-4585)
#define RTCP_PACKET_TYPE_PSFB   206 // Payload-specific FB message (RFC-4585)
#define RTCP_PACKET_TYPE_XR     207 // Extended Report (RFC-3611)

static bool useAlternateTurnAddr = false;
static bool tryAlternateTurnAddr = false;
static char charpool[] = {   // 64
        0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
        0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c,
        0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x51,
        0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
        0x58, 0x59, 0x5a, 0x61, 0x62, 0x63,
        0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
        0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
        0x70, 0x71, 0x71, 0x72, 0x73, 0x74,
        0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
        0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
        0x36, 0x37, 0x38, 0x39
};

static dword getTimestamp8kHz()
{
    return (dword)(ITime::TimeStampMilliseconds() << 3);
}

static dword timestamp2ntp32(dword timestamp)
{
    ulong64 t = timestamp;
    return (dword)((t<<13)/1000);
}

static const char * mediaType(enum MediaType mediaType)
{
    if (mediaType == MediaType::AUDIO) return "AUDIO";
    if (mediaType == MediaType::VIDEO) return "VIDEO";
    if (mediaType == MediaType::APPSHARING) return "APPSHARING";
    return "";
}

class IMediaProvider * CreateMediaProvider()
{
    return new MediaProvider();
}

MediaProvider::MediaProvider()
{
}

class IMedia * MediaProvider::CreateMedia(class IIoMux * iomux, class UMedia * const user, IInstanceLog * const log, enum MediaType mediaType)
{
    return new Media(iomux, user, log);
}

/*---------------------------------------------------------------------------*/
/* Media                                                                     */
/*---------------------------------------------------------------------------*/

Media::Media(class IIoMux * iomux, class UMedia * const user, class IInstanceLog * const log):
log(log),
taskDns(this, &Media::dnsRequestComplete, 0, 0)
{
    log->Log(LOG_MEDIA, "Media(%p)::Media", this);
    this->iomux = iomux;
    this->user = user;
    this->stunServers = NULL;
    this->turnServers = NULL;
    this->turnUsername = NULL;
    this->turnPassword = NULL;
    this->socketContext = NULL;
    this->foundation = 0;
    this->mediaConfig = NULL;
    this->remoteMediaConfig = NULL;
    this->channelConnected = NULL;
    this->multicastChannel = NULL;
    this->closing = false;
    this->nominating = false;
    this->interfaces = NULL;
    this->dns = NULL;
    this->reqPBX = NULL;
    this->sockPBX = NULL;
    this->noVpnAddresses = false;
    this->currentUserSip = NULL;
    this->assignmentsList = NULL;
    this->iceTurnError4 = NULL;
    this->iceTurnError6 = NULL;
    this->recordingUrl = NULL;
    this->recordOn = false;
    this->recordingUsername = NULL;
    this->recordingPassword = NULL;
    for (unsigned int i = 0; i < CHANNEL_MAX_CANDIDATE; i++) {
        this->excludeInfs[i] = NULL;
    }
    this->recordRtpStream = false;
    for(unsigned int i=0; i<CHANNEL_MAX_CANDIDATE; i++) {
        relayedAddrCand[i] = NULL;
    }
    for(unsigned int i=0; i<CHANNEL_MAX_CANDIDATE; i++) {
        srflxAddrCand[i] = NULL;
    }
    IRandom::Init((dword)time(NULL));
}

Media::~Media()
{
    ASSERT(!reqPBX && !sockPBX && channels.empty(), "Media::~Media deleted without waiting for MediaCloseComplete");

    log->Log(LOG_MEDIA, "Media(%p)::~Media ch=%d", this, this->channels.empty());

    if(stunServers) free(stunServers);
    if(turnServers) free(turnServers);
    if(turnUsername) free(turnUsername);
    if(turnPassword) free(turnPassword);
    for(unsigned int i = 0; i<CHANNEL_MAX_CANDIDATE; i++) {
        if(relayedAddrCand[i]) free(relayedAddrCand[i]);
    }
    for(unsigned int i=0; i<CHANNEL_MAX_CANDIDATE; i++) {
        if(srflxAddrCand[i]) free(srflxAddrCand[i]);
    }
    if(!this->channels.empty()) {
        while(this->channels.front()) {
            delete this->channels.front();
        }
    }
    if(interfaces) delete interfaces;
    if(mediaConfig) delete mediaConfig;
    if(remoteMediaConfig) delete remoteMediaConfig;

    if(currentUserSip) free(currentUserSip);
    if(iceTurnError4) delete iceTurnError4;
    if(iceTurnError6) delete iceTurnError6;
    if(recordingUrl) free(recordingUrl);
    if(recordingUsername) free(recordingUsername);
    if(recordingPassword) free(recordingPassword);
    for (unsigned int i = 0; i < CHANNEL_MAX_CANDIDATE; i++) {
        if (excludeInfs[i]) free(excludeInfs[i]);
    }
}

void Media::ParseExcludeInterfaces(const char * excludeInterfaces)
{
    if (!excludeInterfaces || !excludeInterfaces[0]) return;

    int len = strlen(excludeInterfaces);
    char* bufInf = (char*)malloc(len + 1);

    int j = 0;
    for (int i = 0; i < len; i++) {
        if (excludeInterfaces[i] != 0x20) bufInf[j++] = excludeInterfaces[i];
    }
    bufInf[j] = 0;

    debug->printf("Media(%p)::ParseExcludeInterface excludeInterfaces=%s (%s)", this, excludeInterfaces, bufInf);

    char * tmpInf = bufInf;
    char* bufInfEnd = bufInf + strlen(bufInf);
    int index = 0;
    while (index < CHANNEL_MAX_CANDIDATE) {
        char* separator = strstr(tmpInf, ",");
        if (separator) separator[0] = 0;
        excludeInfs[index] = _strdup(tmpInf);
        debug->printf("Media(%p)::ParseExcludeInterface[%d] exclude interface %s %p (%p)", this, index, excludeInfs[index], separator, bufInfEnd);
        if (!separator || ((separator + 1) == bufInfEnd)) break;
        tmpInf = separator + 1;
        index++;
    }
    free(bufInf);
}

bool Media::ExcludeMediaInterface(const char * addr)
{
    if (!addr) return false;

    int lenAddr = strlen(addr);
    for (unsigned int i = 0; i < CHANNEL_MAX_CANDIDATE; i++) {
        if (!excludeInfs[i]) return false;
        else {
            int lenEx = strlen(excludeInfs[i]);
            int cmpLen = lenEx < lenAddr ? lenEx : lenAddr;
            if (!memcmp(excludeInfs[i], addr, cmpLen)) {
                log->Log(LOG_MEDIA, "Media(%p)::ExcludeMediaInterface %s", this, addr);
                return true;
            }
        }
    }
    return false;
}

void Media::dnsRequestComplete(class DnsRequest * task)
{
    log->Log(LOG_MEDIA, "Media(%p,%p)::dnsRequestComplete %s -> %s (%d)", this, task, task->hostname, (task->addr ? task->addr : "NULL"), task->isIPv6);
    if(task == reqPBX) {
        reqPBX = NULL;
        if(closing) {
            delete task;
            if(!reqPBX && !sockPBX && channels.empty()) DeleteAll();
            return;
        }
        if(task->addr) {
            char remoteAddress[128];
            sockPBX = udpSocketProvider->CreateSocket(iomux, this, log, task->isIPv6);
            if (task->isIPv6) {
                sprintf(remoteAddress, "[%s]:%u", task->addr, 443);
            }
            else {
                sprintf(remoteAddress, "%s:%u", task->addr, 443);
            }
            sockPBX->Connect(remoteAddress);
        }
        else {
            if(interfaces == NULL) {
                interfaces = INetwork::GetLocalAddresses();
                if(interfaces->localAddresses.empty()) {
                    user->MediaInitializeComplete(this, this->mediaConfig);
                    this->mediaConfig = NULL;
                }
                else {
                    class LocalAddr* inf;
                    int totalNumInterfaces = 0;
                    for (inf = interfaces->localAddresses.front(); inf; inf = inf->goNext()) {
                        if (!(noVpnAddresses && inf->vpn) && inf->addr && inf->preferred
#ifdef _IOS_
                         && ((strcmp(inf->addr, "127.0.0.1") != 0) || (type != MediaType::AUDIO))
#endif
                           ) {
                            totalNumInterfaces += rtpOverTcp ? 2 : 1;
                        }
                    }
                    int interfaceIndex = 1;
                    for(inf = interfaces->localAddresses.front(); inf; inf = inf->goNext()) {
                        if(!(noVpnAddresses && inf->vpn) && inf->addr && !ExcludeMediaInterface(inf->addr) && inf->preferred
#ifdef _IOS_
                         && ((strcmp(inf->addr, "127.0.0.1") != 0) || (type != MediaType::AUDIO))
#endif
                           ) {
                            class MediaChannel* ch = new class MediaChannel(udpSocketProvider, tcpSocketProvider, socketContext, minPort, maxPort, iomux, this, type, inf->addr, inf->iPv6, false, false, stunServers, turnServers, turnUsername, turnPassword, turnOnly, iceNoHost, stunSlow, dropMediaTx, dropMediaRx, mediaConfig, log, interfaceIndex, totalNumInterfaces, tlsSocketProvider, recordingUrl, &guidMedia, recordOn, recordRtpStream, currentUserSip, recordingUsername, recordingPassword);
                            channels.push_back(ch);
                            log->Log(LOG_MEDIA, "Media(%p,%p)::dnsRequestComplete LocalAddr(%d/%d) %s (primary=%d) Media %s ipv6 %d Channel %p", this, interfaces, interfaceIndex, totalNumInterfaces, inf->addr, false, mediaType(type), inf->iPv6, ch);
                            interfaceIndex++;
                            if(rtpOverTcp) {
                                ch = new class MediaChannel(udpSocketProvider, tcpSocketProvider, socketContext, minPort, maxPort, iomux, this, type, inf->addr, inf->iPv6, true, false, stunServers, turnServers, turnUsername, turnPassword, turnOnly, iceNoHost, stunSlow, dropMediaTx, dropMediaRx, mediaConfig, log, interfaceIndex, totalNumInterfaces, tlsSocketProvider, recordingUrl, &guidMedia, recordOn, recordRtpStream, currentUserSip, recordingUsername, recordingPassword);
                                channels.push_back(ch);
                                interfaceIndex++;
                            }
                        }
                    }
                    if(channels.empty()) {
                        user->MediaInitializeComplete(this, this->mediaConfig);
                        this->mediaConfig = NULL;
                    }
                }
            }
        }
        delete task;
    }
}

void Media::SocketConnectComplete(ISocket * const socket)
{
    char localAdress[128];
    word lport = 0;
    socket->GetLocalAddress(localAdress, 128, &lport);
    socket->Shutdown();

    debug->printf("Media(%p)::SocketConnectComplete %s:%d", this, localAdress, lport);

    if(closing) return;
    interfaces = INetwork::GetLocalAddresses();
    if(interfaces->localAddresses.empty()) {
        user->MediaInitializeComplete(this, this->mediaConfig);
        this->mediaConfig = NULL;
    }
    else {
        class LocalAddr* inf;
        int totalNumInterfaces = 0;
        int primaryInterfaces = 0;
        for (inf = interfaces->localAddresses.front(); inf; inf = inf->goNext()) {
            bool primary = (lport != 0 && !strcmp(localAdress, inf->addr));
            if (!(noVpnAddresses && inf->vpn) && inf->addr && (inf->preferred || primary)
#ifdef _IOS_
             && ((strcmp(inf->addr, "127.0.0.1") != 0) || (type != MediaType::AUDIO))
#endif
              ) {
                if (primary) primaryInterfaces += rtpOverTcp ? 2 : 1;
                totalNumInterfaces += rtpOverTcp ? 2 : 1;
            }
        }
#if TARGET_OS_IPHONE
        if (!connectivity_lan && (primaryInterfaces != 0) && !rtpOverTcp) totalNumInterfaces = primaryInterfaces;
#endif
        int interfaceIndex = 1;
        for(inf = interfaces->localAddresses.front(); inf; inf = inf->goNext()) {
            bool primary = (lport != 0 && !strcmp(localAdress, inf->addr));
            if(!(noVpnAddresses && inf->vpn) && inf->addr && !ExcludeMediaInterface(inf->addr) && (inf->preferred || primary)
#ifdef _IOS_
#if TARGET_OS_IPHONE
             && (connectivity_lan || (primaryInterfaces == 0) || rtpOverTcp || primary)
#endif
             && ((strcmp(inf->addr, "127.0.0.1") != 0) || (type != MediaType::AUDIO))
#endif
              ) {
                class MediaChannel * ch = new class MediaChannel(udpSocketProvider, tcpSocketProvider, socketContext, minPort, maxPort, iomux, this, type, inf->addr, inf->iPv6, false, primary, stunServers, turnServers, turnUsername, turnPassword, turnOnly, iceNoHost, stunSlow, dropMediaTx, dropMediaRx, mediaConfig, log, interfaceIndex, totalNumInterfaces, tlsSocketProvider, recordingUrl, &guidMedia, recordOn, recordRtpStream, currentUserSip, recordingUsername, recordingPassword);
                channels.push_back(ch);
                log->Log(LOG_MEDIA, "Media(%p,%p)::Interface LocalAddr(%d/%d) %s (primary=%d/%d) Media %s ipv6 %d Channel %p", this, interfaces, interfaceIndex, totalNumInterfaces, inf->addr, primary, primaryInterfaces, mediaType(type), inf->iPv6, ch);
                interfaceIndex++;
                if(rtpOverTcp) {
                    ch = new class MediaChannel(udpSocketProvider, tcpSocketProvider, socketContext, minPort, maxPort, iomux, this, type, inf->addr, inf->iPv6, true, primary, stunServers, turnServers, turnUsername, turnPassword, turnOnly, iceNoHost, stunSlow, dropMediaTx, dropMediaRx, mediaConfig, log, interfaceIndex, totalNumInterfaces, tlsSocketProvider, recordingUrl, &guidMedia, recordOn, recordRtpStream, currentUserSip, recordingUsername, recordingPassword);
                    channels.push_back(ch);
                    interfaceIndex++;
                }
            }
        }
        if(channels.empty()) {
            user->MediaInitializeComplete(this, this->mediaConfig);
            this->mediaConfig = NULL;
        }
    }
}

void Media::SocketShutdown(ISocket * const socket, shutdownreason_t reason)
{
    debug->printf("Media(%p)::SocketShutdown interfaces=%p", this, interfaces);
    delete socket;
    sockPBX = NULL;

    if(closing) {
        if(!reqPBX && !sockPBX && channels.empty()) DeleteAll();
        return;
    }
    if(interfaces == NULL) {
        interfaces = INetwork::GetLocalAddresses();
        if(interfaces->localAddresses.empty()) {
            user->MediaInitializeComplete(this, this->mediaConfig);
            this->mediaConfig = NULL;
        }
        else {
            class LocalAddr* inf;
            int totalNumInterfaces = 0;
            for (inf = interfaces->localAddresses.front(); inf; inf = inf->goNext()) {
                if (!(noVpnAddresses && inf->vpn) && inf->addr && inf->preferred
#ifdef _IOS_
                 && ((strcmp(inf->addr, "127.0.0.1") != 0) || (type != MediaType::AUDIO))
#endif
                 ) {
                    totalNumInterfaces += rtpOverTcp ? 2 : 1;
                }
            }
            int interfaceIndex = 1;
            for(inf = interfaces->localAddresses.front(); inf; inf = inf->goNext()) {
                if(!(noVpnAddresses && inf->vpn) && inf->addr && !ExcludeMediaInterface(inf->addr) && inf->preferred
#ifdef _IOS_
                 && ((strcmp(inf->addr, "127.0.0.1") != 0) || (type != MediaType::AUDIO))
#endif
                  ) {
                    class MediaChannel * ch = new class MediaChannel(udpSocketProvider, tcpSocketProvider, socketContext, minPort, maxPort, iomux, this, type, inf->addr, inf->iPv6, false, false, stunServers, turnServers, turnUsername, turnPassword, turnOnly, iceNoHost, stunSlow, dropMediaTx, dropMediaRx, mediaConfig, log, interfaceIndex, totalNumInterfaces, tlsSocketProvider, recordingUrl, &guidMedia, recordOn, recordRtpStream, currentUserSip, recordingUsername, recordingPassword);
                    channels.push_back(ch);
                    log->Log(LOG_MEDIA, "Media(%p,%p)::Interface LocalAddr(%d/%d) %s (primary=%d) Media %s ipv6 %d Channel %p", this, interfaces, interfaceIndex, totalNumInterfaces, inf->addr, false, mediaType(type), inf->iPv6, ch);
                    interfaceIndex++;
                    if(rtpOverTcp) {
                        ch = new class MediaChannel(udpSocketProvider, tcpSocketProvider, socketContext, minPort, maxPort, iomux, this, type, inf->addr, inf->iPv6, true, false, stunServers, turnServers, turnUsername, turnPassword, turnOnly, iceNoHost, stunSlow, rtpOverTcp, dropMediaRx, mediaConfig, log, interfaceIndex, totalNumInterfaces, tlsSocketProvider, recordingUrl, &guidMedia, recordOn, recordRtpStream, currentUserSip, recordingUsername, recordingPassword);
                        channels.push_back(ch);
                        interfaceIndex++;
                    }
                }
            }
            if(channels.empty()) {
                user->MediaInitializeComplete(this, this->mediaConfig);
                this->mediaConfig = NULL;
            }
        }
    }
}

void Media::Close()
{
    log->Log(LOG_MEDIA, "Media(%p)::Close type=%s channels=%d multicast=%d", this, mediaType(type), channels.empty(), remoteMediaConfig ? (remoteMediaConfig->mcAddr && remoteMediaConfig->mcPort) : false);

    if (closing) return;
    closing = true;

    if(remoteMediaConfig && remoteMediaConfig->mcAddr && remoteMediaConfig->mcPort) {
        if(this->multicastChannel) {
            this->multicastChannel->disconnect(remoteMediaConfig, IceAbortReason::CALL_TERMINATED);
            this->multicastChannel = NULL;
        }
    }
    else {
        if(!channels.empty()) {
            class MediaChannel * ch;
            for (ch = channels.front(); ch; ch = ch->goNext()) {
                ch->disconnect(remoteMediaConfig, IceAbortReason::CALL_TERMINATED);
            }
        }
    }
    if(remoteMediaConfig) delete remoteMediaConfig;
    remoteMediaConfig = NULL;
    if(interfaces) delete interfaces;
    interfaces = NULL;
    if (!reqPBX && !sockPBX && channels.empty()) iomux->SetExec(this, NULL);
}

void Media::IoExec(void * context)
{
    DeleteAll();
}

void Media::DeleteAll()
{
    log->Log(LOG_MEDIA, "Media(%p)::DeleteAll", this);

    if (stunServers) { free(stunServers); stunServers = NULL; }
    if (turnServers) { free(turnServers); turnServers = NULL; }
    if (turnUsername) { free(turnUsername); turnUsername = NULL; }
    if (turnPassword) { free(turnPassword); turnPassword = NULL; }
    while(!this->channels.empty()) {
        delete this->channels.front();
    }
    if(mediaConfig) delete mediaConfig;
    mediaConfig = NULL;
    if(dns) delete dns;
    dns = NULL;
    foundation = 0;
    nominating = false;
    for(unsigned int i=0; i<CHANNEL_MAX_CANDIDATE; i++) {
        if(relayedAddrCand[i]) free(relayedAddrCand[i]);
        relayedAddrCand[i] = NULL;
    }
    for(unsigned int i=0; i<CHANNEL_MAX_CANDIDATE; i++) {
        if(srflxAddrCand[i]) free(srflxAddrCand[i]);
        srflxAddrCand[i] = NULL;
    }
    user->MediaCloseComplete(this);
}

void Media::Initialize(ISocketProvider * udpSocketProvider, ISocketProvider * tcpSocketProvider, class ISocketContext * socketContext, word minPort, word maxPort, const char * stunServers, const char * turnServers, const char * turnUsername, const char * turnPassword, enum MediaType media, bool stunSlow, bool turnOnly, bool iceNoHost, bool rtpOverTcp, int dropMediaTx, int dropMediaRx, const char * hostPbx, bool noVpnAddresses, istd::list<class CodecAssignments>* assignmentsList, bool rtcpMux, class ISocketProvider * tlsSocketProvider, const char * excludeInterfaces, const char * recordingUrl, OS_GUID * guid, bool recordOn, bool recordRtpStream, const char * currentUserSip, const char * recordingUsername, const char * recordingPassword)
{
    log->Log(LOG_MEDIA, "Media(%p)::Initialize for %s Ports[%d-%d] socketContext=%p stunSlow %d turnOnly %d iceNoHost %d tcp %d (%s,%s) dropMedia=tx=%d,rx=%d recordRtpStream=%d noVpnAddresses=%d", this, mediaType(media), minPort, maxPort, socketContext, stunSlow, turnOnly, iceNoHost, rtpOverTcp, (stunServers ? stunServers : ""), (turnServers ? turnServers : ""), dropMediaTx, dropMediaRx, recordRtpStream, noVpnAddresses);

    if (closing) return;
    this->mediaConfig = new class MediaConfig();
    class IceCandidates * ice = new class IceCandidates();
    ice->rtcpMux = rtcpMux;
    ice->icePwd = GenerateRandomString(22);
    ice->iceUfrag = GenerateRandomString(4);
    mediaConfig->ice = ice;
    if(stunServers) this->stunServers = _strdup(stunServers);
    if(turnServers) this->turnServers = _strdup(turnServers);
    if(turnUsername) this->turnUsername = _strdup(turnUsername);
    if(turnPassword) this->turnPassword = _strdup(turnPassword);
    this->udpSocketProvider = udpSocketProvider;
    this->tcpSocketProvider = tcpSocketProvider;
    this->socketContext = socketContext;
    if(socketContext) {
        size_t fingerprintLen;
        if(socketContext->GetCertificateFingerprint(&fingerprintLen) == NULL) {
            log->Log(LOG_MEDIA, "Media(%p)::Initialize no certificate fingerprint available!", this);
            size_t certLen;
            byte * cert = ISSLProvider::SharedInstance()->CreateCertificate(log, &certLen);
            if(cert && certLen) {
                this->socketContext->SetServerCertificate(cert, certLen);
                this->socketContext->SetClientCertificate(cert, certLen);
                ISSLProvider::SharedInstance()->ValidateCertificate(log, cert, certLen);
            }
            if(cert) free(cert);
        }
    }
    this->dns = NULL;
    this->minPort = minPort;
    this->maxPort = maxPort;
    this->stunSlow = stunSlow;
    this->turnOnly = turnOnly;
    this->iceNoHost = iceNoHost;
    this->rtpOverTcp = rtpOverTcp;
    this->dropMediaTx = dropMediaTx;
    this->dropMediaRx = dropMediaRx;
    this->iceDisabled = false;
    this->type = media;
    this->mediaConfig->type = media;
    this->noVpnAddresses = noVpnAddresses;
    this->currentUserSip = _strdup(currentUserSip);
    this->assignmentsList = assignmentsList;

    this->tlsSocketProvider = tlsSocketProvider;
    this->recordingUrl = _strdup(recordingUrl);
    this->recordOn = recordOn;
    this->recordingUsername = _strdup(recordingUsername);
    this->recordingPassword = _strdup(recordingPassword);
    ParseExcludeInterfaces(excludeInterfaces);
    this->recordRtpStream = recordRtpStream;
    if(guid) memcpy(&guidMedia,guid,sizeof(OS_GUID));

    if(hostPbx) {
        char * t = _strdup(hostPbx);
        char * p = t;
        char * pbxAddr;
        while(*p == ' ') p++;
        pbxAddr = p;
        if(*p == '[') {
            p = strchr(p + 1, ']');
        }
        if(p) {
            p = strchr(p, ':');
        }
        if(p) {
            *p++ = 0;
        }
        this->dns = IDns::Create(iomux);
        this->reqPBX = new class DnsRequest(pbxAddr, dns, log, 1000, 0, p ? atoi(p) : 0);
        this->reqPBX->Start(&taskDns);
        free(t);
    }
    else {
        interfaces = INetwork::GetLocalAddresses();
        if(interfaces->localAddresses.empty()) {
            user->MediaInitializeComplete(this, this->mediaConfig);
            this->mediaConfig = NULL;
        }
        else {
            class LocalAddr * inf;
            int totalNumInterfaces = 0;
            for (inf = interfaces->localAddresses.front(); inf; inf = inf->goNext()) {
                if (!(noVpnAddresses && inf->vpn) && inf->addr && inf->preferred
#ifdef _IOS_
                 && ((strcmp(inf->addr, "127.0.0.1") != 0) || (type != MediaType::AUDIO))
#endif
                 ) {
                    totalNumInterfaces += rtpOverTcp ? 2 : 1;
                }
            }
            int interfaceIndex = 1;
            for(inf = interfaces->localAddresses.front(); inf; inf = inf->goNext()) {
                if(!(noVpnAddresses && inf->vpn) && inf->addr && !ExcludeMediaInterface(inf->addr) && inf->preferred
#ifdef _IOS_
                 && ((strcmp(inf->addr, "127.0.0.1") != 0) || (type != MediaType::AUDIO))
#endif
                 ) {
                    class MediaChannel * ch = new class MediaChannel(udpSocketProvider, tcpSocketProvider, socketContext, minPort, maxPort, iomux, this, type, inf->addr, inf->iPv6, false, false, stunServers, turnServers, turnUsername, turnPassword, turnOnly, iceNoHost, stunSlow, dropMediaTx, dropMediaRx, mediaConfig, log, interfaceIndex, totalNumInterfaces, tlsSocketProvider, recordingUrl, &guidMedia, recordOn, recordRtpStream, currentUserSip, recordingUsername, recordingPassword);
                    channels.push_back(ch);
                    log->Log(LOG_MEDIA, "Media(%p,%p)::Interface(%d/%d) LocalAddr %s (primary=%d) Media %s ipv6 %d Channel %p", this, interfaces, interfaceIndex, totalNumInterfaces, inf->addr, false, mediaType(type), inf->iPv6, ch);
                    interfaceIndex++;
                    if(rtpOverTcp) {
                        ch = new class MediaChannel(udpSocketProvider, tcpSocketProvider, socketContext, minPort, maxPort, iomux, this, type, inf->addr, inf->iPv6, true, false, stunServers, turnServers, turnUsername, turnPassword, turnOnly, iceNoHost, stunSlow, dropMediaTx, dropMediaRx, mediaConfig, log, interfaceIndex, totalNumInterfaces, tlsSocketProvider, recordingUrl, &guidMedia, recordOn, recordRtpStream, currentUserSip, recordingUsername, recordingPassword);
                        channels.push_back(ch);
                        interfaceIndex++;
                    }
                }
            }
            if(channels.empty()) {
                user->MediaInitializeComplete(this, this->mediaConfig);
                this->mediaConfig = NULL;
            }
        }
    }
}

void Media::Connect(class MediaConfig *remoteMediaConfig, bool iceRemoteRole, enum SetupRole remoteDtlsRole)
{
    if(closing) return;
    if(this->remoteMediaConfig) delete this->remoteMediaConfig;
    this->remoteMediaConfig = remoteMediaConfig;

#if 0
    if(remoteMediaConfig) {
        log->Log(LOG_MEDIA, "Media::Connect type %s %s:%d", (remoteMediaConfig->type == MediaType::AUDIO ? "Audio" : remoteMediaConfig->type == MediaType::VIDEO ? "Video" : "AppSharing"),
                     (remoteMediaConfig->defAddr ? remoteMediaConfig->defAddr : ""), remoteMediaConfig->defPort);
        class Codec * c;
        for (c = remoteMediaConfig->codecList.front(); c; c = c->goNext()) {
            log->Log(LOG_MEDIA, "Media::Connect codec (%d) %s:%d pt=%d", c->coder, (c->addr ? c->addr : ""), c->port, c->pt);
        }
        if(remoteMediaConfig->ice) {
            log->Log(LOG_MEDIA, "Media::Connect RtcpMux(%d) ICE(%s:%s)", remoteMediaConfig->ice->rtcpMux,
                            ((remoteMediaConfig->ice && remoteMediaConfig->ice->iceUfrag) ? remoteMediaConfig->ice->iceUfrag : ""), ((remoteMediaConfig->ice && remoteMediaConfig->ice->icePwd) ? remoteMediaConfig->ice->icePwd : ""));
            class IceCandidate * c;
            for (c = remoteMediaConfig->ice->candidateList.front(); c; c = c->goNext()) {
                if(c->type == 0)
                    debug->printf("Media::Connect candidate(%d) %s:%d,%d (%s)", c->type, c->addr, c->rtpPort, c->rtcpPort, (c->foundation ? c->foundation : ""));
                else
                    debug->printf("Media::Connect candidate(%d) %s:%d,%d (%s:%d,%d) (%s)", c->type, c->addr, c->rtpPort, c->rtcpPort, c->relatedAddr, c->relatedRtpPort, c->relatedRtcpPort, (c->foundation ? c->foundation : ""));
            }
        }
    }
#endif
    log->Log(LOG_MEDIA, "Media(%p)::Connect type MediaType=%s iceRole=%u dtlsRole=%u", this, remoteMediaConfig ? remoteMediaConfig->type == 0 ? "AUDIO" : "VIDEO" : "UNKNOWN", iceRemoteRole, remoteDtlsRole);

    channelConnected = NULL;
    if(remoteMediaConfig && remoteMediaConfig->mcAddr && remoteMediaConfig->mcPort) {
        class MediaChannel * ch;
        for(ch = channels.front(); ch; ch = ch->goNext()) {
            if(ch->ipv6 == false) {
                ch->connect(remoteMediaConfig, iceRemoteRole, remoteDtlsRole);
                break;
            }
        }
        if(!ch) {
            ch = channels.front();
            ch->connect(remoteMediaConfig, iceRemoteRole, remoteDtlsRole);
        }
        this->multicastChannel = ch;
    }
    else {
        iceDtlsConnected = false;
        nominating = false;
        class MediaChannel * ch;
        for (ch = channels.front(); ch; ch = ch->goNext()) {
            ch->connect(remoteMediaConfig, iceRemoteRole, remoteDtlsRole);
        }
    }
}

void Media::RtpSend(const void * buf, size_t len, dword timestamp, enum MediaType mediaType, bool isConference)
{
    if(channelConnected) channelConnected->MediaSend(buf, len, timestamp, 0, mediaType, isConference);
}

void Media::RtpForward(const void * buf, size_t len, dword timestamp, short sequenceNumberDiff, bool marker)
{
    if(channelConnected) channelConnected->RtpSend((char *)buf, len, timestamp, sequenceNumberDiff, marker);
}

void Media::RtpDtmf(char digit, byte pt)
{
    if(channelConnected) channelConnected->RtpDtmf(digit, pt);
}

void Media::RecordingOn(bool on)
{
    if (channelConnected) channelConnected->RecordingOn(on);
}

void Media::RtcpSend(const void * buf, size_t len)
{
    if(channelConnected) channelConnected->RtcpSend((char *)buf, len);
}

void Media::SendFIR()
{
    if (channelConnected) channelConnected->RtcpSendFirRequest(false);
}

void Media::RtcpSendFirRequest(bool retransmission)
{
    if (channelConnected) channelConnected->RtcpSendFirRequest(retransmission);
}

void Media::SctpSend(const void * buf, size_t len, unsigned num)
{
    if(channelConnected) channelConnected->MediaSend(buf, len, 0, num, MediaType::APPSHARING, false);
}

void Media::Recv(void * buf, size_t len, bool recvPartial)
{
}

void Media::CleanSrtpKeys()
{
    class MediaChannel* ch;
    for (ch = channels.front(); ch; ch = ch->goNext()) {
        ch->CleanSrtpKeys();
    }
}

void Media::RewriteIceCredentials(const char* usr, const char* pwd)
{
    class MediaChannel* ch;
    for (ch = channels.front(); ch; ch = ch->goNext()) {
        ch->RewriteIceCredentials(usr, pwd);
    }
}

char * Media::GenerateRandomString(int len)
{
    char *result = (char *)malloc((len+1)*sizeof(char));
    if(result) {
        for(int i=0; i<len; i++) {
            int u = (IRandom::GetRandom() % 64);
            result[i] = charpool[u];
        }
        result[len] = 0x0;
    }
    return result;
}

bool Media::addIceCandidate(class IceCandidate * cand)
{
    log->Log(LOG_MEDIA, "Media(%p,%p)::addIceCandidate type %d addr %s:%d,%d related (%s:%d,%d)", this, mediaConfig, cand->type,
                 cand->addr, cand->rtpPort, cand->rtcpPort,
                 cand->relatedAddr ? cand->relatedAddr : "", cand->relatedAddr ? cand->relatedRtpPort : 0, cand->relatedAddr ? cand->relatedRtcpPort : 0);
    if(!mediaConfig) return false;

    if(cand->type == CHANNEL_CANDIDATE_RELAY) {
        unsigned int i;
        for(i=0; i<CHANNEL_MAX_CANDIDATE; i++) {
            if(!relayedAddrCand[i]) break;
            else if(!strcmp(relayedAddrCand[i], cand->addr)) return false;
        }
        if(i == CHANNEL_MAX_CANDIDATE) return false;
        relayedAddrCand[i] = _strdup(cand->addr);
    }
    else if(turnOnly == true) {
        return false;
    }
    else if(cand->type == CHANNEL_CANDIDATE_SRFLX) {
        // Using a local TURN/STUN Server delivers the local IP as SRFLX address
        if(cand->addr && cand->relatedAddr && !strcmp(cand->addr, cand->relatedAddr)) return false;
        unsigned int i;
        // Repeated SRFLX Candidate
        for(i=0; i<CHANNEL_MAX_CANDIDATE; i++) {
            if(!srflxAddrCand[i]) break;
            else if(!strcmp(srflxAddrCand[i], cand->addr)) return false;
        }
        // Repeated Relay Candidate
        for(i = 0; i<CHANNEL_MAX_CANDIDATE; i++) {
            if(!relayedAddrCand[i]) break;
            else if(!strcmp(relayedAddrCand[i], cand->addr)) return false;
        }
        if(i == CHANNEL_MAX_CANDIDATE) return false;
        srflxAddrCand[i] = _strdup(cand->addr);
    }
    else if(iceNoHost == true) {
        return false;
    }
    if(cand->foundation) free(cand->foundation);
    cand->foundation = (char *)malloc(32+1);
    _sprintf(cand->foundation, "%d", foundation++);
    mediaConfig->ice->count++;
    mediaConfig->ice->candidateList.push_back(cand);
    return true;
}

static const char * strError[] = { nullptr, "IceFailed", "DtlsFailed", "SctpFailed", "NoCoder", "UnknownCoder", "NoRemoteConfig", "MessageError", "NotReachable" };

void Media::connectResult(class MediaChannel * ch, enum Error err, const class IceTurnError * iceTurnError)
{
    if (iceTurnError) {
        if (ch->ipv6) {
            if (!this->iceTurnError6 || ((iceTurnError->code == 0) && ((this->iceTurnError6)->code != 0))) {
                if (this->iceTurnError6) delete this->iceTurnError6;
                this->iceTurnError6 = new class IceTurnError(iceTurnError->code, iceTurnError->errorText, iceTurnError->src, iceTurnError->hostname, iceTurnError->addr, iceTurnError->port);
            }
        }
        else {
            if (!this->iceTurnError4 || ((iceTurnError->code == 0) && ((this->iceTurnError4)->code != 0))) {
                if (this->iceTurnError4) delete this->iceTurnError4;
                this->iceTurnError4 = new class IceTurnError(iceTurnError->code, iceTurnError->errorText, iceTurnError->src, iceTurnError->hostname, iceTurnError->addr, iceTurnError->port);
            }
        }
    }
    if(remoteMediaConfig && remoteMediaConfig->mcAddr && remoteMediaConfig->mcPort) {
        if(!err) channelConnected = ch;
        user->MediaConnectResult(this, strError[err]);
    }
    else {
        if(!err) {
            if(iceDtlsConnected == false) {
                channelConnected = ch;
                user->MediaConnectResult(this, strError[err]);
                iceDtlsConnected = true;
            }
            class MediaChannel * tmpCh;
            for (tmpCh = channels.front(); tmpCh; tmpCh = tmpCh->goNext()) {
                if (tmpCh == channelConnected) continue;
                tmpCh->disconnectIcePath(IceAbortReason::ICE_PATH_FOUND);
            }
        }
        else {
            class MediaChannel * ch;
            for (ch = channels.front(); ch; ch = ch->goNext()) {
                if (ch->IceDtlsConnectDone() == false) return;
            }
            if (iceDtlsConnected == false) {
                if (err == Media::IceFailed) {
                    EventReceived(CHANNEL_MEDIA_ERROR, new class EventMediaError(
                        (this->iceTurnError4 && (this->iceTurnError4->code != 0)) ? this->iceTurnError4->code : ((this->iceTurnError6 && (this->iceTurnError6->code != 0)) ? this->iceTurnError6->code : FAULT_CODE_RTP_ICE_FAILED),
                        (this->iceTurnError4 && (this->iceTurnError4->code != 0)) ? this->iceTurnError4->errorText : ((this->iceTurnError6 && (this->iceTurnError6->code != 0)) ? this->iceTurnError6->errorText : "ICE: No network connection between endpoints"),
                        (this->iceTurnError4 && (this->iceTurnError4->code != 0)) ? this->iceTurnError4->src : ((this->iceTurnError6 && (this->iceTurnError6->code != 0)) ? this->iceTurnError6->src : "RTP")));
                }
                if (this->iceTurnError4 && (this->iceTurnError4->code != 0) && this->iceTurnError4->hostname && this->iceTurnError4->addr && this->iceTurnError4->port) {
                    char * p = 0;
                    str::to_ip(this->iceTurnError4->hostname, &p, /*prefix*/0);
                    if (p == this->iceTurnError4->hostname) {
                        dns->UnsuitableAddress(this->iceTurnError4->hostname, log, this->iceTurnError4->addr, this->iceTurnError4->port);
                    }
                }
                if (this->iceTurnError6 && (this->iceTurnError6->code != 0) && this->iceTurnError6->hostname && this->iceTurnError6->addr && this->iceTurnError6->port) {
                    char * p = 0;
                    str::to_ip(this->iceTurnError6->hostname, &p, /*prefix*/0);
                    if (p == this->iceTurnError6->hostname) {
                        dns->UnsuitableAddress(this->iceTurnError6->hostname, log, this->iceTurnError6->addr, this->iceTurnError6->port);
                    }
                }
                user->MediaConnectResult(this, strError[err]);
            }
        }
    }
}

unsigned Media::AvailableCoderCount()
{
    switch(type) {
        case MediaType::AUDIO:      return user->AvailableAudioCoderCount();
        case MediaType::VIDEO:      return user->AvailableVideoCoderCount();
        case MediaType::VIDEO_APPSHARING: return user->AvailableVideoCoderCount();
        case MediaType::APPSHARING: return user->AvailableAppSharingCoderCount();
        case MediaType::APPSHARING_CONTAINER: return 0;
        case MediaType::MP4_CONTAINER: return 0;
        case MediaType::WEBM_VP8_CONTAINER: return 0;
        case MediaType::WEBM_VP9_CONTAINER: return 0;
    }
    return 0;
}

int Media::AvailableCoder(unsigned coderNumber)
{
    switch(type) {
        case MediaType::AUDIO:      return user->AvailableAudioCoder(coderNumber);
        case MediaType::VIDEO:      return user->AvailableVideoCoder(coderNumber);
        case MediaType::VIDEO_APPSHARING: return user->AvailableVideoCoder(coderNumber);
        case MediaType::APPSHARING: return user->AvailableAppSharingCoder(coderNumber);
        case MediaType::APPSHARING_CONTAINER: return 0;
        case MediaType::MP4_CONTAINER: return 0;
        case MediaType::WEBM_VP8_CONTAINER: return 0;
        case MediaType::WEBM_VP9_CONTAINER: return 0;
    }
    return 0;
}

int Media::GetCodecPayloadType(int coder)
{
    if(assignmentsList && !assignmentsList->empty()) {
        class CodecAssignments * assignment = assignmentsList->front();
        while(assignment) {
            if (coder == assignment->coder) return assignment->pt;
            assignment = assignment->goNext();
        }
    }
    return 0;
}

void Media::CreateMediaCodecs()
{
    unsigned i;
    class MediaChannel * ch;
    for (ch = channels.front(); ch; ch = ch->goNext()) {
        if(ch->primary) break;
    }
    if(!ch) {
        for(ch = channels.front(); ch; ch = ch->goNext()) {
            if(!ch->ipv6 && ch->iceLocalAddr && ch->iceLocalPort) {
                break;
            }
        }
    }
    if(!ch) {
         ch = channels.front();
    }
    if(ch == NULL) return;
    mediaConfig->defAddr = _strdup(ch->iceLocalAddr);
    mediaConfig->defPort = ch->iceLocalPort;

    log->Log(LOG_MEDIA, "Media(%p)::CreateMediaCodecs %s:%d", this, ch->iceLocalAddr, ch->iceLocalPort);

    if (mediaConfig->type == MediaType::AUDIO && recordRtpStream) {
        class Codec* c = new class Codec();
        c->coder = AvailableCoder(3);
        c->number = 3;
        c->xmitPacket = 0;
        c->recvPacket = (mediaConfig->type == MediaType::APPSHARING) ? 1024 : 0;  // (max-packet-size);
        c->rate = 0;
        c->addr = _strdup(ch->iceLocalAddr);
        c->port = ch->iceLocalPort;
        c->mcAddr = 0;
        c->mcPort = 0;
        c->flags = 0;
        c->pt = GetCodecPayloadType(c->coder);

        mediaConfig->codecList.push_back(c);
    }
    else {
        for (i = 0; i < AvailableCoderCount(); i++) {
            class Codec* c = new class Codec();
            c->coder = AvailableCoder(i);
            c->number = i;
            c->xmitPacket = 0;
            c->recvPacket = (mediaConfig->type == MediaType::APPSHARING) ? 1024 : 0;  // (max-packet-size);
            c->rate = 0;
            c->addr = _strdup(ch->iceLocalAddr);
            c->port = ch->iceLocalPort;
            c->mcAddr = 0;
            c->mcPort = 0;
            c->flags = 0;
            c->pt = GetCodecPayloadType(c->coder);

            mediaConfig->codecList.push_back(c);
        }
    }
}

void Media::tryMediaInitialized(bool stun, bool turn)
{
    class MediaChannel * ch;
    bool allIceCompleted = (channels.front() ? true : false);

    for (ch = channels.front(); ch; ch = ch->goNext()) {
        if(!ch->iceComplete) {
            allIceCompleted = false;
            log->Log(LOG_MEDIA, "Media(%p):tryMediaInitialized %x,%x,%x,%x", this, !stunServers, (stunServers && stun), !turnServers, (turnServers && turn));
            if((!stunServers || (stunServers && stun)) && (!turnServers || (turnServers && turn))) {
                log->Log(LOG_MEDIA, "Media(%p,%d,%d):tryMediaInitialized channel %p, %s still not finished", this, stun, turn, ch, ch->iceLocalAddr);
                ch->stopStunTurnDiscovery();
            }
        }
    }
    if(allIceCompleted && mediaConfig) {
        // TURN Failed, no TURN candidate available
        if (turnServers && !relayedAddrCand[0]) {
            useAlternateTurnAddr = tryAlternateTurnAddr;
        }
        CreateMediaCodecs();
        user->MediaInitializeComplete(this, mediaConfig);
        mediaConfig = NULL;
    }
}

void Media::channelDisconnected(class MediaChannel * ch)
{
    log->Log(LOG_MEDIA, "Media(%p):channelDisconnected %p == %p", this, ch, channelConnected);

    class MediaChannel * tmp;
    for (tmp = channels.front(); tmp; tmp = tmp->goNext()) {
        if(tmp == ch) {
            ch->remove();
            delete ch;
            break;
        }
    }
    if(ch == channelConnected) channelConnected = NULL;
    if(closing && !reqPBX && !sockPBX && channels.empty()) DeleteAll();
}

static int cntTx = 0;
static int cntRx = 0;

MediaChannel::MediaChannel(ISocketProvider * udpSocketProvider, ISocketProvider * tcpSocketProvider, class ISocketContext * socketContext, word minPort, word maxPort, class IIoMux * iomux, class Media * media, enum MediaType type, const char * addr, bool ipv6, bool rtpOverTcp, bool primary, const char * stunServers, const char * turnServers, const char * turnUsername, const char * turnPassword, bool turnOnly, bool iceNoHost, bool stunSlow, int dropMediaTx, int dropMediaRx, class MediaConfig * localCfg, class IInstanceLog * const log, int interfaceIndex, int totalNumInterfaces, class ISocketProvider * tlsSocketProvider, const char * recordingUrl, OS_GUID * guid, bool recordOn, bool recordRtpStream, const char * currentUserSip, const char * recordingUsername, const char * recordingPassword) :
    Ice(iomux, log, interfaceIndex, totalNumInterfaces),
    SctpRtc(this, log, dropMediaTx, dropMediaRx),
    log(log),
    record(iomux, tcpSocketProvider, tlsSocketProvider, str::to_ip(addr), recordingUrl, guid, type, log, recordOn, currentUserSip, recordingUsername, recordingPassword),
    turnRtp(iomux, log, this, &turnRtp, false), turnRtcp(iomux, log, this, &turnRtcp, true),
    taskDns(this, &MediaChannel::DnsRequestComplete, 0, 0),
    taskCloseSocket(this, &MediaChannel::CloseSocketComplete, 0, 0)
{
    this->udpSocketProvider = udpSocketProvider;
    this->tcpSocketProvider = tcpSocketProvider;
    this->socketContext = socketContext;
    this->sctpHandshakeTimer = new ITimer(iomux, this);
    this->sctpRetransmitTimer = new ITimer(iomux, this);
    this->iceTimer = new ITimer(iomux, this);
    this->dtlsRtpTimer = new ITimer(iomux, this);
    this->dtlsRtcpTimer = new ITimer(iomux, this);
    this->dtlsRtcp = NULL;
    this->rtcpTimer = new ITimer(iomux, this);
    this->dtmfTimer = new ITimer(iomux, this);
    this->errorTimer = new ITimer(iomux, this);
    this->turnRtpTimer = new ITimer(iomux, this);
    this->turnRtcpTimer = new ITimer(iomux, this);
    this->rtxRtcpTimer = new ITimer(iomux, this);
    this->resendSequence = 0;
    this->resendSequenceNumberDiff = 0;
    this->resendFirCounter = 0;
    this->resendNackCounter = 0;
    this->firCommandSequence = 0;
    this->resendFirCommandSequenceSet = false;
    this->data = (byte *)malloc(8192);
    this->rtcpData = NULL;
    this->tcpData = NULL;
    this->indexTcpBuffer = 0;
    this->rtcpTcpData = NULL;
    this->indexTcpBufferRtcp = 0;
    this->proxies = NULL;
    this->proxy = NULL;
    this->tunnelTargetHost = NULL;
    this->tunnelTargetPort = 0;
    this->connectTunnel = false;
    this->connectTunnelRtcp = false;
    this->disconnecting = false;
    this->iceTurnError = NULL;
    this->iceDtlsDone = false;
    this->dns = IDns::Create(iomux);
    this->media = media;
    this->iomux = iomux;
    this->rxPayload = (type == MediaType::VIDEO ? DEFAULT_PAYLOAD_TYPE_H264 : type == MediaType::AUDIO ? DEFAULT_PAYLOAD_TYPE_G711_A : DEFAULT_SCTP_CHANNEL_NUMBER);
    this->txPayload = (type == MediaType::VIDEO ? DEFAULT_PAYLOAD_TYPE_H264 : type == MediaType::AUDIO ? DEFAULT_PAYLOAD_TYPE_G711_A : DEFAULT_SCTP_CHANNEL_NUMBER);
    this->endpoint = NULL;
    this->ipv6 = ipv6;
    this->iceLocalAddr = _strdup(addr);
    this->iceLocalPort = 0;
    this->iceLocalRtcpPort = 0;
    this->startPort = minPort;
    this->maxPort = maxPort;
    this->rtpBindFailed = false;
    this->rtcpBindFailed = false;
    this->mcSocket = NULL;
    this->socket = NULL;
    this->rtcpSocket = NULL;
    this->tcpSocket = NULL;
    this->tcpAltSocket = NULL;
    this->rtcpTcpSocket = NULL;
    this->rtpTcpListen = NULL;
    this->rtpTcpSocket = NULL;
    this->iceSlow = stunSlow;
    this->iceStun1Addr = NULL;
    this->iceStun1Port = 0;
    this->stun1AddrStorage = ip_anyaddr;
    this->iceStun2Addr = NULL;
    this->iceStun2Port = 0;
    this->stun2AddrStorage = ip_anyaddr;
    this->iceTurnHostname = NULL;
    this->iceTurnAddr = NULL;
    this->iceTurnPort = 0;
    this->iceTurnTcp = false;
    this->iceTurnAltHostname = NULL;
    this->iceTurnAltAddr = NULL;
    this->iceTurnAltPort = 0;
    this->iceTurnAltTcp = false;
    this->turnOnly = turnOnly;
#if (defined(__ANDROID__) || defined(_IOS_))
    this->iceNoHost = iceNoHost || (strncmp(addr, "192.0.0.", sizeof("192.0.0.") - 1) == 0); /* DS-Lite interface doesn't make sense as host candidate */
#else
    this->iceNoHost = iceNoHost;
#endif
    this->rtpOverTcp = rtpOverTcp;
    this->localCfg = localCfg;
    this->initComplete = false;
    this->iceComplete = false;
    this->waitBindRtp = true;
    this->waitBindRtcp = false;
    this->reqStun1 = NULL;
    this->reqStun2 = NULL;
    this->reqStun3 = NULL;
    this->reqStun4 = NULL;
    this->reqTurn1 = NULL;
    this->reqTurn2 = NULL;
    if(stunServers) this->stunServers = _strdup(stunServers);
    else this->stunServers = NULL;
    if(turnServers) this->turnServers = _strdup(turnServers);
    else this->turnServers = NULL;
    if(turnUsername) this->turnUsr = _strdup(turnUsername);
    else this->turnUsr = NULL;
    if(turnPassword) this->turnPwd = _strdup(turnPassword);
    else this->turnPwd = NULL;
    this->mediaType = type;
    this->rtcpMux = false;
    this->srtpSend = NULL;
    this->srtpRecv = NULL;
    this->srtcpSend = NULL;
    this->srtcpRecv = NULL;
    this->iceControlling = false;
    this->rtpRecvUpdate = false;
    this->closeRtp = NULL;
    this->closeRtcp = NULL;
    this->closeMc = NULL;
    this->dtmfXmitPt = 0;
    this->dtmfRecvPt = 0;
    this->redRecvPt = 0;
    this->dropMediaTx = dropMediaTx;
    this->numAllowPacketsTx = dropMediaTx ? (100 / dropMediaTx) : 0;
    this->dropSeveralTx = 0;
    this->primary = primary;
    this->dropMediaRx = dropMediaRx;
    this->numAllowPacketsRx = dropMediaRx ? (100 / dropMediaRx) : 0;
    this->dropSeveralRx = 0;
    this->lastRoundTripT38Pages = 0xffffffff;
    this->lastJitterT38Speed = 0xffffffff;
    this->connectedTs = 0;
    this->dtlsDisabled = false;
    this->dropOffset = 0;
    this->mediaErrorMessage = Media::Success;
    this->execCnt = 0;
    this->rtpStream = NULL;
    this->rtpStreamRx = NULL;
    this->recordRtpStream = recordRtpStream;
    this->recordRtpStreamTrace = false;
    this->recordRtpStreamRxTrace = false;

    startTimestamp = getTimestamp8kHz();
    oldMediaAddr = ip_anyaddr;
    oldMediaPort = 0;
    oldMediaRtcpPort = 0;
    mediaAddr = ip_anyaddr;
    mediaPort = 0;
    mediaRtcpPort = 0;
    noRtcp = (MediaType::APPSHARING == type) || rtpOverTcp;
    mute = true;
    sendonly = false;
    errorCount = 0;
    errorReported = false;
    ssrc = IRandom::GetRandom();            // 0xaabbccdd;
    xSeq = ((word)IRandom::GetRandom())/2;  // 1000;
    xPackets = 0;
    xOctets = 0;
    mediaTurn = false;
    rAuthFailed = 0;
    resetRtcp();
    rTotal = 0;
    rLost = 0;
    rWrongPt = 0;
    rAuthFailed = 0;
    remoteSsrc = 0;
    oldRemoteSsrc = 0;
    for(int i=0; i<4; i++) rDuplicate[i] = 0xffff;
    timestampRateMultiplier = 1;
    redirectCounter = 1;
    xDtmfTimestamp = 0;
    xDtmfDuration = 0;
    rDtmfTimestamp = 0xffffffff;
    rDtmfEnd = false;
    dtmfXmitCount = 0;
    dtmfBufLen = 0;

    xmitTimestampDelta = 0;
    xmitTimestampDeltaSet = false;

    if(this->rtpOverTcp) {
        log->Log(LOG_MEDIA, "MediaChannel(%p)::MediaChannel %s:%d (%s)", this, iceLocalAddr, startPort, "RTP over TCP");
        this->dtlsRtp = IDtls::Create(iomux, this, (void*)DTLS_RTP_CONTEXT, log);
        rtpTcpListen = tcpSocketProvider->CreateSocket(iomux, this, log, ipv6);
        rtpTcpListen->Bind(iceLocalAddr, 0);
    }
    else {
        // Before creating the sockets, dnsPending
#ifdef _IOS_
        if (strcmp(iceLocalAddr, "127.0.0.1") != 0)
#endif
        {
            if (!ipv6) GetStunServers(stunServers);
            GetTurnServers(turnServers);
        }
        log->Log(LOG_MEDIA, "MediaChannel(%p)::MediaChannel %s:%d (%s)", this, iceLocalAddr, startPort, iceTurnTcp ? "tcp" : "udp");

        socket = udpSocketProvider->CreateSocket(iomux, this, log, ipv6);
        this->dtlsRtp = IDtls::Create(iomux, this, (void*)DTLS_RTP_CONTEXT, log);
        socket->Bind(iceLocalAddr, startPort);
        if(this->mediaType == MediaType::VIDEO || this->mediaType == MediaType::AUDIO) {
            this->rtcpData = (byte *)malloc(8192);
            this->waitBindRtcp = true;
            rtcpSocket = udpSocketProvider->CreateSocket(iomux, this, log, ipv6);
            this->dtlsRtcp = IDtls::Create(iomux, this, (void*)DTLS_RTCP_CONTEXT, log);
        }
        if(this->iceTurnTcp || this->iceTurnAltTcp) {
            this->tcpData = (byte *)malloc(8192);
            tcpSocket = tcpSocketProvider->CreateSocket(iomux, this, log, ipv6);
            tcpAltSocket = tcpSocketProvider->CreateSocket(iomux, this, log, ipv6);
            if(this->mediaType == MediaType::VIDEO || this->mediaType == MediaType::AUDIO) {
                this->rtcpTcpData = (byte *)malloc(8192);
                rtcpTcpSocket = tcpSocketProvider->CreateSocket(iomux, this, log, ipv6);
            }
        }
    }
}

MediaChannel::~MediaChannel()
{
    log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p)::~MediaChannel remote %p,%p (%p,%p,%p)", this, &remoteIce, remoteIce, this->closeRtp, this->closeRtcp, this->closeMc);

    if (rtpStream) {
        rtpStream->Close();
        delete rtpStream;
    }
    if (rtpStreamRx) {
        rtpStreamRx->Close();
        delete rtpStreamRx;
    }
    iceAbortConnect(IceAbortReason::CALL_TERMINATED);
    if(!turnRtp.isIdle() || !turnRtcp.isIdle()) {
        if(!iceTurnTcp && socket) turnRtp.releaseAllocation();
        else if(iceTurnTcp && tcpSocket) turnRtp.releaseAllocation();
        if(!iceTurnTcp && rtcpSocket) turnRtcp.releaseAllocation();
        else if(iceTurnTcp && rtcpTcpSocket) turnRtcp.releaseAllocation();
    }
    if(dtlsRtp) delete dtlsRtp;
    if(dtlsRtcp) delete dtlsRtcp;
    if(turnRtpTimer) delete turnRtpTimer;
    if(turnRtcpTimer) delete turnRtcpTimer;
    if(data) free(data);
    if(rtcpData) free(rtcpData);
    if(tcpData) free(tcpData);
    if(rtcpTcpData) free(rtcpTcpData);
    if(proxies) delete proxies;
    if(tunnelTargetHost) free(tunnelTargetHost);
    if(iceTurnError) delete iceTurnError;
    if(iceLocalAddr) free(iceLocalAddr);
    if(iceStun1Addr) free(iceStun1Addr);
    if(iceStun2Addr) free(iceStun2Addr);
    if(iceTurnHostname) free(iceTurnHostname);
    if(iceTurnAddr) free(iceTurnAddr);
    if(iceTurnAltHostname) free(iceTurnAltHostname);
    if(iceTurnAltAddr) free(iceTurnAltAddr);
    if(stunServers) free(stunServers);
    if(turnServers) free(turnServers);
    if(this->turnUsr) free(this->turnUsr);
    if(this->turnPwd) free(this->turnPwd);
    if(endpoint) delete endpoint;
    if(srtpSend) delete srtpSend;
    if(srtpRecv) delete srtpRecv;
    if(srtcpSend) delete srtcpSend;
    if(srtcpRecv) delete srtcpRecv;
    if(mcSocket) delete mcSocket;
    if(socket) delete socket;
    if(rtcpSocket) delete rtcpSocket;
    if(tcpSocket) delete tcpSocket;
    if(tcpAltSocket) delete tcpAltSocket;
    if(rtcpTcpSocket) delete rtcpTcpSocket;
    if(rtpTcpListen) delete rtpTcpListen;
    if(rtpTcpSocket) delete rtpTcpSocket;
    delete rtxRtcpTimer;
    delete sctpHandshakeTimer;
    delete sctpRetransmitTimer;
    delete dtlsRtpTimer;
    delete dtlsRtcpTimer;
    delete iceTimer;
    delete rtcpTimer;
    delete dtmfTimer;
    delete errorTimer;
    delete dns;
};

void MediaChannel::IoExec(void * execContext)
{
    execCnt--;
    if(!disconnecting) {
        iceDtlsDone = true;
        media->connectResult(this, (Media::Error)mediaErrorMessage, iceTurnError);
    }
    else if (!this->mcSocket && !this->socket && !this->rtcpSocket && !this->tcpSocket && !this->tcpAltSocket && !this->rtcpTcpSocket && !this->rtpTcpListen && !this->rtpTcpSocket && !this->closeRtp && !this->closeRtcp && !this->closeMc && !this->reqStun1 && !this->reqStun2 && !this->reqStun3 && !this->reqStun4 && !this->reqTurn1 && !this->reqTurn2 && !record.On() && !execCnt) media->channelDisconnected(this);
}

void MediaChannel::TimerOnTimeout(ITimer * timer)
{
    if(timer == iceTimer) {
        iceTimeout();
    }
    else if(timer == sctpHandshakeTimer) {
        SctpTimeout(0);
    }
    else if(timer == sctpRetransmitTimer) {
        SctpTimeout(1);
    }
    else if(timer == dtlsRtpTimer) {
        dtlsRtp->DtlsTimeout();
    }
    else if(timer == dtlsRtcpTimer) {
        dtlsRtcp->DtlsTimeout();
    }
    else if (timer == turnRtpTimer) {
        turnRtp.timeout();
    }
    else if (timer == turnRtcpTimer) {
        turnRtcp.timeout();
    }
    else if (timer == rtxRtcpTimer) {
        bool startTimer = false;
        if (resendSequenceNumberDiff) {
            SendRtcpNack(resendSequence, resendSequenceNumberDiff, true);
            if (resendNackCounter++ > 3) {
                resendSequence = resendSequenceNumberDiff = 0;
            }
            else startTimer = true;
        }
        if (resendFirCommandSequenceSet) {
            RtcpSendFirRequest(true);
            if (resendFirCounter++ > 3) {
                resendFirCommandSequenceSet = false;
            }
            else startTimer = true;
        }
        if(startTimer) rtxRtcpTimer->Start(100);
    }
    else if(timer == rtcpTimer) {
        rtcpTimer->Start(RTCP_PERIOD);
        class ISocket * rtcpSendSocket = rtcpMux ? (rtpOverTcp ? rtpTcpSocket : socket) : rtcpSocket;
        word mediaRtcpPort = rtcpMux ? this->mediaPort : this->mediaRtcpPort;
        class Turn * turnSendSocket = rtcpMux ? &turnRtp : &turnRtcp;
        class ISrtpSession* srtpTx = rtcpMux ? srtpSend : srtcpSend;

        if(rtcpSendSocket && mediaRtcpPort > 1) {
            dword dlsr = 0;
            dword loss = 0;
            word p;
            dword timestamp = getTimestamp8kHz();

            dword ntp = timestamp2ntp32(timestamp-startTimestamp);
            if(lsrTimestamp) {
                dlsr = timestamp2ntp32(timestamp-lsrTimestamp);
            }
            timestamp = timestamp * timestampRateMultiplier;
            if(rLost) {
                loss = ((rLost<<8)/rTotal)<<24;
                loss |= rLost &0xffffff;
            }

            unsigned char * buf = (unsigned char *)malloc(512);

            buf[0] = 0x81;  // V=2, RC=1
            buf[1] = 0xc8;  // SR
            buf[2] = 0x00;  // length is 12 dwords
            buf[3] = 0x0c;

            WRITE_DWORD_NET(&buf[4],ssrc);                 // our ssrc
            WRITE_DWORD_NET(&buf[8],ntp>>16);              // NTP high
            WRITE_DWORD_NET(&buf[12],ntp<<16);             // NTP low
            WRITE_DWORD_NET(&buf[16],timestamp);           // RTP timestamp
            WRITE_DWORD_NET(&buf[20],xPackets);            // sender packet count
            WRITE_DWORD_NET(&buf[24],xOctets);             // sender packet octet count

            WRITE_DWORD_NET(&buf[28],remoteSsrc);          // remote ssrc
            WRITE_DWORD_NET(&buf[32],loss);                // packet loss
            WRITE_DWORD_NET(&buf[36],rSeq);                // ext. seq number received
            WRITE_DWORD_NET(&buf[40],rJitter);             // jitter
            WRITE_DWORD_NET(&buf[44],lsr);                 // last SR
            WRITE_DWORD_NET(&buf[48],dlsr);                // delay since last SR

            buf[52] = 0x81; // V=2, RC=1
            buf[53] = 0xca; // SDE
            buf[54] = 0x00; // length high byte
            buf[55] = 0x00;
            WRITE_DWORD_NET(&buf[56],ssrc); // our ssrc

            p = 60;
            buf[p] = 0x01;
            buf[p+1] = _sprintf((char *)&buf[p+2],"client@%s", iceLocalAddr ? iceLocalAddr : "");
            p +=buf[p+1]+2;

            buf[p] = 0x02;
            buf[p+1] = _sprintf((char *)&buf[p+2],"client");
            p +=buf[p+1]+2;

            buf[p] = 0x06;
            buf[p+1] = _sprintf((char *)&buf[p+2],"innovaphone");
            p +=buf[p+1]+2;

            buf[p] = 0;  // add padding
            buf[p+1] = 0;
            buf[p+2] = 0;

            buf[55] = (p+3+4)/4;  // update length

            int msgLen = 52+(buf[55]+1)*4;

            if (!srtpTx) {
                if(!mediaTurn) {
                    if(rtpOverTcp) {
                        byte l[2];
                        WRITE_WORD_NET(l, msgLen);
                        rtcpSendSocket->Send(l, sizeof(l));
                        rtcpSendSocket->Send((char *)buf, msgLen);
                    }
                    else rtcpSendSocket->SendTo((char *)buf, msgLen, mediaAddr, mediaRtcpPort);
                }
                else turnSendSocket->stunSend((char *)buf, msgLen, mediaAddr, mediaRtcpPort);
            }
            else {
                int outLen;
                byte * out;
                if ((out = srtpTx->SrtcpProtect(buf, msgLen, &outLen)) != NULL) {
                    if(!mediaTurn) {
                        if(rtpOverTcp) {
                            byte l[2];
                            WRITE_WORD_NET(l, outLen);
                            rtcpSendSocket->Send(l, sizeof(l));
                            rtcpSendSocket->Send((char *)out, outLen);
                        }
                        else rtcpSendSocket->SendTo((char *)out, outLen, mediaAddr, mediaRtcpPort);
                    }
                    else turnSendSocket->stunSend((char *)out, outLen, mediaAddr, mediaRtcpPort);
                    free(out);
                }
                else {
                    log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p)::TimerOnTimeout SRTCP Encryption failed", this);
                }
            }
            free(buf);

            xmitTimestampDeltaSet = false;

            if(/*roundTripDelta > 32 || jitterDelta > 4 || */ lastRecvLoss != loss) {
                lastRecvLoss = loss;
                media->EventReceived(CHANNEL_MEDIA_STATISTICS, new class EventMediaStatistics(false, loss & 0x00ffffff, rJitter, 0));
            }
        }
    }
    else if(timer == dtmfTimer) {
        if(dtmfXmitCount<3) {
            XmitRtpDtmf(dtmfBuf[0],960,false,false);
            dtmfXmitCount++;
        }
        else if(dtmfXmitCount<6) {
            XmitRtpDtmf(dtmfBuf[0],960+80,false,true);
            dtmfXmitCount++;
        }
        else {
            dtmfBufLen--;
            if(dtmfBufLen) {
                for(byte i=0; i<dtmfBufLen; i++) dtmfBuf[i] = dtmfBuf[i+1];
                XmitRtpDtmf(dtmfBuf[0],960,true,false);
                dtmfXmitCount=1;
            }
            else {
                dtmfXmitCount=0;
            }
        }
        if(dtmfXmitCount) {
            // we indicated a DTMF tone duration of 960 (120ms)
            // make first end event comes 160ms after first start event
            // [S1] 20ms [S2] 20ms [S3] 40ms [E1] 20ms [E2] 20ms [E3]
            word delay = (dtmfXmitCount==3) ? 40 : 20;
            dtmfTimer->Start(delay);
        }
    }
    else if(timer == errorTimer) {
        // If more than ~3% errors, then report
        if(rTotal<errorCount*30) {
            if(!errorReported) {
                char message[48];
                _sprintf(message, "Excessive loss of Data (%u%%/10s)", (int)((errorCount * 100) / MAX(rTotal,errorCount)));

                errorReported = true;
                log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p)::TimerOnTimeout msg=(%s)", this, message);
                media->EventReceived(CHANNEL_MEDIA_ERROR, new class EventMediaError(FAULT_CODE_RTP_EXCESSIVE_LOSS_OF_DATA, (const char *)message, "RTP"));
            }
        }
        else {
            errorCount = 0;
        }
    }
}

#ifdef SRTCP_TEST_MSG
void MediaChannel::srtcpTestMsg(void)
{
    class ISocket * rtcpSendSocket = rtcpMux ? (rtpOverTcp ? rtpTcpSocket : socket) : rtcpSocket;
    struct sockaddr_storage * mediaAddr = rtcpMux ? &this->mediaAddr : &this->mediaRtcpAddr;
    class Turn * turnSendSocket = rtcpMux ? &turnRtp : &turnRtcp;

    word mediaRtcpPort = rtcpMux ? this->mediaPort : this->mediaRtcpPort;
    if (rtcpSendSocket && mediaRtcpPort > 1) {
        unsigned char * buf = (unsigned char *)malloc(512);
        dword dlsr = 0;
        dword loss = 0;
        word p;
        dword timestamp = getTimestamp8kHz();

        dword ntp = timestamp2ntp32(timestamp-startTimestamp);
        if(lsrTimestamp) {
            dlsr = timestamp2ntp32(timestamp-lsrTimestamp);
        }
        timestamp = timestamp * timestampRateMultiplier;
        if(rLost) {
            loss = ((rLost<<8)/rTotal)<<24;
            loss |= rLost &0xffffff;
        }

        buf[0] = 0x81;  // V=2, RC=1
        buf[1] = 0xc8;  // SR
        buf[2] = 0x00;  // length is 12 dwords
        buf[3] = 0x0c;

        WRITE_DWORD_NET(&buf[4],ssrc);                 // our ssrc
        WRITE_DWORD_NET(&buf[8],ntp>>16);              // NTP high
        WRITE_DWORD_NET(&buf[12],ntp<<16);             // NTP low
        WRITE_DWORD_NET(&buf[16],timestamp);           // RTP timestamp
        WRITE_DWORD_NET(&buf[20],xPackets);            // sender packet count
        WRITE_DWORD_NET(&buf[24],xOctets);             // sender packet octet count

        WRITE_DWORD_NET(&buf[28],remoteSsrc);          // remote ssrc
        WRITE_DWORD_NET(&buf[32],loss);                // packet loss
        WRITE_DWORD_NET(&buf[36],rSeq);                // ext. seq number received
        WRITE_DWORD_NET(&buf[40],rJitter);             // jitter
        WRITE_DWORD_NET(&buf[44],lsr);                 // last SR
        WRITE_DWORD_NET(&buf[48],dlsr);                // delay since last SR

        buf[52] = 0x81; // V=2, RC=1
        buf[53] = 0xca; // SDE
        buf[54] = 0x00; // length high byte
        buf[55] = 0x00;
        WRITE_DWORD_NET(&buf[56],ssrc); // our ssrc

        p = 60;
        buf[p] = 0x01;
        buf[p+1] = _sprintf((char *)&buf[p+2],"client@%s", iceLocalAddr ? iceLocalAddr : "");
        p +=buf[p+1]+2;

        buf[p] = 0x02;
        buf[p+1] = _sprintf((char *)&buf[p+2],"client");
        p +=buf[p+1]+2;

        buf[p] = 0x06;
        buf[p+1] = _sprintf((char *)&buf[p+2],"innovaphone");
        p +=buf[p+1]+2;

        buf[p] = 0;  // add padding
        buf[p+1] = 0;
        buf[p+2] = 0;

        buf[55] = (p+3+4)/4;  // update length

        int msgLen = 52+(buf[55]+1)*4;

        if (!srtpSend) {
            if(!mediaTurn) {
                if(rtpOverTcp) {
                    byte l[2];
                    WRITE_WORD_NET(l, msgLen);
                    rtcpSendSocket->Send(l, sizeof(l));
                    rtcpSendSocket->Send((char *)buf, msgLen);
                }
                else rtcpSendSocket->SendTo((char *)buf, msgLen, mediaAddr, mediaRtcpPort);
            }
            else turnSendSocket->stunSend((char *)buf, msgLen, mediaAddr, mediaRtcpPort);
        }
        else {
            int outLen;
            byte * out;
            if ((out = srtpSend->SrtcpProtect(buf, msgLen, &outLen)) != NULL) {
                if(!mediaTurn) {
                    if(rtpOverTcp) {
                        byte l[2];
                        WRITE_WORD_NET(l, outLen);
                        rtcpSendSocket->Send(l, sizeof(l));
                        rtcpSendSocket->Send((char *)out, outLen);
                    }
                    else rtcpSendSocket->SendTo((char *)out, outLen, mediaAddr, mediaRtcpPort);
                }
                else turnSendSocket->stunSend((char *)out, outLen, mediaAddr, mediaRtcpPort);
                free(out);
            }
            else {
                log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p)::TimerOnTimeout SRTCP Encryption failed", this);
            }
        }
        free(buf);
    }
}
#endif

void MediaChannel::DtlsTimerStart(void * ctx, int to)
{
    if (ctx == (void*)DTLS_RTP_CONTEXT) dtlsRtpTimer->Start(to);
    else if (ctx == (void*)DTLS_RTCP_CONTEXT) dtlsRtcpTimer->Start(to);
}

void MediaChannel::DtlsTimerStop(void * ctx)
{
    if (ctx == (void*)DTLS_RTP_CONTEXT) dtlsRtpTimer->Cancel();
    else if (ctx == (void*)DTLS_RTCP_CONTEXT) dtlsRtcpTimer->Cancel();
}

void MediaChannel::DtlsConnectionClosed(void * ctx, int error)
{
    iceDtlsDone = true;
    if (ctx == (void*)DTLS_RTP_CONTEXT) {
        log->Log(LOG_DTLS, "MediaChannel(%p)::dtlsConnectionClosed (RTP) error=%d", this, error);
    }
    else if (ctx == (void*)DTLS_RTCP_CONTEXT) {
        log->Log(LOG_DTLS, "MediaChannel(%p)::dtlsConnectionClosed (RTCP) error=%d", this, error);
    }
    if (error) {
        char message[128];
        _sprintf(message, "DTLS failed with error code %u", error);
        media->EventReceived(CHANNEL_MEDIA_ERROR, new class EventMediaError(FAULT_CODE_RTP_DTLS_SRTP_FAILED, (const char *)message, (ctx == (void*)DTLS_RTCP_CONTEXT) ? "RTCP" : "RTP"));
    }
}

void MediaChannel::CleanSrtpKeys()
{
    if (this->srtpSend) {
        delete this->srtpSend;
        this->srtpSend = NULL;
    }
    if (this->srtpRecv) {
        delete this->srtpRecv;
        this->srtpRecv = NULL;
    }
    if (this->srtcpSend) {
        delete this->srtcpSend;
        this->srtcpSend = NULL;
    }
    if (this->srtcpRecv) {
        delete this->srtcpRecv;
        this->srtcpRecv = NULL;
    }
}

void MediaChannel::DtlsSrtpKeys(void* ctx, byte * keys, const char * profile)
{
    if(mediaType == MediaType::APPSHARING) return;

    bool server = remoteSetupRole == SetupRole::SETUP_ROLE_ACTIVE ? true : remoteSetupRole == SetupRole::SETUP_ROLE_PASSIVE ? false : iceControlling;

    if (ctx == (void*)DTLS_RTP_CONTEXT) {
        if (this->srtpSend) delete this->srtpSend;
        if (this->srtpRecv) delete this->srtpRecv;

        if (server) {
            this->srtpSend = ISrtpSession::Create(&keys[SRTP_MASTER_KEY_KEY_LEN], &keys[(SRTP_MASTER_KEY_KEY_LEN << 1) + SRTP_MASTER_KEY_SALT_LEN], profile);
            this->srtpRecv = ISrtpSession::Create(&keys[0], &keys[SRTP_MASTER_KEY_KEY_LEN << 1], profile);
        }
        else {
            this->srtpRecv = ISrtpSession::Create(&keys[SRTP_MASTER_KEY_KEY_LEN], &keys[(SRTP_MASTER_KEY_KEY_LEN << 1) + SRTP_MASTER_KEY_SALT_LEN], profile);
            this->srtpSend = ISrtpSession::Create(&keys[0], &keys[SRTP_MASTER_KEY_KEY_LEN << 1], profile);
        }
    }
    else {
        if (this->srtcpSend) delete this->srtcpSend;
        if (this->srtcpRecv) delete this->srtcpRecv;

        if (server) {
            this->srtcpSend = ISrtpSession::Create(&keys[SRTP_MASTER_KEY_KEY_LEN], &keys[(SRTP_MASTER_KEY_KEY_LEN << 1) + SRTP_MASTER_KEY_SALT_LEN], profile);
            this->srtcpRecv = ISrtpSession::Create(&keys[0], &keys[SRTP_MASTER_KEY_KEY_LEN << 1], profile);
        }
        else {
            this->srtcpRecv = ISrtpSession::Create(&keys[SRTP_MASTER_KEY_KEY_LEN], &keys[(SRTP_MASTER_KEY_KEY_LEN << 1) + SRTP_MASTER_KEY_SALT_LEN], profile);
            this->srtcpSend = ISrtpSession::Create(&keys[0], &keys[SRTP_MASTER_KEY_KEY_LEN << 1], profile);
        }
    }
    activateMediaConfig();
}

void MediaChannel::DtlsSend(void * ctx, char * buf, int len)
{
    if (disconnecting == false) {
        if (ctx == (void*)DTLS_RTP_CONTEXT) {
            if(log->LogFlagSet(LOG_DTLS)) {
                char dstAddr[128];
                str::from_ip(dstAddr, 128, &mediaAddr);
                debug->printf("MediaChannel(%p)::dtlsSend (%d) (RTP%s) %s:%u", this, len, mediaTurn ? (iceTurnTcp ? ",TURN/TCP" : ",TURN") : (rtpOverTcp ? ",TCP" : ""), dstAddr, mediaPort);
            }
            if(!mediaTurn) {
                if(rtpOverTcp) {
                    if(rtpTcpSocket) {
                        byte l[2];
                        WRITE_WORD_NET(l, len);
                        rtpTcpSocket->Send(l, sizeof(l));
                        rtpTcpSocket->Send(buf, len);
                    }
                }
                else if(socket) socket->SendTo(buf, len, mediaAddr, mediaPort);
            }
            else turnRtp.stunSend(buf, len, mediaAddr, mediaPort);
        }
        else if (ctx == (void*)DTLS_RTCP_CONTEXT) {
            class ISocket * rtcpSendSocket = rtcpMux ? socket : rtcpSocket;
            word mediaRtcpPort = rtcpMux ? this->mediaPort : this->mediaRtcpPort;
            class Turn * turnSendSocket = rtcpMux ? &turnRtp : &turnRtcp;

            if(log->LogFlagSet(LOG_DTLS)) {
                char dstAddr[128];
                str::from_ip(dstAddr, 128, &mediaAddr);
                debug->printf("MediaChannel(%p,%d)::dtlsSend (%d) (RTCP%s) %s:%u", this, len, rtcpMux, mediaTurn ? (iceTurnTcp ? ",TURN/TCP" : ",TURN") : "", dstAddr, mediaPort);
            }

            if(!mediaTurn) {
                if(rtcpSendSocket) rtcpSendSocket->SendTo(buf, len, mediaAddr, mediaRtcpPort);
            }
            else turnSendSocket->stunSend(buf, len, mediaAddr, mediaRtcpPort);
        }
    }
}

void MediaChannel::DtlsRecvApplicationData(void * ctx, char * buf, int len)
{
    if (mediaType == MediaType::APPSHARING) {
        if (dropMediaRx) {
            numAllowPacketsRx--;
            if (dropSeveralRx || (numAllowPacketsRx == 0)) {
                debug->printf("MediaChannel(%p)::DtlsRecvApplicationData drop message len=%d numAllowPackets=%d dropSeveral=%d", this, len, numAllowPacketsRx, dropSeveralRx);
                if (dropSeveralRx) {
                    dropSeveralRx--;
                    free(buf);
                    return;
                }
                dropSeveralRx = (IRandom::GetRandom() % 4);
                dropOffsetRx = dropSeveralRx + 1;
                numAllowPacketsRx = (100 / dropMediaRx) * dropSeveralRx;
                free(buf);
                return;
            }
        }
        SctpRecv(buf, len);
    }
    free(buf);
}

void MediaChannel::SctpDtlsSend(char * buf, int len)
{
    if (dtlsRtp) dtlsRtp->DtlsSendApplicationData(buf, len);
    free(buf);
}

void MediaChannel::SctpRecvAck(unsigned num)
{
    media->SctpRecvAck(num);
}

void MediaChannel::SctpRecvApplicationData(char * buf, int len)
{
    if (endpoint) endpoint->Recv(buf, len, 0, 0, false);
}

void MediaChannel::IceTimerStart(int to)
{
    iceTimer->Start(to);
}

void MediaChannel::IceTimerStop()
{
    iceTimer->Cancel();
}

void MediaChannel::RtpRecvResult(char * buf, int len, dword timestamp, short sequenceNumberDiff, bool marker)
{
    return media->RtpRecvResult(buf, len, timestamp, sequenceNumberDiff, marker);
};

void MediaChannel::RtpSendResult()
{
    return media->RtpSendResult();
};

void MediaChannel::RtpSend(char * buf, int len, dword timestamp, short sequenceNumberDiff, bool marker)
{
    if(dropMediaTx) {
        numAllowPacketsTx--;
        if(dropSeveralTx || (numAllowPacketsTx == 0)) {
            debug->printf("MediaChannel(%p)::RtpSend drop message len=%d seq=%d ts=%u media=%s numAllowPackets=%d dropSeveral=%d", this, len, xSeq, timestamp, ::mediaType(mediaType), numAllowPacketsTx, dropSeveralTx);
            if(dropSeveralTx) {
                dropSeveralTx--;
                return;
            }
            dropSeveralTx = (IRandom::GetRandom() % 4);
            dropOffset = dropSeveralTx + 1;
            numAllowPacketsTx = (100 / dropMediaTx) * dropOffset;
            return;
        }
    }
    byte rtpHeader[12];

    // Connection may be closed
    if(mediaTurn && iceTurnTcp && !tcpSocket) return;
    else if(!(rtpOverTcp ? rtpTcpSocket : socket)) return;

#ifdef _WINDOWS
    if (recordRtpStream && mediaType == MediaType::AUDIO) {
        if (!rtpStream) {
            char buf[2048];
            char* envPathLocalAppData = Environment::GetEnv("LOCALAPPDATA");
            if (_PRODUCT_NAME_ && !strcmp(_PRODUCT_NAME_, myappsPluginForWindows)) sprintf(buf, "%s\\innovaphone\\myappsPlugin\\RTP-Send-%d", envPathLocalAppData, cntTx);
            else sprintf(buf, "%s\\innovaphone\\myapps\\RTP-Send-%d", envPathLocalAppData, cntTx);
            cntTx = cntTx >= 5 ? 0 : cntTx + 1;
            rtpStream = FileSystem::OpenWriteFile(buf, false, NULL);
            if (!rtpStream && !recordRtpStreamTrace) {
                debug->printf("MediaChannel::RtpSend failed to open RTP-Send file");
                recordRtpStreamTrace = true;
            }
            free(envPathLocalAppData);
        }
        if (rtpStream) rtpStream->Write(buf, len);
    }
#elif defined(__IGEL__) || defined(__linux__)
    if (recordRtpStream && mediaType == MediaType::AUDIO) {
        if (!rtpStream) {
            char buf[2048];
            sprintf(buf, "//var//log//apps//myAppsPlugin//RTP-Send-%d", cntTx);
            cntTx = cntTx >= 5 ? 0 : cntTx + 1;
            rtpStream = FileSystem::OpenWriteFile(buf, false, NULL);
            if (!rtpStream && !recordRtpStreamTrace) {
                debug->printf("MediaChannel::RtpSend failed to open RTP-Send file");
                recordRtpStreamTrace = true;
            }
        }
        if (rtpStream) rtpStream->Write(buf, len);
    }
#endif

    bool dtmfXmit = false;

    if(xDtmfDuration) {
        // supress RTP audio while sending RTP dtmf:
        int delta1 = timestamp-xDtmfTimestamp;
        int delta2 = (xDtmfTimestamp+xDtmfDuration)-timestamp;
        if(delta1>=0 && delta2>=0) dtmfXmit = true;
        else if(delta2<0) xDtmfDuration = 0;
    }

    if (!dtmfXmit) {
        rtpHeader[0] = 0x80;
        rtpHeader[1] = (txPayload & 0x7f);
        if(marker) rtpHeader[1] |= 0x80;
        xSeq += 1 + sequenceNumberDiff + dropOffset;
        dropOffset = 0;
        xPackets++;
        xOctets += len;
        WRITE_WORD_NET(&rtpHeader[2], xSeq);
        WRITE_DWORD_NET(&rtpHeader[4], timestamp);
        WRITE_DWORD_NET(&rtpHeader[8], ssrc);
        if(!xmitTimestampDeltaSet && txPayload!=13) {
            xmitTimestampDelta = getTimestamp8kHz() * timestampRateMultiplier - timestamp;
            xmitTimestampDeltaSet = true;
        }

        if (!srtpSend) {
            char * rtp = (char *)malloc(len + 12);
            memcpy(rtp, rtpHeader, 12);
            memcpy(&rtp[12], (byte *)buf, len);
            if(record.On()) record.RtpXmit(rtp, len + 12);
            if(!mediaTurn) {
                if(rtpOverTcp) {
                    byte l[2];
                    WRITE_WORD_NET(l, len + 12);
                    rtpTcpSocket->Send(l, sizeof(l));
                    rtpTcpSocket->Send(rtp, len + 12);
                }
                else socket->SendTo(rtp, len + 12, mediaAddr, mediaPort);
            }
            else turnRtp.stunSend(rtp, len + 12, mediaAddr, mediaPort);
            free(rtp);
        }
        else {
            if(record.On()) {
                char * rtp = (char *)malloc(len + 12);
                memcpy(rtp, rtpHeader, 12);
                memcpy(&rtp[12], (byte *)buf, len);
                record.RtpXmit(rtp, len + 12);
            }
            int outLen;
            byte *out = srtpSend->Protect(rtpHeader, 12, (byte *)buf, len, &outLen);
            if(!mediaTurn) {
                if(rtpOverTcp) {
                    byte l[2];
                    WRITE_WORD_NET(l, outLen);
                    rtpTcpSocket->Send(l, sizeof(l));
                    rtpTcpSocket->Send(out, outLen);
                }
                else socket->SendTo((char *)out, outLen, mediaAddr, mediaPort);
            }
            else turnRtp.stunSend((char *)out, outLen, mediaAddr, mediaPort);
            free(out);
        }
    }
}

static char rtpDtmfChar[17] = {'0','1','2','3','4','5','6','7','8','9','*','#','A','B','C','D','!'};

void MediaChannel::RtpDtmf(char digit, byte pt)
{
//    debug->printf("MediaChannel::RtpDtmf %c", digit);
    dtmfXmitPt = pt;
    if(dtmfBufLen<sizeof(dtmfBuf)) {
        byte d;
        if('a'<=digit && digit<='z') digit -=32; // make uppercase
        for(d=0; d<17 && rtpDtmfChar[d]!=digit; d++);
        if(d<17) {
            dtmfBuf[dtmfBufLen++] = d;
            if (!mute) StartXmitRtpDtmf();
        }
    }
}

void MediaChannel::RecordingOn(bool on)
{
    record.Recording(on);
}

void MediaChannel::StartXmitRtpDtmf()
{
    if (dtmfBufLen && !dtmfXmitCount) {
        dtmfXmitCount++;
        XmitRtpDtmf(dtmfBuf[0], 960, true, false);
        dtmfTimer->Start(20);
    }
}

void MediaChannel::XmitRtpDtmf(byte digit, word duration, bool start, bool end)
{
	byte rtpDtmf[16];

    // Connection may be closed
    if(mediaTurn && iceTurnTcp && !tcpSocket) return;
    else if(!(rtpOverTcp ? rtpTcpSocket : socket)) return;

    rtpDtmf[0] = 0x80;
    if(start) {
        xDtmfTimestamp = getTimestamp8kHz() * timestampRateMultiplier - xmitTimestampDelta;
        xDtmfDuration = (duration + 800) * timestampRateMultiplier; /* discard audio 100 ms longer to avoid that local echo leaks to the peer */
        rtpDtmf[1] = dtmfXmitPt|0x80;
    }
    else {
        rtpDtmf[1] = dtmfXmitPt;
    }
    WRITE_WORD_NET(&rtpDtmf[2],++xSeq);
    WRITE_DWORD_NET(&rtpDtmf[4],xDtmfTimestamp);
    WRITE_DWORD_NET(&rtpDtmf[8],ssrc);

    rtpDtmf[12+0] = digit;     // digit
    rtpDtmf[12+1] = end? 0x80:0x00;
    WRITE_WORD_NET(&rtpDtmf[12+2],duration);

//    debug->printf("MediaChannel::XmitRtpDtmf %c", rtpDtmfChar[digit]);

    if (!srtpSend) {
        if(!mediaTurn) {
            if(rtpOverTcp) {
                byte l[2];
                WRITE_WORD_NET(l, 16);
                rtpTcpSocket->Send(l, sizeof(l));
                rtpTcpSocket->Send(rtpDtmf, 16);
            }
            else socket->SendTo((char *)rtpDtmf, 16, mediaAddr, mediaPort);
        }
        else turnRtp.stunSend((char *)rtpDtmf, 16, mediaAddr, mediaPort);
    }
    else {
        int outLen;
        byte *out = srtpSend->Protect(rtpDtmf, 12, &rtpDtmf[12], 4, &outLen);
        if(!mediaTurn) {
            if(rtpOverTcp) {
                byte l[2];
                WRITE_WORD_NET(l, outLen);
                rtpTcpSocket->Send(l, sizeof(l));
                rtpTcpSocket->Send((char *)out, outLen);
            }
            else socket->SendTo((char *)out, outLen, mediaAddr, mediaPort);
        }
        else turnRtp.stunSend((char *)out, outLen, mediaAddr, mediaPort);
        free(out);
    }
}

void MediaChannel::RtcpSend(char * buf, int len)
{
    class ISocket * rtcpSendSocket = rtcpMux ? (rtpOverTcp ? rtpTcpSocket : socket) : rtcpSocket;
    word mediaRtcpPort = rtcpMux ? this->mediaPort : this->mediaRtcpPort;
    class Turn * turnSendSocket = rtcpMux ? &turnRtp : &turnRtcp;
    class ISrtpSession * srtpTx = rtcpMux ? srtpSend : srtcpSend;

    byte* b = (byte*)buf;

    if(rtcpSendSocket && mediaRtcpPort > 1) {
        if(b[1] == 0xce) {  // PT=206
            if((b[0] & 0xf) == 4) {                           // FIR Message
                WRITE_DWORD_NET(&buf[4], ssrc);                 // our ssrc
                WRITE_DWORD_NET(&buf[8], 0);                    // peer ssrc, is not used and SHALL be set to 0
                WRITE_DWORD_NET(&buf[12], remoteSsrc);          // ssrc the FIR is asserted for
            }
            else if((b[0] & 0xf) == 1) {                      // PLI Message
                WRITE_DWORD_NET(&buf[4], ssrc);                 // our ssrc
                WRITE_DWORD_NET(&buf[8], remoteSsrc);           // peer ssrc
            }
        }
        else if(b[1] == 0xc0) {  // PT=192
            WRITE_DWORD_NET(&buf[4], ssrc);                 // our ssrc
        }

        if(!srtpTx) {
            if(!mediaTurn) {
                if(rtpOverTcp) {
                    byte l[2];
                    WRITE_WORD_NET(l, (buf[3] + 1) * 4);
                    rtcpSendSocket->Send(l, sizeof(l));
                    rtcpSendSocket->Send((char *)buf, (buf[3] + 1) * 4);
                }
                else rtcpSendSocket->SendTo((char *)buf, (buf[3] + 1) * 4, mediaAddr, mediaRtcpPort);
            }
            else turnSendSocket->stunSend((char *)buf, (buf[3] + 1) * 4, mediaAddr, mediaRtcpPort);
        }
        else {
            int outLen;
            byte * out;
            if((out = srtpTx->SrtcpProtect((byte *)buf, (buf[3]+1)*4, &outLen)) != NULL) {
                if(!mediaTurn) {
                    if(rtpOverTcp) {
                        byte l[2];
                        WRITE_WORD_NET(l, outLen);
                        rtcpSendSocket->Send(l, sizeof(l));
                        rtcpSendSocket->Send((char *)out, outLen);
                    }
                    else rtcpSendSocket->SendTo((char *)out, outLen, mediaAddr, mediaRtcpPort);
                }
                else turnSendSocket->stunSend((char *)out, outLen, mediaAddr, mediaRtcpPort);
                free(out);
            }
            else {
                log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p)::RtcpSend SRTCP Encryption failed", this);
            }
        }
    }
}

void MediaChannel::SctpRecvResult(char * buf, int len)
{
    return media->SctpRecvResult(buf, len);
};

void MediaChannel::SctpSendResult()
{
    return media->SctpSendResult();
};

void MediaChannel::SctpSend(char * buf, int len, unsigned num)
{
    // Connection may be closed
    if(mediaTurn && iceTurnTcp && !tcpSocket) return;
    else if(!(rtpOverTcp ? rtpTcpSocket : socket)) return;

    SctpSendApplicationData(buf, len, num);
}

void MediaChannel::ConnectMulticast(char * addr, int port)
{
    this->mcSocket = udpSocketProvider->CreateSocket(iomux, this, log, this->ipv6);
    this->mcSocket->Bind(addr, port);
}

void MediaChannel::connect(class MediaConfig * remoteMediaConfig, bool iceRemoteRole, enum SetupRole remoteDtlsRole)
{
    unsigned i;

    if (disconnecting) return;
    iceDtlsDone = false;
    this->remoteSetupRole = remoteDtlsRole;
    this->iceControlling = !iceRemoteRole;
    if(remoteMediaConfig) {
        iceAbortConnect(IceAbortReason::RESET_ICE_PATH);
        turnRtp.clearAllBinds();
        turnRtcp.clearAllBinds();
        remoteIce.common.count = 0;
        class Codec * c = remoteMediaConfig->codecList.front();
        if(!c || !c->coder) {
            log->Log(LOG_MEDIA, "MediaChannel(%p)::Connect No codec available %p", this, c);
            mediaErrorMessage = Media::NoCoder;
            iomux->SetExec(this, NULL);
            execCnt++;
            media->EventReceived(CHANNEL_MEDIA_ERROR, new class EventMediaError(FAULT_CODE_RTP_NO_MEDIA_CODEC, "No Media Codec found", mediaType == MediaType::AUDIO ? "Audio" : "Video"));
            return;
        }
        i = 0;
        while ((i < media->AvailableCoderCount()) && (media->AvailableCoder(i) != c->coder)) {
            i++;
        }
        if (i == media->AvailableCoderCount()) {
            log->Log(LOG_MEDIA, "MediaChannel(%p)::Connect Unknown Audio/Video codec (%d)", this, c->coder);
            mediaErrorMessage = Media::UnknownCoder;
            iomux->SetExec(this, NULL);
            execCnt++;
            media->EventReceived(CHANNEL_MEDIA_ERROR, new class EventMediaError(FAULT_CODE_RTP_NO_MATCHING_MEDIA_CODEC, "No suitable Media Codec found", mediaType == MediaType::AUDIO ? "Audio" : "Video"));
            return;
        }
        if((mediaType == MediaType::VIDEO) || (mediaType == MediaType::AUDIO)) {
            if((c->coder == AudioCoder::OPUS_NB) || (c->coder == AudioCoder::OPUS_WB)) timestampRateMultiplier = 6;
            else if((c->coder == VideoCoder::H264) || (c->coder == VideoCoder::VP8)) timestampRateMultiplier = 11;
        }
        if(this->endpoint) {
            delete this->endpoint;
            this->endpoint = NULL;
        }
        switch (remoteMediaConfig->type) {
            case MediaType::AUDIO:
#if !defined(__REMOTE_CONTROL_CLIENT__)
                this->audioCoder = (enum AudioCoder)(c->coder);
                this->endpoint = new class AudioEndpoint((enum AudioCoder)(c->coder), this, log);
#endif
                break;
            case MediaType::VIDEO:
            case MediaType::VIDEO_APPSHARING:
#if !defined(__REMOTE_CONTROL_CLIENT__)
                this->endpoint = new class VideoEndpoint(iomux, (enum VideoCoder)(c->coder), this, log);
#endif
                break;
            case MediaType::APPSHARING:
                this->endpoint = new class AppSharingEndpoint((enum AppSharingCoder)(c->coder), this, log);
                break;
            case MediaType::APPSHARING_CONTAINER:
            case MediaType::MP4_CONTAINER:
            case MediaType::WEBM_VP8_CONTAINER:
            case MediaType::WEBM_VP9_CONTAINER:
                this->endpoint = NULL;
                break;
        }
        this->rxPayload = c->pt;
        this->txPayload = c->pt;
        this->dtmfXmitPt = remoteMediaConfig->remoteDTMF;
        this->dtmfRecvPt = remoteMediaConfig->localDTMF;
        if(remoteMediaConfig->mcAddr && remoteMediaConfig->mcPort) {
            debug->printf("MediaChannel(%p)::Connect codec mc=(%s:%d)", this, remoteMediaConfig->mcAddr, remoteMediaConfig->mcPort);

            ConnectMulticast(remoteMediaConfig->mcAddr, remoteMediaConfig->mcPort);
            return;
        }
        else {
            debug->printf("MediaChannel(%p)::Connect(%d) type Payload=%d (LocalAddr %s:%d,%d) %d %s:%d", this, iceControlling, this->txPayload, iceLocalAddr, iceLocalPort, iceLocalRtcpPort, remoteMediaConfig->type, (remoteMediaConfig->defAddr ? remoteMediaConfig->defAddr : ""), remoteMediaConfig->defPort);
            for(c = remoteMediaConfig->codecList.front(); c; c = c->goNext()) {
                debug->printf("MediaChannel(%p)::Connect codec (%d) %s:%d payload %d", this, c->coder, (c->addr ? c->addr : ""), c->port, c->pt);
            }
            if(remoteMediaConfig->ice == NULL) {
                c = remoteMediaConfig->codecList.front();
                if(!primary || !c || !c->addr) {
                    mediaErrorMessage = Media::NotReachable;
                    iomux->SetExec(this, NULL);
                    execCnt++;
                    return;
                }
                oldMediaAddr = str::to_ip(c->addr);
                oldMediaPort = c->port;
                oldMediaRtcpPort = 0;
                mediaAddr = str::to_ip(c->addr);
                mediaPort = c->port;
                mediaRtcpPort = 0;
                mute = false;
                mediaErrorMessage = Media::Success;
                iomux->SetExec(this, NULL);
                execCnt++;
                return;
            }
            else {
                if (remoteMediaConfig->ice->iceUfrag) {
                    str::to_str(remoteMediaConfig->ice->iceUfrag, remoteIce.common.usr, sizeof(remoteIce.common.usr));
                }
                else {
                    remoteIce.common.usr[0] = '\0';
                }
                if (remoteMediaConfig->ice->icePwd) {
                    str::to_str(remoteMediaConfig->ice->icePwd, remoteIce.common.pwd, sizeof(remoteIce.common.pwd));
                }
                else {
                    remoteIce.common.pwd[0] = '\0';
                }
                if(remoteMediaConfig->ice->fingerprint) {
                    remoteIce.common.fingerprintLen = (byte)str::to_hexmem((const char *)remoteMediaConfig->ice->fingerprint, remoteIce.common.fingerprint, sizeof(remoteIce.common.fingerprint));
                }
                else {
                    remoteIce.common.fingerprintLen = 0;
                    remoteIce.common.fingerprint[0] = '\0';
                }
                rtcpMux = remoteMediaConfig->ice->rtcpMux;
                remoteIce.common.rtcpMux = remoteMediaConfig->ice->rtcpMux;
                debug->printf("MediaChannel(%p)::Connect RtcpMux(%d) ICE(%s:%s)", this, remoteMediaConfig->ice->rtcpMux,
                                ((remoteMediaConfig->ice && remoteMediaConfig->ice->iceUfrag) ? remoteMediaConfig->ice->iceUfrag : ""), ((remoteMediaConfig->ice && remoteMediaConfig->ice->icePwd) ? remoteMediaConfig->ice->icePwd : ""));
                class IceCandidate * c;
                for (c = remoteMediaConfig->ice->candidateList.front(); c; c = c->goNext()) {
                    if(c->type == CHANNEL_CANDIDATE_HOST)
                        debug->printf("MediaChannel(%p)::Connect candidate(%d,%d) %s:%d,%d (%s) mDns=%d turnPort=%d,%d", this, remoteIce.common.count, c->type, c->addr, c->rtpPort, c->rtcpPort, (c->foundation ? c->foundation : ""), strstr(c->addr, ".local") != NULL, turnRtp.relayedPort, turnRtcp.relayedPort);
                    else
                        debug->printf("MediaChannel(%p)::Connect candidate(%d,%d) %s:%d,%d (%s:%d,%d) (%s) turnPort=%d,%d", this, remoteIce.common.count, c->type, c->addr, c->rtpPort, c->rtcpPort, c->relatedAddr ? c->relatedAddr : "", c->relatedRtpPort, c->relatedRtcpPort, (c->foundation ? c->foundation : ""), turnRtp.relayedPort, turnRtcp.relayedPort);
                    remoteIce.candidate[remoteIce.common.count].addr = str::to_ip(c->addr);
                    remoteIce.candidate[remoteIce.common.count].relatedAddr = str::to_ip(c->relatedAddr);
                    remoteIce.candidate[remoteIce.common.count].rtpPort = c->rtpPort;
                    remoteIce.candidate[remoteIce.common.count].rtcpPort = c->rtcpPort;
                    remoteIce.candidate[remoteIce.common.count].relatedRtpPort = c->relatedRtpPort;
                    remoteIce.candidate[remoteIce.common.count].relatedRtcpPort = c->relatedRtcpPort;
                    remoteIce.candidate[remoteIce.common.count].rtpPriority = c->rtpPriority;
                    remoteIce.candidate[remoteIce.common.count].rtcpPriority = c->rtcpPriority;
                    remoteIce.candidate[remoteIce.common.count].type = c->type;
                    remoteIce.candidate[remoteIce.common.count].mDns = ((c->type == CHANNEL_CANDIDATE_HOST) && strstr(c->addr, ".local")) ? true : false;
                    if(c->foundation) strcpy(&remoteIce.candidate[remoteIce.common.count].foundation[0], c->foundation);
                    if(!is_anyaddr(remoteIce.candidate[remoteIce.common.count].addr) && turnRtp.relayedPort) {
                        turnRtp.bind(remoteIce.candidate[remoteIce.common.count].addr, remoteIce.candidate[remoteIce.common.count].rtpPort);
                        if(remoteIce.candidate[remoteIce.common.count].rtcpPort) turnRtcp.bind(remoteIce.candidate[remoteIce.common.count].addr, remoteIce.candidate[remoteIce.common.count].rtcpPort);
                    }
                    remoteIce.common.count++;
                    if (remoteIce.common.count == CHANNEL_MAX_CANDIDATE) {
                        debug->printf("MediaChannel(%p)::Connect reached maximum number of candidates, break", this);
                        break;
                    }
                }
                iceConnect(&remoteIce, iceControlling, false);
            }
        }
    }
    else {
        mediaErrorMessage = Media::NoRemoteConfig;
        iomux->SetExec(this, NULL);
        execCnt++;
    }
}

void MediaChannel::disconnectIcePath(enum IceAbortReason disc)
{
    debug->printf("MediaChannel(%p)::disconnectIcePath disc=%d", this, disc);

    iceAbortConnect(disc);

    iceDtlsDone = true;
}

void MediaChannel::disconnect(class MediaConfig * remoteMediaConfig, enum IceAbortReason disc)
{
    log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p)::disconnect %p,%p,%p,%p,%p,%p,%p,%p reason=%d", this, socket, mcSocket, rtcpSocket, tcpSocket, tcpAltSocket, rtcpTcpSocket, rtpTcpListen, rtpTcpSocket, disc);

    if(disconnecting) return;

    if(!sendonly && connectedTs && (getTimestamp8kHz() - connectedTs) > (10 * 8000) && !rTotal && ((mediaType == MediaType::AUDIO) || (mediaType == MediaType::VIDEO))) {
        media->EventReceived(CHANNEL_MEDIA_ERROR, new class EventMediaError(FAULT_CODE_RTP_NO_MEDIA_DATA, "No Media Data received", "RTP"));
    }

    iceAbortConnect(disc);
    if(dtlsRtp) dtlsRtp->DtlsAbortConnect();
    if(dtlsRtcp) dtlsRtcp->DtlsAbortConnect();
    if(!turnRtp.isIdle() || !turnRtcp.isIdle()) {
        if(!iceTurnTcp && socket) turnRtp.releaseAllocation();
        else if(iceTurnTcp && tcpSocket) turnRtp.releaseAllocation();
        if(!iceTurnTcp && rtcpSocket) turnRtcp.releaseAllocation();
        else if(iceTurnTcp && rtcpTcpSocket) turnRtcp.releaseAllocation();
    }
    TurnTimerStop(&turnRtp);
    TurnTimerStop(&turnRtcp);
    iceTimer->Cancel();
    dtlsRtpTimer->Cancel();
    dtlsRtcpTimer->Cancel();
    rtcpTimer->Cancel();
    dtmfTimer->Cancel();
    errorTimer->Cancel();
    SctpRetransmitStop();

    iceDtlsDone = true;

    record.Close();

    disconnecting = true;
    if(socket) {
        closeRtp = new class CloseSocket(iomux, udpSocketProvider, this->ipv6, (class ISocket *)socket, iceLocalAddr, iceLocalPort, log);
        closeRtp->Start(&taskCloseSocket);
    }
    if(rtcpSocket) {
        closeRtcp = new class CloseSocket(iomux, udpSocketProvider, this->ipv6, (class ISocket *)rtcpSocket, iceLocalAddr, iceLocalRtcpPort, log);
        closeRtcp->Start(&taskCloseSocket);
    }
    if(mcSocket) {
        closeMc = new class CloseSocket(iomux, udpSocketProvider, this->ipv6, (class ISocket*)mcSocket, remoteMediaConfig->mcAddr, remoteMediaConfig->mcPort, log);
        closeMc->Start(&taskCloseSocket);
    }
    if(tcpSocket) {
        tcpSocket->Shutdown();
    }
    if(tcpAltSocket) {
        tcpAltSocket->Shutdown();
    }
    if(rtcpTcpSocket) {
        rtcpTcpSocket->Shutdown();
    }
    if(rtpTcpListen) {
        rtpTcpListen->Shutdown();
    }
    if(rtpTcpSocket) {
        rtpTcpSocket->Shutdown();
    }
}

void MediaChannel::CloseSocketComplete(class CloseSocket * task)
{
    log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p,rtp=%p,rtcp=%p,mc=%p)::CloseSocketComplete tasks %p == (%p,%p) %s", this, this->socket, this->rtcpSocket, task, this->closeRtp, this->closeRtcp, this->closeMc, (socket == rtcpSocket) ? "RTCP" : (socket == rtcpTcpSocket) ? "TCP-RTCP" : (socket == tcpSocket) ? "TCP" : (socket == mcSocket) ? "MC" : "RTP");
    if(this->closeRtp == task) {
        this->closeRtp = NULL;
        if(this->socket) this->socket->Shutdown();             // this->socket may be NULL if a shutdown happened unexpectedly before CloseSocketComplete
    }
    else if(this->closeRtcp == task) {
        this->closeRtcp = NULL;
        if(this->rtcpSocket) this->rtcpSocket->Shutdown();    // this->rtcpSocket may be NULL if a shutdown happened unexpectedly before CloseSocketComplete
    }
    else if (this->closeMc == task) {
        this->closeMc = NULL;
        if (this->mcSocket) this->mcSocket->Shutdown();    // this->rtcpSocket may be NULL if a shutdown happened unexpectedly before CloseSocketComplete
    }
    if(!this->mcSocket && !this->socket && !this->rtcpSocket && !this->tcpSocket && !this->tcpAltSocket && !this->rtcpTcpSocket && !this->rtpTcpListen && !this->rtpTcpSocket && !this->closeRtp && !this->closeRtcp && !this->closeMc && !this->reqStun1 && !this->reqStun2 && !this->reqStun3 && !this->reqStun4 && !this->reqTurn1 && !this->reqTurn2 && !record.On() && !execCnt) media->channelDisconnected(this);
}

void MediaChannel::DnsRequestComplete(class DnsRequest * task)
{
    log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p,%p)::dnsRequestComplete %s -> %s (%d)", this, task, task->hostname, (task->addr ? task->addr : "NULL"), task->isIPv6);
    if(task == reqStun1) {
        if(this->ipv6 == task->isIPv6 && task->addr) {
            free(iceStun1Addr);
            iceStun1Addr = _strdup(task->addr);
            stun1AddrStorage = str::to_ip(iceStun1Addr);
        }
        else {
            iceStun1Port = 0;
            if (!task->addr) {
                media->EventReceived(CHANNEL_MEDIA_ERROR, new class EventMediaError(FAULT_CODE_RTP_STUN_DNS_FAILED, "DNS name of STUN server 1 could not be resolved", "RTP"));
            }
        }
        reqStun1 = NULL;
    }
    else if (task == reqStun2) {
        if(this->ipv6 == task->isIPv6 && task->addr) {
            free(iceStun1Addr2);
            iceStun1Addr2 = _strdup(task->addr);
            stun1Addr2Storage = str::to_ip(iceStun1Addr2);
        }
        else {
            iceStun1Port2 = 0;
            if (!task->addr) {
                media->EventReceived(CHANNEL_MEDIA_ERROR, new class EventMediaError(FAULT_CODE_RTP_STUN_DNS_FAILED, "DNS name of STUN server 2 could not be resolved", "RTP"));
            }
        }
        reqStun2 = NULL;
    }
    else if (task == reqStun3) {
        if (this->ipv6 == task->isIPv6 && task->addr) {
            free(iceStun2Addr);
            iceStun2Addr = _strdup(task->addr);
            stun2AddrStorage = str::to_ip(iceStun2Addr);
        }
        else {
            iceStun2Port = 0;
            if (!task->addr) {
                media->EventReceived(CHANNEL_MEDIA_ERROR, new class EventMediaError(FAULT_CODE_RTP_STUN_DNS_FAILED, "DNS name of STUN server 3 could not be resolved", "RTP"));
            }
        }
        reqStun3 = NULL;
    }
    else if (task == reqStun4) {
        if (this->ipv6 == task->isIPv6 && task->addr) {
            free(iceStun2Addr2);
            iceStun2Addr2 = _strdup(task->addr);
            stun2Addr2Storage = str::to_ip(iceStun2Addr2);
        }
        else {
            iceStun2Port2 = 0;
            if (!task->addr) {
                media->EventReceived(CHANNEL_MEDIA_ERROR, new class EventMediaError(FAULT_CODE_RTP_STUN_DNS_FAILED, "DNS name of STUN server 4 could not be resolved", "RTP"));
            }
        }
        reqStun4 = NULL;
    }
    else if (task == reqTurn1) {
        reqTurn1 = NULL;
        if(this->ipv6 == task->isIPv6 && task->addr && (!iceTurnTcp || proxy)) {
            free(iceTurnAddr);
            iceTurnAddr = _strdup(task->addr);
            turnRtp.turnAddr = str::to_ip(iceTurnAddr);
            turnRtp.turnPort = iceTurnPort;
        }
        else {
            if (iceTurnTcp) {
                if (!proxies) {
                    tunnelTargetHost = _strdup(iceTurnHostname);
                    tunnelTargetPort = (iceTurnPort != 0) ? iceTurnPort : 3478;
                    char buf[1024];
                    _sprintf(buf, "%s://%s", "http", iceTurnHostname);
                    proxies = INetwork::GetHTTPProxiesForURL(buf);
                    proxy = proxies->httpProxies.empty() ? NULL : proxies->httpProxies.front();
                }
                else if (proxy) {
                    proxy = proxy->goNext();
                }
                bool proxyIPv6 = ipv6;
                do {
                    while (proxy && (proxy->type != HTTP_PROXY_TYPE_HTTP) && (proxy->type != HTTP_PROXY_TYPE_HTTPS)) {
                        proxy = proxy->goNext();
                    }
                    if (proxy) {
                        char * p = 0;
                        str::to_ip(proxy->host, &p, /*prefix*/0);
                        if (p != proxy->host) {
                            proxyIPv6 = (strchr(proxy->host, ':') != NULL);
                            if (proxyIPv6 != ipv6) {
                                proxy = proxy->goNext();
                            }
                        }
                        else {
                            proxyIPv6 = ipv6;
                        }
                    }
                }
                while (proxy && (proxyIPv6 != ipv6));
                if (proxy) {
                    free(iceTurnHostname);
                    free(iceTurnAddr);
                    iceTurnHostname = _strdup(proxy->host);
                    iceTurnAddr = _strdup(proxy->host);
                    iceTurnPort = (proxy->port != 0) ? proxy->port : 80;
                    connectTunnel = true;
                    connectTunnelRtcp = true;
                    reqTurn1 = new class DnsRequest(iceTurnHostname, dns, log, iceSlow ? 3000 : 1000, 0, iceTurnPort);
                    reqTurn1->Start(&taskDns);
                    log->Log(LOG_TURN, "(%p) Turn1 tunnel (%p) %s:%d %d,%s", reqTurn1, &iceTurnAddr, iceTurnAddr, iceTurnPort, strlen(iceTurnHostname), iceTurnHostname);
                }
            }
            if (!proxy) {
                if(this->ipv6 == task->isIPv6 && task->addr) {
                    free(iceTurnAddr);
                    iceTurnAddr = _strdup(task->addr);
                    turnRtp.turnAddr = str::to_ip(iceTurnAddr);
                    turnRtp.turnPort = iceTurnPort;
                }
                else {
                    iceTurnPort = 0;
                    if (!this->ipv6 && !task->addr) {
                        media->EventReceived(CHANNEL_MEDIA_ERROR, new class EventMediaError(FAULT_CODE_RTP_STUN_DNS_FAILED, "DNS name of TURN server 1 could not be resolved", "RTP"));
                    }
                }
            }
        }
    }
    else if (task == reqTurn2) {
        reqTurn2 = NULL;
        if(this->ipv6 == task->isIPv6 && task->addr && (!iceTurnAltTcp || proxy)) {
            free(iceTurnAltAddr);
            iceTurnAltAddr = _strdup(task->addr);
            turnRtp.turnAltAddr = str::to_ip(iceTurnAltAddr);
            turnRtp.turnAltPort = iceTurnAltPort;
        }
        else {
            if (iceTurnAltTcp) {
                if (!proxies) {
                    tunnelTargetHost = _strdup(iceTurnAltHostname);
                    tunnelTargetPort = (iceTurnAltPort != 0) ? iceTurnAltPort : 3478;
                    char buf[1024];
                    _sprintf(buf, "%s://%s", "http", iceTurnAltHostname);
                    proxies = INetwork::GetHTTPProxiesForURL(buf);
                    proxy = proxies->httpProxies.empty() ? NULL : proxies->httpProxies.front();
                }
                else if (proxy) {
                    proxy = proxy->goNext();
                }
                bool proxyIPv6 = ipv6;
                do {
                    while (proxy && (proxy->type != HTTP_PROXY_TYPE_HTTP) && (proxy->type != HTTP_PROXY_TYPE_HTTPS)) {
                        proxy = proxy->goNext();
                    }
                    if (proxy) {
                        char * p = 0;
                        str::to_ip(proxy->host, &p, /*prefix*/0);
                        if (p != proxy->host) {
                            proxyIPv6 = (strchr(proxy->host, ':') != NULL);
                            if (proxyIPv6 != ipv6) {
                                proxy = proxy->goNext();
                            }
                        }
                        else {
                            proxyIPv6 = ipv6;
                        }
                    }
                }
                while (proxy && (proxyIPv6 != ipv6));
                if (proxy) {
                    free(iceTurnAltHostname);
                    free(iceTurnAltAddr);
                    iceTurnAltHostname = _strdup(proxy->host);
                    iceTurnAltAddr = _strdup(proxy->host);
                    iceTurnAltPort = (proxy->port != 0) ? proxy->port : 80;
                    connectTunnel = true;
                    connectTunnelRtcp = true;
                    reqTurn2 = new class DnsRequest(iceTurnAltHostname, dns, log, iceSlow ? 3000 : 1000, 0, iceTurnAltPort);
                    reqTurn2->Start(&taskDns);
                    log->Log(LOG_TURN, "(%p) Turn2 tunnel (%p) %s:%d %d,%s", reqTurn2, &iceTurnAltAddr, iceTurnAltAddr, iceTurnAltPort, strlen(iceTurnAltHostname), iceTurnAltHostname);
                }
            }
            if (!proxy) {
                if(this->ipv6 == task->isIPv6 && task->addr) {
                    free(iceTurnAltAddr);
                    iceTurnAltAddr = _strdup(task->addr);
                    turnRtp.turnAltAddr = str::to_ip(iceTurnAltAddr);
                    turnRtp.turnAltPort = iceTurnAltPort;
                }
                else {
                    iceTurnAltPort = 0;
                    if (!this->ipv6 && !task->addr) {
                        media->EventReceived(CHANNEL_MEDIA_ERROR, new class EventMediaError(FAULT_CODE_RTP_STUN_DNS_FAILED, "DNS name of TURN server 2 could not be resolved", "RTP"));
                    }
                }
            }
        }
    }
    delete task;
    if(!disconnecting) tryInitialized();
    else if(!this->mcSocket && !this->socket && !this->rtcpSocket && !this->tcpSocket && !this->tcpAltSocket && !this->rtcpTcpSocket && !this->rtpTcpListen && !this->rtpTcpSocket && !this->closeRtp && !this->closeRtcp && !this->closeMc && !this->reqStun1 && !this->reqStun2 && !this->reqStun3 && !this->reqStun4 && !this->reqTurn1 && !this->reqTurn2 && !record.On() && !execCnt) media->channelDisconnected(this);
}

void MediaChannel::GetStunServers(const char* stunServers)
{
    char* server1 = (char *)stunServers;
    char* server2 = (char *)stunServers ? (char*)strchr(stunServers, ';') : 0;
    if (server2) {
        *server2 = 0;
        ++server2;
    }
    ParseStunServer1(server1);
    if(server2) ParseStunServer2(server2);
}

void MediaChannel::ParseStunServer1(const char * stunServer)
{
    if(stunServers) {
        char * servers = (char *)malloc(strlen(stunServer)+1);
        strcpy(servers, stunServer);
        char * server1 = servers;
        char * server2 = servers ? strchr(servers, ',') : 0;
        if (server2) {
            *server2 = 0;
            ++server2;
        }
        if (server1) {
            char * p = server1;
            while(*p == ' ') p++;
            server1 = p;
            if (*p == '[') {
                p = strchr(p+1, ']');
            }
            if (p) {
                p = strchr(p, ':');
            }
            if (p) {
                *p++ = 0;
                iceStun1Port = (int)strtoul(p, 0, 0);
            }
            if (*server1) {
                if(!iceStun1Port) iceStun1Port = 3478;
                iceStun1Addr = _strdup(server1);
                reqStun1 = new class DnsRequest(iceStun1Addr, dns, log, 1000, ipv6 ? DNS_GETHOSTBYNAME_ONLY_IPV6 : DNS_GETHOSTBYNAME_NO_IPV6, iceStun1Port);
                reqStun1->Start(&taskDns);
                log->Log(LOG_ICE, "(%p) Stun1 (%p) %s:%d", reqStun1, &iceStun1Addr, iceStun1Addr, iceStun1Port);
            }
        }
        if (server2) {
            char * p = server2;
            while(*p == ' ') p++;
            server2 = p;
            if (*p == '[') {
                p = strchr(p+1, ']');
            }
            if (p) {
                p = strchr(p, ':');
            }
            if (p) {
                *p++ = 0;
                iceStun1Port2 = (int)strtoul(p, 0, 0);
            }
            if (*server2) {
                if(!iceStun1Port2) iceStun1Port2 = 3478;
                iceStun1Addr2 = _strdup(server2);
                reqStun2 = new class DnsRequest(iceStun1Addr2, dns, log, 1000, ipv6 ? DNS_GETHOSTBYNAME_ONLY_IPV6 : DNS_GETHOSTBYNAME_NO_IPV6, iceStun1Port2);
                reqStun2->Start(&taskDns);
                log->Log(LOG_ICE, "(%p) Stun2 %s:%d", reqStun2, iceStun1Addr2, iceStun1Port2);
            }
        }
        free(servers);
    }
}

void MediaChannel::ParseStunServer2(const char* stunServer)
{
    if (stunServers) {
        char* servers = (char*)malloc(strlen(stunServer) + 1);
        strcpy(servers, stunServer);
        char* server1 = servers;
        char* server2 = servers ? strchr(servers, ',') : 0;
        if (server2) {
            *server2 = 0;
            ++server2;
        }
        if (server1) {
            char* p = server1;
            while (*p == ' ') p++;
            server1 = p;
            if (*p == '[') {
                p = strchr(p + 1, ']');
            }
            if (p) {
                p = strchr(p, ':');
            }
            if (p) {
                *p++ = 0;
                iceStun1Port = (int)strtoul(p, 0, 0);
            }
            if (*server1) {
                if (!iceStun2Port) iceStun2Port = 3478;
                iceStun2Addr = _strdup(server1);
                reqStun3 = new class DnsRequest(iceStun2Addr, dns, log, 1000, ipv6 ? DNS_GETHOSTBYNAME_ONLY_IPV6 : DNS_GETHOSTBYNAME_NO_IPV6, iceStun2Port);
                reqStun3->Start(&taskDns);
                log->Log(LOG_ICE, "(%p) Stun3 (%p) %s:%d", reqStun3, &iceStun2Addr, iceStun2Addr, iceStun2Port);
            }
        }
        if (server2) {
            char* p = server2;
            while (*p == ' ') p++;
            server2 = p;
            if (*p == '[') {
                p = strchr(p + 1, ']');
            }
            if (p) {
                p = strchr(p, ':');
            }
            if (p) {
                *p++ = 0;
                iceStun2Port2 = (int)strtoul(p, 0, 0);
            }
            if (*server2) {
                if (!iceStun2Port2) iceStun2Port2 = 3478;
                iceStun2Addr2 = _strdup(server2);
                reqStun4 = new class DnsRequest(iceStun2Addr2, dns, log, 1000, ipv6 ? DNS_GETHOSTBYNAME_ONLY_IPV6 : DNS_GETHOSTBYNAME_NO_IPV6, iceStun2Port2);
                reqStun4->Start(&taskDns);
                log->Log(LOG_ICE, "(%p) Stun4 %s:%d", reqStun4, iceStun2Addr2, iceStun2Port2);
            }
        }
        free(servers);
    }
}

void MediaChannel::GetTurnServers(const char * turnServers)
{
    if(turnServers) {
        char * servers = (char *)malloc(strlen(turnServers)+1);
        strcpy(servers, turnServers);
        char * server1 = servers;
        char * server2 = servers ? strchr(servers, ',') : 0;
        if (server2) {
            *server2 = 0;
            ++server2;
        }
        tryAlternateTurnAddr = (server2 != NULL);
        char * server = (useAlternateTurnAddr && tryAlternateTurnAddr) ? server2 : server1;
        char * serverAlt = (!useAlternateTurnAddr && tryAlternateTurnAddr) ? server2 : server1;
        if (server) {
            char * p = strchr(server, '?');
            if (p) {
                *p++ = 0;
                if (strstr(p, "tcp")) {
                    iceTurnTcp = true;
                }
            }
            p = server;
            while(*p == ' ') p++;
            server = p;
            if (*p == '[') {
                p = strchr(p+1, ']');
            }
            if (p) {
                p = strchr(p, ':');
            }
            if (p) {
                *p++ = 0;
                iceTurnPort = (int)strtoul(p, 0, 0);
            }
            if (*server) {
                if(!iceTurnPort) iceTurnPort = 3478;
                iceTurnHostname = _strdup(server);
                iceTurnAddr = _strdup(server);
                reqTurn1 = new class DnsRequest(iceTurnAddr, dns, log, iceSlow ? 3000 : 1000, ipv6 ? DNS_GETHOSTBYNAME_ONLY_IPV6 : DNS_GETHOSTBYNAME_NO_IPV6, iceTurnPort);
                reqTurn1->Start(&taskDns);
                log->Log(LOG_TURN, "(%p) Turn (%p) %s:%d %d,%s", reqTurn1, &iceTurnAddr, iceTurnAddr, iceTurnPort, strlen(server), server);

                if (serverAlt) {
                    if (serverAlt == server) {
                        iceTurnAltTcp = iceTurnTcp;
                        iceTurnAltPort = iceTurnPort;
                    }
                    else {
                        char * p = strchr(serverAlt, '?');
                        if (p) {
                            *p++ = 0;
                            if (strstr(p, "tcp")) {
                                iceTurnAltTcp = true;
                            }
                        }
                        p = serverAlt;
                        while(*p == ' ') p++;
                        serverAlt = p;
                        if (*p == '[') {
                            p = strchr(p+1, ']');
                        }
                        if (p) {
                            p = strchr(p, ':');
                        }
                        if (p) {
                            *p++ = 0;
                            iceTurnAltPort = (int)strtoul(p, 0, 0);
                        }
                    }
                    if (*serverAlt) {
                        if(!iceTurnAltPort) iceTurnAltPort = 3478;
                        iceTurnAltHostname = _strdup(serverAlt);
                        iceTurnAltAddr = _strdup(serverAlt);
                        reqTurn2 = new class DnsRequest(iceTurnAltAddr, dns, log, iceSlow ? 3000 : 1000, ipv6 ? DNS_GETHOSTBYNAME_ONLY_IPV6 : DNS_GETHOSTBYNAME_NO_IPV6, iceTurnAltPort);
                        reqTurn2->Start(&taskDns);
                        log->Log(LOG_TURN, "(%p) TurnAlt (%p) %s:%d %d,%s", reqTurn2, &iceTurnAltAddr, iceTurnAltAddr, iceTurnAltPort, strlen(serverAlt), serverAlt);
                    }
                    else {
                        iceTurnAltPort = 0;
                    }
                }
            }
        }
        if (server == server2) tryAlternateTurnAddr = false;
        free(servers);
    }
}

bool MediaChannel::AddIceCandidate(int type, IPaddr localAddr, int localRtp, int localRtcpPort, IPaddr relayedAddr, int relayedRtpPort, int relayedRtcpPort, unsigned int rtpPriority, unsigned int rtcpPriority)
{
    class IceCandidate * cand = new class IceCandidate();
    cand->type = type;
    if(ChannelCandidateIsHost(cand->type)) {
        cand->addr = (char *)malloc(128);
        str::from_ip(cand->addr, 128, &localAddr);
        cand->rtpPort = localRtp;
        cand->rtcpPort = localRtcpPort;
        cand->relatedAddr = NULL;
        cand->relatedRtpPort = 0;
        cand->relatedRtcpPort = 0;
        cand->rtpPriority = rtpPriority;
        cand->rtcpPriority = rtcpPriority;
    }
    else {
        cand->addr = (char *)malloc(128);
        str::from_ip(cand->addr, 128, &relayedAddr);
        cand->rtpPort = relayedRtpPort;
        cand->rtcpPort = relayedRtcpPort;
        cand->relatedAddr = (char *)malloc(128);
        str::from_ip(cand->relatedAddr, 128, &localAddr);
        cand->relatedRtpPort = localRtp;
        cand->relatedRtcpPort = localRtcpPort;
        cand->rtpPriority = rtpPriority;
        cand->rtcpPriority = rtcpPriority;
    }
    cand->foundation = NULL;
    if(media->addIceCandidate(cand)) return true;
    else {
        delete cand;
        return false;
    }
}

void MediaChannel::IceConnected(IPaddr localAddr, word localRtpPort, word localRtcpPort, IPaddr remoteAddr, word remoteRtpPort, word remoteRtcpPort, bool turnCh, byte * fingerprint, int fingerprintLen, bool controlling)
{
    if(ip_match(localAddr, ip_anyaddr) || ip_match(remoteAddr, ip_anyaddr)) {
        iceDtlsDone = true;
        media->connectResult(this, Media::IceFailed, iceTurnError);
    }
    else {
        if(log->LogFlagSet(LOG_ICE)) {
            char addrLocal[128];
            str::from_ip(addrLocal, 128, &localAddr);
            char addrRemote[128];
            str::from_ip(addrRemote, 128, &remoteAddr);
            debug->printf("MediaChannel(%p,%d)::IceConnected %s:%d,%d -> %s:%d,%d remoteSetupRole=%d iceControlling=%d", this, turnCh, addrLocal, localRtpPort, localRtcpPort, addrRemote, remoteRtpPort, remoteRtcpPort, remoteSetupRole, iceControlling);
        }
        iceControlling = controlling;
        oldMediaAddr = remoteAddr;
        oldMediaPort = remoteRtpPort;
        oldMediaRtcpPort = remoteRtcpPort;
        mediaAddr = remoteAddr;
        mediaPort = remoteRtpPort;
        mediaRtcpPort = remoteRtcpPort;
        mediaTurn = turnCh;
        if (fingerprint && fingerprintLen && !dtlsDisabled) {
            bool server = remoteSetupRole == SetupRole::SETUP_ROLE_ACTIVE ? true : remoteSetupRole == SetupRole::SETUP_ROLE_PASSIVE ? false : iceControlling;
            char remoteAddress[128];
            char t[120];
            str::from_ip(t, 120, &remoteAddr);
            if (is_ip4(remoteAddr)) {
                sprintf(remoteAddress, "%s:%u", t, remoteRtpPort);
            }
            else {
                sprintf(remoteAddress, "[%s]:%u", t, remoteRtpPort);
            }
            if(memcmp(&localAddr, &remoteAddr, sizeof(IPaddr))) {
                socket->SetConnectedAddress(remoteAddress);
                socket->SetQoS(mediaType == MediaType::AUDIO ? QOS_VOICE : QOS_AUDIO_VIDEO);
            }
            dtlsRtp->DtlsConnect(server, DTLS_OPTION_USE_SRTP, remoteAddress, socketContext, fingerprint, fingerprintLen, false);
            if(!noRtcp && !rtcpMux) {
                if(is_ip4(remoteAddr)) {
                    sprintf(remoteAddress, "%s:%u", t, remoteRtcpPort);
                }
                else {
                    sprintf(remoteAddress, "[%s]:%u", t, remoteRtcpPort);
                }
                dtlsRtcp->DtlsConnect(server, DTLS_OPTION_USE_SRTP, remoteAddress, socketContext, fingerprint, fingerprintLen, true);
            }
        }
        else {
            iceDtlsDone = true;
            activateMediaConfig();
            media->connectResult(this, Media::Success, iceTurnError);
            media->EventReceived(CHANNEL_MEDIA_WARNING, new class EventMediaError(FAULT_CODE_RTP_SRTP_AUTH_FAILED, "No Encryption", "RTP"));
        }
#if 0 // That was needed in myPBX since we used socket->send(), myApps uses socket->sendTo(), no need to connect the socket!
        if(!mediaTurn) {
            char iceConnectedAddr[128];
            _sprintf(iceConnectedAddr, "%s:%u", remoteAddr, remoteRtpPort);
            socket->Connect(iceConnectedAddr);
            if(rtcpSocket && !rtcpMux) {
                _sprintf(iceConnectedAddr, "%s:%u", remoteAddr, remoteRtcpPort);
                rtcpSocket->Connect(iceConnectedAddr);
            }
        }
#endif
    }
}

void MediaChannel::activateMediaConfig()
{
    if (!disconnecting) {
        mute = false;
        if(mediaType == MediaType::AUDIO || mediaType == MediaType::VIDEO) {
            if (!noRtcp) rtcpTimer->Start(RTCP_PERIOD);
        }
        resetRtcp();

        if (!IP_ANYADDR(mediaAddr) && mediaType == MediaType::AUDIO ) {
            record.Start(mediaAddr, audioCoder, txPayload, dtmfRecvPt, dtmfXmitPt, this);
        }
    }
}

bool MediaChannel::IsSocketClosed(class ISocket * socket)
{
    if(socket == this->socket) return false;
    else if(socket == this->rtcpSocket) return false;
    else if(socket == this->tcpSocket) return false;
    else if(socket == this->tcpAltSocket) return false;
    else if(socket == this->rtcpTcpSocket) return false;
    else if(socket == this->rtpTcpListen) return false;
    else if(socket == this->rtpTcpSocket) return false;
    return true;
}

void MediaChannel::IceSendto(class ISocket * socket, char * data, int len, IPaddr addr, word port, bool turn)
{
    if(log->LogFlagSet(LOG_ICE)) {
        char dstAddr[128];
        str::from_ip(dstAddr, 128, &addr);
        char turnAddr[128];
        if(turn) str::from_ip(turnAddr, 128, &turnRtp.relayedAddr);
        else memset(turnAddr, 0, 128);
        debug->printf("MediaChannel(%p,%p,%d,%d,%d,tcp=%d,closed=%d)::iceSendto %s:%d -> %s:%d len=%d turn=%d", this, socket, (socket == rtcp4()), turnOnly, iceNoHost, iceTurnTcp, IsSocketClosed(socket),
            turn ? turnAddr : iceLocalAddr, (socket == rtcp4()) ? (turn ? turnRtcp.relayedPort : iceLocalRtcpPort) : (turn ? turnRtp.relayedPort : iceLocalPort), dstAddr, port, len, turn);
    }

    if(IsSocketClosed(socket)) return;

    if(rtpOverTcp) {
        if (rtpTcpSocket && ip_match(addr, rtpTcpRemoteAddr) && ((port == 9) || (port == rtpTcpRemotePort))) {
            byte l[2];
            WRITE_WORD_NET(l, len);
            rtpTcpSocket->Send(l, sizeof(l));
            rtpTcpSocket->Send(data, len);
        }
    }
    else if(turn && turnRtp.relayedPort && ((socket == this->socket) || (socket == tcpSocket))) {
        turnRtp.stunSend(data, len, addr, port);
    }
    else if(turn && turnRtcp.relayedPort && ((socket == rtcpSocket) || (socket == rtcpTcpSocket))) {
        turnRtcp.stunSend(data, len, addr, port);
    }
    else if(socket && !turnOnly && (!iceNoHost || !is_private(addr))) {
        socket->SendTo(data, len, addr, port);
    }
}

void MediaChannel::RewriteIceCredentials(const char* usr, const char* pwd)
{
    iceRewriteCredentials(usr, pwd);
}

void MediaChannel::IceInitialized(struct ChannelIce * ice)
{
    if(initComplete) {
        log->Log(LOG_ICE, "MediaChannel(%p): iceInitialized %u iceTurnTcp=%d", this, rtpOverTcp, iceTurnTcp);
        iceComplete = true;
        bool stun = false, turn = false;
        if(ice) {
            ice->common.rtcpMux = rtcpMux;
            strcpy(&ice->common.usr[0], localCfg->ice->iceUfrag);
            strcpy(&ice->common.pwd[0], localCfg->ice->icePwd);
            for(int i=0; i<ice->common.count; i++) {
                if(ice->candidate[i].type == CHANNEL_CANDIDATE_SRFLX) stun = true;
                else if(ice->candidate[i].type == CHANNEL_CANDIDATE_RELAY) turn = true;
            }
            showIceCandidates();
        }
        media->tryMediaInitialized(stun, turn);
    }
}

bool MediaChannel::IceCanNominate()
{
    return media->iceCanNominate();
}

void MediaChannel::SocketConnectComplete(ISocket * const socket)
{
    log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p,%p,%d): SocketConnectComplete", this, socket, iceTurnTcp);

    if(socket == tcpAltSocket) {
        if (!disconnecting && turnRtp.turnAltPort) {
            log->Log(LOG_TURN, "MediaChannel(%p)::SocketConnectComplete Using TurnAlt %s:%d %s %s:%d", this, iceTurnAltAddr, iceTurnAltPort, iceTurnAltHostname, iceLocalAddr, iceLocalPort);
            turnRtp.turnAddr = turnRtp.turnAltAddr;
            turnRtp.turnPort = turnRtp.turnAltPort;
            turnRtp.turnAltAddr = ip_anyaddr;
            turnRtp.turnAltPort = 0;
            free(iceTurnHostname);
            iceTurnHostname = iceTurnAltHostname;
            iceTurnAltHostname = NULL;
            free(iceTurnAddr);
            iceTurnAddr = iceTurnAltAddr;
            iceTurnAltAddr = NULL;
            iceTurnPort = iceTurnAltPort;
            iceTurnAltPort = 0;
            if (iceTurnTcp && tcpSocket) tcpSocket->Shutdown();
            bool t = iceTurnTcp;
            iceTurnTcp = iceTurnAltTcp;
            iceTurnAltTcp = t;
            ISocket *s = tcpAltSocket;
            tcpAltSocket = tcpSocket;
            tcpSocket = s;
        }
    }
    if(socket == tcpSocket && iceTurnTcp) {
        if (!disconnecting) {
            if (turnRtp.turnAltPort) {
                log->Log(LOG_TURN, "MediaChannel(%p)::SocketConnectComplete Cancelling TurnAlt %s:%d %s %s:%d", this, iceTurnAltAddr, iceTurnAltPort, iceTurnAltHostname, iceLocalAddr, iceLocalPort);
                turnRtp.turnAltAddr = ip_anyaddr;
                turnRtp.turnAltPort = 0;
                free(iceTurnAltHostname);
                iceTurnAltHostname = NULL;
                free(iceTurnAltAddr);
                iceTurnAltAddr = NULL;
                iceTurnAltPort = 0;
                if (iceTurnAltTcp && tcpAltSocket) tcpAltSocket->Shutdown();
            }
            turnRtp.turnTcpConnecting();
            if (connectTunnel) {
                log->Log(LOG_TURN, "MediaChannel(%p)::SocketConnectComplete connect tunnel %s:%u", this, tunnelTargetHost, tunnelTargetPort);
                socket->Send(tcpData, _sprintf((char *)tcpData, "CONNECT %s:%u HTTP/1.1\r\nHost: %s:%u\r\nProxy-Connection: keep-alive\r\n\r\n", tunnelTargetHost, tunnelTargetPort, tunnelTargetHost, tunnelTargetPort));
            }
            else {
                turnRtp.startAllocation(TURN_FLAG_TRANSPORT_UDP | TURN_FLAG_EVEN_PORT | TURN_FLAG_RESERVE_NEXTHIGHER_PORT | (iceSlow ? TURN_FLAG_SLOW_SERVER : 0));
            }
            socket->Recv(tcpData, 8192, true);
        }
    }
    else if(socket == rtcpTcpSocket && iceTurnTcp) {
        if (!disconnecting) {
            if (connectTunnelRtcp) {
                log->Log(LOG_TURN, "MediaChannel(%p)::SocketConnectComplete connect tunnel %s", this, tunnelTargetHost);
                socket->Send(rtcpTcpData, _sprintf((char *)rtcpTcpData, "CONNECT %s:%u HTTP/1.1\r\nHost: %s:%u\r\nProxy-Connection: keep-alive\r\n\r\n", tunnelTargetHost, tunnelTargetPort, tunnelTargetHost, tunnelTargetPort));
            }
            else {
                turnRtcp.startAllocation(TURN_FLAG_TRANSPORT_UDP | TURN_FLAG_RESERVATION_TOKEN | (iceSlow ? TURN_FLAG_SLOW_SERVER : 0));
            }
            socket->Recv(rtcpTcpData, 8192, true);
        }
    }
    socket->SetQoS(mediaType == MediaType::AUDIO ? QOS_VOICE : QOS_AUDIO_VIDEO);
}

void MediaChannel::SocketBindResult(ISocket * const socket, const char * localAddr, word localPort)
{
    log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p,%p,%s,tcp=%d)::SocketBindResult %s:%d", this, socket, (socket == rtcpSocket) ? "RTCP" : (socket == this->mcSocket) ? "MC" : "RTP", iceTurnTcp, localAddr, localPort);
    if(this->mcSocket == socket) {
        iceDtlsDone = true;
        if(!localPort) return media->connectResult(this, Media::NotReachable, iceTurnError);
        socket->Recv(data, 8192, true);
        return media->connectResult(this, Media::Success, iceTurnError);
    }
    else if(this->socket == socket) {
        if(localPort) {
            localAddrStorage = str::to_ip(localAddr);
            this->iceLocalPort = localPort;
            waitBindRtp = false;
            socket->Recv(data, 8192, true);
            if(rtcpSocket) {
                startPort = localPort+1;
                rtcpSocket->Bind(iceLocalAddr, startPort);
            }
        }
        else if(startPort < maxPort) {
            startPort = startPort+1;
            socket->Bind(iceLocalAddr, startPort);
        }
        else {
            waitBindRtp = false;
            waitBindRtcp = false;
            rtpBindFailed = true;
            rtcpBindFailed = true;
            iceComplete = true;
        }
    }
    else if(this->rtcpSocket == socket) {
        if(localPort) {
            this->iceLocalRtcpPort = localPort;
            waitBindRtcp = false;
            rtcpSocket->Recv(rtcpData, 8192, true);
        }
        else if(startPort < maxPort) {
            // If RTCP fails, then RTP must be closed, reopen and bound
            waitBindRtp = true;
            if(this->socket) this->socket->Shutdown();
            startPort = startPort+1;
            if(startPort & 0x1) startPort = startPort+1;
        }
        else {
            waitBindRtcp = false;
            rtcpBindFailed = true;
            iceComplete = true;
        }
    }
    else if(this->rtpTcpListen == socket) {
        localAddrStorage = str::to_ip(localAddr);
        this->iceLocalPort = localPort;
        rtpTcpListen->Listen();
        waitBindRtp = false;
    }
    tryInitialized();
}

void MediaChannel::SocketListenResult(ISocket * const socket, const char * remoteAddr, word remotePort)
{
    log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p,%p)::SocketListenResult %s:%d", this, socket, remoteAddr, remotePort);
    if(this->rtpTcpListen == socket) {
        if (disconnecting) {
            this->rtpTcpListen->Accept(NULL);
        }
        else {
            if (this->rtpTcpSocket) {
                this->rtpTcpSocket->Shutdown();
            }
            rtpTcpRemoteAddr = str::to_ip(remoteAddr);
            this->rtpTcpRemotePort = remotePort;
            this->rtpTcpListen->Accept(this);
        }
    }
}

void MediaChannel::SocketAcceptComplete(ISocket * const socket)
{
    log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p,%p): SocketAcceptComplete", this, socket);

    this->rtpTcpSocket = socket;
    if (disconnecting) {
        socket->Shutdown();
    }
    else {
        socket->Recv(rtpTcpLen, sizeof(rtpTcpLen));
    }
}

void MediaChannel::SocketShutdown(ISocket * const socket, shutdownreason_t reason)
{
    log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p,%p,%d,%d)::SocketShutdown %s %d (%p,%p,%p)", this, socket, waitBindRtp, waitBindRtcp, (socket == rtcpSocket) ? "RTCP" : (socket == rtcpTcpSocket) ? "TCP-RTCP" : (socket == tcpSocket) ? "TCP" : (socket == mcSocket) ? "MC" : "RTP", reason, this->closeRtp, this->closeRtcp, this->closeMc);
    delete socket;
    // RTP and RTCP not consecutive, RTP was closed and must be bound again.
    if(!disconnecting && this->socket == socket && waitBindRtp && waitBindRtcp) {
        this->socket = udpSocketProvider->CreateSocket(iomux, this, log, this->ipv6);
        this->socket->Bind(iceLocalAddr, startPort);
        return;
    }
    if(this->mcSocket == socket) {
        this->mcSocket = NULL;
        media->channelDisconnected(this);
        return;
    }
    if(this->socket == socket) this->socket = NULL;
    else if(this->rtcpSocket == socket) this->rtcpSocket = NULL;
    else if(this->rtpTcpListen == socket) this->rtpTcpListen = NULL;
    else if(this->rtpTcpSocket == socket) this->rtpTcpSocket = NULL;
    else if(this->tcpSocket == socket) {
        this->tcpSocket = NULL;
        if (!turnRtp.turnAltPort || (iceTurnAltTcp && !this->tcpAltSocket)) turnRtp.turnTcpShutdown();
    }
    else if(this->tcpAltSocket == socket) {
        this->tcpAltSocket = NULL;
        if (!turnRtp.turnPort || (iceTurnTcp && !this->tcpSocket)) turnRtp.turnTcpShutdown();
    }
    else if(this->rtcpTcpSocket == socket) {
        this->rtcpTcpSocket = NULL;
        turnRtcp.turnTcpShutdown();
    }

    if(!this->mcSocket && !this->socket && !this->rtcpSocket && !this->tcpSocket && !this->tcpAltSocket && !this->rtcpTcpSocket && !this->rtpTcpListen && !this->rtpTcpSocket && !this->closeRtp && !this->closeRtcp && !this->closeMc && !this->reqStun1 && !this->reqStun2 && !this->reqStun3 && !this->reqStun4 && !this->reqTurn1 && !this->reqTurn2 && !record.On() && !execCnt) media->channelDisconnected(this);
}

void MediaChannel::SocketSendResult(ISocket * const socket)
{
    //if (mediaType == MediaType::AUDIO) log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p,%p)::SocketSendResult %s", this, socket, (socket == rtcpSocket) ? "RTCP" : (socket == rtcpTcpSocket) ?  "TCP-RTCP" : (socket == tcpSocket) ?  "TCP" : "RTP");

    if (media) {
        if (mediaType == MediaType::APPSHARING) media->SctpSendResult();
        else media->RtpSendResult();
    }
}

void MediaChannel::MediaSend(const void * buf, size_t len, dword timestamp, unsigned num, enum MediaType mediaType, bool isConference)
{
    if(endpoint) endpoint->Send((char *)buf, len, timestamp, num, mediaType, isConference);
}

void MediaChannel::SocketRecvResult(ISocket * const socket, void * buf, size_t len)
{
    size_t msgLength;
    log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p,%p)::SocketRecvResult recv=%d %s (%d,%d) (%x,%x)", this, socket, len, (socket == rtcpSocket) ? "RTCP" : (socket == rtcpTcpSocket) ? "TCP-RTCP" : (socket == tcpSocket) ? "TCP" : "RTP", indexTcpBuffer, indexTcpBufferRtcp, tcpData ? tcpData[0] : 0, rtcpTcpData ? rtcpTcpData[0] : 0);

    if(disconnecting) return;

    if(socket == this->tcpSocket) {
        indexTcpBuffer += len;
        if (connectTunnel) {
            msgLength = 0;
            while ((msgLength + 4 <= indexTcpBuffer) && ((tcpData[msgLength] != '\r') || (tcpData[msgLength + 1] != '\n') || (tcpData[msgLength + 2] != '\r') || (tcpData[msgLength + 3] != '\n'))) {
                msgLength++;
            }
            if (msgLength + 4 <= indexTcpBuffer) {
                unsigned responseCode = 0;
                if (!str::n_casecmp((const char *)tcpData, "HTTP/", 5)) {
                    unsigned i = 5;
                    while ((i < msgLength) && (tcpData[i] != ' ')) {
                        i++;
                    }
                    while ((i < msgLength) && (tcpData[i] == ' ')) {
                        i++;
                    }
                    responseCode = atoi((const char *)(&tcpData[i]));
                }
                connectTunnel = false;
                if ((responseCode >= 200) && (responseCode < 300)) {
                    log->Log(LOG_TURN, "MediaChannel(%p)::SocketRecvResult connect tunnel response %u", this, responseCode);
                    turnRtp.startAllocation(TURN_FLAG_TRANSPORT_UDP | TURN_FLAG_EVEN_PORT | TURN_FLAG_RESERVE_NEXTHIGHER_PORT | (iceSlow ? TURN_FLAG_SLOW_SERVER : 0));
                }
                else {
                    log->Log(LOG_TURN, "MediaChannel(%p)::SocketRecvResult unexpected tunnel response %.*s", this, msgLength, tcpData);
                    socket->Shutdown();
                }
                indexTcpBuffer -= msgLength + 4;
                for(size_t i = 0, j = msgLength + 4; i < indexTcpBuffer; i++, j++) {
                    tcpData[i] = tcpData[j];
                }
            }
        }
        if (!connectTunnel) {
            while((msgLength = turnRtp.turnTcpParse(tcpData, indexTcpBuffer)) != 0) {
                mediaRecvEvent(socket, tcpData, msgLength, turnRtp.turnAddr, turnRtp.turnPort);
                // Copy remaining bits
                indexTcpBuffer -= msgLength;
                for(size_t i = 0, j = msgLength; i < indexTcpBuffer; i++, j++) {
                    tcpData[i] = tcpData[j];
                }
            }
        }
        socket->Recv(&tcpData[indexTcpBuffer], 8192 - indexTcpBuffer, true);
    }
    else if(socket == this->rtcpTcpSocket) {
        indexTcpBufferRtcp += len;
        if (connectTunnelRtcp) {
            msgLength = 0;
            while ((msgLength + 4 <= indexTcpBufferRtcp) && ((rtcpTcpData[msgLength] != '\r') || (rtcpTcpData[msgLength + 1] != '\n') || (rtcpTcpData[msgLength + 2] != '\r') || (rtcpTcpData[msgLength + 3] != '\n'))) {
                msgLength++;
            }
            if (msgLength + 4 <= indexTcpBufferRtcp) {
                unsigned responseCode = 0;
                if (!str::n_casecmp((const char *)rtcpTcpData, "HTTP/", 5)) {
                    unsigned i = 5;
                    while ((i < msgLength) && (rtcpTcpData[i] != ' ')) {
                        i++;
                    }
                    while ((i < msgLength) && (rtcpTcpData[i] == ' ')) {
                        i++;
                    }
                    responseCode = atoi((const char *)(&rtcpTcpData[i]));
                }
                connectTunnelRtcp = false;
                if ((responseCode >= 200) && (responseCode < 300)) {
                    log->Log(LOG_TURN, "MediaChannel(%p)::SocketRecvResult connect tunnel response %u", this, responseCode);
                    turnRtcp.startAllocation(TURN_FLAG_TRANSPORT_UDP | TURN_FLAG_RESERVATION_TOKEN | (iceSlow ? TURN_FLAG_SLOW_SERVER : 0));
                }
                else {
                    log->Log(LOG_TURN, "MediaChannel(%p)::SocketRecvResult unexpected tunnel response %.*s", this, msgLength, rtcpTcpData);
                    socket->Shutdown();
                }
                indexTcpBufferRtcp -= msgLength + 4;
                for(size_t i = 0, j = msgLength + 4; i < indexTcpBufferRtcp; i++, j++) {
                    rtcpTcpData[i] = rtcpTcpData[j];
                }
            }
        }
        if (!connectTunnel) {
            while((msgLength = turnRtcp.turnTcpParse(rtcpTcpData, indexTcpBufferRtcp)) != 0) {
                mediaRtcpRecvEvent(socket, rtcpTcpData, msgLength, turnRtcp.turnAddr, turnRtcp.turnPort);
                indexTcpBufferRtcp -= msgLength;
                // Copy remaining bits
                for(size_t i = 0, j = msgLength; i < indexTcpBufferRtcp; i++, j++) {
                    rtcpTcpData[i] = rtcpTcpData[j];
                }
            }
        }
        socket->Recv(&rtcpTcpData[indexTcpBufferRtcp], 8192 - indexTcpBufferRtcp, true);
    }
    else if(socket == this->rtpTcpSocket) {
        if (buf == rtpTcpLen) {
            if (READ_WORD_NET(rtpTcpLen) != 0) {
                socket->Recv(data, READ_WORD_NET(rtpTcpLen));
            }
            else {
                socket->Recv(rtpTcpLen, sizeof(rtpTcpLen));
            }
        }
        else {
            mediaRecvEvent(socket, buf, len, rtpTcpRemoteAddr, rtpTcpRemotePort);
            socket->Recv(rtpTcpLen, sizeof(rtpTcpLen));
        }
    }
}

void MediaChannel::SocketRecvFromResult(ISocket * const socket, void * buf, size_t len, IPaddr srcAddr, word srcPort)
{
    //log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p,%p,%d)::SocketRecvFromResult recv=%d %s Media=%s", this, socket, disconnecting, len, (socket == rtcpSocket) ? "RTCP" : (socket == rtcpTcpSocket) ? "TCP-RTCP" : (socket == tcpSocket) ? "TCP" : (socket == mcSocket) ? "MC" : "RTP", ::mediaType(mediaType));

    if(disconnecting) return;

    if(socket == this->socket || socket == this->mcSocket) {
        if(len) mediaRecvEvent(socket, buf, len, srcAddr, srcPort);
        socket->Recv(data, 8192, true);
    }
    else if(socket == this->rtcpSocket) {
        if(len) mediaRtcpRecvEvent(socket, buf, len, srcAddr, srcPort);
        socket->Recv(rtcpData, 8192, true);
    }
    else log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p,%p,%d)::SocketRecvFromResult recv=%d %s Media=%s", this, socket, disconnecting, len, (socket == rtcpSocket) ? "RTCP" : (socket == rtcpTcpSocket) ? "TCP-RTCP" : (socket == tcpSocket) ? "TCP" : (socket == mcSocket) ? "MC" : "RTP", ::mediaType(mediaType));
}

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

void MediaChannel::EventReceived(enum MediaEndpointEvent event, void* ctx)
{
    if (media) media->EventReceived(event, ctx);
}

void MediaChannel::RtcpSendFirRequest(bool retransmission)
{
    byte buf[20];

    if(!retransmission) debug->printf("MediaChannel(%p)::SendFullIntraRequest %d retx=%d timer=%d", this, firCommandSequence, retransmission, rtxRtcpTimer->IsRunning());

    if (rtxRtcpTimer->IsRunning()) rtxRtcpTimer->Cancel();

    // https://tools.ietf.org/html/rfc5104#section-4.3.1
    // RTP/AVPF RFC4585 RFC5104
    buf[0] = 0x84;  // V=2, FMR=4 (FIR - Full Intra Request)
    buf[1] = 0xce;  // PT=206 (PSFB - Payload-specific feedback message)
    buf[2] = 0x00;  // length is 4 dwords
    buf[3] = 0x04;
    // SSRCs
    WRITE_DWORD_NET(&buf[4], ssrc);                 // our ssrc
    WRITE_DWORD_NET(&buf[8], 0);                    // peer ssrc, is not used and SHALL be set to 0
    WRITE_DWORD_NET(&buf[12], remoteSsrc);          // ssrc the FIR is asserted for

    buf[16] = retransmission ? firCommandSequence - 1 : firCommandSequence;   // Full intra request command sequence
    buf[17] = 0x00;  // reserved
    buf[18] = 0x00;  // reserved
    buf[19] = 0x00;  // reserved

    RtcpSend((char*)buf, 20);

    if (retransmission == false) {
        resendFirCommandSequenceSet = true;
        firCommandSequence++;
        resendFirCounter = 0;
    }

    rtxRtcpTimer->Start(100);
}

void MediaChannel::SendRtcpNack(word sequence, short sequenceNumberDiff, bool retransmission)
{
    if (sequenceNumberDiff < 1) return;

    byte buf[16];

    debug->printf("MediaChannel(%p)::SendRtcpNack seq=%d diff=%d retx=%d timer=%d", this, sequence, sequenceNumberDiff, retransmission, rtxRtcpTimer->IsRunning());

    if (rtxRtcpTimer->IsRunning()) rtxRtcpTimer->Cancel();

    buf[0] = 0x81;  // V=2, FMT=1 (Generic NACK)
    buf[1] = 0xcd;  // PT=205 (PSFB - Payload-specific feedback message)
    buf[2] = 0x00;  // length is 4 dwords
    buf[3] = 0x03;

    // SSRCs
    WRITE_DWORD_NET(&buf[4], ssrc);                 // our ssrc
    WRITE_DWORD_NET(&buf[8], remoteSsrc);           // peer ssrc

    // Feedback Control Information (FCI):
    WRITE_WORD_NET(&buf[12], sequence);          // PID
    word bitMask = 0;
    for (int i = 1; i < MIN(16, sequenceNumberDiff); i++) bitMask |= 1 << (i-1);
    WRITE_WORD_NET(&buf[14], bitMask);          // PID

    RtcpSend((char*)buf, 16);

    if (retransmission == false) {
        resendSequence = sequence;
        resendSequenceNumberDiff = sequenceNumberDiff;
        resendNackCounter = 0;
    }

    rtxRtcpTimer->Start(100);
}

void MediaChannel::mediaRtcpRecvEvent(ISocket * const socket, void * data, size_t len, IPaddr srcAddr, word srcPort)
{
    int msgLen = len;
    char * chBuf = (char *)data;
    char * buf = chBuf;
    bool authSuccess = true;
    bool addressOk = (ip_match(mediaAddr, srcAddr) && (rtcpMux ? mediaPort == srcPort : mediaRtcpPort == srcPort));
    word turnRtcpCh = 0;
    IPaddr src = srcAddr;
    word port = srcPort;
    log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p,%p,%d,%d)::mediaRtcpRecvEvent recv=%d ch0=0x%x %s Media=%s", this, socket, rtcpMux, addressOk, len, READ_WORD_NET(chBuf), (socket == rtcpSocket) ? "RTCP" : (socket == rtcpTcpSocket) ? "TCP-RTCP" : (socket == tcpSocket) ? "TCP" : (socket == mcSocket) ? "MC" : "RTP", ::mediaType(mediaType));
    switch (chBuf[0] & 0xc0) {
        case 0x40:
            {
                word turnChannelNumber = READ_WORD_NET(&chBuf[0]) - 0x4000;
                word channelDataLength = READ_WORD_NET(&chBuf[2]);
                if (turnChannelNumber < turnRtp.peerCount) {
                    int padding = (-((int)channelDataLength) & 0x3);
                    if ((((int)channelDataLength) == ((int)(len - 4))) || (((int)channelDataLength) == ((int)(len - 4 - padding)))) {
                        msgLen -= 4;
                        buf = &chBuf[4];
                        if (((int)channelDataLength) != msgLen) {
                            msgLen -= padding;
                        }
                        src = turnRtcp.peerTable[turnChannelNumber].addr;
                        port = turnRtcp.peerTable[turnChannelNumber].port;
                        addressOk = (ip_match(mediaAddr, src) && (rtcpMux ? mediaPort == port : mediaRtcpPort == port));
                        turnRtcpCh = READ_WORD_NET(&chBuf[0]);
                    }
                }
            }
        case 0x00:
            if (turnRtcp.iceTurnRecv(rtcpSocket, src, port, localAddrStorage, iceLocalRtcpPort, buf, msgLen)) {
                return;
            }
            if (iceStunRecv(rtcpSocket, src, port, turnRtcpCh != 0 ? turnRtcp.relayedAddr : localAddrStorage, turnRtcpCh != 0 ? turnRtcp.relayedPort : iceLocalRtcpPort, buf, msgLen, turnRtcpCh)) {
                return;
            }
            if (dtlsRtcp->DtlsRecv(buf, msgLen, src, port)) {
                return;
            }
            break;
    }

    if (addressOk) {
        class ISrtpSession* srtpRx = rtcpMux ? srtpRecv : srtcpRecv;

        if(srtpRx) {
            if((msgLen = srtpRx->SrtcpUnprotect((char *)buf, msgLen)) == 0) {
                log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p)::mediaRtcpRecvEvent Error: SRTCP authentication failed", this);
                authSuccess = false;
            }
        }
        if(authSuccess) {
            byte * rtcpHdr = (byte *)data;

            if (msgLen > 8 && rtcpHdr[0] & 0x20) {
                int paddingLength = rtcpHdr[msgLen - 1];
                if (paddingLength < msgLen)
                    msgLen -= paddingLength;
            }

            int index = 0;
            while (index + 4 <= msgLen) {
                int len = READ_WORD_NET(&rtcpHdr[index + 2]) * 4 + 4;
                if (index + len > msgLen) break;

                switch (rtcpHdr[index + 1]) {
                case RTCP_PACKET_TYPE_SR:
                {
                    int report;

                    log->Log(LOG_MEDIA_CHANNEL, "RTCP RR-SR type=%x len=%d", rtcpHdr[index + 1], len);

                    dword t = getTimestamp8kHz();
                    if (rtcpHdr[index+1] == 0xc8) {
                        lsr = READ_DWORD_NET(&rtcpHdr[index+10]);
                        lsrTimestamp = t;
                        report = 28;
                    }
                    else {
                        report = 8;
                    }

                    // if RC >0 calculate round trip
                    if ((rtcpHdr[index+0] &0x1f) && READ_DWORD_NET(&rtcpHdr[index+report+16])) {
                        dword roundTrip = timestamp2ntp32(t - startTimestamp) - READ_DWORD_NET(&rtcpHdr[index+report+16]) - READ_DWORD_NET(&rtcpHdr[index+report+20]);
                        dword jitter = READ_DWORD_NET(&rtcpHdr[index+report+12]);
                        dword loss = READ_DWORD_NET(&rtcpHdr[index+report+4]) &0xffffff;

                        dword roundTripDelta = roundTrip>lastRoundTripT38Pages? roundTrip-lastRoundTripT38Pages:lastRoundTripT38Pages-roundTrip;
                        dword jitterDelta = jitter>lastJitterT38Speed? jitter-lastJitterT38Speed:lastJitterT38Speed-jitter;

                        if(roundTripDelta>32 || jitterDelta>4 || lastLoss!=loss) {
                            lastRoundTripT38Pages = roundTrip;
                            lastJitterT38Speed = jitter;
                            lastLoss = loss;
                            media->EventReceived(CHANNEL_MEDIA_STATISTICS, new class EventMediaStatistics(true, loss, jitter, roundTrip));
                        }
                    }
                    break;
                }
                case RTCP_PACKET_TYPE_RR:
                case RTCP_PACKET_TYPE_SDES:
                    break;
                case RTCP_PACKET_TYPE_PSFB:
                {
                    int fbMsg = rtcpHdr[index + 0] & 0x1f;
                    switch (fbMsg) {
                    case 1:
                        log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p)::mediaRtcpRecvEventRTCP PSFB PLI type=%x len=%d", this, rtcpHdr[index+1], len);
                        media->EventReceived(CHANNEL_PACKET_LOST_INDICATION, NULL);
                        break;
                    case 2:
                        log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p)::mediaRtcpRecvEventRTCP PSFB SLI type=%x len=%d", this, rtcpHdr[index+1], len);
                        media->EventReceived(CHANNEL_PACKET_LOST_INDICATION, NULL);
                        break;
                    case 3:
                        log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p)::mediaRtcpRecvEventRTCP PSFB RPSI type=%x len=%d", this, rtcpHdr[index+1], len);
                        break;
                    case 4:
                    {
                        log->Log(LOG_MEDIA_CHANNEL, "RTCP PSFB FIR type=%x len=%d", rtcpHdr[index+1], len);
                        if ((msgLen >= 4+4*READ_WORD_NET(&rtcpHdr[index+2])) && (READ_WORD_NET(&rtcpHdr[index+2]) >= 2)) {
                            for (int i = 0; 2 + 2 + 2*i <= READ_WORD_NET(&rtcpHdr[index+2]); i++) {
                                if ((rtcpHdr[index+16 + 8*i] != rFirCommandSequence) || !rFirCommandSequenceSet) {
                                    log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p)::mediaRtcpRecvEvent Full Intra Request received", this);
                                    rFirCommandSequence = rtcpHdr[index + 16 + 8*i];
                                    rFirCommandSequenceSet = true;
                                    media->EventReceived(CHANNEL_FULL_INTRA_REQUEST, NULL);
                                }
                            }
                        }
                    }
                    break;
                    case 15:
                        if (rtcpHdr[index + 12] == 0x52 && rtcpHdr[index + 13] == 0x45 && rtcpHdr[index + 14] == 0x4D && rtcpHdr[index + 15] == 0x42) {  // Receiver Estimated Max Bitrate (REMB)
                            int exp = rtcpHdr[index + 17];
                            int mantissa = READ_WORD_NET(&rtcpHdr[index + 18]);
                            log->Log(LOG_MEDIA_CHANNEL, "RTCP PSFB Application layer FB (AFB) message REMB %d", mantissa << exp);
                        }
                        break;
                    default:
                        log->Log(LOG_MEDIA_CHANNEL, "RTCP PSFB Unknown Message type=%d", fbMsg);
                    };
                }
                break;
                case RTCP_PACKET_TYPE_RTPFB:
                {
                    int tlfbMsg = rtcpHdr[index + 0] & 0x1f;
                    dword srcSsrc = READ_DWORD_NET(&rtcpHdr[index + 4]);
                    dword dstSsrc = READ_DWORD_NET(&rtcpHdr[index + 8]);
                    switch (tlfbMsg) {
                    case 1:
                        log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p)::mediaRtcpRecvEvent RTCP Transport layer FB NACK message len=%d ssrc=%x(%x) ssrc=%x(%x) PID=%d MASK=%x",
                              this, len, srcSsrc, remoteSsrc, dstSsrc, ssrc, READ_WORD_NET(&rtcpHdr[index + 12]), READ_WORD_NET(&rtcpHdr[index + 14]));
                        if (srcSsrc == remoteSsrc || dstSsrc == ssrc) {
                            media->EventReceived(CHANNEL_NACK_MESSAGE, &rtcpHdr[index + 12]);
                        }
                        break;
                    default:
                        log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p)::mediaRtcpRecvEvent RTCP Transport layer FB Unknown message len=%d type=%d", this, len, tlfbMsg);
                    };
                }
                break;
                case RTCP_PACKET_TYPE_XR:
                    log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p)::mediaRtcpRecvEvent RTCP Extended Report len=%d", this, len);
                    // RFC-3611: XR packets are composed of report blocks,
                    // and seven block types are defined here. The purpose of the
                    // extended reporting format is to convey information that supplements
                    // the six statistics that are contained in the report blocks used by
                    // RTCP's Sender Report (SR) and Receiver Report (RR) packets.
                    //  1  Loss RLE Report Block
                    //  2  Duplicate RLE Report Block
                    //  3  Packet Receipt Times Report Block
                    //  4  Receiver Reference Time Report Block
                    //  5  DLRR Report Block
                    //  6  Statistics Summary Report Block
                    //  7  VoIP Metrics Report Block
                    break;
                case RTCP_PACKET_TYPE_BYE:
                {
                    log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p)::mediaRtcpRecvEvent RTCP Bye message len=%d", this, len);
                }
                break;
                default:
                    log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p)::mediaRtcpRecvEvent unknown RTCP Message %x", this, rtcpHdr[index + 1]);
                };
                index += len;
            }
        }

        if(!authSuccess && !errorReported) {
            byte* rtcpHdr = (byte*)data;

            // check, if it's valid RTCP at all before generating error (#74903)
            if (msgLen >= 8 && ((rtcpHdr[0] & 0xC0) == 0x80) && (rtcpHdr[1] >= 200) && rtcpHdr[1] <= 204) {
                errorReported = true;
                media->EventReceived(CHANNEL_MEDIA_ERROR, new class EventMediaError(FAULT_CODE_RTP_SRTCP_AUTH_FAILED, "SRTCP authentication failed", "RTP"));
            }
        }

    }

    if (!authSuccess && !errorReported) {
        byte * rtcpHdr = (byte *)data;
        // check, if it's valid RTCP at all before generating error (#74903)
        if (msgLen>=8 && ((rtcpHdr[0]&0xC0)==0x80) && (rtcpHdr[1]>=200) && rtcpHdr[1]<=204) {
            errorReported = true;
            log->Log(LOG_MEDIA, "MediaChannel(%p)::mediaRtcpRecvEvent Error: SRTCP authentication failed", this);
            //medialib->media_error(FAULT_CODE_RTP_SRTCP_AUTH_FAILED,"SRTCP authentication failed","RTP");
        }
    }
}

void MediaChannel::mediaRecvEvent(ISocket * const socket, void * data, size_t len, IPaddr srcAddr, word srcPort)
{
    int msgLen = len;
    char * chBuf = (char *)data;
    char * buf = chBuf;
    word turnRtpCh = 0;
    IPaddr src = srcAddr;
    word port = srcPort;
    log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p,%p)::mediaRecvEvent recv=%d ch0=0x%x %s Media=%s", this, socket, len, READ_WORD_NET(chBuf), (socket == rtcpSocket) ? "RTCP" : (socket == rtcpTcpSocket) ? "TCP-RTCP" : (socket == tcpSocket) ? "TCP" : (socket == mcSocket) ? "MC" : "RTP", ::mediaType(mediaType));
    switch (chBuf[0] & 0xc0) {
        case 0x40:
            {
                word turnChannelNumber = READ_WORD_NET(&chBuf[0]) - 0x4000;
                word channelDataLength = READ_WORD_NET(&chBuf[2]);
                if (turnChannelNumber < turnRtp.peerCount) {
                    int padding = (-((int)channelDataLength) & 0x3);
                    if ((((int)channelDataLength) == ((int)(len - 4))) || (((int)channelDataLength) == ((int)(len - 4 - padding)))) {
                        msgLen -= 4;
                        buf = &chBuf[4];
                        if (((int)channelDataLength) != msgLen) {
                            msgLen -= padding;
                        }
                        src = turnRtp.peerTable[turnChannelNumber].addr;
                        port = turnRtp.peerTable[turnChannelNumber].port;
                        turnRtpCh = READ_WORD_NET(&chBuf[0]);
                        if (chBuf[4] & 0x80) {
                            break;
                        }
                    }
                }
            }
        case 0x00:
            if (turnRtp.isTurnPacket(buf, msgLen)) {
                if (turnRtp.turnAltPort && ip_match(src, turnRtp.turnAltAddr) && (port == turnRtp.turnAltPort)) {
                    log->Log(LOG_TURN, "MediaChannel(%p)::mediaRecvEvent Using TurnAlt %s:%d %s %s:%d", this, iceTurnAltAddr, iceTurnAltPort, iceTurnAltHostname, iceLocalAddr, iceLocalPort);
                    turnRtp.turnAddr = turnRtp.turnAltAddr;
                    turnRtp.turnPort = turnRtp.turnAltPort;
                    turnRtp.turnAltAddr = ip_anyaddr;
                    turnRtp.turnAltPort = 0;
                    free(iceTurnHostname);
                    iceTurnHostname = iceTurnAltHostname;
                    iceTurnAltHostname = NULL;
                    free(iceTurnAddr);
                    iceTurnAddr = iceTurnAltAddr;
                    iceTurnAltAddr = NULL;
                    iceTurnPort = iceTurnAltPort;
                    iceTurnAltPort = 0;
                    if (iceTurnTcp && tcpSocket) tcpSocket->Shutdown();
                    bool t = iceTurnTcp;
                    iceTurnTcp = iceTurnAltTcp;
                    iceTurnAltTcp = t;
                    ISocket *s = tcpAltSocket;
                    tcpAltSocket = tcpSocket;
                    tcpSocket = s;
                }
                if (turnRtp.turnAltPort && ip_match(src, turnRtp.turnAddr) && (port == turnRtp.turnPort)) {
                    log->Log(LOG_TURN, "MediaChannel(%p)::mediaRecvEvent Cancelling TurnAlt %s:%d %s %s:%d", this, iceTurnAltAddr, iceTurnAltPort, iceTurnAltHostname, iceLocalAddr, iceLocalPort);
                    turnRtp.turnAltAddr = ip_anyaddr;
                    turnRtp.turnAltPort = 0;
                    free(iceTurnAltHostname);
                    iceTurnAltHostname = NULL;
                    free(iceTurnAltAddr);
                    iceTurnAltAddr = NULL;
                    iceTurnAltPort = 0;
                    if (iceTurnAltTcp && tcpAltSocket) tcpAltSocket->Shutdown();
                }
                if (turnRtp.iceTurnRecv(socket, src, port, localAddrStorage, iceLocalPort, buf, msgLen)) {
                    return;
                }
            }
            if (iceStunRecv(socket, src, port, turnRtpCh != 0 ? turnRtp.relayedAddr : localAddrStorage, turnRtpCh != 0 ? turnRtp.relayedPort : iceLocalPort, buf, msgLen, turnRtpCh)) {
                return;
            }
            if (dtlsRtp->DtlsRecv(buf, msgLen, src, port)) {
                return;
            }
            break;
    }

    byte *rtpHeader = (byte *)buf;

    // sort out RTCP packets in case of RTCP-MUX
    if (rtcpMux && (200 <= rtpHeader[1] && rtpHeader[1] <= 206)) {
        if (rtcpSocket) {
            mediaRtcpRecvEvent(rtcpSocket, buf, msgLen, src, port);
        }
        return;
    }

    if(!endpoint || sendonly || mute) {
        return;
    }

    if (msgLen < 12 || (rtpHeader[0] & 0xc0) != 0x80) {
        return;
    }

    if(dropMediaRx) {
        numAllowPacketsRx--;
        if(dropSeveralRx || (numAllowPacketsRx == 0)) {
            debug->printf("MediaChannel(%p)::mediaRecvEvent drop message len=%d seq=%u numAllowPackets=%d dropSeveral=%d", this, len, READ_WORD_NET(&rtpHeader[2]), numAllowPacketsRx, dropSeveralRx);
            if (dropSeveralRx) {
                dropSeveralRx--;
                return;
            }
            dropSeveralRx = (IRandom::GetRandom() % 4);
            dropOffsetRx = dropSeveralRx + 1;
            numAllowPacketsRx = (100 / dropMediaRx) * dropSeveralRx;
            return;
        }
    }

    if(srtpRecv) {
#ifdef MYPBX_SRTP_DEC
        byte * b_tmp = pSrtp->srtp_decrypt_msg(&srtp_state, (byte *)buf, msgLen, &msgLen);
        if(b_tmp) {
            debug->printf("Data Unprotected %x,%x,%x", b_tmp[12], b_tmp[13], b_tmp[14], b_tmp[28], b_tmp[29]);
            memcpy(buf, b_tmp, msgLen);
            free(b_tmp);
        }
        else {
            log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p)::mediaRecvEvent Error:SRTP authentication failed", this);
            return;
        }
#else
        if((msgLen = srtpRecv->Unprotect(buf, msgLen)) == 0) {
            debug->printf("MediaChannel(%p)::mediaRecvEvent Error:SRTP authentication failed len=%d", this, len);
            if(++rAuthFailed>=10 && !errorReported) {
                errorReported = true;
                log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p)::mediaRecvEvent Error:SRTP authentication failed", this);
                media->EventReceived(CHANNEL_MEDIA_ERROR, new class EventMediaError(FAULT_CODE_RTP_SRTP_AUTH_FAILED, "SRTP authentication failed", "RTP"));
            }
            return;
        }
        else {
            if(rAuthFailed<10) rAuthFailed = 0;
        }
#endif
    }

    word seq;
    int seqDelta;
    dword rTimestamp;
    dword sTimestamp;
    dword rSsrc;
    int d;
    bool marker;

    if(rtpHeader[0] & 0x20) {
        byte paddingLen = rtpHeader[msgLen-1];
        if (paddingLen > msgLen) {
            return;
        }
        msgLen -= paddingLen;
        rtpHeader[0] &= 0xdf;
    }

    int offset = (12+(rtpHeader[0]&0x0f)*4);
    msgLen -= offset;
    seq = READ_WORD_NET(&rtpHeader[2]);

    rSsrc = READ_DWORD_NET(&rtpHeader[8]);

    if(redirectCounter) {
            // Unexpected Source?
        if(!ip_match(mediaAddr, src) && mediaPort == port /* && !mc_port*/) {
            char maddr[128];
            str::from_ip(maddr, 128, &mediaAddr);
            char daddr[128];
            str::from_ip(daddr, 128, &src);
            debug->printf("mediaAddr changed %s:%d %s:%d", maddr, mediaPort, daddr, port);
            if(!doNatWorkaround(src, port, rSsrc)) {
                return;
            }
        }
        else {
                // if RTP was received from the expected address and no redirection took place
                // turn off check for NAT workaround
            if(!redirectDone) redirectCounter = 0;
        }
    }

    if(rSsrc != remoteSsrc) {
        resetRtcp();
        remoteSsrc = rSsrc;
    }

    if(!rSeq) rSeq = seq-1;

    rTotal++;
    seqDelta = (int16)(seq-(word)(rSeq&0xffff));
    if(seqDelta!=1 && !(rtpHeader[1] &0x80)) {
        //debug->printf("MediaChannel(%p)::mediaRecvEvent lost sequence %d,%d delta=%d mediaType=%d", this, seq, rSeq, seqDelta, mediaType);
        if(seqDelta>1) {
            // 24.03.2021 @sma: Avoid sending RTCP NACK Messages since Browser resends lost packets
            // We should get track of receiving sequences to avoid sending new NACKs
            // video_endpoint already sents PLIs or FIRs depending on the lost packet
            //if(mediaType == MediaType::VIDEO || mediaType == MediaType::VIDEO_APPSHARING) SendRtcpNack((word)(rSeq & 0xffff)+1, seqDelta-1, false);

            int e = (seqDelta - 1);
            if (e > 8) e = 1;
            rLost +=e;
            errors(e);
        }
        else if(rDuplicate[seq&3]!=seq) {
            rLost -=1;
            if(errorCount) errorCount--;
        }
    }
    rDuplicate[seq&3] = seq;

    if((rSeq &0xffff)>(seq) && rDuplicate[seq&3]!=seq) rSeq +=0x10000;
    rSeq &=0xffff0000;
    rSeq +=seq;

    rTimestamp = READ_DWORD_NET(&rtpHeader[4]);
    rxPayload = rtpHeader[1];
    marker = ((rtpHeader[1] & 0x80) != 0);
    rtpHeader[1] &=0x7f;   // mask mark bit

    if(dtmfRecvPt && (rtpHeader[1]==dtmfRecvPt || rtpHeader[1]==dtmfXmitPt)) {
        const byte *rtpDtmf = &((byte *)buf)[offset];
        if(msgLen>=4) {
            if((rtpDtmf[1]&RTP_DTMF_FLAG_END)==0) {
                if(rDtmfTimestamp!=rTimestamp) {
                    rDtmfTimestamp = rTimestamp;
                    rDtmfEnd = false;
                    debug->printf("received rtpDtmf start %c", (char)(char)rtpDtmfChar[MIN(rtpDtmf[0],16)]);
                    if(rtpDtmf[0]<=16) media->user->MediaRtpDtmfNearStart((char)rtpDtmfChar[rtpDtmf[0]]);
                }
            }
            else if(rDtmfTimestamp==rTimestamp) {
                if(!rDtmfEnd) {
                    rDtmfEnd = true;
                    debug->printf("received rtpDtmf %c", (char)(char)rtpDtmfChar[MIN(rtpDtmf[0],16)]);
                    if(rtpDtmf[0]<=16) media->user->MediaRtpDtmfNear((char)rtpDtmfChar[rtpDtmf[0]]);
                }
            }
        }
        return;
    }
    if(redRecvPt && rtpHeader[1]==redRecvPt) {
        return;
    }
    if(rtpHeader[1]==13) {
        return;
    }
#if 0
    if(rtpHeader[1]!=audio_recv_pt) {
        if(media_type && ++rWrongPt==50) {
            if(!errorReported) {
                errorReported = true;
                debug->printf("Wrong Payload Type received");
            }
        }
    }
    if(rWrongPt<50) rWrongPt = 0;
#endif

    sTimestamp = getTimestamp8kHz() * timestampRateMultiplier;

    if(rsTimestamp) {
        d = (rTimestamp-rrTimestamp) - (sTimestamp-rsTimestamp);
        if(d<0) d = -d;
        d = d / timestampRateMultiplier;
        if(d>RTP_ERROR_JITTER_LIMIT) errors(1);
        rJitter += ((d-(int)rJitter)>>4);
    }
    rrTimestamp = rTimestamp;
    rsTimestamp = sTimestamp;

#ifdef _WINDOWS
    if (recordRtpStream && mediaType == MediaType::AUDIO) {
        if (!rtpStreamRx) {
            char buf[2048];
            char* envPathLocalAppData = Environment::GetEnv("LOCALAPPDATA");
            if (_PRODUCT_NAME_ && !strcmp(_PRODUCT_NAME_, myappsPluginForWindows)) sprintf(buf, "%s\\innovaphone\\myappsPlugin\\RTP-Recv-%d", envPathLocalAppData, cntRx);
            else sprintf(buf, "%s\\innovaphone\\myapps\\RTP-Recv-%d", envPathLocalAppData, cntRx);
            cntRx = cntRx >= 5 ? 0 : cntRx + 1;
            rtpStreamRx = FileSystem::OpenWriteFile(buf, false, NULL);
            if (!rtpStreamRx && !recordRtpStreamRxTrace) {
                debug->printf("MediaChannel::RtpSend failed to open RTP-Recv file");
                recordRtpStreamRxTrace = true;
            }
            free(envPathLocalAppData);
        }
        if (rtpStreamRx) rtpStreamRx->Write(&buf[offset], msgLen);
    }
#elif defined(__IGEL__) || defined(__linux__)
    if (recordRtpStream && mediaType == MediaType::AUDIO) {
        if (!rtpStreamRx) {
            char buf[2048];
            sprintf(buf, "//var//log//apps//myAppsPlugin//RTP-Recv-%d", cntRx);
            cntRx = cntRx >= 5 ? 0 : cntRx + 1;
            rtpStreamRx = FileSystem::OpenWriteFile(buf, false, NULL);
            if (!rtpStreamRx && !recordRtpStreamRxTrace) {
                debug->printf("MediaChannel::RtpSend failed to open RTP-Recv file");
                recordRtpStreamRxTrace = true;
            }
        }
        if (rtpStreamRx) rtpStreamRx->Write(&buf[offset], msgLen);
    }
#endif

    if(record.On()) record.RtpRecv((char*)buf, msgLen + offset);
    if(endpoint) endpoint->Recv(&buf[offset], msgLen, rTimestamp, (short)(seqDelta - 1), marker);
    if(rtpRecvUpdate == false) {
        char addr[128];
        str::from_ip(addr, 128, &src);
        media->RtpRecv(addr, port, ssrc, rtpHeader[1]);
        rtpRecvUpdate = true;
    }
}

bool MediaChannel::doNatWorkaround(IPaddr srcAddr, word srcPort, dword srcSsrc)
{
    // check for left-overs from last peer
    bool oldSrc = (remoteSsrc == oldRemoteSsrc && ip_match(srcAddr, oldMediaAddr) && srcPort == oldMediaPort);
    unsigned threshold = oldSrc ? 10 : 4;

    // only a continuous stream causes redirect
    if(redirectPort == srcPort && ip_match(redirectAddr, srcAddr) && redirectSsrc == srcSsrc) {
        this->redirectCounter++;
        if(this->redirectCounter > threshold) {
            this->mediaAddr = srcAddr;
            this->mediaPort = srcPort;
            this->redirectCounter = 1;
            this->redirectDone = true;
        }
        else {
            return false;
        }
    }
    else {
        // keep remote source as redirect candidate
        this->redirectAddr = srcAddr;
        this->redirectPort = srcPort;
        this->redirectSsrc = srcSsrc;
        this->redirectCounter = 2;
    }
    return true;
}

void MediaChannel::resetRtcp()
{
    rSeq = 0;
    rrTimestamp = 0;
    rsTimestamp = 0;
    rJitter = 0;
    lsr = 0;
    lsrTimestamp = 0;
}

void MediaChannel::errors(int count)
{
    if(!errorReported) {
        if(errorCount==0) {
            errorTotal = rTotal;
            errorTimer->Start(RTP_ERROR_COUNT_TIMEOUT);
        }
        errorCount +=count;
    }
}

void MediaChannel::initialized()
{
    if(initComplete) {
        log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p): initialized", this);
        size_t certFingerprintLen = 0;
        const byte * certFingerprint = socketContext ? socketContext->GetCertificateFingerprint(&certFingerprintLen) : NULL;
        dtlsRtp->DtlsInitialize(certFingerprint, certFingerprintLen);
        if(!noRtcp) dtlsRtcp->DtlsInitialize(certFingerprint, certFingerprintLen);
    }
}

void MediaChannel::SctpRtcConnected(bool err)
{
    iceDtlsDone = true;
    media->connectResult(this, err ? Media::SctpFailed : Media::Success, iceTurnError);
}

void MediaChannel::DtlsConnected(void * ctx)
{
    if (ctx == (void*)DTLS_RTP_CONTEXT) {
        bool server = remoteSetupRole == SetupRole::SETUP_ROLE_ACTIVE ? true : remoteSetupRole == SetupRole::SETUP_ROLE_PASSIVE ? false : iceControlling;

        debug->printf("MediaChannel(%p)::dtlsConnected (%s)", this, (this->mediaType == MediaType::APPSHARING ? "SCTP" : "RTP"));
        // RTCP to be done
        if (!noRtcp && !rtcpMux) dtlsRtcp->DtlsUnpause();
        // AppSharing, SCTP Handshake to be done
        else if(this->mediaType == MediaType::APPSHARING) DoSctpHandshake(server, this->txPayload);
        // Audio/Video without RTCP
        else {
            iceDtlsDone = true;
            media->connectResult(this, Media::Success, iceTurnError);
            connectedTs = getTimestamp8kHz();
        }
    }
    else if (ctx == (void*)DTLS_RTCP_CONTEXT) {
        debug->printf("MediaChannel(%p)::dtlsConnected (RTCP)", this);
        iceDtlsDone = true;
        media->connectResult(this, Media::Success, iceTurnError);
        connectedTs = getTimestamp8kHz();
    }
}

void MediaChannel::DtlsInitialized(void * ctx, const byte * fingerprint, size_t fingerprintLen)
{
    if (disconnecting) return;
    if (ctx == (void*)DTLS_RTP_CONTEXT) {
        if(localCfg->ice->fingerprint == NULL) {
            static char xupper[] = "0123456789ABCDEF";
            int l = 0;
            localCfg->ice->fingerprint = (char *)malloc((fingerprintLen<<1)+1);
            for (size_t j=0; j<fingerprintLen; j++) {
                localCfg->ice->fingerprint[l++] = xupper[(fingerprint[j] >> 4) & 0xf];
                localCfg->ice->fingerprint[l++] = xupper[(fingerprint[j] >> 0) & 0xf];
            }
            localCfg->ice->fingerprint[l] = 0;
        }
        log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p)::dtlsInitialized (RTP) fingerprint %s", this, localCfg->ice->fingerprint);
    }
    if ((ctx == (void*)DTLS_RTCP_CONTEXT) || noRtcp) {
        log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p)::dtlsInitialized (RTCP) %d %d,%d", this, mediaType, rtpBindFailed, rtcpBindFailed);
        if(!rtpBindFailed && !rtcpBindFailed) {
            iceInitialize(!noRtcp && ((mediaType == MediaType::VIDEO || mediaType == MediaType::AUDIO)), (byte *)fingerprint, fingerprintLen);
            if(turnRtp.turnPort) {
                if (ip_match(turnRtp.turnAltAddr, turnRtp.turnAddr) && (turnRtp.turnAltPort == turnRtp.turnPort) && (iceTurnAltTcp == iceTurnTcp)) {
                    log->Log(LOG_TURN, "MediaChannel(%p)::dtlsInitialized Discarding TurnAlt %s:%d %s %s:%d", this, iceTurnAltAddr, iceTurnAltPort, iceTurnAltHostname, iceLocalAddr, iceLocalPort);
                    turnRtp.turnAltAddr = ip_anyaddr;
                    turnRtp.turnAltPort = 0;
                    free(iceTurnAltHostname);
                    iceTurnAltHostname = NULL;
                    free(iceTurnAltAddr);
                    iceTurnAltAddr = NULL;
                    iceTurnAltPort = 0;
                }
                if(iceTurnTcp) {
                    char turnAddr[128];
                    _sprintf(turnAddr, ipv6 ? "[%s]:%u" : "%s:%u", iceTurnAddr, iceTurnPort);
                    tcpSocket->Connect(turnAddr);
                }
                if (turnRtp.turnAltPort && iceTurnAltTcp) {
                    char turnAltAddr[128];
                    _sprintf(turnAltAddr, ipv6 ? "[%s]:%u" : "%s:%u", iceTurnAltAddr, iceTurnAltPort);
                    tcpAltSocket->Connect(turnAltAddr);
                }
                if (iceTurnTcp && (!turnRtp.turnAltPort || iceTurnAltTcp)) {
                    turnRtp.turnTcpConnecting();
                }
                else {
                    turnRtp.startAllocation(TURN_FLAG_TRANSPORT_UDP | TURN_FLAG_EVEN_PORT | TURN_FLAG_RESERVE_NEXTHIGHER_PORT | (iceSlow ? TURN_FLAG_SLOW_SERVER : 0));
                }
            }
            else {
                iceTurnInitialized(ip_anyaddr, 0, 0, ip_anyaddr, 0, 0, ip_anyaddr, 0, 0);
            }
            iceHostAddr(localAddrStorage, rtpOverTcp ? CHANNEL_CANDIDATE_HOST_TCP_PASSIVE : CHANNEL_CANDIDATE_HOST);
        }
    }
}

void MediaChannel::stopStunTurnDiscovery()
{
    iceAbortStun();
    TurnFailed(&turnRtcp, 0, 0);
    TurnTimerStop(&turnRtcp);
    TurnReleased(&turnRtcp);
    TurnFailed(&turnRtp, 0, 0);
    TurnTimerStop(&turnRtp);
    TurnReleased(&turnRtp);
}

void MediaChannel::tryInitialized()
{
    log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p)::tryInitialized (complete=%d,%d,bind=%d,%d,stun=%d,%d,%d,%d,turn=%d,%d)", this, initComplete, iceComplete, waitBindRtp, waitBindRtcp, reqStun1 == NULL, reqStun2 == NULL, reqStun3 == NULL, reqStun4 == NULL, reqTurn1 == NULL, reqTurn2 == NULL);

    if (!initComplete && !waitBindRtp && !waitBindRtcp && reqStun1 == NULL && reqStun2 == NULL && reqStun3 == NULL && reqStun4 == NULL && reqTurn1 == NULL && reqTurn2 == NULL) {
        initComplete = true;
        if(iceComplete) media->tryMediaInitialized(false, false);
        else initialized();
    }
}

void MediaChannel::TurnAllocated(void * ctx)
{
    log->Log(LOG_TURN, "MediaChannel(%p,%d)::TurnAllocated (%s) iceTurnTcp=%d", this, iceTurnTcp, (ctx == &turnRtp) ? "RTP" : (ctx == &turnRtcp) ? "RTCP" : "Unknown", iceTurnTcp);

    if (ctx == &turnRtp) {
        if(log->LogFlagSet(LOG_TURN)) {
            char relayedAddr[128];
            str::from_ip(relayedAddr, 128, &turnRtp.relayedAddr);
            char mappedAddr[128];
            str::from_ip(mappedAddr, 128, &turnRtp.mappedAddr);
            debug->printf("MediaChannel(%p)::TurnAllocated (RTP) %s:%d (%s:%d) %s:%d", this, relayedAddr, turnRtp.relayedPort, mappedAddr, turnRtp.mappedPort, iceLocalAddr, iceLocalPort);
        }
        if (mediaType == MediaType::VIDEO || mediaType == MediaType::AUDIO) {  // AppSharing has no RTCP
            turnRtcp.turnAddr = turnRtp.turnAddr;
            turnRtcp.turnPort = turnRtp.turnPort;
            memcpy(turnRtcp.reservationToken, turnRtp.reservationToken, sizeof(turnRtp.reservationToken));
            if (iceTurnTcp) {
                char turnAddr[128];
                _sprintf(turnAddr, ipv6 ? "[%s]:%u" : "%s:%u", iceTurnAddr, iceTurnPort);
                rtcpTcpSocket->Connect(turnAddr);
                turnRtcp.turnTcpConnecting();
                return;
            }
            else {
                turnRtcp.startAllocation(TURN_FLAG_TRANSPORT_UDP | TURN_FLAG_RESERVATION_TOKEN | (iceSlow ? TURN_FLAG_SLOW_SERVER : 0));
            }
        }
    }
    else if (ctx == &turnRtcp) {
        if(log->LogFlagSet(LOG_TURN)) {
            char relayedAddr[128];
            str::from_ip(relayedAddr, 128, &turnRtcp.relayedAddr);
            char mappedAddr[128];
            str::from_ip(mappedAddr, 128, &turnRtcp.mappedAddr);
            debug->printf("MediaChannel(%p)::TurnAllocated (RTP) %s:%d (%s:%d) %s:%d", this, relayedAddr, turnRtcp.relayedPort, mappedAddr, turnRtcp.mappedPort, iceLocalAddr, iceLocalRtcpPort);
        }
    }
    if (!iceTurnError) iceTurnError = new class IceTurnError(0, NULL, NULL);
    // done if RTP/RTCP are all allocated
    if (!turnRtp.isAllocating() && !turnRtcp.isAllocating()) {
        iceTurnInitialized(localAddrStorage, iceLocalPort, iceLocalRtcpPort, turnRtp.relayedAddr, turnRtp.relayedPort, turnRtcp.relayedPort, turnRtp.mappedAddr, turnRtp.mappedPort, turnRtcp.mappedPort);
    }
}

void MediaChannel::TurnReleased(void * ctx)
{
    if (ctx == &turnRtp) {
        log->Log(LOG_TURN, "MediaChannel(%p)::TurnReleased RTP", this);
    }
    else if (ctx == &turnRtcp) {
        log->Log(LOG_TURN, "MediaChannel(%p)::TurnReleased RTCP", this);
    }
    if(disconnecting && turnRtp.isIdle() && turnRtcp.isIdle()) {
        //finishClose();
    }
}

void MediaChannel::TurnFailed(void * ctx, dword code, const char * errorText)
{
    log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p)::turnFailed %d,%d", this, (ctx == &turnRtp), (ctx == &turnRtcp));

    const char* errorSrc = 0;
    if (ctx == &turnRtp) {
        log->Log(LOG_TURN, "MediaChannel(%p): turnFailed (RTP)", this);
        iceTurnInitialized(ip_anyaddr, 0, 0, ip_anyaddr, 0, 0, ip_anyaddr, 0, 0);
        errorSrc = "TURN-RTP";
    }
    else if (ctx == &turnRtcp) {
        log->Log(LOG_TURN, "MediaChannel(%p): turnFailed (RTCP)", this);
        iceTurnInitialized(ip_anyaddr, 0, 0, ip_anyaddr, 0, 0, ip_anyaddr, 0, 0);
        errorSrc = "TURN-RTCP";
    }
    if(errorText) {
        if (!iceTurnError) iceTurnError = new class IceTurnError(FAULT_CODE_RTP_TURN_ERROR | code, (const char*)errorText, errorSrc, iceTurnHostname, iceTurnAddr, iceTurnPort);
        else media->EventReceived(CHANNEL_MEDIA_ERROR, new class EventMediaError(FAULT_CODE_RTP_TURN_ERROR | code, (const char*)errorText, errorSrc));
    }
}

void MediaChannel::TurnSend(void * ctx, char * data, int len)
{
    if (ctx == &turnRtp) {
        if(log->LogFlagSet(LOG_MEDIA_CHANNEL) && log->LogFlagSet(LOG_TURN)) {
            char turnAddr[128];
            str::from_ip(turnAddr, 128, &turnRtp.turnAddr);
            debug->printf("MediaChannel(%p,%p,len=%d,ch=0x%x)::TurnSend (%sRTP) %s:%d -> %s:%d iceTurnTcp=%d", this, socket, len, READ_WORD_NET(data), iceTurnTcp ? "TCP-" : "", iceLocalAddr, iceLocalPort, turnAddr, turnRtp.turnPort, iceTurnTcp);
        }
        if(!iceTurnTcp) {
            if(socket) socket->SendTo(data, len, turnRtp.turnAddr, turnRtp.turnPort);
        }
        else if(!turnRtp.turnAltPort && tcpSocket) tcpSocket->Send(data, len);
        if (turnRtp.turnAltPort && !iceTurnAltTcp && socket) socket->SendTo(data, len, turnRtp.turnAltAddr, turnRtp.turnAltPort);
    }
    else if (ctx == &turnRtcp) {
        if(log->LogFlagSet(LOG_MEDIA_CHANNEL) && log->LogFlagSet(LOG_TURN)) {
            char turnAddr[128];
            str::from_ip(turnAddr, 128, &turnRtcp.turnAddr);
            debug->printf("MediaChannel(%p,%p,len=%d,ch=0x%x)::TurnSend (%sRTCP) %s:%d -> %s:%d", this, socket, len, READ_WORD_NET(data), iceTurnTcp ? "TCP-" : "", iceLocalAddr, iceLocalRtcpPort, turnAddr, turnRtcp.turnPort);
        }
        if(!iceTurnTcp) {
            if(rtcpSocket) rtcpSocket->SendTo(data, len, turnRtcp.turnAddr, turnRtcp.turnPort);
        }
        else if(rtcpTcpSocket) rtcpTcpSocket->Send(data, len);
    }
}

void MediaChannel::TurnTimerStart(void * ctx, int to)
{
    log->Log(LOG_TURN, "MediaChannel(%p)::TurnTimerStart (%d,%d) to %d iceTurnTcp=%d", this, (ctx == &turnRtp), (ctx == &turnRtcp), to, iceTurnTcp);

    if (ctx == &turnRtp) turnRtpTimer->Start(to);
    else if (ctx == &turnRtcp) turnRtcpTimer->Start(to);
}

void MediaChannel::TurnTimerStop(void * ctx)
{
    log->Log(LOG_TURN, "MediaChannel(%p)::TurnTimerStop (%d,%d)", this, (ctx == &turnRtp), (ctx == &turnRtcp));

    if (ctx == &turnRtp) turnRtpTimer->Cancel();
    else if (ctx == &turnRtcp) turnRtcpTimer->Cancel();
}

void MediaChannel::RecordingStopped(const char * error)
{
    log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p)::RecordingStopped error: %s", this, error);
    if (media) media->RecordingStopped(error);
    if (error) {
        char* message = (char*)malloc(128 + strlen(error));
        if(message) _sprintf(message, "Recording failed (%s)", error);

        media->EventReceived(CHANNEL_MEDIA_ERROR, new class EventMediaError(FAULT_CODE_RTP_RECORDING_FAILED, (const char*)message, "RTP"));
        free(message);
    }
}

void MediaChannel::RecordingStarted()
{
    log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p)::RecordingStarted", this);
    if (media) media->RecordingStarted();
}

void MediaChannel::RecordingCloseComplete()
{
    log->Log(LOG_MEDIA_CHANNEL, "MediaChannel(%p)::RecordingCloseComplete", this);

    if (!this->mcSocket && !this->socket && !this->rtcpSocket && !this->tcpSocket && !this->tcpAltSocket && !this->rtcpTcpSocket && !this->rtpTcpListen && !this->rtpTcpSocket && !this->closeRtp && !this->closeRtcp && !this->closeMc && !this->reqStun1 && !this->reqStun2 && !this->reqStun3 && !this->reqStun4 && !this->reqTurn1 && !this->reqTurn2 && !record.On() && !execCnt) media->channelDisconnected(this);
}

DnsRequest::DnsRequest(const char * hostname, IDns * const dns, class IInstanceLog * const log, dword timeout, dword flags, word servicePort) :
log(log),
dns(dns)
{
    log->Log(LOG_MEDIA, "DnsRequest(%p)::DnsRequest", this);

    this->hostname = _strdup(hostname);
    this->timeout = timeout;
    this->flags = flags;
    this->servicePort = servicePort;
    this->addr = NULL;
}

DnsRequest::~DnsRequest()
{
    log->Log(LOG_MEDIA, "DnsRequest(%p)::~DnsRequest", this);
    if(addr) free(addr);
    if(hostname) free(hostname);
}

void DnsRequest::Start(class UTask * user)
{
    this->user = user;
    dns->GetHostByName(hostname, this, log, timeout, flags, servicePort);
}

void DnsRequest::TaskComplete(class ITask * const task)
{
    user->TaskComplete(this);
}

void DnsRequest::DnsGetHostByNameResult(const char * addr, bool isIPv6)
{
    if(addr) this->addr = _strdup(addr);
    this->isIPv6 = isIPv6;
    TaskComplete(this);
}

CloseSocket::CloseSocket(class IIoMux * const iomux, ISocketProvider * socketProvider, bool IPv6, class ISocket * socket, char * addr, int port, class IInstanceLog * const log) :
log(log),
iomux(iomux),
socket(NULL),
socketProvider(socketProvider),
discSocket(socket),
ipv6(IPv6)
{
    if(IPv6)
        _sprintf(this->dstAddr, "[%s]:%d", addr, port);
    else
        _sprintf(this->dstAddr, "%s:%d", addr, port);
    log->Log(LOG_MEDIA_CHANNEL, "CloseSocket(%p)::CloseSocket (%s) udpsocket=%p", this, this->dstAddr, socket);
}

CloseSocket::~CloseSocket()
{
    log->Log(LOG_MEDIA_CHANNEL, "CloseSocket(%p)::~CloseSocket", this);
    if(socket) delete socket;
}

void CloseSocket::Start(class UTask * user)
{
    log->Log(LOG_MEDIA_CHANNEL, "CloseSocket(%p)::Start %s", this, this->dstAddr);

    this->user = user;
    socket = socketProvider->CreateSocket(iomux, this, log, ipv6);
    socket->Connect(this->dstAddr);
}

void CloseSocket::SocketConnectComplete(ISocket * const socket)
{
    log->Log(LOG_MEDIA_CHANNEL, "CloseSocket(%p)::SocketConnectComplete %s", this, this->dstAddr);
    unsigned char buf[8];
    buf[0] = 0xcc;
    socket->Send(buf, 1);
}

void CloseSocket::SocketShutdown(ISocket * const socket, shutdownreason_t reason)
{
    log->Log(LOG_MEDIA_CHANNEL, "CloseSocket(%p)::SocketShutdown %d", this, reason);
    user->TaskComplete(this);
    delete this;
}

void CloseSocket::SocketSendResult(ISocket * const socket)
{
    log->Log(LOG_MEDIA_CHANNEL, "CloseSocket(%p)::SocketSendResult %s", this, this->dstAddr);
    socket->Shutdown();
}

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

/*---------------------------------------------------------------------------*/
/* class Srtp                                                                */
/*---------------------------------------------------------------------------*/

ISrtpSession * ISrtpSession::Create(byte * srtpMasterkey, byte * srtpMastersalt, const char * profile)
{
    if(profile) {
        if(!strcmp(profile, "SRTP_AES128_CM_SHA1_80")) {
            return new SrtpSession(AES_CM_128_HMAC_SHA1_80, srtpMasterkey, srtpMastersalt);
        }
        else if(!strcmp(profile, "SRTP_AES128_CM_SHA1_32")) {
            return new SrtpSession(AES_CM_128_HMAC_SHA1_32, srtpMasterkey, srtpMastersalt);
        }
        else {
            debug->printf("ISrtpSession::Create Unknown Profile! %s", profile);
            return NULL;
        }
    }
    else {
        return new SrtpSession(AES_CM_128_HMAC_SHA1_80, srtpMasterkey, srtpMastersalt);
    }
}

SrtpSession::SrtpSession(srtp_cryptosuite_id srtpCsid, byte * srtpMasterkey, byte * srtpMastersalt) :
    ctx(srtpCsid, srtpCsid)
{
    this->DeriveKeys(false, srtpMasterkey, srtpMastersalt, ctx.srtp.key, ctx.srtp.salt, ctx.srtp.akey, ctx.srtp.nK, ctx.srtp.nS, ctx.srtp.nA, ctx.srtp.nMk);
    this->DeriveKeys(true, srtpMasterkey, srtpMastersalt, ctx.srtcp.key, ctx.srtcp.salt, ctx.srtcp.akey, ctx.srtcp.nK, ctx.srtcp.nS, ctx.srtcp.nA, ctx.srtcp.nMk);

    aesRtp = new class Cipher();
    aesRtcp = new class Cipher();

    aesRtp->Init(CIPHER_AES, ctx.srtp.key, ctx.srtp.nK);
    aesRtcp->Init(CIPHER_AES, ctx.srtcp.key, ctx.srtcp.nK);

    //SHA1 hash initialization
    unsigned int i;

    for (i = 0; i < ctx.srtp.nA; i++) {
        a1[i] = (char)(ctx.srtp.akey[i] ^ 0x36);
        a2[i] = (char)(ctx.srtp.akey[i] ^ 0x5c);
    }

    for (i = 0; i < ctx.srtcp.nA; i++) {
        a3[i] = (char)(ctx.srtcp.akey[i] ^ 0x36);
        a4[i] = (char)(ctx.srtcp.akey[i] ^ 0x5c);
    }

    memset(&a1[ctx.srtp.nA], 0x36, 64 - ctx.srtp.nA);
    memset(&a2[ctx.srtp.nA], 0x5c, 64 - ctx.srtp.nA);
    memset(&a3[ctx.srtcp.nA], 0x36, 64 - ctx.srtcp.nA);
    memset(&a4[ctx.srtcp.nA], 0x5c, 64 - ctx.srtcp.nA);
}

SrtpSession::~SrtpSession()
{
    if(aesRtp) delete aesRtp;
    if(aesRtcp) delete aesRtcp;
}

byte * SrtpSession::Protect(byte * header, int hLen, byte * payload, int pLen, int * oLen)
{
    byte* data = (byte*)malloc(hLen + pLen + 4 + 20);
    byte* aTag = &data[hLen + pLen + 4];

    // determine ROC
    word packetSeq = READ_WORD_NET(&header[2]);
    ctx.roc = calculateRoc(ctx.roc, packetSeq, ctx.lastSeq);
    ctx.lastSeq = packetSeq;

    memcpy(&data[0], header, hLen);
    memcpy(&data[hLen], payload, pLen);

    // encrypt payload
    createSrtpIv(ctx.srtp.iv, &header[8], &header[2], ctx.roc, ctx.srtp.salt);
    aesRtp->Reset();
    aesRtp->CtrCrypt(&data[hLen], &data[hLen], pLen, ctx.srtp.iv);

    // authenticate packet
    WRITE_DWORD_NET(&data[hLen+pLen], ctx.roc);

    hmacSha1((byte*)data, hLen+pLen+4, aTag, a1, a2);
/*
    char srtpSha1[64];
    int slen = 0;
    for(int i=0; i<ctx.srtp.nTag && i<(sizeof(srtpSha1)-1)/2; i++) {
        slen += _sprintf(&srtpSha1[slen], "%02x", aTag[i]);
    }
    debug->printf("SrtpSession(%p)::Protect Hash (%d,%d) %s", this, ctx.srtp.nTag, hLen+pLen, srtpSha1);
*/
    memmove(&data[hLen+pLen], aTag, ctx.srtp.nTag);

    *oLen = hLen+pLen+ctx.srtp.nTag;
    return data;
}

int SrtpSession::Unprotect(char * srtpPacket, int len)
{
    byte aTag[20], aTagSoll[20];
    memcpy(aTag, &srtpPacket[len-ctx.srtp.nTag], ctx.srtp.nTag);

    unsigned int payloadLen = len - 12 - ctx.srtp.nTag;
    unsigned int packetLen = len - ctx.srtp.nTag;
    byte* data = (byte*) malloc(packetLen + 4);

    memcpy(data, srtpPacket, packetLen);
    byte * header = &data[0];
    byte * payload = &data[12];

    // determine ROC
    word packetSeq = READ_WORD_NET(&header[2]);
    dword roc = ctx.lastSeqSet ? calculateRoc(ctx.roc, packetSeq, ctx.lastSeq) : 0;

    // check authentication
    WRITE_DWORD_NET(&data[packetLen], roc);

    hmacSha1((byte*)data, packetLen+4, aTagSoll, a1, a2);
/*
    char srtpSha1[64];
    int slen = 0;
    for(int i=0; i<ctx.srtp.nTag && i<(sizeof(srtpSha1)-1)/2; i++) {
        slen += _sprintf(&srtpSha1[slen], "%02x", aTagSoll[i]);
    }
    debug->printf("SrtpSession(%p)::Unprotect Hash(%d,%d) %s", this, ctx.srtp.nTag, packetLen+4, srtpSha1);
*/
    if (memcmp(aTag, aTagSoll, ctx.srtp.nTag) == 0) {
        ctx.lastSeqSet = true;
        ctx.lastSeq = packetSeq;
        ctx.roc = roc;

        // decrypt payload
        createSrtpIv(ctx.srtp.iv, &header[8], &header[2], ctx.roc, ctx.srtp.salt);
        aesRtp->Reset();
        aesRtp->CtrCrypt(payload, payload, payloadLen, ctx.srtp.iv);
        memcpy(&srtpPacket[12], payload, payloadLen);

        free(data);
        return packetLen;
    }
    free(data);
    return 0;
}

byte * SrtpSession::SrtcpProtect(byte * rtcpPacket, int pLen, int * olen)
{
    unsigned int payloadLen = pLen - 8;
    byte* data = (byte*) malloc(pLen + 4 + 20);
    memcpy(data, rtcpPacket, pLen);
    byte aTag[20];

    WRITE_DWORD_NET(&data[pLen], ctx.srtcpIndex);

    if (ctx.srtcpEncrypted) {
        // payload encryption
        createSrtcpIv(ctx.srtcp.iv, &data[4], ctx.srtcpIndex, ctx.srtcp.salt);
        aesRtcp->Reset();
        aesRtcp->CtrCrypt(&data[8], &data[8], payloadLen, ctx.srtcp.iv);
        data[pLen] |= 0x80;
    }
    ctx.srtcpIndex++;

    // packet authentication
    hmacSha1((byte*)data, pLen+4, aTag, a3, a4);
/*
    char srtcpSha1[64];
    int slen = 0;
    for(int i=0; i<ctx.srtp.nTag && i<(sizeof(srtcpSha1)-1)/2; i++) {
        slen += _sprintf(&srtcpSha1[slen], "%02x", aTag[i]);
    }
    debug->printf("SrtpSession(%p)::SrtcpProtect Hash(%d,%d) %s", this, ctx.srtcp.nTag, pLen+4, srtcpSha1);
*/
    memcpy(&data[pLen+4], aTag, ctx.srtcp.nTag);

    *olen = pLen+4+ctx.srtcp.nTag;

    return data;
}

int SrtpSession::SrtcpUnprotect(char * srtcpPacket, int len)
{
    byte aTag[20], aTagSoll[20];
    memcpy(aTag, &srtcpPacket[len-ctx.srtcp.nTag], ctx.srtcp.nTag);

    unsigned int payloadLen = len - 12 - ctx.srtp.nTag;  // 8 Bytes Header + 4 Bytes Tail
    unsigned int packetLen = len - ctx.srtp.nTag;
    byte* data = (byte*) malloc(packetLen);

    memcpy(data, srtcpPacket, packetLen);
    dword srtcpIndexRecv;

    hmacSha1((byte*)data, packetLen, aTagSoll, a3, a4);
/*
    char srtcpSha1[64];
    int slen = 0;
    for(int i=0; i<ctx.srtp.nTag && i<(sizeof(srtcpSha1)-1)/2; i++) {
        slen += _sprintf(&srtcpSha1[slen], "%02x", aTagSoll[i]);
    }
    debug->printf("SrtpSession(%p)::SrtcpUnprotect Hash(%d,%d) %s", this, ctx.srtcp.nTag, packetLen, srtcpSha1);
*/
    if (memcmp(aTag, aTagSoll, ctx.srtcp.nTag) == 0) {
        byte * srtcpIndexData = &data[packetLen - 4];
        bool srtcpEncrypted = ((srtcpIndexData[0] & 0x80) == 0x80);

        if (srtcpEncrypted) {
            srtcpIndexData[0] &= 0x7f;
            srtcpIndexRecv = READ_DWORD_NET(&srtcpIndexData[0]);

            // decrypt payload
            createSrtcpIv(ctx.srtcp.iv, &data[4], srtcpIndexRecv, ctx.srtcp.salt);
            aesRtcp->Reset();
            aesRtcp->CtrCrypt(&data[8], &data[8], payloadLen, ctx.srtcp.iv);
            memcpy(&srtcpPacket[8], &data[8], payloadLen);
        }
        free(data);
        return packetLen-4;
    }
    free(data);
    return 0;
}

    // SRTP library functions
void SrtpSession::createSrtpIv(byte* iv, byte* ssrc, byte* seq, dword roc, byte* srtpSalt)
{
    byte rocdata[4];
    WRITE_DWORD_NET(rocdata, roc);

    iv[0] = srtpSalt[0];
    iv[1] = srtpSalt[1];
    iv[2] = srtpSalt[2];
    iv[3] = srtpSalt[3];
    iv[4] = srtpSalt[4] ^ ssrc[0];
    iv[5] = srtpSalt[5] ^ ssrc[1];
    iv[6] = srtpSalt[6] ^ ssrc[2];
    iv[7] = srtpSalt[7] ^ ssrc[3];
    iv[8] = srtpSalt[8] ^ rocdata[0];
    iv[9] = srtpSalt[9] ^ rocdata[1];
    iv[10] = srtpSalt[10] ^ rocdata[2];
    iv[11] = srtpSalt[11] ^ rocdata[3];
    iv[12] = srtpSalt[12] ^ seq[0];
    iv[13] = srtpSalt[13] ^ seq[1];
    iv[14] = 0x00;
    iv[15] = 0x00;
}

void SrtpSession::createSrtcpIv(byte* iv, byte* ssrc, dword srtcpIndex, byte* srtcpSalt)
{
    byte srtcpIndexData[4];
    WRITE_DWORD_NET(srtcpIndexData, srtcpIndex);

    iv[0] = srtcpSalt[0];
    iv[1] = srtcpSalt[1];
    iv[2] = srtcpSalt[2];
    iv[3] = srtcpSalt[3];
    iv[4] = srtcpSalt[4] ^ ssrc[0];
    iv[5] = srtcpSalt[5] ^ ssrc[1];
    iv[6] = srtcpSalt[6] ^ ssrc[2];
    iv[7] = srtcpSalt[7] ^ ssrc[3];
    iv[8] = srtcpSalt[8];
    iv[9] = srtcpSalt[9];
    iv[10] = srtcpSalt[10] ^ srtcpIndexData[0];
    iv[11] = srtcpSalt[11] ^ srtcpIndexData[1];
    iv[12] = srtcpSalt[12] ^ srtcpIndexData[2];
    iv[13] = srtcpSalt[13] ^ srtcpIndexData[3];
    iv[14] = 0x00;
    iv[15] = 0x00;
}

void SrtpSession::hmacSha1(byte* data, unsigned int len, byte *digest, byte * inner, byte * outer)
{
    class hash * hash = new class hash();
    hash->init(HASH_SHA1);
    hash->update(inner, 64);
    hash->update((byte*)data, len);
    hash->final(digest);
    delete hash;

    hash = new class hash();
    hash->init(HASH_SHA1);
    hash->update(outer, 64);
    hash->update((byte*)digest, 20);
    hash->final(digest);
    delete hash;
}

dword SrtpSession::calculateRoc(dword roc, word packetSeq, word lastSeq)
{
    if (lastSeq > 57343 && packetSeq < 8192) {
        return roc + 1;
    } else if (roc > 0 && lastSeq < 8192 && packetSeq > 57343) {
        return roc - 1;
    }
    return roc;
}

void SrtpSession::createDerivationIv(byte* iv, byte label, byte* r, byte* mastersalt)
{
    iv[0] = mastersalt[0];
    iv[1] = mastersalt[1];
    iv[2] = mastersalt[2];
    iv[3] = mastersalt[3];
    iv[4] = mastersalt[4];
    iv[5] = mastersalt[5];
    iv[6] = mastersalt[6];
    iv[7] = mastersalt[7] ^ label;
    iv[8] = mastersalt[8] ^ r[0];
    iv[9] = mastersalt[9] ^ r[1];
    iv[10] = mastersalt[10] ^ r[2];
    iv[11] = mastersalt[11] ^ r[3];
    iv[12] = mastersalt[12] ^ r[4];
    iv[13] = mastersalt[13] ^ r[5];
    iv[14] = 0x00;
    iv[15] = 0x00;
}

void SrtpSession::DeriveKeys(bool srtcp, byte* masterkey, byte* mastersalt, byte* key, byte* salt, byte* akey, byte nK, byte nS, byte nA, byte nMk)
{
    byte iv[16];
    byte zeros[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
    byte r[] = { 0,0,0,0,0,0 };

    class Cipher * aesKeys = new class Cipher();

    aesKeys->Init(CIPHER_AES, masterkey, nMk);

    if (srtcp) {
        // SRTCP
        createDerivationIv(iv, 0x03, r, mastersalt);
        aesKeys->Reset();
        aesKeys->CtrCrypt(zeros, key, nK, iv);

        createDerivationIv(iv, 0x05, r, mastersalt);
        aesKeys->Reset();
        aesKeys->CtrCrypt(zeros, salt, nS, iv);

        createDerivationIv(iv, 0x04, r, mastersalt);
        aesKeys->Reset();
        aesKeys->CtrCrypt(zeros, akey, nA, iv);
    }
    else {
        // SRTP
        createDerivationIv(iv, 0x00, r, mastersalt);
        aesKeys->Reset();
        aesKeys->CtrCrypt(zeros, key, nK, iv);

        createDerivationIv(iv, 0x02, r, mastersalt);
        aesKeys->Reset();
        aesKeys->CtrCrypt(zeros, salt, nS, iv);

        createDerivationIv(iv, 0x01, r, mastersalt);
        aesKeys->Reset();
        aesKeys->CtrCrypt(zeros, akey, nA, iv);
    }
    delete aesKeys;
}
