Index: /pjproject/trunk/pjnath/build/pjnath.dsp
===================================================================
--- /pjproject/trunk/pjnath/build/pjnath.dsp	(revision 1084)
+++ /pjproject/trunk/pjnath/build/pjnath.dsp	(revision 1085)
@@ -42,5 +42,5 @@
 # PROP Target_Dir ""
 # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c
-# ADD CPP /nologo /MD /W4 /GX /Zi /O2 /Ob2 /I "../include" /I "../../pjlib/include" /D "NDEBUG" /D "WIN32" /D "_MBCS" /D "_LIB" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /FD /c
+# ADD CPP /nologo /MD /W4 /GX /Zi /O2 /Ob2 /I "../include" /I "../../pjlib/include" /I "../../pjlib-util/include" /D "NDEBUG" /D "WIN32" /D "_MBCS" /D "_LIB" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /FD /c
 # SUBTRACT CPP /YX
 # ADD BASE RSC /l 0x409 /d "NDEBUG"
@@ -66,5 +66,5 @@
 # PROP Target_Dir ""
 # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c
-# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../include" /I "../../pjlib/include" /D "_DEBUG" /D "WIN32" /D "_MBCS" /D "_LIB" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /FD /GZ /c
+# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../include" /I "../../pjlib/include" /I "../../pjlib-util/include" /D "_DEBUG" /D "WIN32" /D "_MBCS" /D "_LIB" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /FD /GZ /c
 # SUBTRACT CPP /YX
 # ADD BASE RSC /l 0x409 /d "_DEBUG"
Index: /pjproject/trunk/pjnath/include/pjnath/ice.h
===================================================================
--- /pjproject/trunk/pjnath/include/pjnath/ice.h	(revision 1084)
+++ /pjproject/trunk/pjnath/include/pjnath/ice.h	(revision 1085)
@@ -25,5 +25,5 @@
  */
 #include <pjnath/types.h>
-#include <pjnath/stun_endpoint.h>
+#include <pjnath/stun_session.h>
 #include <pjlib-util/resolver.h>
 #include <pj/sock.h>
@@ -65,7 +65,7 @@
 
 
-#define PJ_ICE_MAX_CAND	    32
+#define PJ_ICE_MAX_CAND	    16
 #define PJ_ICE_MAX_COMP	    8
-
+#define PJ_ICE_MAX_CHECKS   32
 
 /**
@@ -105,5 +105,8 @@
 typedef struct pj_ice_check
 {
-    unsigned		local_cand_id;
+    unsigned		cand_id;
+    pj_uint32_t		comp_id;
+    pj_str_t		foundation;
+
     pj_uint64_t		check_prio;
     pj_ice_check_state	check_state;
@@ -128,5 +131,5 @@
     pj_ice_checklist_state   state;
     unsigned		     count;
-    pj_ice_check	    *checks;
+    pj_ice_check	     checks[PJ_ICE_MAX_CHECKS];
 } pj_ice_checklist;
 
@@ -137,8 +140,8 @@
 typedef struct pj_ice_cb
 {
-    pj_bool_t (*on_found_cand)(pj_ice *sock,
-			       pj_ice_cand_type type,
-			       const pj_sockaddr_t *addr,
-			       int addr_len);
+    pj_status_t (*on_send_pkt)(pj_ice *ice, 
+			       const void *pkt, pj_size_t size,
+			       const pj_sockaddr_t *dst_addr,
+			       unsigned addr_len);
 } pj_ice_cb;
 
@@ -154,4 +157,10 @@
 } pj_ice_state;
 
+typedef enum pj_ice_role
+{
+    PJ_ICE_ROLE_CONTROLLED,
+    PJ_ICE_ROLE_CONTROLLING
+} pj_ice_role;
+
 /**
  * ICE structure.
@@ -165,6 +174,14 @@
     int			 af;
     int			 sock_type;
-
+    pj_ice_role		 role;
     pj_ice_state	 state;
+
+    pj_ice_cb		 cb;
+
+    /* STUN credentials */
+    pj_str_t		 tx_uname;
+    pj_str_t		 tx_pass;
+    pj_str_t		 rx_uname;
+    pj_str_t		 rx_pass;
 
     /* Components */
@@ -173,9 +190,14 @@
 
     /* Local candidates */
-    unsigned		 cand_cnt;
-    pj_ice_cand		 cand[PJ_ICE_MAX_CAND];
-
-    /* Checklist */
+    unsigned		 lcand_cnt;
+    pj_ice_cand		 lcand[PJ_ICE_MAX_CAND];
+
+    /* Remote candidates */
+    unsigned		 rcand_cnt;
+    pj_ice_cand		 rcand[PJ_ICE_MAX_CAND];
+
+    /* Checklists */
     pj_ice_checklist	 cklist;
+    pj_ice_checklist	 valid_list;
 
     /* STUN servers */
@@ -184,4 +206,8 @@
     pj_bool_t		 relay_enabled;
     pj_sockaddr		 stun_srv;
+
+    /* STUN sessions */
+    pj_stun_session	*tx_sess;
+    pj_stun_session	*rx_sess;
 };
 
@@ -189,4 +215,6 @@
 PJ_DECL(pj_status_t) pj_ice_create(pj_stun_config *cfg,
 				   const char *name,
+				   pj_ice_role role,
+				   const pj_ice_cb *cb,
 				   int af,
 				   int sock_type,
@@ -208,5 +236,9 @@
 					  unsigned comp_id,
 					  pj_sock_t sock);
-
+PJ_DECL(pj_status_t) pj_ice_set_credentials(pj_ice *ice,
+					    const pj_str_t *local_ufrag,
+					    const pj_str_t *local_pass,
+					    const pj_str_t *remote_ufrag,
+					    const pj_str_t *remote_pass);
 PJ_DECL(pj_status_t) pj_ice_start_gather(pj_ice *ice,
 					 unsigned flags);
Index: /pjproject/trunk/pjnath/include/pjnath/stun_msg.h
===================================================================
--- /pjproject/trunk/pjnath/include/pjnath/stun_msg.h	(revision 1084)
+++ /pjproject/trunk/pjnath/include/pjnath/stun_msg.h	(revision 1085)
@@ -1513,5 +1513,4 @@
  * @param length	Length of data, or zero if no data is to be
  *			copied now.
- * @param p_attr	Pointer to receive the attribute.
  *
  * @return		PJ_SUCCESS on success or the appropriate error code.
@@ -1523,4 +1522,29 @@
 						 unsigned length);
 
+/**
+ * Create STUN empty attribute.
+ *
+ * @param pool		The pool to allocate memory from.
+ * @param attr_type	The attribute type, from #pj_stun_attr_type.
+ * @param p_attr	Pointer to receive the attribute.
+ *
+ * @return		PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_empty_attr_create(pj_pool_t *pool,
+					       int attr_type,
+					       pj_stun_empty_attr **p_attr);
+
+/**
+ * Create STUN empty attribute and add the attribute to the message.
+ *
+ * @param pool		The pool to allocate memory from.
+ * @param msg		The STUN message.
+ * @param attr_type	The attribute type, from #pj_stun_attr_type.
+ *
+ * @return		PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_msg_add_empty_attr(pj_pool_t *pool,
+						pj_stun_msg *msg,
+						int attr_type);
 
 /**
Index: /pjproject/trunk/pjnath/include/pjnath/stun_session.h
===================================================================
--- /pjproject/trunk/pjnath/include/pjnath/stun_session.h	(revision 1084)
+++ /pjproject/trunk/pjnath/include/pjnath/stun_session.h	(revision 1085)
@@ -139,4 +139,6 @@
     pj_stun_msg		*msg;		/**< The STUN message.		    */
 
+    void		*user_data;	/**< Arbitrary application data.    */
+
     pj_stun_client_tsx	*client_tsx;	/**< Client STUN transaction.	    */
     pj_uint32_t		 msg_magic;	/**< Message magic.		    */
Index: /pjproject/trunk/pjnath/src/pjnath/ice.c
===================================================================
--- /pjproject/trunk/pjnath/src/pjnath/ice.c	(revision 1084)
+++ /pjproject/trunk/pjnath/src/pjnath/ice.c	(revision 1085)
@@ -19,9 +19,20 @@
 #include <pjnath/ice.h>
 #include <pjnath/errno.h>
+#include <pj/addr_resolv.h>
 #include <pj/assert.h>
+#include <pj/log.h>
 #include <pj/os.h>
 #include <pj/pool.h>
 #include <pj/string.h>
 
+
+static const char *check_state_name[] = 
+{
+    "Frozen",
+    "Waiting",
+    "In Progress",
+    "Succeeded",
+    "Failed"
+};
 
 static void destroy_ice(pj_ice *ice,
@@ -30,7 +41,46 @@
 			  pj_ice_state new_state);
 
+static pj_status_t on_stun_send_msg(pj_stun_session *sess,
+				    const void *pkt,
+				    pj_size_t pkt_size,
+				    const pj_sockaddr_t *dst_addr,
+				    unsigned addr_len);
+static pj_status_t on_stun_rx_request(pj_stun_session *sess,
+				      const pj_uint8_t *pkt,
+				      unsigned pkt_len,
+				      const pj_stun_msg *msg,
+				      const pj_sockaddr_t *src_addr,
+				      unsigned src_addr_len);
+static void on_stun_request_complete(pj_stun_session *sess,
+				     pj_status_t status,
+				     pj_stun_tx_data *tdata,
+				     const pj_stun_msg *response);
+static pj_status_t on_stun_rx_indication(pj_stun_session *sess,
+					 const pj_uint8_t *pkt,
+					 unsigned pkt_len,
+					 const pj_stun_msg *msg,
+					 const pj_sockaddr_t *src_addr,
+					 unsigned src_addr_len);
+
+static pj_status_t stun_auth_get_auth(void *user_data,
+				      pj_pool_t *pool,
+				      pj_str_t *realm,
+				      pj_str_t *nonce);
+static pj_status_t stun_auth_get_password(void *user_data, 
+					  const pj_str_t *realm,
+					  const pj_str_t *username,
+					  pj_pool_t *pool,
+					  int *data_type,
+					  pj_str_t *data);
+static pj_bool_t stun_auth_verify_nonce(void *user_data,
+					const pj_str_t *realm,
+					const pj_str_t *username,
+					const pj_str_t *nonce);
+
 
 PJ_DEF(pj_status_t) pj_ice_create(pj_stun_config *cfg,
 				  const char *name,
+				  pj_ice_role role,
+				  const pj_ice_cb *cb,
 				  int af,
 				  int sock_type,
@@ -39,7 +89,9 @@
     pj_pool_t *pool;
     pj_ice *ice;
+    pj_stun_session_cb sess_cb;
+    pj_stun_auth_cred auth_cred;
     pj_status_t status;
 
-    PJ_ASSERT_RETURN(cfg && p_ice, PJ_EINVAL);
+    PJ_ASSERT_RETURN(cfg && cb && p_ice, PJ_EINVAL);
     PJ_ASSERT_RETURN(sock_type==PJ_SOCK_DGRAM || sock_type==PJ_SOCK_STREAM,
 		     PJ_EINVAL);
@@ -53,4 +105,5 @@
     ice->af = af;
     ice->sock_type = sock_type;
+    ice->role = role;
 
     pj_ansi_snprintf(ice->obj_name, sizeof(ice->obj_name),
@@ -64,5 +117,48 @@
     }
 
+    pj_memcpy(&ice->cb, cb, sizeof(*cb));
+
+    /* Init STUN callbacks */
+    pj_bzero(&sess_cb, sizeof(sess_cb));
+    sess_cb.on_request_complete = &on_stun_request_complete;
+    sess_cb.on_rx_indication = &on_stun_rx_indication;
+    sess_cb.on_rx_request = &on_stun_rx_request;
+    sess_cb.on_send_msg = &on_stun_send_msg;
+
+    /* Init STUN authentication credential */
+    pj_bzero(&auth_cred, sizeof(auth_cred));
+    auth_cred.type = PJ_STUN_AUTH_CRED_DYNAMIC;
+    auth_cred.data.dyn_cred.get_auth = &stun_auth_get_auth;
+    auth_cred.data.dyn_cred.get_password = &stun_auth_get_password;
+    auth_cred.data.dyn_cred.verify_nonce = &stun_auth_verify_nonce;
+
+    /* Create STUN session for outgoing requests */
+    status = pj_stun_session_create(cfg, ice->obj_name, &sess_cb, PJ_FALSE,
+				    &ice->tx_sess);
+    if (status != PJ_SUCCESS) {
+	destroy_ice(ice, status);
+	return status;
+    }
+
+    pj_stun_session_set_user_data(ice->tx_sess, ice);
+    auth_cred.data.dyn_cred.user_data = ice->tx_sess;
+    pj_stun_session_set_credential(ice->tx_sess, &auth_cred);
+
+    /* Create STUN session for incoming requests */
+    status = pj_stun_session_create(cfg, ice->obj_name, &sess_cb, PJ_FALSE,
+				    &ice->rx_sess);
+    if (status != PJ_SUCCESS) {
+	destroy_ice(ice, status);
+	return status;
+    }
+
+    pj_stun_session_set_user_data(ice->rx_sess, ice);
+    auth_cred.data.dyn_cred.user_data = ice->rx_sess;
+    pj_stun_session_set_credential(ice->rx_sess, &auth_cred);
+
+    /* Done */
     *p_ice = ice;
+
+    PJ_LOG(4,(ice->obj_name, "ICE session created"));
 
     return PJ_SUCCESS;
@@ -73,4 +169,8 @@
 			pj_status_t reason)
 {
+    if (reason == PJ_SUCCESS) {
+	PJ_LOG(4,(ice->obj_name, "Destroying ICE session"));
+    }
+
     if (ice->resv_q) {
 	pj_dns_resolver_cancel_query(ice->resv_q, PJ_FALSE);
@@ -109,4 +209,7 @@
 {
     pj_assert(!"Not implemented yet!");
+    PJ_UNUSED_ARG(user_data);
+    PJ_UNUSED_ARG(status);
+    PJ_UNUSED_ARG(response);
 }
 
@@ -260,4 +363,103 @@
 
 
+static pj_status_t stun_auth_get_auth(void *user_data,
+				      pj_pool_t *pool,
+				      pj_str_t *realm,
+				      pj_str_t *nonce)
+{
+    PJ_UNUSED_ARG(user_data);
+    PJ_UNUSED_ARG(pool);
+
+    realm->slen = 0;
+    nonce->slen = 0;
+
+    return PJ_SUCCESS;
+}
+
+
+static pj_status_t stun_auth_get_password(void *user_data, 
+					  const pj_str_t *realm,
+					  const pj_str_t *username,
+					  pj_pool_t *pool,
+					  int *data_type,
+					  pj_str_t *data)
+{
+    pj_stun_session *sess = (pj_stun_session *)user_data;
+    pj_ice *ice = (pj_ice*) pj_stun_session_get_user_data(sess);
+
+    PJ_UNUSED_ARG(realm);
+    PJ_UNUSED_ARG(pool);
+
+    if (sess == ice->tx_sess) {
+	/* Verify username */
+	if (pj_strcmp(username, &ice->tx_uname) != 0)
+	    return -1;
+	*data_type = 0;
+	*data = ice->tx_pass;
+    } else {
+	/* The agent MUST accept a credential if the username consists
+	 * of two values separated by a colon, where the first value is
+	 * equal to the username fragment generated by the agent in an offer
+	 * or answer for a session in-progress, and the MESSAGE-INTEGRITY 
+	 * is the output of a hash of the password and the STUN packet's 
+	 * contents. 
+	 */
+	PJ_TODO(CHECK_USERNAME_FOR_INCOMING_STUN_REQUEST);
+	*data_type = 0;
+	*data = ice->rx_pass;
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+static pj_bool_t stun_auth_verify_nonce(void *user_data,
+					const pj_str_t *realm,
+					const pj_str_t *username,
+					const pj_str_t *nonce)
+{
+    /* We don't use NONCE */
+    PJ_UNUSED_ARG(user_data);
+    PJ_UNUSED_ARG(realm);
+    PJ_UNUSED_ARG(username);
+    PJ_UNUSED_ARG(nonce);
+    return PJ_TRUE;
+}
+
+
+PJ_DEF(pj_status_t) pj_ice_set_credentials(pj_ice *ice,
+					   const pj_str_t *local_ufrag,
+					   const pj_str_t *local_pass,
+					   const pj_str_t *remote_ufrag,
+					   const pj_str_t *remote_pass)
+{
+    char buf[128];
+    pj_str_t username;
+
+    username.ptr = buf;
+
+    PJ_ASSERT_RETURN(ice && local_ufrag && local_pass &&
+		     remote_ufrag && remote_pass, PJ_EINVAL);
+    PJ_ASSERT_RETURN(local_ufrag->slen + remote_ufrag->slen <
+		     sizeof(buf), PJ_ENAMETOOLONG);
+
+    pj_strcpy(&username, remote_ufrag);
+    pj_strcat2(&username, ":");
+    pj_strcat(&username, local_ufrag);
+
+    pj_strdup(ice->pool, &ice->tx_uname, &username);
+    pj_strdup(ice->pool, &ice->tx_pass, remote_pass);
+
+    pj_strcpy(&username, local_ufrag);
+    pj_strcat2(&username, ":");
+    pj_strcat(&username, remote_ufrag);
+
+    pj_strdup(ice->pool, &ice->rx_uname, &username);
+    pj_strdup(ice->pool, &ice->rx_pass, local_pass);
+
+    return PJ_SUCCESS;
+}
+
+
 static pj_status_t gather_host_cands(pj_ice *ice)
 {
@@ -293,4 +495,5 @@
 static pj_status_t gather_mapped_cands(pj_ice *ice)
 {
+    PJ_UNUSED_ARG(ice);
     return PJ_ENOTSUP;
 }
@@ -298,4 +501,5 @@
 static pj_status_t gather_relayed_cands(pj_ice *ice)
 {
+    PJ_UNUSED_ARG(ice);
     return PJ_ENOTSUP;
 }
@@ -305,4 +509,6 @@
 {
     pj_status_t status;
+
+    PJ_UNUSED_ARG(flags);
 
     /* Gather host candidate */
@@ -348,30 +554,30 @@
 				    unsigned *p_cand_id)
 {
-    pj_ice_cand *cand;
+    pj_ice_cand *lcand;
     pj_status_t status = PJ_SUCCESS;
 
     pj_mutex_lock(ice->mutex);
 
-    if (ice->cand_cnt >= PJ_ARRAY_SIZE(ice->cand)) {
+    if (ice->lcand_cnt >= PJ_ARRAY_SIZE(ice->lcand)) {
 	status = PJ_ETOOMANY;
 	goto on_error;
     }
 
-    cand = &ice->cand[ice->cand_cnt];
-    cand->comp_id = comp_id;
-    cand->type = type;
-    pj_strdup(ice->pool, &cand->foundation, foundation);
-    cand->prio = CALC_CAND_PRIO(type, local_pref, cand->comp_id);
-    pj_memcpy(&cand->addr, addr, addr_len);
-    pj_memcpy(&cand->base_addr, base_addr, addr_len);
+    lcand = &ice->lcand[ice->lcand_cnt];
+    lcand->comp_id = comp_id;
+    lcand->type = type;
+    pj_strdup(ice->pool, &lcand->foundation, foundation);
+    lcand->prio = CALC_CAND_PRIO(type, local_pref, lcand->comp_id);
+    pj_memcpy(&lcand->addr, addr, addr_len);
+    pj_memcpy(&lcand->base_addr, base_addr, addr_len);
     if (srv_addr)
-	pj_memcpy(&cand->srv_addr, srv_addr, addr_len);
+	pj_memcpy(&lcand->srv_addr, srv_addr, addr_len);
     else
-	pj_bzero(&cand->srv_addr, sizeof(cand->srv_addr));
+	pj_bzero(&lcand->srv_addr, sizeof(lcand->srv_addr));
 
     if (p_cand_id)
-	*p_cand_id = ice->cand_cnt;
-
-    ++ice->cand_cnt;
+	*p_cand_id = ice->lcand_cnt;
+
+    ++ice->lcand_cnt;
 
 on_error:
@@ -383,5 +589,5 @@
 PJ_DEF(unsigned) pj_ice_get_cand_cnt(pj_ice *ice)
 {
-    return ice->cand_cnt;
+    return ice->lcand_cnt;
 }
 
@@ -394,7 +600,9 @@
     unsigned i, count;
 
+    PJ_UNUSED_ARG(sort_by);
+
     pj_mutex_lock(ice->mutex);
 
-    count = (*p_count < ice->cand_cnt) ? *p_count : ice->cand_cnt;
+    count = (*p_count < ice->lcand_cnt) ? *p_count : ice->lcand_cnt;
     for (i=0; i<count; ++i)
 	cand_ids[i] = i;
@@ -412,7 +620,7 @@
 {
     PJ_ASSERT_RETURN(ice && p_cand, PJ_EINVAL);
-    PJ_ASSERT_RETURN(cand_id <= ice->cand_cnt, PJ_EINVAL);
-
-    *p_cand = &ice->cand[cand_id];
+    PJ_ASSERT_RETURN(cand_id <= ice->lcand_cnt, PJ_EINVAL);
+
+    *p_cand = &ice->lcand[cand_id];
 
     return PJ_SUCCESS;
@@ -433,4 +641,72 @@
 }
 
+static const char *dump_check(char *buffer, unsigned bufsize,
+			      const pj_ice *ice,
+			      const pj_ice_check *check)
+{
+    char local_addr[128];
+    int len;
+
+    pj_ansi_strcpy(local_addr, 
+		   pj_inet_ntoa(ice->lcand[check->cand_id].addr.ipv4.sin_addr));
+
+    len = pj_ansi_snprintf(buffer, bufsize,
+	      "%s:%d-->%s:%d",
+	      local_addr,
+	      (int)pj_ntohs(ice->lcand[check->cand_id].addr.ipv4.sin_port),
+	      pj_inet_ntoa(check->rem_addr.ipv4.sin_addr),
+	      (int)pj_ntohs(check->rem_addr.ipv4.sin_port));
+    if (len < 0)
+	len = 0;
+    else if (len >= (int)bufsize)
+	len = bufsize - 1;
+
+    buffer[len] = '\0';
+    return buffer;
+}
+
+#if PJ_LOG_MAX_LEVEL >= 4
+static void dump_checklist(const char *title, const pj_ice *ice, 
+			   const pj_ice_checklist *clist)
+{
+    unsigned i;
+    char buffer[128];
+
+    PJ_LOG(4,(ice->obj_name, "%s", title));
+    for (i=0; i<clist->count; ++i) {
+	const pj_ice_check *c = &clist->checks[i];
+	PJ_LOG(4,(ice->obj_name, " %d: %s (prio=%u, state=%s)",
+		  i, dump_check(buffer, sizeof(buffer), ice, c),
+		  c->check_prio, check_state_name[c->check_state]));
+    }
+}
+#else
+#define dump_checklist(ice, clist)
+#endif
+
+static void sort_checklist(pj_ice_checklist *clist)
+{
+    unsigned i;
+
+    for (i=0; i<clist->count-1; ++i) {
+	unsigned j, highest = i;
+	for (j=i+1; j<clist->count; ++j) {
+	    if (clist->checks[j].check_prio > 
+		clist->checks[highest].check_prio) 
+	    {
+		highest = j;
+	    }
+	}
+
+	if (highest != i) {
+	    pj_ice_check tmp;
+
+	    pj_memcpy(&tmp, &clist->checks[i], sizeof(pj_ice_check));
+	    pj_memcpy(&clist->checks[i], &clist->checks[highest], 
+		      sizeof(pj_ice_check));
+	    pj_memcpy(&clist->checks[highest], &tmp, sizeof(pj_ice_check));
+	}
+    }
+}
 
 PJ_DEF(pj_status_t) pj_ice_create_check_list(pj_ice *ice,
@@ -440,19 +716,25 @@
 {
     pj_ice_checklist *clist;
-    unsigned i, j, count;
+    unsigned i, j;
 
     PJ_ASSERT_RETURN(ice && rem_cand_cnt && rem_cand, PJ_EINVAL);
+    PJ_ASSERT_RETURN(rem_cand_cnt < PJ_ICE_MAX_CAND, PJ_ETOOMANY);
 
     pj_mutex_lock(ice->mutex);
 
-    /* Create checklist */
+    /* Save remote candidates */
+    ice->rcand_cnt = 0;
+    for (i=0; i<rem_cand_cnt; ++i) {
+	pj_ice_cand *cn = &ice->rcand[ice->rcand_cnt++];
+	pj_memcpy(cn, &rem_cand[i], sizeof(pj_ice_cand));
+	pj_strdup(ice->pool, &cn->foundation, &rem_cand[i].foundation);
+    }
+
+    /* Generate checklist */
     clist = &ice->cklist;
-    clist->checks = pj_pool_calloc(ice->pool, 
-				   ice->cand_cnt * rem_cand_cnt,
-				   sizeof(pj_ice_check));
-    for (i=0, count=0; i<ice->cand_cnt; ++i) {
+    for (i=0; i<ice->lcand_cnt; ++i) {
 	for (j=0; j<rem_cand_cnt; ++j) {
 
-	    pj_ice_check *c = &clist->checks[count++];
+	    pj_ice_check *c = &clist->checks[clist->count++];
 
 	    /* A local candidate is paired with a remote candidate if
@@ -460,17 +742,19 @@
 	     * and have the same IP address version. 
 	     */
-	    if (ice->cand[i].comp_id != rem_cand[j].comp_id ||
-		pj_strcmp(&ice->cand[i].foundation,&rem_cand[j].foundation)==0)
+	    if (ice->lcand[i].comp_id != rem_cand[j].comp_id ||
+		pj_strcmp(&ice->lcand[i].foundation,&rem_cand[j].foundation)==0)
 	    {
 		continue;
 	    }
 
-	    c->local_cand_id = i;
+	    c->cand_id = i;
+	    c->comp_id = ice->lcand[i].comp_id;
+	    c->foundation = ice->lcand[i].foundation;
 
 	    if (is_remote_offer) {
 		c->check_prio = CALC_CHECK_PRIO(rem_cand[j].prio,
-						ice->cand[i].prio);
+						ice->lcand[i].prio);
 	    } else {
-		c->check_prio = CALC_CHECK_PRIO(ice->cand[i].prio, 
+		c->check_prio = CALC_CHECK_PRIO(ice->lcand[i].prio, 
 						rem_cand[j].prio);
 	    }
@@ -486,26 +770,6 @@
     }
 
-    clist->count = count;
-
     /* Sort checklist based on priority */
-    for (i=0; i<clist->count-1; ++i) {
-	unsigned highest = i;
-	for (j=i+1; j<clist->count; ++j) {
-	    if (clist->checks[j].check_prio > 
-		clist->checks[highest].check_prio) 
-	    {
-		highest = j;
-	    }
-	}
-
-	if (highest != i) {
-	    pj_ice_check tmp;
-
-	    pj_memcpy(&tmp, &clist->checks[i], sizeof(pj_ice_check));
-	    pj_memcpy(&clist->checks[i], &clist->checks[highest], 
-		      sizeof(pj_ice_check));
-	    pj_memcpy(&clist->checks[highest], &tmp, sizeof(pj_ice_check));
-	}
-    }
+    sort_checklist(clist);
 
     /* Prune the checklist */
@@ -514,4 +778,7 @@
     }
 
+    /* Log checklist */
+    dump_checklist("Checklist created:", ice, clist);
+
     pj_mutex_lock(ice->mutex);
 
@@ -519,8 +786,121 @@
 }
 
+
+struct req_data
+{
+    pj_ice		*ice;
+    pj_ice_checklist	*clist;
+    unsigned		 ckid;
+};
+
+/* Perform check on the specified candidate pair */
+static pj_status_t perform_check(pj_ice *ice, pj_ice_checklist *clist,
+				 unsigned check_id)
+{
+    pj_stun_tx_data *tdata;
+    struct req_data *rd;
+    pj_ice_check *check;
+    pj_uint32_t prio;
+    char buffer[128];
+    pj_status_t status;
+
+    check = &clist->checks[check_id];
+
+    PJ_LOG(5,(ice->obj_name, 
+	      "Sending connectivity check for check %d: %s", 
+	      check_id, dump_check(buffer, sizeof(buffer), ice, check)));
+
+    /* Create request */
+    status = pj_stun_session_create_req(ice->tx_sess, 
+					PJ_STUN_BINDING_REQUEST, &tdata);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Attach data to be retrieved later when STUN request transaction
+     * completes and on_stun_request_complete() callback is called.
+     */
+    rd = PJ_POOL_ZALLOC_T(tdata->pool, struct req_data);
+    rd->ice = ice;
+    rd->clist = clist;
+    rd->ckid = check_id;
+    tdata->user_data = (void*) rd;
+
+    /* Add PRIORITY */
+    prio = CALC_CAND_PRIO(check->rem_type, 65535, check->comp_id);
+    pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_PRIORITY,
+			      prio);
+
+    /* Add USE-CANDIDATE */
+    if (ice->role == PJ_ICE_ROLE_CONTROLLING) {
+	pj_stun_msg_add_empty_attr(tdata->pool, tdata->msg, 
+				   PJ_STUN_ATTR_USE_CANDIDATE);
+    }
+
+    /* Initiate STUN transaction to send the request */
+    status = pj_stun_session_send_msg(ice->tx_sess, PJ_FALSE, 
+				      &check->rem_addr, 
+				      sizeof(pj_sockaddr_in), tdata);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    check->check_state = PJ_ICE_CHECK_STATE_IN_PROGRESS;
+    return PJ_SUCCESS;
+}
 
 /* Start periodic check for the specified checklist */
 static pj_status_t start_periodic_check(pj_ice *ice, pj_ice_checklist *clist)
 {
+    unsigned i, start_count=0;
+    pj_status_t status;
+
+    /* Checklist state must be idle or completed */
+    pj_assert(clist->state == PJ_ICE_CHECKLIST_ST_IDLE ||
+	      clist->state == PJ_ICE_CHECKLIST_ST_COMPLETED);
+
+    /* Set checklist state to Running */
+    clist->state = PJ_ICE_CHECKLIST_ST_RUNNING;
+
+    PJ_LOG(4,(ice->obj_name, "Starting checklist periodic check"));
+
+    /* Send STUN Binding request for checks in Waiting list */
+    for (i=0; i<clist->count; ++i) {
+	pj_ice_check *check = &clist->checks[i];
+
+	if (check->check_state == PJ_ICE_CHECK_STATE_WAITING) {
+	    status = perform_check(ice, clist, i);
+	    if (status != PJ_SUCCESS)
+		return status;
+
+	    ++start_count;
+	}
+    }
+
+    /* If we don't have anything in Waiting state, perform check to
+     * highest priority pair that is in Frozen state.
+     */
+    if (start_count==0) {
+	for (i=0; i<clist->count; ++i) {
+	    pj_ice_check *check = &clist->checks[i];
+
+	    if (check->check_state == PJ_ICE_CHECK_STATE_FROZEN) {
+		status = perform_check(ice, clist, i);
+		if (status != PJ_SUCCESS)
+		    return status;
+
+		++start_count;
+		break;
+	    }
+	}
+    }
+
+    /* Cannot start check because there's no suitable candidate pair.
+     * Set checklist state to Completed.
+     */
+    if (start_count==0) {
+	clist->state = PJ_ICE_CHECKLIST_ST_COMPLETED;
+	PJ_LOG(4,(ice->obj_name, "Checklist completed"));
+    }
+
+    return PJ_SUCCESS;
 }
 
@@ -530,6 +910,5 @@
 {
     pj_ice_checklist *clist;
-    unsigned i, comp_id;
-    pj_str_t fnd;
+    unsigned i;
 
     PJ_ASSERT_RETURN(ice, PJ_EINVAL);
@@ -547,14 +926,11 @@
      * states to Waiting as well.
      */
-    comp_id = ice->cand[clist->checks[0].local_cand_id].comp_id;
-    fnd = ice->cand[clist->checks[0].local_cand_id].foundation;
-
     for (i=1; i<clist->count; ++i) {
 	pj_ice_check *cki = &clist->checks[i];
 
-	if (ice->cand[cki->local_cand_id].comp_id != comp_id)
+	if (cki->comp_id != clist->checks[0].comp_id)
 	    continue;
 
-	if (pj_strcmp(&ice->cand[cki->local_cand_id].foundation, &fnd)==0)
+	if (pj_strcmp(&cki->foundation, &clist->checks[0].foundation)==0)
 	    continue;
 
@@ -565,2 +941,169 @@
     return start_periodic_check(ice, clist);
 }
+
+
+//////////////////////////////////////////////////////////////////////////////
+
+static pj_status_t on_stun_send_msg(pj_stun_session *sess,
+				    const void *pkt,
+				    pj_size_t pkt_size,
+				    const pj_sockaddr_t *dst_addr,
+				    unsigned addr_len)
+{
+    pj_ice *ice = (pj_ice*) pj_stun_session_get_user_data(sess);
+    return (*ice->cb.on_send_pkt)(ice, pkt, pkt_size, dst_addr, addr_len);
+}
+
+
+static pj_status_t on_stun_rx_request(pj_stun_session *sess,
+				      const pj_uint8_t *pkt,
+				      unsigned pkt_len,
+				      const pj_stun_msg *msg,
+				      const pj_sockaddr_t *src_addr,
+				      unsigned src_addr_len)
+{
+    pj_stun_tx_data *tdata;
+    pj_status_t status;
+
+    /* 7.2.1.2.  Learning Peer Reflexive Candidates */
+    PJ_TODO(LEARN_PEER_REFLEXIVE_CANDIDATES);
+
+    /* Reject any requests except Binding request */
+    if (msg->hdr.type != PJ_STUN_BINDING_REQUEST) {
+	status = pj_stun_session_create_response(sess, msg, 
+						 PJ_STUN_SC_BAD_REQUEST,
+						 NULL, &tdata);
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	status = pj_stun_session_send_msg(sess, PJ_TRUE, 
+					  src_addr, src_addr_len, tdata);
+	return status;
+    }
+
+    status = pj_stun_session_create_response(sess, msg, 0, NULL, &tdata);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    status = pj_stun_msg_add_sockaddr_attr(tdata->pool, msg, 
+					   PJ_STUN_ATTR_XOR_MAPPED_ADDR,
+					   PJ_TRUE, src_addr, src_addr_len);
+
+    status = pj_stun_session_send_msg(sess, PJ_TRUE, 
+				      src_addr, src_addr_len, tdata);
+
+    /* 7.2.1.3.  Triggered Checks:
+     * Next, the agent constructs a pair whose local candidate is equal to
+     * the transport address on which the STUN request was received, and a
+     * remote candidate equal to the source transport address where the
+     * request came from (which may be peer-reflexive remote candidate that
+     * was just learned). 
+     */
+    
+    return status;
+}
+
+/* This callback is called when outgoing STUN request completed */
+static void on_stun_request_complete(pj_stun_session *sess,
+				     pj_status_t status,
+				     pj_stun_tx_data *tdata,
+				     const pj_stun_msg *response)
+{
+    struct req_data *rd = (struct req_data*) tdata->user_data;
+    pj_ice *ice;
+    pj_ice_check *check, *valid_check;
+    pj_ice_checklist *clist;
+    char buffer[128];
+
+    ice = rd->ice;
+    check = &rd->clist->checks[rd->ckid];
+    clist = rd->clist;
+
+    PJ_LOG(5,(ice->obj_name, 
+	      "Connectivity check %s for check %d: %s",
+	      (status==PJ_SUCCESS ? "SUCCESS" : "FAILED"), rd->ckid, 
+	      dump_check(buffer, sizeof(buffer), ice, check)));
+
+    if (status != PJ_SUCCESS) {
+	check->check_state = PJ_ICE_CHECK_STATE_FAILED;
+	return;
+    }
+
+    /* The agent MUST check that the source IP address and port of the
+     * response equals the destination IP address and port that the Binding
+     * Request was sent to, and that the destination IP address and port of
+     * the response match the source IP address and port that the Binding
+     * Request was sent from.
+     */
+    PJ_TODO(CHECK_ICE_RESPONSE_SOURCE_ADDRESS);
+
+    /* Get the STUN MAPPED-ADDRESS attribute. If the
+     * transport address does not match any of the local candidates that the
+     * agent knows about, the mapped address represents a new candidate - a
+     * peer reflexive candidate 
+     */
+    PJ_TODO(CHECK_ICE_RESPONSE_SOURCE_ADDRESS2);
+
+    /* Sets the state of the pair that generated the check to succeeded. */
+    check->check_state = PJ_ICE_CHECK_STATE_SUCCEEDED;
+
+    /* This is a valid pair, so add this to the valid list */
+    valid_check = &ice->valid_list.checks[ice->valid_list.count++];
+    pj_memcpy(valid_check, check, sizeof(*check));
+
+    /* Sort valid_list */
+    sort_checklist(&ice->valid_list);
+
+
+    /* If the pair had a component ID of 1, the agent MUST change the
+     * states for all other Frozen pairs for the same media stream and
+     * same foundation, but different component IDs, to Waiting.
+     */
+    if (check->comp_id == 1) {
+	unsigned i;
+	for (i=0; i<clist->count; ++i)  {
+	    pj_ice_check *c = &clist->checks[i];
+
+	    if (c->check_state == PJ_ICE_CHECK_STATE_FROZEN &&
+		c->comp_id != check->comp_id &&
+		pj_strcmp(&c->foundation, &check->foundation)==0)
+	    {
+		/* Unfreeze and start check */
+		PJ_LOG(5,(ice->obj_name, "Unfreezing check %d", i));
+		c->check_state = PJ_ICE_CHECK_STATE_WAITING;
+		perform_check(ice, clist, i);
+	    }
+	}
+
+    } 
+    /* If the pair had a component ID equal to the number of components
+     * for the media stream (where this is the actual number of
+     * components being used, in cases where the number of components
+     * signaled in the SDP differs from offerer to answerer), the agent
+     * MUST change the state for all other Frozen pairs for the first
+     * component of different media streams (and thus in different check
+     * lists) but the same foundation, to Waiting.
+     */
+    else if (0) {
+	PJ_TODO(UNFREEZE_OTHER_COMPONENT_ID);
+    }
+    /* If the pair has any other component ID, no other pairs can be
+     * unfrozen.
+     */
+    else {
+	PJ_TODO(UNFREEZE_OTHER_COMPONENT_ID1);
+    }
+
+}
+
+static pj_status_t on_stun_rx_indication(pj_stun_session *sess,
+					 const pj_uint8_t *pkt,
+					 unsigned pkt_len,
+					 const pj_stun_msg *msg,
+					 const pj_sockaddr_t *src_addr,
+					 unsigned src_addr_len)
+{
+    PJ_TODO(SUPPORT_RX_BIND_REQUEST_AS_INDICATION);
+    return PJ_ENOTSUP;
+}
+
Index: /pjproject/trunk/pjnath/src/pjnath/stun_msg.c
===================================================================
--- /pjproject/trunk/pjnath/src/pjnath/stun_msg.c	(revision 1084)
+++ /pjproject/trunk/pjnath/src/pjnath/stun_msg.c	(revision 1085)
@@ -874,4 +874,21 @@
 
 
+/*
+ * Create STUN empty attribute and add the attribute to the message.
+ */
+PJ_DEF(pj_status_t) pj_stun_msg_add_empty_attr( pj_pool_t *pool,
+						pj_stun_msg *msg,
+						int attr_type)
+{
+    pj_stun_empty_attr *attr = NULL;
+    pj_status_t status;
+
+    status = pj_stun_empty_attr_create(pool, attr_type, &attr);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    return pj_stun_msg_add_attr(msg, &attr->hdr);
+}
+
 static pj_status_t decode_empty_attr(pj_pool_t *pool, 
 				     const pj_uint8_t *buf, 
