Index: /pjproject/trunk/pjmedia/src/pjmedia/transport_ice.c
===================================================================
--- /pjproject/trunk/pjmedia/src/pjmedia/transport_ice.c	(revision 1151)
+++ /pjproject/trunk/pjmedia/src/pjmedia/transport_ice.c	(revision 1152)
@@ -465,10 +465,10 @@
 			(pj_uint16_t)sdp_med->desc.port);
 
-    /* Find ice-ufrag attribute in session descriptor */
-    attr = pjmedia_sdp_attr_find2(rem_sdp->attr_count, rem_sdp->attr,
+    /* Find ice-ufrag attribute in media descriptor */
+    attr = pjmedia_sdp_attr_find2(sdp_med->attr_count, sdp_med->attr,
 				  "ice-ufrag", NULL);
     if (attr == NULL) {
-	/* Find in media descriptor */
-	attr = pjmedia_sdp_attr_find2(sdp_med->attr_count, sdp_med->attr,
+	/* Find ice-ufrag attribute in session descriptor */
+	attr = pjmedia_sdp_attr_find2(rem_sdp->attr_count, rem_sdp->attr,
 				      "ice-ufrag", NULL);
 	if (attr == NULL) {
@@ -479,10 +479,10 @@
     uname = attr->value;
 
-    /* Find ice-pwd attribute in session descriptor */
-    attr = pjmedia_sdp_attr_find2(rem_sdp->attr_count, rem_sdp->attr,
+    /* Find ice-pwd attribute in media descriptor */
+    attr = pjmedia_sdp_attr_find2(sdp_med->attr_count, sdp_med->attr,
 				  "ice-pwd", NULL);
     if (attr == NULL) {
-	/* Not found, find in media descriptor */
-	attr = pjmedia_sdp_attr_find2(sdp_med->attr_count, sdp_med->attr,
+	/* Find ice-pwd attribute in session descriptor */
+	attr = pjmedia_sdp_attr_find2(rem_sdp->attr_count, rem_sdp->attr,
 				      "ice-pwd", NULL);
 	if (attr == NULL) {
Index: /pjproject/trunk/pjnath/include/pjnath/config.h
===================================================================
--- /pjproject/trunk/pjnath/include/pjnath/config.h	(revision 1151)
+++ /pjproject/trunk/pjnath/include/pjnath/config.h	(revision 1152)
@@ -166,4 +166,23 @@
 
 /**
+ * According to ICE Section 8.2. Updating States, if an In-Progress pair in 
+ * the check list is for the same component as a nominated pair, the agent 
+ * SHOULD cease retransmissions for its check if its pair priority is lower
+ * than the lowest priority nominated pair for that component.
+ *
+ * If a higher priority check is In Progress, this rule would cause that
+ * check to be performed even when it most likely will fail.
+ *
+ * The macro here controls if ICE session should cancel all In Progress 
+ * checks for the same component regardless of its priority.
+ *
+ * Default: 1 (yes, cancel all)
+ */
+#ifndef PJ_ICE_CANCEL_ALL
+#   define PJ_ICE_CANCEL_ALL			    1
+#endif
+
+
+/**
  * Minimum interval value to be used for sending STUN keep-alive on the ICE
  * stream transport, in seconds. This minimum interval, plus a random value
@@ -196,5 +215,4 @@
 
 
-
 /**
  * @}
Index: /pjproject/trunk/pjnath/include/pjnath/ice_session.h
===================================================================
--- /pjproject/trunk/pjnath/include/pjnath/ice_session.h	(revision 1151)
+++ /pjproject/trunk/pjnath/include/pjnath/ice_session.h	(revision 1152)
@@ -420,4 +420,29 @@
 
 /**
+ * This structure represents an incoming check (an incoming Binding
+ * request message), and is mainly used to keep early checks in the
+ * list in the ICE session. An early check is a request received
+ * from remote when we haven't received SDP answer yet, therefore we
+ * can't perform triggered check. For such cases, keep the incoming
+ * request in a list, and we'll do triggered checks (simultaneously)
+ * as soon as we receive answer.
+ */
+typedef struct pj_ice_rx_check
+{
+    PJ_DECL_LIST_MEMBER(struct pj_ice_rx_check);
+
+    unsigned		 comp_id;	/**< Component ID.		*/
+
+    pj_sockaddr		 src_addr;	/**< Source address of request	*/
+    unsigned		 src_addr_len;	/**< Length of src address.	*/
+
+    pj_bool_t		 use_candidate;	/**< USE-CANDIDATE is present?	*/
+    pj_uint32_t		 priority;	/**< PRIORITY value in the req.	*/
+    pj_stun_uint64_attr *role_attr;	/**< ICE-CONTROLLING/CONTROLLED	*/
+
+} pj_ice_rx_check;
+
+
+/**
  * This structure describes the ICE session. For this version of PJNATH,
  * an ICE session corresponds to a single media stream (unlike the ICE
@@ -463,4 +488,7 @@
     unsigned		 rcand_cnt;		    /**< # of remote cand.  */
     pj_ice_sess_cand	 rcand[PJ_ICE_MAX_CAND];    /**< Array of cand.	    */
+
+    /* List of eearly checks */
+    pj_ice_rx_check	 early_check;		    /**< Early checks.	    */
 
     /* Checklist */
Index: /pjproject/trunk/pjnath/include/pjnath/stun_session.h
===================================================================
--- /pjproject/trunk/pjnath/include/pjnath/stun_session.h	(revision 1151)
+++ /pjproject/trunk/pjnath/include/pjnath/stun_session.h	(revision 1152)
@@ -359,4 +359,18 @@
 
 /**
+ * Explicitly request retransmission of the request. Normally application
+ * doesn't need to do this, but this functionality is needed by ICE to
+ * speed up connectivity check completion.
+ *
+ * @param sess	    The STUN session instance.
+ * @param tdata	    The request message previously sent.
+ *
+ * @return	    PJ_SUCCESS on success, or the appropriate error.
+ */
+PJ_DECL(pj_status_t) pj_stun_session_retransmit_req(pj_stun_session *sess,
+						    pj_stun_tx_data *tdata);
+
+
+/**
  * Application must call this function to notify the STUN session about
  * the arrival of STUN packet. The STUN packet MUST have been checked
Index: /pjproject/trunk/pjnath/include/pjnath/stun_transaction.h
===================================================================
--- /pjproject/trunk/pjnath/include/pjnath/stun_transaction.h	(revision 1151)
+++ /pjproject/trunk/pjnath/include/pjnath/stun_transaction.h	(revision 1152)
@@ -222,4 +222,14 @@
 						 unsigned pkt_len);
 
+/**
+ * Request to retransmit the request. Normally application should not need
+ * to call this function since retransmission would be handled internally,
+ * but this functionality is needed by ICE.
+ *
+ * @param tsx		The STUN client transaction instance.
+ *
+ * @return		PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_client_tsx_retransmit(pj_stun_client_tsx *tsx);
 
 
Index: /pjproject/trunk/pjnath/src/pjnath/ice_session.c
===================================================================
--- /pjproject/trunk/pjnath/src/pjnath/ice_session.c	(revision 1151)
+++ /pjproject/trunk/pjnath/src/pjnath/ice_session.c	(revision 1152)
@@ -81,4 +81,7 @@
 
 
+/* The data that will be attached to the STUN session on each
+ * component.
+ */
 typedef struct stun_data
 {
@@ -88,4 +91,8 @@
 } stun_data;
 
+
+/* The data that will be attached to the timer to perform
+ * periodic check.
+ */
 typedef struct timer_data
 {
@@ -95,5 +102,5 @@
 
 
-
+/* Forward declarations */
 static void destroy_ice(pj_ice_sess *ice,
 			pj_status_t reason);
@@ -101,5 +108,9 @@
 					pj_timer_entry *te);
 static void periodic_timer(pj_timer_heap_t *th, 
-			 pj_timer_entry *te);
+			  pj_timer_entry *te);
+static void handle_incoming_check(pj_ice_sess *ice,
+				  const pj_ice_rx_check *rcheck);
+
+/* These are the callbacks registered to the STUN sessions */
 static pj_status_t on_stun_send_msg(pj_stun_session *sess,
 				    const void *pkt,
@@ -126,4 +137,5 @@
 					 unsigned src_addr_len);
 
+/* These are the callbacks for performing STUN authentication */
 static pj_status_t stun_auth_get_auth(void *user_data,
 				      pj_pool_t *pool,
@@ -299,4 +311,5 @@
     }
 
+    pj_list_init(&ice->early_check);
 
     /* Done */
@@ -483,7 +496,19 @@
 	 * 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. 
+	 * contents.
 	 */
-	PJ_TODO(CHECK_USERNAME_FOR_INCOMING_STUN_REQUEST);
+	const char *pos;
+	pj_str_t ufrag;
+
+	pos = (const char*)pj_memchr(username->ptr, ':', username->slen);
+	if (pos == NULL)
+	    return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNKNOWN_USERNAME);
+
+	ufrag.ptr = (char*)username->ptr;
+	ufrag.slen = (pos - username->ptr);
+
+	if (pj_strcmp(&ufrag, &ice->rx_ufrag) != 0)
+	    return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNKNOWN_USERNAME);
+
 	*data_type = 0;
 	*data = ice->rx_pass;
@@ -921,18 +946,39 @@
      * 
      * 1.  The agent changes the states for all other Frozen pairs for the
-     * same media stream and same foundation to Waiting.  Typically
-     * these other pairs will have different component IDs but not
-     * always.
-     */
-
-
-    /* If there is at least one nominated pair in the valid list:
-     * - The agent MUST remove all Waiting and Frozen pairs in the check
-     *   list for the same component as the nominated pairs for that
-     *   media stream
-     * - If an In-Progress pair in the check list is for the same
-     *   component as a nominated pair, the agent SHOULD cease
-     *   retransmissions for its check if its pair priority is lower
-     *   than the lowest priority nominated pair for that component
+     *     same media stream and same foundation to Waiting.  Typically
+     *     these other pairs will have different component IDs but not
+     *     always.
+     */
+    if (check->err_code==PJ_SUCCESS) {
+	for (i=0; i<ice->clist.count; ++i) {
+	    pj_ice_sess_check *c = &ice->clist.checks[i];
+	    if (pj_strcmp(&c->lcand->foundation, &check->lcand->foundation)==0
+		 && c->state == PJ_ICE_SESS_CHECK_STATE_FROZEN)
+	    {
+		check_set_state(ice, c, PJ_ICE_SESS_CHECK_STATE_WAITING, 0);
+	    }
+	}
+    }
+
+    /* 8.2.  Updating States
+     * 
+     * For both controlling and controlled agents, the state of ICE
+     * processing depends on the presence of nominated candidate pairs in
+     * the valid list and on the state of the check list:
+     *
+     * o  If there are no nominated pairs in the valid list for a media
+     *    stream and the state of the check list is Running, ICE processing
+     *    continues.
+     *
+     * o  If there is at least one nominated pair in the valid list:
+     *
+     *    - The agent MUST remove all Waiting and Frozen pairs in the check
+     *      list for the same component as the nominated pairs for that
+     *      media stream
+     *
+     *    - If an In-Progress pair in the check list is for the same
+     *      component as a nominated pair, the agent SHOULD cease
+     *      retransmissions for its check if its pair priority is lower
+     *      than the lowest priority nominated pair for that component
      */
     if (check->err_code==PJ_SUCCESS && check->nominated) {
@@ -946,7 +992,11 @@
 
 	for (i=0; i<ice->clist.count; ++i) {
+
 	    pj_ice_sess_check *c = &ice->clist.checks[i];
+
 	    if (c->lcand->comp_id == check->lcand->comp_id) {
+
 		if (c->state < PJ_ICE_SESS_CHECK_STATE_IN_PROGRESS) {
+
 		    /* Just fail Frozen/Waiting check */
 		    LOG5((ice->obj_name, 
@@ -957,5 +1007,8 @@
 				    PJ_ECANCELLED);
 
-		} else if (c->state == PJ_ICE_SESS_CHECK_STATE_IN_PROGRESS) {
+		} else if (c->state == PJ_ICE_SESS_CHECK_STATE_IN_PROGRESS
+			   && (PJ_ICE_CANCEL_ALL ||
+			        CMP_CHECK_PRIO(c, check) < 0)) {
+
 		    /* State is IN_PROGRESS, cancel transaction */
 		    if (c->tdata) {
@@ -982,34 +1035,21 @@
     }
 
-    /* Once there is at least one nominated pair in the valid list for
-     * every component of at least one media stream:
-     * - The agent MUST change the state of processing for its check
-     *   list for that media stream to Completed.
-     * - The agent MUST continue to respond to any checks it may still
-     *   receive for that media stream, and MUST perform triggered
-     *   checks if required by the processing of Section 7.2.
-     * - The agent MAY begin transmitting media for this media stream as
-     *   described in Section 11.1
-     */
-    /* TODO */
-
-    /* Once there is at least one nominated pair in the valid list for
-     * each component of each media stream:
-     * - The agent sets the state of ICE processing overall to
-     *   Completed.
-     * - If an agent is controlling, it examines the highest priority
-     *   nominated candidate pair for each component of each media
-     *   stream.  If any of those candidate pairs differ from the
-     *   default candidate pairs in the most recent offer/answer
-     *   exchange, the controlling agent MUST generate an updated offer
-     *   as described in Section 9.  If the controlling agent is using
-     *   an aggressive nomination algorithm, this may result in several
-     *   updated offers as the pairs selected for media change.  An
-     *   agent MAY delay sending the offer for a brief interval (one
-     *   second is RECOMMENDED) in order to allow the selected pairs to
-     *   stabilize.
-     */
-    /* TODO */
-
+
+    /* Still in 8.2.  Updating States
+     * 
+     * o  Once there is at least one nominated pair in the valid list for
+     *    every component of at least one media stream and the state of the
+     *    check list is Running:
+     *    
+     *    *  The agent MUST change the state of processing for its check
+     *       list for that media stream to Completed.
+     *    
+     *    *  The agent MUST continue to respond to any checks it may still
+     *       receive for that media stream, and MUST perform triggered
+     *       checks if required by the processing of Section 7.2.
+     *    
+     *    *  The agent MAY begin transmitting media for this media stream as
+     *       described in Section 11.1
+     */
 
     /* See if all components have nominated pair. If they do, then mark
@@ -1025,4 +1065,27 @@
 	return PJ_TRUE;
     }
+
+    /* Note: this is the stuffs that we don't do in 7.1.2.2.2, since our
+     *       ICE session only supports one media stream for now:
+     * 
+     * 7.1.2.2.2.  Updating Pair States
+     *
+     * 2.  If there is a pair in the valid list for every component of this
+     *     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 success of this check may
+     *     unfreeze checks for other media streams. 
+     */
+
+    /* 7.1.2.3.  Check List and Timer State Updates
+     * Regardless of whether the check was successful or failed, the
+     * completion of the transaction may require updating of check list and
+     * timer states.
+     * 
+     * If all of the pairs in the check list are now either in the Failed or
+     * Succeeded state, and there is not a pair in the valid list for each
+     * component of the media stream, the state of the check list is set to
+     * Failed.  
+     */
 
     /* 
@@ -1366,5 +1429,10 @@
 }
 
-/* Start ICE check */
+
+/*
+ * Start ICE periodic check. This function will return immediately, and
+ * application will be notified about the connectivity check status in
+ * #pj_ice_sess_cb callback.
+ */
 PJ_DEF(pj_status_t) pj_ice_sess_start_check(pj_ice_sess *ice)
 {
@@ -1372,4 +1440,5 @@
     const pj_ice_sess_cand *cand0;
     const pj_str_t *flist[PJ_ICE_MAX_CAND];
+    pj_ice_rx_check *rcheck;
     unsigned i, flist_cnt = 0;
 
@@ -1429,4 +1498,15 @@
     }
 
+    /* First, perform all pending triggered checks, simultaneously. */
+    rcheck = ice->early_check.next;
+    while (rcheck != &ice->early_check) {
+	LOG4((ice->obj_name, 
+	      "Performing delayed triggerred check for component %d",
+	      rcheck->comp_id));
+	handle_incoming_check(ice, rcheck);
+	rcheck = rcheck->next;
+    }
+    pj_list_init(&ice->early_check);
+
     /* Start periodic check */
     return start_periodic_check(ice->stun_cfg.timer_heap, &clist->timer);
@@ -1492,6 +1572,9 @@
 
 	if (status==PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_ROLE_CONFLICT)) {
+
 	    /* Role conclict response.
+	     *
 	     * 7.1.2.1.  Failure Cases:
+	     *
 	     * If the request had contained the ICE-CONTROLLED attribute, 
 	     * the agent MUST switch to the controlling role if it has not
@@ -1518,5 +1601,5 @@
 	    if (new_role != ice->role) {
 		LOG4((ice->obj_name, 
-		      "Changing role because of role conflict"));
+		      "Changing role because of role conflict response"));
 		pj_ice_sess_change_role(ice, new_role);
 	    }
@@ -1544,5 +1627,7 @@
 
 
-    /* The agent MUST check that the source IP address and port of the
+    /* 7.1.2.1.  Failure Cases
+     *
+     * 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
@@ -1562,4 +1647,20 @@
     }
 
+    /* 7.1.2.2.  Success Cases
+     * 
+     * A check is considered to be a success if all of the following are
+     * true:
+     * 
+     * o  the STUN transaction generated a success response
+     * 
+     * o  the source IP address and port of the response equals the
+     *    destination IP address and port that the Binding Request was sent
+     *    to
+     * 
+     * o  the destination IP address and port of the response match the
+     *    source IP address and port that the Binding Request was sent from
+     */
+
+
     LOG4((ice->obj_name, 
 	 "Check %s%s: connectivity check SUCCESS",
@@ -1588,5 +1689,6 @@
     }
 
-    /* If the transport address returned in XOR-MAPPED-ADDRESS does not match
+    /* 7.1.2.2.1.  Discovering Peer Reflexive Candidates
+     * If the transport address returned in XOR-MAPPED-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.
@@ -1599,5 +1701,5 @@
 			       &check->lcand->base_addr);
 
-	/* According to: 7.1.2.2.1. Discovering Peer Reflexive Candidates:
+	/* Still in 7.1.2.2.1.  Discovering Peer Reflexive Candidates
 	 * Its priority is set equal to the value of the PRIORITY attribute
          * in the Binding Request.
@@ -1628,5 +1730,12 @@
     }
 
+    /* 7.1.2.2.3.  Constructing a Valid Pair
+     * Next, the agent constructs a candidate pair whose local candidate
+     * equals the mapped address of the response, and whose remote candidate
+     * equals the destination address to which the request was sent.    
+     */
+
     /* Add pair to valid list */
+    pj_assert(ice->valid_list.count < PJ_ICE_MAX_CHECKS);
     new_check = &ice->valid_list.checks[ice->valid_list.count++];
     new_check->lcand = lcand;
@@ -1641,11 +1750,14 @@
 
 
-    /* Sets the state of the original pair that generated the check to 
-     * succeeded. 
+    /* 7.1.2.2.2.  Updating Pair States
+     * 
+     * The agent sets the state of the pair that generated the check to
+     * Succeeded.  The success of this check might also cause the state of
+     * other checks to change as well.
      */
     check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_SUCCEEDED, 
 		    PJ_SUCCESS);
 
-    /* Inform about check completion.
+    /* Perform 7.1.2.2.2.  Updating Pair States.
      * This may terminate ICE processing.
      */
@@ -1656,50 +1768,7 @@
     }
 
-    /* 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 (lcand->comp_id == 1) {
-	unsigned i;
-	pj_bool_t unfrozen = PJ_FALSE;
-
-	for (i=0; i<clist->count; ++i)  {
-	    pj_ice_sess_check *c = &clist->checks[i];
-
-	    if (c->state == PJ_ICE_SESS_CHECK_STATE_FROZEN &&
-		c->lcand->comp_id != lcand->comp_id &&
-		pj_strcmp(&c->lcand->foundation, &lcand->foundation)==0)
-	    {
-		/* Unfreeze and start check */
-		check_set_state(ice, c, PJ_ICE_SESS_CHECK_STATE_WAITING, 
-				PJ_SUCCESS);
-		unfrozen = PJ_TRUE;
-	    }
-	}
-
-	if (unfrozen && clist->timer.id == PJ_FALSE)
-	    start_periodic_check(ice->stun_cfg.timer_heap, &clist->timer);
-    } 
-
-    /* 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);
-    }
-
     pj_mutex_unlock(ice->mutex);
 }
+
 
 /* This callback is called by the STUN session associated with a candidate
@@ -1715,12 +1784,9 @@
     stun_data *sd;
     pj_ice_sess *ice;
-    pj_stun_priority_attr *ap;
-    pj_stun_use_candidate_attr *uc;
-    pj_ice_sess_comp *comp;
-    pj_ice_sess_cand *lcand = NULL;
-    pj_ice_sess_cand *rcand;
-    unsigned i;
+    pj_stun_priority_attr *prio_attr;
+    pj_stun_use_candidate_attr *uc_attr;
+    pj_stun_uint64_attr *role_attr;
     pj_stun_tx_data *tdata;
-    pj_bool_t is_relayed;
+    pj_ice_rx_check *rcheck, tmp_rcheck;
     pj_status_t status;
 
@@ -1743,12 +1809,19 @@
     sd = (stun_data*) pj_stun_session_get_user_data(sess);
     ice = sd->ice;
-    comp = sd->comp;
 
     pj_mutex_lock(ice->mutex);
 
+    /*
+     * Note:
+     *  Be aware that when STUN request is received, we might not get
+     *  SDP answer yet, so we might not have remote candidates and
+     *  checklist yet. This case will be handled after we send
+     *  a response.
+     */
+
     /* Get PRIORITY attribute */
-    ap = (pj_stun_priority_attr*)
-	 pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_PRIORITY, 0);
-    if (ap == 0) {
+    prio_attr = (pj_stun_priority_attr*)
+	        pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_PRIORITY, 0);
+    if (prio_attr == NULL) {
 	LOG5((ice->obj_name, "Received Binding request with no PRIORITY"));
 	pj_mutex_unlock(ice->mutex);
@@ -1757,15 +1830,61 @@
 
     /* Get USE-CANDIDATE attribute */
-    uc = (pj_stun_use_candidate_attr*)
-	 pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_USE_CANDIDATE, 0);
-
-    /* For simplicity, ignore incoming requests when we don't have remote
-     * candidates yet. The peer agent should retransmit the STUN request
-     * and we'll receive it again later.
-     */
-    if (ice->rcand_cnt == 0) {
-	pj_mutex_unlock(ice->mutex);
-	return PJ_SUCCESS;
-    }
+    uc_attr = (pj_stun_use_candidate_attr*)
+	      pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_USE_CANDIDATE, 0);
+
+
+    /* Get ICE-CONTROLLING or ICE-CONTROLLED */
+    role_attr = (pj_stun_uint64_attr*)
+	        pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ICE_CONTROLLING, 0);
+    if (role_attr == NULL) {
+	role_attr = (pj_stun_uint64_attr*)
+	            pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ICE_CONTROLLED, 0);
+    }
+
+    /* 7.2.1.1.  Detecting and Repairing Role Conflicts
+     */
+    if (ice->role == PJ_ICE_SESS_ROLE_CONTROLLING &&
+	role_attr && role_attr->hdr.type == PJ_STUN_ATTR_ICE_CONTROLLING)
+    {
+	if (pj_cmp_timestamp(&ice->tie_breaker, &role_attr->value) < 0) {
+	    /* Switch role to controlled */
+	    LOG4((ice->obj_name, 
+		  "Changing role because of ICE-CONTROLLING attribute"));
+	    pj_ice_sess_change_role(ice, PJ_ICE_SESS_ROLE_CONTROLLED);
+	} else {
+	    /* Generate 487 response */
+	    status = pj_stun_session_create_response(sess, msg, 
+					             PJ_STUN_SC_ROLE_CONFLICT,
+						     NULL, &tdata);
+	    if (status == PJ_SUCCESS) {
+		pj_stun_session_send_msg(sess, PJ_TRUE, 
+				         src_addr, src_addr_len, tdata);
+	    }
+	    pj_mutex_unlock(ice->mutex);
+	    return PJ_SUCCESS;
+	}
+
+    } else if (ice->role == PJ_ICE_SESS_ROLE_CONTROLLED &&
+	       role_attr && role_attr->hdr.type == PJ_STUN_ATTR_ICE_CONTROLLED)
+    {
+	if (pj_cmp_timestamp(&ice->tie_breaker, &role_attr->value) < 0) {
+	    /* Generate 487 response */
+	    status = pj_stun_session_create_response(sess, msg, 
+					             PJ_STUN_SC_ROLE_CONFLICT,
+						     NULL, &tdata);
+	    if (status == PJ_SUCCESS) {
+		pj_stun_session_send_msg(sess, PJ_TRUE, 
+				         src_addr, src_addr_len, tdata);
+	    }
+	    pj_mutex_unlock(ice->mutex);
+	    return PJ_SUCCESS;
+	} else {
+	    /* Switch role to controlled */
+	    LOG4((ice->obj_name, 
+		  "Changing role because of ICE-CONTROLLED attribute"));
+	    pj_ice_sess_change_role(ice, PJ_ICE_SESS_ROLE_CONTROLLING);
+	}
+    }
+
 
     /* 
@@ -1786,13 +1905,66 @@
 
 
+    /* 
+     * Handling early check.
+     *
+     * It's possible that we receive this request before we receive SDP
+     * answer. In this case, we can't perform trigger check since we
+     * don't have checklist yet, so just save this check in a pending
+     * triggered check array to be acted upon later.
+     */
+    if (ice->rcand_cnt == 0) {
+	rcheck = PJ_POOL_ZALLOC_T(ice->pool, pj_ice_rx_check);
+    } else {
+	rcheck = &tmp_rcheck;
+    }
+
+    /* Init rcheck */
+    rcheck->comp_id = sd->comp_id;
+    rcheck->src_addr_len = src_addr_len;
+    pj_memcpy(&rcheck->src_addr, src_addr, src_addr_len);
+    rcheck->use_candidate = (uc_attr != NULL);
+    rcheck->priority = prio_attr->value;
+    rcheck->role_attr = role_attr;
+
+    if (ice->rcand_cnt == 0) {
+	/* We don't have answer yet, so keep this request for later */
+	LOG4((ice->obj_name, "Received an early check for comp %d",
+	      rcheck->comp_id));
+	pj_list_push_back(&ice->early_check, rcheck);
+    } else {
+	/* Handle this check */
+	handle_incoming_check(ice, rcheck);
+    }
+
+    pj_mutex_unlock(ice->mutex);
+    return PJ_SUCCESS;
+}
+
+
+/* Handle incoming Binding request and perform triggered check.
+ * This function may be called by on_stun_rx_request(), or when
+ * SDP answer is received and we have received early checks.
+ */
+static void handle_incoming_check(pj_ice_sess *ice,
+				  const pj_ice_rx_check *rcheck)
+{
+    pj_ice_sess_comp *comp;
+    pj_ice_sess_cand *lcand = NULL;
+    pj_ice_sess_cand *rcand;
+    unsigned i;
+    pj_bool_t is_relayed;
+
+    comp = find_comp(ice, rcheck->comp_id);
+
     /* Find remote candidate based on the source transport address of 
      * the request.
      */
     for (i=0; i<ice->rcand_cnt; ++i) {
-	if (sockaddr_cmp((const pj_sockaddr*)src_addr, &ice->rcand[i].addr)==0)
+	if (sockaddr_cmp(&rcheck->src_addr, &ice->rcand[i].addr)==0)
 	    break;
     }
 
-    /* If the source transport address of the request does not match any
+    /* 7.2.1.3.  Learning Peer Reflexive Candidates
+     * If the source transport address of the request does not match any
      * existing remote candidates, it represents a new peer reflexive remote
      * candidate.
@@ -1800,12 +1972,12 @@
     if (i == ice->rcand_cnt) {
 	rcand = &ice->rcand[ice->rcand_cnt++];
-	rcand->comp_id = sd->comp_id;
+	rcand->comp_id = rcheck->comp_id;
 	rcand->type = PJ_ICE_CAND_TYPE_PRFLX;
-	rcand->prio = ap->value;
-	pj_memcpy(&rcand->addr, src_addr, src_addr_len);
+	rcand->prio = rcheck->priority;
+	pj_memcpy(&rcand->addr, &rcheck->src_addr, rcheck->src_addr_len);
 
 	/* Foundation is random, unique from other foundation */
-	rcand->foundation.ptr = pj_pool_alloc(ice->pool, 32);
-	rcand->foundation.slen = pj_ansi_snprintf(rcand->foundation.ptr, 32,
+	rcand->foundation.ptr = pj_pool_alloc(ice->pool, 36);
+	rcand->foundation.slen = pj_ansi_snprintf(rcand->foundation.ptr, 36,
 						  "f%p", 
 						  rcand->foundation.ptr);
@@ -1843,10 +2015,17 @@
     for (i=0; i<ice->clist.count; ++i) {
 	pj_ice_sess_check *c = &ice->clist.checks[i];
-	if (c->lcand->comp_id == sd->comp_id) {
+	if (c->lcand->comp_id == rcheck->comp_id) {
 	    lcand = c->lcand;
 	    break;
 	}
     }
-    pj_assert(lcand != NULL);
+    if (lcand == NULL) {
+	/* Should not happen, but just in case remote is sending a
+	 * Binding request for a component which it doesn't have.
+	 */
+	LOG4((ice->obj_name, 
+	     "Received Binding request but no local candidate is found!"));
+	return;
+    }
 #endif
 
@@ -1860,5 +2039,8 @@
     is_relayed = PJ_FALSE;
 
-    /* Now that we have local and remote candidate, check if we already
+    /* 
+     * 7.2.1.4.  Triggered Checks
+     *
+     * Now that we have local and remote candidate, check if we already
      * have this pair in our checklist.
      */
@@ -1888,5 +2070,5 @@
 	 * Note: DO NOT overwrite nominated flag if one is already set.
 	 */
-	c->nominated = ((uc != NULL) || c->nominated);
+	c->nominated = ((rcheck->use_candidate) || c->nominated);
 
 	if (c->state == PJ_ICE_SESS_CHECK_STATE_FROZEN ||
@@ -1897,9 +2079,10 @@
 
 	} else if (c->state == PJ_ICE_SESS_CHECK_STATE_IN_PROGRESS) {
-	    /* Should retransmit here, but how??
-	     * TODO
+	    /* Should retransmit immediately
 	     */
 	    LOG5((ice->obj_name, "Triggered check for check %d not performed "
-				"because it's in progress", i));
+		  "because it's in progress. Retransmitting", i));
+	    pj_stun_session_retransmit_req(comp->stun_sess, c->tdata);
+
 	} else if (c->state == PJ_ICE_SESS_CHECK_STATE_SUCCEEDED) {
 	    /* Check complete for this component.
@@ -1913,6 +2096,5 @@
 	    complete = on_check_complete(ice, c);
 	    if (complete) {
-		pj_mutex_unlock(ice->mutex);
-		return PJ_SUCCESS;
+		return;
 	    }
 	}
@@ -1933,5 +2115,5 @@
 	c->prio = CALC_CHECK_PRIO(ice, lcand, rcand);
 	c->state = PJ_ICE_SESS_CHECK_STATE_WAITING;
-	c->nominated = (uc != NULL);
+	c->nominated = rcheck->use_candidate;
 	c->err_code = PJ_SUCCESS;
 
@@ -1944,7 +2126,4 @@
 	     "TOO MANY CHECKS IN CHECKLIST!"));
     }
-
-    pj_mutex_unlock(ice->mutex);
-    return status;
 }
 
Index: /pjproject/trunk/pjnath/src/pjnath/stun_msg_dump.c
===================================================================
--- /pjproject/trunk/pjnath/src/pjnath/stun_msg_dump.c	(revision 1151)
+++ /pjproject/trunk/pjnath/src/pjnath/stun_msg_dump.c	(revision 1152)
@@ -228,5 +228,5 @@
 
     len = pj_ansi_snprintf(p, end-p, 
-			   " Hdr: length=%d, magic=%08x, tsx_id=%08x %08x %08x\n"
+			   " Hdr: length=%d, magic=%08x, tsx_id=%08x%08x%08x\n"
 			   " Attributes:\n",
 			   msg->hdr.length,
Index: /pjproject/trunk/pjnath/src/pjnath/stun_session.c
===================================================================
--- /pjproject/trunk/pjnath/src/pjnath/stun_session.c	(revision 1151)
+++ /pjproject/trunk/pjnath/src/pjnath/stun_session.c	(revision 1152)
@@ -711,6 +711,26 @@
     pj_mutex_unlock(sess->mutex);
     return PJ_SUCCESS;
-
-}
+}
+
+/*
+ * Explicitly request retransmission of the request.
+ */
+PJ_DEF(pj_status_t) pj_stun_session_retransmit_req(pj_stun_session *sess,
+						   pj_stun_tx_data *tdata)
+{
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(sess && tdata, PJ_EINVAL);
+    PJ_ASSERT_RETURN(PJ_STUN_IS_REQUEST(tdata->msg->hdr.type), PJ_EINVAL);
+
+    pj_mutex_lock(sess->mutex);
+
+    status = pj_stun_client_tsx_retransmit(tdata->client_tsx);
+
+    pj_mutex_unlock(sess->mutex);
+
+    return status;
+}
+
 
 /* Send response */
Index: /pjproject/trunk/pjnath/src/pjnath/stun_transaction.c
===================================================================
--- /pjproject/trunk/pjnath/src/pjnath/stun_transaction.c	(revision 1151)
+++ /pjproject/trunk/pjnath/src/pjnath/stun_transaction.c	(revision 1152)
@@ -295,4 +295,20 @@
 }
 
+/*
+ * Request to retransmit the request.
+ */
+PJ_DEF(pj_status_t) pj_stun_client_tsx_retransmit(pj_stun_client_tsx *tsx)
+{
+    if (tsx->destroy_timer.id != 0) {
+	return PJ_SUCCESS;
+    }
+
+    if (tsx->retransmit_timer.id != 0) {
+	pj_timer_heap_cancel(tsx->cfg->timer_heap, &tsx->retransmit_timer);
+	tsx->retransmit_timer.id = 0;
+    }
+
+    return tsx_transmit_msg(tsx);
+}
 
 /* Timer callback to destroy transaction */
Index: /pjproject/trunk/pjnath/src/pjstun-client/client_main.c
===================================================================
--- /pjproject/trunk/pjnath/src/pjstun-client/client_main.c	(revision 1151)
+++ /pjproject/trunk/pjnath/src/pjstun-client/client_main.c	(revision 1152)
@@ -30,5 +30,7 @@
 #define REQ_IP		NULL		    /* IP address string */
 
-#define OPTIONS		PJ_STUN_NO_AUTHENTICATE
+//#define OPTIONS		PJ_STUN_NO_AUTHENTICATE
+#define OPTIONS		0
+
 
 static struct global
