* #27232: jni: added pjproject checkout as regular git content

We will remove it once the next release of pjsip (with Android support)
comes out and is merged into SFLphone.
diff --git a/jni/pjproject-android/.svn/pristine/34/34177eefc558d3abf4e60ec54174b418f4f1a675.svn-base b/jni/pjproject-android/.svn/pristine/34/34177eefc558d3abf4e60ec54174b418f4f1a675.svn-base
new file mode 100644
index 0000000..42fa635
--- /dev/null
+++ b/jni/pjproject-android/.svn/pristine/34/34177eefc558d3abf4e60ec54174b418f4f1a675.svn-base
@@ -0,0 +1,2044 @@
+/* $Id$ */
+/* 
+ * 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/turn_session.h>
+#include <pjnath/errno.h>
+#include <pjlib-util/srv_resolver.h>
+#include <pj/addr_resolv.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/hash.h>
+#include <pj/lock.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/rand.h>
+#include <pj/sock.h>
+
+#define PJ_TURN_CHANNEL_MIN	    0x4000
+#define PJ_TURN_CHANNEL_MAX	    0x7FFF  /* inclusive */
+#define PJ_TURN_CHANNEL_HTABLE_SIZE 8
+#define PJ_TURN_PERM_HTABLE_SIZE    8
+
+static const char *state_names[] = 
+{
+    "Null",
+    "Resolving",
+    "Resolved",
+    "Allocating",
+    "Ready",
+    "Deallocating",
+    "Deallocated",
+    "Destroying"
+};
+
+enum timer_id_t
+{
+    TIMER_NONE,
+    TIMER_KEEP_ALIVE,
+    TIMER_DESTROY
+};
+
+/* This structure describes a channel binding. A channel binding is index by
+ * the channel number or IP address and port number of the peer.
+ */
+struct ch_t
+{
+    /* The channel number */
+    pj_uint16_t	    num;
+
+    /* PJ_TRUE if we've received successful response to ChannelBind request
+     * for this channel.
+     */
+    pj_bool_t	    bound;
+
+    /* The peer IP address and port */
+    pj_sockaddr	    addr;
+
+    /* The channel binding expiration */
+    pj_time_val	    expiry;
+};
+
+
+/* This structure describes a permission. A permission is identified by the
+ * IP address only.
+ */
+struct perm_t
+{
+    /* Cache of hash value to speed-up lookup */
+    pj_uint32_t	    hval;
+
+    /* The permission IP address. The port number MUST be zero */
+    pj_sockaddr	    addr;
+
+    /* Number of peers that uses this permission. */
+    unsigned	    peer_cnt;
+
+    /* Automatically renew this permission once it expires? */
+    pj_bool_t	    renew;
+
+    /* The permission expiration */
+    pj_time_val	    expiry;
+
+    /* Arbitrary/random pointer value (token) to map this perm with the 
+     * request to create it. It is used to invalidate this perm when the 
+     * request fails.
+     */
+    void	   *req_token;
+};
+
+
+/* The TURN client session structure */
+struct pj_turn_session
+{
+    pj_pool_t		*pool;
+    const char		*obj_name;
+    pj_turn_session_cb	 cb;
+    void		*user_data;
+    pj_stun_config	 stun_cfg;
+    pj_bool_t		 is_destroying;
+
+    pj_grp_lock_t	*grp_lock;
+    int			 busy;
+
+    pj_turn_state_t	 state;
+    pj_status_t		 last_status;
+    pj_bool_t		 pending_destroy;
+
+    pj_stun_session	*stun;
+
+    unsigned		 lifetime;
+    int			 ka_interval;
+    pj_time_val		 expiry;
+
+    pj_timer_heap_t	*timer_heap;
+    pj_timer_entry	 timer;
+
+    pj_uint16_t		 default_port;
+
+    pj_uint16_t		 af;
+    pj_turn_tp_type	 conn_type;
+    pj_uint16_t		 srv_addr_cnt;
+    pj_sockaddr		*srv_addr_list;
+    pj_sockaddr		*srv_addr;
+
+    pj_bool_t		 pending_alloc;
+    pj_turn_alloc_param	 alloc_param;
+
+    pj_sockaddr		 mapped_addr;
+    pj_sockaddr		 relay_addr;
+
+    pj_hash_table_t	*ch_table;
+    pj_hash_table_t	*perm_table;
+
+    pj_uint32_t		 send_ind_tsx_id[3];
+    /* tx_pkt must be 16bit aligned */
+    pj_uint8_t		 tx_pkt[PJ_TURN_MAX_PKT_LEN];
+
+    pj_uint16_t		 next_ch;
+};
+
+
+/*
+ * Prototypes.
+ */
+static void sess_shutdown(pj_turn_session *sess,
+			  pj_status_t status);
+static void turn_sess_on_destroy(void *comp);
+static void do_destroy(pj_turn_session *sess);
+static void send_refresh(pj_turn_session *sess, int lifetime);
+static pj_status_t stun_on_send_msg(pj_stun_session *sess,
+				    void *token,
+				    const void *pkt,
+				    pj_size_t pkt_size,
+				    const pj_sockaddr_t *dst_addr,
+				    unsigned addr_len);
+static void stun_on_request_complete(pj_stun_session *sess,
+				     pj_status_t status,
+				     void *token,
+				     pj_stun_tx_data *tdata,
+				     const pj_stun_msg *response,
+				     const pj_sockaddr_t *src_addr,
+				     unsigned src_addr_len);
+static pj_status_t stun_on_rx_indication(pj_stun_session *sess,
+					 const pj_uint8_t *pkt,
+					 unsigned pkt_len,
+					 const pj_stun_msg *msg,
+					 void *token,
+					 const pj_sockaddr_t *src_addr,
+					 unsigned src_addr_len);
+static void dns_srv_resolver_cb(void *user_data,
+				pj_status_t status,
+				const pj_dns_srv_record *rec);
+static struct ch_t *lookup_ch_by_addr(pj_turn_session *sess,
+				      const pj_sockaddr_t *addr,
+				      unsigned addr_len,
+				      pj_bool_t update,
+				      pj_bool_t bind_channel);
+static struct ch_t *lookup_ch_by_chnum(pj_turn_session *sess,
+				       pj_uint16_t chnum);
+static struct perm_t *lookup_perm(pj_turn_session *sess,
+				  const pj_sockaddr_t *addr,
+				  unsigned addr_len,
+				  pj_bool_t update);
+static void invalidate_perm(pj_turn_session *sess,
+			    struct perm_t *perm);
+static void on_timer_event(pj_timer_heap_t *th, pj_timer_entry *e);
+
+
+/*
+ * Create default pj_turn_alloc_param.
+ */
+PJ_DEF(void) pj_turn_alloc_param_default(pj_turn_alloc_param *prm)
+{
+    pj_bzero(prm, sizeof(*prm));
+}
+
+/*
+ * Duplicate pj_turn_alloc_param.
+ */
+PJ_DEF(void) pj_turn_alloc_param_copy( pj_pool_t *pool, 
+				       pj_turn_alloc_param *dst,
+				       const pj_turn_alloc_param *src)
+{
+    PJ_UNUSED_ARG(pool);
+    pj_memcpy(dst, src, sizeof(*dst));
+}
+
+/*
+ * Get TURN state name.
+ */
+PJ_DEF(const char*) pj_turn_state_name(pj_turn_state_t state)
+{
+    return state_names[state];
+}
+
+/*
+ * Create TURN client session.
+ */
+PJ_DEF(pj_status_t) pj_turn_session_create( const pj_stun_config *cfg,
+					    const char *name,
+					    int af,
+					    pj_turn_tp_type conn_type,
+					    pj_grp_lock_t *grp_lock,
+					    const pj_turn_session_cb *cb,
+					    unsigned options,
+					    void *user_data,
+					    pj_turn_session **p_sess)
+{
+    pj_pool_t *pool;
+    pj_turn_session *sess;
+    pj_stun_session_cb stun_cb;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(cfg && cfg->pf && cb && p_sess, PJ_EINVAL);
+    PJ_ASSERT_RETURN(cb->on_send_pkt, PJ_EINVAL);
+
+    PJ_UNUSED_ARG(options);
+
+    if (name == NULL)
+	name = "turn%p";
+
+    /* Allocate and create TURN session */
+    pool = pj_pool_create(cfg->pf, name, PJNATH_POOL_LEN_TURN_SESS,
+			  PJNATH_POOL_INC_TURN_SESS, NULL);
+    sess = PJ_POOL_ZALLOC_T(pool, pj_turn_session);
+    sess->pool = pool;
+    sess->obj_name = pool->obj_name;
+    sess->timer_heap = cfg->timer_heap;
+    sess->af = (pj_uint16_t)af;
+    sess->conn_type = conn_type;
+    sess->ka_interval = PJ_TURN_KEEP_ALIVE_SEC;
+    sess->user_data = user_data;
+    sess->next_ch = PJ_TURN_CHANNEL_MIN;
+
+    /* Copy STUN session */
+    pj_memcpy(&sess->stun_cfg, cfg, sizeof(pj_stun_config));
+
+    /* Copy callback */
+    pj_memcpy(&sess->cb, cb, sizeof(*cb));
+
+    /* Peer hash table */
+    sess->ch_table = pj_hash_create(pool, PJ_TURN_CHANNEL_HTABLE_SIZE);
+
+    /* Permission hash table */
+    sess->perm_table = pj_hash_create(pool, PJ_TURN_PERM_HTABLE_SIZE);
+
+    /* Session lock */
+    if (grp_lock) {
+	sess->grp_lock = grp_lock;
+    } else {
+	status = pj_grp_lock_create(pool, NULL, &sess->grp_lock);
+	if (status != PJ_SUCCESS) {
+	    pj_pool_release(pool);
+	    return status;
+	}
+    }
+
+    pj_grp_lock_add_ref(sess->grp_lock);
+    pj_grp_lock_add_handler(sess->grp_lock, pool, sess,
+                            &turn_sess_on_destroy);
+
+    /* Timer */
+    pj_timer_entry_init(&sess->timer, TIMER_NONE, sess, &on_timer_event);
+
+    /* Create STUN session */
+    pj_bzero(&stun_cb, sizeof(stun_cb));
+    stun_cb.on_send_msg = &stun_on_send_msg;
+    stun_cb.on_request_complete = &stun_on_request_complete;
+    stun_cb.on_rx_indication = &stun_on_rx_indication;
+    status = pj_stun_session_create(&sess->stun_cfg, sess->obj_name, &stun_cb,
+				    PJ_FALSE, sess->grp_lock, &sess->stun);
+    if (status != PJ_SUCCESS) {
+	do_destroy(sess);
+	return status;
+    }
+
+    /* Attach ourself to STUN session */
+    pj_stun_session_set_user_data(sess->stun, sess);
+
+    /* Done */
+
+    PJ_LOG(4,(sess->obj_name, "TURN client session created"));
+
+    *p_sess = sess;
+    return PJ_SUCCESS;
+}
+
+
+static void turn_sess_on_destroy(void *comp)
+{
+    pj_turn_session *sess = (pj_turn_session*) comp;
+
+    /* Destroy pool */
+    if (sess->pool) {
+	pj_pool_t *pool = sess->pool;
+
+	PJ_LOG(4,(sess->obj_name, "TURN client session destroyed"));
+
+	sess->pool = NULL;
+	pj_pool_release(pool);
+    }
+}
+
+/* Destroy */
+static void do_destroy(pj_turn_session *sess)
+{
+    PJ_LOG(4,(sess->obj_name, "TURN session destroy request, ref_cnt=%d",
+	      pj_grp_lock_get_ref(sess->grp_lock)));
+
+    pj_grp_lock_acquire(sess->grp_lock);
+    if (sess->is_destroying) {
+	pj_grp_lock_release(sess->grp_lock);
+	return;
+    }
+
+    sess->is_destroying = PJ_TRUE;
+    pj_timer_heap_cancel_if_active(sess->timer_heap, &sess->timer, TIMER_NONE);
+    pj_stun_session_destroy(sess->stun);
+
+    pj_grp_lock_dec_ref(sess->grp_lock);
+    pj_grp_lock_release(sess->grp_lock);
+}
+
+
+/* Set session state */
+static void set_state(pj_turn_session *sess, enum pj_turn_state_t state)
+{
+    pj_turn_state_t old_state = sess->state;
+
+    if (state==sess->state)
+	return;
+
+    PJ_LOG(4,(sess->obj_name, "State changed %s --> %s",
+	      state_names[old_state], state_names[state]));
+    sess->state = state;
+
+    if (sess->cb.on_state) {
+	(*sess->cb.on_state)(sess, old_state, state);
+    }
+}
+
+/*
+ * Notify application and shutdown the TURN session.
+ */
+static void sess_shutdown(pj_turn_session *sess,
+			  pj_status_t status)
+{
+    pj_bool_t can_destroy = PJ_TRUE;
+
+    PJ_LOG(4,(sess->obj_name, "Request to shutdown in state %s, cause:%d",
+	      state_names[sess->state], status));
+
+    if (sess->last_status == PJ_SUCCESS && status != PJ_SUCCESS)
+	sess->last_status = status;
+
+    switch (sess->state) {
+    case PJ_TURN_STATE_NULL:
+	break;
+    case PJ_TURN_STATE_RESOLVING:
+	/* Wait for DNS callback invoked, it will call the this function
+	 * again. If the callback happens to get pending_destroy==FALSE,
+	 * the TURN allocation will call this function again.
+	 */
+	sess->pending_destroy = PJ_TRUE;
+	can_destroy = PJ_FALSE;
+	break;
+    case PJ_TURN_STATE_RESOLVED:
+	break;
+    case PJ_TURN_STATE_ALLOCATING:
+	/* We need to wait until allocation complete */
+	sess->pending_destroy = PJ_TRUE;
+	can_destroy = PJ_FALSE;
+	break;
+    case PJ_TURN_STATE_READY:
+	/* Send REFRESH with LIFETIME=0 */
+	can_destroy = PJ_FALSE;
+	send_refresh(sess, 0);
+	break;
+    case PJ_TURN_STATE_DEALLOCATING:
+	can_destroy = PJ_FALSE;
+	/* This may recursively call this function again with
+	 * state==PJ_TURN_STATE_DEALLOCATED.
+	 */
+	/* No need to deallocate as we're already deallocating!
+	 * See https://trac.pjsip.org/repos/ticket/1551
+	send_refresh(sess, 0);
+	*/
+	break;
+    case PJ_TURN_STATE_DEALLOCATED:
+    case PJ_TURN_STATE_DESTROYING:
+	break;
+    }
+
+    if (can_destroy) {
+	/* Schedule destroy */
+	pj_time_val delay = {0, 0};
+
+	set_state(sess, PJ_TURN_STATE_DESTROYING);
+
+	pj_timer_heap_cancel_if_active(sess->timer_heap, &sess->timer,
+	                               TIMER_NONE);
+	pj_timer_heap_schedule_w_grp_lock(sess->timer_heap, &sess->timer,
+	                                  &delay, TIMER_DESTROY,
+	                                  sess->grp_lock);
+    }
+}
+
+
+/*
+ * Public API to destroy TURN client session.
+ */
+PJ_DEF(pj_status_t) pj_turn_session_shutdown(pj_turn_session *sess)
+{
+    PJ_ASSERT_RETURN(sess, PJ_EINVAL);
+
+    pj_grp_lock_acquire(sess->grp_lock);
+
+    sess_shutdown(sess, PJ_SUCCESS);
+
+    pj_grp_lock_release(sess->grp_lock);
+
+    return PJ_SUCCESS;
+}
+
+
+/**
+ * Forcefully destroy the TURN session.
+ */
+PJ_DEF(pj_status_t) pj_turn_session_destroy( pj_turn_session *sess,
+					     pj_status_t last_err)
+{
+    PJ_ASSERT_RETURN(sess, PJ_EINVAL);
+
+    if (last_err != PJ_SUCCESS && sess->last_status == PJ_SUCCESS)
+	sess->last_status = last_err;
+    set_state(sess, PJ_TURN_STATE_DEALLOCATED);
+    sess_shutdown(sess, PJ_SUCCESS);
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Get TURN session info.
+ */
+PJ_DEF(pj_status_t) pj_turn_session_get_info( pj_turn_session *sess,
+					      pj_turn_session_info *info)
+{
+    pj_time_val now;
+
+    PJ_ASSERT_RETURN(sess && info, PJ_EINVAL);
+
+    pj_gettimeofday(&now);
+
+    info->state = sess->state;
+    info->conn_type = sess->conn_type;
+    info->lifetime = sess->expiry.sec - now.sec;
+    info->last_status = sess->last_status;
+
+    if (sess->srv_addr)
+	pj_memcpy(&info->server, sess->srv_addr, sizeof(info->server));
+    else
+	pj_bzero(&info->server, sizeof(info->server));
+
+    pj_memcpy(&info->mapped_addr, &sess->mapped_addr, 
+	      sizeof(sess->mapped_addr));
+    pj_memcpy(&info->relay_addr, &sess->relay_addr, 
+	      sizeof(sess->relay_addr));
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Re-assign user data.
+ */
+PJ_DEF(pj_status_t) pj_turn_session_set_user_data( pj_turn_session *sess,
+						   void *user_data)
+{
+    sess->user_data = user_data;
+    return PJ_SUCCESS;
+}
+
+
+/**
+ * Retrieve user data.
+ */
+PJ_DEF(void*) pj_turn_session_get_user_data(pj_turn_session *sess)
+{
+    return sess->user_data;
+}
+
+
+/*
+ * Configure message logging. By default all flags are enabled.
+ *
+ * @param sess		The TURN client session.
+ * @param flags		Bitmask combination of #pj_stun_sess_msg_log_flag
+ */
+PJ_DEF(void) pj_turn_session_set_log( pj_turn_session *sess,
+				      unsigned flags)
+{
+    pj_stun_session_set_log(sess->stun, flags);
+}
+
+
+/*
+ * Set software name
+ */
+PJ_DEF(pj_status_t) pj_turn_session_set_software_name( pj_turn_session *sess,
+						       const pj_str_t *sw)
+{
+    pj_status_t status;
+
+    pj_grp_lock_acquire(sess->grp_lock);
+    status = pj_stun_session_set_software_name(sess->stun, sw);
+    pj_grp_lock_release(sess->grp_lock);
+
+    return status;
+}
+
+
+/**
+ * Set the server or domain name of the server.
+ */
+PJ_DEF(pj_status_t) pj_turn_session_set_server( pj_turn_session *sess,
+					        const pj_str_t *domain,
+						int default_port,
+						pj_dns_resolver *resolver)
+{
+    pj_sockaddr tmp_addr;
+    pj_bool_t is_ip_addr;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(sess && domain, PJ_EINVAL);
+    PJ_ASSERT_RETURN(sess->state == PJ_TURN_STATE_NULL, PJ_EINVALIDOP);
+
+    pj_grp_lock_acquire(sess->grp_lock);
+
+    /* See if "domain" contains just IP address */
+    tmp_addr.addr.sa_family = sess->af;
+    status = pj_inet_pton(sess->af, domain, 
+			  pj_sockaddr_get_addr(&tmp_addr));
+    is_ip_addr = (status == PJ_SUCCESS);
+
+    if (!is_ip_addr && resolver) {
+	/* Resolve with DNS SRV resolution, and fallback to DNS A resolution
+	 * if default_port is specified.
+	 */
+	unsigned opt = 0;
+	pj_str_t res_name;
+
+	switch (sess->conn_type) {
+	case PJ_TURN_TP_UDP:
+	    res_name = pj_str("_turn._udp.");
+	    break;
+	case PJ_TURN_TP_TCP:
+	    res_name = pj_str("_turn._tcp.");
+	    break;
+	case PJ_TURN_TP_TLS:
+	    res_name = pj_str("_turns._tcp.");
+	    break;
+	default:
+	    status = PJNATH_ETURNINTP;
+	    goto on_return;
+	}
+
+	/* Fallback to DNS A only if default port is specified */
+	if (default_port>0 && default_port<65536) {
+	    opt = PJ_DNS_SRV_FALLBACK_A;
+	    sess->default_port = (pj_uint16_t)default_port;
+	}
+
+	PJ_LOG(5,(sess->obj_name, "Resolving %.*s%.*s with DNS SRV",
+		  (int)res_name.slen, res_name.ptr,
+		  (int)domain->slen, domain->ptr));
+	set_state(sess, PJ_TURN_STATE_RESOLVING);
+
+	/* User may have destroyed us in the callback */
+	if (sess->state != PJ_TURN_STATE_RESOLVING) {
+	    status = PJ_ECANCELLED;
+	    goto on_return;
+	}
+
+	status = pj_dns_srv_resolve(domain, &res_name, default_port, 
+				    sess->pool, resolver, opt, sess, 
+				    &dns_srv_resolver_cb, NULL);
+	if (status != PJ_SUCCESS) {
+	    set_state(sess, PJ_TURN_STATE_NULL);
+	    goto on_return;
+	}
+
+    } else {
+	/* Resolver is not specified, resolve with standard gethostbyname().
+	 * The default_port MUST be specified in this case.
+	 */
+	pj_addrinfo *ai;
+	unsigned i, cnt;
+
+	/* Default port must be specified */
+	PJ_ASSERT_RETURN(default_port>0 && default_port<65536, PJ_EINVAL);
+	sess->default_port = (pj_uint16_t)default_port;
+
+	cnt = PJ_TURN_MAX_DNS_SRV_CNT;
+	ai = (pj_addrinfo*)
+	     pj_pool_calloc(sess->pool, cnt, sizeof(pj_addrinfo));
+
+	PJ_LOG(5,(sess->obj_name, "Resolving %.*s with DNS A",
+		  (int)domain->slen, domain->ptr));
+	set_state(sess, PJ_TURN_STATE_RESOLVING);
+
+	/* User may have destroyed us in the callback */
+	if (sess->state != PJ_TURN_STATE_RESOLVING) {
+	    status = PJ_ECANCELLED;
+	    goto on_return;
+	}
+
+	status = pj_getaddrinfo(sess->af, domain, &cnt, ai);
+	if (status != PJ_SUCCESS)
+	    goto on_return;
+
+	sess->srv_addr_cnt = (pj_uint16_t)cnt;
+	sess->srv_addr_list = (pj_sockaddr*)
+		              pj_pool_calloc(sess->pool, cnt, 
+					     sizeof(pj_sockaddr));
+	for (i=0; i<cnt; ++i) {
+	    pj_sockaddr *addr = &sess->srv_addr_list[i];
+	    pj_memcpy(addr, &ai[i].ai_addr, sizeof(pj_sockaddr));
+	    addr->addr.sa_family = sess->af;
+	    addr->ipv4.sin_port = pj_htons(sess->default_port);
+	}
+
+	sess->srv_addr = &sess->srv_addr_list[0];
+	set_state(sess, PJ_TURN_STATE_RESOLVED);
+    }
+
+on_return:
+    pj_grp_lock_release(sess->grp_lock);
+    return status;
+}
+
+
+/**
+ * Set credential to be used by the session.
+ */
+PJ_DEF(pj_status_t) pj_turn_session_set_credential(pj_turn_session *sess,
+					     const pj_stun_auth_cred *cred)
+{
+    PJ_ASSERT_RETURN(sess && cred, PJ_EINVAL);
+    PJ_ASSERT_RETURN(sess->stun, PJ_EINVALIDOP);
+
+    pj_grp_lock_acquire(sess->grp_lock);
+
+    pj_stun_session_set_credential(sess->stun, PJ_STUN_AUTH_LONG_TERM, cred);
+
+    pj_grp_lock_release(sess->grp_lock);
+
+    return PJ_SUCCESS;
+}
+
+
+/**
+ * Create TURN allocation.
+ */
+PJ_DEF(pj_status_t) pj_turn_session_alloc(pj_turn_session *sess,
+					  const pj_turn_alloc_param *param)
+{
+    pj_stun_tx_data *tdata;
+    pj_bool_t retransmit;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(sess, PJ_EINVAL);
+    PJ_ASSERT_RETURN(sess->state>PJ_TURN_STATE_NULL && 
+		     sess->state<=PJ_TURN_STATE_RESOLVED, 
+		     PJ_EINVALIDOP);
+
+    pj_grp_lock_acquire(sess->grp_lock);
+
+    if (param && param != &sess->alloc_param) 
+	pj_turn_alloc_param_copy(sess->pool, &sess->alloc_param, param);
+
+    if (sess->state < PJ_TURN_STATE_RESOLVED) {
+	sess->pending_alloc = PJ_TRUE;
+
+	PJ_LOG(4,(sess->obj_name, "Pending ALLOCATE in state %s",
+		  state_names[sess->state]));
+
+	pj_grp_lock_release(sess->grp_lock);
+	return PJ_SUCCESS;
+
+    }
+
+    /* Ready to allocate */
+    pj_assert(sess->state == PJ_TURN_STATE_RESOLVED);
+    
+    /* Create a bare request */
+    status = pj_stun_session_create_req(sess->stun, PJ_STUN_ALLOCATE_REQUEST,
+					PJ_STUN_MAGIC, NULL, &tdata);
+    if (status != PJ_SUCCESS) {
+	pj_grp_lock_release(sess->grp_lock);
+	return status;
+    }
+
+    /* MUST include REQUESTED-TRANSPORT attribute */
+    pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg,
+			      PJ_STUN_ATTR_REQ_TRANSPORT, 
+			      PJ_STUN_SET_RT_PROTO(PJ_TURN_TP_UDP));
+
+    /* Include BANDWIDTH if requested */
+    if (sess->alloc_param.bandwidth > 0) {
+	pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg,
+				  PJ_STUN_ATTR_BANDWIDTH,
+				  sess->alloc_param.bandwidth);
+    }
+
+    /* Include LIFETIME if requested */
+    if (sess->alloc_param.lifetime > 0) {
+	pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg,
+				  PJ_STUN_ATTR_LIFETIME,
+				  sess->alloc_param.lifetime);
+    }
+
+    /* Server address must be set */
+    pj_assert(sess->srv_addr != NULL);
+
+    /* Send request */
+    set_state(sess, PJ_TURN_STATE_ALLOCATING);
+    retransmit = (sess->conn_type == PJ_TURN_TP_UDP);
+    status = pj_stun_session_send_msg(sess->stun, NULL, PJ_FALSE, 
+				      retransmit, sess->srv_addr,
+				      pj_sockaddr_get_len(sess->srv_addr), 
+				      tdata);
+    if (status != PJ_SUCCESS) {
+	/* Set state back to RESOLVED. We don't want to destroy session now,
+	 * let the application do it if it wants to.
+	 */
+	set_state(sess, PJ_TURN_STATE_RESOLVED);
+    }
+
+    pj_grp_lock_release(sess->grp_lock);
+    return status;
+}
+
+
+/*
+ * Install or renew permissions
+ */
+PJ_DEF(pj_status_t) pj_turn_session_set_perm( pj_turn_session *sess,
+					      unsigned addr_cnt,
+					      const pj_sockaddr addr[],
+					      unsigned options)
+{
+    pj_stun_tx_data *tdata;
+    pj_hash_iterator_t it_buf, *it;
+    void *req_token;
+    unsigned i, attr_added=0;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(sess && addr_cnt && addr, PJ_EINVAL);
+
+    pj_grp_lock_acquire(sess->grp_lock);
+
+    /* Create a bare CreatePermission request */
+    status = pj_stun_session_create_req(sess->stun, 
+					PJ_STUN_CREATE_PERM_REQUEST,
+					PJ_STUN_MAGIC, NULL, &tdata);
+    if (status != PJ_SUCCESS) {
+	pj_grp_lock_release(sess->grp_lock);
+	return status;
+    }
+
+    /* Create request token to map the request to the perm structures
+     * which the request belongs.
+     */
+    req_token = (void*)(pj_ssize_t)pj_rand();
+
+    /* Process the addresses */
+    for (i=0; i<addr_cnt; ++i) {
+	struct perm_t *perm;
+
+	/* Lookup the perm structure and create if it doesn't exist */
+	perm = lookup_perm(sess, &addr[i], pj_sockaddr_get_len(&addr[i]),
+			   PJ_TRUE);
+	perm->renew = (options & 0x01);
+
+	/* Only add to the request if the request doesn't contain this
+	 * address yet.
+	 */
+	if (perm->req_token != req_token) {
+	    perm->req_token = req_token;
+
+	    /* Add XOR-PEER-ADDRESS */
+	    status = pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg,
+						   PJ_STUN_ATTR_XOR_PEER_ADDR,
+						   PJ_TRUE,
+						   &addr[i],
+						   sizeof(addr[i]));
+	    if (status != PJ_SUCCESS)
+		goto on_error;
+
+	    ++attr_added;
+	}
+    }
+
+    pj_assert(attr_added != 0);
+
+    /* Send the request */
+    status = pj_stun_session_send_msg(sess->stun, req_token, PJ_FALSE, 
+				      (sess->conn_type==PJ_TURN_TP_UDP),
+				      sess->srv_addr,
+				      pj_sockaddr_get_len(sess->srv_addr), 
+				      tdata);
+    if (status != PJ_SUCCESS) {
+	/* tdata is already destroyed */
+	tdata = NULL;
+	goto on_error;
+    }
+
+    pj_grp_lock_release(sess->grp_lock);
+    return PJ_SUCCESS;
+
+on_error:
+    /* destroy tdata */
+    if (tdata) {
+	pj_stun_msg_destroy_tdata(sess->stun, tdata);
+    }
+    /* invalidate perm structures associated with this request */
+    it = pj_hash_first(sess->perm_table, &it_buf);
+    while (it) {
+	struct perm_t *perm = (struct perm_t*)
+			      pj_hash_this(sess->perm_table, it);
+	it = pj_hash_next(sess->perm_table, it);
+	if (perm->req_token == req_token)
+	    invalidate_perm(sess, perm);
+    }
+    pj_grp_lock_release(sess->grp_lock);
+    return status;
+}
+
+/*
+ * Send REFRESH
+ */
+static void send_refresh(pj_turn_session *sess, int lifetime)
+{
+    pj_stun_tx_data *tdata;
+    pj_status_t status;
+
+    PJ_ASSERT_ON_FAIL(sess->state==PJ_TURN_STATE_READY, return);
+
+    /* Create a bare REFRESH request */
+    status = pj_stun_session_create_req(sess->stun, PJ_STUN_REFRESH_REQUEST,
+					PJ_STUN_MAGIC, NULL, &tdata);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    /* Add LIFETIME */
+    if (lifetime >= 0) {
+	pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg,
+				  PJ_STUN_ATTR_LIFETIME, lifetime);
+    }
+
+    /* Send request */
+    if (lifetime == 0) {
+	set_state(sess, PJ_TURN_STATE_DEALLOCATING);
+    }
+
+    status = pj_stun_session_send_msg(sess->stun, NULL, PJ_FALSE, 
+				      (sess->conn_type==PJ_TURN_TP_UDP),
+				      sess->srv_addr,
+				      pj_sockaddr_get_len(sess->srv_addr), 
+				      tdata);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    return;
+
+on_error:
+    if (lifetime == 0) {
+	set_state(sess, PJ_TURN_STATE_DEALLOCATED);
+	sess_shutdown(sess, status);
+    }
+}
+
+
+/**
+ * Relay data to the specified peer through the session.
+ */
+PJ_DEF(pj_status_t) pj_turn_session_sendto( pj_turn_session *sess,
+					    const pj_uint8_t *pkt,
+					    unsigned pkt_len,
+					    const pj_sockaddr_t *addr,
+					    unsigned addr_len)
+{
+    struct ch_t *ch;
+    struct perm_t *perm;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(sess && pkt && pkt_len && addr && addr_len, 
+		     PJ_EINVAL);
+
+    /* Return error if we're not ready */
+    if (sess->state != PJ_TURN_STATE_READY) {
+	return PJ_EIGNORED;
+    }
+
+    /* Lock session now */
+    pj_grp_lock_acquire(sess->grp_lock);
+
+    /* Lookup permission first */
+    perm = lookup_perm(sess, addr, pj_sockaddr_get_len(addr), PJ_FALSE);
+    if (perm == NULL) {
+	/* Permission doesn't exist, install it first */
+	char ipstr[PJ_INET6_ADDRSTRLEN+2];
+
+	PJ_LOG(4,(sess->obj_name, 
+		  "sendto(): IP %s has no permission, requesting it first..",
+		  pj_sockaddr_print(addr, ipstr, sizeof(ipstr), 2)));
+
+	status = pj_turn_session_set_perm(sess, 1, (const pj_sockaddr*)addr, 
+					  0);
+	if (status != PJ_SUCCESS) {
+	    pj_grp_lock_release(sess->grp_lock);
+	    return status;
+	}
+    }
+
+    /* See if the peer is bound to a channel number */
+    ch = lookup_ch_by_addr(sess, addr, pj_sockaddr_get_len(addr), 
+			   PJ_FALSE, PJ_FALSE);
+    if (ch && ch->num != PJ_TURN_INVALID_CHANNEL && ch->bound) {
+	unsigned total_len;
+
+	/* Peer is assigned a channel number, we can use ChannelData */
+	pj_turn_channel_data *cd = (pj_turn_channel_data*)sess->tx_pkt;
+	
+	pj_assert(sizeof(*cd)==4);
+
+	/* Calculate total length, including paddings */
+	total_len = (pkt_len + sizeof(*cd) + 3) & (~3);
+	if (total_len > sizeof(sess->tx_pkt)) {
+	    status = PJ_ETOOBIG;
+	    goto on_return;
+	}
+
+	cd->ch_number = pj_htons((pj_uint16_t)ch->num);
+	cd->length = pj_htons((pj_uint16_t)pkt_len);
+	pj_memcpy(cd+1, pkt, pkt_len);
+
+	pj_assert(sess->srv_addr != NULL);
+
+	status = sess->cb.on_send_pkt(sess, sess->tx_pkt, total_len,
+				      sess->srv_addr,
+				      pj_sockaddr_get_len(sess->srv_addr));
+
+    } else {
+	/* Use Send Indication. */
+	pj_stun_sockaddr_attr peer_attr;
+	pj_stun_binary_attr data_attr;
+	pj_stun_msg send_ind;
+	pj_size_t send_ind_len;
+
+	/* Increment counter */
+	++sess->send_ind_tsx_id[2];
+
+	/* Create blank SEND-INDICATION */
+	status = pj_stun_msg_init(&send_ind, PJ_STUN_SEND_INDICATION,
+				  PJ_STUN_MAGIC, 
+				  (const pj_uint8_t*)sess->send_ind_tsx_id);
+	if (status != PJ_SUCCESS)
+	    goto on_return;
+
+	/* Add XOR-PEER-ADDRESS */
+	pj_stun_sockaddr_attr_init(&peer_attr, PJ_STUN_ATTR_XOR_PEER_ADDR,
+				   PJ_TRUE, addr, addr_len);
+	pj_stun_msg_add_attr(&send_ind, (pj_stun_attr_hdr*)&peer_attr);
+
+	/* Add DATA attribute */
+	pj_stun_binary_attr_init(&data_attr, NULL, PJ_STUN_ATTR_DATA, NULL, 0);
+	data_attr.data = (pj_uint8_t*)pkt;
+	data_attr.length = pkt_len;
+	pj_stun_msg_add_attr(&send_ind, (pj_stun_attr_hdr*)&data_attr);
+
+	/* Encode the message */
+	status = pj_stun_msg_encode(&send_ind, sess->tx_pkt, 
+				    sizeof(sess->tx_pkt), 0,
+				    NULL, &send_ind_len);
+	if (status != PJ_SUCCESS)
+	    goto on_return;
+
+	/* Send the Send Indication */
+	status = sess->cb.on_send_pkt(sess, sess->tx_pkt, 
+				      (unsigned)send_ind_len,
+				      sess->srv_addr,
+				      pj_sockaddr_get_len(sess->srv_addr));
+    }
+
+on_return:
+    pj_grp_lock_release(sess->grp_lock);
+    return status;
+}
+
+
+/**
+ * Bind a peer address to a channel number.
+ */
+PJ_DEF(pj_status_t) pj_turn_session_bind_channel(pj_turn_session *sess,
+						 const pj_sockaddr_t *peer_adr,
+						 unsigned addr_len)
+{
+    struct ch_t *ch;
+    pj_stun_tx_data *tdata;
+    pj_uint16_t ch_num;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(sess && peer_adr && addr_len, PJ_EINVAL);
+    PJ_ASSERT_RETURN(sess->state == PJ_TURN_STATE_READY, PJ_EINVALIDOP);
+
+    pj_grp_lock_acquire(sess->grp_lock);
+
+    /* Create blank ChannelBind request */
+    status = pj_stun_session_create_req(sess->stun, 
+					PJ_STUN_CHANNEL_BIND_REQUEST,
+					PJ_STUN_MAGIC, NULL, &tdata);
+    if (status != PJ_SUCCESS)
+	goto on_return;
+
+    /* Lookup if this peer has already been assigned a number */
+    ch = lookup_ch_by_addr(sess, peer_adr, pj_sockaddr_get_len(peer_adr),
+			   PJ_TRUE, PJ_FALSE);
+    pj_assert(ch);
+
+    if (ch->num != PJ_TURN_INVALID_CHANNEL) {
+	/* Channel is already bound. This is a refresh request. */
+	ch_num = ch->num;
+    } else {
+	PJ_ASSERT_ON_FAIL(sess->next_ch <= PJ_TURN_CHANNEL_MAX, 
+			    {status=PJ_ETOOMANY; goto on_return;});
+	ch->num = ch_num = sess->next_ch++;
+    }
+
+    /* Add CHANNEL-NUMBER attribute */
+    pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg,
+			      PJ_STUN_ATTR_CHANNEL_NUMBER,
+			      PJ_STUN_SET_CH_NB(ch_num));
+
+    /* Add XOR-PEER-ADDRESS attribute */
+    pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg,
+				  PJ_STUN_ATTR_XOR_PEER_ADDR, PJ_TRUE,
+				  peer_adr, addr_len);
+
+    /* Send the request, associate peer data structure with tdata 
+     * for future reference when we receive the ChannelBind response.
+     */
+    status = pj_stun_session_send_msg(sess->stun, ch, PJ_FALSE, 
+				      (sess->conn_type==PJ_TURN_TP_UDP),
+				      sess->srv_addr,
+				      pj_sockaddr_get_len(sess->srv_addr),
+				      tdata);
+
+on_return:
+    pj_grp_lock_release(sess->grp_lock);
+    return status;
+}
+
+
+/**
+ * Notify TURN client session upon receiving a packet from server.
+ * The packet maybe a STUN packet or ChannelData packet.
+ */
+PJ_DEF(pj_status_t) pj_turn_session_on_rx_pkt(pj_turn_session *sess,
+					      void *pkt,
+					      pj_size_t pkt_len,
+					      pj_size_t *parsed_len)
+{
+    pj_bool_t is_stun;
+    pj_status_t status;
+    pj_bool_t is_datagram;
+
+    /* Packet could be ChannelData or STUN message (response or
+     * indication).
+     */
+
+    /* Start locking the session */
+    pj_grp_lock_acquire(sess->grp_lock);
+
+    is_datagram = (sess->conn_type==PJ_TURN_TP_UDP);
+
+    /* Quickly check if this is STUN message */
+    is_stun = ((((pj_uint8_t*)pkt)[0] & 0xC0) == 0);
+
+    if (is_stun) {
+	/* This looks like STUN, give it to the STUN session */
+	unsigned options;
+
+	options = PJ_STUN_CHECK_PACKET | PJ_STUN_NO_FINGERPRINT_CHECK;
+	if (is_datagram)
+	    options |= PJ_STUN_IS_DATAGRAM;
+	status=pj_stun_session_on_rx_pkt(sess->stun, pkt, pkt_len,
+					 options, NULL, parsed_len,
+					 sess->srv_addr,
+					 pj_sockaddr_get_len(sess->srv_addr));
+
+    } else {
+	/* This must be ChannelData. */
+	pj_turn_channel_data cd;
+	struct ch_t *ch;
+
+	if (pkt_len < 4) {
+	    if (parsed_len) *parsed_len = 0;
+	    return PJ_ETOOSMALL;
+	}
+
+	/* Decode ChannelData packet */
+	pj_memcpy(&cd, pkt, sizeof(pj_turn_channel_data));
+	cd.ch_number = pj_ntohs(cd.ch_number);
+	cd.length = pj_ntohs(cd.length);
+
+	/* Check that size is sane */
+	if (pkt_len < cd.length+sizeof(cd)) {
+	    if (parsed_len) {
+		if (is_datagram) {
+		    /* Discard the datagram */
+		    *parsed_len = pkt_len;
+		} else {
+		    /* Insufficient fragment */
+		    *parsed_len = 0;
+		}
+	    }
+	    status = PJ_ETOOSMALL;
+	    goto on_return;
+	} else {
+	    if (parsed_len) {
+		/* Apply padding too */
+		*parsed_len = ((cd.length + 3) & (~3)) + sizeof(cd);
+	    }
+	}
+
+	/* Lookup channel */
+	ch = lookup_ch_by_chnum(sess, cd.ch_number);
+	if (!ch || !ch->bound) {
+	    status = PJ_ENOTFOUND;
+	    goto on_return;
+	}
+
+	/* Notify application */
+	if (sess->cb.on_rx_data) {
+	    (*sess->cb.on_rx_data)(sess, ((pj_uint8_t*)pkt)+sizeof(cd), 
+				   cd.length, &ch->addr,
+				   pj_sockaddr_get_len(&ch->addr));
+	}
+
+	status = PJ_SUCCESS;
+    }
+
+on_return:
+    pj_grp_lock_release(sess->grp_lock);
+    return status;
+}
+
+
+/*
+ * This is a callback from STUN session to send outgoing packet.
+ */
+static pj_status_t stun_on_send_msg(pj_stun_session *stun,
+				    void *token,
+				    const void *pkt,
+				    pj_size_t pkt_size,
+				    const pj_sockaddr_t *dst_addr,
+				    unsigned addr_len)
+{
+    pj_turn_session *sess;
+
+    PJ_UNUSED_ARG(token);
+
+    sess = (pj_turn_session*) pj_stun_session_get_user_data(stun);
+    return (*sess->cb.on_send_pkt)(sess, (const pj_uint8_t*)pkt, 
+				   (unsigned)pkt_size, 
+				   dst_addr, addr_len);
+}
+
+
+/*
+ * Handle failed ALLOCATE or REFRESH request. This may switch to alternate
+ * server if we have one.
+ */
+static void on_session_fail( pj_turn_session *sess, 
+			     enum pj_stun_method_e method,
+			     pj_status_t status,
+			     const pj_str_t *reason)
+{
+    sess->last_status = status;
+
+    do {
+	pj_str_t reason1;
+	char err_msg[PJ_ERR_MSG_SIZE];
+
+	if (reason == NULL) {
+	    pj_strerror(status, err_msg, sizeof(err_msg));
+	    reason1 = pj_str(err_msg);
+	    reason = &reason1;
+	}
+
+	PJ_LOG(4,(sess->obj_name, "%s error: %.*s",
+		  pj_stun_get_method_name(method),
+		  (int)reason->slen, reason->ptr));
+
+	/* If this is ALLOCATE response and we don't have more server 
+	 * addresses to try, notify application and destroy the TURN
+	 * session.
+	 */
+	if (method==PJ_STUN_ALLOCATE_METHOD &&
+	    sess->srv_addr == &sess->srv_addr_list[sess->srv_addr_cnt-1]) 
+	{
+
+	    set_state(sess, PJ_TURN_STATE_DEALLOCATED);
+	    sess_shutdown(sess, status);
+	    return;
+	}
+
+	/* Otherwise if this is not ALLOCATE response, notify application
+	 * that session has been TERMINATED.
+	 */
+	if (method!=PJ_STUN_ALLOCATE_METHOD) {
+	    set_state(sess, PJ_TURN_STATE_DEALLOCATED);
+	    sess_shutdown(sess, status);
+	    return;
+	}
+
+	/* Try next server */
+	++sess->srv_addr;
+	reason = NULL;
+
+	PJ_LOG(4,(sess->obj_name, "Trying next server"));
+	set_state(sess, PJ_TURN_STATE_RESOLVED);
+
+    } while (0);
+}
+
+
+/*
+ * Handle successful response to ALLOCATE or REFRESH request.
+ */
+static void on_allocate_success(pj_turn_session *sess, 
+				enum pj_stun_method_e method,
+				const pj_stun_msg *msg)
+{
+    const pj_stun_lifetime_attr *lf_attr;
+    const pj_stun_xor_relayed_addr_attr *raddr_attr;
+    const pj_stun_sockaddr_attr *mapped_attr;
+    pj_str_t s;
+    pj_time_val timeout;
+
+    /* Must have LIFETIME attribute */
+    lf_attr = (const pj_stun_lifetime_attr*)
+	      pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_LIFETIME, 0);
+    if (lf_attr == NULL) {
+	on_session_fail(sess, method, PJNATH_EINSTUNMSG,
+			pj_cstr(&s, "Error: Missing LIFETIME attribute"));
+	return;
+    }
+
+    /* If LIFETIME is zero, this is a deallocation */
+    if (lf_attr->value == 0) {
+	set_state(sess, PJ_TURN_STATE_DEALLOCATED);
+	sess_shutdown(sess, PJ_SUCCESS);
+	return;
+    }
+
+    /* Update lifetime and keep-alive interval */
+    sess->lifetime = lf_attr->value;
+    pj_gettimeofday(&sess->expiry);
+
+    if (sess->lifetime < PJ_TURN_KEEP_ALIVE_SEC) {
+	if (sess->lifetime <= 2) {
+	    on_session_fail(sess, method, PJ_ETOOSMALL,
+			     pj_cstr(&s, "Error: LIFETIME too small"));
+	    return;
+	}
+	sess->ka_interval = sess->lifetime - 2;
+	sess->expiry.sec += (sess->ka_interval-1);
+    } else {
+	int timeout;
+
+	sess->ka_interval = PJ_TURN_KEEP_ALIVE_SEC;
+
+	timeout = sess->lifetime - PJ_TURN_REFRESH_SEC_BEFORE;
+	if (timeout < sess->ka_interval)
+	    timeout = sess->ka_interval - 1;
+
+	sess->expiry.sec += timeout;
+    }
+
+    /* Check that relayed transport address contains correct
+     * address family.
+     */
+    raddr_attr = (const pj_stun_xor_relayed_addr_attr*)
+		 pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_XOR_RELAYED_ADDR, 0);
+    if (raddr_attr == NULL && method==PJ_STUN_ALLOCATE_METHOD) {
+	on_session_fail(sess, method, PJNATH_EINSTUNMSG,
+		        pj_cstr(&s, "Error: Received ALLOCATE without "
+				    "RELAY-ADDRESS attribute"));
+	return;
+    }
+    if (raddr_attr && raddr_attr->sockaddr.addr.sa_family != sess->af) {
+	on_session_fail(sess, method, PJNATH_EINSTUNMSG,
+			pj_cstr(&s, "Error: RELAY-ADDRESS with non IPv4"
+				    " address family is not supported "
+				    "for now"));
+	return;
+    }
+    if (raddr_attr && !pj_sockaddr_has_addr(&raddr_attr->sockaddr)) {
+	on_session_fail(sess, method, PJNATH_EINSTUNMSG,
+			pj_cstr(&s, "Error: Invalid IP address in "
+				    "RELAY-ADDRESS attribute"));
+	return;
+    }
+    
+    /* Save relayed address */
+    if (raddr_attr) {
+	/* If we already have relay address, check if the relay address 
+	 * in the response matches our relay address.
+	 */
+	if (pj_sockaddr_has_addr(&sess->relay_addr)) {
+	    if (pj_sockaddr_cmp(&sess->relay_addr, &raddr_attr->sockaddr)) {
+		on_session_fail(sess, method, PJNATH_EINSTUNMSG,
+				pj_cstr(&s, "Error: different RELAY-ADDRESS is"
+					    "returned by server"));
+		return;
+	    }
+	} else {
+	    /* Otherwise save the relayed address */
+	    pj_memcpy(&sess->relay_addr, &raddr_attr->sockaddr, 
+		      sizeof(pj_sockaddr));
+	}
+    }
+
+    /* Get mapped address */
+    mapped_attr = (const pj_stun_sockaddr_attr*)
+		  pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_XOR_MAPPED_ADDR, 0);
+    if (mapped_attr) {
+	pj_memcpy(&sess->mapped_addr, &mapped_attr->sockaddr,
+		  sizeof(mapped_attr->sockaddr));
+    }
+
+    /* Success */
+
+    /* Cancel existing keep-alive timer, if any */
+    pj_assert(sess->timer.id != TIMER_DESTROY);
+    if (sess->timer.id == TIMER_KEEP_ALIVE) {
+	pj_timer_heap_cancel_if_active(sess->timer_heap, &sess->timer,
+				       TIMER_NONE);
+    }
+
+    /* Start keep-alive timer once allocation succeeds */
+    if (sess->state < PJ_TURN_STATE_DEALLOCATING) {
+	timeout.sec = sess->ka_interval;
+	timeout.msec = 0;
+
+	pj_timer_heap_schedule_w_grp_lock(sess->timer_heap, &sess->timer,
+					  &timeout, TIMER_KEEP_ALIVE,
+					  sess->grp_lock);
+
+	set_state(sess, PJ_TURN_STATE_READY);
+    }
+}
+
+/*
+ * Notification from STUN session on request completion.
+ */
+static void stun_on_request_complete(pj_stun_session *stun,
+				     pj_status_t status,
+				     void *token,
+				     pj_stun_tx_data *tdata,
+				     const pj_stun_msg *response,
+				     const pj_sockaddr_t *src_addr,
+				     unsigned src_addr_len)
+{
+    pj_turn_session *sess;
+    enum pj_stun_method_e method = (enum pj_stun_method_e) 
+			      	   PJ_STUN_GET_METHOD(tdata->msg->hdr.type);
+
+    PJ_UNUSED_ARG(src_addr);
+    PJ_UNUSED_ARG(src_addr_len);
+
+    sess = (pj_turn_session*)pj_stun_session_get_user_data(stun);
+
+    if (method == PJ_STUN_ALLOCATE_METHOD) {
+
+	/* Destroy if we have pending destroy request */
+	if (sess->pending_destroy) {
+	    if (status == PJ_SUCCESS)
+		sess->state = PJ_TURN_STATE_READY;
+	    else
+		sess->state = PJ_TURN_STATE_DEALLOCATED;
+	    sess_shutdown(sess, PJ_SUCCESS);
+	    return;
+	}
+
+	/* Handle ALLOCATE response */
+	if (status==PJ_SUCCESS && 
+	    PJ_STUN_IS_SUCCESS_RESPONSE(response->hdr.type)) 
+	{
+
+	    /* Successful Allocate response */
+	    on_allocate_success(sess, method, response);
+
+	} else {
+	    /* Failed Allocate request */
+	    const pj_str_t *err_msg = NULL;
+
+	    if (status == PJ_SUCCESS) {
+		const pj_stun_errcode_attr *err_attr;
+		err_attr = (const pj_stun_errcode_attr*)
+			   pj_stun_msg_find_attr(response,
+						 PJ_STUN_ATTR_ERROR_CODE, 0);
+		if (err_attr) {
+		    status = PJ_STATUS_FROM_STUN_CODE(err_attr->err_code);
+		    err_msg = &err_attr->reason;
+		} else {
+		    status = PJNATH_EINSTUNMSG;
+		}
+	    }
+
+	    on_session_fail(sess, method, status, err_msg);
+	}
+
+    } else if (method == PJ_STUN_REFRESH_METHOD) {
+	/* Handle Refresh response */
+	if (status==PJ_SUCCESS && 
+	    PJ_STUN_IS_SUCCESS_RESPONSE(response->hdr.type)) 
+	{
+	    /* Success, schedule next refresh. */
+	    on_allocate_success(sess, method, response);
+
+	} else {
+	    /* Failed Refresh request */
+	    const pj_str_t *err_msg = NULL;
+
+	    pj_assert(status != PJ_SUCCESS);
+
+	    if (response) {
+		const pj_stun_errcode_attr *err_attr;
+		err_attr = (const pj_stun_errcode_attr*)
+			   pj_stun_msg_find_attr(response,
+						 PJ_STUN_ATTR_ERROR_CODE, 0);
+		if (err_attr) {
+		    status = PJ_STATUS_FROM_STUN_CODE(err_attr->err_code);
+		    err_msg = &err_attr->reason;
+		}
+	    }
+
+	    /* Notify and destroy */
+	    on_session_fail(sess, method, status, err_msg);
+	}
+
+    } else if (method == PJ_STUN_CHANNEL_BIND_METHOD) {
+	/* Handle ChannelBind response */
+	if (status==PJ_SUCCESS && 
+	    PJ_STUN_IS_SUCCESS_RESPONSE(response->hdr.type)) 
+	{
+	    /* Successful ChannelBind response */
+	    struct ch_t *ch = (struct ch_t*)token;
+
+	    pj_assert(ch->num != PJ_TURN_INVALID_CHANNEL);
+	    ch->bound = PJ_TRUE;
+
+	    /* Update hash table */
+	    lookup_ch_by_addr(sess, &ch->addr,
+			      pj_sockaddr_get_len(&ch->addr),
+			      PJ_TRUE, PJ_TRUE);
+
+	} else {
+	    /* Failed ChannelBind response */
+	    pj_str_t reason = {"", 0};
+	    int err_code = 0;
+	    char errbuf[PJ_ERR_MSG_SIZE];
+
+	    pj_assert(status != PJ_SUCCESS);
+
+	    if (response) {
+		const pj_stun_errcode_attr *err_attr;
+		err_attr = (const pj_stun_errcode_attr*)
+			   pj_stun_msg_find_attr(response,
+						 PJ_STUN_ATTR_ERROR_CODE, 0);
+		if (err_attr) {
+		    err_code = err_attr->err_code;
+		    status = PJ_STATUS_FROM_STUN_CODE(err_attr->err_code);
+		    reason = err_attr->reason;
+		}
+	    } else {
+		err_code = status;
+		reason = pj_strerror(status, errbuf, sizeof(errbuf));
+	    }
+
+	    PJ_LOG(1,(sess->obj_name, "ChannelBind failed: %d/%.*s",
+		      err_code, (int)reason.slen, reason.ptr));
+
+	    if (err_code == PJ_STUN_SC_ALLOCATION_MISMATCH) {
+		/* Allocation mismatch means allocation no longer exists */
+		on_session_fail(sess, PJ_STUN_CHANNEL_BIND_METHOD,
+				status, &reason);
+		return;
+	    }
+	}
+
+    } else if (method == PJ_STUN_CREATE_PERM_METHOD) {
+	/* Handle CreatePermission response */
+	if (status==PJ_SUCCESS && 
+	    PJ_STUN_IS_SUCCESS_RESPONSE(response->hdr.type)) 
+	{
+	    /* No special handling when the request is successful. */
+	} else {
+	    /* Iterate the permission table and invalidate all permissions
+	     * that are related to this request.
+	     */
+	    pj_hash_iterator_t it_buf, *it;
+	    char ipstr[PJ_INET6_ADDRSTRLEN+10];
+	    int err_code;
+	    char errbuf[PJ_ERR_MSG_SIZE];
+	    pj_str_t reason;
+
+	    pj_assert(status != PJ_SUCCESS);
+
+	    if (response) {
+		const pj_stun_errcode_attr *eattr;
+
+		eattr = (const pj_stun_errcode_attr*)
+			pj_stun_msg_find_attr(response, 
+					      PJ_STUN_ATTR_ERROR_CODE, 0);
+		if (eattr) {
+		    err_code = eattr->err_code;
+		    reason = eattr->reason;
+		} else {
+		    err_code = -1;
+		    reason = pj_str("?");
+		}
+	    } else {
+		err_code = status;
+		reason = pj_strerror(status, errbuf, sizeof(errbuf));
+	    }
+
+	    it = pj_hash_first(sess->perm_table, &it_buf);
+	    while (it) {
+		struct perm_t *perm = (struct perm_t*)
+				      pj_hash_this(sess->perm_table, it);
+		it = pj_hash_next(sess->perm_table, it);
+
+		if (perm->req_token == token) {
+		    PJ_LOG(1,(sess->obj_name, 
+			      "CreatePermission failed for IP %s: %d/%.*s",
+			      pj_sockaddr_print(&perm->addr, ipstr, 
+						sizeof(ipstr), 2),
+			      err_code, (int)reason.slen, reason.ptr));
+
+		    invalidate_perm(sess, perm);
+		}
+	    }
+
+	    if (err_code == PJ_STUN_SC_ALLOCATION_MISMATCH) {
+		/* Allocation mismatch means allocation no longer exists */
+		on_session_fail(sess, PJ_STUN_CREATE_PERM_METHOD,
+				status, &reason);
+		return;
+	    }
+	}
+
+    } else {
+	PJ_LOG(4,(sess->obj_name, "Unexpected STUN %s response",
+		  pj_stun_get_method_name(response->hdr.type)));
+    }
+}
+
+
+/*
+ * Notification from STUN session on incoming STUN Indication
+ * message.
+ */
+static pj_status_t stun_on_rx_indication(pj_stun_session *stun,
+					 const pj_uint8_t *pkt,
+					 unsigned pkt_len,
+					 const pj_stun_msg *msg,
+					 void *token,
+					 const pj_sockaddr_t *src_addr,
+					 unsigned src_addr_len)
+{
+    pj_turn_session *sess;
+    pj_stun_xor_peer_addr_attr *peer_attr;
+    pj_stun_icmp_attr *icmp;
+    pj_stun_data_attr *data_attr;
+
+    PJ_UNUSED_ARG(token);
+    PJ_UNUSED_ARG(pkt);
+    PJ_UNUSED_ARG(pkt_len);
+    PJ_UNUSED_ARG(src_addr);
+    PJ_UNUSED_ARG(src_addr_len);
+
+    sess = (pj_turn_session*)pj_stun_session_get_user_data(stun);
+
+    /* Expecting Data Indication only */
+    if (msg->hdr.type != PJ_STUN_DATA_INDICATION) {
+	PJ_LOG(4,(sess->obj_name, "Unexpected STUN %s indication",
+		  pj_stun_get_method_name(msg->hdr.type)));
+	return PJ_EINVALIDOP;
+    }
+
+    /* Check if there is ICMP attribute in the message */
+    icmp = (pj_stun_icmp_attr*)
+	   pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ICMP, 0);
+    if (icmp != NULL) {
+	/* This is a forwarded ICMP packet. Ignore it for now */
+	return PJ_SUCCESS;
+    }
+
+    /* Get XOR-PEER-ADDRESS attribute */
+    peer_attr = (pj_stun_xor_peer_addr_attr*)
+		pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_XOR_PEER_ADDR, 0);
+
+    /* Get DATA attribute */
+    data_attr = (pj_stun_data_attr*)
+		pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_DATA, 0);
+
+    /* Must have both XOR-PEER-ADDRESS and DATA attributes */
+    if (!peer_attr || !data_attr) {
+	PJ_LOG(4,(sess->obj_name, 
+		  "Received Data indication with missing attributes"));
+	return PJ_EINVALIDOP;
+    }
+
+    /* Notify application */
+    if (sess->cb.on_rx_data) {
+	(*sess->cb.on_rx_data)(sess, data_attr->data, data_attr->length, 
+			       &peer_attr->sockaddr,
+			       pj_sockaddr_get_len(&peer_attr->sockaddr));
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Notification on completion of DNS SRV resolution.
+ */
+static void dns_srv_resolver_cb(void *user_data,
+				pj_status_t status,
+				const pj_dns_srv_record *rec)
+{
+    pj_turn_session *sess = (pj_turn_session*) user_data;
+    unsigned i, cnt, tot_cnt;
+
+    /* Check failure */
+    if (status != PJ_SUCCESS || sess->pending_destroy) {
+	set_state(sess, PJ_TURN_STATE_DESTROYING);
+	sess_shutdown(sess, status);
+	return;
+    }
+
+    /* Calculate total number of server entries in the response */
+    tot_cnt = 0;
+    for (i=0; i<rec->count; ++i) {
+	tot_cnt += rec->entry[i].server.addr_count;
+    }
+
+    if (tot_cnt > PJ_TURN_MAX_DNS_SRV_CNT)
+	tot_cnt = PJ_TURN_MAX_DNS_SRV_CNT;
+
+    /* Allocate server entries */
+    sess->srv_addr_list = (pj_sockaddr*)
+		           pj_pool_calloc(sess->pool, tot_cnt, 
+					  sizeof(pj_sockaddr));
+
+    /* Copy results to server entries */
+    for (i=0, cnt=0; i<rec->count && cnt<PJ_TURN_MAX_DNS_SRV_CNT; ++i) {
+	unsigned j;
+
+	for (j=0; j<rec->entry[i].server.addr_count && 
+		  cnt<PJ_TURN_MAX_DNS_SRV_CNT; ++j) 
+	{
+	    pj_sockaddr_in *addr = &sess->srv_addr_list[cnt].ipv4;
+
+	    addr->sin_family = sess->af;
+	    addr->sin_port = pj_htons(rec->entry[i].port);
+	    addr->sin_addr.s_addr = rec->entry[i].server.addr[j].s_addr;
+
+	    ++cnt;
+	}
+    }
+    sess->srv_addr_cnt = (pj_uint16_t)cnt;
+
+    /* Set current server */
+    sess->srv_addr = &sess->srv_addr_list[0];
+
+    /* Set state to PJ_TURN_STATE_RESOLVED */
+    set_state(sess, PJ_TURN_STATE_RESOLVED);
+
+    /* Run pending allocation */
+    if (sess->pending_alloc) {
+	pj_turn_session_alloc(sess, NULL);
+    }
+}
+
+
+/*
+ * Lookup peer descriptor from its address.
+ */
+static struct ch_t *lookup_ch_by_addr(pj_turn_session *sess,
+				      const pj_sockaddr_t *addr,
+				      unsigned addr_len,
+				      pj_bool_t update,
+				      pj_bool_t bind_channel)
+{
+    pj_uint32_t hval = 0;
+    struct ch_t *ch;
+
+    ch = (struct ch_t*) 
+	 pj_hash_get(sess->ch_table, addr, addr_len, &hval);
+    if (ch == NULL && update) {
+	ch = PJ_POOL_ZALLOC_T(sess->pool, struct ch_t);
+	ch->num = PJ_TURN_INVALID_CHANNEL;
+	pj_memcpy(&ch->addr, addr, addr_len);
+
+	/* Register by peer address */
+	pj_hash_set(sess->pool, sess->ch_table, &ch->addr, addr_len,
+		    hval, ch);
+    }
+
+    if (ch && update) {
+	pj_gettimeofday(&ch->expiry);
+	ch->expiry.sec += PJ_TURN_PERM_TIMEOUT - sess->ka_interval - 1;
+
+	if (bind_channel) {
+	    pj_uint32_t hval = 0;
+	    /* Register by channel number */
+	    pj_assert(ch->num != PJ_TURN_INVALID_CHANNEL && ch->bound);
+
+	    if (pj_hash_get(sess->ch_table, &ch->num, 
+			    sizeof(ch->num), &hval)==0) {
+		pj_hash_set(sess->pool, sess->ch_table, &ch->num,
+			    sizeof(ch->num), hval, ch);
+	    }
+	}
+    }
+
+    /* Also create/update permission for this destination. Ideally we
+     * should update this when we receive the successful response,
+     * but that would cause duplicate CreatePermission to be sent
+     * during refreshing.
+     */
+    if (ch && update) {
+	lookup_perm(sess, &ch->addr, pj_sockaddr_get_len(&ch->addr), PJ_TRUE);
+    }
+
+    return ch;
+}
+
+
+/*
+ * Lookup channel descriptor from its channel number.
+ */
+static struct ch_t *lookup_ch_by_chnum(pj_turn_session *sess,
+					 pj_uint16_t chnum)
+{
+    return (struct ch_t*) pj_hash_get(sess->ch_table, &chnum, 
+				      sizeof(chnum), NULL);
+}
+
+
+/*
+ * Lookup permission and optionally create if it doesn't exist.
+ */
+static struct perm_t *lookup_perm(pj_turn_session *sess,
+				  const pj_sockaddr_t *addr,
+				  unsigned addr_len,
+				  pj_bool_t update)
+{
+    pj_uint32_t hval = 0;
+    pj_sockaddr perm_addr;
+    struct perm_t *perm;
+
+    /* make sure port number if zero */
+    if (pj_sockaddr_get_port(addr) != 0) {
+	pj_memcpy(&perm_addr, addr, addr_len);
+	pj_sockaddr_set_port(&perm_addr, 0);
+	addr = &perm_addr;
+    }
+
+    /* lookup and create if it doesn't exist and wanted */
+    perm = (struct perm_t*) 
+	   pj_hash_get(sess->perm_table, addr, addr_len, &hval);
+    if (perm == NULL && update) {
+	perm = PJ_POOL_ZALLOC_T(sess->pool, struct perm_t);
+	pj_memcpy(&perm->addr, addr, addr_len);
+	perm->hval = hval;
+
+	pj_hash_set(sess->pool, sess->perm_table, &perm->addr, addr_len,
+		    perm->hval, perm);
+    }
+
+    if (perm && update) {
+	pj_gettimeofday(&perm->expiry);
+	perm->expiry.sec += PJ_TURN_PERM_TIMEOUT - sess->ka_interval - 1;
+
+    }
+
+    return perm;
+}
+
+/*
+ * Delete permission
+ */
+static void invalidate_perm(pj_turn_session *sess,
+			    struct perm_t *perm)
+{
+    pj_hash_set(NULL, sess->perm_table, &perm->addr,
+		pj_sockaddr_get_len(&perm->addr), perm->hval, NULL);
+}
+
+/*
+ * Scan permission's hash table to refresh the permission.
+ */
+static unsigned refresh_permissions(pj_turn_session *sess, 
+				    const pj_time_val *now)
+{
+    pj_stun_tx_data *tdata = NULL;
+    unsigned count = 0;
+    void *req_token = NULL;
+    pj_hash_iterator_t *it, itbuf;
+    pj_status_t status;
+
+    it = pj_hash_first(sess->perm_table, &itbuf);
+    while (it) {
+	struct perm_t *perm = (struct perm_t*)
+			      pj_hash_this(sess->perm_table, it);
+
+	it = pj_hash_next(sess->perm_table, it);
+
+	if (perm->expiry.sec-1 <= now->sec) {
+	    if (perm->renew) {
+		/* Renew this permission */
+		if (tdata == NULL) {
+		    /* Create a bare CreatePermission request */
+		    status = pj_stun_session_create_req(
+					sess->stun, 
+					PJ_STUN_CREATE_PERM_REQUEST,
+					PJ_STUN_MAGIC, NULL, &tdata);
+		    if (status != PJ_SUCCESS) {
+			PJ_LOG(1,(sess->obj_name, 
+				 "Error creating CreatePermission request: %d",
+				 status));
+			return 0;
+		    }
+
+		    /* Create request token to map the request to the perm
+		     * structures which the request belongs.
+		     */
+		    req_token = (void*)(pj_ssize_t)pj_rand();
+		}
+
+		status = pj_stun_msg_add_sockaddr_attr(
+					tdata->pool, 
+					tdata->msg,
+					PJ_STUN_ATTR_XOR_PEER_ADDR,
+					PJ_TRUE,
+					&perm->addr,
+					sizeof(perm->addr));
+		if (status != PJ_SUCCESS) {
+		    pj_stun_msg_destroy_tdata(sess->stun, tdata);
+		    return 0;
+		}
+
+		perm->expiry = *now;
+		perm->expiry.sec += PJ_TURN_PERM_TIMEOUT-sess->ka_interval-1;
+		perm->req_token = req_token;
+		++count;
+
+	    } else {
+		/* This permission has expired and app doesn't want
+		 * us to renew, so delete it from the hash table.
+		 */
+		invalidate_perm(sess, perm);
+	    }
+	}
+    }
+
+    if (tdata) {
+	status = pj_stun_session_send_msg(sess->stun, req_token, PJ_FALSE, 
+					  (sess->conn_type==PJ_TURN_TP_UDP),
+					  sess->srv_addr,
+					  pj_sockaddr_get_len(sess->srv_addr), 
+					  tdata);
+	if (status != PJ_SUCCESS) {
+	    PJ_LOG(1,(sess->obj_name, 
+		      "Error sending CreatePermission request: %d",
+		      status));
+	    count = 0;
+	}
+
+    }
+
+    return count;
+}
+
+/*
+ * Timer event.
+ */
+static void on_timer_event(pj_timer_heap_t *th, pj_timer_entry *e)
+{
+    pj_turn_session *sess = (pj_turn_session*)e->user_data;
+    enum timer_id_t eid;
+
+    PJ_UNUSED_ARG(th);
+
+    pj_grp_lock_acquire(sess->grp_lock);
+
+    eid = (enum timer_id_t) e->id;
+    e->id = TIMER_NONE;
+    
+    if (eid == TIMER_KEEP_ALIVE) {
+	pj_time_val now;
+	pj_hash_iterator_t itbuf, *it;
+	pj_bool_t resched = PJ_TRUE;
+	pj_bool_t pkt_sent = PJ_FALSE;
+
+	if (sess->state >= PJ_TURN_STATE_DEALLOCATING) {
+	    /* Ignore if we're deallocating */
+	    goto on_return;
+	}
+
+	pj_gettimeofday(&now);
+
+	/* Refresh allocation if it's time to do so */
+	if (PJ_TIME_VAL_LTE(sess->expiry, now)) {
+	    int lifetime = sess->alloc_param.lifetime;
+
+	    if (lifetime == 0)
+		lifetime = -1;
+
+	    send_refresh(sess, lifetime);
+	    resched = PJ_FALSE;
+	    pkt_sent = PJ_TRUE;
+	}
+
+	/* Scan hash table to refresh bound channels */
+	it = pj_hash_first(sess->ch_table, &itbuf);
+	while (it) {
+	    struct ch_t *ch = (struct ch_t*) 
+			      pj_hash_this(sess->ch_table, it);
+	    if (ch->bound && PJ_TIME_VAL_LTE(ch->expiry, now)) {
+
+		/* Send ChannelBind to refresh channel binding and 
+		 * permission.
+		 */
+		pj_turn_session_bind_channel(sess, &ch->addr,
+					     pj_sockaddr_get_len(&ch->addr));
+		pkt_sent = PJ_TRUE;
+	    }
+
+	    it = pj_hash_next(sess->ch_table, it);
+	}
+
+	/* Scan permission table to refresh permissions */
+	if (refresh_permissions(sess, &now))
+	    pkt_sent = PJ_TRUE;
+
+	/* If no packet is sent, send a blank Send indication to
+	 * refresh local NAT.
+	 */
+	if (!pkt_sent && sess->alloc_param.ka_interval > 0) {
+	    pj_stun_tx_data *tdata;
+	    pj_status_t rc;
+
+	    /* Create blank SEND-INDICATION */
+	    rc = pj_stun_session_create_ind(sess->stun, 
+					    PJ_STUN_SEND_INDICATION, &tdata);
+	    if (rc == PJ_SUCCESS) {
+		/* Add DATA attribute with zero length */
+		pj_stun_msg_add_binary_attr(tdata->pool, tdata->msg,
+					    PJ_STUN_ATTR_DATA, NULL, 0);
+
+		/* Send the indication */
+		pj_stun_session_send_msg(sess->stun, NULL, PJ_FALSE, 
+					 PJ_FALSE, sess->srv_addr,
+					 pj_sockaddr_get_len(sess->srv_addr),
+					 tdata);
+	    }
+	}
+
+	/* Reshcedule timer */
+	if (resched) {
+	    pj_time_val delay;
+
+	    delay.sec = sess->ka_interval;
+	    delay.msec = 0;
+
+	    pj_timer_heap_schedule_w_grp_lock(sess->timer_heap, &sess->timer,
+	                                      &delay, TIMER_KEEP_ALIVE,
+	                                      sess->grp_lock);
+	}
+
+    } else if (eid == TIMER_DESTROY) {
+	/* Time to destroy */
+	do_destroy(sess);
+    } else {
+	pj_assert(!"Unknown timer event");
+    }
+
+on_return:
+    pj_grp_lock_release(sess->grp_lock);
+}
+