PJ_DEF(pj_status_t) pjsip_regc_create( pjsip_endpoint *endpt, void *token, pjsip_regc_cb *cb, pjsip_regc **p_regc) { pj_pool_t *pool; pjsip_regc *regc; pj_status_t status; /* Verify arguments. */ PJ_ASSERT_RETURN(endpt && cb && p_regc, PJ_EINVAL); pool = pjsip_endpt_create_pool(endpt, "regc%p", 1024, 1024); PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); regc = PJ_POOL_ZALLOC_T(pool, pjsip_regc); regc->pool = pool; regc->endpt = endpt; regc->token = token; regc->cb = cb; regc->expires = PJSIP_REGC_EXPIRATION_NOT_SPECIFIED; regc->add_xuid_param = pjsip_cfg()->regc.add_xuid_param; status = pj_lock_create_recursive_mutex(pool, pool->obj_name, ®c->lock); if (status != PJ_SUCCESS) { pj_pool_release(pool); return status; } status = pj_atomic_create(pool, 0, ®c->busy_ctr); if (status != PJ_SUCCESS) { pj_lock_destroy(regc->lock); pj_pool_release(pool); return status; } status = pjsip_auth_clt_init(®c->auth_sess, endpt, regc->pool, 0); if (status != PJ_SUCCESS) return status; 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; return PJ_SUCCESS; }
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. */ pj_size_t 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; }
/* * This callback is called by transport manager to send SIP message */ static pj_status_t tcp_send_msg(pjsip_transport *transport, pjsip_tx_data *tdata, const pj_sockaddr_t *rem_addr, int addr_len, void *token, pjsip_transport_callback callback) { struct tcp_transport *tcp = (struct tcp_transport*)transport; pj_ssize_t size; pj_bool_t delayed = PJ_FALSE; pj_status_t status = PJ_SUCCESS; /* Sanity check */ PJ_ASSERT_RETURN(transport && tdata, PJ_EINVAL); /* Check that there's no pending operation associated with the tdata */ PJ_ASSERT_RETURN(tdata->op_key.tdata == NULL, PJSIP_EPENDINGTX); /* Check the address is supported */ PJ_ASSERT_RETURN(rem_addr && (addr_len==sizeof(pj_sockaddr_in) || addr_len==sizeof(pj_sockaddr_in6)), PJ_EINVAL); /* Init op key. */ tdata->op_key.tdata = tdata; tdata->op_key.token = token; tdata->op_key.callback = callback; /* If asynchronous connect() has not completed yet, just put the * transmit data in the pending transmission list since we can not * use the socket yet. */ if (tcp->has_pending_connect) { /* * Looks like connect() is still in progress. Check again (this time * with holding the lock) to be sure. */ pj_lock_acquire(tcp->base.lock); if (tcp->has_pending_connect) { struct delayed_tdata *delayed_tdata; /* * connect() is still in progress. Put the transmit data to * the delayed list. * Starting from #1583 (https://trac.pjsip.org/repos/ticket/1583), * we also add timeout value for the transmit data. When the * connect() is completed, the timeout value will be checked to * determine whether the transmit data needs to be sent. */ delayed_tdata = PJ_POOL_ZALLOC_T(tdata->pool, struct delayed_tdata); delayed_tdata->tdata_op_key = &tdata->op_key; if (tdata->msg && tdata->msg->type == PJSIP_REQUEST_MSG) { pj_gettickcount(&delayed_tdata->timeout); delayed_tdata->timeout.msec += pjsip_cfg()->tsx.td; pj_time_val_normalize(&delayed_tdata->timeout); } pj_list_push_back(&tcp->delayed_list, delayed_tdata); status = PJ_EPENDING; /* Prevent pj_ioqueue_send() to be called below */ delayed = PJ_TRUE; } pj_lock_release(tcp->base.lock); }
static pj_ssize_t pjsip_url_print( pjsip_uri_context_e context, const pjsip_sip_uri *url, char *buf, pj_size_t size) { int printed; char *startbuf = buf; char *endbuf = buf+size; const pj_str_t *scheme; const pjsip_parser_const_t *pc = pjsip_parser_const(); *buf = '\0'; /* Print scheme ("sip:" or "sips:") */ scheme = pjsip_uri_get_scheme(url); copy_advance_check(buf, *scheme); *buf++ = ':'; /* Print "user:password@", if any. */ if (url->user.slen) { copy_advance_escape(buf, url->user, pc->pjsip_USER_SPEC); if (url->passwd.slen) { *buf++ = ':'; copy_advance_escape(buf, url->passwd, pc->pjsip_PASSWD_SPEC); } *buf++ = '@'; } /* Print host. */ pj_assert(url->host.slen != 0); /* Detect IPv6 IP address */ if (pj_memchr(url->host.ptr, ':', url->host.slen)) { copy_advance_pair_quote_cond(buf, "", 0, url->host, '[', ']'); } else { copy_advance_check(buf, url->host); } /* Only print port if it is explicitly specified. * Port is not allowed in To and From header, see Table 1 in * RFC 3261 Section 19.1.1 */ /* Note: ticket #1141 adds run-time setting to allow port number to * appear in From/To header. Default is still false. */ if (url->port && (context != PJSIP_URI_IN_FROMTO_HDR || pjsip_cfg()->endpt.allow_port_in_fromto_hdr)) { if (endbuf - buf < 10) return -1; *buf++ = ':'; printed = pj_utoa(url->port, buf); buf += printed; } /* User param is allowed in all contexes */ copy_advance_pair_check(buf, ";user="******";method=", 8, url->method_param, pc->pjsip_PARAM_CHAR_SPEC); } /* Transport is not allowed in From/To header. */ if (context != PJSIP_URI_IN_FROMTO_HDR) { copy_advance_pair_escape(buf, ";transport=", 11, url->transport_param, pc->pjsip_PARAM_CHAR_SPEC); } /* TTL param is not allowed in From, To, Route, and Record-Route header. */ if (url->ttl_param >= 0 && context != PJSIP_URI_IN_FROMTO_HDR && context != PJSIP_URI_IN_ROUTING_HDR) { if (endbuf - buf < 15) return -1; pj_memcpy(buf, ";ttl=", 5); printed = pj_utoa(url->ttl_param, buf+5); buf += printed + 5; } /* maddr param is not allowed in From and To header. */ if (context != PJSIP_URI_IN_FROMTO_HDR && url->maddr_param.slen) { /* Detect IPv6 IP address */ if (pj_memchr(url->maddr_param.ptr, ':', url->maddr_param.slen)) { copy_advance_pair_quote_cond(buf, ";maddr=", 7, url->maddr_param, '[', ']'); } else { copy_advance_pair_escape(buf, ";maddr=", 7, url->maddr_param, pc->pjsip_PARAM_CHAR_SPEC); } } /* lr param is not allowed in From, To, and Contact header. */ if (url->lr_param && context != PJSIP_URI_IN_FROMTO_HDR && context != PJSIP_URI_IN_CONTACT_HDR) { pj_str_t lr = { ";lr", 3 }; if (endbuf - buf < 3) return -1; copy_advance_check(buf, lr); } /* Other param. */ printed = pjsip_param_print_on(&url->other_param, buf, endbuf-buf, &pc->pjsip_PARAM_CHAR_SPEC, &pc->pjsip_PARAM_CHAR_SPEC, ';'); if (printed < 0) return -1; buf += printed; /* Header param. * Header param is only allowed in these contexts: * - PJSIP_URI_IN_CONTACT_HDR * - PJSIP_URI_IN_OTHER */ if (context == PJSIP_URI_IN_CONTACT_HDR || context == PJSIP_URI_IN_OTHER) { printed = pjsip_param_print_on(&url->header_param, buf, endbuf-buf, &pc->pjsip_HDR_CHAR_SPEC, &pc->pjsip_HDR_CHAR_SPEC, '?'); if (printed < 0) return -1; buf += printed; } *buf = '\0'; return buf-startbuf; }
/* * main() * * argv[] contains the registration information. */ int main(int argc, char *argv[]) { pj_status_t status; int loglevel = 0; if (argc < 4) { usage(argv[0]); exit(1); } if (argc > 5){ sscanf(argv[5], "%d", &loglevel); } pjsua_acc_id acc_id; /* Create pjsua first! */ status = pjsua_create(); if (status != PJ_SUCCESS) error_exit("Error in pjsua_create()", status); /* Init pjsua */ { pjsua_config cfg; pjsua_logging_config log_cfg; pjsua_media_config media_cfg; // This will prevent the SIP stack from trying to switch to TCP. // It will prevent the stack from giving "Transport unavailable" errors. // http://trac.pjsip.org/repos/wiki/Using_SIP_TCP pjsip_cfg()->endpt.disable_tcp_switch = PJ_TRUE; pjsua_config_default(&cfg); cfg.cb.on_incoming_call = &on_incoming_call; cfg.cb.on_call_media_state = &on_call_media_state; cfg.cb.on_call_state = &on_call_state; cfg.cb.on_call_tsx_state = &on_call_tsx_state; pjsua_logging_config_default(&log_cfg); log_cfg.console_level = loglevel; // 0 = Mute console, 3 = somewhat useful, 4 = overly verbose. pjsua_media_config_default(&media_cfg); status = pjsua_init(&cfg, &log_cfg, &media_cfg); if (status != PJ_SUCCESS) error_exit("Error in pjsua_init()", status); } /* Add UDP transport. */ { pjsua_transport_config cfg; pjsua_transport_config_default(&cfg); sscanf(argv[4], "%d", &cfg.port); status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &cfg, NULL); if (status != PJ_SUCCESS) error_exit("Error creating transport", status); printf("Created UDP transport on port %d.\n", cfg.port); } /* Initialization is done, now start pjsua */ status = pjsua_start(); if (status != PJ_SUCCESS) error_exit("Error starting pjsua", status); /* Register to SIP server by creating SIP account. */ { char reg_uri_buf[80] = "sip:"; char uri_buf[80] = "sip:"; pjsua_acc_config cfg; char* username = argv[1]; char* password = argv[2]; char* domain = argv[3]; strcat (uri_buf, username); strcat (reg_uri_buf, domain); strcat (uri_buf, "@"); strcat (uri_buf, domain); pjsua_acc_config_default(&cfg); cfg.id = pj_str(uri_buf); printf("Registering: %.*s.\n", (int)cfg.id.slen, cfg.id.ptr); cfg.reg_uri = pj_str(reg_uri_buf); cfg.cred_count = 1; cfg.cred_info[0].realm = pj_str(domain); cfg.cred_info[0].scheme = pj_str("Digest"); cfg.cred_info[0].username = pj_str(username); cfg.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; cfg.cred_info[0].data = pj_str(password); cfg.register_on_acc_add = PJ_FALSE; status = pjsua_acc_add(&cfg, PJ_TRUE, &acc_id); if (status != PJ_SUCCESS) error_exit("Error adding account", status); or_event(OR_READY, "Agent initialized.."); } /* Start by muting the audio device. This a passive agent, and sound is just wasted CPU time and uwanted side effects. */ pjsua_set_null_snd_dev(); fflush(stdout); /* Wait until user press "q" to quit. */ for (;;) { char option[256]; if (fgets(option, sizeof (option), stdin) == NULL) { or_status (OR_ERROR, "EOF while reading stdin, will quit now.."); break; } /* Dial command. */ if (option[0] == 'd') { pj_str_t uri = pj_str(&option[1]); status = pjsua_call_make_call(acc_id, &uri, 0, NULL, NULL, NULL); if (status != PJ_SUCCESS) { or_status (OR_ERROR, "Could not make call"); } else { or_status (OR_OK, "Dialling..."); } } /* Register */ else if (option[0] == 'r') { register_account(acc_id); } /* Unregister */ else if (option[0] == 'u') { unregister_account(acc_id); } /* Enable autoanswer */ else if (option[0] == 'a') { if (option[1] == '1') { autoanswer[0] = true; or_status(OR_OK, "Autoanswer enabled."); } else { or_status(OR_ERROR, "Invalid account."); } } /* Disable autoanswer (manual answer) */ else if (option[0] == 'm') { autoanswer[0] = false; or_status(OR_OK, "Autoanswer disabled."); } /* Pickup incoming call, unsupported for now. */ else if (option[0] == 'p') { if (current_call != PJSUA_INVALID_ID) { pjsua_call_answer(current_call, 200, NULL, NULL); or_status(OR_OK, "Call picked up."); } else { or_status(OR_ERROR, "No call to pick up."); } } /* Hang up current call */ else if (option[0] == 'H') { if (current_call != PJSUA_INVALID_ID) { pjsua_call_hangup (current_call, 0,NULL, NULL); or_status (OR_OK, "Hanging up current call..."); } else { or_status(OR_ERROR, "No call to hang up."); } } /* Full hangup.. */ else if (option[0] == 'h') { pjsua_call_hangup_all(); or_status (OR_OK, "Hanging up all calls..."); } /* Status */ else if (option[0] == 's') { or_dump_status(); } /* Exit application. */ else if (option[0] == 'q') { break; } else { or_status (OR_ERROR, "Unknown command:"); } } pjsua_destroy(); or_status (OR_OK, "Exiting..."); return 0; }
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:[email protected]: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, 600, 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:[email protected]: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, 600, 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:[email protected]: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:[email protected]: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:[email protected]: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:[email protected]: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:[email protected]: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:[email protected]: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:[email protected]: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:[email protected]: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:[email protected]: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; }
/* * Message receiver handler. */ static pj_bool_t on_rx_message(pjsip_rx_data *rdata) { pjsip_msg *msg = rdata->msg_info.msg; pj_str_t branch_param = rdata->msg_info.via->branch_param; pj_status_t status; if (pj_stricmp2(&branch_param, TEST1_BRANCH_ID) == 0 || pj_stricmp2(&branch_param, TEST2_BRANCH_ID) == 0) { /* * TEST1_BRANCH_ID tests that non-INVITE transaction transmits 2xx * final response using correct transport and terminates transaction * after 32 seconds. * * TEST2_BRANCH_ID performs similar test for non-2xx final response. */ int status_code = (pj_stricmp2(&branch_param, TEST1_BRANCH_ID) == 0) ? TEST1_STATUS_CODE : TEST2_STATUS_CODE; if (msg->type == PJSIP_REQUEST_MSG) { /* On received request, create UAS and respond with final * response. */ pjsip_transaction *tsx; status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx); if (status != PJ_SUCCESS) { app_perror(" error: unable to create transaction", status); test_complete = -110; return PJ_TRUE; } pjsip_tsx_recv_msg(tsx, rdata); save_key(tsx); send_response(rdata, tsx, status_code); } else { /* Verify the response received. */ ++recv_count; /* Verify status code. */ if (msg->line.status.code != status_code) { PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); test_complete = -113; } /* Verify that no retransmissions is received. */ if (recv_count > 1) { PJ_LOG(3,(THIS_FILE, " error: retransmission received")); test_complete = -114; } } return PJ_TRUE; } else if (pj_stricmp2(&branch_param, TEST3_BRANCH_ID) == 0) { /* TEST3_BRANCH_ID tests provisional response. */ if (msg->type == PJSIP_REQUEST_MSG) { /* On received request, create UAS and respond with provisional * response, then schedule timer to send final response. */ pjsip_transaction *tsx; status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx); if (status != PJ_SUCCESS) { app_perror(" error: unable to create transaction", status); test_complete = -116; return PJ_TRUE; } pjsip_tsx_recv_msg(tsx, rdata); save_key(tsx); send_response(rdata, tsx, TEST3_PROVISIONAL_CODE); schedule_send_response(rdata, &tsx->transaction_key, TEST3_STATUS_CODE, 2000); } else { /* Verify the response received. */ ++recv_count; if (recv_count == 1) { /* Verify status code. */ if (msg->line.status.code != TEST3_PROVISIONAL_CODE) { PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); test_complete = -123; } } else if (recv_count == 2) { /* Verify status code. */ if (msg->line.status.code != TEST3_STATUS_CODE) { PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); test_complete = -124; } } else { PJ_LOG(3,(THIS_FILE, " error: retransmission received")); test_complete = -125; } } return PJ_TRUE; } else if (pj_stricmp2(&branch_param, TEST4_BRANCH_ID) == 0 || pj_stricmp2(&branch_param, TEST5_BRANCH_ID) == 0 || pj_stricmp2(&branch_param, TEST6_BRANCH_ID) == 0) { /* TEST4_BRANCH_ID: absorbs retransmissions in TRYING state. */ /* TEST5_BRANCH_ID: retransmit last response in PROCEEDING state. */ /* TEST6_BRANCH_ID: retransmit last response in COMPLETED state. */ if (msg->type == PJSIP_REQUEST_MSG) { /* On received request, create UAS. */ pjsip_transaction *tsx; PJ_LOG(4,(THIS_FILE, " received request (probably retransmission)")); status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx); if (status != PJ_SUCCESS) { app_perror(" error: unable to create transaction", status); test_complete = -130; return PJ_TRUE; } pjsip_tsx_recv_msg(tsx, rdata); save_key(tsx); if (pj_stricmp2(&branch_param, TEST4_BRANCH_ID) == 0) { } else if (pj_stricmp2(&branch_param, TEST5_BRANCH_ID) == 0) { send_response(rdata, tsx, TEST5_PROVISIONAL_CODE); } else if (pj_stricmp2(&branch_param, TEST6_BRANCH_ID) == 0) { PJ_LOG(4,(THIS_FILE, " sending provisional response")); send_response(rdata, tsx, TEST6_PROVISIONAL_CODE); PJ_LOG(4,(THIS_FILE, " sending final response")); send_response(rdata, tsx, TEST6_STATUS_CODE); } } else { /* Verify the response received. */ PJ_LOG(4,(THIS_FILE, " received response number %d", recv_count)); ++recv_count; if (pj_stricmp2(&branch_param, TEST4_BRANCH_ID) == 0) { PJ_LOG(3,(THIS_FILE, " error: not expecting response!")); test_complete = -132; } else if (pj_stricmp2(&branch_param, TEST5_BRANCH_ID) == 0) { if (rdata->msg_info.msg->line.status.code!=TEST5_PROVISIONAL_CODE) { PJ_LOG(3,(THIS_FILE, " error: incorrect status code!")); test_complete = -133; } if (recv_count > TEST5_RESPONSE_COUNT) { PJ_LOG(3,(THIS_FILE, " error: not expecting response!")); test_complete = -134; } } else if (pj_stricmp2(&branch_param, TEST6_BRANCH_ID) == 0) { int code = rdata->msg_info.msg->line.status.code; switch (recv_count) { case 1: if (code != TEST6_PROVISIONAL_CODE) { PJ_LOG(3,(THIS_FILE, " error: invalid code!")); test_complete = -135; } break; case 2: case 3: if (code != TEST6_STATUS_CODE) { PJ_LOG(3,(THIS_FILE, " error: invalid code %d " "(expecting %d)", code, TEST6_STATUS_CODE)); test_complete = -136; } break; default: PJ_LOG(3,(THIS_FILE, " error: not expecting response")); test_complete = -137; break; } } } return PJ_TRUE; } else if (pj_stricmp2(&branch_param, TEST7_BRANCH_ID) == 0 || pj_stricmp2(&branch_param, TEST8_BRANCH_ID) == 0) { /* * TEST7_BRANCH_ID and TEST8_BRANCH_ID test the retransmission * of INVITE final response */ if (msg->type == PJSIP_REQUEST_MSG) { /* On received request, create UAS. */ pjsip_transaction *tsx; status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx); if (status != PJ_SUCCESS) { app_perror(" error: unable to create transaction", status); test_complete = -140; return PJ_TRUE; } pjsip_tsx_recv_msg(tsx, rdata); save_key(tsx); if (pj_stricmp2(&branch_param, TEST7_BRANCH_ID) == 0) { send_response(rdata, tsx, TEST7_STATUS_CODE); } else { send_response(rdata, tsx, TEST8_STATUS_CODE); } } else { int code; ++recv_count; if (pj_stricmp2(&branch_param, TEST7_BRANCH_ID) == 0) code = TEST7_STATUS_CODE; else code = TEST8_STATUS_CODE; if (recv_count==1) { if (rdata->msg_info.msg->line.status.code != code) { PJ_LOG(3,(THIS_FILE," error: invalid status code")); test_complete = -141; } recv_last = rdata->pkt_info.timestamp; } else { pj_time_val now; unsigned msec, msec_expected; now = rdata->pkt_info.timestamp; PJ_TIME_VAL_SUB(now, recv_last); msec = now.sec*1000 + now.msec; msec_expected = (1 << (recv_count-2)) * pjsip_cfg()->tsx.t1; if (msec_expected > pjsip_cfg()->tsx.t2) msec_expected = pjsip_cfg()->tsx.t2; if (DIFF(msec, msec_expected) > MAX_ALLOWED_DIFF) { PJ_LOG(3,(THIS_FILE, " error: incorrect retransmission " "time (%d ms expected, %d ms received", msec_expected, msec)); test_complete = -142; } if (recv_count > 11) { PJ_LOG(3,(THIS_FILE," error: too many responses (%d)", recv_count)); test_complete = -143; } recv_last = rdata->pkt_info.timestamp; } } return PJ_TRUE; } else if (pj_stricmp2(&branch_param, TEST9_BRANCH_ID) == 0) { /* * TEST9_BRANCH_ID tests that the retransmission of INVITE final * response should cease when ACK is received. Transaction also MUST * terminate in T4 seconds. */ if (msg->type == PJSIP_REQUEST_MSG) { /* On received request, create UAS. */ pjsip_transaction *tsx; status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx); if (status != PJ_SUCCESS) { app_perror(" error: unable to create transaction", status); test_complete = -150; return PJ_TRUE; } pjsip_tsx_recv_msg(tsx, rdata); save_key(tsx); send_response(rdata, tsx, TEST9_STATUS_CODE); } else { ++recv_count; if (rdata->msg_info.msg->line.status.code != TEST9_STATUS_CODE) { PJ_LOG(3,(THIS_FILE," error: invalid status code")); test_complete = -151; } if (recv_count==1) { recv_last = rdata->pkt_info.timestamp; } else if (recv_count < 5) { /* Let UAS retransmit some messages before we send ACK. */ pj_time_val now; unsigned msec, msec_expected; now = rdata->pkt_info.timestamp; PJ_TIME_VAL_SUB(now, recv_last); msec = now.sec*1000 + now.msec; msec_expected = (1 << (recv_count-2)) * pjsip_cfg()->tsx.t1; if (msec_expected > pjsip_cfg()->tsx.t2) msec_expected = pjsip_cfg()->tsx.t2; if (DIFF(msec, msec_expected) > MAX_ALLOWED_DIFF) { PJ_LOG(3,(THIS_FILE, " error: incorrect retransmission " "time (%d ms expected, %d ms received", msec_expected, msec)); test_complete = -152; } recv_last = rdata->pkt_info.timestamp; } else if (recv_count == 5) { pjsip_tx_data *tdata; pjsip_sip_uri *uri; pjsip_via_hdr *via; status = pjsip_endpt_create_request_from_hdr( endpt, &pjsip_ack_method, rdata->msg_info.to->uri, rdata->msg_info.from, rdata->msg_info.to, NULL, rdata->msg_info.cid, rdata->msg_info.cseq->cseq, NULL, &tdata); if (status != PJ_SUCCESS) { app_perror(" error: unable to create ACK", status); test_complete = -153; return PJ_TRUE; } uri=(pjsip_sip_uri*)pjsip_uri_get_uri(tdata->msg->line.req.uri); uri->transport_param = pj_str("loop-dgram"); via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL); via->branch_param = pj_str(TEST9_BRANCH_ID); status = pjsip_endpt_send_request_stateless(endpt, tdata, NULL, NULL); if (status != PJ_SUCCESS) { app_perror(" error: unable to send ACK", status); test_complete = -154; } } else { PJ_LOG(3,(THIS_FILE," error: too many responses (%d)", recv_count)); test_complete = -155; } } return PJ_TRUE; } else if (pj_stricmp2(&branch_param, TEST10_BRANCH_ID) == 0 || pj_stricmp2(&branch_param, TEST11_BRANCH_ID) == 0 || pj_stricmp2(&branch_param, TEST12_BRANCH_ID) == 0) { int test_num, code1, code2; if (pj_stricmp2(&branch_param, TEST10_BRANCH_ID) == 0) test_num=10, code1 = 100, code2 = 0; else if (pj_stricmp2(&branch_param, TEST11_BRANCH_ID) == 0) test_num=11, code1 = 100, code2 = 200; else test_num=12, code1 = 200, code2 = 0; if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG) { /* On received response, create UAS. */ pjsip_transaction *tsx; status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx); if (status != PJ_SUCCESS) { app_perror(" error: unable to create transaction", status); test_complete = -150; return PJ_TRUE; } pjsip_tsx_recv_msg(tsx, rdata); save_key(tsx); schedule_send_response(rdata, &tsx_key, code1, 1000); if (code2) schedule_send_response(rdata, &tsx_key, code2, 2000); } else { } return PJ_TRUE; } return PJ_FALSE; }
/* * This callback is called by active socket when pending accept() operation * has completed. */ static pj_bool_t on_accept_complete(pj_activesock_t *asock, pj_sock_t sock, const pj_sockaddr_t *src_addr, int src_addr_len) { struct tcp_listener *listener; struct tcp_transport *tcp; char addr[PJ_INET6_ADDRSTRLEN+10]; pjsip_tp_state_callback state_cb; pj_sockaddr tmp_src_addr, tmp_dst_addr; int addr_len; pj_status_t status; PJ_UNUSED_ARG(src_addr_len); listener = (struct tcp_listener*) pj_activesock_get_user_data(asock); PJ_ASSERT_RETURN(sock != PJ_INVALID_SOCKET, PJ_TRUE); if (!listener->is_registered) return PJ_FALSE; PJ_LOG(4,(listener->factory.obj_name, "TCP listener %.*s:%d: got incoming TCP connection " "from %s, sock=%d", (int)listener->factory.addr_name.host.slen, listener->factory.addr_name.host.ptr, listener->factory.addr_name.port, pj_sockaddr_print(src_addr, addr, sizeof(addr), 3), sock)); /* Apply QoS, if specified */ status = pj_sock_apply_qos2(sock, listener->qos_type, &listener->qos_params, 2, listener->factory.obj_name, "incoming SIP TCP socket"); /* Apply socket options, if specified */ if (listener->sockopt_params.cnt) status = pj_sock_setsockopt_params(sock, &listener->sockopt_params); /* tcp_create() expect pj_sockaddr, so copy src_addr to temporary var, * just in case. */ pj_bzero(&tmp_src_addr, sizeof(tmp_src_addr)); pj_sockaddr_cp(&tmp_src_addr, src_addr); /* Get local address */ addr_len = sizeof(tmp_dst_addr); status = pj_sock_getsockname(sock, &tmp_dst_addr, &addr_len); if (status != PJ_SUCCESS) { pj_sockaddr_cp(&tmp_dst_addr, &listener->factory.local_addr); } /* * Incoming connection! * Create TCP transport for the new socket. */ status = tcp_create( listener, NULL, sock, PJ_TRUE, &tmp_dst_addr, &tmp_src_addr, &tcp); if (status == PJ_SUCCESS) { status = tcp_start_read(tcp); if (status != PJ_SUCCESS) { PJ_LOG(3,(tcp->base.obj_name, "New transport cancelled")); tcp_destroy(&tcp->base, status); } else { if (tcp->base.is_shutdown || tcp->base.is_destroying) { return PJ_TRUE; } /* Start keep-alive timer */ if (pjsip_cfg()->tcp.keep_alive_interval) { pj_time_val delay = { 0 }; delay.sec = pjsip_cfg()->tcp.keep_alive_interval; pjsip_endpt_schedule_timer(listener->endpt, &tcp->ka_timer, &delay); tcp->ka_timer.id = PJ_TRUE; pj_gettimeofday(&tcp->last_activity); } /* Notify application of transport state accepted */ state_cb = pjsip_tpmgr_get_state_cb(tcp->base.tpmgr); if (state_cb) { pjsip_transport_state_info state_info; pj_bzero(&state_info, sizeof(state_info)); (*state_cb)(&tcp->base, PJSIP_TP_STATE_CONNECTED, &state_info); } } } return PJ_TRUE; }
static pj_status_t init_options(int argc, char *argv[]) { enum { OPT_THREAD_COUNT = 1, OPT_REAL_SDP, OPT_TRYING, OPT_RINGING }; struct pj_getopt_option long_options[] = { { "local-port", 1, 0, 'p' }, { "count", 1, 0, 'c' }, { "thread-count", 1, 0, OPT_THREAD_COUNT }, { "method", 1, 0, 'm' }, { "help", 0, 0, 'h' }, { "stateless", 0, 0, 's' }, { "timeout", 1, 0, 't' }, { "real-sdp", 0, 0, OPT_REAL_SDP }, { "verbose", 0, 0, 'v' }, { "use-tcp", 0, 0, 'T' }, { "window", 1, 0, 'w' }, { "delay", 1, 0, 'd' }, { "trying", 0, 0, OPT_TRYING}, { "ringing", 0, 0, OPT_RINGING}, { NULL, 0, 0, 0 }, }; int c; int option_index; /* Init default application configs */ app.local_port = 5060; app.thread_count = 1; app.client.job_count = DEFAULT_COUNT; app.client.method = *pjsip_get_options_method(); app.client.job_window = c = JOB_WINDOW; app.client.timeout = 60; app.log_level = 3; /* Parse options */ pj_optind = 0; while((c=pj_getopt_long(argc,argv, "p:c:m:t:w:d:hsv", long_options, &option_index))!=-1) { switch (c) { case 'p': app.local_port = my_atoi(pj_optarg); if (app.local_port < 0 || app.local_port > 65535) { PJ_LOG(3,(THIS_FILE, "Invalid --local-port %s", pj_optarg)); return -1; } break; case 'c': app.client.job_count = my_atoi(pj_optarg); if (app.client.job_count < 0) { PJ_LOG(3,(THIS_FILE, "Invalid --local-port %s", pj_optarg)); return -1; } if (app.client.job_count > pjsip_cfg()->tsx.max_count) PJ_LOG(3,(THIS_FILE, "Warning: --count value (%d) exceeds maximum " "transaction count (%d)", app.client.job_count, pjsip_cfg()->tsx.max_count)); break; case OPT_THREAD_COUNT: app.thread_count = my_atoi(pj_optarg); if (app.thread_count < 1 || app.thread_count > 16) { PJ_LOG(3,(THIS_FILE, "Invalid --thread-count %s", pj_optarg)); return -1; } break; case 'm': { pj_str_t temp = pj_str((char*)pj_optarg); pjsip_method_init_np(&app.client.method, &temp); } break; case 'h': usage(); return -1; case 's': app.client.stateless = PJ_TRUE; break; case OPT_REAL_SDP: app.real_sdp = 1; break; case 'v': app.log_level++; break; case 't': app.client.timeout = my_atoi(pj_optarg); if (app.client.timeout < 0 || app.client.timeout > 600) { PJ_LOG(3,(THIS_FILE, "Invalid --timeout %s", pj_optarg)); return -1; } break; case 'w': app.client.job_window = my_atoi(pj_optarg); if (app.client.job_window <= 0) { PJ_LOG(3,(THIS_FILE, "Invalid --window %s", pj_optarg)); return -1; } break; case 'T': app.use_tcp = PJ_TRUE; break; case 'd': app.server.delay = my_atoi(pj_optarg); if (app.server.delay > 3600) { PJ_LOG(3,(THIS_FILE, "I think --delay %s is too long", pj_optarg)); return -1; } break; case OPT_TRYING: app.server.send_trying = 1; break; case OPT_RINGING: app.server.send_ringing = 1; break; default: PJ_LOG(1,(THIS_FILE, "Invalid argument. Use --help to see help")); return -1; } } if (pj_optind != argc) { if (verify_sip_url(argv[pj_optind]) != PJ_SUCCESS) { PJ_LOG(1,(THIS_FILE, "Invalid SIP URI %s", argv[pj_optind])); return -1; } app.client.dst_uri = pj_str(argv[pj_optind]); pj_optind++; } if (pj_optind != argc) { PJ_LOG(1,(THIS_FILE, "Error: unknown options %s", argv[pj_optind])); return -1; } return 0; }
/* * This is the handler to receive message for this test. It is used to * control and verify the behavior of the message transmitted by the * transaction. */ static pj_bool_t msg_receiver_on_rx_request(pjsip_rx_data *rdata) { if (pj_stricmp2(&rdata->msg_info.via->branch_param, TEST1_BRANCH_ID) == 0) { /* * The TEST1_BRANCH_ID test performs the verifications for transaction * retransmission mechanism. It will not answer the incoming request * with any response. */ pjsip_msg *msg = rdata->msg_info.msg; PJ_LOG(4,(THIS_FILE, " received request")); /* Only wants to take INVITE or OPTIONS method. */ if (msg->line.req.method.id != PJSIP_INVITE_METHOD && msg->line.req.method.id != PJSIP_OPTIONS_METHOD) { PJ_LOG(3,(THIS_FILE, " error: received unexpected method %.*s", msg->line.req.method.name.slen, msg->line.req.method.name.ptr)); test_complete = -600; return PJ_TRUE; } if (recv_count == 0) { recv_count++; //pj_gettimeofday(&recv_last); recv_last = rdata->pkt_info.timestamp; } else { pj_time_val now; unsigned msec_expected, msec_elapsed; int max_received; //pj_gettimeofday(&now); now = rdata->pkt_info.timestamp; PJ_TIME_VAL_SUB(now, recv_last); msec_elapsed = now.sec*1000 + now.msec; ++recv_count; msec_expected = (1<<(recv_count-2))*pjsip_cfg()->tsx.t1; if (msg->line.req.method.id != PJSIP_INVITE_METHOD) { if (msec_expected > pjsip_cfg()->tsx.t2) msec_expected = pjsip_cfg()->tsx.t2; max_received = 11; } else { max_received = 7; } if (DIFF(msec_expected, msec_elapsed) > TEST1_ALLOWED_DIFF) { PJ_LOG(3,(THIS_FILE, " error: expecting retransmission no. %d in %d " "ms, received in %d ms", recv_count-1, msec_expected, msec_elapsed)); test_complete = -610; } if (recv_count > max_received) { PJ_LOG(3,(THIS_FILE, " error: too many messages (%d) received", recv_count)); test_complete = -620; } //pj_gettimeofday(&recv_last); recv_last = rdata->pkt_info.timestamp; } return PJ_TRUE; } else if (pj_stricmp2(&rdata->msg_info.via->branch_param, TEST4_BRANCH_ID) == 0) { /* * The TEST4_BRANCH_ID test simulates transport failure after several * retransmissions. */ recv_count++; if (recv_count == TEST4_RETRANSMIT_CNT) { /* Simulate transport failure. */ pjsip_loop_set_failure(loop, 2, NULL); } else if (recv_count > TEST4_RETRANSMIT_CNT) { PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!", recv_count)); test_complete = -631; } return PJ_TRUE; } else if (pj_stricmp2(&rdata->msg_info.via->branch_param, TEST5_BRANCH_ID) == 0) { /* * The TEST5_BRANCH_ID test simulates user terminating the transaction * after several retransmissions. */ recv_count++; if (recv_count == TEST5_RETRANSMIT_CNT+1) { pj_str_t key; pjsip_transaction *tsx; pjsip_tsx_create_key( rdata->tp_info.pool, &key, PJSIP_ROLE_UAC, &rdata->msg_info.msg->line.req.method, rdata); tsx = pjsip_tsx_layer_find_tsx(&key, PJ_TRUE); if (tsx) { pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED); pj_grp_lock_release(tsx->grp_lock); } else { PJ_LOG(3,(THIS_FILE, " error: uac transaction not found!")); test_complete = -633; } } else if (recv_count > TEST5_RETRANSMIT_CNT+1) { PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!", recv_count)); test_complete = -634; } return PJ_TRUE; } else if (pj_stricmp2(&rdata->msg_info.via->branch_param, TEST6_BRANCH_ID) == 0) { /* * The TEST6_BRANCH_ID test successfull non-INVITE transaction. */ pj_status_t status; recv_count++; if (recv_count > 1) { PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!", recv_count)); test_complete = -635; } status = pjsip_endpt_respond_stateless(endpt, rdata, 202, NULL, NULL, NULL); if (status != PJ_SUCCESS) { app_perror(" error: unable to send response", status); test_complete = -636; } return PJ_TRUE; } else if (pj_stricmp2(&rdata->msg_info.via->branch_param, TEST7_BRANCH_ID) == 0) { /* * The TEST7_BRANCH_ID test successfull non-INVITE transaction * with provisional response. */ pj_status_t status; pjsip_response_addr res_addr; struct response *r; pjsip_tx_data *tdata; pj_time_val delay = { 2, 0 }; recv_count++; if (recv_count > 1) { PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!", recv_count)); test_complete = -640; return PJ_TRUE; } /* Respond with provisional response */ status = pjsip_endpt_create_response(endpt, rdata, 100, NULL, &tdata); pj_assert(status == PJ_SUCCESS); status = pjsip_get_response_addr(tdata->pool, rdata, &res_addr); pj_assert(status == PJ_SUCCESS); status = pjsip_endpt_send_response(endpt, &res_addr, tdata, NULL, NULL); pj_assert(status == PJ_SUCCESS); /* Create the final response. */ status = pjsip_endpt_create_response(endpt, rdata, 202, NULL, &tdata); pj_assert(status == PJ_SUCCESS); /* Schedule sending final response in couple of of secs. */ r = PJ_POOL_ALLOC_T(tdata->pool, struct response); r->res_addr = res_addr; r->tdata = tdata; if (r->res_addr.transport) pjsip_transport_add_ref(r->res_addr.transport); timer.entry.cb = &send_response_callback; timer.entry.user_data = r; pjsip_endpt_schedule_timer(endpt, &timer.entry, &delay); return PJ_TRUE; } else
/* * Verify that incoming request with Replaces header can be processed. */ PJ_DEF(pj_status_t) pjsip_replaces_verify_request( pjsip_rx_data *rdata, pjsip_dialog **p_dlg, pj_bool_t lock_dlg, pjsip_tx_data **p_tdata) { const pj_str_t STR_REPLACES = { "Replaces", 8 }; pjsip_replaces_hdr *rep_hdr; int code = 200; const char *warn_text = NULL; pjsip_hdr res_hdr_list; pjsip_dialog *dlg = NULL; pjsip_inv_session *inv; pj_status_t status = PJ_SUCCESS; PJ_ASSERT_RETURN(rdata && p_dlg, PJ_EINVAL); /* Check that pjsip_replaces_init_module() has been called. */ PJ_ASSERT_RETURN(the_endpt != NULL, PJ_EINVALIDOP); /* Init output arguments */ *p_dlg = NULL; if (p_tdata) *p_tdata = NULL; pj_list_init(&res_hdr_list); /* Find Replaces header */ rep_hdr = (pjsip_replaces_hdr*) pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_REPLACES, NULL); if (!rep_hdr) { /* No Replaces header. No further processing is necessary. */ return PJ_SUCCESS; } /* Check that there's no other Replaces header and return 400 Bad Request * if not. */ if (pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_REPLACES, rep_hdr->next)) { code = PJSIP_SC_BAD_REQUEST; warn_text = "Found multiple Replaces headers"; goto on_return; } /* Find the dialog identified by Replaces header (and always lock the * dialog no matter what application wants). */ dlg = pjsip_ua_find_dialog(&rep_hdr->call_id, &rep_hdr->to_tag, &rep_hdr->from_tag, PJ_TRUE); /* Respond with 481 "Call/Transaction Does Not Exist" response if * no dialog is found. */ if (dlg == NULL) { code = PJSIP_SC_CALL_TSX_DOES_NOT_EXIST; warn_text = "No dialog found for Replaces request"; goto on_return; } /* Get the invite session within the dialog */ inv = pjsip_dlg_get_inv_session(dlg); /* Return 481 if no invite session is present. */ if (inv == NULL) { code = PJSIP_SC_CALL_TSX_DOES_NOT_EXIST; warn_text = "No INVITE session found for Replaces request"; goto on_return; } /* Return 603 Declined response if invite session has already * terminated */ if (inv->state >= PJSIP_INV_STATE_DISCONNECTED) { code = PJSIP_SC_DECLINE; warn_text = "INVITE session already terminated"; goto on_return; } /* If "early-only" flag is present, check that the invite session * has not been confirmed yet. If the session has been confirmed, * return 486 "Busy Here" response. */ if (rep_hdr->early_only && inv->state >= PJSIP_INV_STATE_CONNECTING) { code = PJSIP_SC_BUSY_HERE; warn_text = "INVITE session already established"; goto on_return; } /* If the Replaces header field matches an early dialog that was not * initiated by this UA, it returns a 481 (Call/Transaction Does Not * Exist) response to the new INVITE. */ if (inv->state <= PJSIP_INV_STATE_EARLY && inv->role != PJSIP_ROLE_UAC) { /* Really return 481 only if call haven't reached early state or * accept-replace-in-early-state (ticket #1587) is not allowed. */ if (inv->state != PJSIP_INV_STATE_EARLY || pjsip_cfg()->endpt.accept_replace_in_early_state == PJ_FALSE) { code = PJSIP_SC_CALL_TSX_DOES_NOT_EXIST; warn_text = "Found early INVITE session but not initiated by " "this UA"; goto on_return; } } /* * Looks like everything is okay!! */ *p_dlg = dlg; status = PJ_SUCCESS; code = 200; on_return: /* Create response if necessary */ if (code != 200) { /* If we have dialog we must unlock it */ if (dlg) pjsip_dlg_dec_lock(dlg); /* Create response */ if (p_tdata) { pjsip_tx_data *tdata; const pjsip_hdr *h; status = pjsip_endpt_create_response(the_endpt, rdata, code, NULL, &tdata); if (status != PJ_SUCCESS) return status; /* Add response headers. */ h = res_hdr_list.next; while (h != &res_hdr_list) { pjsip_hdr *cloned; cloned = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, h); PJ_ASSERT_RETURN(cloned, PJ_ENOMEM); pjsip_msg_add_hdr(tdata->msg, cloned); h = h->next; } /* Add warn text, if any */ if (warn_text) { pjsip_warning_hdr *warn_hdr; pj_str_t warn_value = pj_str((char*)warn_text); warn_hdr=pjsip_warning_hdr_create(tdata->pool, 399, pjsip_endpt_name(the_endpt), &warn_value); pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)warn_hdr); } *p_tdata = tdata; } /* Can not return PJ_SUCCESS when response message is produced. * Ref: PROTOS test ~#2490 */ if (status == PJ_SUCCESS) status = PJSIP_ERRNO_FROM_SIP_STATUS(code); } else { /* If application doesn't want to lock the dialog, unlock it */ if (!lock_dlg) pjsip_dlg_dec_lock(dlg); } return status; }