/* $Id: ice_strans.c 4412 2013-03-05 03:12:32Z riza $ */
/*
 * 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 <pjnath/ice_strans.h>
#include <pjnath/errno.h>
#include <pj/addr_resolv.h>
#include <pj/array.h>
#include <pj/assert.h>
#include <pj/ip_helper.h>
#include <pj/lock.h>
#include <pj/log.h>
#include <pj/os.h>
#include <pj/pool.h>
#include <pj/rand.h>
#include <pj/string.h>
#include <pj/compat/socket.h>

#define ENABLE_TRACE 0

#if defined(ENABLE_TRACE) && (ENABLE_TRACE != 0)
#  define TRACE_PKT(expr)	    PJ_LOG(5,expr)
#else
#  define TRACE_PKT(expr)
#endif


/* Transport IDs */
enum tp_type
{
    TP_NONE,
    TP_STUN,
    TP_TURN
};

/* Candidate's local preference values. This is mostly used to
 * specify preference among candidates with the same type. Since
 * we don't have the facility to specify that, we'll just set it
 * all to the same value.
 */
#if PJNATH_ICE_PRIO_STD
#   define SRFLX_PREF  65535
#   define HOST_PREF   65535
#   define RELAY_PREF  65535
#else
#   define SRFLX_PREF  0
#   define HOST_PREF   0
#   define RELAY_PREF  0
#endif


/* The candidate type preference when STUN candidate is used */
static pj_uint8_t srflx_pref_table[PJ_ICE_CAND_TYPE_MAX] =
{
#if PJNATH_ICE_PRIO_STD
    100,    /**< PJ_ICE_HOST_PREF	    */
    110,    /**< PJ_ICE_SRFLX_PREF	    */
    126,    /**< PJ_ICE_PRFLX_PREF	    */
    0	    /**< PJ_ICE_RELAYED_PREF    */
#else
    /* Keep it to 2 bits */
    1,	/**< PJ_ICE_HOST_PREF	    */
    2,	/**< PJ_ICE_SRFLX_PREF	    */
    3,	/**< PJ_ICE_PRFLX_PREF	    */
    0	/**< PJ_ICE_RELAYED_PREF    */
#endif
};


/* ICE callbacks */
static void	   on_ice_complete(pj_ice_sess *ice, pj_status_t status);
static pj_status_t ice_tx_pkt(pj_ice_sess *ice,
			      unsigned comp_id,
			      unsigned transport_id,
			      const void *pkt, pj_size_t size,
			      const pj_sockaddr_t *dst_addr,
			      unsigned dst_addr_len);
static void	   ice_rx_data(pj_ice_sess *ice,
			       unsigned comp_id,
			       unsigned transport_id,
			       void *pkt, pj_size_t size,
			       const pj_sockaddr_t *src_addr,
			       unsigned src_addr_len);


/* STUN socket callbacks */
/* Notification when incoming packet has been received. */
static pj_bool_t stun_on_rx_data(pj_stun_sock *stun_sock,
				 void *pkt,
				 unsigned pkt_len,
				 const pj_sockaddr_t *src_addr,
				 unsigned addr_len);
/* Notifification when asynchronous send operation has completed. */
static pj_bool_t stun_on_data_sent(pj_stun_sock *stun_sock,
				   pj_ioqueue_op_key_t *send_key,
				   pj_ssize_t sent);
/* Notification when the status of the STUN transport has changed. */
static pj_bool_t stun_on_status(pj_stun_sock *stun_sock,
				pj_stun_sock_op op,
				pj_status_t status);


/* TURN callbacks */
static void turn_on_rx_data(pj_turn_sock *turn_sock,
			    void *pkt,
			    unsigned pkt_len,
			    const pj_sockaddr_t *peer_addr,
			    unsigned addr_len);
static void turn_on_state(pj_turn_sock *turn_sock, pj_turn_state_t old_state,
			  pj_turn_state_t new_state);



/* Forward decls */
static void ice_st_on_destroy(void *obj);
static void destroy_ice_st(pj_ice_strans *ice_st);
#define ice_st_perror(ice_st,msg,rc) pjnath_perror(ice_st->obj_name,msg,rc)
static void sess_init_update(pj_ice_strans *ice_st);

/**
 * This structure describes an ICE stream transport component. A component
 * in ICE stream transport typically corresponds to a single socket created
 * for this component, and bound to a specific transport address. This
 * component may have multiple alias addresses, for example one alias
 * address for each interfaces in multi-homed host, another for server
 * reflexive alias, and another for relayed alias. For each transport
 * address alias, an ICE stream transport candidate (#pj_ice_sess_cand) will
 * be created, and these candidates will eventually registered to the ICE
 * session.
 */
typedef struct pj_ice_strans_comp
{
    pj_ice_strans	*ice_st;	/**< ICE stream transport.	*/
    unsigned		 comp_id;	/**< Component ID.		*/

    pj_stun_sock	*stun_sock;	/**< STUN transport.		*/
    pj_turn_sock	*turn_sock;	/**< TURN relay transport.	*/
    pj_bool_t		 turn_log_off;	/**< TURN loggin off?		*/
    unsigned		 turn_err_cnt;	/**< TURN disconnected count.	*/

    unsigned		 cand_cnt;	/**< # of candidates/aliaes.	*/
    pj_ice_sess_cand	 cand_list[PJ_ICE_ST_MAX_CAND];	/**< Cand array	*/

    unsigned		 default_cand;	/**< Default candidate.		*/

} pj_ice_strans_comp;


/**
 * This structure represents the ICE stream transport.
 */
struct pj_ice_strans
{
    char		    *obj_name;	/**< Log ID.			*/
    pj_pool_t		    *pool;	/**< Pool used by this object.	*/
    void		    *user_data;	/**< Application data.		*/
    pj_ice_strans_cfg	     cfg;	/**< Configuration.		*/
    pj_ice_strans_cb	     cb;	/**< Application callback.	*/
    pj_grp_lock_t	    *grp_lock;  /**< Group lock.		*/

    pj_ice_strans_state	     state;	/**< Session state.		*/
    pj_ice_sess		    *ice;	/**< ICE session.		*/
    pj_time_val		     start_time;/**< Time when ICE was started	*/

    unsigned		     comp_cnt;	/**< Number of components.	*/
    pj_ice_strans_comp	   **comp;	/**< Components array.		*/

    pj_timer_entry	     ka_timer;	/**< STUN keep-alive timer.	*/

    pj_bool_t		     destroy_req;/**< Destroy has been called?	*/
    pj_bool_t		     cb_called;	/**< Init error callback called?*/
};


/* Set ICE state*/
static void set_ice_state(pj_ice_strans *ice_st, pj_ice_strans_state state)
{
    pj_ice_strans_state prev = ice_st->state;

    if (prev != state) {
        ice_st->state = state;
        if (ice_st->cb.on_ice_state)
            (*ice_st->cb.on_ice_state)(ice_st, prev, state);
    }
}


/* Validate configuration */
static pj_status_t pj_ice_strans_cfg_check_valid(const pj_ice_strans_cfg *cfg)
{
    pj_status_t status;

    status = pj_stun_config_check_valid(&cfg->stun_cfg);
    if (!status)
	return status;

    return PJ_SUCCESS;
}


/*
 * Initialize ICE transport configuration with default values.
 */
PJ_DEF(void) pj_ice_strans_cfg_default(pj_ice_strans_cfg *cfg)
{
    pj_bzero(cfg, sizeof(*cfg));

    pj_stun_config_init(&cfg->stun_cfg, NULL, 0, NULL, NULL);
    pj_stun_sock_cfg_default(&cfg->stun.cfg);
    pj_turn_alloc_param_default(&cfg->turn.alloc_param);
    pj_turn_sock_cfg_default(&cfg->turn.cfg);

    pj_ice_sess_options_default(&cfg->opt);

    cfg->af = pj_AF_INET();
    cfg->stun.port = PJ_STUN_PORT;
    cfg->turn.conn_type = PJ_TURN_TP_UDP;

    cfg->stun.max_host_cands = 64;
    cfg->stun.ignore_stun_error = PJ_FALSE;
}


/*
 * Copy configuration.
 */
PJ_DEF(void) pj_ice_strans_cfg_copy( pj_pool_t *pool,
				     pj_ice_strans_cfg *dst,
				     const pj_ice_strans_cfg *src)
{
    pj_memcpy(dst, src, sizeof(*src));

    if (src->stun.server.slen)
	pj_strdup(pool, &dst->stun.server, &src->stun.server);
    if (src->turn.server.slen)
	pj_strdup(pool, &dst->turn.server, &src->turn.server);
    pj_stun_auth_cred_dup(pool, &dst->turn.auth_cred,
			  &src->turn.auth_cred);
}


/*
 * Add or update TURN candidate.
 */
static pj_status_t add_update_turn(pj_ice_strans *ice_st,
				   pj_ice_strans_comp *comp)
{
    pj_turn_sock_cb turn_sock_cb;
    pj_ice_sess_cand *cand = NULL;
    unsigned i;
    pj_status_t status;

    /* Find relayed candidate in the component */
    for (i=0; i<comp->cand_cnt; ++i) {
	if (comp->cand_list[i].type == PJ_ICE_CAND_TYPE_RELAYED) {
	    cand = &comp->cand_list[i];
	    break;
	}
    }

    /* If candidate is found, invalidate it first */
    if (cand) {
	cand->status = PJ_EPENDING;

	/* Also if this component's default candidate is set to relay,
	 * move it temporarily to something else.
	 */
	if ((int)comp->default_cand == cand - comp->cand_list) {
	    /* Init to something */
	    comp->default_cand = 0;
	    /* Use srflx candidate as the default, if any */
	    for (i=0; i<comp->cand_cnt; ++i) {
		if (comp->cand_list[i].type == PJ_ICE_CAND_TYPE_SRFLX) {
		    comp->default_cand = i;
		    break;
		}
	    }
	}
    }

    /* Init TURN socket */
    pj_bzero(&turn_sock_cb, sizeof(turn_sock_cb));
    turn_sock_cb.on_rx_data = &turn_on_rx_data;
    turn_sock_cb.on_state = &turn_on_state;

    /* Override with component specific QoS settings, if any */
    if (ice_st->cfg.comp[comp->comp_id-1].qos_type) {
	ice_st->cfg.turn.cfg.qos_type =
	    ice_st->cfg.comp[comp->comp_id-1].qos_type;
    }
    if (ice_st->cfg.comp[comp->comp_id-1].qos_params.flags) {
	pj_memcpy(&ice_st->cfg.turn.cfg.qos_params,
		  &ice_st->cfg.comp[comp->comp_id-1].qos_params,
		  sizeof(ice_st->cfg.turn.cfg.qos_params));
    }

    /* Override with component specific socket buffer size settings, if any */
    if (ice_st->cfg.comp[comp->comp_id-1].so_rcvbuf_size > 0) {
	ice_st->cfg.turn.cfg.so_rcvbuf_size =
	    ice_st->cfg.comp[comp->comp_id-1].so_rcvbuf_size;
    }
    if (ice_st->cfg.comp[comp->comp_id-1].so_sndbuf_size > 0) {
	ice_st->cfg.turn.cfg.so_sndbuf_size =
	    ice_st->cfg.comp[comp->comp_id-1].so_sndbuf_size;
    }

    /* Create the TURN transport */
    status = pj_turn_sock_create(&ice_st->cfg.stun_cfg, ice_st->cfg.af,
				 ice_st->cfg.turn.conn_type,
				 &turn_sock_cb, &ice_st->cfg.turn.cfg,
				 comp, &comp->turn_sock);
    if (status != PJ_SUCCESS) {
	return status;
    }

    /* Add pending job */
    ///sess_add_ref(ice_st);

    /* Start allocation */
    status=pj_turn_sock_alloc(comp->turn_sock,
			      &ice_st->cfg.turn.server,
			      ice_st->cfg.turn.port,
			      ice_st->cfg.resolver,
			      &ice_st->cfg.turn.auth_cred,
			      &ice_st->cfg.turn.alloc_param);
    if (status != PJ_SUCCESS) {
	///sess_dec_ref(ice_st);
	return status;
    }

    /* Add relayed candidate with pending status if there's no existing one */
    if (cand == NULL) {
	cand = &comp->cand_list[comp->cand_cnt++];
	cand->type = PJ_ICE_CAND_TYPE_RELAYED;
	cand->status = PJ_EPENDING;
	cand->local_pref = RELAY_PREF;
	cand->transport_id = TP_TURN;
	cand->comp_id = (pj_uint8_t) comp->comp_id;
    }

    PJ_LOG(4,(ice_st->obj_name,
		  "Comp %d: TURN relay candidate waiting for allocation",
		  comp->comp_id));

    return PJ_SUCCESS;
}

static pj_bool_t ice_cand_equals(pj_ice_sess_cand *lcand, 
		    	         pj_ice_sess_cand *rcand)
{
    if (lcand == NULL && rcand == NULL){
        return PJ_TRUE;
    }
    if (lcand == NULL || rcand == NULL){
        return PJ_FALSE;
    }
    
    if (lcand->type != rcand->type
        || lcand->status != rcand->status
        || lcand->comp_id != rcand->comp_id
        || lcand->transport_id != rcand->transport_id
        || lcand->local_pref != rcand->local_pref
        || lcand->prio != rcand->prio
        || pj_sockaddr_cmp(&lcand->addr, &rcand->addr) != 0
        || pj_sockaddr_cmp(&lcand->base_addr, &rcand->base_addr) != 0)
    {
        return PJ_FALSE;
    }
    
    return PJ_TRUE;
}

/*
 * Create the component.
 */
static pj_status_t create_comp(pj_ice_strans *ice_st, unsigned comp_id)
{
    pj_ice_strans_comp *comp = NULL;
    pj_status_t status;

    /* Verify arguments */
    PJ_ASSERT_RETURN(ice_st && comp_id, PJ_EINVAL);

    /* Check that component ID present */
    PJ_ASSERT_RETURN(comp_id <= ice_st->comp_cnt, PJNATH_EICEINCOMPID);

    /* Create component */
    comp = PJ_POOL_ZALLOC_T(ice_st->pool, pj_ice_strans_comp);
    comp->ice_st = ice_st;
    comp->comp_id = comp_id;

    ice_st->comp[comp_id-1] = comp;

    /* Initialize default candidate */
    comp->default_cand = 0;

    /* Create STUN transport if configured */
    if (ice_st->cfg.stun.server.slen || ice_st->cfg.stun.max_host_cands) {
	pj_stun_sock_cb stun_sock_cb;
	pj_ice_sess_cand *cand;

	pj_bzero(&stun_sock_cb, sizeof(stun_sock_cb));
	stun_sock_cb.on_rx_data = &stun_on_rx_data;
	stun_sock_cb.on_status = &stun_on_status;
	stun_sock_cb.on_data_sent = &stun_on_data_sent;

	/* Override component specific QoS settings, if any */
	if (ice_st->cfg.comp[comp_id-1].qos_type) {
	    ice_st->cfg.stun.cfg.qos_type =
		ice_st->cfg.comp[comp_id-1].qos_type;
	}
	if (ice_st->cfg.comp[comp_id-1].qos_params.flags) {
	    pj_memcpy(&ice_st->cfg.stun.cfg.qos_params,
		      &ice_st->cfg.comp[comp_id-1].qos_params,
		      sizeof(ice_st->cfg.stun.cfg.qos_params));
	}

	/* Override component specific socket buffer size settings, if any */
	if (ice_st->cfg.comp[comp_id-1].so_rcvbuf_size > 0) {
	    ice_st->cfg.stun.cfg.so_rcvbuf_size =
		ice_st->cfg.comp[comp_id-1].so_rcvbuf_size;
	}
	if (ice_st->cfg.comp[comp_id-1].so_sndbuf_size > 0) {
	    ice_st->cfg.stun.cfg.so_sndbuf_size =
		ice_st->cfg.comp[comp_id-1].so_sndbuf_size;
	}

	/* Create the STUN transport */
	status = pj_stun_sock_create(&ice_st->cfg.stun_cfg, NULL,
				     ice_st->cfg.af, &stun_sock_cb,
				     &ice_st->cfg.stun.cfg,
				     comp, &comp->stun_sock);
	if (status != PJ_SUCCESS)
	    return status;

	/* Start STUN Binding resolution and add srflx candidate
	 * only if server is set
	 */
	if (ice_st->cfg.stun.server.slen) {
	    pj_stun_sock_info stun_sock_info;

	    /* Add pending job */
	    ///sess_add_ref(ice_st);

	    PJ_LOG(4,(ice_st->obj_name,
		      "Comp %d: srflx candidate starts Binding discovery",
		      comp_id));

	    pj_log_push_indent();

	    /* Start Binding resolution */
	    status = pj_stun_sock_start(comp->stun_sock,
					&ice_st->cfg.stun.server,
					ice_st->cfg.stun.port,
					ice_st->cfg.resolver);
	    if (status != PJ_SUCCESS) {
		///sess_dec_ref(ice_st);
		pj_log_pop_indent();
		return status;
	    }

	    /* Enumerate addresses */
	    status = pj_stun_sock_get_info(comp->stun_sock, &stun_sock_info);
	    if (status != PJ_SUCCESS) {
		///sess_dec_ref(ice_st);
		pj_log_pop_indent();
		return status;
	    }

	    /* Add srflx candidate with pending status. */
	    cand = &comp->cand_list[comp->cand_cnt++];
	    cand->type = PJ_ICE_CAND_TYPE_SRFLX;
	    cand->status = PJ_EPENDING;
	    cand->local_pref = SRFLX_PREF;
	    cand->transport_id = TP_STUN;
	    cand->comp_id = (pj_uint8_t) comp_id;
	    pj_sockaddr_cp(&cand->base_addr, &stun_sock_info.aliases[0]);
	    pj_sockaddr_cp(&cand->rel_addr, &cand->base_addr);
	    pj_ice_calc_foundation(ice_st->pool, &cand->foundation,
				   cand->type, &cand->base_addr);

	    /* Set default candidate to srflx */
	    comp->default_cand = (unsigned)(cand - comp->cand_list);

	    pj_log_pop_indent();
	}

	/* Add local addresses to host candidates, unless max_host_cands
	 * is set to zero.
	 */
	if (ice_st->cfg.stun.max_host_cands) {
	    pj_stun_sock_info stun_sock_info;
	    unsigned i;

	    /* Enumerate addresses */
	    status = pj_stun_sock_get_info(comp->stun_sock, &stun_sock_info);
	    if (status != PJ_SUCCESS)
		return status;

	    for (i=0; i<stun_sock_info.alias_cnt &&
		      i<ice_st->cfg.stun.max_host_cands; ++i)
	    {
		unsigned j;
		pj_bool_t cand_duplicate = PJ_FALSE;
		char addrinfo[PJ_INET6_ADDRSTRLEN+10];
		const pj_sockaddr *addr = &stun_sock_info.aliases[i];

		/* Leave one candidate for relay */
		if (comp->cand_cnt >= PJ_ICE_ST_MAX_CAND-1) {
		    PJ_LOG(4,(ice_st->obj_name, "Too many host candidates"));
		    break;
		}

		/* Ignore loopback addresses unless cfg->stun.loop_addr
		 * is set
		 */
		if ((pj_ntohl(addr->ipv4.sin_addr.s_addr)>>24)==127) {
		    if (ice_st->cfg.stun.loop_addr==PJ_FALSE)
			continue;
		}

		cand = &comp->cand_list[comp->cand_cnt];

		cand->type = PJ_ICE_CAND_TYPE_HOST;
		cand->status = PJ_SUCCESS;
		cand->local_pref = HOST_PREF;
		cand->transport_id = TP_STUN;
		cand->comp_id = (pj_uint8_t) comp_id;
		pj_sockaddr_cp(&cand->addr, addr);
		pj_sockaddr_cp(&cand->base_addr, addr);
		pj_bzero(&cand->rel_addr, sizeof(cand->rel_addr));
            
		/* Check if not already in list */
		for (j=0; j<comp->cand_cnt; j++) {
		    if (ice_cand_equals(cand, &comp->cand_list[j])) {
			cand_duplicate = PJ_TRUE;
			break;
		    }
		}

		if (cand_duplicate) {
		    PJ_LOG(4, (ice_st->obj_name,
			   "Comp %d: host candidate %s is a duplicate",
			   comp_id, pj_sockaddr_print(&cand->addr, addrinfo,
			   sizeof(addrinfo), 3)));

		    pj_bzero(&cand->addr, sizeof(cand->addr));
		    pj_bzero(&cand->base_addr, sizeof(cand->base_addr));
		    continue;
		} else {
		    comp->cand_cnt+=1;
		}
            
		pj_ice_calc_foundation(ice_st->pool, &cand->foundation,
				       cand->type, &cand->base_addr);

		PJ_LOG(4,(ice_st->obj_name,
			  "Comp %d: host candidate %s added",
			  comp_id, pj_sockaddr_print(&cand->addr, addrinfo,
						     sizeof(addrinfo), 3)));
	    }
	}
    }

    /* Create TURN relay if configured. */
    if (ice_st->cfg.turn.server.slen) {
	add_update_turn(ice_st, comp);
    }

    /* It's possible that we end up without any candidates */
    if (comp->cand_cnt == 0) {
	PJ_LOG(4,(ice_st->obj_name,
		  "Error: no candidate is created due to settings"));
	return PJ_EINVAL;
    }

    return PJ_SUCCESS;
}


/*
 * Create ICE stream transport
 */
PJ_DEF(pj_status_t) pj_ice_strans_create( const char *name,
					  const pj_ice_strans_cfg *cfg,
					  unsigned comp_cnt,
					  void *user_data,
					  const pj_ice_strans_cb *cb,
					  pj_ice_strans **p_ice_st)
{
    pj_pool_t *pool;
    pj_ice_strans *ice_st;
    unsigned i;
    pj_status_t status;

    status = pj_ice_strans_cfg_check_valid(cfg);
    if (status != PJ_SUCCESS)
	return status;

    PJ_ASSERT_RETURN(comp_cnt && cb && p_ice_st &&
		     comp_cnt <= PJ_ICE_MAX_COMP , PJ_EINVAL);

    if (name == NULL)
	name = "ice%p";

    pool = pj_pool_create(cfg->stun_cfg.pf, name, PJNATH_POOL_LEN_ICE_STRANS,
			  PJNATH_POOL_INC_ICE_STRANS, NULL);
    ice_st = PJ_POOL_ZALLOC_T(pool, pj_ice_strans);
    ice_st->pool = pool;
    ice_st->obj_name = pool->obj_name;
    ice_st->user_data = user_data;

    PJ_LOG(4,(ice_st->obj_name,
	      "Creating ICE stream transport with %d component(s)",
	      comp_cnt));
    pj_log_push_indent();

    status = pj_grp_lock_create(pool, NULL, &ice_st->grp_lock);
    if (status != PJ_SUCCESS) {
	pj_pool_release(pool);
	pj_log_pop_indent();
	return status;
    }

    pj_grp_lock_add_ref(ice_st->grp_lock);
    pj_grp_lock_add_handler(ice_st->grp_lock, pool, ice_st,
			    &ice_st_on_destroy);

    pj_ice_strans_cfg_copy(pool, &ice_st->cfg, cfg);
    ice_st->cfg.stun.cfg.grp_lock = ice_st->grp_lock;
    ice_st->cfg.turn.cfg.grp_lock = ice_st->grp_lock;
    pj_memcpy(&ice_st->cb, cb, sizeof(*cb));

    ice_st->comp_cnt = comp_cnt;
    ice_st->comp = (pj_ice_strans_comp**)
		   pj_pool_calloc(pool, comp_cnt, sizeof(pj_ice_strans_comp*));

    /* Move state to candidate gathering */
    set_ice_state(ice_st, PJ_ICE_STRANS_STATE_INIT);

    /* Acquire initialization mutex to prevent callback to be
     * called before we finish initialization.
     */
    pj_grp_lock_acquire(ice_st->grp_lock);

    for (i=0; i<comp_cnt; ++i) {
	status = create_comp(ice_st, i+1);
	if (status != PJ_SUCCESS) {
	    pj_grp_lock_release(ice_st->grp_lock);
	    destroy_ice_st(ice_st);
	    pj_log_pop_indent();
	    return status;
	}
    }

    /* Done with initialization */
    pj_grp_lock_release(ice_st->grp_lock);

    PJ_LOG(4,(ice_st->obj_name, "ICE stream transport %p created", ice_st));

    *p_ice_st = ice_st;

    /* Check if all candidates are ready (this may call callback) */
    sess_init_update(ice_st);

    pj_log_pop_indent();

    return PJ_SUCCESS;
}

/* REALLY destroy ICE */
static void ice_st_on_destroy(void *obj)
{
    pj_ice_strans *ice_st = (pj_ice_strans*)obj;

    PJ_LOG(4,(ice_st->obj_name, "ICE stream transport %p destroyed", obj));

    /* Done */
    pj_pool_release(ice_st->pool);
}

/* Destroy ICE */
static void destroy_ice_st(pj_ice_strans *ice_st)
{
    unsigned i;

    PJ_LOG(5,(ice_st->obj_name, "ICE stream transport %p destroy request..",
	      ice_st));
    pj_log_push_indent();

    pj_grp_lock_acquire(ice_st->grp_lock);

    if (ice_st->destroy_req) {
	pj_grp_lock_release(ice_st->grp_lock);
	return;
    }

    ice_st->destroy_req = PJ_TRUE;

    /* Destroy ICE if we have ICE */
    if (ice_st->ice) {
	pj_ice_sess_destroy(ice_st->ice);
	ice_st->ice = NULL;
    }

    /* Destroy all components */
    for (i=0; i<ice_st->comp_cnt; ++i) {
	if (ice_st->comp[i]) {
	    if (ice_st->comp[i]->stun_sock) {
		pj_stun_sock_destroy(ice_st->comp[i]->stun_sock);
		ice_st->comp[i]->stun_sock = NULL;
	    }
	    if (ice_st->comp[i]->turn_sock) {
		pj_turn_sock_destroy(ice_st->comp[i]->turn_sock);
		ice_st->comp[i]->turn_sock = NULL;
	    }
	}
    }

    pj_grp_lock_dec_ref(ice_st->grp_lock);
    pj_grp_lock_release(ice_st->grp_lock);

    pj_log_pop_indent();
}

/* Get ICE session state. */
PJ_DEF(pj_ice_strans_state) pj_ice_strans_get_state(pj_ice_strans *ice_st)
{
    return ice_st->state;
}

/* State string */
PJ_DEF(const char*) pj_ice_strans_state_name(pj_ice_strans_state state)
{
    const char *names[] = {
	"Null",
	"Candidate Gathering",
	"Candidate Gathering Complete",
	"Session Initialized",
	"Negotiation In Progress",
	"Negotiation Success",
	"Negotiation Failed"
    };

    PJ_ASSERT_RETURN(state <= PJ_ICE_STRANS_STATE_FAILED, "???");
    return names[state];
}

/* Notification about failure */
static void sess_fail(pj_ice_strans *ice_st, pj_ice_strans_op op,
		      const char *title, pj_status_t status)
{
    char errmsg[PJ_ERR_MSG_SIZE];

    pj_strerror(status, errmsg, sizeof(errmsg));
    PJ_LOG(4,(ice_st->obj_name, "%s: %s", title, errmsg));
    pj_log_push_indent();

    if (op==PJ_ICE_STRANS_OP_INIT && ice_st->cb_called) {
	pj_log_pop_indent();
	return;
    }

    ice_st->cb_called = PJ_TRUE;

    if (ice_st->cb.on_ice_complete)
	(*ice_st->cb.on_ice_complete)(ice_st, op, status);

    pj_log_pop_indent();
}

/* Update initialization status */
static void sess_init_update(pj_ice_strans *ice_st)
{
    unsigned i;

    /* Ignore if init callback has been called */
    if (ice_st->cb_called)
	return;

    /* Notify application when all candidates have been gathered */
    for (i=0; i<ice_st->comp_cnt; ++i) {
	unsigned j;
	pj_ice_strans_comp *comp = ice_st->comp[i];

	for (j=0; j<comp->cand_cnt; ++j) {
	    pj_ice_sess_cand *cand = &comp->cand_list[j];

	    if (cand->status == PJ_EPENDING)
		return;
	}
    }

    /* All candidates have been gathered */
    ice_st->cb_called = PJ_TRUE;
    set_ice_state(ice_st, PJ_ICE_STRANS_STATE_READY);
    if (ice_st->cb.on_ice_complete)
	(*ice_st->cb.on_ice_complete)(ice_st, PJ_ICE_STRANS_OP_INIT,
				      PJ_SUCCESS);
}

/*
 * Destroy ICE stream transport.
 */
PJ_DEF(pj_status_t) pj_ice_strans_destroy(pj_ice_strans *ice_st)
{
    destroy_ice_st(ice_st);
    return PJ_SUCCESS;
}


/*
 * Get user data
 */
PJ_DEF(void*) pj_ice_strans_get_user_data(pj_ice_strans *ice_st)
{
    PJ_ASSERT_RETURN(ice_st, NULL);
    return ice_st->user_data;
}


/*
 * Get the value of various options of the ICE stream transport.
 */
PJ_DEF(pj_status_t) pj_ice_strans_get_options( pj_ice_strans *ice_st,
					       pj_ice_sess_options *opt)
{
    PJ_ASSERT_RETURN(ice_st && opt, PJ_EINVAL);
    pj_memcpy(opt, &ice_st->cfg.opt, sizeof(*opt));
    return PJ_SUCCESS;
}

/*
 * Specify various options for this ICE stream transport.
 */
PJ_DEF(pj_status_t) pj_ice_strans_set_options(pj_ice_strans *ice_st,
					      const pj_ice_sess_options *opt)
{
    PJ_ASSERT_RETURN(ice_st && opt, PJ_EINVAL);
    pj_memcpy(&ice_st->cfg.opt, opt, sizeof(*opt));
    if (ice_st->ice)
	pj_ice_sess_set_options(ice_st->ice, &ice_st->cfg.opt);
    return PJ_SUCCESS;
}

/**
 * Get the group lock for this ICE stream transport.
 */
PJ_DEF(pj_grp_lock_t *) pj_ice_strans_get_grp_lock(pj_ice_strans *ice_st)
{
    PJ_ASSERT_RETURN(ice_st, NULL);
    return ice_st->grp_lock;
}

/*
 * Create ICE!
 */
PJ_DEF(pj_status_t) pj_ice_strans_init_ice(pj_ice_strans *ice_st,
					   pj_ice_sess_role role,
					   const pj_str_t *local_ufrag,
					   const pj_str_t *local_passwd)
{
    pj_status_t status;
    unsigned i;
    pj_ice_sess_cb ice_cb;
    //const pj_uint8_t srflx_prio[4] = { 100, 126, 110, 0 };

    /* Check arguments */
    PJ_ASSERT_RETURN(ice_st, PJ_EINVAL);
    /* Must not have ICE */
    PJ_ASSERT_RETURN(ice_st->ice == NULL, PJ_EINVALIDOP);
    /* Components must have been created */
    PJ_ASSERT_RETURN(ice_st->comp[0] != NULL, PJ_EINVALIDOP);

    /* Init callback */
    pj_bzero(&ice_cb, sizeof(ice_cb));
    ice_cb.on_ice_complete = &on_ice_complete;
    ice_cb.on_rx_data = &ice_rx_data;
    ice_cb.on_tx_pkt = &ice_tx_pkt;

    /* Create! */
    status = pj_ice_sess_create(&ice_st->cfg.stun_cfg, ice_st->obj_name, role,
			        ice_st->comp_cnt, &ice_cb,
			        local_ufrag, local_passwd,
			        ice_st->grp_lock,
			        &ice_st->ice);
    if (status != PJ_SUCCESS)
	return status;

    /* Associate user data */
    ice_st->ice->user_data = (void*)ice_st;

    /* Set options */
    pj_ice_sess_set_options(ice_st->ice, &ice_st->cfg.opt);

    /* If default candidate for components are SRFLX one, upload a custom
     * type priority to ICE session so that SRFLX candidates will get
     * checked first.
     */
    if (ice_st->comp[0]->cand_list[ice_st->comp[0]->default_cand].type
	    == PJ_ICE_CAND_TYPE_SRFLX)
    {
	pj_ice_sess_set_prefs(ice_st->ice, srflx_pref_table);
    }

    /* Add components/candidates */
    for (i=0; i<ice_st->comp_cnt; ++i) {
	unsigned j;
	pj_ice_strans_comp *comp = ice_st->comp[i];

	/* Re-enable logging for Send/Data indications */
	if (comp->turn_sock) {
	    PJ_LOG(5,(ice_st->obj_name,
		      "Disabling STUN Indication logging for "
		      "component %d", i+1));
	    pj_turn_sock_set_log(comp->turn_sock, 0xFFFF);
	    comp->turn_log_off = PJ_FALSE;
	}

	for (j=0; j<comp->cand_cnt; ++j) {
	    pj_ice_sess_cand *cand = &comp->cand_list[j];
	    unsigned ice_cand_id;

	    /* Skip if candidate is not ready */
	    if (cand->status != PJ_SUCCESS) {
		PJ_LOG(5,(ice_st->obj_name,
			  "Candidate %d of comp %d is not added (pending)",
			  j, i));
		continue;
	    }

	    /* Must have address */
	    pj_assert(pj_sockaddr_has_addr(&cand->addr));

	    /* Add the candidate */
	    status = pj_ice_sess_add_cand(ice_st->ice, comp->comp_id,
					  cand->transport_id, cand->type,
					  cand->local_pref,
					  &cand->foundation, &cand->addr,
					  &cand->base_addr,  &cand->rel_addr,
					  pj_sockaddr_get_len(&cand->addr),
					  (unsigned*)&ice_cand_id);
	    if (status != PJ_SUCCESS)
		goto on_error;
	}
    }

    /* ICE session is ready for negotiation */
    set_ice_state(ice_st, PJ_ICE_STRANS_STATE_SESS_READY);

    return PJ_SUCCESS;

on_error:
    pj_ice_strans_stop_ice(ice_st);
    return status;
}

/*
 * Check if the ICE stream transport has the ICE session created.
 */
PJ_DEF(pj_bool_t) pj_ice_strans_has_sess(pj_ice_strans *ice_st)
{
    PJ_ASSERT_RETURN(ice_st, PJ_FALSE);
    return ice_st->ice != NULL;
}

/*
 * Check if ICE negotiation is still running.
 */
PJ_DEF(pj_bool_t) pj_ice_strans_sess_is_running(pj_ice_strans *ice_st)
{
    return ice_st && ice_st->ice && ice_st->ice->rcand_cnt &&
	   !pj_ice_strans_sess_is_complete(ice_st);
}


/*
 * Check if ICE negotiation has completed.
 */
PJ_DEF(pj_bool_t) pj_ice_strans_sess_is_complete(pj_ice_strans *ice_st)
{
    return ice_st && ice_st->ice && ice_st->ice->is_complete;
}


/*
 * Get the current/running component count.
 */
PJ_DEF(unsigned) pj_ice_strans_get_running_comp_cnt(pj_ice_strans *ice_st)
{
    PJ_ASSERT_RETURN(ice_st, PJ_EINVAL);

    if (ice_st->ice && ice_st->ice->rcand_cnt) {
	return ice_st->ice->comp_cnt;
    } else {
	return ice_st->comp_cnt;
    }
}


/*
 * Get the ICE username fragment and password of the ICE session.
 */
PJ_DEF(pj_status_t) pj_ice_strans_get_ufrag_pwd( pj_ice_strans *ice_st,
						 pj_str_t *loc_ufrag,
						 pj_str_t *loc_pwd,
						 pj_str_t *rem_ufrag,
						 pj_str_t *rem_pwd)
{
    PJ_ASSERT_RETURN(ice_st && ice_st->ice, PJ_EINVALIDOP);

    if (loc_ufrag) *loc_ufrag = ice_st->ice->rx_ufrag;
    if (loc_pwd) *loc_pwd = ice_st->ice->rx_pass;

    if (rem_ufrag || rem_pwd) {
	PJ_ASSERT_RETURN(ice_st->ice->rcand_cnt != 0, PJ_EINVALIDOP);
	if (rem_ufrag) *rem_ufrag = ice_st->ice->tx_ufrag;
	if (rem_pwd) *rem_pwd = ice_st->ice->tx_pass;
    }

    return PJ_SUCCESS;
}

/*
 * Get number of candidates
 */
PJ_DEF(unsigned) pj_ice_strans_get_cands_count(pj_ice_strans *ice_st,
					       unsigned comp_id)
{
    unsigned i, cnt;

    PJ_ASSERT_RETURN(ice_st && ice_st->ice && comp_id &&
		     comp_id <= ice_st->comp_cnt, 0);

    cnt = 0;
    for (i=0; i<ice_st->ice->lcand_cnt; ++i) {
	if (ice_st->ice->lcand[i].comp_id != comp_id)
	    continue;
	++cnt;
    }

    return cnt;
}

/*
 * Enum candidates
 */
PJ_DEF(pj_status_t) pj_ice_strans_enum_cands(pj_ice_strans *ice_st,
					     unsigned comp_id,
					     unsigned *count,
					     pj_ice_sess_cand cand[])
{
    unsigned i, cnt;

    PJ_ASSERT_RETURN(ice_st && ice_st->ice && comp_id &&
		     comp_id <= ice_st->comp_cnt && count && cand, PJ_EINVAL);

    cnt = 0;
    for (i=0; i<ice_st->ice->lcand_cnt && cnt<*count; ++i) {
	if (ice_st->ice->lcand[i].comp_id != comp_id)
	    continue;
	pj_memcpy(&cand[cnt], &ice_st->ice->lcand[i],
		  sizeof(pj_ice_sess_cand));
	++cnt;
    }

    *count = cnt;
    return PJ_SUCCESS;
}

/*
 * Get default candidate.
 */
PJ_DEF(pj_status_t) pj_ice_strans_get_def_cand( pj_ice_strans *ice_st,
						unsigned comp_id,
						pj_ice_sess_cand *cand)
{
    const pj_ice_sess_check *valid_pair;

    PJ_ASSERT_RETURN(ice_st && comp_id && comp_id <= ice_st->comp_cnt &&
		      cand, PJ_EINVAL);

    valid_pair = pj_ice_strans_get_valid_pair(ice_st, comp_id);
    if (valid_pair) {
	pj_memcpy(cand, valid_pair->lcand, sizeof(pj_ice_sess_cand));
    } else {
	pj_ice_strans_comp *comp = ice_st->comp[comp_id - 1];
	pj_assert(comp->default_cand<comp->cand_cnt);
	pj_memcpy(cand, &comp->cand_list[comp->default_cand],
		  sizeof(pj_ice_sess_cand));
    }
    return PJ_SUCCESS;
}

/*
 * Get the current ICE role.
 */
PJ_DEF(pj_ice_sess_role) pj_ice_strans_get_role(pj_ice_strans *ice_st)
{
    PJ_ASSERT_RETURN(ice_st && ice_st->ice, PJ_ICE_SESS_ROLE_UNKNOWN);
    return ice_st->ice->role;
}

/*
 * Change session role.
 */
PJ_DEF(pj_status_t) pj_ice_strans_change_role( pj_ice_strans *ice_st,
					       pj_ice_sess_role new_role)
{
    PJ_ASSERT_RETURN(ice_st && ice_st->ice, PJ_EINVALIDOP);
    return pj_ice_sess_change_role(ice_st->ice, new_role);
}

/*
 * Start ICE processing !
 */
PJ_DEF(pj_status_t) pj_ice_strans_start_ice( pj_ice_strans *ice_st,
					     const pj_str_t *rem_ufrag,
					     const pj_str_t *rem_passwd,
					     unsigned rem_cand_cnt,
					     const pj_ice_sess_cand rem_cand[])
{
    pj_status_t status;

    PJ_ASSERT_RETURN(ice_st && rem_ufrag && rem_passwd &&
		     rem_cand_cnt && rem_cand, PJ_EINVAL);

    /* Mark start time */
    pj_gettimeofday(&ice_st->start_time);

    /* Build check list */
    status = pj_ice_sess_create_check_list(ice_st->ice, rem_ufrag, rem_passwd,
					   rem_cand_cnt, rem_cand);
    if (status != PJ_SUCCESS)
	return status;

    /* If we have TURN candidate, now is the time to create the permissions */
    if (ice_st->comp[0]->turn_sock) {
	unsigned i;

	for (i=0; i<ice_st->comp_cnt; ++i) {
	    pj_ice_strans_comp *comp = ice_st->comp[i];
	    pj_sockaddr addrs[PJ_ICE_ST_MAX_CAND];
	    unsigned j, count=0;

	    /* Gather remote addresses for this component */
	    for (j=0; j<rem_cand_cnt && count<PJ_ARRAY_SIZE(addrs); ++j) {
		if (rem_cand[j].comp_id==i+1) {
		    pj_memcpy(&addrs[count++], &rem_cand[j].addr,
			      pj_sockaddr_get_len(&rem_cand[j].addr));
		}
	    }

	    if (count) {
		status = pj_turn_sock_set_perm(comp->turn_sock, count,
					       addrs, 0);
		if (status != PJ_SUCCESS) {
		    pj_ice_strans_stop_ice(ice_st);
		    return status;
		}
	    }
	}
    }

    /* Start ICE negotiation! */
    status = pj_ice_sess_start_check(ice_st->ice);
    if (status != PJ_SUCCESS) {
	pj_ice_strans_stop_ice(ice_st);
	return status;
    }

    set_ice_state(ice_st, PJ_ICE_STRANS_STATE_NEGO);
    return status;
}

/*
 * Get valid pair.
 */
PJ_DEF(const pj_ice_sess_check*)
pj_ice_strans_get_valid_pair(const pj_ice_strans *ice_st,
			     unsigned comp_id)
{
    PJ_ASSERT_RETURN(ice_st && comp_id && comp_id <= ice_st->comp_cnt,
		     NULL);

    if (ice_st->ice == NULL)
	return NULL;

    return ice_st->ice->comp[comp_id-1].valid_check;
}

/*
 * Get ICE session.
 */
PJ_DEF(pj_ice_sess*)
pj_ice_strans_get_session(const pj_ice_strans *ice_st)
{
    return ice_st->ice;
}

/*
 * Get ICE start time.
 */
PJ_DEF(pj_time_val)
pj_ice_strans_get_start_time(const pj_ice_strans *ice_st)
{

    return ice_st->start_time;
}

/*
 * Stop ICE!
 */
PJ_DEF(pj_status_t) pj_ice_strans_stop_ice(pj_ice_strans *ice_st)
{
    PJ_ASSERT_RETURN(ice_st, PJ_EINVAL);
    
    /* Protect with group lock, since this may cause race condition with
     * pj_ice_strans_sendto().
     * See ticket #1877.
     */
    pj_grp_lock_acquire(ice_st->grp_lock);

    if (ice_st->ice) {
	pj_ice_sess_destroy(ice_st->ice);
	ice_st->ice = NULL;
    }

    set_ice_state(ice_st, PJ_ICE_STRANS_STATE_INIT);

    pj_grp_lock_release(ice_st->grp_lock);

    return PJ_SUCCESS;
}

/*
 * Application wants to send outgoing packet.
 */
PJ_DEF(pj_status_t) pj_ice_strans_sendto( pj_ice_strans *ice_st,
					  unsigned comp_id,
					  const void *data,
					  pj_size_t data_len,
					  const pj_sockaddr_t *dst_addr,
					  int dst_addr_len)
{
    pj_ice_strans_comp *comp;
    unsigned def_cand;
    pj_status_t status;

    PJ_ASSERT_RETURN(ice_st && comp_id && comp_id <= ice_st->comp_cnt &&
		     dst_addr && dst_addr_len, PJ_EINVAL);

    comp = ice_st->comp[comp_id-1];

    /* Check that default candidate for the component exists */
    def_cand = comp->default_cand;
    if (def_cand >= comp->cand_cnt)
	return PJ_EINVALIDOP;

    /* Protect with group lock, since this may cause race condition with
     * pj_ice_strans_stop_ice().
     * See ticket #1877.
     */
    pj_grp_lock_acquire(ice_st->grp_lock);

    /* If ICE is available, send data with ICE, otherwise send with the
     * default candidate selected during initialization.
     *
     * https://trac.pjsip.org/repos/ticket/1416:
     * Once ICE has failed, also send data with the default candidate.
     */
    if (ice_st->ice && ice_st->state == PJ_ICE_STRANS_STATE_RUNNING) {
	status = pj_ice_sess_send_data(ice_st->ice, comp_id, data, data_len);
	
	pj_grp_lock_release(ice_st->grp_lock);
	
	return status;
    } 
    
    pj_grp_lock_release(ice_st->grp_lock);
    
    if (comp->cand_list[def_cand].status == PJ_SUCCESS) {

	if (comp->cand_list[def_cand].type == PJ_ICE_CAND_TYPE_RELAYED) {

	    enum {
		msg_disable_ind = 0xFFFF &
				  ~(PJ_STUN_SESS_LOG_TX_IND|
				    PJ_STUN_SESS_LOG_RX_IND)
	    };

	    /* https://trac.pjsip.org/repos/ticket/1316 */
	    if (comp->turn_sock == NULL) {
		/* TURN socket error */
		return PJ_EINVALIDOP;
	    }

	    if (!comp->turn_log_off) {
		/* Disable logging for Send/Data indications */
		PJ_LOG(5,(ice_st->obj_name,
			  "Disabling STUN Indication logging for "
			  "component %d", comp->comp_id));
		pj_turn_sock_set_log(comp->turn_sock, msg_disable_ind);
		comp->turn_log_off = PJ_TRUE;
	    }

	    status = pj_turn_sock_sendto(comp->turn_sock,
					 (const pj_uint8_t*)data,
					 (unsigned)data_len,
					 dst_addr, dst_addr_len);
	    return (status==PJ_SUCCESS||status==PJ_EPENDING) ?
		    PJ_SUCCESS : status;
	} else {
	    status = pj_stun_sock_sendto(comp->stun_sock, NULL, data,
					 (unsigned)data_len, 0, dst_addr,
					 dst_addr_len);
	    return (status==PJ_SUCCESS||status==PJ_EPENDING) ?
		    PJ_SUCCESS : status;
	}

    } else
	return PJ_EINVALIDOP;
}

/*
 * Callback called by ICE session when ICE processing is complete, either
 * successfully or with failure.
 */
static void on_ice_complete(pj_ice_sess *ice, pj_status_t status)
{
    pj_ice_strans *ice_st = (pj_ice_strans*)ice->user_data;
    pj_time_val t;
    unsigned msec;

    pj_grp_lock_add_ref(ice_st->grp_lock);

    pj_gettimeofday(&t);
    PJ_TIME_VAL_SUB(t, ice_st->start_time);
    msec = PJ_TIME_VAL_MSEC(t);

    if (ice_st->cb.on_ice_complete) {
	if (status != PJ_SUCCESS) {
	    char errmsg[PJ_ERR_MSG_SIZE];
	    pj_strerror(status, errmsg, sizeof(errmsg));
	    PJ_LOG(4,(ice_st->obj_name,
		      "ICE negotiation failed after %ds:%03d: %s",
		      msec/1000, msec%1000, errmsg));
	} else {
	    unsigned i;
	    enum {
		msg_disable_ind = 0xFFFF &
				  ~(PJ_STUN_SESS_LOG_TX_IND|
				    PJ_STUN_SESS_LOG_RX_IND)
	    };

	    PJ_LOG(4,(ice_st->obj_name,
		      "ICE negotiation success after %ds:%03d",
		      msec/1000, msec%1000));

	    for (i=0; i<ice_st->comp_cnt; ++i) {
		const pj_ice_sess_check *check;

		check = pj_ice_strans_get_valid_pair(ice_st, i+1);
		if (check) {
		    char lip[PJ_INET6_ADDRSTRLEN+10];
		    char rip[PJ_INET6_ADDRSTRLEN+10];

		    pj_sockaddr_print(&check->lcand->addr, lip,
				      sizeof(lip), 3);
		    pj_sockaddr_print(&check->rcand->addr, rip,
				      sizeof(rip), 3);

		    if (check->lcand->transport_id == TP_TURN) {
			/* Activate channel binding for the remote address
			 * for more efficient data transfer using TURN.
			 */
			status = pj_turn_sock_bind_channel(
					ice_st->comp[i]->turn_sock,
					&check->rcand->addr,
					sizeof(check->rcand->addr));

			/* Disable logging for Send/Data indications */
			PJ_LOG(5,(ice_st->obj_name,
				  "Disabling STUN Indication logging for "
				  "component %d", i+1));
			pj_turn_sock_set_log(ice_st->comp[i]->turn_sock,
					     msg_disable_ind);
			ice_st->comp[i]->turn_log_off = PJ_TRUE;
		    }

		    PJ_LOG(4,(ice_st->obj_name, " Comp %d: "
			      "sending from %s candidate %s to "
			      "%s candidate %s",
			      i+1,
			      pj_ice_get_cand_type_name(check->lcand->type),
			      lip,
			      pj_ice_get_cand_type_name(check->rcand->type),
			      rip));

		} else {
		    PJ_LOG(4,(ice_st->obj_name,
			      "Comp %d: disabled", i+1));
		}
	    }
	}

	set_ice_state(ice_st, (status==PJ_SUCCESS) ? PJ_ICE_STRANS_STATE_RUNNING :
					             PJ_ICE_STRANS_STATE_FAILED);

	pj_log_push_indent();
	(*ice_st->cb.on_ice_complete)(ice_st, PJ_ICE_STRANS_OP_NEGOTIATION,
				      status);
	pj_log_pop_indent();

    }

    pj_grp_lock_dec_ref(ice_st->grp_lock);
}

/*
 * Callback called by ICE session when it wants to send outgoing packet.
 */
static pj_status_t ice_tx_pkt(pj_ice_sess *ice,
			      unsigned comp_id,
			      unsigned transport_id,
			      const void *pkt, pj_size_t size,
			      const pj_sockaddr_t *dst_addr,
			      unsigned dst_addr_len)
{
    pj_ice_strans *ice_st = (pj_ice_strans*)ice->user_data;
    pj_ice_strans_comp *comp;
    pj_status_t status;
#if defined(ENABLE_TRACE) && (ENABLE_TRACE != 0)
    char daddr[PJ_INET6_ADDRSTRLEN];
#endif

    PJ_ASSERT_RETURN(comp_id && comp_id <= ice_st->comp_cnt, PJ_EINVAL);

    comp = ice_st->comp[comp_id-1];

    TRACE_PKT((comp->ice_st->obj_name,
	       "Component %d TX packet to %s:%d with transport %d",
	       comp_id,
	       pj_sockaddr_print(dst_addr, daddr, sizeof(addr), 0),
	       pj_sockaddr_get_port(dst_addr),
	       transport_id));

    if (transport_id == TP_TURN) {
	if (comp->turn_sock) {
	    status = pj_turn_sock_sendto(comp->turn_sock,
					 (const pj_uint8_t*)pkt,
					 (unsigned)size,
					 dst_addr, dst_addr_len);
	} else {
	    status = PJ_EINVALIDOP;
	}
    } else if (transport_id == TP_STUN) {
	status = pj_stun_sock_sendto(comp->stun_sock, NULL,
				     pkt, (unsigned)size, 0,
				     dst_addr, dst_addr_len);
    } else {
	pj_assert(!"Invalid transport ID");
	status = PJ_EINVALIDOP;
    }

    return (status==PJ_SUCCESS||status==PJ_EPENDING) ? PJ_SUCCESS : status;
}

/*
 * Callback called by ICE session when it receives application data.
 */
static void ice_rx_data(pj_ice_sess *ice,
		        unsigned comp_id,
			unsigned transport_id,
		        void *pkt, pj_size_t size,
		        const pj_sockaddr_t *src_addr,
		        unsigned src_addr_len)
{
    pj_ice_strans *ice_st = (pj_ice_strans*)ice->user_data;

    PJ_UNUSED_ARG(transport_id);

    if (ice_st->cb.on_rx_data) {
	(*ice_st->cb.on_rx_data)(ice_st, comp_id, pkt, size,
				 src_addr, src_addr_len);
    }
}

/* Notification when incoming packet has been received from
 * the STUN socket.
 */
static pj_bool_t stun_on_rx_data(pj_stun_sock *stun_sock,
				 void *pkt,
				 unsigned pkt_len,
				 const pj_sockaddr_t *src_addr,
				 unsigned addr_len)
{
    pj_ice_strans_comp *comp;
    pj_ice_strans *ice_st;
    pj_status_t status;

    comp = (pj_ice_strans_comp*) pj_stun_sock_get_user_data(stun_sock);
    if (comp == NULL) {
	/* We have disassociated ourselves from the STUN socket */
	return PJ_FALSE;
    }

    ice_st = comp->ice_st;

    pj_grp_lock_add_ref(ice_st->grp_lock);

    if (ice_st->ice == NULL) {
	/* The ICE session is gone, but we're still receiving packets.
	 * This could also happen if remote doesn't do ICE. So just
	 * report this to application.
	 */
	if (ice_st->cb.on_rx_data) {
	    (*ice_st->cb.on_rx_data)(ice_st, comp->comp_id, pkt, pkt_len,
				     src_addr, addr_len);
	}

    } else {

	/* Hand over the packet to ICE session */
	status = pj_ice_sess_on_rx_pkt(comp->ice_st->ice, comp->comp_id,
				       TP_STUN, pkt, pkt_len,
				       src_addr, addr_len);

	if (status != PJ_SUCCESS) {
	    ice_st_perror(comp->ice_st, "Error processing packet",
			  status);
	}
    }

    return pj_grp_lock_dec_ref(ice_st->grp_lock) ? PJ_FALSE : PJ_TRUE;
}

/* Notifification when asynchronous send operation to the STUN socket
 * has completed.
 */
static pj_bool_t stun_on_data_sent(pj_stun_sock *stun_sock,
				   pj_ioqueue_op_key_t *send_key,
				   pj_ssize_t sent)
{
    PJ_UNUSED_ARG(stun_sock);
    PJ_UNUSED_ARG(send_key);
    PJ_UNUSED_ARG(sent);
    return PJ_TRUE;
}

/* Notification when the status of the STUN transport has changed. */
static pj_bool_t stun_on_status(pj_stun_sock *stun_sock,
				pj_stun_sock_op op,
				pj_status_t status)
{
    pj_ice_strans_comp *comp;
    pj_ice_strans *ice_st;
    pj_ice_sess_cand *cand = NULL;
    unsigned i;

    pj_assert(status != PJ_EPENDING);

    comp = (pj_ice_strans_comp*) pj_stun_sock_get_user_data(stun_sock);
    ice_st = comp->ice_st;

    pj_grp_lock_add_ref(ice_st->grp_lock);

    /* Wait until initialization completes */
    pj_grp_lock_acquire(ice_st->grp_lock);

    /* Find the srflx cancidate */
    for (i=0; i<comp->cand_cnt; ++i) {
	if (comp->cand_list[i].type == PJ_ICE_CAND_TYPE_SRFLX) {
	    cand = &comp->cand_list[i];
	    break;
	}
    }

    pj_grp_lock_release(ice_st->grp_lock);

    /* It is possible that we don't have srflx candidate even though this
     * callback is called. This could happen when we cancel adding srflx
     * candidate due to initialization error.
     */
    if (cand == NULL) {
	return pj_grp_lock_dec_ref(ice_st->grp_lock) ? PJ_FALSE : PJ_TRUE;
    }

    switch (op) {
    case PJ_STUN_SOCK_DNS_OP:
	if (status != PJ_SUCCESS) {
	    /* May not have cand, e.g. when error during init */
	    if (cand)
		cand->status = status;
	    if (!ice_st->cfg.stun.ignore_stun_error) {
		sess_fail(ice_st, PJ_ICE_STRANS_OP_INIT,
		          "DNS resolution failed", status);
	    } else {
		PJ_LOG(4,(ice_st->obj_name,
			  "STUN error is ignored for comp %d",
			  comp->comp_id));
	    }
	}
	break;
    case PJ_STUN_SOCK_BINDING_OP:
    case PJ_STUN_SOCK_MAPPED_ADDR_CHANGE:
	if (status == PJ_SUCCESS) {
	    pj_stun_sock_info info;

	    status = pj_stun_sock_get_info(stun_sock, &info);
	    if (status == PJ_SUCCESS) {
		char ipaddr[PJ_INET6_ADDRSTRLEN+10];
		const char *op_name = (op==PJ_STUN_SOCK_BINDING_OP) ?
				    "Binding discovery complete" :
				    "srflx address changed";
		pj_bool_t dup = PJ_FALSE;

		/* Eliminate the srflx candidate if the address is
		 * equal to other (host) candidates.
		 */
		for (i=0; i<comp->cand_cnt; ++i) {
		    if (comp->cand_list[i].type == PJ_ICE_CAND_TYPE_HOST &&
			pj_sockaddr_cmp(&comp->cand_list[i].addr,
					&info.mapped_addr) == 0)
		    {
			dup = PJ_TRUE;
			break;
		    }
		}

		if (dup) {
		    /* Duplicate found, remove the srflx candidate */
		    unsigned idx = (unsigned)(cand - comp->cand_list);

		    /* Update default candidate index */
		    if (comp->default_cand > idx) {
			--comp->default_cand;
		    } else if (comp->default_cand == idx) {
			comp->default_cand = 0;
		    }

		    /* Remove srflx candidate */
		    pj_array_erase(comp->cand_list, sizeof(comp->cand_list[0]),
				   comp->cand_cnt, idx);
		    --comp->cand_cnt;
		} else {
		    /* Otherwise update the address */
		    pj_sockaddr_cp(&cand->addr, &info.mapped_addr);
		    cand->status = PJ_SUCCESS;
		}

		PJ_LOG(4,(comp->ice_st->obj_name,
			  "Comp %d: %s, "
			  "srflx address is %s",
			  comp->comp_id, op_name,
			  pj_sockaddr_print(&info.mapped_addr, ipaddr,
					     sizeof(ipaddr), 3)));

		sess_init_update(ice_st);
	    }
	}

	if (status != PJ_SUCCESS) {
	    /* May not have cand, e.g. when error during init */
	    if (cand)
		cand->status = status;
	    if (!ice_st->cfg.stun.ignore_stun_error || comp->cand_cnt==1) {
		sess_fail(ice_st, PJ_ICE_STRANS_OP_INIT,
			  "STUN binding request failed", status);
	    } else {
		PJ_LOG(4,(ice_st->obj_name,
			  "STUN error is ignored for comp %d",
			  comp->comp_id));

		if (cand) {
		    unsigned idx = (unsigned)(cand - comp->cand_list);

		    /* Update default candidate index */
		    if (comp->default_cand == idx) {
			comp->default_cand = !idx;
		    }
		}

		sess_init_update(ice_st);
	    }
	}
	break;
    case PJ_STUN_SOCK_KEEP_ALIVE_OP:
	if (status != PJ_SUCCESS) {
	    pj_assert(cand != NULL);
	    cand->status = status;
	    if (!ice_st->cfg.stun.ignore_stun_error) {
		sess_fail(ice_st, PJ_ICE_STRANS_OP_INIT,
			  "STUN keep-alive failed", status);
	    } else {
		PJ_LOG(4,(ice_st->obj_name, "STUN error is ignored"));
	    }
	}
	break;
    }

    return pj_grp_lock_dec_ref(ice_st->grp_lock)? PJ_FALSE : PJ_TRUE;
}

/* Callback when TURN socket has received a packet */
static void turn_on_rx_data(pj_turn_sock *turn_sock,
			    void *pkt,
			    unsigned pkt_len,
			    const pj_sockaddr_t *peer_addr,
			    unsigned addr_len)
{
    pj_ice_strans_comp *comp;
    pj_status_t status;

    comp = (pj_ice_strans_comp*) pj_turn_sock_get_user_data(turn_sock);
    if (comp == NULL) {
	/* We have disassociated ourselves from the TURN socket */
	return;
    }

    pj_grp_lock_add_ref(comp->ice_st->grp_lock);

    if (comp->ice_st->ice == NULL) {
	/* The ICE session is gone, but we're still receiving packets.
	 * This could also happen if remote doesn't do ICE and application
	 * specifies TURN as the default address in SDP.
	 * So in this case just give the packet to application.
	 */
	if (comp->ice_st->cb.on_rx_data) {
	    (*comp->ice_st->cb.on_rx_data)(comp->ice_st, comp->comp_id, pkt,
					   pkt_len, peer_addr, addr_len);
	}

    } else {

	/* Hand over the packet to ICE */
	status = pj_ice_sess_on_rx_pkt(comp->ice_st->ice, comp->comp_id,
				       TP_TURN, pkt, pkt_len,
				       peer_addr, addr_len);

	if (status != PJ_SUCCESS) {
	    ice_st_perror(comp->ice_st,
			  "Error processing packet from TURN relay",
			  status);
	}
    }

    pj_grp_lock_dec_ref(comp->ice_st->grp_lock);
}


/* Callback when TURN client state has changed */
static void turn_on_state(pj_turn_sock *turn_sock, pj_turn_state_t old_state,
			  pj_turn_state_t new_state)
{
    pj_ice_strans_comp *comp;

    comp = (pj_ice_strans_comp*) pj_turn_sock_get_user_data(turn_sock);
    if (comp == NULL) {
	/* Not interested in further state notification once the relay is
	 * disconnecting.
	 */
	return;
    }

    PJ_LOG(5,(comp->ice_st->obj_name, "TURN client state changed %s --> %s",
	      pj_turn_state_name(old_state), pj_turn_state_name(new_state)));
    pj_log_push_indent();

    pj_grp_lock_add_ref(comp->ice_st->grp_lock);

    if (new_state == PJ_TURN_STATE_READY) {
	pj_turn_session_info rel_info;
	char ipaddr[PJ_INET6_ADDRSTRLEN+8];
	pj_ice_sess_cand *cand = NULL;
	unsigned i;

	comp->turn_err_cnt = 0;

	/* Get allocation info */
	pj_turn_sock_get_info(turn_sock, &rel_info);

	/* Wait until initialization completes */
	pj_grp_lock_acquire(comp->ice_st->grp_lock);

	/* Find relayed candidate in the component */
	for (i=0; i<comp->cand_cnt; ++i) {
	    if (comp->cand_list[i].type == PJ_ICE_CAND_TYPE_RELAYED) {
		cand = &comp->cand_list[i];
		break;
	    }
	}
	pj_assert(cand != NULL);

	pj_grp_lock_release(comp->ice_st->grp_lock);

	/* Update candidate */
	pj_sockaddr_cp(&cand->addr, &rel_info.relay_addr);
	pj_sockaddr_cp(&cand->base_addr, &rel_info.relay_addr);
	pj_sockaddr_cp(&cand->rel_addr, &rel_info.mapped_addr);
	pj_ice_calc_foundation(comp->ice_st->pool, &cand->foundation,
			       PJ_ICE_CAND_TYPE_RELAYED,
			       &rel_info.relay_addr);
	cand->status = PJ_SUCCESS;

	/* Set default candidate to relay */
	comp->default_cand = (unsigned)(cand - comp->cand_list);

	PJ_LOG(4,(comp->ice_st->obj_name,
		  "Comp %d: TURN allocation complete, relay address is %s",
		  comp->comp_id,
		  pj_sockaddr_print(&rel_info.relay_addr, ipaddr,
				     sizeof(ipaddr), 3)));

	sess_init_update(comp->ice_st);

    } else if (new_state >= PJ_TURN_STATE_DEALLOCATING) {
	pj_turn_session_info info;

	++comp->turn_err_cnt;

	pj_turn_sock_get_info(turn_sock, &info);

	/* Unregister ourself from the TURN relay */
	pj_turn_sock_set_user_data(turn_sock, NULL);
	comp->turn_sock = NULL;

	/* Set session to fail on error. last_status PJ_SUCCESS means normal
	 * deallocation, which should not trigger sess_fail as it may have
	 * been initiated by ICE destroy
	 */
	if (info.last_status != PJ_SUCCESS) {
	    if (comp->ice_st->state < PJ_ICE_STRANS_STATE_READY) {
		sess_fail(comp->ice_st, PJ_ICE_STRANS_OP_INIT,
			  "TURN allocation failed", info.last_status);
	    } else if (comp->turn_err_cnt > 1) {
		sess_fail(comp->ice_st, PJ_ICE_STRANS_OP_KEEP_ALIVE,
			  "TURN refresh failed", info.last_status);
	    } else {
		PJ_PERROR(4,(comp->ice_st->obj_name, info.last_status,
			  "Comp %d: TURN allocation failed, retrying",
			  comp->comp_id));
		add_update_turn(comp->ice_st, comp);
	    }
	}
    }

    pj_grp_lock_dec_ref(comp->ice_st->grp_lock);

    pj_log_pop_indent();
}

