diff options
author | Benny Prijono <bennylp@teluu.com> | 2008-05-17 12:45:00 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2008-05-17 12:45:00 +0000 |
commit | 472248b19890714c6befb93b12106a69ea0382a1 (patch) | |
tree | 0de964bbabc80840bfba431511c14bce5c6e75af /pjsip | |
parent | 973f06d4e3bc295be5bf96618e8c463dcfadc672 (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
Diffstat (limited to 'pjsip')
-rw-r--r-- | pjsip/build/Makefile | 2 | ||||
-rw-r--r-- | pjsip/build/test_pjsip.dsp | 4 | ||||
-rw-r--r-- | pjsip/build/test_pjsip.vcproj | 4 | ||||
-rw-r--r-- | pjsip/include/pjsip-ua/sip_regc.h | 30 | ||||
-rw-r--r-- | pjsip/include/pjsip/sip_config.h | 47 | ||||
-rw-r--r-- | pjsip/src/pjsip-ua/sip_reg.c | 434 | ||||
-rw-r--r-- | pjsip/src/test-pjsip/regc_test.c | 1161 | ||||
-rw-r--r-- | pjsip/src/test-pjsip/test.c | 4 | ||||
-rw-r--r-- | pjsip/src/test-pjsip/test.h | 3 |
9 files changed, 1560 insertions, 129 deletions
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(®c->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(®c->route_set); pj_list_init(®c->hdr_list); pj_list_init(®c->contact_hdr_list); + pj_list_init(®c->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, ®c->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(®c->contact_hdr_list); + /* Save existing contact list to removed_contact_hdr_list and + * clear contact_hdr_list. + */ + pj_list_merge_last(®c->removed_contact_hdr_list, + ®c->contact_hdr_list); + + /* Set the expiration of Contacts in to removed_contact_hdr_list + * zero. + */ + h = regc->removed_contact_hdr_list.next; + while (h != ®c->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 != ®c->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(®c->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( ®c->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(®c->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 != ®c->contact_hdr_list) { + hdr = (pjsip_hdr*)regc->contact_hdr_list.next; + while (hdr != (pjsip_hdr*)®c->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(®c->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(®c->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*)®c->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(®c->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( ®c->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*)®c->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, ®c->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() */ + ®s_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, + ®istrar.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(®istrar.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, ®c); + 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, ®c); + 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, ®c); + 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, ®c); + 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, ®c); + + 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, ®istrar.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, + ®_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(®istrar_uri); + if (rc != 0) + goto on_return; + + /* Send error on refresh without destroy on callback */ + rc = refresh_error(®istrar_uri, PJ_FALSE); + if (rc != 0) + goto on_return; + + /* Send error on refresh, destroy on callback */ + rc = refresh_error(®istrar_uri, PJ_TRUE); + if (rc != 0) + goto on_return; + + /* Updating contact */ + rc = update_test(®istrar_uri); + if (rc != 0) + goto on_return; + + /* Send error during auth, don't destroy on callback */ + rc = auth_send_error(®istrar_uri, PJ_FALSE); + if (rc != 0) + goto on_return; + + /* Send error during auth, destroy on callback */ + rc = auth_send_error(®istrar_uri, PJ_FALSE); + if (rc != 0) + goto on_return; + +on_return: + if (registrar.mod.id != -1) { + pjsip_endpt_unregister_module(endpt, ®istrar.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 { |