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

// According to OpenSSL Doc, the maximum size of an TLS Socket 
///#define MAX_TLS_FRAME_SIZE  16384

enum tlsProtocolType {
    TLS_PROTOCOL_UNSUPPORTED = -1,
    TLS_PROTOCOL_NULL = 0,
    TLS_PROTOCOL_CHANGE_CIPHER_SPEC = 20,
    TLS_PROTOCOL_ALERT = 21,
    TLS_PROTOCOL_HANDSHAKE = 22,
    TLS_PROTOCOL_HANDSHAKE_V2 = 0x80,
    TLS_PROTOCOL_APPLICATION = 23
};

enum tlsHandshakeMessageType {
    TLS_HANDSHAKE_HELLO_REQUEST = 0,
    TLS_HANDSHAKE_CLIENT_HELLO = 1,
    TLS_HANDSHAKE_SERVER_HELLO = 2,
    TLS_HANDSHAKE_HELLO_VERIFY_REQUEST = 3,
    TLS_HANDSHAKE_CERTIFICATE = 11,
    TLS_HANDSHAKE_SERVER_KEY_EXCHANGE = 12,
    TLS_HANDSHAKE_CERTIFICATE_REQUEST = 13,
    TLS_HANDSHAKE_SERVER_HELLO_DONE = 14,
    TLS_HANDSHAKE_CERTIFICATE_VERIFY = 15,
    TLS_HANDSHAKE_CLIENT_KEY_EXCHANGE = 16,
    TLS_HANDSHAKE_FINISHED = 20
};

enum tlsAlertType {
    TLS_ALERT_TYPE_CLOSE_NOTIFY = 0,
    TLS_ALERT_TYPE_UNEXPECTED_MESSAGE = 10,
    TLS_ALERT_TYPE_BAD_RECORD_MAC = 20,
    TLS_ALERT_TYPE_DECRYPTION_FAILED = 21,
    TLS_ALERT_TYPE_RECORD_OVERFLOW = 22,
    TLS_ALERT_TYPE_DECOMPRESSION_FAILURE = 30,
    TLS_ALERT_TYPE_HANDSHAKE_FAILURE = 40,
    TLS_ALERT_TYPE_SSL_NO_CERTIFICATE = 41,
    TLS_ALERT_TYPE_BAD_CERTIFICATE = 42,
    TLS_ALERT_TYPE_UNSUPPORTED_CERTIFICATE = 43,
    TLS_ALERT_TYPE_CERTIFICATE_REVOKED = 44,
    TLS_ALERT_TYPE_CERTIFICATE_EXPIRED = 45,
    TLS_ALERT_TYPE_CERTIFICATE_UNKNOWN = 46,
    TLS_ALERT_TYPE_ILLEGAL_PARAMETER = 47,
    TLS_ALERT_TYPE_UNKNOWN_CA = 48,
    TLS_ALERT_TYPE_ACCESS_DENIED = 49,
    TLS_ALERT_TYPE_DECODE_ERROR = 50,
    TLS_ALERT_TYPE_DECRYPT_ERROR = 51,
    TLS_ALERT_TYPE_EXPORT_RESTRICTION = 60,
    TLS_ALERT_TYPE_PROTOCOL_VERSION = 70,
    TLS_ALERT_TYPE_INSUFFICIENT_SECURITY = 71,
    TLS_ALERT_TYPE_INTERNAL_ERROR = 80,
    TLS_ALERT_TYPE_USER_CANCELLED = 90,
    TLS_ALERT_TYPE_NO_RENEGOTIATION = 100,
    TLS_ALERT_TYPE_OK = 255
};

enum tlsAlertLevel {
    TLS_ALERT_LEVEL_WARNING = 1,
    TLS_ALERT_LEVEL_FATAL = 2
};

enum dtlsProtocolState {
    DTLS_STATE_IDLE,
    DTLS_STATE_HS_START,
    DTLS_STATE_CLIENT_KEY_EXCHANGE,
    DTLS_STATE_CLIENT_KEY_EXCHANGE_ENCRYPT,
    DTLS_STATE_CLIENT_KEY_EXCHANGE_VERIFY,
    DTLS_STATE_CLIENT_KEY_EXCHANGE_DHE_PUBLIC,
    DTLS_STATE_CLIENT_KEY_EXCHANGE_DHE_PRIVATE,
    DTLS_STATE_CLIENT_CERTIFICATE_VERIFY_SIGN,
    DTLS_STATE_CLIENT_FINISH,
    DTLS_STATE_CLIENT_RESUME,
    DTLS_STATE_SERVER_ACCEPT,
    DTLS_STATE_SERVER_KEY_EXCHANGE,
    DTLS_STATE_SERVER_KEY_EXCHANGE_SIGN,
    DTLS_STATE_SERVER_KEY_EXCHANGE_DECRYPT,
    DTLS_STATE_SERVER_KEY_EXCHANGE_DHE_PUBLIC,
    DTLS_STATE_SERVER_KEY_EXCHANGE_DHE_PRIVATE,
    DTLS_STATE_SERVER_CERTIFICATE_VERIFY,
    DTLS_STATE_SERVER_FINISH,
    DTLS_STATE_SERVER_RESUME,
    DTLS_STATE_APPLICATION_DATA,
    DTLS_STATE_NEED_READ
};

enum dtlsErrorType {
    DTLS_ERROR_NONE = 0,
    DTLS_ERROR_TIMEOUT,
    DTLS_ERROR_REMOTE,
    DTLS_ERROR_HANDSHAKE,
    DTLS_ERROR_FINGERPRINT,
    DTLS_ERROR_SRTP,
    DTLS_ERROR_ENCODING,
    DTLS_ERROR_DECODING,
    DTLS_ERROR_CLOSED,
    DTLS_ERROR_SSL_INSTANCE
};

class HandshakeMsg : public istd::listElement<HandshakeMsg> {
public:
    HandshakeMsg(char * msg, int len) {
        this->msg = (char *)malloc(len);
        memcpy(this->msg, msg, len);
        this->len = len;
    };
    ~HandshakeMsg() { if(this->msg) free(this->msg); };

    char * msg;
    int len;
};

class Dtls : public IDtls, public UIoExec, public USSLSocketContext {
    IInstanceLog * const log;
    class IIoMux * iomux;
    class UDtls * user;
    class ISocketContext * context;
    class ISSLInstance * ssl;
    bool server;

    byte * sendBuffer;
    size_t sendBufferSize;
    byte * recvBuffer;
    size_t recvBufferSize;
    size_t userDataReceived;
    bool userReadPartial;

    void * userCtx;
    enum dtlsProtocolState state;
    int options;
    bool paused;
    bool associated;
    IPaddr associatedAddr;
    word associatedPort;
    int retransmissionCount;
    int retransmissionCountMax;
    int retransmissionTime;
    istd::list<class HandshakeMsg> sendQueue;

    char* cookie;
    byte cookieLen;
    bool dataSentHs;
    char* handshakeMsgRx;
    int handshakeMsgRxLen;

    void reset();
    void close(int error, bool alert);
    void sendApplicationData(char * buf, int len);
    void readApplicationData(char * buf, int len);
    void sendHandshakeFlight();
    void clearHandshakeSendQueue(bool fastconnect = false);
    void queueHandshake(tlsHandshakeMessageType messageType, char * buf, int len);
    int SendSSLData();
    void SendSSLDataChunks(void* ctx, char* buf, int len);
    void DoSSLHandshake();
    void ReadSSLData();
    void ReadAlert(byte* buf, int len);
    void ReadChangeCipherSpec();

    void ParseTxDtlsMessage(byte* sendBuffer, int len);
    void ParseTxHandshakeMessage(byte* buf, int len);
    void ParseTxHelloVerifyRequest(byte* buf, int len);

    void ParseRxDtlsMessage(byte* sendBuffer, int len);
    void ParseRxHandshakeMessage(byte* buf, int len);
    void ParseRxClientHello(byte* buf, int len);
    void ParseRxServerHelloDone();

    void ExtractSrtpKeys(byte * srtpKeys, const char * profile) override;
    void GetAlertDescription(char* buf, tlsAlertType type);

public:
    Dtls(class IIoMux * iomux, class UDtls * user, void * userCtx, IInstanceLog * const log);
	virtual ~Dtls();

    void IoExec(void* execContext) override;

    void DtlsInitialize(const byte * certificateFingerprint, size_t certificateFingerprintLen) override;
    void DtlsConnect(bool server, int options, const char * remoteAddress, class ISocketContext * context, byte * fingerprint, byte fingerprintLen, bool paused) override;
    void DtlsUnpause() override;
    void DtlsTimeout() override;
    void DtlsClose() override;
    void DtlsSendApplicationData(char * buf, int len) override;
    bool DtlsRecv(char * buf, int len, IPaddr remoteAddr, word remotePort) override;
    void DtlsAbortConnect() override;
};
