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

#define RTP_ERROR_COUNT_TIMEOUT     (10*50*20)
#define RTP_ERROR_JITTER_LIMIT      (200*8)

#define RTCP_PERIOD (5*50*20)

#define DTLS_RTP_CONTEXT        3
#define DTLS_RTCP_CONTEXT       4

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

enum PeerStatus {
    PEER_CLOSED = 0,
    PEER_NOT_BOUND,
    PEER_BOUNDING_RTP,
    PEER_BOUND_RTP,
    PEER_BOUNDING_RTCP,
    PEER_BOUND
};

class CloseSocket : public USocket, public ITask, public UTask
{
    class IInstanceLog * const log;
    class IIoMux * const iomux;
    class ISocket * socket;
    ISocketProvider * const socketProvider;
    char dstAddr[128];

    void TaskComplete(class ITask * const task) {};
    void TaskFailed(class ITask * const task) {};

public:
    CloseSocket(class IIoMux * const iomux, ISocketProvider * socketProvider, bool IPv6, class ISocket * const socket, char * addr, int port, class IInstanceLog * const log);
    ~CloseSocket();
    void Start(class UTask * user);

    void SocketConnectComplete(ISocket * const socket);
    void SocketShutdown(ISocket * const socket, shutdownreason_t reason);
    void SocketSendResult(ISocket * const socket);
    void SocketRecvCanceled(ISocket * const socket, void * buf);

    class ISocket * const discSocket;
    bool ipv6;
};

class DnsRequest : public UDns, public ITask, public UTask
{
    class IInstanceLog * const log;
    IDns * const dns;

    void TaskComplete(class ITask * const task);
    void TaskFailed(class ITask * const task) {};

public:
    DnsRequest(const char * hostname, IDns * const dns, class IInstanceLog * const log, dword timeout, dword flags, word servicePort);
    ~DnsRequest();
    void Start(class UTask * user);

    void DnsGetHostByNameResult(const char * addr, bool isIPv6);

    char * hostname;
    char * addr;
    bool isIPv6;
    dword timeout;
    dword flags;
    word servicePort;
};

class IceTurnError
{
public:
    IceTurnError(int code, const char * errorText, const char * src, char * hostname = NULL, char * addr = NULL, int port = 0)
    {
        this->code = code;
        this->errorText = _strdup(errorText);
        this->src = _strdup(src);
        this->hostname = _strdup(hostname);
        this->addr = _strdup(addr);
        this->port = port;
    }
    ~IceTurnError()
    {
        if(errorText) free(errorText);
        if(src) free(src);
        if(hostname) free(hostname);
        if(addr) free(addr);
    }

    int code;
    char * errorText;
    char * src;
    char * hostname;
    char * addr;
    int port;
};

class MediaChannel: public Ice, public USocket, public TurnUser, public UTimer, public UMediaEndpoint, public UDtls, public USctpRtc, public SctpRtc,
                    public MediaRecordingUser, public UIoExec, public istd::listElement<MediaChannel> {
public:
    class IInstanceLog * const log;
    IDns * dns;
    class MediaConfig * localCfg;
    class Media * media;
    class IIoMux * iomux;
    class IMediaEndpoint * endpoint;
    ISocketProvider * udpSocketProvider;
    ISocketProvider * tcpSocketProvider;
    class ISocketContext * socketContext;
    class ISocket * mcSocket;
    class ISocket * socket;
    class ISocket * rtcpSocket;
    class ISocket * tcpSocket;
    class ISocket * tcpAltSocket;
    class ISocket * rtcpTcpSocket;
    class ISocket * rtpTcpListen;
    class ISocket * rtpTcpSocket;
    IPaddr rtpTcpRemoteAddr;
    int rtpTcpRemotePort;
    bool ipv6;
    bool rtpOverTcp;
    enum MediaType mediaType;

    class CloseSocket * closeRtp;
    class CloseSocket * closeRtcp;
    class CloseSocket * closeMc;

    int startPort;
    int maxPort;
    byte dtmfXmitPt;
    byte dtmfRecvPt;
    byte redRecvPt;

    int dropMediaRx;
    int numAllowPacketsRx;
    int dropSeveralRx;
    int dropOffsetRx;

    class ISrtpSession * srtpSend;
    class ISrtpSession * srtpRecv;
    class ISrtpSession * srtcpSend;
    class ISrtpSession * srtcpRecv;
    class ITimer * sctpHandshakeTimer;
    class ITimer * sctpRetransmitTimer;
    class ITimer * iceTimer;
    class ITimer * dtlsRtpTimer;
    class ITimer * dtlsRtcpTimer;
    class ITimer * rtcpTimer;
    class ITimer * dtmfTimer;
    class ITimer * errorTimer;

    class IDtls * dtlsRtp;
    class IDtls * dtlsRtcp;
    enum SetupRole remoteSetupRole;

    class MediaRecording record;

    byte * data;
    byte * rtcpData;
    byte * tcpData;
    byte rtpTcpLen[2];
    size_t indexTcpBuffer;
    byte * rtcpTcpData;
    size_t indexTcpBufferRtcp;
    class HTTPProxies * proxies;
    class HTTPProxy * proxy;
    char * tunnelTargetHost;
    word tunnelTargetPort;
    bool connectTunnel;
    bool connectTunnelRtcp;
    bool disconnecting;
    bool rtpRecvUpdate;

    class ITimer * rtxRtcpTimer;
    word resendSequence;
    word resendSequenceNumberDiff;
    int resendNackCounter;
    byte firCommandSequence;
    bool resendFirCommandSequenceSet;
    int resendFirCounter;

    class ITimer * turnRtpTimer;
    class ITimer * turnRtcpTimer;
    class Turn turnRtp;
    class Turn turnRtcp;
    char * iceTurnHostname;
    char * iceTurnAddr;
    int iceTurnPort;
    bool iceTurnTcp;
    char * iceTurnAltHostname;
    char * iceTurnAltAddr;
    int iceTurnAltPort;
    bool iceTurnAltTcp;

    int maxRtpVideoData;
    byte errorCount;
    int errorTotal;
    bool errorReported;    // Only report error once per RTP channel
    dword startTimestamp;
    word xSeq;
    dword ssrc;
    dword xPackets;
    dword xOctets;
    word rxPayload;
    word txPayload;
    int rTotal;
    int rLost;
    int rWrongPt;
    int rAuthFailed;
    word rDuplicate[4];
    dword remoteSsrc;
    dword oldRemoteSsrc;
    dword rSeq;
    dword rrTimestamp;
    dword rsTimestamp;
    dword rJitter;
    dword lsr;
    dword lsrTimestamp;
    word timestampRateMultiplier;

    dword xDtmfTimestamp;
    dword xDtmfDuration;
    dword rDtmfTimestamp;
    bool rDtmfEnd;
    int xmitTimestampDelta;
    bool xmitTimestampDeltaSet;
    word dtmfXmitCount;
    byte dtmfBuf[16];
    byte dtmfBufLen;

    dword lastLoss;
    dword lastJitterT38Speed;
    dword lastRoundTripT38Pages;
    dword lastRecvLoss;
    int dropMediaTx;
    int dropOffset;
    int numAllowPacketsTx;
    int dropSeveralTx;
    IFileWrite* rtpStream;
    IFileWrite* rtpStreamRx;
    bool recordRtpStream;

    byte rFirCommandSequence;
    bool rFirCommandSequenceSet;

    char * stunServers;
    char * turnServers;
    bool initComplete;
    bool iceComplete;
    bool waitBindRtp;
    bool rtpBindFailed;
    bool waitBindRtcp;
    bool rtcpBindFailed;
    class DnsRequest * reqStun1;
    class DnsRequest * reqStun2;
    class DnsRequest * reqStun3;
    class DnsRequest * reqStun4;
    class DnsRequest * reqTurn1;
    class DnsRequest * reqTurn2;
    bool mediaTurn;
    bool sendonly;
    bool mute;
    bool noRtcp;
    IPaddr mediaAddr;
    word mediaPort;
    word mediaRtcpPort;
    IPaddr oldMediaAddr;
    word oldMediaPort;
    word oldMediaRtcpPort;
    dword redirectSsrc;
    IPaddr redirectAddr;
    word redirectPort;
    byte redirectCounter;
    bool redirectDone;
    bool primary;

    class IceTurnError * iceTurnError;
    bool iceControlling;
    bool iceDtlsDone;
    struct ChannelIce remoteIce;
    dword connectedTs;

    int execCnt;

    void initialized();

    void IceInitialized(struct ChannelIce * ice);

    void GetStunServers(const char * stunServers);
    void ParseStunServer1(const char * stunServer);
    void ParseStunServer2(const char * stunServer);
    void GetTurnServers(const char * turnServers);

    class ISocket *rtp4() { return rtpOverTcp ? rtpTcpListen : socket; };
    class ISocket *rtcp4() { return rtcpSocket; };
    class ISocket *rtpTcp4() { return rtpTcpSocket; };
    class ISocket *rtp6() { return rtpOverTcp ? rtpTcpListen : socket; };
    class ISocket *rtcp6() { return rtcpSocket; };
    class ISocket *rtpTcp6() { return rtpTcpSocket; };
    class ISocket *tcp4() { return iceTurnTcp ? tcpSocket : socket; };
    class ISocket *rtcpTcp4() { return iceTurnTcp ? rtcpTcpSocket : rtcpSocket; };
    bool AddIceCandidate(int type, IPaddr localAddr, int localRtp, int localRtcpPort, IPaddr relayedAddr, int relayedRtpPort, int relayedRtcpPort, unsigned int rtpPriority, unsigned int rtcpPriority);
    void IceConnected(IPaddr localAddr, word localRtpPort, word localRtcpPort, IPaddr remoteAddr, word remoteRtpPort, word remoteRtcpPort, bool turnCh, byte * fingerprint, int fingerprintLen, bool controlling);
    void IceSendto(class ISocket * socket, char * data, int len, IPaddr addr, word port, bool turn);
    bool IceCanNominate();

    void DnsRequestComplete(class DnsRequest * task);
    void CloseSocketComplete(class CloseSocket * task);

    class UTaskTemplate<MediaChannel, class DnsRequest> taskDns;
    class UTaskTemplate<MediaChannel, class CloseSocket> taskCloseSocket;

    void mediaRecvEvent(ISocket * const socket, void * data, size_t len, IPaddr srcAddr, word srcPort);
    void mediaRtcpRecvEvent(ISocket * const socket, void * data, size_t len, IPaddr srcAddr, word srcPort);

    void resetRtcp();
    void errors(int count);

    void TimerOnTimeout(ITimer * timer);
    void DtlsTimerStart(void * ctx, int to);
    void DtlsTimerStop(void * ctx);
    void IceTimerStart(int to);
    void IceTimerStop();

    void activateMediaConfig();
    bool doNatWorkaround(IPaddr srcAddr, word srcPort, dword srcSsrc);

    void StartXmitRtpDtmf();
    void XmitRtpDtmf(byte digit, word duration, bool start, bool end);

    void RtpRecvResult(char * buf, int len, dword timestamp, short sequenceNumberDiff, bool marker) override;
    void RtpSendResult() override;
    void RtpDtmf(char digit, byte pt);
    void RecordingOn(bool on);
    void RtcpSend(char * buf, int len) override;
    void SctpRecvResult(char * buf, int len) override;
    void SctpSendResult() override;
    void SctpSend(char * buf, int len, unsigned num) override;
    word GetRtpSequence() override { return xSeq; };
    void EventReceived(enum MediaEndpointEvent event, void* ctx) override;

    void DtlsInitialized(void* ctx, const byte* fingerprint, size_t fingerprintLen) override;
    void DtlsConnected(void * ctx) override;
    void DtlsConnectionClosed(void * ctx, int error) override;
    void DtlsSend(void * ctx, char * buf, int len) override;
    void DtlsRecvApplicationData(void * ctx, char * buf, int len) override;
    void DtlsSrtpKeys(void* ctx, byte * keys, const char * profile) override;

    void SctpDtlsSend(char * buf, int len);
    void SctpRecvApplicationData(char * buf, int len);
    void SctpRecvAck(unsigned num);
    void SctpRetransmitStart(unsigned to) { sctpRetransmitTimer->Start(to); };
    void SctpRetransmitStop() { sctpRetransmitTimer->Cancel(); };
    void SctpHandshakeStart(unsigned to) { sctpHandshakeTimer->Start(to); };
    void SctpHandshakeStop() { sctpHandshakeTimer->Cancel(); };
    void SctpRtcConnected(bool err);

    bool IsSocketClosed(class ISocket * socket);

    void ConnectMulticast(char * addr, int port);

#ifdef SRTCP_TEST_MSG
    void srtcpTestMsg(void);
#endif

    void SendRtcpNack(word sequence, short sequenceNumberDiff, bool retransmission);
    void RtcpSendFirRequest(bool retransmission);

    void RecordingStopped(const char * error) override;
    void RecordingStarted() override;
    void RecordingCloseComplete() override;

    void IoExec(void* execContext) override;
    int mediaErrorMessage;

public:
    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);
    ~MediaChannel();

    void tryInitialized();
    void disconnect(class MediaConfig * remoteMediaConfig, enum IceAbortReason disc);
    void disconnectIcePath(enum IceAbortReason disc);
    void stopStunTurnDiscovery();
    void connect(class MediaConfig * remoteMediaConfig, bool iceRemoteRole, enum SetupRole remoteDtlsRole);
    bool IceDtlsConnectDone() { return iceDtlsDone; };

    void MediaSend(const void * buf, size_t len, dword timestamp, unsigned num, enum MediaType mediaType, bool isConference);
    void MediaForward(const void * buf, size_t len, dword timestamp, short sequenceNumberDiff, bool marker);

    void SocketConnectComplete(ISocket * const socket) override;
    void SocketBindResult(ISocket * const socket, const char * localAddr, word localPort) override;
    void SocketListenResult(ISocket * const socket, const char * remoteAddr, word remotePort) override;
    void SocketAcceptComplete(ISocket * const socket) override;
    void SocketShutdown(ISocket * const socket, shutdownreason_t reason) override;
    void SocketSendResult(ISocket * const socket) override;
    void SocketRecvResult(ISocket * const socket, void * buf, size_t len) override;
    void SocketRecvFromResult(ISocket * const socket, void * buf, size_t len, IPaddr srcAddr, word srcPort) override;
    void SocketRecvCanceled(ISocket * const socket, void * buf) override;

    void RtpSend(char * buf, int len, dword timestamp, short sequenceNumberDiff, bool marker = false);

    void TurnSend(void * ctx, char * data, int len);
    void TurnTimerStart(void * ctx, int to);
    void TurnTimerStop(void * ctx);
    void TurnAllocated(void * ctx);
    void TurnReleased(void * ctx);
    void TurnFailed(void * ctx, dword code, const char * errorText);

    void RewriteIceCredentials(const char* usr, const char* pwd);
    void CleanSrtpKeys();

    AudioCoder audioCoder;
};

class Media : public IMedia, public USocket, public UIoExec, public IInstanceLog  {
    friend class MediaChannel;

    class IInstanceLog * const log;
    UMedia * user;
    class IIoMux * iomux;
    class MediaConfig *mediaConfig;
    class MediaConfig *remoteMediaConfig;
    ISocketProvider * udpSocketProvider;
    ISocketProvider * tcpSocketProvider;
    class ISocketContext * socketContext;
    IDns * dns;

    class LocalAddresses * interfaces;

    char * relayedAddrCand[CHANNEL_MAX_CANDIDATE];
    char * srflxAddrCand[CHANNEL_MAX_CANDIDATE];

    bool closing;
    bool nominating; 

    char * stunServers;
    char * turnServers;
    char * turnUsername;
    char * turnPassword;
    word minPort;
    word maxPort;
    istd::list<class MediaChannel> channels;
    bool iceDtlsConnected;
    class MediaChannel * channelConnected;
    class MediaChannel * multicastChannel;

    class DnsRequest * reqPBX;
    void dnsRequestComplete(class DnsRequest * task);
    class UTaskTemplate<Media, class DnsRequest> taskDns;
    class ISocket* sockPBX;

    bool turnOnly;
    bool iceNoHost;
    bool rtpOverTcp;
    bool stunSlow;
    bool iceDisabled;
    int foundation;
    int dropMediaTx;
    int dropMediaRx;
    bool noVpnAddresses;
    char * currentUserSip;
    istd::list<class CodecAssignments>* assignmentsList;
    class IceTurnError * iceTurnError4;
    class IceTurnError * iceTurnError6;

    enum MediaType type;

    class ISocketProvider * tlsSocketProvider;
    char * recordingUrl;
    char * recordingUsername;
    char * recordingPassword;
    bool recordOn;
    OS_GUID guidMedia;

    char * excludeInfs[CHANNEL_MAX_CANDIDATE];

    const char * GetAppName() const { return "media"; };
    const char * GetAppDomain() const { return ""; };

    char * GenerateRandomString(int len);
    unsigned AvailableCoderCount();
    int AvailableCoder(unsigned coderNumber);
    void CreateMediaCodecs();
    int GetCodecPayloadType(int coder);

    void DeleteAll();

    bool recordRtpStream;

    bool ExcludeMediaInterface(const char * addr);
    void ParseExcludeInterfaces(const char * excludeInterfaces);

public:
    Media(class IIoMux * iomux, class UMedia * const user, class IInstanceLog * const log);
    ~Media();

    enum Error { Success = 0, IceFailed, DtlsFailed, SctpFailed, NoCoder, UnknownCoder, NoRemoteConfig, MessageError, NotReachable };

    void tryMediaInitialized(bool stun, bool turn);
    bool addIceCandidate(class IceCandidate * cand);
    void channelDisconnected(class MediaChannel * ch);
    void connectResult(class MediaChannel * ch, enum Error err, const class IceTurnError * iceTurnError);

    void RtpRecv(const char * src_addr, word src_port, dword ssrc, word pt) { if (user) user->MediaRtpRecv(this, src_addr, src_port, ssrc, pt); };
    void RtpRecvResult(char * buf, int len, dword timestamp, short sequenceNumberDiff, bool marker) { if (user) user->MediaRtpRecvResult(this, buf, len, timestamp, sequenceNumberDiff, marker); };
    void SctpRecvResult(char * buf, int len) { if (user) user->MediaSctpRecvResult(this, buf, len); };
    void SctpRecvAck(unsigned num) { if (user) user->MediaSctpRecvAck(this, num); };
    void RtpSendResult() { if (user) user->MediaRtpSendResult(this); };
    void SctpSendResult() { if (user) user->MediaSctpSendResult(this); };
    void EventReceived(enum MediaEndpointEvent event, void * ctx) { if (user) user->MediaEventReceived(this, event, ctx); };
    void RtcpSendFirRequest(bool retransmission);

    void RecordingStopped(const char * error) { if (user) user->RecordingStopped(error);};
    void RecordingStarted() { if (user) user->RecordingStarted();};

    void SocketConnectComplete(ISocket * const socket) override;
    void SocketShutdown(ISocket * const socket, shutdownreason_t reason) override;
    void SocketRecvCanceled(ISocket * const socket, void * buf) override {};

    void IoExec(void * context) override;

    bool iceCanNominate() { 
        if(!nominating) { 
            nominating = true; 
            return true; 
        } 
        else return false; 
    };

    bool IsClosing() override { return closing; }

    void Initialize(ISocketProvider * udpSocketProvider, ISocketProvider * tcpSocketProvider, class ISocketContext * context, 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) override;
    void Connect(class MediaConfig *remoteMediaConfig, bool iceRemoteRole, enum SetupRole remoteDtlsRole) override;
    void RtpSend(const void * buf, size_t len, dword timestamp, enum MediaType mediaType, bool isConference) override;
    void RtpForward(const void * buf, size_t len, dword timestamp, short sequenceNumberDiff, bool marker) override;
    void RtcpSend(const void * buf, size_t len) override;
    void SendFIR() override;
    void RtpDtmf(char digit, byte pt) override;
    void RecordingOn(bool on) override;
    void SctpSend(const void * buf, size_t len, unsigned num) override;
    void Recv(void * buf, size_t len, bool recvPartial = false) override;
    void Close() override;
    void RewriteIceCredentials(const char* usr, const char* pwd) override;
    void CleanSrtpKeys() override;
};

enum srtp_cryptosuite_id {
    NULL_MODE = 0x00,
    AES_CM_128_HMAC_SHA1_32 = 0x21,
    AES_CM_128_HMAC_SHA1_80 = 0x22,
    AES_CM_192_HMAC_SHA1_32 = 0x31,
    AES_CM_192_HMAC_SHA1_80 = 0x32,
    AES_CM_256_HMAC_SHA1_32 = 0x41,
    AES_CM_256_HMAC_SHA1_80 = 0x42
};

enum tls_ciphersuite_id {
    TLS_NULL = 0,
    TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000a,
    TLS_RSA_WITH_AES_128_CBC_SHA = 0x002f,
    TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035,
    TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033,
    TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039,
    TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xc013,
    TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xc014,
    TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xc009,
    TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xc00a,
    TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003c,
    TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x003d,
    TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x0067,
    TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006b,
    TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xc027,
    TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xc028,
    TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xc023,
    TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xc024,
    TLS_EMPTY_RENEGOTIATION_INFO_SCSV = 0x00ff
};

struct SrtpHalfContext {
    srtp_cryptosuite_id csid;
    byte nMk;
    byte nMs;
    byte nB;
    byte nK;
    byte nS;
    byte nA;
    byte nTag;
    byte nMki;

    byte key[32];
    byte salt[14];
    byte akey[20];
    byte iv[16];
    dword maxKeyLifetime;

    unsigned int BlockLen(unsigned int cipherid) 
    {
        switch (cipherid) {
            case AES_CM_128_HMAC_SHA1_32:
            case AES_CM_192_HMAC_SHA1_32:
            case AES_CM_256_HMAC_SHA1_32:
            case AES_CM_128_HMAC_SHA1_80:
            case AES_CM_192_HMAC_SHA1_80:
            case AES_CM_256_HMAC_SHA1_80:
            case TLS_RSA_WITH_AES_128_CBC_SHA:
            case TLS_RSA_WITH_AES_256_CBC_SHA:
            case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
            case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
            case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
            case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
            case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
            case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
            case TLS_RSA_WITH_AES_128_CBC_SHA256:
            case TLS_RSA_WITH_AES_256_CBC_SHA256:
            case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
            case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
            case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
            case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
            case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
            case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
                return 16;
                break;
            case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
                return 8;
                break;
        }
        return 0;
    }

    unsigned int KeyLen(unsigned int cipherid, bool includingKey, bool includingSalt) 
    {
        switch(cipherid) {
            case AES_CM_128_HMAC_SHA1_32:
            case AES_CM_128_HMAC_SHA1_80:
                return (includingKey ? 16 : 0) + (includingSalt ? 14 : 0);
                break;
            case AES_CM_192_HMAC_SHA1_32:
            case AES_CM_192_HMAC_SHA1_80:
                return (includingKey ? 24 : 0) + (includingSalt ? 14 : 0);
                break;
            case AES_CM_256_HMAC_SHA1_32:
            case AES_CM_256_HMAC_SHA1_80:
                return (includingKey ? 32 : 0) + (includingSalt ? 14 : 0);
                break;
            case TLS_RSA_WITH_AES_128_CBC_SHA:
            case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
            case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
            case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
            case TLS_RSA_WITH_AES_128_CBC_SHA256:
            case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
            case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
            case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
                return (includingKey ? 16 : 0) + (includingSalt ? 0 : 0);
                break;
            case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
                return (includingKey ? 24 : 0) + (includingSalt ? 0 : 0);
                break;
            case TLS_RSA_WITH_AES_256_CBC_SHA:
            case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
            case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
            case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
            case TLS_RSA_WITH_AES_256_CBC_SHA256:
            case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
            case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
            case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
                return (includingKey ? 32 : 0) + (includingSalt ? 0 : 0);
                break;
        }
        return 0;
    }

    unsigned int MacLen(unsigned int cipherid) 
    {
        switch (cipherid) {
            case AES_CM_128_HMAC_SHA1_32:
            case AES_CM_192_HMAC_SHA1_32:
            case AES_CM_256_HMAC_SHA1_32:
                return 4;
                break;
            case AES_CM_128_HMAC_SHA1_80:
            case AES_CM_192_HMAC_SHA1_80:
            case AES_CM_256_HMAC_SHA1_80:
                return 10;
                break;
            case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
            case TLS_RSA_WITH_AES_128_CBC_SHA:
            case TLS_RSA_WITH_AES_256_CBC_SHA:
            case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
            case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
            case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
            case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
            case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
            case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
                return 20;
                break;
            case TLS_RSA_WITH_AES_128_CBC_SHA256:
            case TLS_RSA_WITH_AES_256_CBC_SHA256:
            case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
            case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
            case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
            case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
                return 32;
                break;
            case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
            case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
                return 48;
                break;
        }
        return 0;
    }

    void setCsid(srtp_cryptosuite_id csid) {
        this->csid = csid;
        nB = BlockLen(csid);
        nMk = nK = KeyLen(csid, true, false);
        nMs = nS = KeyLen(csid, false, true);
        nTag = MacLen(csid);
        nA = 20;
        nMki = 0;
    }
};

struct SrtpContext {
    dword roc;
    word lastSeq;
    bool lastSeqSet;
    bool srtcpEncrypted;
    dword srtcpIndex;
    struct SrtpHalfContext srtp;
    struct SrtpHalfContext srtcp;

    SrtpContext(srtp_cryptosuite_id srtpCsid = NULL_MODE, srtp_cryptosuite_id srtcpCsid = NULL_MODE) {
        roc = 0;
        lastSeq = 0;
        lastSeqSet = false;
        srtcpEncrypted = true;
        srtcpIndex = 0;
        srtp.maxKeyLifetime = 0xffffffff;
        srtcp.maxKeyLifetime = 0xffffff;
        srtp.setCsid(srtpCsid);
        srtcp.setCsid(srtcpCsid);
    }
};

class SrtpSession : public ISrtpSession {
    byte a1[64];
    byte a2[64];
    byte a3[64];
    byte a4[64];

    class Cipher * aesRtp;
    class Cipher * aesRtcp;

    // SRTP library functions
    void createSrtpIv(byte* iv, byte* ssrc, byte* seq, dword roc, byte* srtpSalt);
    void createSrtcpIv(byte* iv, byte* ssrc, dword srtcpIndex, byte* srtcpSalt);
    dword calculateRoc(dword roc, word packetSeq, word lastSeq);

    void hmacSha1(byte* data, unsigned int len, byte *digest, byte * inner, byte * outer); 

    void DeriveKeys(bool srtcp, byte* masterkey, byte* mastersalt, byte* key, byte* salt, byte* akey, byte nK, byte nS, byte nA, byte nMk);
    void createDerivationIv(byte* iv, byte label, byte* r, byte* mastersalt);

public:
    SrtpSession(srtp_cryptosuite_id srtpCsid, byte * srtpMasterkey, byte * srtpMastersalt);
    ~SrtpSession();

    byte * Protect(byte * header, int hLen, byte * payload, int pLen, int * oLen);
    int Unprotect(char * srtpPacket, int len);
    byte * SrtcpProtect(byte * rtcpPacket, int pLen, int * olen);
    int SrtcpUnprotect(char * srtcpPacket, int len);

    struct SrtpContext ctx;
};

