/*
 * 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
 */

#if defined(PJ_HAS_SSL_SOCK) && (PJ_HAS_SSL_SOCK != 0)

/* Include OpenSSL libraries for MSVC */
#  ifdef _MSC_VER 
#    if (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_OPENSSL)
#      include <openssl/opensslv.h>
#      if OPENSSL_VERSION_NUMBER >= 0x10100000L
#        pragma comment(lib, "libcrypto")
#        pragma comment(lib, "libssl")
#        pragma comment(lib, "crypt32")
#      else
#        pragma comment(lib, "libeay32")
#        pragma comment(lib, "ssleay32")
#      endif
#    endif
#  endif
#endif

#if (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_APPLE)
    #include <Security/SecRandom.h>
#endif

#include <pj/rand.h>


static pj_status_t sdes_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 sdes_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 sdes_media_start (pjmedia_transport *tp,
                                     pj_pool_t *pool,
                                     const pjmedia_sdp_session *sdp_local,
                                     const pjmedia_sdp_session *sdp_remote,
                                     unsigned media_index);
static pj_status_t sdes_media_stop  (pjmedia_transport *tp);


static pjmedia_transport_op sdes_op =
{
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    &sdes_media_create,
    &sdes_encode_sdp,
    &sdes_media_start,
    &sdes_media_stop,
    NULL,
    NULL
};


static pj_status_t sdes_create(transport_srtp *srtp,
                               pjmedia_transport **p_keying)
{
    pjmedia_transport *sdes;

    sdes = PJ_POOL_ZALLOC_T(srtp->pool, pjmedia_transport);
    pj_ansi_strxcpy(sdes->name, srtp->pool->obj_name, PJ_MAX_OBJ_NAME);
    pj_memcpy(sdes->name, "sdes", 4);
    sdes->type = (pjmedia_transport_type)PJMEDIA_SRTP_KEYING_SDES;
    sdes->op = &sdes_op;
    sdes->user_data = srtp;

    *p_keying = sdes;
    PJ_LOG(5,(srtp->pool->obj_name, "SRTP keying SDES created"));
    return PJ_SUCCESS;
}


/* Generate crypto attribute, including crypto key.
 * If crypto-suite chosen is crypto NULL, just return PJ_SUCCESS,
 * and set buffer_len = 0.
 */
static pj_status_t generate_crypto_attr_value(pj_pool_t *pool,
                                              char *buffer, int *buffer_len,
                                              pjmedia_srtp_crypto *crypto,
                                              int tag)
{
    pj_status_t status;
    int cs_idx = get_crypto_idx(&crypto->name);
    char b64_key[PJ_BASE256_TO_BASE64_LEN(MAX_KEY_LEN)+1];
    int b64_key_len = sizeof(b64_key);
    int print_len;

    if (cs_idx == -1)
        return PJMEDIA_SRTP_ENOTSUPCRYPTO;

    /* Crypto-suite NULL. */
    if (cs_idx == 0) {
        *buffer_len = 0;
        return PJ_SUCCESS;
    }

    /* Generate key if not specified. */
    if (crypto->key.slen == 0) {
        pj_bool_t key_ok;
        char key[MAX_KEY_LEN];
        unsigned i;

        PJ_ASSERT_RETURN(MAX_KEY_LEN >= crypto_suites[cs_idx].cipher_key_len,
                         PJ_ETOOSMALL);

        do {
#if defined(PJ_HAS_SSL_SOCK) && (PJ_HAS_SSL_SOCK != 0) && \
    (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_OPENSSL)
            int err = RAND_bytes((unsigned char*)key,
                                 crypto_suites[cs_idx].cipher_key_len);
            if (err != 1) {
                PJ_LOG(4,(THIS_FILE, "Failed generating random key "
                          "(native err=%d)", err));
                return PJMEDIA_ERRNO_FROM_LIBSRTP(1);
            }
#elif defined(PJ_HAS_SSL_SOCK) && (PJ_HAS_SSL_SOCK != 0) && \
      (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_APPLE)
            int err = SecRandomCopyBytes(kSecRandomDefault,
                                         crypto_suites[cs_idx].cipher_key_len,
                                         &key);
            if (err != errSecSuccess) {
                PJ_LOG(4,(THIS_FILE, "Failed generating random key "
                          "(native err=%d)", err));
                return PJMEDIA_ERRNO_FROM_LIBSRTP(1);
            }
#else
            PJ_LOG(3,(THIS_FILE, "Warning: simple random generator is used "
                                 "for generating SRTP key"));
            for (i=0; i<crypto_suites[cs_idx].cipher_key_len; ++i) {
                pj_timestamp ts;
                if (pj_rand() % 7 < 2)
                    pj_thread_sleep(pj_rand() % 11);
                pj_get_timestamp(&ts);
                key[i] = (char)((pj_rand() + ts.u32.lo) & 0xFF);
            }
#endif

            key_ok = PJ_TRUE;
            for (i=0; i<crypto_suites[cs_idx].cipher_key_len && key_ok; ++i)
                if (key[i] == 0) key_ok = PJ_FALSE;

        } while (!key_ok);
        crypto->key.ptr = (char*)
                          pj_pool_zalloc(pool,
                                         crypto_suites[cs_idx].cipher_key_len);
        pj_memcpy(crypto->key.ptr, key, crypto_suites[cs_idx].cipher_key_len);
        crypto->key.slen = crypto_suites[cs_idx].cipher_key_len;
    }

    if (crypto->key.slen != (pj_ssize_t)crypto_suites[cs_idx].cipher_key_len)
        return PJMEDIA_SRTP_EINKEYLEN;

    /* Key transmitted via SDP should be base64 encoded. */
    status = pj_base64_encode((pj_uint8_t*)crypto->key.ptr,
                              (int)crypto->key.slen,
                              b64_key, &b64_key_len);
    if (status != PJ_SUCCESS) {
        PJ_PERROR(4,(THIS_FILE, status,
                     "Failed encoding plain key to base64"));
        return status;
    }

    b64_key[b64_key_len] = '\0';

    PJ_ASSERT_RETURN(*buffer_len >= (crypto->name.slen + \
                     b64_key_len + 16), PJ_ETOOSMALL);

    /* Print the crypto attribute value. */
    print_len = pj_ansi_snprintf(buffer, *buffer_len, "%d %s inline:%s",
                                   tag,
                                   crypto_suites[cs_idx].name,
                                   b64_key);
    if (print_len < 1 || print_len >= *buffer_len)
        return PJ_ETOOSMALL;

    *buffer_len = print_len;

    return PJ_SUCCESS;
}


/* Parse crypto attribute line */
static pj_status_t parse_attr_crypto(pj_pool_t *pool,
                                     const pjmedia_sdp_attr *attr,
                                     pjmedia_srtp_crypto *crypto,
                                     int *tag)
{
    pj_str_t token, delim;
    pj_status_t status;
    int itmp;
    pj_ssize_t found_idx;

    pj_bzero(crypto, sizeof(*crypto));

    /* Tag */
    delim = pj_str(" ");
    found_idx = pj_strtok(&attr->value, &delim, &token, 0);
    if (found_idx == attr->value.slen) {
        PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting tag"));
        return PJMEDIA_SDP_EINATTR;
    }

    /* Tag must not use leading zeroes. */
    if (token.slen > 1 && *token.ptr == '0')
        return PJMEDIA_SDP_EINATTR;

    /* Tag must be decimal, i.e: contains only digit '0'-'9'. */
    for (itmp = 0; itmp < token.slen; ++itmp)
        if (!pj_isdigit(token.ptr[itmp]))
            return PJMEDIA_SDP_EINATTR;

    /* Get tag value. */
    *tag = pj_strtoul(&token);

    /* Crypto-suite */
    found_idx = pj_strtok(&attr->value, &delim, &token, found_idx+token.slen);
    if (found_idx == attr->value.slen) {
        PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting crypto suite"));
        return PJMEDIA_SDP_EINATTR;
    }
    pj_strdup(pool, &crypto->name, &token);

    /* Key method */
    delim = pj_str(": ");
    found_idx = pj_strtok(&attr->value, &delim, &token, found_idx+token.slen);
    if (found_idx == attr->value.slen) {
        PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting key method"));
        return PJMEDIA_SDP_EINATTR;
    }
    if (pj_stricmp2(&token, "inline")) {
        PJ_LOG(4,(THIS_FILE, "Attribute crypto key method '%.*s' "
                  "not supported!", (int)token.slen, token.ptr));
        return PJMEDIA_SDP_EINATTR;
    }

    /* Key */    
    delim = pj_str("| ");
    found_idx = pj_strtok(&attr->value, &delim, &token, found_idx+token.slen);
    if (found_idx == attr->value.slen) {
        PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting key"));
        return PJMEDIA_SDP_EINATTR;
    }
    
    if (PJ_BASE64_TO_BASE256_LEN(token.slen) > MAX_KEY_LEN) {
        PJ_LOG(4,(THIS_FILE, "Key too long"));
        return PJMEDIA_SRTP_EINKEYLEN;
    }

    /* Decode key */
    crypto->key.ptr = (char*) pj_pool_zalloc(pool, MAX_KEY_LEN);
    itmp = MAX_KEY_LEN;
    status = pj_base64_decode(&token, (pj_uint8_t*)crypto->key.ptr,
                              &itmp);
    if (status != PJ_SUCCESS) {
        PJ_PERROR(4,(THIS_FILE, status,
                     "Failed decoding crypto key from base64"));
        return status;
    }
    crypto->key.slen = itmp;

    return PJ_SUCCESS;
}


static pj_status_t sdes_media_create( pjmedia_transport *tp,
                                      pj_pool_t *sdp_pool,
                                      unsigned options,
                                      const pjmedia_sdp_session *sdp_remote,
                                      unsigned media_index)
{
    struct transport_srtp *srtp = (struct transport_srtp*)tp->user_data;
    pj_uint32_t rem_proto = 0;

    PJ_UNUSED_ARG(options);
    PJ_UNUSED_ARG(sdp_pool);

    /* Verify remote media transport, it has to be RTP/AVP or RTP/SAVP */
    if (!srtp->offerer_side) {
        pjmedia_sdp_media *m = sdp_remote->media[media_index];

        /* Get transport protocol and drop any RTCP-FB flag */
        rem_proto = pjmedia_sdp_transport_get_proto(&m->desc.transport);
        PJMEDIA_TP_PROTO_TRIM_FLAG(rem_proto, PJMEDIA_TP_PROFILE_RTCP_FB);
        if (rem_proto != PJMEDIA_TP_PROTO_RTP_AVP &&
            rem_proto != PJMEDIA_TP_PROTO_RTP_SAVP)
        {
            return PJMEDIA_SRTP_ESDPINTRANSPORT;
        }
    }

    /* Validations */
    if (srtp->offerer_side) {
        /* As offerer: do nothing. */
    } else {
        /* Validate remote media transport based on SRTP usage option. */
        switch (srtp->setting.use) {
            case PJMEDIA_SRTP_DISABLED:
                if (rem_proto == PJMEDIA_TP_PROTO_RTP_SAVP)
                    return PJMEDIA_SRTP_ESDPINTRANSPORT;
                break;
            case PJMEDIA_SRTP_OPTIONAL:
                break;
            case PJMEDIA_SRTP_MANDATORY:
                if (rem_proto != PJMEDIA_TP_PROTO_RTP_SAVP)
                    return PJMEDIA_SRTP_ESDPINTRANSPORT;
                break;
        }
    }

    return PJ_SUCCESS;
}

static pj_status_t sdes_encode_sdp( pjmedia_transport *tp,
                                    pj_pool_t *sdp_pool,
                                    pjmedia_sdp_session *sdp_local,
                                    const pjmedia_sdp_session *sdp_remote,
                                    unsigned media_index)
{
    struct transport_srtp *srtp = (struct transport_srtp*)tp->user_data;
    pjmedia_sdp_media *m_rem, *m_loc;
    enum { MAXLEN = 512 };
    char buffer[MAXLEN];
    int buffer_len;
    pj_status_t status;
    pjmedia_sdp_attr *attr;
    pj_str_t attr_value;
    unsigned i, j;

    m_rem = sdp_remote ? sdp_remote->media[media_index] : NULL;
    m_loc = sdp_local->media[media_index];

    /* Verify media transport, it has to be RTP/AVP or RTP/SAVP */
    {
        pjmedia_sdp_media *m = sdp_remote? m_rem : m_loc;
        pj_uint32_t proto = 0;

        /* Get transport protocol and drop any RTCP-FB flag */
        proto = pjmedia_sdp_transport_get_proto(&m->desc.transport);
        PJMEDIA_TP_PROTO_TRIM_FLAG(proto, PJMEDIA_TP_PROFILE_RTCP_FB);
        if (proto != PJMEDIA_TP_PROTO_RTP_AVP &&
            proto != PJMEDIA_TP_PROTO_RTP_SAVP)
        {
            return PJMEDIA_SRTP_ESDPINTRANSPORT;
        }
    }

    /* If the media is inactive, do nothing. */
    /* No, we still need to process SRTP offer/answer even if the media is
     * marked as inactive, because the transport is still alive in this
     * case (e.g. for keep-alive). See:
     *   https://github.com/pjsip/pjproject/issues/1079
     */
    /*
    if (pjmedia_sdp_media_find_attr(m_loc, &ID_INACTIVE, NULL) ||
        (m_rem && pjmedia_sdp_media_find_attr(m_rem, &ID_INACTIVE, NULL)))
        goto BYPASS_SRTP;
    */

    /* Check remote media transport & set local media transport
     * based on SRTP usage option.
     */
    if (srtp->offerer_side) {

        /* Generate transport */
        switch (srtp->setting.use) {
            case PJMEDIA_SRTP_DISABLED:
                /* Should never reach here */
                return PJ_SUCCESS;
            case PJMEDIA_SRTP_OPTIONAL:
                m_loc->desc.transport =
                                (srtp->peer_use == PJMEDIA_SRTP_MANDATORY)?
                                ID_RTP_SAVP : ID_RTP_AVP;
                break;
            case PJMEDIA_SRTP_MANDATORY:
                m_loc->desc.transport = ID_RTP_SAVP;
                break;
        }

        /* Generate crypto attribute if not yet */
        if (pjmedia_sdp_media_find_attr(m_loc, &ID_CRYPTO, NULL) == NULL) {
            int tag = 1;

            /* Offer only current active crypto if any, otherwise offer all
             * crypto-suites in the setting.
             */
            for (i=0; i<srtp->setting.crypto_count; ++i) {
                if (srtp->srtp_ctx.tx_policy.name.slen &&
                    pj_stricmp(&srtp->srtp_ctx.tx_policy.name,
                               &srtp->setting.crypto[i].name) != 0)
                {
                    continue;
                }

                buffer_len = MAXLEN;
                status = generate_crypto_attr_value(srtp->pool, buffer,
                                                    &buffer_len,
                                                    &srtp->setting.crypto[i],
                                                    tag);
                if (status != PJ_SUCCESS)
                    return status;

                /* If buffer_len==0, just skip the crypto attribute. */
                if (buffer_len) {
                    pj_strset(&attr_value, buffer, buffer_len);
                    attr = pjmedia_sdp_attr_create(srtp->pool, ID_CRYPTO.ptr,
                                                   &attr_value);
                    m_loc->attr[m_loc->attr_count++] = attr;
                    ++tag;
                }
            }
        }

    } else {
        /* Answerer side */
        pj_uint32_t rem_proto = 0;

        pj_assert(sdp_remote && m_rem);

        /* Get transport protocol and drop any RTCP-FB flag */
        rem_proto = pjmedia_sdp_transport_get_proto(&m_rem->desc.transport);
        PJMEDIA_TP_PROTO_TRIM_FLAG(rem_proto, PJMEDIA_TP_PROFILE_RTCP_FB);

        /* Generate transport */
        switch (srtp->setting.use) {
            case PJMEDIA_SRTP_DISABLED:
                /* Should never reach here */
                if (rem_proto == PJMEDIA_TP_PROTO_RTP_SAVP)
                    return PJMEDIA_SRTP_ESDPINTRANSPORT;
                return PJ_SUCCESS;
            case PJMEDIA_SRTP_OPTIONAL:
                break;
            case PJMEDIA_SRTP_MANDATORY:
                if (rem_proto != PJMEDIA_TP_PROTO_RTP_SAVP)
                    return PJMEDIA_SRTP_ESDPINTRANSPORT;
                break;
        }

        /* Generate crypto attribute if not yet */
        if (pjmedia_sdp_media_find_attr(m_loc, &ID_CRYPTO, NULL) == NULL) {

            pjmedia_srtp_crypto tmp_rx_crypto;
            pj_bool_t has_crypto_attr = PJ_FALSE;
            int matched_idx = -1;
            int chosen_tag = 0;
            int tags[64]; /* assume no more than 64 crypto attrs in a media */
            unsigned cr_attr_count = 0;

            /* Find supported crypto-suite, get the tag, and assign
             * policy_local.
             */
            for (i=0; i<m_rem->attr_count; ++i) {
                if (pj_stricmp(&m_rem->attr[i]->name, &ID_CRYPTO) != 0)
                    continue;

                has_crypto_attr = PJ_TRUE;

                status = parse_attr_crypto(srtp->pool, m_rem->attr[i],
                                           &tmp_rx_crypto,
                                           &tags[cr_attr_count]);
                if (status != PJ_SUCCESS)
                    return status;

                /* Check duplicated tag */
                for (j=0; j<cr_attr_count; ++j) {
                    if (tags[j] == tags[cr_attr_count]) {
                        //DEACTIVATE_MEDIA(sdp_pool, m_loc);
                        return PJMEDIA_SRTP_ESDPDUPCRYPTOTAG;
                    }
                }

                if (matched_idx == -1) {
                    /* lets see if the crypto-suite offered is supported */
                    for (j=0; j<srtp->setting.crypto_count; ++j)
                        if (pj_stricmp(&tmp_rx_crypto.name,
                                       &srtp->setting.crypto[j].name) == 0)
                        {
                            int cs_idx = get_crypto_idx(&tmp_rx_crypto.name);
                            
                            if (cs_idx == -1)
                                return PJMEDIA_SRTP_ENOTSUPCRYPTO;

                            if (tmp_rx_crypto.key.slen !=
                                (int)crypto_suites[cs_idx].cipher_key_len)
                                return PJMEDIA_SRTP_EINKEYLEN;

                            srtp->srtp_ctx.rx_policy_neg = tmp_rx_crypto;
                            chosen_tag = tags[cr_attr_count];
                            matched_idx = j;
                            break;
                        }
                }
                cr_attr_count++;
            }

            /* Check crypto negotiation result */
            switch (srtp->setting.use) {
                case PJMEDIA_SRTP_DISABLED:
                    /* Should never reach here */
                    break;

                case PJMEDIA_SRTP_OPTIONAL:
                    /* Bypass SDES if remote uses RTP/AVP and:
                     * - has no crypto-attr, or
                     * - has no matching crypto
                     */
                    if ((!has_crypto_attr || matched_idx == -1) &&
                        !PJMEDIA_TP_PROTO_HAS_FLAG(rem_proto,
                                                   PJMEDIA_TP_PROFILE_SRTP))
                    {
                        return PJ_SUCCESS;
                    }
                    break;

                case PJMEDIA_SRTP_MANDATORY:
                    /* Do nothing, intentional */
                    break;
            }

            /* No crypto attr */
            if (!has_crypto_attr) {
                //DEACTIVATE_MEDIA(sdp_pool, m_loc);
                return PJMEDIA_SRTP_ESDPREQCRYPTO;
            }

            /* No crypto match */
            if (matched_idx == -1) {
                //DEACTIVATE_MEDIA(sdp_pool, m_loc);
                return PJMEDIA_SRTP_ENOTSUPCRYPTO;
            }

            /* we have to generate crypto answer,
             * with srtp->srtp_ctx.tx_policy_neg matched the offer
             * and rem_tag contains matched offer tag.
             */
            buffer_len = MAXLEN;
            status = generate_crypto_attr_value(
                                        srtp->pool, buffer, &buffer_len,
                                        &srtp->setting.crypto[matched_idx],
                                        chosen_tag);
            if (status != PJ_SUCCESS)
                return status;

            srtp->srtp_ctx.tx_policy_neg = srtp->setting.crypto[matched_idx];

            /* If buffer_len==0, just skip the crypto attribute. */
            if (buffer_len) {
                pj_strset(&attr_value, buffer, buffer_len);
                attr = pjmedia_sdp_attr_create(sdp_pool, ID_CRYPTO.ptr,
                                               &attr_value);
                m_loc->attr[m_loc->attr_count++] = attr;
            }

            /* At this point, we get valid rx_policy_neg & tx_policy_neg. */
        }

        /* Update transport description in local media SDP */
        m_loc->desc.transport = m_rem->desc.transport;
    }

    return PJ_SUCCESS;
}


/* Fill local crypto array from local SDP.
 * If as_offerer is true, the crypto will be put in the array based on
 * their tag number. Note that as offerer, we generate the tag number
 * from 0..N, so the tag number minus one is always inline with array index.
 * As answerer, the crypto will be put sequentially in the array.
 */
static pj_status_t fill_local_crypto(pj_pool_t *pool,
                                     const pjmedia_sdp_media *m_loc,
                                     pj_bool_t as_offerer,
                                     pjmedia_srtp_crypto loc_crypto[],
                                     int *count)
{
    int i;
    int crypto_count = 0;
    pj_status_t status = PJ_SUCCESS;
    
    for (i = 0; i < *count; ++i) {
        pj_bzero(&loc_crypto[i], sizeof(loc_crypto[i]));
    }

    for (i = 0; i < (int)m_loc->attr_count; ++i) {      
        pjmedia_srtp_crypto tmp_crypto;
        int loc_tag;

        if (pj_stricmp(&m_loc->attr[i]->name, &ID_CRYPTO) != 0)
            continue;

        status = parse_attr_crypto(pool, m_loc->attr[i],
                                   &tmp_crypto, &loc_tag);
        if (status != PJ_SUCCESS)
            return status;

        if (loc_tag <= 0 || loc_tag > *count)
            return PJMEDIA_SRTP_ESDPINCRYPTOTAG;

        if (as_offerer) {
            loc_crypto[loc_tag-1] = tmp_crypto;
        } else {
            loc_crypto[crypto_count] = tmp_crypto;
        }
        
        if (++crypto_count >= *count)
            break;
    }
    *count = crypto_count;
    return status;
}


static pj_status_t sdes_media_start( pjmedia_transport *tp,
                                     pj_pool_t *pool,
                                     const pjmedia_sdp_session *sdp_local,
                                     const pjmedia_sdp_session *sdp_remote,
                                     unsigned media_index)
{
    struct transport_srtp *srtp = (struct transport_srtp*)tp->user_data;
    pjmedia_sdp_media *m_rem, *m_loc;
    pj_status_t status;
    unsigned i;
    pjmedia_srtp_crypto loc_crypto[PJMEDIA_SRTP_MAX_CRYPTOS];
    int loc_cryto_cnt = PJMEDIA_SRTP_MAX_CRYPTOS;
    pjmedia_srtp_crypto tmp_tx_crypto;
    pj_bool_t has_crypto_attr = PJ_FALSE;
    int rem_tag;
    int j;


    m_rem = sdp_remote->media[media_index];
    m_loc = sdp_local->media[media_index];

    /* Verify media transport, it has to be RTP/AVP or RTP/SAVP */
    {
        pj_uint32_t rem_proto;

        /* Get transport protocol and drop any RTCP-FB flag */
        rem_proto = pjmedia_sdp_transport_get_proto(&m_rem->desc.transport);
        PJMEDIA_TP_PROTO_TRIM_FLAG(rem_proto, PJMEDIA_TP_PROFILE_RTCP_FB);
        if (rem_proto != PJMEDIA_TP_PROTO_RTP_AVP &&
            rem_proto != PJMEDIA_TP_PROTO_RTP_SAVP)
        {
            return PJMEDIA_SRTP_ESDPINTRANSPORT;
        }

        /* Also check if peer signal SRTP as mandatory */
        if (rem_proto == PJMEDIA_TP_PROTO_RTP_SAVP)
            srtp->peer_use = PJMEDIA_SRTP_MANDATORY;
        else
            srtp->peer_use = PJMEDIA_SRTP_OPTIONAL;
    }

    /* For answerer side, SRTP crypto policies have been populated in
     * media_encode_sdp(). Check if the key changes on the local SDP.
     */
    if (!srtp->offerer_side) {
        if (srtp->srtp_ctx.tx_policy_neg.name.slen == 0)
            return PJ_SUCCESS;

        /* Get the local crypto. */
        fill_local_crypto(srtp->pool, m_loc, PJ_FALSE,
                          loc_crypto, &loc_cryto_cnt);

        if (loc_cryto_cnt == 0)
            return PJ_SUCCESS;

        if ((pj_stricmp(&srtp->srtp_ctx.tx_policy_neg.name,
                        &loc_crypto[0].name) == 0) &&
            (pj_stricmp(&srtp->srtp_ctx.tx_policy_neg.key,
                        &loc_crypto[0].key) != 0))
        {
            srtp->srtp_ctx.tx_policy_neg = loc_crypto[0];
            for (i = 0; i<srtp->setting.crypto_count ;++i) {
                if ((pj_stricmp(&srtp->setting.crypto[i].name,
                                &loc_crypto[0].name) == 0) &&
                    (pj_stricmp(&srtp->setting.crypto[i].key,
                                 &loc_crypto[0].key) != 0))
                {
                    pj_strdup(srtp->pool, &srtp->setting.crypto[i].key,
                              &loc_crypto[0].key);
                }
            }
        }

        return PJ_SUCCESS;
    }

    /* Check remote media transport & set local media transport
     * based on SRTP usage option.
     */
    if (srtp->setting.use == PJMEDIA_SRTP_DISABLED) {
        if (pjmedia_sdp_media_find_attr(m_rem, &ID_CRYPTO, NULL)) {
            DEACTIVATE_MEDIA(pool, m_loc);
            return PJMEDIA_SRTP_ESDPINCRYPTO;
        }
        return PJ_SUCCESS;
    } else if (srtp->setting.use == PJMEDIA_SRTP_OPTIONAL) {
        // Regardless the answer's transport type (RTP/AVP or RTP/SAVP),
        // the answer must be processed through in optional mode.
        // Please note that at this point transport type is ensured to be
        // RTP/AVP or RTP/SAVP, see sdes_media_create()
        //if (pj_stricmp(&m_rem->desc.transport, &m_loc->desc.transport)) {
            //DEACTIVATE_MEDIA(pool, m_loc);
            //return PJMEDIA_SDP_EINPROTO;
        //}
        fill_local_crypto(srtp->pool, m_loc, PJ_TRUE,
                          loc_crypto, &loc_cryto_cnt);
    } else if (srtp->setting.use == PJMEDIA_SRTP_MANDATORY) {
        if (srtp->peer_use != PJMEDIA_SRTP_MANDATORY) {
            DEACTIVATE_MEDIA(pool, m_loc);
            return PJMEDIA_SDP_EINPROTO;
        }
        fill_local_crypto(srtp->pool, m_loc, PJ_TRUE,
                          loc_crypto, &loc_cryto_cnt);
    }

    /* find supported crypto-suite, get the tag, and assign policy_local */
    for (i=0; i<m_rem->attr_count; ++i) {
        if (pj_stricmp(&m_rem->attr[i]->name, &ID_CRYPTO) != 0)
            continue;

        /* more than one crypto attribute in media answer */
        if (has_crypto_attr) {
            DEACTIVATE_MEDIA(pool, m_loc);
            return PJMEDIA_SRTP_ESDPAMBIGUEANS;
        }

        has_crypto_attr = PJ_TRUE;

        status = parse_attr_crypto(srtp->pool, m_rem->attr[i],
                                   &tmp_tx_crypto, &rem_tag);
        if (status != PJ_SUCCESS)
            return status;


        /* Tag range check, our tags in the offer must be in the SRTP 
         * setting range, so does the remote answer's. The remote answer's 
         * tag must not exceed the tag range of the local offer.
         */
        if (rem_tag < 1 || rem_tag > (int)srtp->setting.crypto_count ||
            rem_tag > loc_cryto_cnt) 
        {
            DEACTIVATE_MEDIA(pool, m_loc);
            return PJMEDIA_SRTP_ESDPINCRYPTOTAG;
        }

        /* match the crypto name */
        if (pj_stricmp(&tmp_tx_crypto.name, &loc_crypto[rem_tag-1].name))
        {
            DEACTIVATE_MEDIA(pool, m_loc);
            return PJMEDIA_SRTP_ECRYPTONOTMATCH;
        }

        /* Find the crypto from the local crypto. */
        for (j = 0; j < (int)loc_cryto_cnt; ++j) {
            if (pj_stricmp(&tmp_tx_crypto.name,
                           &loc_crypto[j].name) == 0)
            {
                srtp->srtp_ctx.tx_policy_neg = loc_crypto[j];
                break;
            }
        }

        srtp->srtp_ctx.rx_policy_neg = tmp_tx_crypto;
    }

    if (srtp->setting.use == PJMEDIA_SRTP_DISABLED) {
        /* should never reach here */
        return PJ_SUCCESS;
    } else if (srtp->setting.use == PJMEDIA_SRTP_OPTIONAL) {
        if (!has_crypto_attr)
            return PJ_SUCCESS;
    } else if (srtp->setting.use == PJMEDIA_SRTP_MANDATORY) {
        if (!has_crypto_attr) {
            DEACTIVATE_MEDIA(pool, m_loc);
            return PJMEDIA_SRTP_ESDPREQCRYPTO;
        }
    }

    /* At this point, we get valid rx_policy_neg & tx_policy_neg. */

    return PJ_SUCCESS;
}

static pj_status_t sdes_media_stop(pjmedia_transport *tp)
{
    PJ_UNUSED_ARG(tp);
    return PJ_SUCCESS;
}
