Index: pjproject/trunk/pjlib/build/pjlib_test.vcproj
===================================================================
--- pjproject/trunk/pjlib/build/pjlib_test.vcproj	(revision 1958)
+++ pjproject/trunk/pjlib/build/pjlib_test.vcproj	(revision 1959)
@@ -209,4 +209,8 @@
 			>
 			<File
+				RelativePath="..\src\pjlib-test\activesock.c"
+				>
+			</File>
+			<File
 				RelativePath="..\src\pjlib-test\atomic.c"
 				>
Index: pjproject/trunk/pjsip/build/Makefile
===================================================================
--- pjproject/trunk/pjsip/build/Makefile	(revision 1958)
+++ pjproject/trunk/pjsip/build/Makefile	(revision 1959)
@@ -87,5 +87,5 @@
 export TEST_SRCDIR = ../src/test-pjsip
 export TEST_OBJS += dlg_core_test.o dns_test.o msg_err_test.o \
-		    msg_logger.o msg_test.o \
+		    msg_logger.o msg_test.o regc_test.o \
 		    test.o transport_loop_test.o transport_tcp_test.o \
 		    transport_test.o transport_udp_test.o \
Index: pjproject/trunk/pjsip/build/test_pjsip.dsp
===================================================================
--- pjproject/trunk/pjsip/build/test_pjsip.dsp	(revision 1958)
+++ pjproject/trunk/pjsip/build/test_pjsip.dsp	(revision 1959)
@@ -118,4 +118,8 @@
 # Begin Source File
 
+SOURCE="..\src\test-pjsip\regc_test.c"
+# End Source File
+# Begin Source File
+
 SOURCE="..\src\test-pjsip\test.c"
 # End Source File
Index: pjproject/trunk/pjsip/build/test_pjsip.vcproj
===================================================================
--- pjproject/trunk/pjsip/build/test_pjsip.vcproj	(revision 1958)
+++ pjproject/trunk/pjsip/build/test_pjsip.vcproj	(revision 1959)
@@ -348,4 +348,8 @@
 			</File>
 			<File
+				RelativePath="..\src\test-pjsip\regc_test.c"
+				>
+			</File>
+			<File
 				RelativePath="..\src\test-pjsip\test.c"
 				>
Index: pjproject/trunk/pjsip/include/pjsip-ua/sip_regc.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsip-ua/sip_regc.h	(revision 1958)
+++ pjproject/trunk/pjsip/include/pjsip-ua/sip_regc.h	(revision 1959)
@@ -63,6 +63,12 @@
 {
     pjsip_regc		*regc;	    /**< Client registration structure.	    */
-    void		*token;	    /**< Arbitrary token.		    */
-    pj_status_t		 status;    /**< Error status.			    */
+    void		*token;	    /**< Arbitrary token set by application */
+
+    /** Error status. If this value is non-PJ_SUCCESS, some error has occured.
+     *  Note that even when this contains PJ_SUCCESS the registration might
+     *  have failed; in this case the \a code field will contain non
+     *  successful (non-2xx status class) code
+     */
+    pj_status_t		 status;
     int			 code;	    /**< SIP status code received.	    */
     pj_str_t		 reason;    /**< SIP reason phrase received.	    */
@@ -296,10 +302,21 @@
 
 /**
- * Update Contact details in the client registration structure.
+ * Update Contact details in the client registration structure. For each
+ * contact URI, if the uri is not found in existing contact, it will be
+ * added to the Contact list. If the URI matches existing contact, nothing
+ * will be added. This function will also mark existing contacts which
+ * are not specified in the new contact list as to be removed, by adding
+ * "expires=0" parameter to these contacts.
+ *
+ * Once the contact list has been updated, application must update the
+ * registration by creating a new REGISTER request and send it to the
+ * registrar. This request will contain both old and new contacts; the
+ * old contacts will have it's expires parameter set to zero to instruct
+ * the registrar to remove the bindings.
  *
  * @param regc	    The client registration structure.
  * @param ccnt	    Number of contacts.
- * @param contact   Array of contacts.
- * @return	    zero if sucessfull.
+ * @param contact   Array of contact URIs.
+ * @return	    PJ_SUCCESS if sucessfull.
  */
 PJ_DECL(pj_status_t) pjsip_regc_update_contact( pjsip_regc *regc,
@@ -308,5 +325,6 @@
 
 /**
- * Update the expires value.
+ * Update the expires value. The next REGISTER request will contain
+ * new expires value for the registration.
  *
  * @param regc	    The client registration structure.
Index: pjproject/trunk/pjsip/include/pjsip/sip_config.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsip/sip_config.h	(revision 1958)
+++ pjproject/trunk/pjsip/include/pjsip/sip_config.h	(revision 1959)
@@ -109,5 +109,5 @@
 	/**
 	 * Specify whether client registration should check for its 
-	 * registered contact in Contact header of successful REGISTE 
+	 * registered contact in Contact header of successful REGISTER 
 	 * response to determine whether registration has been successful. 
 	 * This setting may be disabled if non-compliant registrar is unable
@@ -118,4 +118,15 @@
 	pj_bool_t   check_contact;
 
+	/**
+	 * Specify whether client registration should add "x-uid" extension
+	 * parameter in all Contact URIs that it registers to assist the
+	 * matching of Contact URIs in the 200/OK REGISTER response, in 
+	 * case the registrar is unable to return exact Contact URI in the
+	 * 200/OK response.
+	 *
+	 * Default is PJSIP_REGISTER_CLIENT_ADD_XUID_PARAM.
+	 */
+	pj_bool_t   add_xuid_param;
+
     } regc;
 
@@ -714,5 +725,15 @@
 #endif
 
-PJ_END_DECL
+
+/**
+ * Specify the number of seconds to refresh the client registration
+ * before the registration expires.
+ *
+ * Default: 5 seconds
+ */
+#ifndef PJSIP_REGISTER_CLIENT_DELAY_BEFORE_REFRESH
+#   define PJSIP_REGISTER_CLIENT_DELAY_BEFORE_REFRESH  5
+#endif
+
 
 /**
@@ -722,5 +743,6 @@
  * if non-compliant registrar is unable to return correct Contact header.
  *
- * This setting can be changed in run-time with using pjsip_cfg().
+ * This setting can be changed in run-time by settting \a regc.check_contact
+ * field of pjsip_cfg().
  *
  * Default is 1
@@ -732,4 +754,23 @@
 
 /**
+ * Specify whether client registration should add "x-uid" extension
+ * parameter in all Contact URIs that it registers to assist the
+ * matching of Contact URIs in the 200/OK REGISTER response, in 
+ * case the registrar is unable to return exact Contact URI in the
+ * 200/OK response.
+ *
+ * This setting can be changed in run-time by setting 
+ * \a regc.add_xuid_param field of pjsip_cfg().
+ *
+ * Default is 0.
+ */
+#ifndef PJSIP_REGISTER_CLIENT_ADD_XUID_PARAM
+#   define PJSIP_REGISTER_CLIENT_ADD_XUID_PARAM	0
+#endif
+
+
+PJ_END_DECL
+
+/**
  * @}
  */
Index: pjproject/trunk/pjsip/src/pjsip-ua/sip_reg.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsip-ua/sip_reg.c	(revision 1958)
+++ 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);
Index: pjproject/trunk/pjsip/src/test-pjsip/regc_test.c
===================================================================
--- pjproject/trunk/pjsip/src/test-pjsip/regc_test.c	(revision 1959)
+++ pjproject/trunk/pjsip/src/test-pjsip/regc_test.c	(revision 1959)
@@ -0,0 +1,1161 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2007 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 "test.h"
+#include <pjsip_ua.h>
+#include <pjsip.h>
+#include <pjlib.h>
+
+#define THIS_FILE   "regc_test.c"
+
+
+/************************************************************************/
+/* A module to inject error into outgoing sending operation */
+static pj_status_t mod_send_on_tx_request(pjsip_tx_data *tdata);
+
+static struct 
+{
+    pjsip_module mod;
+    unsigned	 count;
+    unsigned	 count_before_reject;
+} send_mod = 
+{
+    {
+	NULL, NULL,			    /* prev, next.		*/
+	{ "mod-send", 8 },		    /* Name.			*/
+	-1,				    /* Id			*/
+	PJSIP_MOD_PRIORITY_TRANSPORT_LAYER,	    /* Priority			*/
+	NULL,				    /* load()			*/
+	NULL,				    /* start()			*/
+	NULL,				    /* stop()			*/
+	NULL,				    /* unload()			*/
+	NULL,				    /* on_rx_request()		*/
+	NULL,				    /* on_rx_response()		*/
+	&mod_send_on_tx_request,		    /* on_tx_request.		*/
+	NULL,				    /* on_tx_response()		*/
+	NULL,				    /* on_tsx_state()		*/
+    },
+    0,
+    0xFFFF
+};
+
+
+static pj_status_t mod_send_on_tx_request(pjsip_tx_data *tdata)
+{
+    PJ_UNUSED_ARG(tdata);
+
+    if (++send_mod.count > send_mod.count_before_reject)
+	return PJ_ECANCELLED;
+    else
+	return PJ_SUCCESS;
+};
+
+
+/************************************************************************/
+/* Registrar for testing */
+static pj_bool_t regs_rx_request(pjsip_rx_data *rdata);
+
+enum contact_op
+{
+    NONE,	/* don't put Contact header	    */
+    EXACT,	/* return exact contact		    */
+    MODIFIED,	/* return modified Contact header   */
+};
+
+struct registrar_cfg
+{
+    pj_bool_t	    respond;	    /* should it respond at all		*/
+    unsigned	    status_code;    /* final response status code	*/
+    pj_bool_t	    authenticate;   /* should we authenticate?		*/
+    enum contact_op contact_op;	    /* What should we do with Contact   */
+    unsigned	    expires_param;  /* non-zero to put in expires param	*/
+    unsigned	    expires;	    /* non-zero to put in Expires header*/
+
+    pj_str_t	    more_contacts;  /* Additional Contact headers to put*/
+};
+
+static struct registrar
+{
+    pjsip_module	    mod;
+    struct registrar_cfg    cfg;
+    unsigned		    response_cnt;
+} registrar = 
+{
+    {
+	NULL, NULL,			    /* prev, next.		*/
+	{ "registrar", 9 },		    /* Name.			*/
+	-1,				    /* Id			*/
+	PJSIP_MOD_PRIORITY_APPLICATION,	    /* Priority			*/
+	NULL,				    /* load()			*/
+	NULL,				    /* start()			*/
+	NULL,				    /* stop()			*/
+	NULL,				    /* unload()			*/
+	&regs_rx_request,		    /* on_rx_request()		*/
+	NULL,				    /* on_rx_response()		*/
+	NULL,				    /* on_tx_request.		*/
+	NULL,				    /* on_tx_response()		*/
+	NULL,				    /* on_tsx_state()		*/
+    }
+};
+
+static pj_bool_t regs_rx_request(pjsip_rx_data *rdata)
+{
+    pjsip_msg *msg = rdata->msg_info.msg;
+    pjsip_hdr hdr_list;
+    int code;
+    pj_status_t status;
+
+    if (msg->line.req.method.id != PJSIP_REGISTER_METHOD)
+	return PJ_FALSE;
+
+    if (!registrar.cfg.respond)
+	return PJ_TRUE;
+
+    pj_list_init(&hdr_list);
+
+    if (registrar.cfg.authenticate && 
+	pjsip_msg_find_hdr(msg, PJSIP_H_AUTHORIZATION, NULL)==NULL) 
+    {
+	pjsip_generic_string_hdr *hwww;
+	const pj_str_t hname = pj_str("WWW-Authenticate");
+	const pj_str_t hvalue = pj_str("Digest realm=\"test\"");
+
+	hwww = pjsip_generic_string_hdr_create(rdata->tp_info.pool, &hname, 
+					       &hvalue);
+	pj_list_push_back(&hdr_list, hwww);
+
+	code = 401;
+
+    } else {
+	if (registrar.cfg.contact_op == EXACT ||
+	    registrar.cfg.contact_op == MODIFIED) 
+	{
+	    pjsip_hdr *hsrc;
+
+	    for (hsrc=msg->hdr.next; hsrc!=&msg->hdr; hsrc=hsrc->next) {
+		pjsip_contact_hdr *hdst;
+
+		if (hsrc->type != PJSIP_H_CONTACT)
+		    continue;
+
+		hdst = (pjsip_contact_hdr*)
+		       pjsip_hdr_clone(rdata->tp_info.pool, hsrc);
+
+		if (hdst->expires==0)
+		    continue;
+
+		if (registrar.cfg.contact_op == MODIFIED) {
+		    if (PJSIP_URI_SCHEME_IS_SIP(hdst->uri) ||
+			PJSIP_URI_SCHEME_IS_SIPS(hdst->uri))
+		    {
+			pjsip_sip_uri *sip_uri = (pjsip_sip_uri*)
+						 pjsip_uri_get_uri(hdst->uri);
+			sip_uri->host = pj_str("x-modified-host");
+			sip_uri->port = 1;
+		    }
+		}
+
+		if (registrar.cfg.expires_param)
+		    hdst->expires = registrar.cfg.expires_param;
+
+		pj_list_push_back(&hdr_list, hdst);
+	    }
+	}
+
+	if (registrar.cfg.more_contacts.slen) {
+	    pjsip_generic_string_hdr *hcontact;
+	    const pj_str_t hname = pj_str("Contact");
+
+	    hcontact = pjsip_generic_string_hdr_create(rdata->tp_info.pool, &hname, 
+						       &registrar.cfg.more_contacts);
+	    pj_list_push_back(&hdr_list, hcontact);
+	}
+
+	if (registrar.cfg.expires) {
+	    pjsip_expires_hdr *hexp;
+
+	    hexp = pjsip_expires_hdr_create(rdata->tp_info.pool, 
+					    registrar.cfg.expires);
+	    pj_list_push_back(&hdr_list, hexp);
+	}
+
+	registrar.response_cnt++;
+
+	code = registrar.cfg.status_code;
+    }
+
+    status = pjsip_endpt_respond(endpt, NULL, rdata, code, NULL,
+				 &hdr_list, NULL, NULL);
+    pj_assert(status == PJ_SUCCESS);
+
+    return PJ_TRUE;
+}
+
+
+/************************************************************************/
+/* Client registration test session */
+struct client
+{
+    /* Result/expected result */
+    int		error;
+    int		code;
+    pj_bool_t	have_reg;
+    int		expiration;
+    unsigned	contact_cnt;
+    pj_bool_t	auth;
+
+    /* Commands */
+    pj_bool_t	destroy_on_cb;
+
+    /* Status */
+    pj_bool_t	done;
+
+    /* Additional results */
+    int		interval;
+    int		next_reg;
+};
+
+/* regc callback */
+static void client_cb(struct pjsip_regc_cbparam *param)
+{
+    struct client *client = (struct client*) param->token;
+    pjsip_regc_info info;
+    pj_status_t status;
+
+    client->done = PJ_TRUE;
+
+    status = pjsip_regc_get_info(param->regc, &info);
+    pj_assert(status == PJ_SUCCESS);
+
+    client->error = (param->status != PJ_SUCCESS);
+    client->code = param->code;
+
+    if (client->error)
+	return;
+
+    client->have_reg = info.auto_reg && info.interval>0 &&
+		       param->expiration>0;
+    client->expiration = param->expiration;
+    client->contact_cnt = param->contact_cnt;
+    client->interval = info.interval;
+    client->next_reg = info.next_reg;
+
+    if (client->destroy_on_cb)
+	pjsip_regc_destroy(param->regc);
+}
+
+
+/* Generic client test session */
+static struct client client_result;
+static int do_test(const char *title,
+		   const struct registrar_cfg *srv_cfg, 
+		   const struct client *client_cfg,
+		   const pj_str_t *registrar_uri,
+		   unsigned contact_cnt,
+		   const pj_str_t contacts[],
+		   unsigned expires,
+		   pj_bool_t leave_session,
+		   pjsip_regc **p_regc)
+{
+    pjsip_regc *regc;
+    unsigned i;
+    const pj_str_t aor = pj_str("<sip:regc-test@pjsip.org>");
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+
+    PJ_LOG(3,(THIS_FILE, "  %s", title));
+
+    /* Modify registrar settings */
+    pj_memcpy(&registrar.cfg, srv_cfg, sizeof(*srv_cfg));
+
+    pj_bzero(&client_result, sizeof(client_result));
+    client_result.destroy_on_cb = client_cfg->destroy_on_cb;
+
+    status = pjsip_regc_create(endpt, &client_result, &client_cb, &regc);
+    if (status != PJ_SUCCESS)
+	return -100;
+
+    status = pjsip_regc_init(regc, registrar_uri, &aor, &aor, contact_cnt,
+			     contacts, expires ? expires : 60);
+    if (status != PJ_SUCCESS) {
+	pjsip_regc_destroy(regc);
+	return -110;
+    }
+
+    if (client_cfg->auth) {
+	pjsip_cred_info cred;
+
+	pj_bzero(&cred, sizeof(cred));
+	cred.realm = pj_str("*");
+	cred.scheme = pj_str("digest");
+	cred.username = pj_str("user");
+	cred.data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
+	cred.data = pj_str("password");
+
+	status = pjsip_regc_set_credentials(regc, 1, &cred);
+	if (status != PJ_SUCCESS) {
+	    pjsip_regc_destroy(regc);
+	    return -115;
+	}
+    }
+
+    /* Register */
+    status = pjsip_regc_register(regc, PJ_TRUE, &tdata);
+    if (status != PJ_SUCCESS) {
+	pjsip_regc_destroy(regc);
+	return -120;
+    }
+    status = pjsip_regc_send(regc, tdata);
+
+    /* That's it, wait until the callback is sent */
+    for (i=0; i<600 && !client_result.done; ++i) {
+	flush_events(100);
+    }
+
+    if (!client_result.done) {
+	PJ_LOG(3,(THIS_FILE, "    error: test has timed out"));
+	pjsip_regc_destroy(regc);
+	return -200;
+    }
+
+    /* Destroy the regc, we're done with the test, unless we're
+     * instructed to leave the session open.
+     */
+    if (!leave_session && !client_cfg->destroy_on_cb)
+	pjsip_regc_destroy(regc);
+
+    /* Compare results with expected results */
+
+    if (client_result.error != client_cfg->error) {
+	PJ_LOG(3,(THIS_FILE, "    error: expecting err=%d, got err=%d",
+		  client_cfg->error, client_result.error));
+	return -210;
+    }
+    if (client_result.code != client_cfg->code) {
+	PJ_LOG(3,(THIS_FILE, "    error: expecting code=%d, got code=%d",
+		  client_cfg->code, client_result.code));
+	return -220;
+    }
+    if (client_result.expiration != client_cfg->expiration) {
+	PJ_LOG(3,(THIS_FILE, "    error: expecting expiration=%d, got expiration=%d",
+		  client_cfg->expiration, client_result.expiration));
+	return -240;
+    }
+    if (client_result.contact_cnt != client_cfg->contact_cnt) {
+	PJ_LOG(3,(THIS_FILE, "    error: expecting contact_cnt=%d, got contact_cnt=%d",
+		  client_cfg->contact_cnt, client_result.contact_cnt));
+	return -250;
+    }
+    if (client_result.have_reg != client_cfg->have_reg) {
+	PJ_LOG(3,(THIS_FILE, "    error: expecting have_reg=%d, got have_reg=%d",
+		  client_cfg->have_reg, client_result.have_reg));
+	return -260;
+    }
+    if (client_result.interval != client_result.expiration) {
+	PJ_LOG(3,(THIS_FILE, "    error: interval (%d) is different than expiration (%d)",
+		  client_result.interval, client_result.expiration));
+	return -270;
+    }
+    if (client_result.expiration > 0 && client_result.next_reg < 1) {
+	PJ_LOG(3,(THIS_FILE, "    error: next_reg=%d, expecting positive number because expiration is %d",
+		  client_result.next_reg, client_result.expiration));
+	return -280;
+    }
+
+    /* Looks like everything is okay. */
+    if (leave_session) {
+	*p_regc = regc;
+    }
+
+    return 0;
+}
+
+
+/************************************************************************/
+/* Customized tests */
+
+/* Check that client is sending register refresh */
+static int keep_alive_test(const pj_str_t *registrar_uri)
+{
+    enum { TIMEOUT = 40 };
+    struct registrar_cfg server_cfg = 
+	/* respond	code	auth	  contact  exp_prm expires more_contacts */
+	{ PJ_TRUE,	200,	PJ_FALSE, EXACT,   TIMEOUT, 0,	    {NULL, 0}};
+    struct client client_cfg = 
+	/* error	code	have_reg    expiration	contact_cnt auth?    destroy*/
+	{ PJ_FALSE,	200,	PJ_TRUE,   TIMEOUT,	1,	    PJ_FALSE,PJ_FALSE};
+    pj_str_t contact = pj_str("<sip:c@C>");
+
+
+    pjsip_regc *regc;
+    unsigned i;
+    int ret;
+
+    ret = do_test("register refresh (takes ~40 secs)", &server_cfg, &client_cfg, registrar_uri,
+		  1, &contact, TIMEOUT, PJ_TRUE, &regc);
+    if (ret != 0)
+	return ret;
+
+    /* Reset server response_cnt */
+    registrar.response_cnt = 0;
+
+    /* Wait until keep-alive/refresh is done */
+    for (i=0; i<(TIMEOUT-1)*10 && registrar.response_cnt==0; ++i) {
+	flush_events(100);
+    }
+
+    if (registrar.response_cnt==0) {
+	PJ_LOG(3,(THIS_FILE, "    error: no refresh is received"));
+	return -400;
+    }
+
+    if (client_result.error) {
+	PJ_LOG(3,(THIS_FILE, "    error: got error"));
+	return -410;
+    }
+    if (client_result.code != 200) {
+	PJ_LOG(3,(THIS_FILE, "    error: expecting code=%d, got code=%d",
+		  200, client_result.code));
+	return -420;
+    }
+    if (client_result.expiration != TIMEOUT) {
+	PJ_LOG(3,(THIS_FILE, "    error: expecting expiration=%d, got expiration=%d",
+		  TIMEOUT, client_result.expiration));
+	return -440;
+    }
+    if (client_result.contact_cnt != 1) {
+	PJ_LOG(3,(THIS_FILE, "    error: expecting contact_cnt=%d, got contact_cnt=%d",
+		  TIMEOUT, client_result.contact_cnt));
+	return -450;
+    }
+    if (client_result.have_reg == 0) {
+	PJ_LOG(3,(THIS_FILE, "    error: expecting have_reg=%d, got have_reg=%d",
+		  1, client_result.have_reg));
+	return -460;
+    }
+    if (client_result.interval != TIMEOUT) {
+	PJ_LOG(3,(THIS_FILE, "    error: interval (%d) is different than expiration (%d)",
+		  client_result.interval, TIMEOUT));
+	return -470;
+    }
+    if (client_result.expiration > 0 && client_result.next_reg < 1) {
+	PJ_LOG(3,(THIS_FILE, "    error: next_reg=%d, expecting positive number because expiration is %d",
+		  client_result.next_reg, client_result.expiration));
+	return -480;
+    }
+
+    /* Success */
+    pjsip_regc_destroy(regc);
+    return 0;
+}
+
+
+/* Send error on refresh */
+static int refresh_error(const pj_str_t *registrar_uri,
+			 pj_bool_t destroy_on_cb)
+{
+    enum { TIMEOUT = 40 };
+    struct registrar_cfg server_cfg = 
+	/* respond	code	auth	  contact  exp_prm expires more_contacts */
+	{ PJ_TRUE,	200,	PJ_FALSE, EXACT,   TIMEOUT, 0,	    {NULL, 0}};
+    struct client client_cfg = 
+	/* error	code	have_reg    expiration	contact_cnt auth?    destroy*/
+	{ PJ_FALSE,	200,	PJ_TRUE,   TIMEOUT,	1,	    PJ_FALSE,PJ_FALSE};
+    pj_str_t contact = pj_str("<sip:c@C>");
+
+    pjsip_regc *regc;
+    unsigned i;
+    int ret;
+
+    ret = do_test("refresh error (takes ~40 secs)", &server_cfg, &client_cfg, registrar_uri,
+		  1, &contact, TIMEOUT, PJ_TRUE, &regc);
+    if (ret != 0)
+	return ret;
+
+    /* Reset server response_cnt */
+    registrar.response_cnt = 0;
+
+    /* inject error for transmission */
+    send_mod.count = 0;
+    send_mod.count_before_reject = 0;
+
+    /* reconfigure client */
+    client_result.done = PJ_FALSE;
+    client_result.destroy_on_cb = destroy_on_cb;
+
+    /* Wait until keep-alive/refresh is done */
+    for (i=0; i<TIMEOUT*10 && !client_result.done; ++i) {
+	flush_events(100);
+    }
+
+    send_mod.count_before_reject = 0xFFFF;
+
+    if (!destroy_on_cb)
+	pjsip_regc_destroy(regc);
+
+    if (!client_result.done) {
+	PJ_LOG(3,(THIS_FILE, "    error: test has timed out"));
+	return -500;
+    }
+
+    /* Expecting error */
+    if (client_result.error==PJ_FALSE && client_result.code/100==2) {
+	PJ_LOG(3,(THIS_FILE, "    error: expecting error got successfull result"));
+	return -510;
+    }
+
+    return PJ_SUCCESS;
+};
+
+
+/* Send error on refresh */
+static int update_test(const pj_str_t *registrar_uri)
+{
+    enum { TIMEOUT = 40 };
+    struct registrar_cfg server_cfg = 
+	/* respond	code	auth	  contact  exp_prm expires more_contacts */
+	{ PJ_TRUE,	200,	PJ_FALSE, EXACT,   TIMEOUT, 0,	    {NULL, 0}};
+    struct client client_cfg = 
+	/* error	code	have_reg    expiration	contact_cnt auth?    destroy*/
+	{ PJ_FALSE,	200,	PJ_TRUE,   TIMEOUT,	1,	    PJ_FALSE,PJ_FALSE};
+    pj_str_t contacts[] = {
+	{ "<sip:a>", 7 },
+	{ "<sip:b>", 7 },
+	{ "<sip:c>", 7 }
+    };
+
+    pjsip_regc *regc;
+    pjsip_contact_hdr *h1, *h2;
+    pjsip_sip_uri *u1, *u2;
+    unsigned i;
+    pj_status_t status;
+    pjsip_tx_data *tdata = NULL;
+    int ret = 0;
+
+    /* initially only has 1 contact */
+    ret = do_test("update test", &server_cfg, &client_cfg, registrar_uri,
+		  1, &contacts[0], TIMEOUT, PJ_TRUE, &regc);
+    if (ret != 0) {
+	return -600;
+    }
+
+    /*****
+     * replace the contact with new one 
+     */
+    PJ_LOG(3,(THIS_FILE, "   replacing contact"));
+    status = pjsip_regc_update_contact(regc, 1, &contacts[1]);
+    if (status != PJ_SUCCESS) {
+	ret = -610;
+	goto on_return;
+    }
+
+    status = pjsip_regc_register(regc, PJ_TRUE, &tdata);
+    if (status != PJ_SUCCESS) {
+	ret = -620;
+	goto on_return;
+    }
+
+    /* Check that the REGISTER contains two Contacts:
+     *  - <sip:a>;expires=0,
+     *  - <sip:b>
+     */
+    h1 = (pjsip_contact_hdr*) 
+	  pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL);
+    if (!h1) {
+	ret = -630;
+	goto on_return;
+    }
+    if (h1->next == (pjsip_contact_hdr*)&tdata->msg->hdr)
+	h2 = NULL;
+    else
+	h2 = (pjsip_contact_hdr*) 
+	      pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, h1->next);
+    if (!h2) {
+	ret = -640;
+	goto on_return;
+    }
+    /* must not have other Contact header */
+    if (h2->next != (pjsip_contact_hdr*)&tdata->msg->hdr &&
+	pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, h2->next) != NULL)
+    {
+	ret = -645;
+	goto on_return;
+    }
+
+    u1 = (pjsip_sip_uri*) pjsip_uri_get_uri(h1->uri);
+    u2 = (pjsip_sip_uri*) pjsip_uri_get_uri(h2->uri);
+
+    if (*u1->host.ptr == 'a') {
+	if (h1->expires != 0) {
+	    ret = -650;
+	    goto on_return;
+	}
+	if (h2->expires == 0) {
+	    ret = -660;
+	    goto on_return;
+	}
+
+    } else {
+	pj_assert(*u1->host.ptr == 'b');
+	if (h1->expires == 0) {
+	    ret = -670;
+	    goto on_return;
+	}
+	if (h2->expires != 0) {
+	    ret = -680;
+	    goto on_return;
+	}
+    }
+
+    /* Destroy tdata */
+    pjsip_tx_data_dec_ref(tdata);
+    tdata = NULL;
+
+
+
+    /** 
+     * First loop, it will update with more contacts. Second loop
+     * should do nothing.
+     */
+    for (i=0; i<2; ++i) {
+	if (i==0)
+	    PJ_LOG(3,(THIS_FILE, "   replacing with more contacts"));
+	else
+	    PJ_LOG(3,(THIS_FILE, "   updating contacts with same contacts"));
+
+	status = pjsip_regc_update_contact(regc, 2, &contacts[1]);
+	if (status != PJ_SUCCESS) {
+	    ret = -710;
+	    goto on_return;
+	}
+
+	status = pjsip_regc_register(regc, PJ_TRUE, &tdata);
+	if (status != PJ_SUCCESS) {
+	    ret = -720;
+	    goto on_return;
+	}
+
+	/* Check that the REGISTER contains two Contacts:
+	 *  - <sip:b>
+	 *  - <sip:c>
+	 */
+	h1 = (pjsip_contact_hdr*) 
+	      pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL);
+	if (!h1) {
+	    ret = -730;
+	    goto on_return;
+	}
+	if (h1->next == (pjsip_contact_hdr*)&tdata->msg->hdr)
+	    h2 = NULL;
+	else
+	    h2 = (pjsip_contact_hdr*) 
+		  pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, h1->next);
+	if (!h2) {
+	    ret = -740;
+	    goto on_return;
+	}
+	/* must not have other Contact header */
+	if (h2->next != (pjsip_contact_hdr*)&tdata->msg->hdr &&
+	    pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, h2->next) != NULL)
+	{
+	    ret = -745;
+	    goto on_return;
+	}
+
+	/* both contacts must not have expires=0 parameter */
+	if (h1->expires == 0) {
+	    ret = -750;
+	    goto on_return;
+	}
+	if (h2->expires == 0) {
+	    ret = -760;
+	    goto on_return;
+	}
+
+	/* Destroy tdata */
+	pjsip_tx_data_dec_ref(tdata);
+	tdata = NULL;
+    }
+
+on_return:
+    if (tdata) pjsip_tx_data_dec_ref(tdata);
+    pjsip_regc_destroy(regc);
+    return ret;
+};
+
+
+/* send error on authentication */
+static int auth_send_error(const pj_str_t *registrar_uri,
+			   pj_bool_t destroy_on_cb)
+{
+    enum { TIMEOUT = 40 };
+    struct registrar_cfg server_cfg = 
+	/* respond	code	auth	  contact  exp_prm expires more_contacts */
+	{ PJ_TRUE,	200,	PJ_TRUE,  EXACT,   75,	    0,	    {NULL, 0}};
+    struct client client_cfg = 
+	/* error	code	have_reg    expiration	contact_cnt auth?    destroy*/
+	{ PJ_TRUE,	401,	PJ_FALSE, -1,		0,	    PJ_TRUE, PJ_TRUE};
+    pj_str_t contact = pj_str("<sip:c@C>");
+
+    pjsip_regc *regc;
+    int ret;
+
+    client_cfg.destroy_on_cb = destroy_on_cb;
+
+    /* inject error for second request retry */
+    send_mod.count = 0;
+    send_mod.count_before_reject = 1;
+
+    ret = do_test("auth send error", &server_cfg, &client_cfg, registrar_uri,
+		  1, &contact, TIMEOUT, PJ_TRUE, &regc);
+
+    send_mod.count_before_reject = 0xFFFF;
+
+    return ret;
+};
+
+
+
+
+/************************************************************************/
+enum
+{
+    OFF	    = 1,
+    ON	    = 2,
+    ON_OFF  = 3,
+};
+
+int regc_test(void)
+{
+    struct test_rec {
+	unsigned		 check_contact;
+	unsigned		 add_xuid_param;
+
+	const char		*title;
+	char			*alt_registrar;
+	unsigned		 contact_cnt;
+	char			*contacts[4];
+	unsigned		 expires;
+	struct registrar_cfg	 server_cfg;
+	struct client		 client_cfg;
+    } test_rec[] = 
+    {
+	/* immediate error */
+	{
+	    OFF,			    /* check_contact	*/
+	    OFF,			    /* add_xuid_param	*/
+	    "immediate error",		    /* title		*/
+	    "sip:unresolved-host-xyy",	    /* alt_registrar	*/
+	    1,				    /* contact cnt	*/
+	    { "sip:user@127.0.0.1:5060" },  /* contacts[]	*/
+	    600,			    /* expires		*/
+
+	    /* registrar config: */
+	    /* respond	code	auth	  contact   exp_prm expires more_contacts */
+	    { PJ_FALSE,	200,	PJ_FALSE, NONE,	    0,	    0,	    {NULL, 0}},
+
+	    /* client expected results: */
+	    /* error	code	have_reg    expiration	contact_cnt auth?*/
+	    { PJ_FALSE,	503,	PJ_FALSE,   -1,		0,	    PJ_FALSE}
+	},
+
+	/* timeout test */
+	{
+	    OFF,			    /* check_contact	*/
+	    OFF,			    /* add_xuid_param	*/
+	    "timeout test (takes ~32 secs)",/* title		*/
+	    NULL,			    /* alt_registrar	*/
+	    1,				    /* contact cnt	*/
+	    { "sip:user@127.0.0.1:5060" },  /* contacts[]	*/
+	    600,			    /* expires		*/
+
+	    /* registrar config: */
+	    /* respond	code	auth	  contact   exp_prm expires more_contacts */
+	    { PJ_FALSE,	200,	PJ_FALSE, NONE,	    0,	    0,	    {NULL, 0}},
+
+	    /* client expected results: */
+	    /* error	code	have_reg    expiration	contact_cnt auth? */
+	    { PJ_FALSE,	408,	PJ_FALSE,   -1,		0,	    PJ_FALSE}
+	},
+
+	/* Basic successful registration scenario:
+	 * a good registrar returns the Contact header as is and
+	 * add expires parameter. In this test no additional bindings
+	 * are returned.
+	 */
+	{
+	    ON_OFF,			    /* check_contact	*/
+	    ON_OFF,			    /* add_xuid_param	*/
+	    "basic",			    /* title		*/
+	    NULL,			    /* alt_registrar	*/
+	    1,				    /* contact cnt	*/
+	    { "<sip:user@127.0.0.1:5060;transport=udp;x-param=1234>" },  /* contacts[]	*/
+	    600,			    /* expires		*/
+
+	    /* registrar config: */
+	    /* respond	code	auth	  contact  exp_prm expires more_contacts */
+	    { PJ_TRUE,	200,	PJ_FALSE, EXACT,    75,	    65,	    {NULL, 0}},
+
+	    /* client expected results: */
+	    /* error	code	have_reg    expiration	contact_cnt auth?*/
+	    { PJ_FALSE,	200,	PJ_TRUE,   75,		1,	    PJ_FALSE}
+	},
+
+	/* Basic successful registration scenario with authentication
+	 */
+	{
+	    ON_OFF,			    /* check_contact	*/
+	    ON_OFF,			    /* add_xuid_param	*/
+	    "authentication",		    /* title		*/
+	    NULL,			    /* alt_registrar	*/
+	    1,				    /* contact cnt	*/
+	    { "<sip:user@127.0.0.1:5060;transport=udp;x-param=1234>" },  /* contacts[]	*/
+	    600,			    /* expires		*/
+
+	    /* registrar config: */
+	    /* respond	code	auth	  contact  exp_prm expires more_contacts */
+	    { PJ_TRUE,	200,	PJ_TRUE,  EXACT,    75,	    65,	    {NULL, 0}},
+
+	    /* client expected results: */
+	    /* error	code	have_reg    expiration	contact_cnt auth?*/
+	    { PJ_FALSE,	200,	PJ_TRUE,   75,		1,	    PJ_TRUE}
+	},
+
+	/* a good registrar returns the Contact header as is and
+	 * add expires parameter. Also it adds bindings from other
+	 * clients in this test.
+	 */
+	{
+	    ON_OFF,			    /* check_contact	*/
+	    ON,				    /* add_xuid_param	*/
+	    "more bindings in response",    /* title		*/
+	    NULL,			    /* alt_registrar	*/
+	    1,				    /* contact cnt	*/
+	    { "<sip:user@127.0.0.1:5060;transport=udp>" },  /* contacts[]	*/
+	    600,			    /* expires		*/
+
+	    /* registrar config: */
+	    /* respond	code	auth	  contact  exp_prm expires more_contacts */
+	    { PJ_TRUE,	200,	PJ_FALSE, EXACT,    75,	    65,	    {"<sip:a@a>;expires=70", 0}},
+
+	    /* client expected results: */
+	    /* error	code	have_reg    expiration	contact_cnt auth?*/
+	    { PJ_FALSE,	200,	PJ_TRUE,   75,		2,	    PJ_FALSE}
+	},
+
+
+	/* a bad registrar returns modified Contact header, but it
+	 * still returns all parameters intact. In this case
+	 * the expiration is taken from the expires param because
+	 * of matching xuid param or because the number of
+	 * Contact header matches.
+	 */
+	{
+	    ON_OFF,			    /* check_contact	*/
+	    ON_OFF,			    /* add_xuid_param	*/
+	    "registrar modifies Contact header",    /* title		*/
+	    NULL,			    /* alt_registrar	*/
+	    1,				    /* contact cnt	*/
+	    { "<sip:user@127.0.0.1:5060>" },  /* contacts[]	*/
+	    600,			    /* expires		*/
+
+	    /* registrar config: */
+	    /* respond	code	auth	  contact   exp_prm expires more_contacts */
+	    { PJ_TRUE,	200,	PJ_FALSE, MODIFIED, 75,	    65,	    {NULL, 0}},
+
+	    /* client expected results: */
+	    /* error	code	have_reg    expiration	contact_cnt auth?*/
+	    { PJ_FALSE,	200,	PJ_TRUE,   75,		1,	    PJ_FALSE}
+	},
+
+
+	/* a bad registrar returns modified Contact header, but it
+	 * still returns all parameters intact. In addition it returns
+	 * bindings from other clients.
+	 *
+	 * In this case the expiration is taken from the expires param 
+	 * because add_xuid_param is enabled.
+	 */
+	{
+	    ON_OFF,			    /* check_contact	*/
+	    ON,				    /* add_xuid_param	*/
+	    "registrar modifies Contact header and add bindings",    /* title		*/
+	    NULL,			    /* alt_registrar	*/
+	    1,				    /* contact cnt	*/
+	    { "<sip:user@127.0.0.1:5060>" },  /* contacts[]	*/
+	    600,			    /* expires		*/
+
+	    /* registrar config: */
+	    /* respond	code	auth	  contact   exp_prm expires more_contacts */
+	    { PJ_TRUE,	200,	PJ_FALSE, MODIFIED, 75,	    65,	    {"<sip:a@a>;expires=70", 0}},
+
+	    /* client expected results: */
+	    /* error	code	have_reg    expiration	contact_cnt auth?*/
+	    { PJ_FALSE,	200,	PJ_TRUE,   75,		2,	    PJ_FALSE}
+	},
+
+
+	/* a bad registrar returns completely different Contact and
+	 * all parameters are gone. In this case the expiration is 
+	 * also taken from the expires param since the number of 
+	 * header matches.
+	 */
+	{
+	    ON_OFF,			    /* check_contact	*/
+	    ON_OFF,			    /* add_xuid_param	*/
+	    "registrar replaces Contact header",    /* title		*/
+	    NULL,			    /* alt_registrar	*/
+	    1,				    /* contact cnt	*/
+	    { "<sip:user@127.0.0.1:5060>" },  /* contacts[]	*/
+	    600,			    /* expires		*/
+
+	    /* registrar config: */
+	    /* respond	code	auth	  contact   exp_prm expires more_contacts */
+	    { PJ_TRUE,	202,	PJ_FALSE, NONE,	    0,	    65,	    {"<sip:a@A>;expires=75", 0}},
+
+	    /* client expected results: */
+	    /* error	code	have_reg    expiration	contact_cnt auth?*/
+	    { PJ_FALSE,	202,	PJ_TRUE,   75,		1,	    PJ_FALSE}
+	},
+
+
+	/* a bad registrar returns completely different Contact (and
+	 * all parameters are gone) and it also includes bindings from
+	 * other clients.
+	 * In this case the expiration is taken from the Expires header.
+	 */
+	{
+	    ON_OFF,			    /* check_contact	*/
+	    ON_OFF,			    /* add_xuid_param	*/
+	    " as above with additional bindings",    /* title		*/
+	    NULL,			    /* alt_registrar	*/
+	    1,				    /* contact cnt	*/
+	    { "<sip:user@127.0.0.1:5060>" },  /* contacts[]	*/
+	    600,			    /* expires		*/
+
+	    /* registrar config: */
+	    /* respond	code	auth	  contact   exp_prm expires more_contacts */
+	    { PJ_TRUE,	200,	PJ_FALSE, NONE,	    0,	    65,	    {"<sip:a@A>;expires=75, <sip:b@B;expires=70>", 0}},
+
+	    /* client expected results: */
+	    /* error	code	have_reg    expiration	contact_cnt auth?*/
+	    { PJ_FALSE,	200,	PJ_TRUE,   65,		2,	    PJ_FALSE}
+	},
+
+	/* the registrar doesn't return any bindings, but for some
+	 * reason it includes an Expires header.
+	 * In this case the expiration is taken from the Expires header.
+	 */
+	{
+	    ON_OFF,			    /* check_contact	*/
+	    ON_OFF,			    /* add_xuid_param	*/
+	    "no Contact but with Expires",  /* title		*/
+	    NULL,			    /* alt_registrar	*/
+	    1,				    /* contact cnt	*/
+	    { "<sip:user@127.0.0.1:5060>" },  /* contacts[]	*/
+	    600,			    /* expires		*/
+
+	    /* registrar config: */
+	    /* respond	code	auth	  contact   exp_prm expires more_contacts */
+	    { PJ_TRUE,	200,	PJ_FALSE, NONE,	    0,	    65,	    {NULL, 0}},
+
+	    /* client expected results: */
+	    /* error	code	have_reg    expiration	contact_cnt auth?*/
+	    { PJ_FALSE,	200,	PJ_TRUE,   65,		0,	    PJ_FALSE}
+	},
+
+	/* Neither Contact header nor Expires header are present.
+	 * In this case the expiration is taken from the request.
+	 */
+	{
+	    ON_OFF,			    /* check_contact	*/
+	    ON_OFF,			    /* add_xuid_param	*/
+	    "no Contact and no Expires",    /* title		*/
+	    NULL,			    /* alt_registrar	*/
+	    1,				    /* contact cnt	*/
+	    { "<sip:user@127.0.0.1:5060>" },/* contacts[]	*/
+	    600,			    /* expires		*/
+
+	    /* registrar config: */
+	    /* respond	code	auth	  contact   exp_prm expires more_contacts */
+	    { PJ_TRUE,	200,	PJ_FALSE, NONE,	    0,	    0,	    {NULL, 0}},
+
+	    /* client expected results: */
+	    /* error	code	have_reg    expiration	contact_cnt auth?*/
+	    { PJ_FALSE,	200,	PJ_TRUE,   600,		0,	    PJ_FALSE}
+	},
+    };
+
+    unsigned i;
+    pj_sockaddr_in addr;
+    pjsip_transport *udp = NULL;
+    pj_uint16_t port; 
+    char registrar_uri_buf[80];
+    pj_str_t registrar_uri;
+    int rc = 0;
+
+    pj_sockaddr_in_init(&addr, 0, 0);
+
+    /* Acquire existing transport, if any */
+    rc = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_UDP, &addr, sizeof(addr), NULL, &udp);
+    if (rc == PJ_SUCCESS) {
+	port = pj_sockaddr_get_port(&udp->local_addr);
+	pjsip_transport_dec_ref(udp);
+	udp = NULL;
+    } else {
+	rc = pjsip_udp_transport_start(endpt, NULL, NULL, 1, &udp);
+	if (rc != PJ_SUCCESS) {
+	    app_perror("   error creating UDP transport", rc);
+	    rc = -2;
+	    goto on_return;
+	}
+
+	port = pj_sockaddr_get_port(&udp->local_addr);
+    }
+
+    /* Register registrar module */
+    rc = pjsip_endpt_register_module(endpt, &registrar.mod);
+    if (rc != PJ_SUCCESS) {
+	app_perror("   error registering module", rc);
+	rc = -3;
+	goto on_return;
+    }
+
+    /* Register send module */
+    rc = pjsip_endpt_register_module(endpt, &send_mod.mod);
+    if (rc != PJ_SUCCESS) {
+	app_perror("   error registering module", rc);
+	rc = -3;
+	goto on_return;
+    }
+
+    pj_ansi_snprintf(registrar_uri_buf, sizeof(registrar_uri_buf),
+		    "sip:127.0.0.1:%d", (int)port);
+    registrar_uri = pj_str(registrar_uri_buf);
+
+    for (i=0; i<PJ_ARRAY_SIZE(test_rec); ++i) {
+	struct test_rec *t = &test_rec[i];
+	unsigned j, x;
+	pj_str_t reg_uri;
+	pj_str_t contacts[8];
+
+	/* Fill in the registrar address if it's not specified */
+	if (t->alt_registrar == NULL) {
+	    reg_uri = registrar_uri;
+	} else {
+	    reg_uri = pj_str(t->alt_registrar);
+	}
+
+	/* Build contact pj_str_t's */
+	for (j=0; j<t->contact_cnt; ++j) {
+	    contacts[j] = pj_str(t->contacts[j]);
+	}
+
+	/* Normalize more_contacts field */
+	if (t->server_cfg.more_contacts.ptr)
+	    t->server_cfg.more_contacts.slen = strlen(t->server_cfg.more_contacts.ptr);
+
+	/* Do tests with three combinations:
+	 *  - check_contact on/off
+	 *  - add_xuid_param on/off
+	 *  - destroy_on_callback on/off
+	 */
+	for (x=1; x<=2; ++x) {
+	    unsigned y;
+
+	    if ((t->check_contact & x) == 0)
+		continue;
+
+	    pjsip_cfg()->regc.check_contact = (x-1);
+
+	    for (y=1; y<=2; ++y) {
+		unsigned z;
+
+		if ((t->add_xuid_param & y) == 0)
+		    continue;
+
+		pjsip_cfg()->regc.add_xuid_param = (y-1);
+
+		for (z=0; z<=1; ++z) {
+		    char new_title[200];
+
+		    t->client_cfg.destroy_on_cb = z;
+
+		    sprintf(new_title, "%s [check=%d, xuid=%d, destroy=%d]", 
+			    t->title, pjsip_cfg()->regc.check_contact,
+			    pjsip_cfg()->regc.add_xuid_param, z);
+		    rc = do_test(new_title, &t->server_cfg, &t->client_cfg, 
+				 &reg_uri, t->contact_cnt, contacts, 
+				 t->expires, PJ_FALSE, NULL);
+		    if (rc != 0)
+			goto on_return;
+		}
+
+	    }
+	}
+
+	/* Sleep between test groups to avoid using up too many
+	 * active transactions.
+	 */
+	pj_thread_sleep(1000);
+    }
+
+    /* keep-alive test */
+    rc = keep_alive_test(&registrar_uri);
+    if (rc != 0)
+	goto on_return;
+
+    /* Send error on refresh without destroy on callback */
+    rc = refresh_error(&registrar_uri, PJ_FALSE);
+    if (rc != 0)
+	goto on_return;
+
+    /* Send error on refresh, destroy on callback */
+    rc = refresh_error(&registrar_uri, PJ_TRUE);
+    if (rc != 0)
+	goto on_return;
+
+    /* Updating contact */
+    rc = update_test(&registrar_uri);
+    if (rc != 0)
+	goto on_return;
+
+    /* Send error during auth, don't destroy on callback */
+    rc = auth_send_error(&registrar_uri, PJ_FALSE);
+    if (rc != 0)
+	goto on_return;
+
+    /* Send error during auth, destroy on callback */
+    rc = auth_send_error(&registrar_uri, PJ_FALSE);
+    if (rc != 0)
+	goto on_return;
+
+on_return:
+    if (registrar.mod.id != -1) {
+	pjsip_endpt_unregister_module(endpt, &registrar.mod);
+    }
+    if (send_mod.mod.id != -1) {
+	pjsip_endpt_unregister_module(endpt, &send_mod.mod);
+    }
+    if (udp) {
+	pjsip_transport_dec_ref(udp);
+    }
+    return rc;
+}
+
+
Index: pjproject/trunk/pjsip/src/test-pjsip/test.c
===================================================================
--- pjproject/trunk/pjsip/src/test-pjsip/test.c	(revision 1958)
+++ pjproject/trunk/pjsip/src/test-pjsip/test.c	(revision 1959)
@@ -360,4 +360,8 @@
 #endif
 
+#if INCLUDE_REGC_TEST
+    DO_TEST(regc_test());
+#endif
+
 
 on_return:
Index: pjproject/trunk/pjsip/src/test-pjsip/test.h
===================================================================
--- pjproject/trunk/pjsip/src/test-pjsip/test.h	(revision 1958)
+++ pjproject/trunk/pjsip/src/test-pjsip/test.h	(revision 1959)
@@ -41,4 +41,5 @@
 #define INCLUDE_TSX_GROUP	    1
 #define INCLUDE_INV_GROUP	    1
+#define INCLUDE_REGC_GROUP	    1
 
 #define INCLUDE_BENCHMARKS	    1
@@ -63,4 +64,5 @@
 #define INCLUDE_TSX_TEST	INCLUDE_TSX_GROUP
 #define INCLUDE_INV_OA_TEST	INCLUDE_INV_GROUP
+#define INCLUDE_REGC_TEST	INCLUDE_REGC_GROUP
 
 
@@ -75,4 +77,5 @@
 int transport_tcp_test(void);
 int resolve_test(void);
+int regc_test(void);
 
 struct tsx_test_param
