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

#define CHANNEL_MAX_CANDIDATE       10

#define CHANNEL_ICE_USR_LEN         4
#define CHANNEL_ICE_PWD_LEN        22

class IceCheckBtreeConnectionRtp : public btree {
    int btree_compare(void * key);
    int btree_compare(class btree * b);
};

class IceCheckBtreeConnectionRtcp : public btree {
    int btree_compare(void * key);
    int btree_compare(class btree * b);
};

class IceCheckBtreeConnectionRtpTcp : public btree {
    int btree_compare(void * key);
    int btree_compare(class btree * b);
};

class IceCheckBtreePriority : public btree {
    int btree_compare(void * key);
    int btree_compare(class btree * b);
};

struct ChannelCandidate {
    IPaddr addr;
    IPaddr relatedAddr;
    word rtpPort;
    word rtcpPort;
    word relatedRtpPort;
    word relatedRtcpPort;
    dword rtpPriority;
    dword rtcpPriority;
    byte type;
    bool mDns;
    char foundation[32+1];
};

struct IceLocalCredentials {
    IceLocalCredentials();
    char usr[CHANNEL_ICE_USR_LEN+1];
    char pwd[CHANNEL_ICE_PWD_LEN+1];
};

struct ChannelIce {
    ChannelIce();
    void clear();
    void copy(const struct ChannelIce * from);
    void copy(const struct IceLocalCredentials * localCredentials);
    void save(struct ChannelIce * & to);
    static int trace(const struct ChannelIce * ice, const char * name, char * buffer, int len);

    struct _common {
        byte count;
        bool lite;
        bool rtcpMux;         // RTP/RTCP multiplexing supported
        byte setupRole;       // connection-oriented media and/or DTLS handshake
        byte fingerprintLen;  // 16=md5, 20=sha-1, 28=sha-224, 32=sha-256, 48=sha-384, 64=sha-512
        byte fingerprint[64];
        char usr[256 + 1];
        char pwd[256 + 1];
    } common;
    struct ChannelCandidate candidate[CHANNEL_MAX_CANDIDATE];
};

class IceCheck : public istd::listElement<IceCheck>, public IceCheckBtreeConnectionRtp, public IceCheckBtreeConnectionRtcp, public IceCheckBtreeConnectionRtpTcp, public IceCheckBtreePriority {
public:
    IceCheck(ChannelCandidate * local, ChannelCandidate * remote, bool controlling, class ISocket * rtpSocket, class ISocket * rtcpSocket, bool turn);
    virtual ~IceCheck();

    void createStunRtp(const char * lfrag, const char * rfrag, const char * password, bool controlling, ulong64 tiebreaker, bool nominating, bool stunDraft2);
    void createStunRtcp(const char * lfrag, const char * rfrag, const char * password, bool controlling, ulong64 tiebreaker, bool nominating, bool stunDraft2);
    int trace(char * buffer, int len, const char * prefix);

    struct iceCheckConnection {
        IPaddr localAddr;
        word localRtpPort;
        word localRtcpPort;
        IPaddr remoteAddr;
        word remoteRtpPort;
        word remoteRtcpPort;
    } connection;

    struct iceCheckPriority {
        ulong64 rtp;
        ulong64 rtcp;
        dword rtpReflexive;
        dword rtcpReflexive;
        dword dummy;
    } priority;

    enum iceCheckState {
        frozen,
        waiting,
        inProgress,
        succeeded,
        failed,
        nominated,
        valid,
        nominating
    } rtpState, rtcpState;

    bool rtpNominated;
    bool rtcpNominated;

    OS_GUID rtpId;
    OS_GUID rtcpId;
    char * stunRtpRequest;
    int stunRtpRequestLen;
    char * stunRtcpRequest;
    int stunRtcpRequestLen;
    byte rtpRetry;
    byte rtcpRetry;
    bool turn;
    class ISocket * rtpSocket;
    class ISocket * rtcpSocket;
};

class Ice {
    class IInstanceLog * const log;

    OS_GUID stun1RtpId;
    OS_GUID stun1RtcpId;
    OS_GUID stun2RtpId;
    OS_GUID stun2RtcpId;
    OS_GUID stun3RtpId;
    OS_GUID stun3RtcpId;
    OS_GUID stun4RtpId;
    OS_GUID stun4RtcpId;

    int stun1RtpRequestLen;
    char * stun1RtpRequest;
    int stun1RtcpRequestLen;
    char * stun1RtcpRequest;
    int stun2RtpRequestLen;
    char * stun2RtpRequest;
    int stun2RtcpRequestLen;
    char * stun2RtcpRequest;
    int stun3RtpRequestLen;
    char* stun3RtpRequest;
    int stun3RtcpRequestLen;
    char* stun3RtcpRequest;
    int stun4RtpRequestLen;
    char* stun4RtpRequest;
    int stun4RtcpRequestLen;
    char* stun4RtcpRequest;

    ChannelCandidate stun1;
    ChannelCandidate stun2;
    ChannelCandidate stun3;
    ChannelCandidate stun4;

    class btree * iceCheckConnectionRtp;
    class btree * iceCheckConnectionRtcp;
    class btree * iceCheckConnectionRtpTcp;
    class btree * iceCheckPriority;

    class IceCheck * currentCheck;
    class IceCheck * nominatedCheck;
    int activeChecks;
    bool waitLast;
    bool nominating;
    byte prflxCheckCount;

    IPaddr iceConnectedLocalAddr;
    word iceConnectedLocalRtpPort;
    word iceConnectedLocalRtcpPort;
    IPaddr iceConnectedRemoteAddr;
    word iceConnectedRemoteRtpPort;
    word iceConnectedRemoteRtcpPort;

    bool iceRtcp;
    bool iceGatheredHost;
    bool iceGatheredStun;
    bool iceGatheredTurn;
    dword iceCandidateCounter;

    bool iceControlling;
    bool iceRoleChanged;
    bool iceStunDraft2;
    ulong64 iceTiebreaker;
    enum { idle, gathering, waiting, connecting, completed } iceState;

    int interfaceIndex;
    int totalNumInterfaces;

    struct ChannelIce * iceLocalConnected;
    struct ChannelIce iceLocal;
    struct ChannelIce iceRemote;

    unsigned int stunNumTry;

    istd::list<IceCheck> preparedChecks;

    virtual class ISocket *rtp4() = 0;
    virtual class ISocket *rtcp4() = 0;
    virtual class ISocket *rtpTcp4() = 0;
    virtual class ISocket *rtp6() = 0;
    virtual class ISocket *rtcp6() = 0;
    virtual class ISocket *rtpTcp6() = 0;
    virtual class ISocket *tcp4() = 0;
    virtual class ISocket *rtcpTcp4() = 0;
    virtual void IceTimerStart(int to) = 0;
    virtual void IceTimerStop() = 0;
    virtual void IceInitialized(struct ChannelIce * channel) = 0;
    virtual bool AddIceCandidate(int type, IPaddr localAddr, int localRtp, int localRtcpPort, IPaddr relayedAddr, int relayedRtpPort, int relayedRtcpPort, unsigned int rtpPriority, unsigned int rtcpPriority) = 0;
    virtual void IceConnected(IPaddr localAddr, word localRtpPort, word localRtcpPort, IPaddr remoteAddr, word remoteRtpPort, word remoteRtcpPort, bool turnCh, byte * fingerprint, int fingerprintLen, bool controlling) = 0;
    virtual void IceSendto(class ISocket * socket, char * data, int len, IPaddr addr, word port, bool turn) = 0;
    virtual bool IceCanNominate() = 0;

    void addCheck(bool tcp, class IceCheck * check);
    void removeCheck(bool tcp, class IceCheck * check);
    class IceCheck * findCheck(bool rtcp, bool tcp, IPaddr localAddr, word localPort, IPaddr remoteAddr, word remotePort);

    void clearStun();
    void clearChecks();

    void iceNextCheck();
    void iceHostInitialized();
    void iceStunInitialized(ChannelCandidate * stun, ChannelCandidate * stun2);
    void iceTryInitialized();
    void iceConnected(bool turnCh);
    void iceSwitchRole();
    void iceSwitchToStunDraft2();

    void addHostCandidate(IPaddr addr, byte type);
    void addSrflxCandidate(ChannelCandidate * ch);
    void addRelayCandidate(IPaddr rtpAddr, word rtpPort, word rtcpPort, IPaddr relatedRtpAddr, word relatedRtpPort, word relatedRtcpPort);
    void addSigCandidate();

public:
    Ice(class IIoMux * iomux, class IInstanceLog * const log, int interfaceIndex, int totalNumInterfaces);
    virtual ~Ice();

    void iceTimeout();
    void iceInitialize(bool useRtcp, byte * fingerprint, int fingerprintLen);
    void iceConnect(struct ChannelIce * ice, bool controlling, bool stunDraft2);
    void iceAbortStun();
    void iceAbortConnect(enum IceAbortReason disc);
    void showIceCandidates();
    bool iceStunRecv(class ISocket * socket, IPaddr srcAddr, word srcPort, IPaddr dstAddr, word dstPort, char * data, int len, word turnCh);
    void iceTurnInitialized(IPaddr rtpAddr, word rtpPort, word rtcpPort, IPaddr relatedRtpAddr, word relatedRtpPort, word relatedRtcpPort, IPaddr mappedRtpAddr, word mappedRtpPort, word mappedRtcpPort);
    void iceHostAddr(IPaddr addr, byte type);
    void icePrepareKeepAlive(bool server1, bool server2, bool server3, bool server4);
    void iceStartKeepAlive();
    void iceRewriteCredentials(const char* usr, const char* pwd);

    class IIoMux * iomux;
    IPaddr localAddrStorage;
    char * iceLocalAddr;
    int iceLocalPort;
    int iceLocalRtcpPort;
    IPaddr stun1AddrStorage;
    char * iceStun1Addr;
    int iceStun1Port;
    IPaddr stun1Addr2Storage;
    char* iceStun1Addr2;
    int iceStun1Port2;

    IPaddr stun2AddrStorage;
    char * iceStun2Addr;
    int iceStun2Port;
    IPaddr stun2Addr2Storage;
    char* iceStun2Addr2;
    int iceStun2Port2;

    bool iceIp4Preference;
    bool iceDisabled;
    bool iceSlow;
    bool turnOnly;
    bool iceNoHost;
    bool rtcpMux;
};
