Index: pjproject/trunk/pjsip/src/pjsip-ua/sip_reg.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsip-ua/sip_reg.c	(revision 1946)
+++ pjproject/trunk/pjsip/src/pjsip-ua/sip_reg.c	(revision 1959)
@@ -36,5 +36,5 @@
 
 #define REFRESH_TIMER		1
-#define DELAY_BEFORE_REFRESH	5
+#define DELAY_BEFORE_REFRESH	PJSIP_REGISTER_CLIENT_DELAY_BEFORE_REFRESH
 #define THIS_FILE		"sip_reg.c"
 
@@ -44,4 +44,16 @@
 #define REGC_TSX_TIMEOUT	33000
 
+enum { NOEXP = 0x1FFFFFFF };
+
+static const pj_str_t XUID_PARAM_NAME = { "x-uid", 5 };
+
+
+/* Current/pending operation */
+enum regc_op
+{
+    REGC_IDLE,
+    REGC_REGISTERING,
+    REGC_UNREGISTERING
+};
 
 /**
@@ -55,4 +67,7 @@
     pj_bool_t			 has_tsx;
     pj_int32_t			 busy;
+    enum regc_op		 current_op;
+
+    pj_bool_t			 add_xuid_param;
 
     void			*token;
@@ -66,8 +81,7 @@
     pjsip_from_hdr		*from_hdr;
     pjsip_to_hdr		*to_hdr;
-    pjsip_hdr			 contact_hdr_list;
+    pjsip_contact_hdr		 contact_hdr_list;
+    pjsip_contact_hdr		 removed_contact_hdr_list;
     pjsip_expires_hdr		*expires_hdr;
-    pjsip_contact_hdr		*unreg_contact_hdr;
-    pjsip_expires_hdr		*unreg_expires_hdr;
     pj_uint32_t			 expires;
     pjsip_route_hdr		 route_set;
@@ -93,5 +107,4 @@
 
 
-
 PJ_DEF(pj_status_t) pjsip_regc_create( pjsip_endpoint *endpt, void *token,
 				       pjsip_regc_cb *cb,
@@ -115,4 +128,5 @@
     regc->cb = cb;
     regc->expires = PJSIP_REGC_EXPIRATION_NOT_SPECIFIED;
+    regc->add_xuid_param = pjsip_cfg()->regc.add_xuid_param;
 
     status = pjsip_auth_clt_init(&regc->auth_sess, endpt, regc->pool, 0);
@@ -123,4 +137,5 @@
     pj_list_init(&regc->hdr_list);
     pj_list_init(&regc->contact_hdr_list);
+    pj_list_init(&regc->removed_contact_hdr_list);
 
     /* Done */
@@ -142,4 +157,8 @@
 	    pjsip_transport_dec_ref(regc->last_transport);
 	    regc->last_transport = NULL;
+	}
+	if (regc->timer.id != 0) {
+	    pjsip_endpt_cancel_timer(regc->endpt, &regc->timer);
+	    regc->timer.id = 0;
 	}
 	pjsip_endpt_release_pool(regc->endpt, regc->pool);
@@ -200,15 +219,29 @@
 {
     const pj_str_t CONTACT = { "Contact", 7 };
+    pjsip_contact_hdr *h;
     int i;
     
-    /* Clear existing contacts */
-    pj_list_init(&regc->contact_hdr_list);
-
+    /* Save existing contact list to removed_contact_hdr_list and
+     * clear contact_hdr_list.
+     */
+    pj_list_merge_last(&regc->removed_contact_hdr_list, 
+		       &regc->contact_hdr_list);
+
+    /* Set the expiration of Contacts in to removed_contact_hdr_list 
+     * zero.
+     */
+    h = regc->removed_contact_hdr_list.next;
+    while (h != &regc->removed_contact_hdr_list) {
+	h->expires = 0;
+	h = h->next;
+    }
+
+    /* Process new contacts */
     for (i=0; i<contact_cnt; ++i) {
-	pjsip_hdr *hdr;
+	pjsip_contact_hdr *hdr;
 	pj_str_t tmp;
 
 	pj_strdup_with_null(regc->pool, &tmp, &contact[i]);
-	hdr = (pjsip_hdr*)
+	hdr = (pjsip_contact_hdr*)
               pjsip_parse_hdr(regc->pool, &CONTACT, tmp.ptr, tmp.slen, NULL);
 	if (hdr == NULL) {
@@ -216,4 +249,40 @@
 		     (int)tmp.slen, tmp.ptr));
 	    return PJSIP_EINVALIDURI;
+	}
+
+	/* Find the new contact in old contact list. If found, remove
+	 * the old header from the old header list.
+	 */
+	h = regc->removed_contact_hdr_list.next;
+	while (h != &regc->removed_contact_hdr_list) {
+	    int rc;
+
+	    rc = pjsip_uri_cmp(PJSIP_URI_IN_CONTACT_HDR, 
+			       h->uri, hdr->uri);
+	    if (rc == 0) {
+		/* Match */
+		pj_list_erase(h);
+		break;
+	    }
+
+	    h = h->next;
+	}
+
+	/* If add_xuid_param option is enabled and Contact URI is sip/sips,
+	 * add xuid parameter to assist matching the Contact URI in the 
+	 * REGISTER response later.
+	 */
+	if (regc->add_xuid_param && (PJSIP_URI_SCHEME_IS_SIP(hdr->uri) ||
+				     PJSIP_URI_SCHEME_IS_SIPS(hdr->uri))) 
+	{
+	    pjsip_param *xuid_param;
+	    pjsip_sip_uri *sip_uri;
+
+	    xuid_param = PJ_POOL_ZALLOC_T(regc->pool, pjsip_param);
+	    xuid_param->name = XUID_PARAM_NAME;
+	    pj_create_unique_string(regc->pool, &xuid_param->value);
+
+	    sip_uri = pjsip_uri_get_uri(hdr->uri);
+	    pj_list_push_back(&sip_uri->other_param, xuid_param);
 	}
 
@@ -288,11 +357,4 @@
     regc->cseq_hdr->cseq = pj_rand() % 0xFFFF;
     pjsip_method_set( &regc->cseq_hdr->method, PJSIP_REGISTER_METHOD);
-
-    /* Create "Contact" header used in unregistration. */
-    regc->unreg_contact_hdr = pjsip_contact_hdr_create(regc->pool);
-    regc->unreg_contact_hdr->star = 1;
-
-    /* Create "Expires" header used in unregistration. */
-    regc->unreg_expires_hdr = pjsip_expires_hdr_create( regc->pool, 0);
 
     /* Done. */
@@ -437,5 +499,5 @@
 {
     pjsip_msg *msg;
-    pjsip_hdr *hdr;
+    pjsip_contact_hdr *hdr;
     pj_status_t status;
     pjsip_tx_data *tdata;
@@ -457,4 +519,13 @@
     }
 
+    /* Also add bindings which are to be removed */
+    while (!pj_list_empty(&regc->removed_contact_hdr_list)) {
+	hdr = regc->removed_contact_hdr_list.next;
+	pjsip_msg_add_hdr(msg, (pjsip_hdr*)
+			       pjsip_hdr_clone(tdata->pool, hdr));
+	pj_list_erase(hdr);
+    }
+    
+
     if (regc->expires_hdr)
 	pjsip_msg_add_hdr(msg, (pjsip_hdr*)
@@ -497,6 +568,6 @@
 
     /* Add Contact headers. */
-    hdr = regc->contact_hdr_list.next;
-    while (hdr != &regc->contact_hdr_list) {
+    hdr = (pjsip_hdr*)regc->contact_hdr_list.next;
+    while (hdr != (pjsip_hdr*)&regc->contact_hdr_list) {
 	pjsip_msg_add_hdr(msg, (pjsip_hdr*)
 			       pjsip_hdr_shallow_clone(tdata->pool, hdr));
@@ -504,5 +575,15 @@
     }
 
-    pjsip_msg_add_hdr( msg, (pjsip_hdr*)regc->unreg_expires_hdr);
+    /* Also add bindings which are to be removed */
+    while (!pj_list_empty(&regc->removed_contact_hdr_list)) {
+	hdr = (pjsip_hdr*)regc->removed_contact_hdr_list.next;
+	pjsip_msg_add_hdr(msg, (pjsip_hdr*)
+			       pjsip_hdr_clone(tdata->pool, hdr));
+	pj_list_erase(hdr);
+    }
+
+    /* Add Expires:0 header */
+    hdr = (pjsip_hdr*) pjsip_expires_hdr_create(tdata->pool, 0);
+    pjsip_msg_add_hdr(msg, hdr);
 
     *p_tdata = tdata;
@@ -514,4 +595,6 @@
 {
     pjsip_tx_data *tdata;
+    pjsip_contact_hdr *hcontact;
+    pjsip_hdr *hdr;
     pjsip_msg *msg;
     pj_status_t status;
@@ -529,6 +612,16 @@
 
     msg = tdata->msg;
-    pjsip_msg_add_hdr( msg, (pjsip_hdr*)regc->unreg_contact_hdr);
-    pjsip_msg_add_hdr( msg, (pjsip_hdr*)regc->unreg_expires_hdr);
+
+    /* Clear removed_contact_hdr_list */
+    pj_list_init(&regc->removed_contact_hdr_list);
+
+    /* Add Contact:* header */
+    hcontact = pjsip_contact_hdr_create(tdata->pool);
+    hcontact->star = 1;
+    pjsip_msg_add_hdr(msg, (pjsip_hdr*)hcontact);
+    
+    /* Add Expires:0 header */
+    hdr = (pjsip_hdr*) pjsip_expires_hdr_create(tdata->pool, 0);
+    pjsip_msg_add_hdr(msg, hdr);
 
     *p_tdata = tdata;
@@ -616,4 +709,171 @@
 }
 
+static pj_int32_t calculate_response_expiration(const pjsip_regc *regc,
+					        const pjsip_rx_data *rdata,
+						unsigned *contact_cnt,
+						unsigned max_contact,
+						pjsip_contact_hdr *contacts[])
+{
+    pj_int32_t expiration = NOEXP;
+    const pjsip_msg *msg = rdata->msg_info.msg;
+    const pjsip_hdr *hdr;
+
+    /* Enumerate all Contact headers in the response */
+    *contact_cnt = 0;
+    for (hdr=msg->hdr.next; hdr!=&msg->hdr; hdr=hdr->next) {
+	if (hdr->type == PJSIP_H_CONTACT && 
+	    *contact_cnt < max_contact) 
+	{
+	    contacts[*contact_cnt] = (pjsip_contact_hdr*)hdr;
+	    ++(*contact_cnt);
+	}
+    }
+
+    if (regc->current_op == REGC_REGISTERING) {
+	pj_bool_t has_our_contact = PJ_FALSE;
+	const pjsip_expires_hdr *expires;
+
+	/* Get Expires header */
+	expires = (const pjsip_expires_hdr*)
+		  pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL);
+
+	/* Try to find the Contact URIs that we register, in the response
+	 * to get the expires value. We'll try both with comparing the URI
+	 * and comparing the extension param only.
+	 */
+	if (pjsip_cfg()->regc.check_contact || regc->add_xuid_param) {
+	    unsigned i;
+	    for (i=0; i<*contact_cnt; ++i) {
+		const pjsip_contact_hdr *our_hdr;
+
+		our_hdr = (const pjsip_contact_hdr*) 
+			  regc->contact_hdr_list.next;
+
+		/* Match with our Contact header(s) */
+		while ((void*)our_hdr != (void*)&regc->contact_hdr_list) {
+
+		    const pjsip_uri *uri1, *uri2;
+		    pj_bool_t matched = PJ_FALSE;
+
+		    /* Exclude the display name when comparing the URI 
+		     * since server may not return it.
+		     */
+		    uri1 = (const pjsip_uri*)
+			   pjsip_uri_get_uri(contacts[i]->uri);
+		    uri2 = (const pjsip_uri*)
+			   pjsip_uri_get_uri(our_hdr->uri);
+
+		    /* First try with exact matching, according to RFC 3261
+		     * Section 19.1.4 URI Comparison
+		     */
+		    if (pjsip_cfg()->regc.check_contact) {
+			matched = pjsip_uri_cmp(PJSIP_URI_IN_CONTACT_HDR,
+						uri1, uri2)==0;
+		    }
+
+		    /* If no match is found, try with matching the extension
+		     * parameter only if extension parameter was added.
+		     */
+		    if (!matched && regc->add_xuid_param &&
+			(PJSIP_URI_SCHEME_IS_SIP(uri1) ||
+			 PJSIP_URI_SCHEME_IS_SIPS(uri1)) && 
+			(PJSIP_URI_SCHEME_IS_SIP(uri2) ||
+			 PJSIP_URI_SCHEME_IS_SIPS(uri2))) 
+		    {
+			const pjsip_sip_uri *sip_uri1, *sip_uri2;
+			const pjsip_param *p1, *p2;
+		
+			sip_uri1 = (const pjsip_sip_uri*)uri1;
+			sip_uri2 = (const pjsip_sip_uri*)uri2;
+
+			p1 = pjsip_param_cfind(&sip_uri1->other_param,
+					       &XUID_PARAM_NAME);
+			p2 = pjsip_param_cfind(&sip_uri2->other_param,
+					       &XUID_PARAM_NAME);
+			matched = p1 && p2 &&
+				  pj_strcmp(&p1->value, &p2->value)==0;
+
+		    }
+
+		    if (matched) {
+			has_our_contact = PJ_TRUE;
+
+			if (contacts[i]->expires >= 0 && 
+			    contacts[i]->expires < expiration) 
+			{
+			    /* Get the lowest expiration time. */
+			    expiration = contacts[i]->expires;
+			}
+
+			break;
+		    }
+
+		    our_hdr = our_hdr->next;
+
+		} /* while ((void.. */
+
+	    }  /* for (i=.. */
+
+	    /* If matching Contact header(s) are found but the
+	     * header doesn't contain expires parameter, get the
+	     * expiration value from the Expires header. And
+	     * if Expires header is not present, get the expiration
+	     * value from the request.
+	     */
+	    if (has_our_contact && expiration == NOEXP) {
+		if (expires) {
+		    expiration = expires->ivalue;
+		} else if (regc->expires_hdr) {
+		    expiration = regc->expires_hdr->ivalue;
+		} else {
+		    /* We didn't request explicit expiration value,
+		     * and server doesn't specify it either. This 
+		     * shouldn't happen unless we have a broken
+		     * registrar.
+		     */
+		    expiration = 3600;
+		}
+	    }
+
+	}
+
+	/* If we still couldn't get matching Contact header(s), it means
+	 * there must be something wrong with the  registrar (e.g. it may
+	 * have modified the URI's in the response, which is prohibited).
+	 */
+	if (expiration==NOEXP) {
+	    /* If the number of Contact headers in the response matches 
+	     * ours, they're all probably ours. Get the expiration
+	     * from there if this is the case, or from Expires header
+	     * if we don't have exact Contact header count, or
+	     * from the request as the last resort.
+	     */
+	    unsigned our_contact_cnt;
+
+	    our_contact_cnt = pj_list_size(&regc->contact_hdr_list);
+
+	    if (*contact_cnt == our_contact_cnt && *contact_cnt &&
+		contacts[0]->expires >= 0) 
+	    {
+		expiration = contacts[0]->expires;
+	    } else if (expires)
+		expiration = expires->ivalue;
+	    else if (regc->expires_hdr)
+		expiration = regc->expires_hdr->ivalue;
+	    else
+		expiration = 3600;
+	}
+
+    } else {
+	/* Just assume that the unregistration has been successful. */
+	expiration = 0;
+    }
+
+    /* Must have expiration value by now */
+    pj_assert(expiration != NOEXP);
+
+    return expiration;
+}
+
 static void tsx_callback(void *token, pjsip_event *event)
 {
@@ -645,4 +905,7 @@
 	pjsip_rx_data *rdata = event->body.tsx_state.src.rdata;
 	pjsip_tx_data *tdata;
+
+	/* reset current op */
+	regc->current_op = REGC_IDLE;
 
 	status = pjsip_auth_clt_reinit_req( &regc->auth_sess,
@@ -683,112 +946,28 @@
 	 */
 
-	/* Nothing to do */
-	;
+	/* Just reset current op */
+	regc->current_op = REGC_IDLE;
 
     } else {
-	int contact_cnt = 0;
+	pjsip_rx_data *rdata;
+	pj_int32_t expiration = NOEXP;
+	unsigned contact_cnt = 0;
 	pjsip_contact_hdr *contact[PJSIP_REGC_MAX_CONTACT];
-	pjsip_rx_data *rdata;
-	enum { NOEXP = 0x1FFFFFFF };
-	pj_int32_t expiration = NOEXP;
 
 	if (tsx->status_code/100 == 2) {
-	    int i;
-	    pjsip_contact_hdr *hdr;
-	    pjsip_msg *msg;
-	    pj_bool_t has_our_contact = PJ_FALSE;
-	    pjsip_expires_hdr *expires;
 
 	    rdata = event->body.tsx_state.src.rdata;
-	    msg = rdata->msg_info.msg;
-
-	    /* Record all Contact headers in the response */
-	    hdr = (pjsip_contact_hdr*)
-		  pjsip_msg_find_hdr( msg, PJSIP_H_CONTACT, NULL);
-	    while (hdr) {
-		contact[contact_cnt++] = hdr;
-		hdr = hdr->next;
-		if (hdr == (void*)&msg->hdr)
-		    break;
-		hdr = (pjsip_contact_hdr*)
-		      pjsip_msg_find_hdr(msg, PJSIP_H_CONTACT, hdr);
-	    }
-
-	    /* Set default expiration value to the value of Expires hdr */
-	    expires = (pjsip_expires_hdr*)
-		      pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL);
-
-	    if (expires)
-		expiration = expires->ivalue;
-	    
-	    /* Enumerate all Contact headers found in the response and
-	     * find the Contact(s) that we register.
-	     *
-	     * Note: 
-	     *	by default we require that the exact same URI that we
-	     *  register is returned in the 200/OK response (by exact,
-	     *  meaning all URI components including transport param),
-	     *  otherwise if we don't detect that our URI is there, we
-	     *  treat registration as failed.
-	     *
-	     *  If your registrar server couldn't do this, you can
-	     *  disable this exact URI checking. See the compile time
-	     *  setting PJSIP_REGISTER_CLIENT_CHECK_CONTACT or the
-	     *  corresponding run-time setting in pjsip_cfg().
-	     */
-	    for (i=0; i<contact_cnt && pjsip_cfg()->regc.check_contact; ++i) {
-		pjsip_contact_hdr *our_contact;
-
-		our_contact = (pjsip_contact_hdr*)
-			      regc->contact_hdr_list.next;
-
-		while ((void*)our_contact != (void*)&regc->contact_hdr_list) {
-
-		    const pjsip_uri *uri1, *uri2;
-
-		    /* Compare URIs.
-		     * Exclude the display name when comparing the URI since
-		     * server may not return it.
-		     */
-
-		    uri1=(const pjsip_uri*)pjsip_uri_get_uri(contact[i]->uri);
-		    uri2=(const pjsip_uri*)pjsip_uri_get_uri(our_contact->uri);
-		    if (pjsip_uri_cmp(PJSIP_URI_IN_CONTACT_HDR, uri1, uri2)==0)
-		    {
-			has_our_contact = PJ_TRUE;
-
-			if (contact[i]->expires >= 0 && 
-			    contact[i]->expires < expiration) 
-			{
-			    /* Get the lowest expiration time. */
-			    expiration = contact[i]->expires;
-			}
-		    }
-
-		    our_contact = our_contact->next;
-		}
-	    }
-
-	    /* If regc.check_contact is disabled, and no Expires header 
-	     * has been found, but the server does return one single
-	     * Contact header, assumes that the server is broken/unable to
-	     * return the correct Contact. In this case, get the expiration
-	     * from the single Contact header in the response.
-	     */
-	    if (expiration==NOEXP && !pjsip_cfg()->regc.check_contact && 
-		contact_cnt==1) 
-	    {
-		if (contact[0]->expires >= 0)
-		    expiration = contact[0]->expires;
-	    }
-
-	    /* When the response doesn't contain our Contact header, that
-	     * means we have been unregistered.
-	     */
-	    if (pjsip_cfg()->regc.check_contact && !has_our_contact)
-		expiration = 0;
+
+	    /* Calculate expiration */
+	    expiration = calculate_response_expiration(regc, rdata, 
+						       &contact_cnt,
+						       PJSIP_REGC_MAX_CONTACT,
+						       contact);
+
+	    /* Mark operation as complete */
+	    regc->current_op = REGC_IDLE;
 
 	    /* Schedule next registration */
-	    if (regc->auto_reg && expiration != 0 && expiration != NOEXP) {
+	    if (regc->auto_reg && expiration > 0) {
 		pj_time_val delay = { 0, 0};
 
@@ -820,6 +999,9 @@
 	++regc->busy;
 
+	/* Update registration */
+	if (expiration==NOEXP) expiration=-1;
+	regc->expires = expiration;
+
 	/* Call callback. */
-	if (expiration == NOEXP) expiration = -1;
 	call_callback(regc, PJ_SUCCESS, tsx->status_code, 
 		      (rdata ? &rdata->msg_info.msg->line.status.reason 
@@ -842,4 +1024,5 @@
     pj_status_t status;
     pjsip_cseq_hdr *cseq_hdr;
+    pjsip_expires_hdr *expires_hdr;
     pj_uint32_t cseq;
 
@@ -852,4 +1035,6 @@
     }
 
+    pj_assert(regc->current_op == REGC_IDLE);
+
     /* Invalidate message buffer. */
     pjsip_tx_data_invalidate_msg(tdata);
@@ -861,4 +1046,8 @@
     cseq_hdr->cseq = cseq;
 
+    /* Find Expires header */
+    expires_hdr = (pjsip_expires_hdr*)
+		  pjsip_msg_find_hdr(tdata->msg, PJSIP_H_EXPIRES, NULL);
+
     /* Bind to transport selector */
     pjsip_tx_data_set_transport(tdata, &regc->tp_sel);
@@ -869,4 +1058,11 @@
     regc->has_tsx = PJ_TRUE;
     ++regc->busy;
+
+    /* Set current operation based on the value of Expires header */
+    if (expires_hdr && expires_hdr->ivalue==0)
+	regc->current_op = REGC_UNREGISTERING;
+    else
+	regc->current_op = REGC_REGISTERING;
+
     status = pjsip_endpt_send_request(regc->endpt, tdata, REGC_TSX_TIMEOUT,
 				      regc, &tsx_callback);
