/*
 * Copyright (C) 2017 Teluu Inc. (http://www.teluu.com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 */

#include <pjmedia/clock.h>
#include <pjmedia/sdp.h>
#include <pjmedia/transport_ice.h>
#include <pj/errno.h>
#include <pj/rand.h>
#include <pj/ssl_sock.h>

/* 
 * Include OpenSSL headers
 */
#include <openssl/bn.h>
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/ssl.h>

#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
    defined(OPENSSL_API_COMPAT) && OPENSSL_API_COMPAT >= 0x10100000L
#  define X509_get_notBefore(x)     X509_getm_notBefore(x)
#  define X509_get_notAfter(x)      X509_getm_notAfter(x)
#endif

/* Set to 1 to enable DTLS-SRTP debugging */
#define DTLS_DEBUG  0

/* DTLS-SRTP transport op */
static pj_status_t dtls_media_create  (pjmedia_transport *tp,
                                       pj_pool_t *sdp_pool,
                                       unsigned options,
                                       const pjmedia_sdp_session *sdp_remote,
                                       unsigned media_index);
static pj_status_t dtls_encode_sdp    (pjmedia_transport *tp,
                                       pj_pool_t *sdp_pool,
                                       pjmedia_sdp_session *sdp_local,
                                       const pjmedia_sdp_session *sdp_remote,
                                       unsigned media_index);
static pj_status_t dtls_media_start   (pjmedia_transport *tp,
                                       pj_pool_t *tmp_pool,
                                       const pjmedia_sdp_session *sdp_local,
                                       const pjmedia_sdp_session *sdp_remote,
                                       unsigned media_index);
static pj_status_t dtls_media_stop    (pjmedia_transport *tp);
static pj_status_t dtls_destroy       (pjmedia_transport *tp);
static pj_status_t dtls_on_recv_rtp   (pjmedia_transport *tp,
                                       const void *pkt,
                                       pj_size_t size);

static void on_ice_complete2(pjmedia_transport *tp,
                             pj_ice_strans_op op,
                             pj_status_t status,
                             void *user_data);


static pjmedia_transport_op dtls_op =
{
    NULL,
    NULL,
    NULL,
    &dtls_on_recv_rtp,      // originally send_rtp()
    NULL,
    NULL,
    &dtls_media_create,
    &dtls_encode_sdp,
    &dtls_media_start,
    &dtls_media_stop,
    NULL,
    &dtls_destroy,
    NULL,
};


typedef enum dtls_setup
{ 
    DTLS_SETUP_UNKNOWN,
    DTLS_SETUP_ACTPASS,
    DTLS_SETUP_ACTIVE,
    DTLS_SETUP_PASSIVE
} dtls_setup;


typedef struct dtls_srtp
{
    pjmedia_transport    base;
    pj_pool_t           *pool;
    transport_srtp      *srtp;

    dtls_setup           setup;
    unsigned long        last_err;
    pj_bool_t            use_ice;
    pj_bool_t            nego_started;
    pj_bool_t            nego_completed;
    pj_str_t             rem_fingerprint;   /* Remote fingerprint in SDP    */
    pj_status_t          rem_fprint_status; /* Fingerprint verif. status    */
    pj_sockaddr          rem_addr;          /* Remote address (from SDP/RTP)*/
    pj_sockaddr          rem_rtcp;          /* Remote RTCP address (SDP)    */
    pj_bool_t            pending_start;     /* media_start() invoked but DTLS
                                               nego not done yet, so start
                                               the SRTP once the nego done  */
    pj_bool_t            got_keys;          /* DTLS nego done & keys ready  */
    pjmedia_srtp_crypto  tx_crypto;
    pjmedia_srtp_crypto  rx_crypto;

    char                 buf[PJMEDIA_MAX_MTU];
    pjmedia_clock       *clock;             /* Timer workaround for retrans */

    SSL_CTX             *ossl_ctx;
    SSL                 *ossl_ssl;
    BIO                 *ossl_rbio;
    BIO                 *ossl_wbio;
} dtls_srtp;


static const pj_str_t ID_TP_DTLS_SRTP = { "UDP/TLS/RTP/SAVP", 16 };
static const pj_str_t ID_SETUP        = { "setup", 5 };
static const pj_str_t ID_ACTPASS      = { "actpass", 7 };
static const pj_str_t ID_ACTIVE       = { "active", 6 };
static const pj_str_t ID_PASSIVE      = { "passive", 7 };
static const pj_str_t ID_FINGERPRINT  = { "fingerprint", 11 };

/* Map of OpenSSL-pjmedia SRTP cryptos. Currently OpenSSL seems to
 * support few cryptos only (based on ssl/d1_srtp.c of OpenSSL 1.1.0c).
 */
#define OPENSSL_PROFILE_NUM 4

static char* ossl_profiles[OPENSSL_PROFILE_NUM] =
{
     "SRTP_AES128_CM_SHA1_80",
     "SRTP_AES128_CM_SHA1_32",
     "SRTP_AEAD_AES_256_GCM",
     "SRTP_AEAD_AES_128_GCM"
};
static char* pj_profiles[OPENSSL_PROFILE_NUM] =
{
    "AES_CM_128_HMAC_SHA1_80",
    "AES_CM_128_HMAC_SHA1_32",
    "AEAD_AES_256_GCM",
    "AEAD_AES_128_GCM"
};

/* This will store the valid OpenSSL profiles which is mapped from 
 * OpenSSL-pjmedia SRTP cryptos.
 */
static char *valid_pj_profiles_list[OPENSSL_PROFILE_NUM];
static char *valid_ossl_profiles_list[OPENSSL_PROFILE_NUM];
static unsigned valid_profiles_cnt;


/* Certificate & private key */
static X509     *dtls_cert;
static EVP_PKEY *dtls_priv_key;
static pj_status_t ssl_generate_cert(X509 **p_cert, EVP_PKEY **p_priv_key);

static pj_status_t dtls_init()
{
    /* Make sure OpenSSL library has been initialized */
    {
        pj_ssl_cipher ciphers[1];
        unsigned cipher_num = 1;
        pj_ssl_cipher_get_availables(ciphers, &cipher_num);
    }

    /* Generate cert if not yet */
    if (!dtls_cert) {
        pj_status_t status;
        status = ssl_generate_cert(&dtls_cert, &dtls_priv_key);
        if (status != PJ_SUCCESS) {
            pj_perror(4, "DTLS-SRTP", status,
                      "Failed generating DTLS certificate");
            return status;
        }
    }

    if (valid_profiles_cnt == 0) {
        unsigned n, j;
        int rc;
        char *p, *end, buf[OPENSSL_PROFILE_NUM*25];

        /* Create DTLS context */
        SSL_CTX *ctx = SSL_CTX_new(DTLS_method());
        if (ctx == NULL) {
            return PJ_ENOMEM;
        }

        p = buf;
        end = buf + sizeof(buf);
        for (j=0; j<PJ_ARRAY_SIZE(ossl_profiles); ++j) {
            rc = SSL_CTX_set_tlsext_use_srtp(ctx, ossl_profiles[j]);
            if (rc == 0) {
                valid_pj_profiles_list[valid_profiles_cnt] =
                    pj_profiles[j];
                valid_ossl_profiles_list[valid_profiles_cnt++] =
                    ossl_profiles[j];

                n = pj_ansi_snprintf(p, end - p, ":%s", pj_profiles[j]);
                p += n;
            }
        }
        SSL_CTX_free(ctx);

        if (valid_profiles_cnt > 0) {
            PJ_LOG(4,("DTLS-SRTP", "%s profile is supported", buf));
        } else {
            PJ_PERROR(4, ("DTLS-SRTP", PJMEDIA_SRTP_DTLS_ENOPROFILE,
                          "Error getting SRTP profile"));

            return PJMEDIA_SRTP_DTLS_ENOPROFILE;
        }
    }

    return PJ_SUCCESS;
}

static void dtls_deinit()
{
    if (dtls_cert) {
        X509_free(dtls_cert);
        dtls_cert = NULL;

        EVP_PKEY_free(dtls_priv_key);
        dtls_priv_key = NULL;
    }

    valid_profiles_cnt = 0;
}


/* Create DTLS-SRTP keying instance */
static pj_status_t dtls_create(transport_srtp *srtp,
                               pjmedia_transport **p_keying)
{
    dtls_srtp *ds;
    pj_pool_t *pool;

    pool = pj_pool_create(srtp->pool->factory, "dtls%p",
                          2000, 256, NULL);
    ds = PJ_POOL_ZALLOC_T(pool, dtls_srtp);
    ds->pool = pool;

    pj_ansi_strncpy(ds->base.name, pool->obj_name, PJ_MAX_OBJ_NAME);
    ds->base.type = (pjmedia_transport_type)PJMEDIA_SRTP_KEYING_DTLS_SRTP;
    ds->base.op = &dtls_op;
    ds->base.user_data = srtp;
    ds->srtp = srtp;

    *p_keying = &ds->base;
    PJ_LOG(5,(srtp->pool->obj_name, "SRTP keying DTLS-SRTP created"));
    return PJ_SUCCESS;
}


/**
 * Mapping from OpenSSL error codes to pjlib error space.
 */
#define PJ_SSL_ERRNO_START              (PJ_ERRNO_START_USER + \
                                         PJ_ERRNO_SPACE_SIZE*6)

#define PJ_SSL_ERRNO_SPACE_SIZE         PJ_ERRNO_SPACE_SIZE

/* Expected maximum value of reason component in OpenSSL error code */
#define MAX_OSSL_ERR_REASON             1200

static pj_status_t STATUS_FROM_SSL_ERR(dtls_srtp *ds,
                                       unsigned long err)
{
    pj_status_t status;

    /* General SSL error, dig more from OpenSSL error queue */
    if (err == SSL_ERROR_SSL)
        err = ERR_get_error();

    /* OpenSSL error range is much wider than PJLIB errno space, so
     * if it exceeds the space, only the error reason will be kept.
     * Note that the last native error will be kept as is and can be
     * retrieved via SSL socket info.
     */
    status = ERR_GET_LIB(err)*MAX_OSSL_ERR_REASON + ERR_GET_REASON(err);
    if (status > PJ_SSL_ERRNO_SPACE_SIZE)
        status = ERR_GET_REASON(err);

    if (status != PJ_SUCCESS)
        status += PJ_SSL_ERRNO_START;

    ds->last_err = err;
    return status;
}


static pj_status_t GET_SSL_STATUS(dtls_srtp *ds)
{
    return STATUS_FROM_SSL_ERR(ds, ERR_get_error());
}


/* SSL cert verification callback. */
static int verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx)
{
    PJ_UNUSED_ARG(preverify_ok);
    PJ_UNUSED_ARG(x509_ctx);
    /* Just skip it for now (as usually it's a self-signed cert) */
    return 1;
}

/* Get fingerprint from TLS cert, output is formatted for SDP a=fingerprint,
 * e.g: "SHA-256 XX:XX:XX...". If is_sha256 is true, SHA-256 hash algo will
 * be used, otherwise it is SHA-1.
 */
static pj_status_t ssl_get_fingerprint(X509 *cert, pj_bool_t is_sha256,
                                       char *buf, pj_size_t *buf_len)
{
    unsigned int len, st_out_len, i;
    unsigned char tmp[EVP_MAX_MD_SIZE];
    char *p;

    if (!X509_digest(cert, (is_sha256?EVP_sha256():EVP_sha1()), tmp, &len))
        return PJ_EUNKNOWN;

    st_out_len =  len*3 + (is_sha256? 7 : 5);
    if (*buf_len < st_out_len + 1)
        return PJ_ETOOSMALL;

    /* Format fingerprint to "SHA-256 XX:XX:XX..." */
    p = buf;
    p += pj_ansi_sprintf(p, "SHA-%s %.2X", (is_sha256?"256":"1"), tmp[0]);
    for (i=1; i<len; ++i)
        p += pj_ansi_sprintf(p, ":%.2X", tmp[i]);

    *buf_len = st_out_len;

    return PJ_SUCCESS;
}

/* Generate self-signed cert */
static pj_status_t ssl_generate_cert(X509 **p_cert, EVP_PKEY **p_priv_key)
{
    BIGNUM *bne = NULL;
    RSA *rsa_key = NULL;
    X509_NAME *cert_name = NULL;
    X509 *cert = NULL;
    EVP_PKEY *priv_key = NULL;

    /* Create big number */
    bne = BN_new();
    if (!bne) goto on_error;
    if (!BN_set_word(bne, RSA_F4)) goto on_error;

    /* Generate RSA key */
    rsa_key = RSA_new();
    if (!rsa_key) goto on_error;
    if (!RSA_generate_key_ex(rsa_key, 2048, bne, NULL)) goto on_error;

    /* Create private key */
    priv_key = EVP_PKEY_new();
    if (!priv_key) goto on_error;
    if (!EVP_PKEY_assign_RSA(priv_key, rsa_key)) goto on_error;
    rsa_key = NULL;

    /* Create certificate */
    cert = X509_new();
    if (!cert) goto on_error;

    /* Set version to 3 (2 = x509v3) */
    X509_set_version(cert, 2);

    /* Set serial number */
    ASN1_INTEGER_set(X509_get_serialNumber(cert), pj_rand());

    /* Set valid period */
    X509_gmtime_adj(X509_get_notBefore(cert), -60*60*24);
    X509_gmtime_adj(X509_get_notAfter(cert), 60*60*24*365);

    /* Set subject name */
    cert_name = X509_get_subject_name(cert);
    if (!cert_name) goto on_error;
    if (!X509_NAME_add_entry_by_txt(cert_name, "CN", MBSTRING_ASC,
                                    (const unsigned char*)"pjmedia.pjsip.org",
                                    -1, -1, 0)) goto on_error;

    /* Set the issuer name (to subject name as this is self-signed cert) */
    if (!X509_set_issuer_name(cert, cert_name)) goto on_error;

    /* Set the public key */
    if (!X509_set_pubkey(cert, priv_key)) goto on_error;

    /* Sign with the private key */
    if (!X509_sign(cert, priv_key, EVP_sha1())) goto on_error;

    /* Free big number */
    BN_free(bne);

    *p_cert = cert;
    *p_priv_key = priv_key;
    return PJ_SUCCESS;

on_error:
    if (bne) BN_free(bne);
    if (rsa_key && !priv_key) RSA_free(rsa_key);
    if (priv_key) EVP_PKEY_free(priv_key);
    if (cert) X509_free(cert);
    return PJ_EUNKNOWN;
}

/* Create and initialize new SSL context and instance */
static pj_status_t ssl_create(dtls_srtp *ds)
{
    SSL_CTX *ctx;
    unsigned i;
    int mode, rc;

    /* Check if it is already instantiated */
    if (ds->ossl_ssl)
        return PJ_SUCCESS;

    /* Create DTLS context */
    ctx = SSL_CTX_new(DTLS_method());
    if (ctx == NULL) {
        return GET_SSL_STATUS(ds);
    }

    if (valid_profiles_cnt == 0) {      
        return PJMEDIA_SRTP_DTLS_ENOPROFILE;
    }

    /* Set crypto */
    if (1) {
        char *p, *end, buf[PJ_ARRAY_SIZE(ossl_profiles)*25];
        unsigned n;

        p = buf;
        end = buf + sizeof(buf);
        for (i=0; i<ds->srtp->setting.crypto_count && p < end; ++i) {
            pjmedia_srtp_crypto *crypto = &ds->srtp->setting.crypto[i];
            unsigned j;
            for (j=0; j < valid_profiles_cnt; ++j) {
                if (!pj_ansi_strcmp(crypto->name.ptr,
                                    valid_pj_profiles_list[j]))
                {
                    n = pj_ansi_snprintf(p, end-p, ":%s",
                                         valid_ossl_profiles_list[j]);
                    p += n;
                    break;
                }
            }

        }
        rc = SSL_CTX_set_tlsext_use_srtp(ctx, buf+1);
        PJ_LOG(4,(ds->base.name, "Setting crypto [%s], errcode=%d", buf, rc));
        if (rc != 0) {
            SSL_CTX_free(ctx);
            return GET_SSL_STATUS(ds);
        }
    }

    /* Set ciphers */
    SSL_CTX_set_cipher_list(ctx, PJMEDIA_SRTP_DTLS_OSSL_CIPHERS);

    /* Set cert & private key */
    rc = SSL_CTX_use_certificate(ctx, dtls_cert);
    pj_assert(rc);
    rc = SSL_CTX_use_PrivateKey(ctx, dtls_priv_key);
    pj_assert(rc);
    rc = SSL_CTX_check_private_key(ctx);
    pj_assert(rc);

    /* Create SSL instance */
    ds->ossl_ctx = ctx;
    ds->ossl_ssl = SSL_new(ds->ossl_ctx);
    if (ds->ossl_ssl == NULL) {
        SSL_CTX_free(ctx);
        return GET_SSL_STATUS(ds);
    }

    /* Set MTU */
#ifdef DTLS_CTRL_SET_LINK_MTU
    if (!SSL_ctrl(ds->ossl_ssl, DTLS_CTRL_SET_LINK_MTU, PJMEDIA_MAX_MTU,
                  NULL))
    {
        PJ_LOG(4, (ds->base.name,
                  "Ignored failure in setting MTU to %d (too small?)",
                  PJMEDIA_MAX_MTU));
    }
#endif

    /* SSL verification options, must be mutual auth */
    mode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
    SSL_set_verify(ds->ossl_ssl, mode, &verify_cb);

    /* Setup SSL BIOs */
    ds->ossl_rbio = BIO_new(BIO_s_mem());
    ds->ossl_wbio = BIO_new(BIO_s_mem());
    (void)BIO_set_close(ds->ossl_rbio, BIO_CLOSE);
    (void)BIO_set_close(ds->ossl_wbio, BIO_CLOSE);
    SSL_set_bio(ds->ossl_ssl, ds->ossl_rbio, ds->ossl_wbio);

    return PJ_SUCCESS;
}


/* Destroy SSL context and instance */
static void ssl_destroy(dtls_srtp *ds)
{
    /* Destroy SSL instance */
    if (ds->ossl_ssl) {
        /**
         * Avoid calling SSL_shutdown() if handshake wasn't completed.
         * OpenSSL 1.0.2f complains if SSL_shutdown() is called during an
         * SSL handshake, while previous versions always return 0.       
         */
        if (SSL_in_init(ds->ossl_ssl) == 0) {
            SSL_shutdown(ds->ossl_ssl);
        }
        SSL_free(ds->ossl_ssl); /* this will also close BIOs */
        ds->ossl_ssl = NULL;
        /* thus reset the BIOs as well */
        ds->ossl_rbio = NULL;
        ds->ossl_wbio = NULL;
    }

    /* Destroy SSL context */
    if (ds->ossl_ctx) {
        SSL_CTX_free(ds->ossl_ctx);
        ds->ossl_ctx = NULL;
    }
}

static pj_status_t ssl_get_srtp_material(dtls_srtp *ds)
{
    unsigned char material[SRTP_MAX_KEY_LEN * 2];
    SRTP_PROTECTION_PROFILE *profile;
    int rc, i, crypto_idx = -1;
    pjmedia_srtp_crypto *tx, *rx;
    pj_status_t status = PJ_SUCCESS;

    /* Get selected crypto-suite */
    profile = SSL_get_selected_srtp_profile(ds->ossl_ssl);
    if (!profile) {
        status = PJMEDIA_SRTP_DTLS_ENOCRYPTO;
        goto on_return;
    }

    tx = &ds->tx_crypto;
    rx = &ds->rx_crypto;
    pj_bzero(tx, sizeof(*tx));
    pj_bzero(rx, sizeof(*rx));
    for (i=0; i<PJ_ARRAY_SIZE(ossl_profiles); ++i) {
        if (pj_ansi_stricmp(profile->name, ossl_profiles[i])==0) {
            pj_strset2(&tx->name, pj_profiles[i]);
            pj_strset2(&rx->name, pj_profiles[i]);
            crypto_idx = get_crypto_idx(&tx->name);
            break;
        }
    }
    if (crypto_idx == -1) {
        status = PJMEDIA_SRTP_ENOTSUPCRYPTO;
        goto on_return;
    }

    /* Get keying material from DTLS nego. There seems to be no info about
     * material length returned by SSL_export_keying_material()?
     */
    rc = SSL_export_keying_material(ds->ossl_ssl, material, sizeof(material),
                                    "EXTRACTOR-dtls_srtp", 19, NULL, 0, 0);
    if (rc == 0) {
        status = PJMEDIA_SRTP_EINKEYLEN;
        goto on_return;
    }

    /* Parse SRTP master key & salt from keying material */
    {
        char *p = (char*)material;
        char *k1, *k2;
        crypto_suite *cs = &crypto_suites[crypto_idx];
        unsigned key_len, salt_len;

        key_len = cs->cipher_key_len - cs->cipher_salt_len;
        salt_len = cs->cipher_salt_len;

        tx->key.ptr = (char*)pj_pool_alloc(ds->pool, key_len+salt_len);
        tx->key.slen = key_len+salt_len;
        rx->key.ptr = (char*)pj_pool_alloc(ds->pool, key_len+salt_len);
        rx->key.slen = key_len+salt_len;
        if (ds->setup == DTLS_SETUP_ACTIVE) {
            k1 = tx->key.ptr;
            k2 = rx->key.ptr;
        } else {
            k1 = rx->key.ptr;
            k2 = tx->key.ptr;
        }
        pj_memcpy(k1, p, key_len); p += key_len;
        pj_memcpy(k2, p, key_len); p += key_len;
        pj_memcpy(k1+key_len, p, salt_len); p += salt_len;
        pj_memcpy(k2+key_len, p, salt_len);
        ds->got_keys = PJ_TRUE;
    }

on_return:
    return status;
}

/* Match remote fingerprint: SDP vs actual */
static pj_status_t ssl_match_fingerprint(dtls_srtp *ds)
{
    X509 *rem_cert;
    pj_bool_t is_sha256;
    char buf[128];
    pj_size_t buf_len = sizeof(buf);
    pj_status_t status;

    /* Check hash algo, currently we only support SHA-256 & SHA-1 */
    if (!pj_strnicmp2(&ds->rem_fingerprint, "SHA-256 ", 8))
        is_sha256 = PJ_TRUE;
    else if (!pj_strnicmp2(&ds->rem_fingerprint, "SHA-1 ", 6))
        is_sha256 = PJ_FALSE;
    else {
        PJ_LOG(4,(ds->base.name, "Hash algo specified in remote SDP for "
                  "its DTLS certificate fingerprint is not supported"));
        return PJ_ENOTSUP;
    }

    /* Get remote cert & calculate the hash */
    rem_cert = SSL_get_peer_certificate(ds->ossl_ssl);
    if (!rem_cert)
        return PJMEDIA_SRTP_DTLS_EPEERNOCERT;

    status = ssl_get_fingerprint(rem_cert, is_sha256, buf, &buf_len);
    X509_free(rem_cert);
    if (status != PJ_SUCCESS)
        return status;

    /* Do they match? */
    if (pj_stricmp2(&ds->rem_fingerprint, buf))
        return PJMEDIA_SRTP_DTLS_EFPNOTMATCH;

    return PJ_SUCCESS;
}


/* Send data to network */
static pj_status_t send_raw(dtls_srtp *ds, const void *buf, pj_size_t len)
{
#if DTLS_DEBUG
    PJ_LOG(2,(ds->base.name, "DTLS-SRTP sending %d bytes", len));
#endif

    return pjmedia_transport_send_rtp(ds->srtp->member_tp, buf, len);
}


/* Start socket if member transport is UDP */
static pj_status_t udp_member_transport_media_start(dtls_srtp *ds)
{
    pjmedia_transport_info info;
    pj_status_t status;

    if (!ds->srtp->member_tp)
        return PJ_SUCCESS;

    pjmedia_transport_info_init(&info);
    status = pjmedia_transport_get_info(ds->srtp->member_tp, &info);
    if (status != PJ_SUCCESS)
        return status;

    if (info.specific_info_cnt == 1 &&
        info.spc_info[0].type == PJMEDIA_TRANSPORT_TYPE_UDP)
    {
        return pjmedia_transport_media_start(ds->srtp->member_tp, 0, 0, 0, 0);
    }

    return PJ_SUCCESS;
}


/* Flush write BIO */
static pj_status_t ssl_flush_wbio(dtls_srtp *ds)
{
    pj_size_t len;
    pj_status_t status = PJ_SUCCESS;

        if (ds->ossl_wbio == NULL) return PJ_EGONE;

    /* Check whether there is data to send */
    if (BIO_ctrl_pending(ds->ossl_wbio) > 0) {
        /* Yes, get and send it */
        len = BIO_read(ds->ossl_wbio, ds->buf, sizeof(ds->buf));
        if (len > 0) {
            status = send_raw(ds, ds->buf, len);
            if (status != PJ_SUCCESS) {
#if DTLS_DEBUG
                pj_perror(2, ds->base.name, status, "Send error");
#endif
                /* This error should be recoverable, remote will retransmit
                 * its packet when not receiving from us.
                 */
            }
        }
    }

    /* Just return if handshake completion procedure (key parsing, fingerprint
     * verification, etc) has been done or handshake is still in progress.
     */
    if (ds->nego_completed || !SSL_is_init_finished(ds->ossl_ssl))
        return PJ_SUCCESS;

    /* Yes, SSL handshake is done! */
    ds->nego_completed = PJ_TRUE;
    PJ_LOG(2,(ds->base.name, "DTLS-SRTP negotiation completed!"));

    /* Stop the retransmission clock. Note that the clock may not be stopped
     * if this function is called from clock thread context. We'll try again
     * later in socket context.
     */
    if (ds->clock)
        pjmedia_clock_stop(ds->clock);

    /* Get SRTP key material */
    status = ssl_get_srtp_material(ds);
    if (status != PJ_SUCCESS) {
        pj_perror(4, ds->base.name, status,
                  "Failed to get SRTP material");
        goto on_return;
    }

    /* Verify remote fingerprint if we've already got one from SDP */
    if (ds->rem_fingerprint.slen && ds->rem_fprint_status == PJ_EPENDING) {
        ds->rem_fprint_status = status = ssl_match_fingerprint(ds);
        if (status != PJ_SUCCESS) {
            pj_perror(4, ds->base.name, status,
                      "Fingerprint specified in remote SDP doesn't match "
                      "to actual remote certificate fingerprint!");
            goto on_return;
        }
    }

    /* If media_start() has been called, start SRTP now */
    if (ds->pending_start) {
        ds->pending_start = PJ_FALSE;
        ds->srtp->keying_pending_cnt--;

        /* Copy negotiated policy to SRTP */
        ds->srtp->tx_policy_neg = ds->tx_crypto;
        ds->srtp->rx_policy_neg = ds->rx_crypto;

        status = start_srtp(ds->srtp);
        if (status != PJ_SUCCESS)
            pj_perror(4, ds->base.name, status, "Failed starting SRTP");
    }

on_return:
    if (ds->srtp->setting.cb.on_srtp_nego_complete) {
        (*ds->srtp->setting.cb.on_srtp_nego_complete)
                                            (&ds->srtp->base, status);
    }

    return status;
}


static void clock_cb(const pj_timestamp *ts, void *user_data)
{
    dtls_srtp *ds = (dtls_srtp*)user_data;

    PJ_UNUSED_ARG(ts);

    if (ds->ossl_ssl) {
        if (DTLSv1_handle_timeout(ds->ossl_ssl) > 0)
            ssl_flush_wbio(ds);
    }
}


/* Asynchronous handshake */
static pj_status_t ssl_handshake(dtls_srtp *ds)
{
    pj_status_t status;
    int err;

    /* Init DTLS (if not yet) */
    status = ssl_create(ds);
    if (status != PJ_SUCCESS)
        return status;

    /* Check if handshake has been initiated or even completed */
    if (ds->nego_started || SSL_is_init_finished(ds->ossl_ssl))
        return PJ_SUCCESS;

    /* Perform SSL handshake */
    if (ds->setup == DTLS_SETUP_ACTIVE) {
        SSL_set_connect_state(ds->ossl_ssl);
    } else {
        SSL_set_accept_state(ds->ossl_ssl);
    }
    err = SSL_do_handshake(ds->ossl_ssl);
    if (err < 0) {
        err = SSL_get_error(ds->ossl_ssl, err);
        if (err == SSL_ERROR_WANT_READ) {
            status = ssl_flush_wbio(ds);
            if (status != PJ_SUCCESS)
                goto on_return;
        } else if (err != SSL_ERROR_NONE) {
            /* Handshake fails */
            status = STATUS_FROM_SSL_ERR(ds, err);
            pj_perror(2, ds->base.name, status, "SSL_do_handshake() error");
            goto on_return;
        }
    }

    /* Create and start clock @4Hz for retransmission */
    if (!ds->clock) {
        status = pjmedia_clock_create(ds->pool, 4, 1, 1,
                                      PJMEDIA_CLOCK_NO_HIGHEST_PRIO, clock_cb,
                                      ds, &ds->clock);
        if (status != PJ_SUCCESS)
            goto on_return;
    }    
    status = pjmedia_clock_start(ds->clock);
    if (status != PJ_SUCCESS)
        goto on_return;

    /* Finally, DTLS nego started! */
    ds->nego_started = PJ_TRUE;
    PJ_LOG(4,(ds->base.name, "DTLS-SRTP negotiation initiated as %s",
              (ds->setup==DTLS_SETUP_ACTIVE? "client":"server")));

on_return:
    if (status != PJ_SUCCESS) {
        if (ds->clock)
            pjmedia_clock_stop(ds->clock);
    }
    return status;
}


/* Parse a=setup & a=fingerprint in remote SDP to update DTLS-SRTP states
 * 'setup' and 'rem_fingerprint'.
 * TODO: check those attributes in a=acap too?
 */
static pj_status_t parse_setup_finger_attr(dtls_srtp *ds,
                                           pj_bool_t rem_as_offerer,
                                           const pjmedia_sdp_session *sdp,
                                           unsigned media_index)
{
    pjmedia_sdp_media *m;
    pjmedia_sdp_attr *a;

    m = sdp->media[media_index];

    /* Parse a=setup */
    a = pjmedia_sdp_media_find_attr(m, &ID_SETUP, NULL);
    if (!a)
        a = pjmedia_sdp_attr_find(sdp->attr_count,
                                  sdp->attr, &ID_SETUP, NULL);
    if (!a)
        return PJMEDIA_SRTP_ESDPAMBIGUEANS;

    if (pj_stristr(&a->value, &ID_PASSIVE) ||
        (rem_as_offerer && pj_stristr(&a->value, &ID_ACTPASS)))
    {
        /* Remote offers/answers 'passive' (or offers 'actpass'), so we are
         * the client.
         */
        ds->setup = DTLS_SETUP_ACTIVE;
    } else if (pj_stristr(&a->value, &ID_ACTIVE)) {
        /* Remote offers/answers 'active' so we are the server. */
        ds->setup = DTLS_SETUP_PASSIVE;
    } else {
        /* Unknown value set in remote a=setup */
        return PJMEDIA_SRTP_ESDPAMBIGUEANS;
    }

    /* Parse a=fingerprint */
    a = pjmedia_sdp_media_find_attr(m, &ID_FINGERPRINT, NULL);
    if (!a)
        a = pjmedia_sdp_attr_find(sdp->attr_count,
                                  sdp->attr, &ID_FINGERPRINT,
                                  NULL);
    if (!a) {
        /* No fingerprint attribute in remote SDP */
        return PJMEDIA_SRTP_DTLS_ENOFPRINT;
    } else {
        pj_str_t rem_fp = a->value;
        pj_strtrim(&rem_fp);
        if (pj_stricmp(&ds->rem_fingerprint, &rem_fp))
            pj_strdup(ds->pool, &ds->rem_fingerprint, &rem_fp);
    }

    return PJ_SUCCESS;
}

static pj_status_t get_rem_addrs(dtls_srtp *ds,
                                 const pjmedia_sdp_session *sdp_remote,
                                 unsigned media_index,
                                 pj_sockaddr *rem_rtp,
                                 pj_sockaddr *rem_rtcp,
                                 pj_bool_t *rtcp_mux)
{
    pjmedia_sdp_media *m_rem = sdp_remote->media[media_index];
    pjmedia_sdp_conn *conn;
    pjmedia_sdp_attr *a;
    int af = pj_AF_UNSPEC();
    pj_bool_t use_ice_info = PJ_FALSE;

    /* Init RTP & RTCP address */
    pj_bzero(rem_rtp, sizeof(*rem_rtp));
    pj_bzero(rem_rtcp, sizeof(*rem_rtcp));

    /* If underlying transport is ICE, get remote addresses from ICE */
    if (ds->use_ice) {
        pjmedia_transport_info info;
        pjmedia_ice_transport_info *ice_info;

        pjmedia_transport_info_init(&info);
        pjmedia_transport_get_info(ds->srtp->member_tp, &info);
        ice_info = (pjmedia_ice_transport_info*)
                   pjmedia_transport_info_get_spc_info(
                                    &info, PJMEDIA_TRANSPORT_TYPE_ICE);
        if (ice_info) {
            *rem_rtp = ice_info->comp[0].rcand_addr;
            if (ice_info->comp_cnt > 1)
                *rem_rtcp = ice_info->comp[1].rcand_addr;

            use_ice_info = PJ_TRUE;
        }
    }

    /* Get remote addresses from SDP */
    if (!use_ice_info) {

        /* Get RTP address */
        conn = m_rem->conn ? m_rem->conn : sdp_remote->conn;
        if (pj_stricmp2(&conn->net_type, "IN")==0) {
            if (pj_stricmp2(&conn->addr_type, "IP4")==0) {
                af = pj_AF_INET();
            } else if (pj_stricmp2(&conn->addr_type, "IP6")==0) {
                af = pj_AF_INET6();
            }
        }
        if (af != pj_AF_UNSPEC()) {
            pj_sockaddr_init(af, rem_rtp, &conn->addr,
                             m_rem->desc.port);
        } else {
            return PJ_EAFNOTSUP;
        }

        /* Get RTCP address. If "rtcp" attribute is present in the SDP,
         * set the RTCP address from that attribute. Otherwise, calculate
         * from RTP address.
         */
        a = pjmedia_sdp_attr_find2(m_rem->attr_count, m_rem->attr,
                                   "rtcp", NULL);
        if (a) {
            pjmedia_sdp_rtcp_attr rtcp;
            pj_status_t status;
            status = pjmedia_sdp_attr_get_rtcp(a, &rtcp);
            if (status == PJ_SUCCESS) {
                if (rtcp.addr.slen) {
                    pj_sockaddr_init(af, rem_rtcp, &rtcp.addr,
                                     (pj_uint16_t)rtcp.port);
                } else {
                    pj_sockaddr_init(af, rem_rtcp, NULL,
                                     (pj_uint16_t)rtcp.port);
                    pj_memcpy(pj_sockaddr_get_addr(rem_rtcp),
                              pj_sockaddr_get_addr(rem_rtp),
                              pj_sockaddr_get_addr_len(rem_rtp));
                }
            }
        }
        if (!pj_sockaddr_has_addr(rem_rtcp)) {
            int rtcp_port;
            pj_memcpy(rem_rtcp, rem_rtp, sizeof(pj_sockaddr));
            rtcp_port = pj_sockaddr_get_port(rem_rtp) + 1;
            pj_sockaddr_set_port(rem_rtcp, (pj_uint16_t)rtcp_port);
        }
    }

    /* Check if remote indicates the desire to use rtcp-mux in its SDP. */
    if (rtcp_mux) {
        a = pjmedia_sdp_attr_find2(m_rem->attr_count, m_rem->attr,
                                   "rtcp-mux", NULL);
        *rtcp_mux = (a? PJ_TRUE: PJ_FALSE);
    }

    return PJ_SUCCESS;
}

/* Check if an incoming packet is a DTLS packet (rfc5764 section 5.1.2) */
#define IS_DTLS_PKT(pkt, pkt_len) (*(char*)pkt > 19 && *(char*)pkt < 64)


/* Received packet (SSL handshake) from socket */
static pj_status_t ssl_on_recv_packet(dtls_srtp *ds,
                                      const void *data, pj_size_t len)
{
    char tmp[128];
    pj_size_t nwritten;

        if (ds->ossl_rbio == NULL) return PJ_EGONE;

    nwritten = BIO_write(ds->ossl_rbio, data, (int)len);
    if (nwritten < len) {
        /* Error? */
        pj_status_t status;
        status = GET_SSL_STATUS(ds);
#if DTLS_DEBUG
        pj_perror(2, ds->base.name, status, "BIO_write() error");
#endif
        return status;
    }

    /* Consume (and ignore) the packet */
    while (1) {
        int rc = SSL_read(ds->ossl_ssl, tmp, sizeof(tmp));
        if (rc <= 0) {
#if DTLS_DEBUG
            pj_status_t status = GET_SSL_STATUS(ds);
            if (status != PJ_SUCCESS)
                pj_perror(2, ds->base.name, status, "SSL_read() error");
#endif
            break;
        }
    }

    /* Flush anything pending in the write BIO */
    return ssl_flush_wbio(ds);
}


static void on_ice_complete2(pjmedia_transport *tp,
                             pj_ice_strans_op op,
                             pj_status_t status,
                             void *user_data)
{
    dtls_srtp *ds = (dtls_srtp*)user_data;
    pj_assert(ds);

    PJ_UNUSED_ARG(tp);

    if (op == PJ_ICE_STRANS_OP_NEGOTIATION && status == PJ_SUCCESS &&
        ds->setup == DTLS_SETUP_ACTIVE)
    {
        pj_status_t tmp_st;
        tmp_st = ssl_handshake(ds);
        if (tmp_st != PJ_SUCCESS)
            pj_perror(4, ds->base.name, tmp_st, "Failed starting DTLS nego");
    }
}


/* *************************************
 *
 * DTLS-SRTP transport keying operations
 *
 * *************************************/

/*
 * This callback is called by SRTP transport when incoming rtp is received.
 * Originally this is send_rtp() op.
 */
static pj_status_t dtls_on_recv_rtp( pjmedia_transport *tp,
                                     const void *pkt,
                                     pj_size_t size)
{
    dtls_srtp *ds = (dtls_srtp*)tp;

    /* Destroy the retransmission clock if handshake has been completed. */
    if (ds->clock && ds->nego_completed) {
        pjmedia_clock_destroy(ds->clock);
        ds->clock = NULL;
    }

    if (size < 1 || !IS_DTLS_PKT(pkt, size))
        return PJ_EIGNORED;

#if DTLS_DEBUG
    PJ_LOG(2,(ds->base.name, "DTLS-SRTP receiving %d bytes", size));
#endif

    /* This is DTLS packet, let's process it. Note that if DTLS nego has
     * been completed, this may be a retransmission (e.g: remote didn't
     * receive our last handshake packet) or just a stray.
     */

    /* Check remote address info, reattach member tp if changed */
    if (!ds->use_ice && !ds->nego_completed) {
        pjmedia_transport_info info;
        pjmedia_transport_get_info(ds->srtp->member_tp, &info);
        if (pj_sockaddr_cmp(&ds->rem_addr, &info.src_rtp_name)) {
            pjmedia_transport_attach_param ap;

            pj_bzero(&ap, sizeof(ap));
            ap.user_data = ds->srtp;
            pj_sockaddr_cp(&ds->rem_addr, &info.src_rtp_name);
            pj_sockaddr_cp(&ap.rem_addr, &ds->rem_addr);
            ap.addr_len = pj_sockaddr_get_len(&ap.rem_addr);
            if (pj_sockaddr_cmp(&info.sock_info.rtp_addr_name,
                                &info.sock_info.rtcp_addr_name) == 0)
            {
                /* Using RTP & RTCP multiplexing */
                pj_sockaddr_cp(&ds->rem_rtcp, &ds->rem_addr);
                pj_sockaddr_cp(&ap.rem_rtcp, &ds->rem_rtcp);
            } else if (pj_sockaddr_has_addr(&ds->rem_rtcp)) {
                pj_sockaddr_cp(&ap.rem_rtcp, &ds->rem_rtcp);
            } else {
                pj_sockaddr_cp(&ap.rem_rtcp, &ds->rem_addr);
                pj_sockaddr_set_port(&ap.rem_rtcp,
                                     pj_sockaddr_get_port(&ds->rem_addr)+1);
            }

            pjmedia_transport_attach2(&ds->srtp->base, &ap);

#if DTLS_DEBUG
            {
                char addr[PJ_INET6_ADDRSTRLEN];
                PJ_LOG(2,(ds->base.name, "Re-attached transport to update "
                          "remote addr=%s:%d",
                          pj_sockaddr_print(&ap.rem_addr, addr,
                                            sizeof(addr), 2),
                          pj_sockaddr_get_port(&ap.rem_addr)));
            }
#endif
        }
    }

    /* If our setup is ACTPASS, incoming packet may be a client hello,
     * so let's update setup to PASSIVE and initiate DTLS handshake.
     */
    if (!ds->nego_started &&
        (ds->setup == DTLS_SETUP_ACTPASS || ds->setup == DTLS_SETUP_PASSIVE))
    {
        pj_status_t status;
        ds->setup = DTLS_SETUP_PASSIVE;
        status = ssl_handshake(ds);
        if (status != PJ_SUCCESS)
            return status;
    }

    /* Send it to OpenSSL */
    ssl_on_recv_packet(ds, pkt, size);
    return PJ_SUCCESS;
}

static pj_status_t dtls_media_create( pjmedia_transport *tp,
                                      pj_pool_t *sdp_pool,
                                      unsigned options,
                                      const pjmedia_sdp_session *sdp_remote,
                                      unsigned media_index)
{
    dtls_srtp *ds = (dtls_srtp*) tp;
    pj_status_t status = PJ_SUCCESS;

#if DTLS_DEBUG
    PJ_LOG(2,(ds->base.name, "dtls_media_create()"));
#endif

    PJ_UNUSED_ARG(sdp_pool);
    PJ_UNUSED_ARG(options);

    if (ds->srtp->offerer_side) {
        /* As offerer: do nothing. */
    } else {
        /* As answerer:
         *    Check for DTLS-SRTP support in remote SDP. Detect remote
         *    support of DTLS-SRTP by inspecting remote SDP offer for
         *    SDP a=fingerprint attribute. And currently we only support
         *    RTP/AVP transports.
         */
        pjmedia_sdp_media *m_rem = sdp_remote->media[media_index];
        pjmedia_sdp_attr *attr_fp;
        pj_uint32_t rem_proto = 0;

        /* Find SDP a=fingerprint line. */
        attr_fp = pjmedia_sdp_media_find_attr(m_rem, &ID_FINGERPRINT, NULL);
        if (!attr_fp)
            attr_fp = pjmedia_sdp_attr_find(sdp_remote->attr_count,
                                            sdp_remote->attr, &ID_FINGERPRINT,
                                            NULL);

        /* Get media transport proto */
        rem_proto = pjmedia_sdp_transport_get_proto(&m_rem->desc.transport);
        if (!PJMEDIA_TP_PROTO_HAS_FLAG(rem_proto, PJMEDIA_TP_PROTO_RTP_AVP) ||
            !attr_fp)
        {
            /* Remote doesn't signal DTLS-SRTP */
            status = PJMEDIA_SRTP_ESDPINTRANSPORT;
            goto on_return;
        }

        /* Check for a=fingerprint in remote SDP. */
        switch (ds->srtp->setting.use) {
            case PJMEDIA_SRTP_DISABLED:
                if (attr_fp) {
                    status = PJMEDIA_SRTP_ESDPINTRANSPORT;
                    goto on_return;
                }
                break;
            case PJMEDIA_SRTP_OPTIONAL:
                break;
            case PJMEDIA_SRTP_MANDATORY:
                if (!attr_fp) {
                    /* Should never reach here, this is already checked */
                    status = PJMEDIA_SRTP_ESDPINTRANSPORT;
                    goto on_return;
                }
                break;
        }
    }

    /* Set remote cert fingerprint verification status to PJ_EPENDING */
    ds->rem_fprint_status = PJ_EPENDING;

on_return:
#if DTLS_DEBUG
    if (status != PJ_SUCCESS) {
        pj_perror(4, ds->base.name, status, "dtls_media_create() failed");
    }
#endif
    return status;
}

static pj_status_t dtls_encode_sdp( pjmedia_transport *tp,
                                    pj_pool_t *sdp_pool,
                                    pjmedia_sdp_session *sdp_local,
                                    const pjmedia_sdp_session *sdp_remote,
                                    unsigned media_index)
{
    dtls_srtp *ds = (dtls_srtp *)tp;
    pjmedia_sdp_media *m_loc;
    pjmedia_sdp_attr *a;
    pj_bool_t use_ice = PJ_FALSE;
    pj_status_t status = PJ_SUCCESS;

#if DTLS_DEBUG
    PJ_LOG(2,(ds->base.name, "dtls_encode_sdp()"));
#endif

    PJ_UNUSED_ARG(sdp_pool);

    m_loc = sdp_local->media[media_index];
    if (ds->srtp->offerer_side) {
        /* As offerer */

        /* Add attribute a=setup if none (rfc5763 section 5) */
        a = pjmedia_sdp_media_find_attr(m_loc, &ID_SETUP, NULL);
        if (!a)
            a = pjmedia_sdp_attr_find(sdp_local->attr_count,
                                      sdp_local->attr, &ID_SETUP, NULL);
        if (!a) {
            pj_str_t val;

            if (ds->setup == DTLS_SETUP_UNKNOWN)
                ds->setup = DTLS_SETUP_ACTPASS;
            
            if (ds->setup == DTLS_SETUP_ACTIVE)
                val = ID_ACTIVE;
            else if (ds->setup == DTLS_SETUP_PASSIVE)
                val = ID_PASSIVE;
            else
                val = ID_ACTPASS;
            a = pjmedia_sdp_attr_create(ds->pool, ID_SETUP.ptr, &val);
            pjmedia_sdp_media_add_attr(m_loc, a);
        }
    } else {
        /* As answerer */
        dtls_setup last_setup = ds->setup;
        pj_str_t last_rem_fp = ds->rem_fingerprint;
        pj_bool_t rem_addr_changed = PJ_FALSE;

        /* Parse a=setup and a=fingerprint */
        status = parse_setup_finger_attr(ds, PJ_TRUE, sdp_remote,
                                         media_index);
        if (status != PJ_SUCCESS)
            goto on_return;

        /* Add attribute a=setup:active/passive if we are client/server. */
        a = pjmedia_sdp_attr_create(ds->pool, ID_SETUP.ptr,
                    (ds->setup==DTLS_SETUP_ACTIVE? &ID_ACTIVE:&ID_PASSIVE));
        pjmedia_sdp_media_add_attr(m_loc, a);

        if (last_setup != DTLS_SETUP_UNKNOWN && sdp_remote) {
            pj_sockaddr rem_rtp;
            pj_sockaddr rem_rtcp;
            pj_bool_t use_rtcp_mux;

            status = get_rem_addrs(ds, sdp_remote, media_index, &rem_rtp,
                                   &rem_rtcp, &use_rtcp_mux);
            if (status == PJ_SUCCESS) {
                if (use_rtcp_mux) {
                    /* Remote indicates it wants to use rtcp-mux */
                    pjmedia_transport_info info;

                    pjmedia_transport_info_init(&info);
                    pjmedia_transport_get_info(ds->srtp->member_tp, &info);
                    if (pj_sockaddr_cmp(&info.sock_info.rtp_addr_name,
                        &info.sock_info.rtcp_addr_name))
                    {
                        /* But we do not wish to use rtcp mux */
                        use_rtcp_mux = PJ_FALSE;
                    }
                }
                if (pj_sockaddr_has_addr(&ds->rem_addr) &&
                    pj_sockaddr_has_addr(&rem_rtp) &&
                    (pj_sockaddr_cmp(&ds->rem_addr, &rem_rtp) ||
                     (!use_rtcp_mux &&
                      pj_sockaddr_has_addr(&ds->rem_rtcp) &&
                      pj_sockaddr_has_addr(&rem_rtcp) &&
                      pj_sockaddr_cmp(&ds->rem_rtcp, &rem_rtcp))))
                {
                    rem_addr_changed = PJ_TRUE;
                }
            }
        }

        /* Check if remote signals DTLS re-nego by changing its
         * setup/fingerprint in SDP or media transport address in SDP.
         */
        if ((last_setup != DTLS_SETUP_UNKNOWN && last_setup != ds->setup) ||
            (last_rem_fp.slen &&
             pj_memcmp(&last_rem_fp, &ds->rem_fingerprint, sizeof(pj_str_t)))||
            (rem_addr_changed))
        {
            ssl_destroy(ds);
            ds->nego_started = PJ_FALSE;
            ds->nego_completed = PJ_FALSE;
            ds->got_keys = PJ_FALSE;
            ds->rem_fprint_status = PJ_EPENDING;
        }
    }

    /* Set media transport to UDP/TLS/RTP/SAVP if we are the offerer,
     * otherwise just match it to the offer (currently we only accept
     * UDP/TLS/RTP/SAVP in remote offer though).
     */
    if (ds->srtp->offerer_side) {
        m_loc->desc.transport = ID_TP_DTLS_SRTP;
    } else {
        m_loc->desc.transport = 
                            sdp_remote->media[media_index]->desc.transport;
    }

    /* Add a=fingerprint attribute, fingerprint of our TLS certificate */
    {
        char buf[128];
        pj_size_t buf_len = sizeof(buf);
        pj_str_t fp;

        status = ssl_get_fingerprint(dtls_cert, PJ_TRUE, buf, &buf_len);
        if (status != PJ_SUCCESS)
            goto on_return;

        pj_strset(&fp, buf, buf_len);
        a = pjmedia_sdp_attr_create(ds->pool, ID_FINGERPRINT.ptr, &fp);
        pjmedia_sdp_media_add_attr(m_loc, a);
    }

    if (ds->nego_completed) {
        /* This is subsequent SDP offer/answer and no DTLS re-nego has been
         * signalled.
         */
        goto on_return;
    }

    /* Attach member transport, so we can receive DTLS init (if our setup
     * is PASSIVE/ACTPASS) or send DTLS init (if our setup is ACTIVE).
     */
    {
        pjmedia_transport_attach_param ap;
        pjmedia_transport_info info;

        pj_bzero(&ap, sizeof(ap));
        ap.user_data = ds->srtp;
        pjmedia_transport_get_info(ds->srtp->member_tp, &info);

        if (sdp_remote) {
            get_rem_addrs(ds, sdp_remote, media_index, &ds->rem_addr,
                          &ds->rem_rtcp, NULL);
        }

        if (pj_sockaddr_has_addr(&ds->rem_addr)) {
            pj_sockaddr_cp(&ap.rem_addr, &ds->rem_addr);
        } else if (pj_sockaddr_has_addr(&info.sock_info.rtp_addr_name)) {
            pj_sockaddr_cp(&ap.rem_addr, &info.sock_info.rtp_addr_name);
        } else {
            pj_sockaddr_init(pj_AF_INET(), &ap.rem_addr, 0, 0);
        }

        if (pj_sockaddr_cmp(&info.sock_info.rtp_addr_name,
                            &info.sock_info.rtcp_addr_name) == 0)
        {
            /* Using RTP & RTCP multiplexing */
            pj_sockaddr_cp(&ap.rem_rtcp, &ap.rem_addr);
        } else if (pj_sockaddr_has_addr(&ds->rem_rtcp)) {
            pj_sockaddr_cp(&ap.rem_rtcp, &ds->rem_rtcp);
        } else if (pj_sockaddr_has_addr(&info.sock_info.rtcp_addr_name)) {
            pj_sockaddr_cp(&ap.rem_rtcp, &info.sock_info.rtcp_addr_name);
        } else {
            pj_sockaddr_init(pj_AF_INET(), &ap.rem_rtcp, 0, 0);
        }

        ap.addr_len = pj_sockaddr_get_len(&ap.rem_addr);
        status = pjmedia_transport_attach2(&ds->srtp->base, &ap);
        if (status != PJ_SUCCESS)
            goto on_return;

        /* Start member transport if it is UDP, so we can receive packet
         * (see also #2097).
         */
        udp_member_transport_media_start(ds);

#if DTLS_DEBUG
        {
            char addr[PJ_INET6_ADDRSTRLEN];
            PJ_LOG(2,(ds->base.name, "Attached transport, remote addr=%s:%d",
                      pj_sockaddr_print(&ap.rem_addr, addr, sizeof(addr), 2),
                      pj_sockaddr_get_port(&ap.rem_addr)));
        }
#endif
    }

    /* If our setup is ACTIVE and member transport is not ICE,
     * start DTLS nego.
     */
    if (ds->setup == DTLS_SETUP_ACTIVE) {
        pjmedia_transport_info info;
        pjmedia_ice_transport_info *ice_info;

        pjmedia_transport_info_init(&info);
        pjmedia_transport_get_info(ds->srtp->member_tp, &info);
        ice_info = (pjmedia_ice_transport_info*)
                   pjmedia_transport_info_get_spc_info(
                                    &info, PJMEDIA_TRANSPORT_TYPE_ICE);
        use_ice = ice_info && ice_info->comp_cnt;
        if (!use_ice) {
            /* Start SSL nego */
            status = ssl_handshake(ds);
            if (status != PJ_SUCCESS)
                goto on_return;
        }
    }

on_return:
#if DTLS_DEBUG
    if (status != PJ_SUCCESS) {
        pj_perror(4, ds->base.name, status, "dtls_encode_sdp() failed");
    }
#endif
    return status;
}


static pj_status_t dtls_media_start( pjmedia_transport *tp,
                                     pj_pool_t *tmp_pool,
                                     const pjmedia_sdp_session *sdp_local,
                                     const pjmedia_sdp_session *sdp_remote,
                                     unsigned media_index)
{
    dtls_srtp *ds = (dtls_srtp *)tp;
    pj_ice_strans_state ice_state;
    pj_bool_t use_rtcp_mux = PJ_FALSE;
    pj_status_t status = PJ_SUCCESS;
    struct transport_srtp *srtp = (struct transport_srtp*)tp->user_data;

#if DTLS_DEBUG
    PJ_LOG(2,(ds->base.name, "dtls_media_start()"));
#endif

    PJ_UNUSED_ARG(tmp_pool);
    PJ_UNUSED_ARG(sdp_local);

    if (ds->srtp->offerer_side) {
        /* As offerer */
        dtls_setup last_setup = ds->setup;
        pj_str_t last_rem_fp = ds->rem_fingerprint;

        /* Parse a=setup and a=fingerprint */
        status = parse_setup_finger_attr(ds, PJ_FALSE, sdp_remote,
                                         media_index);
        if (status != PJ_SUCCESS)
            goto on_return;

        /* Check if remote signals DTLS re-nego by changing its
         * setup/fingerprint in SDP.
         */
        if ((last_setup != DTLS_SETUP_ACTPASS && last_setup != ds->setup) ||
            (last_rem_fp.slen &&
             pj_memcmp(&last_rem_fp, &ds->rem_fingerprint, sizeof(pj_str_t))))
        {
            ssl_destroy(ds);
            ds->nego_started = PJ_FALSE;
            ds->nego_completed = PJ_FALSE;
            ds->got_keys = PJ_FALSE;
            ds->rem_fprint_status = PJ_EPENDING;
        }
    } else {
        /* As answerer */
        
        /* Nothing to do? */
    }

    /* Check and update ICE and rtcp-mux status */
    {
        pjmedia_transport_info info;
        pjmedia_ice_transport_info *ice_info;

        pjmedia_transport_info_init(&info);
        pjmedia_transport_get_info(ds->srtp->member_tp, &info);
        if (pj_sockaddr_cmp(&info.sock_info.rtp_addr_name,
                            &info.sock_info.rtcp_addr_name) == 0)
        {
            use_rtcp_mux = PJ_TRUE;
        }
        ice_info = (pjmedia_ice_transport_info*)
                   pjmedia_transport_info_get_spc_info(
                                    &info, PJMEDIA_TRANSPORT_TYPE_ICE);
        ds->use_ice = ice_info && ice_info->active;
        ice_state = ds->use_ice? ice_info->sess_state : 0;

        /* Update remote RTP & RTCP addresses */
        get_rem_addrs(ds, sdp_remote, media_index, &ds->rem_addr,
                      &ds->rem_rtcp, NULL);
    }

    /* Check if the background DTLS nego has completed */
    if (ds->got_keys) { 
        ds->srtp->tx_policy_neg = ds->tx_crypto;
        ds->srtp->rx_policy_neg = ds->rx_crypto;

        /* Verify remote fingerprint (if available) */
        if (ds->rem_fingerprint.slen && ds->rem_fprint_status == PJ_EPENDING)
        {
            ds->rem_fprint_status = ssl_match_fingerprint(ds);
            if (ds->rem_fprint_status != PJ_SUCCESS) {
                pj_perror(4, ds->base.name, ds->rem_fprint_status,
                          "Fingerprint specified in remote SDP doesn't match "
                          "to actual remote certificate fingerprint!");
                return ds->rem_fprint_status;
            }
        }

        return PJ_SUCCESS;
    } 

    /* SRTP key is not ready, SRTP start is pending */
    ds->srtp->keying_pending_cnt++;
    ds->pending_start = PJ_TRUE;

    srtp->peer_use = PJMEDIA_SRTP_MANDATORY;

    /* If our DTLS setup is ACTIVE:
     * - start DTLS nego after ICE nego, or
     * - start it now if there is no ICE.
     */
    if (ds->setup == DTLS_SETUP_ACTIVE) {
        if (ds->use_ice && ice_state < PJ_ICE_STRANS_STATE_RUNNING)  {
            /* Register ourselves to listen to ICE notifications */
            pjmedia_ice_cb ice_cb;
            pj_bzero(&ice_cb, sizeof(ice_cb));
            ice_cb.on_ice_complete2 = &on_ice_complete2;
            pjmedia_ice_add_ice_cb(ds->srtp->member_tp, &ice_cb, ds);
        } else {
            /* This can happen when we are SDP offerer and remote wants
             * PASSIVE DTLS role.
             */
            pjmedia_transport_attach_param ap;
            pj_bzero(&ap, sizeof(ap));
            ap.user_data = ds->srtp;

            /* Attach ourselves to member transport for DTLS nego. */
            if (pj_sockaddr_has_addr(&ds->rem_addr))
                pj_sockaddr_cp(&ap.rem_addr, &ds->rem_addr);
            else
                pj_sockaddr_init(pj_AF_INET(), &ap.rem_addr, 0, 0);

            if (use_rtcp_mux) {
                /* Using RTP & RTCP multiplexing */
                pj_sockaddr_cp(&ap.rem_rtcp, &ds->rem_addr);
            } else if (pj_sockaddr_has_addr(&ds->rem_rtcp)) {
                pj_sockaddr_cp(&ap.rem_rtcp, &ds->rem_rtcp);
            } else if (pj_sockaddr_has_addr(&ds->rem_addr)) {
                pj_sockaddr_cp(&ap.rem_rtcp, &ds->rem_addr);
                pj_sockaddr_set_port(&ap.rem_rtcp,
                                     pj_sockaddr_get_port(&ap.rem_rtcp) + 1);
            } else {
                pj_sockaddr_init(pj_AF_INET(), &ap.rem_rtcp, 0, 0);
            }

            ap.addr_len = pj_sockaddr_get_len(&ap.rem_addr);
            status = pjmedia_transport_attach2(&ds->srtp->base, &ap);
            if (status != PJ_SUCCESS)
                goto on_return;
#if DTLS_DEBUG
            {
                char addr[PJ_INET6_ADDRSTRLEN];
                PJ_LOG(2,(ds->base.name, "Attached transport, "
                          "remote addr=%s:%d",
                          pj_sockaddr_print(&ap.rem_addr, addr,
                          sizeof(addr), 2),
                          pj_sockaddr_get_port(&ap.rem_addr)));
            }
#endif
            
            status = ssl_handshake(ds);
            if (status != PJ_SUCCESS)
                goto on_return;
        }
    }

on_return:
#if DTLS_DEBUG
    if (status != PJ_SUCCESS) {
        pj_perror(4, ds->base.name, status, "dtls_media_start() failed");
    }
#endif
    return status;
}

static pj_status_t dtls_media_stop(pjmedia_transport *tp)
{
    dtls_srtp *ds = (dtls_srtp *)tp;

#if DTLS_DEBUG
    PJ_LOG(2,(ds->base.name, "dtls_media_stop()"));
#endif

    if (ds->clock)
        pjmedia_clock_stop(ds->clock);
    
    /* Reset DTLS state */
    ssl_destroy(ds);
    ds->setup = DTLS_SETUP_UNKNOWN;
        ds->use_ice = PJ_FALSE;
    ds->nego_started = PJ_FALSE;
    ds->nego_completed = PJ_FALSE;
    ds->got_keys = PJ_FALSE;
    ds->rem_fingerprint.slen = 0;
    ds->rem_fprint_status = PJ_EPENDING;

    return PJ_SUCCESS;
}

static pj_status_t dtls_destroy(pjmedia_transport *tp)
{
    dtls_srtp *ds = (dtls_srtp *)tp;

#if DTLS_DEBUG
    PJ_LOG(2,(ds->base.name, "dtls_destroy()"));
#endif

    if (ds->clock)
        pjmedia_clock_destroy(ds->clock);
    ssl_destroy(ds);
    pj_pool_safe_release(&ds->pool);

    return PJ_SUCCESS;
}


/* Get fingerprint of local DTLS-SRTP certificate. */
PJ_DEF(pj_status_t) pjmedia_transport_srtp_dtls_get_fingerprint(
                                pjmedia_transport *tp,
                                const char *hash,
                                char *buf, pj_size_t *len)
{
    PJ_ASSERT_RETURN(dtls_cert, PJ_EINVALIDOP);
    PJ_ASSERT_RETURN(tp && hash && buf && len, PJ_EINVAL);
    PJ_ASSERT_RETURN(pj_ansi_strcmp(hash, "SHA-256")==0 ||
                     pj_ansi_strcmp(hash, "SHA-1")==0, PJ_EINVAL);
    PJ_UNUSED_ARG(tp);

    return ssl_get_fingerprint(dtls_cert,
                               pj_ansi_strcmp(hash, "SHA-256")==0,
                               buf, len);
}


/* Manually start DTLS-SRTP negotiation (without SDP offer/answer) */
PJ_DEF(pj_status_t) pjmedia_transport_srtp_dtls_start_nego(
                                pjmedia_transport *tp,
                                const pjmedia_srtp_dtls_nego_param *param)
{
    transport_srtp *srtp = (transport_srtp*)tp;
    dtls_srtp *ds = NULL;
    unsigned j;
    pjmedia_transport_attach_param ap;
    pj_status_t status;

    PJ_ASSERT_RETURN(tp && param, PJ_EINVAL);
    PJ_ASSERT_RETURN(pj_sockaddr_has_addr(&param->rem_addr), PJ_EINVAL);

    /* Find DTLS keying and destroy any other keying. */
    for (j = 0; j < srtp->all_keying_cnt; ++j) {
        if (srtp->all_keying[j]->op == &dtls_op)
            ds = (dtls_srtp*)srtp->all_keying[j];
        else
            pjmedia_transport_close(srtp->all_keying[j]);
    }

    /* DTLS-SRTP is not enabled */
    if (!ds)
        return PJ_ENOTSUP;

    /* Set SRTP keying to DTLS-SRTP only */
    srtp->keying_cnt = 1;
    srtp->keying[0] = &ds->base;
    srtp->keying_pending_cnt = 0;

    /* Apply param to DTLS-SRTP internal states */
    pj_strdup(ds->pool, &ds->rem_fingerprint, &param->rem_fingerprint);
    ds->rem_fprint_status = PJ_EPENDING;
    ds->rem_addr = param->rem_addr;
    ds->rem_rtcp = param->rem_rtcp;
    ds->setup = param->is_role_active? DTLS_SETUP_ACTIVE:DTLS_SETUP_PASSIVE;

    /* Pending start SRTP */
    ds->pending_start = PJ_TRUE;
    srtp->keying_pending_cnt++;

    /* Attach member transport, so we can send/receive DTLS init packets */
    pj_bzero(&ap, sizeof(ap));
    ap.user_data = ds->srtp;
    pj_sockaddr_cp(&ap.rem_addr, &ds->rem_addr);
    pj_sockaddr_cp(&ap.rem_rtcp, &ds->rem_rtcp);
    ap.addr_len = pj_sockaddr_get_len(&ap.rem_addr);
    status = pjmedia_transport_attach2(&ds->srtp->base, &ap);
    if (status != PJ_SUCCESS)
        goto on_return;

#if DTLS_DEBUG
    {
        char addr[PJ_INET6_ADDRSTRLEN];
        PJ_LOG(2,(ds->base.name, "Attached transport, remote addr=%s:%d",
                  pj_sockaddr_print(&ap.rem_addr, addr, sizeof(addr), 2),
                  pj_sockaddr_get_port(&ap.rem_addr)));
    }
#endif

    /* Start DTLS handshake */
    pj_bzero(&srtp->rx_policy_neg, sizeof(srtp->rx_policy_neg));
    pj_bzero(&srtp->tx_policy_neg, sizeof(srtp->tx_policy_neg));
    status = ssl_handshake(ds);
    if (status != PJ_SUCCESS)
        goto on_return;

on_return:
    if (status != PJ_SUCCESS) {
        ssl_destroy(ds);
    }
    return status;
}
