/*
 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
 *
 * 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/transport_srtp.h>
#include <pjmedia/endpoint.h>
#include <pjmedia/rtp.h>
#include <pjlib-util/base64.h>
#include <pj/array.h>
#include <pj/assert.h>
#include <pj/ctype.h>
#include <pj/lock.h>
#include <pj/log.h>
#include <pj/os.h>
#include <pj/pool.h>

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

/* Enable this to test ROC initialization setting. For offerer,
 * it will send packets with ROC 1 and expect to receive ROC 2.
 * For answerer it will be the other way around.
 */
#define TEST_ROC 0

#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && \
    (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_OPENSSL)
#  include <openssl/rand.h>
#  include <openssl/opensslv.h>

/* Suppress compile warning of OpenSSL deprecation (OpenSSL is deprecated
 * since MacOSX 10.7).
 */
#if defined(PJ_DARWINOS) && PJ_DARWINOS==1
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif

#endif

#if defined(PJMEDIA_EXTERNAL_SRTP)

#if (PJMEDIA_EXTERNAL_SRTP == 1)        /* External SRTP 1.x */
#  include <srtp/srtp.h>
#  include <srtp/crypto_kernel.h>
#define srtp_crypto_policy_t            crypto_policy_t
#define srtp_cipher_type_id_t           cipher_type_id_t
#define srtp_cipher_type_t              cipher_type_t
#define srtp_auth_type_id_t             auth_type_id_t
#define srtp_sec_serv_t                 sec_serv_t
#define srtp_err_status_t               err_status_t
#define srtp_err_status_ok              err_status_ok
#define srtp_err_status_replay_old      err_status_replay_old
#define srtp_err_status_replay_fail     err_status_replay_fail
#define srtp_crypto_policy_set_aes_cm_256_hmac_sha1_32 \
             crypto_policy_set_aes_cm_256_hmac_sha1_32
#define srtp_crypto_policy_set_aes_cm_256_hmac_sha1_80 \
             crypto_policy_set_aes_cm_256_hmac_sha1_80
#define SRTP_NULL_CIPHER                NULL_CIPHER
#define SRTP_NULL_AUTH                  NULL_AUTH
#define SRTP_AES_ICM_128                AES_ICM
#define SRTP_AES_ICM_192                AES_ICM
#define SRTP_AES_ICM_256                AES_ICM
#define SRTP_AES_GCM_128                AES_128_GCM
#define SRTP_AES_GCM_256                AES_256_GCM
#define SRTP_HMAC_SHA1                  HMAC_SHA1
#define srtp_aes_gcm_256_openssl        aes_gcm_256_openssl
#define srtp_aes_gcm_128_openssl        aes_gcm_128_openssl

#else                                   /* External SRTP 2.x */
#  include <srtp2/srtp.h>
#  include <srtp2/cipher.h>

/* In libsrtp 2.0.0, the macro SRTP_AES_ICM_128 is not available. 
 * Instead it was named with ICM at the end: SRTP_AES_128_ICM. 
 */
#  ifdef SRTP_AES_128_ICM
#    define SRTP_AES_ICM_128            SRTP_AES_128_ICM
#    define SRTP_AES_ICM_192            SRTP_AES_192_ICM
#    define SRTP_AES_ICM_256            SRTP_AES_256_ICM
#    define SRTP_AES_GCM_128            SRTP_AES_128_GCM
#    define SRTP_AES_GCM_256            SRTP_AES_256_GCM
#  endif

#endif

#else                                   /* Bundled SRTP */

#  include <srtp_config.h>
#  include <srtp.h>
#  include <crypto_kernel.h>

#define srtp_aes_gcm_256_openssl        srtp_aes_gcm_256
#define srtp_aes_gcm_128_openssl        srtp_aes_gcm_128

#endif

#define THIS_FILE   "transport_srtp.c"

/* Maximum size of outgoing packet */
#define MAX_RTP_BUFFER_LEN          PJMEDIA_MAX_MTU
#define MAX_RTCP_BUFFER_LEN         PJMEDIA_MAX_MTU

/* Maximum SRTP crypto key length */
#define MAX_KEY_LEN                 128

/* Initial value of probation counter. When probation counter > 0,
 * it means SRTP is in probation state, and it may restart when
 * srtp_unprotect() returns err_status_replay_*
 */
#define PROBATION_CNT_INIT          100

#define DEACTIVATE_MEDIA(pool, m)   pjmedia_sdp_media_deactivate(pool, m)

#ifdef SRTP_MAX_TRAILER_LEN
#   define MAX_TRAILER_LEN SRTP_MAX_TRAILER_LEN
#else
#   define MAX_TRAILER_LEN 10
#endif

/* Maximum number of SRTP keying method */
#define MAX_KEYING                  2

static const pj_str_t ID_RTP_AVP  = { "RTP/AVP", 7 };
static const pj_str_t ID_RTP_SAVP = { "RTP/SAVP", 8 };
// static const pj_str_t ID_INACTIVE = { "inactive", 8 };
static const pj_str_t ID_CRYPTO   = { "crypto", 6 };

typedef void (*crypto_method_t)(srtp_crypto_policy_t *policy);

typedef struct crypto_suite
{
    char                *name;
    srtp_cipher_type_id_t cipher_type;
    unsigned             cipher_key_len;    /* key + salt length    */
    unsigned             cipher_salt_len;   /* salt only length     */
    srtp_auth_type_id_t  auth_type;
    unsigned             auth_key_len;
    unsigned             srtp_auth_tag_len;
    unsigned             srtcp_auth_tag_len;
    srtp_sec_serv_t      service;
    /* This is an attempt to validate crypto support by libsrtp, i.e: it should
     * raise linking error if the libsrtp does not support the crypto. 
     */
    srtp_cipher_type_t  *ext_cipher_type;
    crypto_method_t      ext_crypto_method;
} crypto_suite;

extern srtp_cipher_type_t srtp_aes_gcm_256_openssl;
extern srtp_cipher_type_t srtp_aes_gcm_128_openssl;
extern srtp_cipher_type_t srtp_aes_icm_192;

/* https://www.iana.org/assignments/sdp-security-descriptions/sdp-security-descriptions.xhtml */
static crypto_suite crypto_suites[] = {
    /* plain RTP/RTCP (no cipher & no auth) */
    {"NULL", SRTP_NULL_CIPHER, 0, SRTP_NULL_AUTH, 0, 0, 0, sec_serv_none},

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

    /* cipher AES_GCM, NULL auth, auth tag len = 16 octets */
    {"AEAD_AES_256_GCM", SRTP_AES_GCM_256, 44, 12,
        SRTP_NULL_AUTH, 0, 16, 16, sec_serv_conf_and_auth,
        &srtp_aes_gcm_256_openssl},

    /* cipher AES_GCM, NULL auth, auth tag len = 8 octets */
    {"AEAD_AES_256_GCM_8", SRTP_AES_GCM_256, 44, 12,
        SRTP_NULL_AUTH, 0, 8, 8, sec_serv_conf_and_auth,
        &srtp_aes_gcm_256_openssl},
#endif
#if defined(PJMEDIA_SRTP_HAS_AES_CM_256)&&(PJMEDIA_SRTP_HAS_AES_CM_256!=0)

    /* cipher AES_CM_256, auth SRTP_HMAC_SHA1, auth tag len = 10 octets */
    {"AES_256_CM_HMAC_SHA1_80", SRTP_AES_ICM_256, 46, 14,
        SRTP_HMAC_SHA1, 20, 10, 10, sec_serv_conf_and_auth,
        NULL, &srtp_crypto_policy_set_aes_cm_256_hmac_sha1_80},

    /* cipher AES_CM_256, auth SRTP_HMAC_SHA1, auth tag len = 10 octets */
    {"AES_256_CM_HMAC_SHA1_32", SRTP_AES_ICM_256, 46, 14,
        SRTP_HMAC_SHA1, 20, 4, 10, sec_serv_conf_and_auth,
        NULL, &srtp_crypto_policy_set_aes_cm_256_hmac_sha1_32},
#endif
#if defined(PJMEDIA_SRTP_HAS_AES_CM_192)&&(PJMEDIA_SRTP_HAS_AES_CM_192!=0)

    /* cipher AES_CM_192, auth SRTP_HMAC_SHA1, auth tag len = 10 octets */
    {"AES_192_CM_HMAC_SHA1_80", SRTP_AES_ICM_192, 38, 14,
        SRTP_HMAC_SHA1, 20, 10, 10, sec_serv_conf_and_auth,
        &srtp_aes_icm_192},

    /* cipher AES_CM_192, auth SRTP_HMAC_SHA1, auth tag len = 4 octets */
    {"AES_192_CM_HMAC_SHA1_32", SRTP_AES_ICM_192, 38, 14,
        SRTP_HMAC_SHA1, 20, 4, 10, sec_serv_conf_and_auth,
        &srtp_aes_icm_192},
#endif
#if defined(PJMEDIA_SRTP_HAS_AES_GCM_128)&&(PJMEDIA_SRTP_HAS_AES_GCM_128!=0)

    /* cipher AES_GCM, NULL auth, auth tag len = 16 octets */
    {"AEAD_AES_128_GCM", SRTP_AES_GCM_128, 28, 12,
        SRTP_NULL_AUTH, 0, 16, 16, sec_serv_conf_and_auth,
        &srtp_aes_gcm_128_openssl},

    /* cipher AES_GCM, NULL auth, auth tag len = 8 octets */
    {"AEAD_AES_128_GCM_8", SRTP_AES_GCM_128, 28, 12,
        SRTP_NULL_AUTH, 0, 8, 8, sec_serv_conf_and_auth,
        &srtp_aes_gcm_128_openssl},
#endif
#if defined(PJMEDIA_SRTP_HAS_AES_CM_128)&&(PJMEDIA_SRTP_HAS_AES_CM_128!=0)

    /* cipher AES_CM_128, auth SRTP_HMAC_SHA1, auth tag len = 10 octets */
    {"AES_CM_128_HMAC_SHA1_80", SRTP_AES_ICM_128, 30, 14,
        SRTP_HMAC_SHA1, 20, 10, 10, sec_serv_conf_and_auth},

    /* cipher AES_CM_128, auth SRTP_HMAC_SHA1, auth tag len = 4 octets */
    {"AES_CM_128_HMAC_SHA1_32", SRTP_AES_ICM_128, 30, 14,
        SRTP_HMAC_SHA1, 20, 4, 10, sec_serv_conf_and_auth},
#endif

    /*
     * F8_128_HMAC_SHA1_8 not supported by libsrtp?
     * {"F8_128_HMAC_SHA1_8", NULL_CIPHER, 0, 0, NULL_AUTH, 0, 0, 0,
     *  sec_serv_none}
     */
};

typedef struct srtp_context
{
    /* SRTP policy */
    char                 tx_key[MAX_KEY_LEN];
    char                 rx_key[MAX_KEY_LEN];
    pjmedia_srtp_crypto  tx_policy;
    pjmedia_srtp_crypto  rx_policy;

    /* Temporary policy for negotiation */
    pjmedia_srtp_crypto  tx_policy_neg;
    pjmedia_srtp_crypto  rx_policy_neg;

    /* libSRTP contexts */
    srtp_t               srtp_tx_ctx;
    srtp_t               srtp_rx_ctx;
} srtp_context;

/* SRTP transport */
typedef struct transport_srtp
{
    pjmedia_transport    base;              /**< Base transport interface.  */
    pj_pool_t           *pool;              /**< Pool for transport SRTP.   */
    pj_lock_t           *mutex;             /**< Mutex for libsrtp contexts.*/
    char                 rtp_tx_buffer[MAX_RTP_BUFFER_LEN];
    char                 rtcp_tx_buffer[MAX_RTCP_BUFFER_LEN];
    pjmedia_srtp_setting setting;
    unsigned             media_option;
    pj_bool_t            use_rtcp_mux;      /**< Use RTP& RTCP multiplexing?*/

    pj_bool_t            session_inited;
    pj_bool_t            offerer_side;
    pj_bool_t            bypass_srtp;

    /* SRTP contexts */
    srtp_context         srtp_ctx;
    srtp_context         srtp_rtcp;         /**< Separate context for RTCP,
                                                 if needed, such as for DTLS*/

    /* Stream information */
    void                *user_data;
    void                (*rtp_cb)( void *user_data,
                                   void *pkt,
                                   pj_ssize_t size);
    void                (*rtp_cb2)(pjmedia_tp_cb_param*);
    void                (*rtcp_cb)(void *user_data,
                                   void *pkt,
                                   pj_ssize_t size);

    /* Transport information */
    pjmedia_transport   *member_tp; /**< Underlying transport.       */
    pj_bool_t            member_tp_attached;
    pj_bool_t            started;

    /* SRTP usage policy of peer. This field is updated when media is starting.
     * This is useful when SRTP is in optional mode and peer is using mandatory
     * mode, so when local is about to reinvite/update, it should offer
     * RTP/SAVP instead of offering RTP/AVP.
     */
    pjmedia_srtp_use     peer_use;

    /* When probation counter > 0, it means SRTP is in probation state,
     * and it may restart when srtp_unprotect() returns err_status_replay_*
     */
    unsigned             probation_cnt;

    /* SRTP keying methods. The keying is implemented using media transport
     * abstraction, so it will also be invoked when the SRTP media transport
     * operation is invoked.
     *
     * As there can be multiple keying methods enabled (currently only SDES &
     * DTLS-SRTP), each keying method will be given the chance to respond to
     * remote SDP. If any keying operation returns non-success, it will be
     * removed from the session. And once SRTP key is obtained via a keying
     * method, any other keying methods will be stopped and destroyed.
     */
    unsigned             all_keying_cnt;
    pjmedia_transport   *all_keying[MAX_KEYING];

    /* Current active SRTP keying methods. */
    unsigned             keying_cnt;
    pjmedia_transport   *keying[MAX_KEYING];

    /* If not zero, keying nego is ongoing (async-ly, e.g: by DTLS-SRTP).
     * This field may be updated by keying method.
     */
    unsigned             keying_pending_cnt;

    /* RTP SSRC in receiving direction, used in getting and setting SRTP
     * roll over counter (ROC) on SRTP restart.
     */
    pj_uint32_t          rx_ssrc;

    pj_uint32_t          tx_ssrc;

} transport_srtp;


/*
 * This callback is called by transport when incoming rtp is received
 */
static void srtp_rtp_cb(pjmedia_tp_cb_param *param);

/*
 * This callback is called by transport when incoming rtcp is received
 */
static void srtp_rtcp_cb( void *user_data, void *pkt, pj_ssize_t size);


/*
 * These are media transport operations.
 */
static pj_status_t transport_get_info (pjmedia_transport *tp,
                                       pjmedia_transport_info *info);
//static pj_status_t transport_attach   (pjmedia_transport *tp,
//                                     void *user_data,
//                                     const pj_sockaddr_t *rem_addr,
//                                     const pj_sockaddr_t *rem_rtcp,
//                                     unsigned addr_len,
//                                     void (*rtp_cb)(void*,
//                                                    void*,
//                                                    pj_ssize_t),
//                                     void (*rtcp_cb)(void*,
//                                                     void*,
//                                                     pj_ssize_t));
static void        transport_detach   (pjmedia_transport *tp,
                                       void *strm);
static pj_status_t transport_send_rtp( pjmedia_transport *tp,
                                       const void *pkt,
                                       pj_size_t size);
static pj_status_t transport_send_rtcp(pjmedia_transport *tp,
                                       const void *pkt,
                                       pj_size_t size);
static pj_status_t transport_send_rtcp2(pjmedia_transport *tp,
                                       const pj_sockaddr_t *addr,
                                       unsigned addr_len,
                                       const void *pkt,
                                       pj_size_t size);
static pj_status_t transport_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 transport_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 transport_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 transport_media_stop(pjmedia_transport *tp);
static pj_status_t transport_simulate_lost(pjmedia_transport *tp,
                                       pjmedia_dir dir,
                                       unsigned pct_lost);
static pj_status_t transport_destroy  (pjmedia_transport *tp);
static pj_status_t transport_attach2  (pjmedia_transport *tp,
                                       pjmedia_transport_attach_param *param);



static pjmedia_transport_op transport_srtp_op =
{
    &transport_get_info,
    NULL, //&transport_attach,
    &transport_detach,
    &transport_send_rtp,
    &transport_send_rtcp,
    &transport_send_rtcp2,
    &transport_media_create,
    &transport_encode_sdp,
    &transport_media_start,
    &transport_media_stop,
    &transport_simulate_lost,
    &transport_destroy,
    &transport_attach2
};

/* Get crypto index from crypto name */
static int get_crypto_idx(const pj_str_t* crypto_name);

/* Is crypto empty (i.e: no name or key)? */
static pj_bool_t srtp_crypto_empty(const pjmedia_srtp_crypto* c);

/* Compare crypto, return zero if same */
static int srtp_crypto_cmp(const pjmedia_srtp_crypto* c1,
                           const pjmedia_srtp_crypto* c2);

/* Start SRTP */
static pj_status_t start_srtp(transport_srtp *srtp);
/* Create SRTP context */
static pj_status_t create_srtp_ctx(transport_srtp *srtp,
                                   srtp_context *ctx,
                                   const pjmedia_srtp_setting *setting,
                                   const pjmedia_srtp_crypto *tx,
                                   const pjmedia_srtp_crypto *rx);
/* Destroy SRTP context */
static void destroy_srtp_ctx(transport_srtp *p_srtp, srtp_context *ctx);

/* SRTP destroy handler */
static void srtp_on_destroy(void *arg);

/* This function may also be used by other module, e.g: pjmedia/errno.c,
 * it should have C compatible declaration.
 */
PJ_BEGIN_DECL
    const char* get_libsrtp_errstr(int err);
PJ_END_DECL

const char* get_libsrtp_errstr(int err)
{
#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
    static char *liberr[] = {
        "ok",                               /* srtp_err_status_ok       = 0  */
        "unspecified failure",              /* err_status_fail          = 1  */
        "unsupported parameter",            /* err_status_bad_param     = 2  */
        "couldn't allocate memory",         /* err_status_alloc_fail    = 3  */
        "couldn't deallocate properly",     /* err_status_dealloc_fail  = 4  */
        "couldn't initialize",              /* err_status_init_fail     = 5  */
        "can't process as much data as requested",
                                            /* err_status_terminus      = 6  */
        "authentication failure",           /* err_status_auth_fail     = 7  */
        "cipher failure",                   /* err_status_cipher_fail   = 8  */
        "replay check failed (bad index)",  /* err_status_replay_fail   = 9  */
        "replay check failed (index too old)",
                                            /* err_status_replay_old    = 10 */
        "algorithm failed test routine",    /* err_status_algo_fail     = 11 */
        "unsupported operation",            /* err_status_no_such_op    = 12 */
        "no appropriate context found",     /* err_status_no_ctx        = 13 */
        "unable to perform desired validation",
                                            /* err_status_cant_check    = 14 */
        "can't use key any more",           /* err_status_key_expired   = 15 */
        "error in use of socket",           /* err_status_socket_err    = 16 */
        "error in use POSIX signals",       /* err_status_signal_err    = 17 */
        "nonce check failed",               /* err_status_nonce_bad     = 18 */
        "couldn't read data",               /* err_status_read_fail     = 19 */
        "couldn't write data",              /* err_status_write_fail    = 20 */
        "error pasring data",               /* err_status_parse_err     = 21 */
        "error encoding data",              /* err_status_encode_err    = 22 */
        "error while using semaphores",     /* err_status_semaphore_err = 23 */
        "error while using pfkey"           /* err_status_pfkey_err     = 24 */
    };
    if (err >= 0 && err < (int)PJ_ARRAY_SIZE(liberr)) {
        return liberr[err];
    } else {
        static char msg[32];
        pj_ansi_snprintf(msg, sizeof(msg), "Unknown libsrtp error %d", err);
        return msg;
    }
#else
    static char msg[32];
    pj_ansi_snprintf(msg, sizeof(msg), "libsrtp error %d", err);
    return msg;
#endif
}

/* SRTP keying method: Session Description */
#if defined(PJMEDIA_SRTP_HAS_SDES) && (PJMEDIA_SRTP_HAS_SDES != 0)
#  include "transport_srtp_sdes.c"
#endif

/* SRTP keying method: DTLS */
#if defined(PJMEDIA_SRTP_HAS_DTLS) && (PJMEDIA_SRTP_HAS_DTLS != 0)
#  include "transport_srtp_dtls.c"
#else
PJ_DEF(pj_status_t) pjmedia_transport_srtp_dtls_start_nego(
                                pjmedia_transport *srtp,
                                const pjmedia_srtp_dtls_nego_param *param)
{
    PJ_UNUSED_ARG(srtp);
    PJ_UNUSED_ARG(param);
    return PJ_ENOTSUP;
}
PJ_DEF(pj_status_t) pjmedia_transport_srtp_dtls_get_fingerprint(
                                pjmedia_transport *srtp,
                                const char *hash,
                                char *buf, pj_size_t *len)
{
    PJ_UNUSED_ARG(srtp);
    PJ_UNUSED_ARG(hash);
    PJ_UNUSED_ARG(buf);
    PJ_UNUSED_ARG(len);
    return PJ_ENOTSUP;
}
#endif


static pj_bool_t libsrtp_initialized;
static void pjmedia_srtp_deinit_lib(pjmedia_endpt *endpt);

PJ_DEF(pj_status_t) pjmedia_srtp_init_lib(pjmedia_endpt *endpt)
{
    pj_status_t status = PJ_SUCCESS;

    if (libsrtp_initialized)
        return PJ_SUCCESS;

#if PJMEDIA_LIBSRTP_AUTO_INIT_DEINIT
    /* Init libsrtp */
    {
        srtp_err_status_t err;

        err = srtp_init();
        if (err != srtp_err_status_ok) {
            PJ_LOG(4, (THIS_FILE, "Failed to initialize libsrtp: %s",
                       get_libsrtp_errstr(err)));
            pjmedia_srtp_deinit_lib(endpt);
            return PJMEDIA_ERRNO_FROM_LIBSRTP(err);
        }
    }
#endif

#if defined(PJMEDIA_SRTP_HAS_DTLS) && (PJMEDIA_SRTP_HAS_DTLS != 0)
    dtls_init();
#endif

    status = pjmedia_endpt_atexit(endpt, pjmedia_srtp_deinit_lib);
    if (status != PJ_SUCCESS) {
        /* There will be memory leak when it fails to schedule libsrtp
         * deinitialization, however the memory leak could be harmless,
         * since in modern OS's memory used by an application is released
         * when the application terminates.
         */
        PJ_PERROR(4, (THIS_FILE, status,
                      "Failed to register libsrtp deinit."));

        /* Ignore this error */
        status = PJ_SUCCESS;
    }

    libsrtp_initialized = PJ_TRUE;

    return status;
}

static void pjmedia_srtp_deinit_lib(pjmedia_endpt *endpt)
{
    srtp_err_status_t err;

    /* Note that currently this SRTP init/deinit is not equipped with
     * reference counter, it should be safe as normally there is only
     * one single instance of media endpoint and even if it isn't, the
     * pjmedia_transport_srtp_create() will invoke SRTP init (the only
     * drawback should be the delay described by #788).
     */

    PJ_UNUSED_ARG(endpt);

#if !defined(PJMEDIA_SRTP_HAS_DEINIT) && !defined(PJMEDIA_SRTP_HAS_SHUTDOWN)
# define PJMEDIA_SRTP_HAS_SHUTDOWN 1
#endif

#if PJMEDIA_LIBSRTP_AUTO_INIT_DEINIT

# if defined(PJMEDIA_SRTP_HAS_DEINIT) && PJMEDIA_SRTP_HAS_DEINIT!=0
    err = srtp_deinit();
# elif defined(PJMEDIA_SRTP_HAS_SHUTDOWN) && PJMEDIA_SRTP_HAS_SHUTDOWN!=0
    err = srtp_shutdown();
# else
    err = srtp_err_status_ok;
# endif
    if (err != srtp_err_status_ok) {
        PJ_LOG(4, (THIS_FILE, "Failed to deinitialize libsrtp: %s",
                   get_libsrtp_errstr(err)));
    }
#endif // PJMEDIA_LIBSRTP_AUTO_INIT_DEINIT

#if defined(PJMEDIA_SRTP_HAS_DTLS) && (PJMEDIA_SRTP_HAS_DTLS != 0)
    dtls_deinit();
#endif

    libsrtp_initialized = PJ_FALSE;
}


static int get_crypto_idx(const pj_str_t* crypto_name)
{
    int i;
    int cs_cnt = PJ_ARRAY_SIZE(crypto_suites);

    /* treat unspecified crypto_name as crypto 'NULL' */
    if (crypto_name->slen == 0)
        return 0;

    for (i=0; i<cs_cnt; ++i) {
        if (!pj_stricmp2(crypto_name, crypto_suites[i].name))
            return i;
    }

    return -1;
}


static int srtp_crypto_cmp(const pjmedia_srtp_crypto* c1,
                           const pjmedia_srtp_crypto* c2)
{
    int r;

    r = pj_strcmp(&c1->key, &c2->key);
    if (r != 0)
        return r;

    r = pj_stricmp(&c1->name, &c2->name);
    if (r != 0)
        return r;

    return (c1->flags != c2->flags);
}


static pj_bool_t srtp_crypto_empty(const pjmedia_srtp_crypto* c)
{
    return (c->name.slen==0 || c->key.slen==0);
}


PJ_DEF(void) pjmedia_srtp_setting_default(pjmedia_srtp_setting *opt)
{
    pj_assert(opt);

    pj_bzero(opt, sizeof(pjmedia_srtp_setting));
    opt->close_member_tp = PJ_TRUE;
    opt->use = PJMEDIA_SRTP_OPTIONAL;
}

/*
 * Enumerate all SRTP cryptos, except "NULL".
 */
PJ_DEF(pj_status_t) pjmedia_srtp_enum_crypto(unsigned *count,
                                             pjmedia_srtp_crypto crypto[])
{
    unsigned i, max;

    PJ_ASSERT_RETURN(count && crypto, PJ_EINVAL);

    max = PJ_ARRAY_SIZE(crypto_suites) - 1;
    if (*count > max)
        *count = max;

    for (i=0; i<*count; ++i) {
        pj_bzero(&crypto[i], sizeof(crypto[0]));
        crypto[i].name = pj_str(crypto_suites[i+1].name);
    }
    
    return PJ_SUCCESS;
}


/*
 * Enumerate available SRTP keying methods.
 */
PJ_DEF(pj_status_t) pjmedia_srtp_enum_keying(unsigned *count,
                                      pjmedia_srtp_keying_method keying[])
{
    unsigned max;

    PJ_ASSERT_RETURN(count && keying, PJ_EINVAL);

    max = *count;
    *count = 0;

#if defined(PJMEDIA_SRTP_HAS_SDES) && (PJMEDIA_SRTP_HAS_SDES != 0)
    if (*count < max)
        keying[(*count)++] = PJMEDIA_SRTP_KEYING_SDES;
#endif
#if defined(PJMEDIA_SRTP_HAS_DTLS) && (PJMEDIA_SRTP_HAS_DTLS != 0)
    if (*count < max)
        keying[(*count)++] = PJMEDIA_SRTP_KEYING_DTLS_SRTP;
#endif
    
    return PJ_SUCCESS;
}


/*
 * Create an SRTP media transport.
 */
PJ_DEF(pj_status_t) pjmedia_transport_srtp_create(
                                       pjmedia_endpt *endpt,
                                       pjmedia_transport *tp,
                                       const pjmedia_srtp_setting *opt,
                                       pjmedia_transport **p_tp)
{
    pj_pool_t *pool;
    transport_srtp *srtp;
    pj_status_t status;
    unsigned i;

    PJ_ASSERT_RETURN(endpt && tp && p_tp, PJ_EINVAL);

    /* Check crypto */
    if (opt && opt->use != PJMEDIA_SRTP_DISABLED) {
        for (i=0; i < opt->crypto_count; ++i) {
            int cs_idx = get_crypto_idx(&opt->crypto[i].name);

            /* check crypto name */
            if (cs_idx == -1)
                return PJMEDIA_SRTP_ENOTSUPCRYPTO;

            /* check key length */
            if (opt->crypto[i].key.slen &&
                opt->crypto[i].key.slen <
                (pj_ssize_t)crypto_suites[cs_idx].cipher_key_len)
                return PJMEDIA_SRTP_EINKEYLEN;
        }
    }

    /* Init libsrtp. */
    status = pjmedia_srtp_init_lib(endpt);
    if (status != PJ_SUCCESS)
        return status;

    pool = pjmedia_endpt_create_pool(endpt, "srtp%p", 1000, 1000);
    srtp = PJ_POOL_ZALLOC_T(pool, transport_srtp);

    srtp->pool = pool;
    srtp->session_inited = PJ_FALSE;
    srtp->bypass_srtp = PJ_FALSE;
    srtp->probation_cnt = PROBATION_CNT_INIT;

    if (opt) {
        srtp->setting = *opt;
        if (opt->use == PJMEDIA_SRTP_DISABLED)
            srtp->setting.crypto_count = 0;

        for (i=0; i < srtp->setting.crypto_count; ++i) {
            int cs_idx = get_crypto_idx(&opt->crypto[i].name);
            pj_str_t tmp_key = opt->crypto[i].key;

            if (cs_idx == -1) continue;

            /* re-set crypto */
            srtp->setting.crypto[i].name = pj_str(crypto_suites[cs_idx].name);
            /* cut key length */
            if (tmp_key.slen)
                tmp_key.slen = crypto_suites[cs_idx].cipher_key_len;
            pj_strdup(pool, &srtp->setting.crypto[i].key, &tmp_key);
        }
    } else {
        pjmedia_srtp_setting_default(&srtp->setting);
    }

    /* If crypto count is set to zero, setup default crypto-suites,
     * i.e: all available crypto but 'NULL'.
     */
    if (srtp->setting.crypto_count == 0 && 
        srtp->setting.use != PJMEDIA_SRTP_DISABLED)
    {
        srtp->setting.crypto_count = PJMEDIA_SRTP_MAX_CRYPTOS;
        pjmedia_srtp_enum_crypto(&srtp->setting.crypto_count,
                                 srtp->setting.crypto);
    }

    status = pj_lock_create_recursive_mutex(pool, pool->obj_name,
                                            &srtp->mutex);
    if (status != PJ_SUCCESS) {
        pj_pool_release(pool);
        return status;
    }

    /* Initialize base pjmedia_transport */
    pj_memcpy(srtp->base.name, pool->obj_name, PJ_MAX_OBJ_NAME);
    if (tp)
        srtp->base.type = tp->type;
    else
        srtp->base.type = PJMEDIA_TRANSPORT_TYPE_UDP;
    srtp->base.op = &transport_srtp_op;
    srtp->base.user_data = srtp->setting.user_data;

    /* Set underlying transport */
    srtp->member_tp = tp;

    /* Setup group lock handler for destroy and callback synchronization */
    if (tp && tp->grp_lock) {
        srtp->base.grp_lock = tp->grp_lock;
        pj_grp_lock_add_ref(tp->grp_lock);
        pj_grp_lock_add_handler(tp->grp_lock, pool, srtp, &srtp_on_destroy);
    }

    /* Initialize peer's SRTP usage mode. */
    srtp->peer_use = srtp->setting.use;

    /* If keying count set to zero, setup default keying count & priorities */
    if (srtp->setting.keying_count == 0) {
        srtp->setting.keying_count = PJMEDIA_SRTP_KEYINGS_COUNT;
        pjmedia_srtp_enum_keying(&srtp->setting.keying_count,
                                 srtp->setting.keying);
    }

    /* Initialize SRTP keying method. */
    for (i = 0; i < srtp->setting.keying_count && i < MAX_KEYING; ++i) {
        switch(srtp->setting.keying[i]) {

        case PJMEDIA_SRTP_KEYING_SDES:
#if defined(PJMEDIA_SRTP_HAS_SDES) && (PJMEDIA_SRTP_HAS_SDES != 0)
            sdes_create(srtp, &srtp->all_keying[srtp->all_keying_cnt++]);
#endif
            break;

        case PJMEDIA_SRTP_KEYING_DTLS_SRTP:
#if defined(PJMEDIA_SRTP_HAS_DTLS) && (PJMEDIA_SRTP_HAS_DTLS != 0)
            dtls_create(srtp, &srtp->all_keying[srtp->all_keying_cnt++]);
#endif
            break;

        default:
            break;
        }
    }

    /* Done */
    *p_tp = &srtp->base;

    PJ_LOG(4, (srtp->pool->obj_name, "SRTP transport created"));

    return PJ_SUCCESS;
}


/*
 * Get SRTP media transport setting.
 */
PJ_DEF(pj_status_t) pjmedia_transport_srtp_get_setting(
                                       pjmedia_transport *tp,
                                       pjmedia_srtp_setting *opt)
{
    transport_srtp  *srtp = (transport_srtp*) tp;
    *opt = srtp->setting;
    return PJ_SUCCESS;
}

/*
 * Modify SRTP media transport setting.
 */
PJ_DEF(pj_status_t) pjmedia_transport_srtp_modify_setting(
                                       pjmedia_transport *tp,
                                       const pjmedia_srtp_setting *opt)
{
    transport_srtp  *srtp = (transport_srtp*) tp;
    srtp->setting = *opt;
    return PJ_SUCCESS;
}


/* Create SRTP context */
static pj_status_t create_srtp_ctx(transport_srtp *srtp,
                                   srtp_context *ctx,
                                   const pjmedia_srtp_setting *setting,
                                   const pjmedia_srtp_crypto *tx,
                                   const pjmedia_srtp_crypto *rx)
{
    srtp_policy_t    tx_;
    srtp_policy_t    rx_;
    srtp_err_status_t err;
    int              cr_tx_idx = 0;
    int              au_tx_idx = 0;
    int              cr_rx_idx = 0;
    int              au_rx_idx = 0;
    pj_status_t      status = PJ_SUCCESS;

    pj_lock_acquire(srtp->mutex);

    if (ctx->srtp_tx_ctx || ctx->srtp_rx_ctx)
        destroy_srtp_ctx(srtp, ctx);

    /* Get encryption and authentication method */
    cr_tx_idx = au_tx_idx = get_crypto_idx(&tx->name);
    if (tx->flags & PJMEDIA_SRTP_NO_ENCRYPTION)
        cr_tx_idx = 0;
    if (tx->flags & PJMEDIA_SRTP_NO_AUTHENTICATION)
        au_tx_idx = 0;

    cr_rx_idx = au_rx_idx = get_crypto_idx(&rx->name);
    if (rx->flags & PJMEDIA_SRTP_NO_ENCRYPTION)
        cr_rx_idx = 0;
    if (rx->flags & PJMEDIA_SRTP_NO_AUTHENTICATION)
        au_rx_idx = 0;

    /* Check whether the crypto-suite requested is supported */
    if (cr_tx_idx == -1 || cr_rx_idx == -1 || au_tx_idx == -1 ||
        au_rx_idx == -1)
    {
        status = PJMEDIA_SRTP_ENOTSUPCRYPTO;
        goto on_return;
    }

    /* If all options points to 'NULL' method, just bypass SRTP */
    if (cr_tx_idx == 0 && cr_rx_idx == 0 && au_tx_idx == 0 && au_rx_idx == 0) {
        /* To bypass SRTP: we return PJ_SUCCESS without allocating SRTP
         * contexts.
         */
        goto on_return;
    }

    /* Check key length */
    if (tx->key.slen != (pj_ssize_t)crypto_suites[cr_tx_idx].cipher_key_len ||
        rx->key.slen != (pj_ssize_t)crypto_suites[cr_rx_idx].cipher_key_len)
    {
        status = PJMEDIA_SRTP_EINKEYLEN;
        goto on_return;
    }

    /* Init transmit direction */
    pj_bzero(&tx_, sizeof(srtp_policy_t));
    pj_memmove(ctx->tx_key, tx->key.ptr, tx->key.slen);
    if (cr_tx_idx && au_tx_idx)
        tx_.rtp.sec_serv    = sec_serv_conf_and_auth;
    else if (cr_tx_idx)
        tx_.rtp.sec_serv    = sec_serv_conf;
    else if (au_tx_idx)
        tx_.rtp.sec_serv    = sec_serv_auth;
    else
        tx_.rtp.sec_serv    = sec_serv_none;
    tx_.key                 = (uint8_t*)ctx->tx_key;
    if (setting->tx_roc.roc != 0 &&
        setting->tx_roc.ssrc != 0)
    {
        tx_.ssrc.type       = ssrc_specific;
        tx_.ssrc.value      = setting->tx_roc.ssrc;
    } else {
        tx_.ssrc.type       = ssrc_any_outbound;
        tx_.ssrc.value      = 0;
    }
    tx_.rtp.cipher_type     = crypto_suites[cr_tx_idx].cipher_type;
    tx_.rtp.cipher_key_len  = crypto_suites[cr_tx_idx].cipher_key_len;
    tx_.rtp.auth_type       = crypto_suites[au_tx_idx].auth_type;
    tx_.rtp.auth_key_len    = crypto_suites[au_tx_idx].auth_key_len;
    tx_.rtp.auth_tag_len    = crypto_suites[au_tx_idx].srtp_auth_tag_len;
    tx_.rtcp                = tx_.rtp;
    tx_.rtcp.auth_tag_len   = crypto_suites[au_tx_idx].srtcp_auth_tag_len;
    tx_.next                = NULL;
    err = srtp_create(&ctx->srtp_tx_ctx, &tx_);
    if (err != srtp_err_status_ok) {
        status = PJMEDIA_ERRNO_FROM_LIBSRTP(err);
        goto on_return;
    }
    if (setting->tx_roc.roc != 0 &&
        setting->tx_roc.ssrc != 0)
    {
        err = srtp_set_stream_roc(ctx->srtp_tx_ctx,
                                  setting->tx_roc.ssrc,
                                  setting->tx_roc.roc);
        PJ_LOG(4, (THIS_FILE, "Initializing SRTP TX ROC to SSRC %d with "
                   "ROC %d %s\n", setting->tx_roc.ssrc,
                   setting->tx_roc.roc,
                   (err == srtp_err_status_ok)? "succeeded": "failed"));
    }
    ctx->tx_policy = *tx;
    pj_strset(&ctx->tx_policy.key,  ctx->tx_key, tx->key.slen);
    ctx->tx_policy.name=pj_str(crypto_suites[get_crypto_idx(&tx->name)].name);


    /* Init receive direction */
    pj_bzero(&rx_, sizeof(srtp_policy_t));
    pj_memmove(ctx->rx_key, rx->key.ptr, rx->key.slen);
    if (cr_rx_idx && au_rx_idx)
        rx_.rtp.sec_serv    = sec_serv_conf_and_auth;
    else if (cr_rx_idx)
        rx_.rtp.sec_serv    = sec_serv_conf;
    else if (au_rx_idx)
        rx_.rtp.sec_serv    = sec_serv_auth;
    else
        rx_.rtp.sec_serv    = sec_serv_none;
    rx_.key                 = (uint8_t*)ctx->rx_key;
    if (setting->rx_roc.roc != 0 &&
        setting->rx_roc.ssrc != 0)
    {
        rx_.ssrc.type       = ssrc_specific;
        rx_.ssrc.value      = setting->rx_roc.ssrc;
    } else {
        rx_.ssrc.type       = ssrc_any_inbound;
        rx_.ssrc.value      = 0;
    }
    rx_.rtp.sec_serv        = crypto_suites[cr_rx_idx].service;
    rx_.rtp.cipher_type     = crypto_suites[cr_rx_idx].cipher_type;
    rx_.rtp.cipher_key_len  = crypto_suites[cr_rx_idx].cipher_key_len;
    rx_.rtp.auth_type       = crypto_suites[au_rx_idx].auth_type;
    rx_.rtp.auth_key_len    = crypto_suites[au_rx_idx].auth_key_len;
    rx_.rtp.auth_tag_len    = crypto_suites[au_rx_idx].srtp_auth_tag_len;
    rx_.rtcp                = rx_.rtp;
    rx_.rtcp.auth_tag_len   = crypto_suites[au_rx_idx].srtcp_auth_tag_len;
    rx_.next                = NULL;
    err = srtp_create(&ctx->srtp_rx_ctx, &rx_);
    if (err != srtp_err_status_ok) {
        srtp_dealloc(ctx->srtp_tx_ctx);
        status = PJMEDIA_ERRNO_FROM_LIBSRTP(err);
        goto on_return;
    }
    if (setting->rx_roc.roc != 0 &&
        setting->rx_roc.ssrc != 0)
    {
        err = srtp_set_stream_roc(ctx->srtp_rx_ctx,
                                  setting->rx_roc.ssrc,
                                  setting->rx_roc.roc);
        PJ_LOG(4, (THIS_FILE, "Initializing SRTP RX ROC from SSRC %d with "
                   "ROC %d %s\n",
                   setting->rx_roc.ssrc, setting->rx_roc.roc,
                   (err == srtp_err_status_ok)? "succeeded": "failed"));
    }
    ctx->rx_policy = *rx;
    pj_strset(&ctx->rx_policy.key,  ctx->rx_key, rx->key.slen);
    ctx->rx_policy.name=pj_str(crypto_suites[get_crypto_idx(&rx->name)].name);

    /* Logging stuffs */
#if PJ_LOG_MAX_LEVEL >= 5
    {
        char b64[PJ_BASE256_TO_BASE64_LEN(MAX_KEY_LEN)];
        int b64_len;

        /* TX crypto and key */
        b64_len = sizeof(b64);
        status = pj_base64_encode((pj_uint8_t*)tx->key.ptr, (int)tx->key.slen,
                                  b64, &b64_len);
        if (status != PJ_SUCCESS)
            b64_len = pj_ansi_snprintf(b64, sizeof(b64), "--key too long--");
        else
            b64[b64_len] = '\0';

        PJ_LOG(5, (srtp->pool->obj_name, "TX: %s key=%s",
                   ctx->tx_policy.name.ptr, b64));
        if (ctx->tx_policy.flags) {
            PJ_LOG(5,(srtp->pool->obj_name, "TX: disable%s%s",
                      (cr_tx_idx?"":" enc"),
                      (au_tx_idx?"":" auth")));
        }

        /* RX crypto and key */
        b64_len = sizeof(b64);
        status = pj_base64_encode((pj_uint8_t*)rx->key.ptr, (int)rx->key.slen,
                                  b64, &b64_len);
        if (status != PJ_SUCCESS)
            b64_len = pj_ansi_snprintf(b64, sizeof(b64), "--key too long--");
        else
            b64[b64_len] = '\0';

        PJ_LOG(5, (srtp->pool->obj_name, "RX: %s key=%s",
                   ctx->rx_policy.name.ptr, b64));
        if (ctx->rx_policy.flags) {
            PJ_LOG(5,(srtp->pool->obj_name,"RX: disable%s%s",
                      (cr_rx_idx?"":" enc"),
                      (au_rx_idx?"":" auth")));
        }
    }
#endif

on_return:
    pj_lock_release(srtp->mutex);
    return status;
}

/*
 * Initialize and start SRTP session with the given parameters.
 */
PJ_DEF(pj_status_t) pjmedia_transport_srtp_start(
                           pjmedia_transport *tp,
                           const pjmedia_srtp_crypto *tx,
                           const pjmedia_srtp_crypto *rx)
{
    transport_srtp  *srtp = (transport_srtp*) tp;
    pj_status_t status;

    PJ_ASSERT_RETURN(tp && tx && rx, PJ_EINVAL);

    pj_lock_acquire(srtp->mutex);

    if (srtp->session_inited) {
        pjmedia_transport_srtp_stop(tp);
    }

    status = create_srtp_ctx(srtp, &srtp->srtp_ctx, &srtp->setting, tx, rx);
    if (status == PJ_SUCCESS) {
        if (srtp->srtp_ctx.srtp_tx_ctx && srtp->srtp_ctx.srtp_rx_ctx) {
            /* Declare SRTP session initialized */
            srtp->session_inited = PJ_TRUE;
        } else {
            srtp->bypass_srtp = PJ_TRUE;
        }
    }

    pj_lock_release(srtp->mutex);

    return status;
}

/* Destroy SRTP context */
static void destroy_srtp_ctx(transport_srtp *p_srtp, srtp_context *ctx)
{
    srtp_err_status_t err;

    if (ctx->srtp_rx_ctx) {
        err = srtp_dealloc(ctx->srtp_rx_ctx);
        if (err != srtp_err_status_ok) {
            PJ_LOG(4, (p_srtp->pool->obj_name,
                       "Failed to dealloc RX SRTP context: %s",
                       get_libsrtp_errstr(err)));
        }
    }
    if (ctx->srtp_tx_ctx) {
        err = srtp_dealloc(ctx->srtp_tx_ctx);
        if (err != srtp_err_status_ok) {
            PJ_LOG(4, (p_srtp->pool->obj_name,
                       "Failed to dealloc TX SRTP context: %s",
                       get_libsrtp_errstr(err)));
        }
    }
    ctx->srtp_rx_ctx = NULL;
    ctx->srtp_tx_ctx = NULL;

    pj_bzero(&ctx->rx_policy, sizeof(ctx->rx_policy));
    pj_bzero(&ctx->tx_policy, sizeof(ctx->tx_policy));
}

/*
 * Stop SRTP session.
 */
PJ_DEF(pj_status_t) pjmedia_transport_srtp_stop(pjmedia_transport *srtp)
{
    transport_srtp *p_srtp = (transport_srtp*) srtp;

    PJ_ASSERT_RETURN(srtp, PJ_EINVAL);

    pj_lock_acquire(p_srtp->mutex);

    if (!p_srtp->session_inited) {
        pj_lock_release(p_srtp->mutex);
        return PJ_SUCCESS;
    }

    destroy_srtp_ctx(p_srtp, &p_srtp->srtp_ctx);
    destroy_srtp_ctx(p_srtp, &p_srtp->srtp_rtcp);

    p_srtp->session_inited = PJ_FALSE;

    pj_lock_release(p_srtp->mutex);

    return PJ_SUCCESS;
}


static pj_status_t start_srtp(transport_srtp *srtp)
{
    /* Make sure we have the SRTP policies */
    if (srtp_crypto_empty(&srtp->srtp_ctx.tx_policy_neg) ||
        srtp_crypto_empty(&srtp->srtp_ctx.rx_policy_neg))
    {
        srtp->bypass_srtp = PJ_TRUE;
        srtp->peer_use = PJMEDIA_SRTP_DISABLED;
        if (srtp->session_inited) {
            pjmedia_transport_srtp_stop(&srtp->base);
        }

        PJ_LOG(4, (srtp->pool->obj_name, "SRTP not active"));
        return PJ_SUCCESS;
    }

    /* Got policy_local & policy_remote, let's initalize the SRTP */

    /* Ticket #1075: media_start() is called whenever media description
     * gets updated, e.g: call hold, however we should restart SRTP only
     * when the SRTP policy settings are updated.
     */
    if (srtp_crypto_cmp(&srtp->srtp_ctx.tx_policy_neg,
                        &srtp->srtp_ctx.tx_policy) ||
        srtp_crypto_cmp(&srtp->srtp_ctx.rx_policy_neg,
                        &srtp->srtp_ctx.rx_policy))
    {
        pj_status_t status;
        status = pjmedia_transport_srtp_start(&srtp->base,
                                              &srtp->srtp_ctx.tx_policy_neg,
                                              &srtp->srtp_ctx.rx_policy_neg);
        if (status != PJ_SUCCESS)
            return status;

        /* Reset probation counts */
        srtp->probation_cnt = PROBATION_CNT_INIT;

        PJ_LOG(4, (srtp->pool->obj_name,
                   "SRTP started, keying=%s, crypto=%s",
                   ((int)srtp->keying[0]->type==PJMEDIA_SRTP_KEYING_SDES?
                    "SDES":"DTLS-SRTP"),
                   srtp->srtp_ctx.tx_policy.name.ptr));
    }

    srtp->bypass_srtp = PJ_FALSE;

    return PJ_SUCCESS;
}


PJ_DEF(pjmedia_transport *) pjmedia_transport_srtp_get_member(
                                                pjmedia_transport *tp)
{
    transport_srtp *srtp = (transport_srtp*) tp;

    PJ_ASSERT_RETURN(tp, NULL);

    return srtp->member_tp;
}


static pj_status_t transport_get_info(pjmedia_transport *tp,
                                      pjmedia_transport_info *info)
{
    transport_srtp *srtp = (transport_srtp*) tp;
    pjmedia_srtp_info srtp_info;
    int spc_info_idx;
    unsigned i;

    PJ_ASSERT_RETURN(tp && info, PJ_EINVAL);
    PJ_ASSERT_RETURN(info->specific_info_cnt <
                     PJMEDIA_TRANSPORT_SPECIFIC_INFO_MAXCNT, PJ_ETOOMANY);
    PJ_ASSERT_RETURN(sizeof(pjmedia_srtp_info) <=
                     PJMEDIA_TRANSPORT_SPECIFIC_INFO_MAXSIZE, PJ_ENOMEM);

    srtp_info.active = srtp->session_inited;
    srtp_info.rx_policy = srtp->srtp_ctx.rx_policy;
    srtp_info.tx_policy = srtp->srtp_ctx.tx_policy;
    srtp_info.use = srtp->setting.use;
    srtp_info.peer_use = srtp->peer_use;

    pj_bzero(&srtp_info.tx_roc, sizeof(srtp_info.tx_roc));
    pj_bzero(&srtp_info.rx_roc, sizeof(srtp_info.rx_roc));

    if (srtp->srtp_ctx.srtp_rx_ctx && srtp->rx_ssrc != 0) {
        srtp_info.rx_roc.ssrc = srtp->rx_ssrc;
        srtp_get_stream_roc(srtp->srtp_ctx.srtp_rx_ctx, srtp->rx_ssrc,
                            &srtp_info.rx_roc.roc);
    } else if (srtp->setting.rx_roc.ssrc != 0) {
        srtp_info.rx_roc.ssrc = srtp->setting.rx_roc.ssrc;
        srtp_info.rx_roc.roc = srtp->setting.rx_roc.roc;
    }
    if (srtp->srtp_ctx.srtp_tx_ctx && srtp->tx_ssrc != 0) {
        srtp_info.tx_roc.ssrc = srtp->tx_ssrc;
        srtp_get_stream_roc(srtp->srtp_ctx.srtp_tx_ctx, srtp->tx_ssrc,
                            &srtp_info.tx_roc.roc);
    } else if (srtp->setting.tx_roc.ssrc != 0) {
        srtp_info.tx_roc.ssrc = srtp->setting.tx_roc.ssrc;
        srtp_info.tx_roc.roc = srtp->setting.tx_roc.roc;
    }

    spc_info_idx = info->specific_info_cnt++;
    info->spc_info[spc_info_idx].type = PJMEDIA_TRANSPORT_TYPE_SRTP;
    info->spc_info[spc_info_idx].tp = tp;
    info->spc_info[spc_info_idx].cbsize = sizeof(srtp_info);
    pj_memcpy(&info->spc_info[spc_info_idx].buffer, &srtp_info,
              sizeof(srtp_info));

    /* Invoke get_info() from any active keying method */
    for (i=0; i < srtp->keying_cnt; i++)
        pjmedia_transport_get_info(srtp->keying[i], info);

    return pjmedia_transport_get_info(srtp->member_tp, info);
}

static pj_status_t transport_attach2(pjmedia_transport *tp,
                                     pjmedia_transport_attach_param *param)
{
    transport_srtp *srtp = (transport_srtp*) tp;
    pjmedia_transport_attach_param member_param;
    pj_status_t status;

    PJ_ASSERT_RETURN(tp && param, PJ_EINVAL);

    /* Save the callbacks */
    pj_lock_acquire(srtp->mutex);
    if (param->rtp_cb || param->rtp_cb2) {
        /* Do not update rtp_cb if not set, as attach() is called by
         * keying method.
         */
        srtp->rtp_cb = param->rtp_cb;
        srtp->rtp_cb2 = param->rtp_cb2;
        srtp->rtcp_cb = param->rtcp_cb;
        srtp->user_data = param->user_data;
    }
    pj_lock_release(srtp->mutex);

    /* Attach self to member transport */
    member_param = *param;
    member_param.user_data = srtp;
    member_param.rtp_cb = NULL;
    member_param.rtp_cb2 = &srtp_rtp_cb;
    member_param.rtcp_cb = &srtp_rtcp_cb;
    status = pjmedia_transport_attach2(srtp->member_tp, &member_param);
    if (status != PJ_SUCCESS) {
        pj_lock_acquire(srtp->mutex);
        srtp->rtp_cb = NULL;
        srtp->rtcp_cb = NULL;
        srtp->user_data = NULL;
        pj_lock_release(srtp->mutex);
        return status;
    }

    /* Check if we are multiplexing RTP & RTCP. */
    srtp->use_rtcp_mux = (pj_sockaddr_has_addr(&param->rem_addr) &&
                          pj_sockaddr_cmp(&param->rem_addr,
                                          &param->rem_rtcp) == 0);
    srtp->member_tp_attached = PJ_TRUE;
    return PJ_SUCCESS;
}

static void transport_detach(pjmedia_transport *tp, void *strm)
{
    transport_srtp *srtp = (transport_srtp*) tp;

    PJ_UNUSED_ARG(strm);
    PJ_ASSERT_ON_FAIL(tp, return);

    if (srtp->member_tp) {
        pjmedia_transport_detach(srtp->member_tp, srtp);
    }

    /* Clear up application infos from transport */
    pj_lock_acquire(srtp->mutex);
    srtp->rtp_cb = NULL;
    srtp->rtp_cb2 = NULL;
    srtp->rtcp_cb = NULL;
    srtp->user_data = NULL;
    pj_lock_release(srtp->mutex);
    srtp->member_tp_attached = PJ_FALSE;
}

static pj_status_t transport_send_rtp( pjmedia_transport *tp,
                                       const void *pkt,
                                       pj_size_t size)
{
    pj_status_t status;
    transport_srtp *srtp = (transport_srtp*) tp;
    int len = (int)size;
    srtp_err_status_t err;

    if (srtp->bypass_srtp)
        return pjmedia_transport_send_rtp(srtp->member_tp, pkt, size);

    if (size > sizeof(srtp->rtp_tx_buffer) - MAX_TRAILER_LEN)
        return PJ_ETOOBIG;

    pj_memcpy(srtp->rtp_tx_buffer, pkt, size);

    pj_lock_acquire(srtp->mutex);
    if (!srtp->session_inited) {
        pj_lock_release(srtp->mutex);
        return PJMEDIA_SRTP_EKEYNOTREADY;
    }

    /* Save outgoing SSRC */
    srtp->tx_ssrc = ntohl(((pjmedia_rtp_hdr*)pkt)->ssrc);

#if TEST_ROC
    if (srtp->setting.tx_roc.ssrc == 0) {
        srtp_err_status_t status;
        status = srtp_set_stream_roc(srtp->srtp_ctx.srtp_tx_ctx, srtp->tx_ssrc,
                                     (srtp->offerer_side? 1: 2));
        if (status == srtp_err_status_ok) {
            srtp->setting.tx_roc.ssrc = srtp->tx_ssrc;
            srtp->setting.tx_roc.roc = (srtp->offerer_side? 1: 2);
            PJ_LOG(4, (THIS_FILE, "Setting TX ROC to SSRC %d to %d",
                   srtp->tx_ssrc, srtp->setting.tx_roc.roc));
        }
    }
#endif

    err = srtp_protect(srtp->srtp_ctx.srtp_tx_ctx, srtp->rtp_tx_buffer, &len);
    pj_lock_release(srtp->mutex);

    if (err == srtp_err_status_ok) {
        status = pjmedia_transport_send_rtp(srtp->member_tp,
                                            srtp->rtp_tx_buffer, len);
    } else {
        status = PJMEDIA_ERRNO_FROM_LIBSRTP(err);
    }

    return status;
}

static pj_status_t transport_send_rtcp(pjmedia_transport *tp,
                                       const void *pkt,
                                       pj_size_t size)
{
    return transport_send_rtcp2(tp, NULL, 0, pkt, size);
}

static pj_status_t transport_send_rtcp2(pjmedia_transport *tp,
                                        const pj_sockaddr_t *addr,
                                        unsigned addr_len,
                                        const void *pkt,
                                        pj_size_t size)
{
    pj_status_t status;
    transport_srtp *srtp = (transport_srtp*) tp;
    int len = (int)size;
    srtp_err_status_t err;

    if (srtp->bypass_srtp) {
        return pjmedia_transport_send_rtcp2(srtp->member_tp, addr, addr_len,
                                            pkt, size);
    }

    if (size > sizeof(srtp->rtcp_tx_buffer) - (MAX_TRAILER_LEN+4))
        return PJ_ETOOBIG;

    pj_memcpy(srtp->rtcp_tx_buffer, pkt, size);

    pj_lock_acquire(srtp->mutex);
    if (!srtp->session_inited) {
        pj_lock_release(srtp->mutex);
        return PJMEDIA_SRTP_EKEYNOTREADY;
    }
    err = srtp_protect_rtcp(srtp->srtp_rtcp.srtp_tx_ctx?
                            srtp->srtp_rtcp.srtp_tx_ctx:
                            srtp->srtp_ctx.srtp_tx_ctx,
                            srtp->rtcp_tx_buffer, &len);
    pj_lock_release(srtp->mutex);

    if (err == srtp_err_status_ok) {
        status = pjmedia_transport_send_rtcp2(srtp->member_tp, addr, addr_len,
                                              srtp->rtcp_tx_buffer, len);
    } else {
        status = PJMEDIA_ERRNO_FROM_LIBSRTP(err);
    }

    return status;
}


static pj_status_t transport_simulate_lost(pjmedia_transport *tp,
                                           pjmedia_dir dir,
                                           unsigned pct_lost)
{
    transport_srtp *srtp = (transport_srtp *) tp;

    PJ_ASSERT_RETURN(tp, PJ_EINVAL);

    return pjmedia_transport_simulate_lost(srtp->member_tp, dir, pct_lost);
}


/* SRTP real destroy */
static void srtp_on_destroy(void *arg)
{
    transport_srtp *srtp = (transport_srtp*)arg;

    PJ_LOG(4, (srtp->pool->obj_name, "SRTP transport destroyed"));

    pj_lock_destroy(srtp->mutex);
    pj_pool_safe_release(&srtp->pool);
}


static pj_status_t transport_destroy  (pjmedia_transport *tp)
{
    transport_srtp *srtp = (transport_srtp *) tp;
    pj_status_t status;
    unsigned i;

    PJ_ASSERT_RETURN(tp, PJ_EINVAL);

    PJ_LOG(4, (srtp->pool->obj_name, "Destroying SRTP transport"));

    /* Close all keying. Note that any keying should not be destroyed before
     * SRTP transport is destroyed as re-INVITE may initiate new keying method
     * without destroying SRTP transport.
     */
    for (i=0; i < srtp->all_keying_cnt; i++)
        pjmedia_transport_close(srtp->all_keying[i]);

    /* Close member if configured */
    if (srtp->setting.close_member_tp && srtp->member_tp) {
        pjmedia_transport_close(srtp->member_tp);
    }

    status = pjmedia_transport_srtp_stop(tp);

    if (srtp->base.grp_lock) {
        pj_grp_lock_dec_ref(srtp->base.grp_lock);
    } else {
        /* Only get here when the underlying transport does not have
         * a group lock, race condition with callbacks may occur.
         * Currently UDP, ICE, and loop have a group lock already.
         */
        PJ_LOG(4,(srtp->pool->obj_name,
                  "Warning: underlying transport does not have group lock"));

        /* In case mutex is being acquired by other thread.
         * An effort to synchronize destroy() & callbacks when the underlying
         * transport does not provide a group lock.
         */
        pj_lock_acquire(srtp->mutex);
        pj_lock_release(srtp->mutex);

        srtp_on_destroy(srtp);
    }

    return status;
}

/*
 * This callback is called by transport when incoming rtp is received
 */
static void srtp_rtp_cb(pjmedia_tp_cb_param *param)
{
    transport_srtp *srtp = (transport_srtp *) param->user_data;
    void *pkt = param->pkt;
    pj_ssize_t size = param->size;
    int len = (int)size;
    srtp_err_status_t err;
    void (*cb)(void*, void*, pj_ssize_t) = NULL;
    void (*cb2)(pjmedia_tp_cb_param*) = NULL;
    void *cb_data = NULL;

    if (srtp->bypass_srtp) {
        if (srtp->rtp_cb2) {
            pjmedia_tp_cb_param param2 = *param;
            param2.user_data = srtp->user_data;
            srtp->rtp_cb2(&param2);
            param->rem_switch = param2.rem_switch;
        } else if (srtp->rtp_cb) {
            srtp->rtp_cb(srtp->user_data, pkt, size);
        }
        return;
    }

    if (size < 0) {
        return;
    }

    /* Give the packet to keying first by invoking its send_rtp() op.
     * Yes, the usage of send_rtp() is rather hacky, but it is convenient
     * as the signature suits the purpose and it is ready to use
     * (no futher registration/setting needed), and it may never be used
     * by any keying method in the future.
     */
    {
        unsigned i;
        pj_status_t status;
        for (i=0; i < srtp->keying_cnt; i++) {
            if (!srtp->keying[i]->op->send_rtp)
                continue;
            status = pjmedia_transport_send_rtp(srtp->keying[i], pkt, size);
            if (status != PJ_EIGNORED) {
                /* Packet is already consumed by the keying method */
                return;
            }
        }
    }

    /* Make sure buffer is 32bit aligned */
    PJ_ASSERT_ON_FAIL( (((pj_ssize_t)pkt) & 0x03)==0, return );

    if (srtp->probation_cnt > 0)
        --srtp->probation_cnt;

    pj_lock_acquire(srtp->mutex);

    if (!srtp->session_inited) {
        pj_lock_release(srtp->mutex);
        return;
    }

    /* Check if multiplexing is allowed and the payload indicates RTCP. */
    if (srtp->use_rtcp_mux) {
        pjmedia_rtp_hdr *hdr = (pjmedia_rtp_hdr *)pkt;
  
        if (hdr->pt >= 64 && hdr->pt <= 95) {   
            pj_lock_release(srtp->mutex);
            srtp_rtcp_cb(srtp, pkt, size);
            return;
        }
    }

#if TEST_ROC
    if (srtp->setting.rx_roc.ssrc == 0) {
        srtp_err_status_t status;
        
        srtp->rx_ssrc = ntohl(((pjmedia_rtp_hdr*)pkt)->ssrc);
        status = srtp_set_stream_roc(srtp->srtp_ctx.srtp_rx_ctx, srtp->rx_ssrc,
                                     (srtp->offerer_side? 2: 1));
        if (status == srtp_err_status_ok) {     
            srtp->setting.rx_roc.ssrc = srtp->rx_ssrc;
            srtp->setting.rx_roc.roc = (srtp->offerer_side? 2: 1);

            PJ_LOG(4, (THIS_FILE, "Setting RX ROC from SSRC %d to %d",
                                  srtp->rx_ssrc, srtp->setting.rx_roc.roc));
        } else {
            PJ_LOG(4, (THIS_FILE, "Setting RX ROC %s",
                       get_libsrtp_errstr(status)));
        }
    }
#endif
    
    err = srtp_unprotect(srtp->srtp_ctx.srtp_rx_ctx, (pj_uint8_t*)pkt, &len);

#if PJMEDIA_SRTP_CHECK_RTP_SEQ_ON_RESTART
    if (srtp->probation_cnt > 0 &&
        (err == srtp_err_status_replay_old ||
         err == srtp_err_status_replay_fail))
    {
        /* Handle such condition that stream is updated (RTP seq is reinited
         * & SRTP is restarted), but some old packets are still coming
         * so SRTP is learning wrong RTP seq. While the newly inited RTP seq
         * comes, SRTP thinks the RTP seq is replayed, so srtp_unprotect()
         * will return err_status_replay_*. Restarting SRTP can resolve this.
         */
        pjmedia_srtp_crypto tx, rx;
        pj_status_t status;

        tx = srtp->srtp_ctx.tx_policy;
        rx = srtp->srtp_ctx.rx_policy;

        /* Stop SRTP first, otherwise srtp_start() will maintain current
         * roll-over counter.
         */
        pjmedia_transport_srtp_stop((pjmedia_transport*)srtp);

        status = pjmedia_transport_srtp_start((pjmedia_transport*)srtp,
                                              &tx, &rx);
        if (status != PJ_SUCCESS) {
            PJ_LOG(5,(srtp->pool->obj_name, "Failed to restart SRTP, err=%s",
                      get_libsrtp_errstr(err)));
        } else if (!srtp->bypass_srtp) {
            err = srtp_unprotect(srtp->srtp_ctx.srtp_rx_ctx,
                                 (pj_uint8_t*)pkt, &len);
        }
    }
#if PJMEDIA_SRTP_CHECK_ROC_ON_RESTART
    else
#endif
#endif

#if PJMEDIA_SRTP_CHECK_ROC_ON_RESTART
    if (srtp->probation_cnt > 0 && err == srtp_err_status_auth_fail &&
        srtp->setting.prev_rx_roc.ssrc != 0 &&
        srtp->setting.prev_rx_roc.ssrc == srtp->setting.rx_roc.ssrc &&
        srtp->setting.prev_rx_roc.roc != srtp->setting.rx_roc.roc)
    {
        unsigned roc, new_roc;
        srtp_err_status_t status;

        srtp_get_stream_roc(srtp->srtp_ctx.srtp_rx_ctx,
                            srtp->setting.rx_roc.ssrc, &roc);
        new_roc = (roc == srtp->setting.rx_roc.roc?
                   srtp->setting.prev_rx_roc.roc: srtp->setting.rx_roc.roc);
        status = srtp_set_stream_roc(srtp->srtp_ctx.srtp_rx_ctx,
                                     srtp->setting.rx_roc.ssrc, new_roc);
        if (status == srtp_err_status_ok) {
            PJ_LOG(4, (srtp->pool->obj_name,
                       "Retrying to unprotect SRTP from ROC %d to new ROC %d",
                       roc, new_roc));
            err = srtp_unprotect(srtp->srtp_ctx.srtp_rx_ctx, (pj_uint8_t*)pkt,
                                 &len);
        }
    }
#endif

    if (err != srtp_err_status_ok) {
        PJ_LOG(5,(srtp->pool->obj_name,
                  "Failed to unprotect SRTP, pkt size=%ld, err=%s",
                  size, get_libsrtp_errstr(err)));
    } else {
        cb = srtp->rtp_cb;
        cb2 = srtp->rtp_cb2;
        cb_data = srtp->user_data;

        /* Save SSRC after successful SRTP unprotect */
        srtp->rx_ssrc = ntohl(((pjmedia_rtp_hdr*)pkt)->ssrc);
    }

    pj_lock_release(srtp->mutex);

    if (cb2) {
        pjmedia_tp_cb_param param2 = *param;
        param2.user_data = cb_data;
        param2.pkt = pkt;
        param2.size = len;
        (*cb2)(&param2);
        param->rem_switch = param2.rem_switch;
    } else if (cb) {
        (*cb)(cb_data, pkt, len);
    }
}

/*
 * This callback is called by transport when incoming rtcp is received
 */
static void srtp_rtcp_cb( void *user_data, void *pkt, pj_ssize_t size)
{
    transport_srtp *srtp = (transport_srtp *) user_data;
    int len = (int)size;
    srtp_err_status_t err;
    void (*cb)(void*, void*, pj_ssize_t) = NULL;
    void *cb_data = NULL;

    if (srtp->bypass_srtp) {
        srtp->rtcp_cb(srtp->user_data, pkt, size);
        return;
    }

    if (size < 0) {
        return;
    }

    /* Give the packet to keying first by invoking its send_rtcp() op,
     * in the same way as for RTP packet above.
     */
    {
        unsigned i;
        pj_status_t status;
        for (i=0; i < srtp->keying_cnt; i++) {
            if (!srtp->keying[i]->op->send_rtcp)
                continue;
            status = pjmedia_transport_send_rtcp(srtp->keying[i], pkt, size);
            if (status != PJ_EIGNORED) {
                /* Packet is already consumed by the keying method */
                return;
            }
        }
    }

    /* Make sure buffer is 32bit aligned */
    PJ_ASSERT_ON_FAIL( (((pj_ssize_t)pkt) & 0x03)==0, return );

    pj_lock_acquire(srtp->mutex);

    if (!srtp->session_inited) {
        pj_lock_release(srtp->mutex);
        return;
    }
    err = srtp_unprotect_rtcp(srtp->srtp_rtcp.srtp_rx_ctx?
                              srtp->srtp_rtcp.srtp_rx_ctx:
                              srtp->srtp_ctx.srtp_rx_ctx,
                              (pj_uint8_t*)pkt, &len);
    if (err != srtp_err_status_ok) {
        PJ_LOG(5,(srtp->pool->obj_name,
                  "Failed to unprotect SRTCP, pkt size=%ld, err=%s",
                  size, get_libsrtp_errstr(err)));
    } else {
        cb = srtp->rtcp_cb;
        cb_data = srtp->user_data;
    }

    pj_lock_release(srtp->mutex);

    if (cb) {
        (*cb)(cb_data, pkt, len);
    }
}


static pj_status_t transport_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;
    unsigned member_tp_option;
    pj_status_t keying_status = PJ_SUCCESS;
    pj_status_t status;
    unsigned i;

    PJ_ASSERT_RETURN(tp, PJ_EINVAL);

    pj_bzero(&srtp->srtp_ctx.rx_policy_neg,
             sizeof(srtp->srtp_ctx.rx_policy_neg));
    pj_bzero(&srtp->srtp_ctx.tx_policy_neg,
             sizeof(srtp->srtp_ctx.tx_policy_neg));

    srtp->tx_ssrc = srtp->rx_ssrc = 0;
    srtp->media_option = member_tp_option = options;
    srtp->offerer_side = (sdp_remote == NULL);

    if (srtp->offerer_side && srtp->setting.use == PJMEDIA_SRTP_DISABLED) {
        /* If we are offerer and SRTP is disabled, simply bypass SRTP and
         * skip keying.
         */
        srtp->bypass_srtp = PJ_TRUE;
        srtp->keying_cnt = 0;
    } else {
        /* If we are answerer and SRTP is disabled, we need to verify that
         * SRTP is disabled too in remote SDP, so we can't just skip keying.
         */
        srtp->bypass_srtp = PJ_FALSE;
        srtp->keying_cnt = srtp->all_keying_cnt;
        for (i = 0; i < srtp->all_keying_cnt; ++i)
            srtp->keying[i] = srtp->all_keying[i];

        member_tp_option |= PJMEDIA_TPMED_NO_TRANSPORT_CHECKING;
    }

    status = pjmedia_transport_media_create(srtp->member_tp, sdp_pool,
                                            member_tp_option, sdp_remote,
                                            media_index);
    if (status != PJ_SUCCESS)
        return status;

    /* Invoke media_create() of all keying methods, keying actions for each
     * SRTP mode:
     * - DISABLED:
     *   - as offerer, nothing (keying is skipped).
     *   - as answerer, verify remote SDP, make sure it has SRTP disabled too,
     *     if not, return error.
     * - OPTIONAL:
     *   - as offerer, general initialization.
     *   - as answerer, optionally verify SRTP attr in remote SDP (if any).
     * - MANDATORY:
     *   - as offerer, general initialization.
     *   - as answerer, verify SRTP attr in remote SDP.
     */
    for (i=0; i < srtp->keying_cnt; ) {
        pj_status_t st;
        st = pjmedia_transport_media_create(srtp->keying[i], sdp_pool,
                                            options, sdp_remote,
                                            media_index);
        if (st != PJ_SUCCESS) {
            /* This keying method returns error, remove it */
            pj_array_erase(srtp->keying, sizeof(srtp->keying[0]),
                           srtp->keying_cnt, i);
            srtp->keying_cnt--;
            keying_status = st;
            continue;
        } else if (srtp->offerer_side) {
            /* Currently we can send one keying only in outgoing offer */
            srtp->keying[0] = srtp->keying[i];
            srtp->keying_cnt = 1;
            break;
        }

        ++i;
    }

    /* All keying method failed to process remote SDP? */
    if (srtp->keying_cnt == 0)
        return keying_status;

    /* Bypass SRTP & skip keying as SRTP is disabled and verification on
     * remote SDP has been done.
     */
    if (srtp->setting.use == PJMEDIA_SRTP_DISABLED) {
        srtp->bypass_srtp = PJ_TRUE;
        srtp->keying_cnt = 0;
    }

    return PJ_SUCCESS;
}

static pj_status_t transport_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;
    pj_status_t keying_status = PJ_SUCCESS;
    pj_status_t status;
    unsigned i;

    PJ_ASSERT_RETURN(tp && sdp_pool && sdp_local, PJ_EINVAL);

    pj_bzero(&srtp->srtp_ctx.rx_policy_neg,
             sizeof(srtp->srtp_ctx.rx_policy_neg));
    pj_bzero(&srtp->srtp_ctx.tx_policy_neg,
             sizeof(srtp->srtp_ctx.tx_policy_neg));

    srtp->offerer_side = (sdp_remote == NULL);

    if (!srtp->offerer_side && srtp->started) {
        /* This is may be incoming reoffer that may change keying */
        srtp->keying_cnt = srtp->all_keying_cnt;
        for (i = 0; i < srtp->all_keying_cnt; ++i)
            srtp->keying[i] = srtp->all_keying[i];
    }

    status = pjmedia_transport_encode_sdp(srtp->member_tp, sdp_pool,
                                          sdp_local, sdp_remote, media_index);
    if (status != PJ_SUCCESS)
        return status;

    /* Invoke encode_sdp() of all keying methods, keying actions for each
     * SRTP mode:
     * - DISABLED: nothing (keying is skipped)
     * - OPTIONAL:
     *   - as offerer, generate offer.
     *   - as answerer, if remote has the same SRTP keying in SDP, verify it,
     *     generate answer, start crypto nego.
     * - MANDATORY:
     *   - as offerer, generate offer.
     *   - as answerer, verify remote SDP, generate answer, start crypto nego.
     *
     * Note: because the SDP will be processed by other keying/components,
     *       keying must do verification on remote SDP first (e.g: keying
     *       is being used) before touching local SDP.
     */
    for (i=0; i < srtp->keying_cnt; ) {
        pj_status_t st;
        st = pjmedia_transport_encode_sdp(srtp->keying[i], sdp_pool,
                                          sdp_local, sdp_remote,
                                          media_index);
        if (st != PJ_SUCCESS) {
            /* This keying method returns error, remove it */
            pj_array_erase(srtp->keying, sizeof(srtp->keying[0]),
                           srtp->keying_cnt, i);
            srtp->keying_cnt--;
            keying_status = st;
            continue;
        } else if (!srtp->offerer_side) {
            /* Answer with one keying only */
            srtp->keying[0] = srtp->keying[i];
            srtp->keying_cnt = 1;
            break;
        }

        i++;
    }

    /* All keying method failed to process remote SDP? */
    if (srtp->keying_cnt == 0) {
        if (keying_status != PJ_SUCCESS) {
            DEACTIVATE_MEDIA(sdp_pool, sdp_local->media[media_index]);
        }
        return keying_status;
    }

    /* Bypass SRTP & skip keying as SRTP is disabled and verification on
     * remote SDP has been done.
     */
    if (srtp->setting.use == PJMEDIA_SRTP_DISABLED) {
        srtp->bypass_srtp = PJ_TRUE;
        srtp->keying_cnt = 0;
    }

    if (srtp->keying_cnt != 0) {
        /* At this point for now, keying count should be 1 */
        pj_assert(srtp->keying_cnt == 1);
        PJ_LOG(4, (srtp->pool->obj_name, "SRTP uses keying method %s",
                   ((int)srtp->keying[0]->type==PJMEDIA_SRTP_KEYING_SDES?
                    "SDES":"DTLS-SRTP")));
    }

    return PJ_SUCCESS;
}


static pj_status_t transport_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;
    pj_status_t keying_status = PJ_SUCCESS;
    pj_status_t status;
    unsigned i;

    PJ_ASSERT_RETURN(tp, PJ_EINVAL);

    /* At this point for now, keying count should be 0 or 1 */
    pj_assert(srtp->keying_cnt <= 1);

    srtp->started = PJ_TRUE;

    status = pjmedia_transport_media_start(srtp->member_tp, pool,
                                           sdp_local, sdp_remote,
                                           media_index);
    if (status != PJ_SUCCESS)
        return status;

    /* Invoke media_start() of all keying methods, keying actions for each
     * SRTP mode:
     * - DISABLED: nothing (keying is skipped)
     * - OPTIONAL:
     *   - as offerer, if remote answer has the same SRTP keying in SDP,
     *     verify it and start crypto nego.
     *   - as answerer, start crypto nego if not yet (usually initated in
     *     encode_sdp()).
     * - MANDATORY:
     *   - as offerer, verify remote answer and start crypto nego.
     *   - as answerer, start crypto nego if not yet (usually initated in
     *     encode_sdp()).
     */
    for (i=0; i < srtp->keying_cnt; ) {
        status = pjmedia_transport_media_start(srtp->keying[i], pool,
                                               sdp_local, sdp_remote,
                                               media_index);
        if (status != PJ_SUCCESS) {
            /* This keying method returns error, remove it */
            pj_array_erase(srtp->keying, sizeof(srtp->keying[0]),
                           srtp->keying_cnt, i);
            srtp->keying_cnt--;
            keying_status = status;
            continue;
        }

        if (!srtp_crypto_empty(&srtp->srtp_ctx.tx_policy_neg) &&
            !srtp_crypto_empty(&srtp->srtp_ctx.rx_policy_neg))
        {
            /* SRTP nego is done */
            srtp->keying_cnt = 1;
            srtp->keying[0] = srtp->keying[i];
            srtp->keying_pending_cnt = 0;
            break;
        }

        i++;
    }

    /* All keying method failed to process remote SDP? */
    if (srtp->keying_cnt == 0)
        return keying_status;

    /* If SRTP key is being negotiated, just return now.
     * The keying method should start the SRTP once keying nego is done.
     */
    if (srtp->keying_pending_cnt)
        return PJ_SUCCESS;

    /* Start SRTP */
    status = start_srtp(srtp);

    return status;
}


static pj_status_t transport_media_stop(pjmedia_transport *tp)
{
    struct transport_srtp *srtp = (struct transport_srtp*) tp;
    pj_status_t status;
    unsigned i;

    PJ_ASSERT_RETURN(tp, PJ_EINVAL);

    srtp->started = PJ_FALSE;

    /* Invoke media_stop() of all keying methods */
    for (i=0; i < srtp->keying_cnt; ++i) {
        pjmedia_transport_media_stop(srtp->keying[i]);
    }

    /* Invoke media_stop() of member tp */
    status = pjmedia_transport_media_stop(srtp->member_tp);
    if (status != PJ_SUCCESS)
        PJ_PERROR(4, (srtp->pool->obj_name, status,
                      "SRTP failed stop underlying media transport."));

    /* Finally, stop SRTP */
    return pjmedia_transport_srtp_stop(tp);
}


/* Utility */
PJ_DEF(pj_status_t) pjmedia_transport_srtp_decrypt_pkt(pjmedia_transport *tp,
                                                       pj_bool_t is_rtp,
                                                       void *pkt,
                                                       int *pkt_len)
{
    transport_srtp *srtp = (transport_srtp *)tp;
    srtp_err_status_t err;

    if (srtp->bypass_srtp)
        return PJ_SUCCESS;

    PJ_ASSERT_RETURN(tp && pkt && (*pkt_len>0), PJ_EINVAL);
    PJ_ASSERT_RETURN(srtp->session_inited, PJ_EINVALIDOP);

    /* Make sure buffer is 32bit aligned */
    PJ_ASSERT_ON_FAIL( (((pj_ssize_t)pkt) & 0x03)==0, return PJ_EINVAL);

    pj_lock_acquire(srtp->mutex);

    if (!srtp->session_inited) {
        pj_lock_release(srtp->mutex);
        return PJ_EINVALIDOP;
    }

    if (is_rtp)
        err = srtp_unprotect(srtp->srtp_ctx.srtp_rx_ctx, pkt, pkt_len);
    else
        err = srtp_unprotect_rtcp(srtp->srtp_ctx.srtp_rx_ctx, pkt, pkt_len);

    if (err != srtp_err_status_ok) {
        PJ_LOG(5,(srtp->pool->obj_name,
                  "Failed to unprotect SRTP, pkt size=%d, err=%s",
                  *pkt_len, get_libsrtp_errstr(err)));
    }

    pj_lock_release(srtp->mutex);

    return (err==srtp_err_status_ok) ? PJ_SUCCESS :
                                       PJMEDIA_ERRNO_FROM_LIBSRTP(err);
}

#endif
