| /* $Id$ */ |
| /* |
| * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) |
| * Copyright (C) 2003-2008 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 && |
| client_cfg->code != 502 && client_cfg->code != 503 && |
| client_result.code != 502 && client_result.code != 503) |
| { |
| 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 ((void*)h1->next == (void*)&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 ((void*)h2->next != (void*)&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 ((void*)h1->next == (void*)&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 ((void*)h2->next != (void*)&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, 502, 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; |
| } |
| |
| |