summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2008-05-17 12:45:00 +0000
committerBenny Prijono <bennylp@teluu.com>2008-05-17 12:45:00 +0000
commit472248b19890714c6befb93b12106a69ea0382a1 (patch)
tree0de964bbabc80840bfba431511c14bce5c6e75af
parent973f06d4e3bc295be5bf96618e8c463dcfadc672 (diff)
Ticket #534: Client register/registration support for various registrar brokenness
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@1959 74dad513-b988-da41-8d7b-12977e46ad98
-rw-r--r--pjlib/build/pjlib_test.vcproj4
-rw-r--r--pjsip/build/Makefile2
-rw-r--r--pjsip/build/test_pjsip.dsp4
-rw-r--r--pjsip/build/test_pjsip.vcproj4
-rw-r--r--pjsip/include/pjsip-ua/sip_regc.h30
-rw-r--r--pjsip/include/pjsip/sip_config.h47
-rw-r--r--pjsip/src/pjsip-ua/sip_reg.c434
-rw-r--r--pjsip/src/test-pjsip/regc_test.c1161
-rw-r--r--pjsip/src/test-pjsip/test.c4
-rw-r--r--pjsip/src/test-pjsip/test.h3
10 files changed, 1564 insertions, 129 deletions
diff --git a/pjlib/build/pjlib_test.vcproj b/pjlib/build/pjlib_test.vcproj
index 2e34a48e..fc33f777 100644
--- a/pjlib/build/pjlib_test.vcproj
+++ b/pjlib/build/pjlib_test.vcproj
@@ -208,6 +208,10 @@
Filter="cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
>
<File
+ RelativePath="..\src\pjlib-test\activesock.c"
+ >
+ </File>
+ <File
RelativePath="..\src\pjlib-test\atomic.c"
>
<FileConfiguration
diff --git a/pjsip/build/Makefile b/pjsip/build/Makefile
index 0f7624e6..8d746f01 100644
--- a/pjsip/build/Makefile
+++ b/pjsip/build/Makefile
@@ -86,7 +86,7 @@ export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT
#
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 \
tsx_basic_test.o tsx_bench.o tsx_uac_test.o \
diff --git a/pjsip/build/test_pjsip.dsp b/pjsip/build/test_pjsip.dsp
index 010c743b..5761f663 100644
--- a/pjsip/build/test_pjsip.dsp
+++ b/pjsip/build/test_pjsip.dsp
@@ -117,6 +117,10 @@ SOURCE="..\src\test-pjsip\msg_test.c"
# End Source File
# 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
# Begin Source File
diff --git a/pjsip/build/test_pjsip.vcproj b/pjsip/build/test_pjsip.vcproj
index dd6c8d37..7ac83454 100644
--- a/pjsip/build/test_pjsip.vcproj
+++ b/pjsip/build/test_pjsip.vcproj
@@ -347,6 +347,10 @@
</FileConfiguration>
</File>
<File
+ RelativePath="..\src\test-pjsip\regc_test.c"
+ >
+ </File>
+ <File
RelativePath="..\src\test-pjsip\test.c"
>
<FileConfiguration
diff --git a/pjsip/include/pjsip-ua/sip_regc.h b/pjsip/include/pjsip-ua/sip_regc.h
index 03c71d9b..71a553e3 100644
--- a/pjsip/include/pjsip-ua/sip_regc.h
+++ b/pjsip/include/pjsip-ua/sip_regc.h
@@ -62,8 +62,14 @@ typedef struct pjsip_regc pjsip_regc;
struct pjsip_regc_cbparam
{
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. */
pjsip_rx_data *rdata; /**< The complete received response. */
@@ -295,19 +301,31 @@ PJ_DECL(pj_status_t) pjsip_regc_unregister_all(pjsip_regc *regc,
pjsip_tx_data **p_tdata);
/**
- * 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,
int ccnt,
const pj_str_t contact[] );
/**
- * 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.
* @param expires The new expires value.
diff --git a/pjsip/include/pjsip/sip_config.h b/pjsip/include/pjsip/sip_config.h
index ded28ebd..a63e2250 100644
--- a/pjsip/include/pjsip/sip_config.h
+++ b/pjsip/include/pjsip/sip_config.h
@@ -108,7 +108,7 @@ typedef struct pjsip_cfg_t
struct {
/**
* 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
* to return correct Contact header.
@@ -117,6 +117,17 @@ typedef struct pjsip_cfg_t
*/
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;
} pjsip_cfg_t;
@@ -713,7 +724,17 @@ PJ_INLINE(pjsip_cfg_t*) pjsip_cfg(void)
# define PJSIP_HAS_DIGEST_AKA_AUTH 0
#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
+
/**
* Specify whether client registration should check for its registered
@@ -721,7 +742,8 @@ PJ_END_DECL
* whether registration has been successful. This setting may be disabled
* 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
*/
@@ -731,6 +753,25 @@ PJ_END_DECL
/**
+ * 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
+
+/**
* @}
*/
diff --git a/pjsip/src/pjsip-ua/sip_reg.c b/pjsip/src/pjsip-ua/sip_reg.c
index b015170d..d6f85720 100644
--- a/pjsip/src/pjsip-ua/sip_reg.c
+++ b/pjsip/src/pjsip-ua/sip_reg.c
@@ -35,7 +35,7 @@
#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"
/* Outgoing transaction timeout when server sends 100 but never replies
@@ -43,6 +43,18 @@
*/
#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
+};
/**
* SIP client registration structure.
@@ -54,6 +66,9 @@ struct pjsip_regc
pj_bool_t _delete_flag;
pj_bool_t has_tsx;
pj_int32_t busy;
+ enum regc_op current_op;
+
+ pj_bool_t add_xuid_param;
void *token;
pjsip_regc_cb *cb;
@@ -65,10 +80,9 @@ struct pjsip_regc
pj_str_t from_uri;
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;
pjsip_hdr hdr_list;
@@ -92,7 +106,6 @@ struct pjsip_regc
};
-
PJ_DEF(pj_status_t) pjsip_regc_create( pjsip_endpoint *endpt, void *token,
pjsip_regc_cb *cb,
pjsip_regc **p_regc)
@@ -114,6 +127,7 @@ PJ_DEF(pj_status_t) pjsip_regc_create( pjsip_endpoint *endpt, void *token,
regc->token = token;
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);
if (status != PJ_SUCCESS)
@@ -122,6 +136,7 @@ PJ_DEF(pj_status_t) pjsip_regc_create( pjsip_endpoint *endpt, void *token,
pj_list_init(&regc->route_set);
pj_list_init(&regc->hdr_list);
pj_list_init(&regc->contact_hdr_list);
+ pj_list_init(&regc->removed_contact_hdr_list);
/* Done */
*p_regc = regc;
@@ -142,6 +157,10 @@ PJ_DEF(pj_status_t) pjsip_regc_destroy(pjsip_regc *regc)
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);
}
@@ -199,17 +218,31 @@ static pj_status_t set_contact( pjsip_regc *regc,
const pj_str_t contact[] )
{
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) {
PJ_LOG(4,(THIS_FILE, "Invalid Contact URI: \"%.*s\"",
@@ -217,6 +250,42 @@ static pj_status_t set_contact( pjsip_regc *regc,
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);
+ }
+
pj_list_push_back(&regc->contact_hdr_list, hdr);
}
@@ -288,13 +357,6 @@ PJ_DEF(pj_status_t) pjsip_regc_init( pjsip_regc *regc,
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. */
return PJ_SUCCESS;
}
@@ -436,7 +498,7 @@ PJ_DEF(pj_status_t) pjsip_regc_register(pjsip_regc *regc, pj_bool_t autoreg,
pjsip_tx_data **p_tdata)
{
pjsip_msg *msg;
- pjsip_hdr *hdr;
+ pjsip_contact_hdr *hdr;
pj_status_t status;
pjsip_tx_data *tdata;
@@ -456,6 +518,15 @@ PJ_DEF(pj_status_t) pjsip_regc_register(pjsip_regc *regc, pj_bool_t autoreg,
hdr = hdr->next;
}
+ /* 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*)
pjsip_hdr_shallow_clone(tdata->pool,
@@ -496,14 +567,24 @@ PJ_DEF(pj_status_t) pjsip_regc_unregister(pjsip_regc *regc,
msg = tdata->msg;
/* 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));
hdr = hdr->next;
}
- 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;
return PJ_SUCCESS;
@@ -513,6 +594,8 @@ PJ_DEF(pj_status_t) pjsip_regc_unregister_all(pjsip_regc *regc,
pjsip_tx_data **p_tdata)
{
pjsip_tx_data *tdata;
+ pjsip_contact_hdr *hcontact;
+ pjsip_hdr *hdr;
pjsip_msg *msg;
pj_status_t status;
@@ -528,8 +611,18 @@ PJ_DEF(pj_status_t) pjsip_regc_unregister_all(pjsip_regc *regc,
return status;
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;
return PJ_SUCCESS;
@@ -615,6 +708,173 @@ static void regc_refresh_timer_cb( pj_timer_heap_t *timer_heap,
}
}
+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)
{
pj_status_t status;
@@ -645,6 +905,9 @@ static void tsx_callback(void *token, pjsip_event *event)
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,
rdata,
tsx->last_tx,
@@ -682,114 +945,30 @@ static void tsx_callback(void *token, pjsip_event *event)
* This regc will be destroyed later in this function.
*/
- /* Nothing to do */
- ;
+ /* Just reset current op */
+ regc->current_op = REGC_IDLE;
} else {
- int contact_cnt = 0;
- pjsip_contact_hdr *contact[PJSIP_REGC_MAX_CONTACT];
pjsip_rx_data *rdata;
- enum { NOEXP = 0x1FFFFFFF };
pj_int32_t expiration = NOEXP;
+ unsigned contact_cnt = 0;
+ pjsip_contact_hdr *contact[PJSIP_REGC_MAX_CONTACT];
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.
- */
+ /* Calculate expiration */
+ expiration = calculate_response_expiration(regc, rdata,
+ &contact_cnt,
+ PJSIP_REGC_MAX_CONTACT,
+ contact);
- 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;
+ /* 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};
delay.sec = expiration - DELAY_BEFORE_REFRESH;
@@ -819,8 +998,11 @@ static void tsx_callback(void *token, pjsip_event *event)
*/
++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
: pjsip_get_status_text(tsx->status_code)),
@@ -841,6 +1023,7 @@ PJ_DEF(pj_status_t) pjsip_regc_send(pjsip_regc *regc, pjsip_tx_data *tdata)
{
pj_status_t status;
pjsip_cseq_hdr *cseq_hdr;
+ pjsip_expires_hdr *expires_hdr;
pj_uint32_t cseq;
/* Make sure we don't have pending transaction. */
@@ -851,6 +1034,8 @@ PJ_DEF(pj_status_t) pjsip_regc_send(pjsip_regc *regc, pjsip_tx_data *tdata)
return PJSIP_EBUSY;
}
+ pj_assert(regc->current_op == REGC_IDLE);
+
/* Invalidate message buffer. */
pjsip_tx_data_invalidate_msg(tdata);
@@ -860,6 +1045,10 @@ PJ_DEF(pj_status_t) pjsip_regc_send(pjsip_regc *regc, pjsip_tx_data *tdata)
pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
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);
@@ -868,6 +1057,13 @@ PJ_DEF(pj_status_t) pjsip_regc_send(pjsip_regc *regc, pjsip_tx_data *tdata)
*/
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);
if (status!=PJ_SUCCESS) {
diff --git a/pjsip/src/test-pjsip/regc_test.c b/pjsip/src/test-pjsip/regc_test.c
new file mode 100644
index 00000000..a9629739
--- /dev/null
+++ b/pjsip/src/test-pjsip/regc_test.c
@@ -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;
+}
+
+
diff --git a/pjsip/src/test-pjsip/test.c b/pjsip/src/test-pjsip/test.c
index b02a8cf6..09ee1244 100644
--- a/pjsip/src/test-pjsip/test.c
+++ b/pjsip/src/test-pjsip/test.c
@@ -359,6 +359,10 @@ int test_main(void)
DO_TEST(inv_offer_answer_test());
#endif
+#if INCLUDE_REGC_TEST
+ DO_TEST(regc_test());
+#endif
+
on_return:
flush_events(500);
diff --git a/pjsip/src/test-pjsip/test.h b/pjsip/src/test-pjsip/test.h
index 1234cebc..31e4451b 100644
--- a/pjsip/src/test-pjsip/test.h
+++ b/pjsip/src/test-pjsip/test.h
@@ -40,6 +40,7 @@ extern pjsip_endpoint *endpt;
#define INCLUDE_TRANSPORT_GROUP 1
#define INCLUDE_TSX_GROUP 1
#define INCLUDE_INV_GROUP 1
+#define INCLUDE_REGC_GROUP 1
#define INCLUDE_BENCHMARKS 1
@@ -62,6 +63,7 @@ extern pjsip_endpoint *endpt;
#define INCLUDE_RESOLVE_TEST INCLUDE_TRANSPORT_GROUP
#define INCLUDE_TSX_TEST INCLUDE_TSX_GROUP
#define INCLUDE_INV_OA_TEST INCLUDE_INV_GROUP
+#define INCLUDE_REGC_TEST INCLUDE_REGC_GROUP
/* The tests */
@@ -74,6 +76,7 @@ int transport_udp_test(void);
int transport_loop_test(void);
int transport_tcp_test(void);
int resolve_test(void);
+int regc_test(void);
struct tsx_test_param
{