diff options
-rw-r--r-- | pjsip-apps/src/pjsua/pjsua_app.c | 62 | ||||
-rw-r--r-- | pjsip/include/pjsip/sip_msg.h | 4 | ||||
-rw-r--r-- | pjsip/include/pjsip/sip_parser.h | 15 | ||||
-rw-r--r-- | pjsip/include/pjsua-lib/pjsua.h | 29 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_dialog.c | 18 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_parser.c | 30 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_call.c | 191 |
7 files changed, 308 insertions, 41 deletions
diff --git a/pjsip-apps/src/pjsua/pjsua_app.c b/pjsip-apps/src/pjsua/pjsua_app.c index 28bbb427..573494a9 100644 --- a/pjsip-apps/src/pjsua/pjsua_app.c +++ b/pjsip-apps/src/pjsua/pjsua_app.c @@ -37,6 +37,7 @@ static struct app_config pjsua_config cfg; pjsua_logging_config log_cfg; pjsua_media_config media_cfg; + pj_bool_t no_refersub; pj_bool_t no_tcp; pj_bool_t no_udp; pjsua_transport_config udp_cfg; @@ -160,6 +161,7 @@ static void usage(void) puts (" --max-calls=N Maximum number of concurrent calls (default:4, max:255)"); puts (" --thread-cnt=N Number of worker threads (default:1)"); puts (" --duration=SEC Set maximum call duration (default:no limit)"); + puts (" --norefersub Suppress event subscription when transfering calls"); puts (""); puts ("When URL is specified, pjsua will immediately initiate call to that URL"); @@ -287,6 +289,7 @@ static pj_status_t parse_args(int argc, char *argv[], OPT_RX_DROP_PCT, OPT_TX_DROP_PCT, OPT_EC_TAIL, OPT_NEXT_ACCOUNT, OPT_NEXT_CRED, OPT_MAX_CALLS, OPT_DURATION, OPT_NO_TCP, OPT_NO_UDP, OPT_THREAD_CNT, + OPT_NOREFERSUB, }; struct pj_getopt_option long_options[] = { { "config-file",1, 0, OPT_CONFIG_FILE}, @@ -301,6 +304,7 @@ static pj_status_t parse_args(int argc, char *argv[], { "ip-addr", 1, 0, OPT_IP_ADDR}, { "no-tcp", 0, 0, OPT_NO_TCP}, { "no-udp", 0, 0, OPT_NO_UDP}, + { "norefersub", 0, 0, OPT_NOREFERSUB}, { "proxy", 1, 0, OPT_PROXY}, { "outbound", 1, 0, OPT_OUTBOUND_PROXY}, { "registrar", 1, 0, OPT_REGISTRAR}, @@ -456,6 +460,10 @@ static pj_status_t parse_args(int argc, char *argv[], cfg->no_udp = PJ_TRUE; break; + case OPT_NOREFERSUB: /* norefersub */ + cfg->no_refersub = PJ_TRUE; + break; + case OPT_NO_TCP: /* no-tcp */ if (cfg->no_udp) { PJ_LOG(1,(THIS_FILE,"Error: can not disable both TCP and UDP")); @@ -947,6 +955,15 @@ static int write_settings(const struct app_config *config, pj_strcat2(&cfg, line); } + /* No TCP ? */ + if (config->no_tcp) { + pj_strcat2(&cfg, "--no-tcp\n"); + } + + /* No UDP ? */ + if (config->no_udp) { + pj_strcat2(&cfg, "--no-udp\n"); + } /* STUN */ if (config->udp_cfg.stun_config.stun_port1) { @@ -1089,6 +1106,12 @@ static int write_settings(const struct app_config *config, pj_strcat2(&cfg, line); } + /* norefersub ? */ + if (config->no_refersub) { + pj_strcat2(&cfg, "--norefersub\n"); + } + + pj_strcat2(&cfg, "\n#\n# Buddies:\n#\n"); /* Add buddies. */ @@ -1473,6 +1496,30 @@ static void on_typing(pjsua_call_id call_id, const pj_str_t *from, } +/** + * Call transfer request status. + */ +static void on_call_transfer_status(pjsua_call_id call_id, + int status_code, + const pj_str_t *status_text, + pj_bool_t final, + pj_bool_t *p_cont) +{ + PJ_LOG(3,(THIS_FILE, "Call %d: transfer status=%d (%.*s) %s", + call_id, status_code, + (int)status_text->slen, status_text->ptr, + (final ? "[final]" : ""))); + + if (status_code/100 == 2) { + PJ_LOG(3,(THIS_FILE, + "Call %d: call transfered successfully, disconnecting call", + call_id)); + pjsua_call_hangup(call_id, PJSIP_SC_GONE, NULL, NULL); + *p_cont = PJ_FALSE; + } +} + + /* * Print buddy list. */ @@ -2147,12 +2194,13 @@ void console_app_main(const pj_str_t *uri_to_call) continue; } - /* Add Refer-Sub: false in outgoing REFER request */ pjsua_msg_data_init(&msg_data); - pjsip_generic_string_hdr_init2(&refer_sub, &STR_REFER_SUB, - &STR_FALSE); - pj_list_push_back(&msg_data.hdr_list, &refer_sub); - + if (app_config.no_refersub) { + /* Add Refer-Sub: false in outgoing REFER request */ + pjsip_generic_string_hdr_init2(&refer_sub, &STR_REFER_SUB, + &STR_FALSE); + pj_list_push_back(&msg_data.hdr_list, &refer_sub); + } if (result.nb_result != NO_NB) { if (result.nb_result == -1 || result.nb_result == 0) puts("You can't do that with transfer call!"); @@ -2167,9 +2215,6 @@ void console_app_main(const pj_str_t *uri_to_call) tmp = pj_str(result.uri_result); pjsua_call_xfer( current_call, &tmp, &msg_data); } - - /* Hangup call regardless of xfer status */ - pjsua_call_hangup(current_call, PJSIP_SC_GONE, NULL, NULL); } break; @@ -2461,6 +2506,7 @@ pj_status_t app_init(int argc, char *argv[]) app_config.cfg.cb.on_buddy_state = &on_buddy_state; app_config.cfg.cb.on_pager = &on_pager; app_config.cfg.cb.on_typing = &on_typing; + app_config.cfg.cb.on_call_transfer_status = &on_call_transfer_status; /* Initialize pjsua */ status = pjsua_init(&app_config.cfg, &app_config.log_cfg, diff --git a/pjsip/include/pjsip/sip_msg.h b/pjsip/include/pjsip/sip_msg.h index b27b2708..6ae33085 100644 --- a/pjsip/include/pjsip/sip_msg.h +++ b/pjsip/include/pjsip/sip_msg.h @@ -431,9 +431,9 @@ typedef enum pjsip_status_code PJSIP_SC_DOES_NOT_EXIST_ANYWHERE = 604, PJSIP_SC_NOT_ACCEPTABLE_ANYWHERE = 606, - PJSIP_SC_TSX_TIMEOUT = 701, + PJSIP_SC_TSX_TIMEOUT = PJSIP_SC_REQUEST_TIMEOUT, /*PJSIP_SC_TSX_RESOLVE_ERROR = 702,*/ - PJSIP_SC_TSX_TRANSPORT_ERROR = 703 + PJSIP_SC_TSX_TRANSPORT_ERROR = PJSIP_SC_SERVICE_UNAVAILABLE } pjsip_status_code; diff --git a/pjsip/include/pjsip/sip_parser.h b/pjsip/include/pjsip/sip_parser.h index d0eb838e..72c103a7 100644 --- a/pjsip/include/pjsip/sip_parser.h +++ b/pjsip/include/pjsip/sip_parser.h @@ -24,7 +24,7 @@ * @brief SIP Message Parser */ -#include <pjsip/sip_types.h> +#include <pjsip/sip_msg.h> #include <pjlib-util/scanner.h> #include <pj/list.h> @@ -191,6 +191,19 @@ PJ_DECL(pjsip_uri*) pjsip_parse_uri( pj_pool_t *pool, unsigned options); /** + * Parse SIP status line. + * + * @param buf Text buffer to parse. + * @param size The size of the buffer. + * @param status_line Structure to receive the parsed elements. + * + * @return PJ_SUCCESS if a status line is parsed successfully. + */ +PJ_DECL(pj_status_t) pjsip_parse_status_line(char *buf, pj_size_t size, + pjsip_status_line *status_line); + + +/** * Parse a packet buffer and build a full SIP message from the packet. This * function parses all parts of the message, including request/status line, * all headers, and the message body. The message body however is only diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h index 2b11df01..39d5caf3 100644 --- a/pjsip/include/pjsua-lib/pjsua.h +++ b/pjsip/include/pjsua-lib/pjsua.h @@ -309,9 +309,32 @@ typedef struct pjsua_callback * is not defined, the default behavior is to accept the * transfer. */ - void (*on_call_transfered)(pjsua_call_id call_id, - const pj_str_t *dst, - pjsip_status_code *code); + void (*on_call_transfer_request)(pjsua_call_id call_id, + const pj_str_t *dst, + pjsip_status_code *code); + + /** + * Notify application of the status of previously sent call + * transfer request. Application can monitor the status of the + * call transfer request, for example to decide whether to + * terminate existing call. + * + * @param call_id Call ID. + * @param status_code Status progress of the transfer request. + * @param status_text Status progress text. + * @param final If non-zero, no further notification will + * be reported. The status_code specified in + * this callback is the final status. + * @param p_cont Initially will be set to non-zero, application + * can set this to FALSE if it no longer wants + * to receie further notification (for example, + * after it hangs up the call). + */ + void (*on_call_transfer_status)(pjsua_call_id call_id, + int status_code, + const pj_str_t *status_text, + pj_bool_t final, + pj_bool_t *p_cont); /** * Notify application when registration status has changed. diff --git a/pjsip/src/pjsip/sip_dialog.c b/pjsip/src/pjsip/sip_dialog.c index aab06704..8c2f287c 100644 --- a/pjsip/src/pjsip/sip_dialog.c +++ b/pjsip/src/pjsip/sip_dialog.c @@ -793,9 +793,23 @@ PJ_DEF(pj_status_t) pjsip_dlg_add_usage( pjsip_dialog *dlg, */ for (index=0; index<dlg->usage_cnt; ++index) { if (dlg->usage[index] == mod) { - pj_assert(!"This module is already registered"); + /* Module may be registered more than once in the same dialog. + * For example, when call transfer fails, application may retry + * call transfer on the same dialog. + * So return PJ_SUCCESS here. + */ + PJ_LOG(4,(dlg->obj_name, + "Module %.*s already registered as dialog usage, " + "updating the data %p", + (int)mod->name.slen, mod->name.ptr, mod_data)); + dlg->mod_data[mod->id] = mod_data; + pjsip_dlg_dec_lock(dlg); - return PJSIP_ETYPEEXISTS; + return PJ_SUCCESS; + + //pj_assert(!"This module is already registered"); + //pjsip_dlg_dec_lock(dlg); + //return PJSIP_ETYPEEXISTS; } if (dlg->usage[index]->priority > mod->priority) diff --git a/pjsip/src/pjsip/sip_parser.c b/pjsip/src/pjsip/sip_parser.c index b3f13bdb..c3f456a7 100644 --- a/pjsip/src/pjsip/sip_parser.c +++ b/pjsip/src/pjsip/sip_parser.c @@ -1471,6 +1471,36 @@ static void int_parse_status_line( pj_scanner *scanner, pj_scan_get_newline( scanner ); } + +/* + * Public API to parse SIP status line. + */ +PJ_DEF(pj_status_t) pjsip_parse_status_line( char *buf, pj_size_t size, + pjsip_status_line *status_line) +{ + pj_scanner scanner; + PJ_USE_EXCEPTION; + + pj_bzero(status_line, sizeof(*status_line)); + pj_scan_init(&scanner, buf, size, 0, &on_syntax_error); + + PJ_TRY { + int_parse_status_line(&scanner, status_line); + } + PJ_CATCH_ANY { + /* Tolerate the error if it is caused only by missing newline */ + if (status_line->code == 0 && status_line->reason.slen == 0) { + pj_scan_fini(&scanner); + return PJSIP_EINVALIDMSG; + } + } + PJ_END; + + pj_scan_fini(&scanner); + return PJ_SUCCESS; +} + + /* Parse ending of header. */ static void parse_hdr_end( pj_scanner *scanner ) { diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c index d3b88a7b..36e5f737 100644 --- a/pjsip/src/pjsua-lib/pjsua_call.c +++ b/pjsip/src/pjsua-lib/pjsua_call.c @@ -71,7 +71,19 @@ static pj_status_t create_inactive_sdp(pjsua_call *call, * Callback called by event framework when the xfer subscription state * has changed. */ -static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event); +static void xfer_client_on_evsub_state( pjsip_evsub *sub, pjsip_event *event); +static void xfer_server_on_evsub_state( pjsip_evsub *sub, pjsip_event *event); + +/* + * Callback called by event framework when NOTIFY is received for outgoing + * REFER subscription. + */ +static void xfer_on_rx_notify(pjsip_evsub *sub, + pjsip_rx_data *rdata, + int *p_st_code, + pj_str_t **p_st_text, + pjsip_hdr *res_hdr, + pjsip_msg_body **p_body); /* * Reset call descriptor. @@ -1078,7 +1090,7 @@ PJ_DEF(pj_status_t) pjsua_call_xfer( pjsua_call_id call_id, /* Create xfer client subscription. */ pj_bzero(&xfer_cb, sizeof(xfer_cb)); - xfer_cb.on_evsub_state = &xfer_on_evsub_state; + xfer_cb.on_evsub_state = &xfer_client_on_evsub_state; status = pjsip_xfer_create_uac(call->inv->dlg, &xfer_cb, &sub); if (status != PJ_SUCCESS) { @@ -1087,6 +1099,9 @@ PJ_DEF(pj_status_t) pjsua_call_xfer( pjsua_call_id call_id, return status; } + /* Associate this call with the client subscription */ + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, call); + /* * Create REFER request. */ @@ -2144,37 +2159,23 @@ static void pjsua_call_on_rx_offer(pjsip_inv_session *inv, * Callback called by event framework when the xfer subscription state * has changed. */ -static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event) +static void xfer_client_on_evsub_state( pjsip_evsub *sub, pjsip_event *event) { PJ_UNUSED_ARG(event); /* - * When subscription is terminated, clear the xfer_sub member of - * the inv_data. - */ - if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { - pjsua_call *call; - - call = pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); - if (!call) - return; - - pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL); - call->xfer_sub = NULL; - - PJ_LOG(4,(THIS_FILE, "Xfer subscription terminated")); - - } - /* * When subscription is accepted (got 200/OK to REFER), check if * subscription suppressed. */ - else if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_ACCEPTED) { + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_ACCEPTED) { pjsip_rx_data *rdata; pjsip_generic_string_hdr *refer_sub; const pj_str_t REFER_SUB = { "Refer-Sub", 9 }; + pjsua_call *call; + + call = pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); /* Must be receipt of response message */ pj_assert(event->type == PJSIP_EVENT_TSX_STATE && @@ -2188,13 +2189,152 @@ static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event) /* Check if subscription is suppressed */ if (refer_sub && pj_stricmp2(&refer_sub->hvalue, "false")==0) { + /* Since no subscription is desired, assume that call has been + * transfered successfully. + */ + if (call && pjsua_var.ua_cfg.cb.on_call_transfer_status) { + const pj_str_t ACCEPTED = { "Accepted", 8 }; + pj_bool_t cont = PJ_FALSE; + (*pjsua_var.ua_cfg.cb.on_call_transfer_status)(call->index, + 200, + &ACCEPTED, + PJ_TRUE, + &cont); + } + /* Yes, subscription is suppressed. * Terminate our subscription now. */ PJ_LOG(4,(THIS_FILE, "Xfer subscription suppressed, terminating " "event subcription...")); pjsip_evsub_terminate(sub, PJ_TRUE); + + } else { + /* Notify application about call transfer progress. + * Initially notify with 100/Accepted status. + */ + if (call && pjsua_var.ua_cfg.cb.on_call_transfer_status) { + const pj_str_t ACCEPTED = { "Accepted", 8 }; + pj_bool_t cont = PJ_FALSE; + (*pjsua_var.ua_cfg.cb.on_call_transfer_status)(call->index, + 100, + &ACCEPTED, + PJ_FALSE, + &cont); + } + } + } + /* + * On incoming NOTIFY, notify application about call transfer progress. + */ + else if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_ACTIVE || + pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) + { + pjsua_call *call; + pjsip_msg *msg; + pjsip_msg_body *body; + pjsip_status_line status_line; + pj_bool_t is_last; + pj_bool_t cont; + pj_status_t status; + + call = pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); + + /* When subscription is terminated, clear the xfer_sub member of + * the inv_data. + */ + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL); + PJ_LOG(4,(THIS_FILE, "Xfer client subscription terminated")); + + } + + if (!call || !event || !pjsua_var.ua_cfg.cb.on_call_transfer_status) { + /* Application is not interested with call progress status */ + return; } + + /* This better be a NOTIFY request */ + if (event->type == PJSIP_EVENT_TSX_STATE && + event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) + { + pjsip_rx_data *rdata; + + rdata = event->body.tsx_state.src.rdata; + + /* Check if there's body */ + msg = rdata->msg_info.msg; + body = msg->body; + if (!body) { + PJ_LOG(4,(THIS_FILE, + "Warning: received NOTIFY without message body")); + return; + } + + /* Check for appropriate content */ + if (pj_stricmp2(&body->content_type.type, "message") != 0 || + pj_stricmp2(&body->content_type.subtype, "sipfrag") != 0) + { + PJ_LOG(4,(THIS_FILE, + "Warning: received NOTIFY with non message/sipfrag " + "content")); + return; + } + + /* Try to parse the content */ + status = pjsip_parse_status_line(body->data, body->len, + &status_line); + if (status != PJ_SUCCESS) { + PJ_LOG(4,(THIS_FILE, + "Warning: received NOTIFY with invalid " + "message/sipfrag content")); + return; + } + + } else { + status_line.code = 500; + status_line.reason = *pjsip_get_status_text(500); + } + + /* Notify application */ + is_last = (pjsip_evsub_get_state(sub)==PJSIP_EVSUB_STATE_TERMINATED); + cont = !is_last; + (*pjsua_var.ua_cfg.cb.on_call_transfer_status)(call->index, + status_line.code, + &status_line.reason, + is_last, &cont); + + if (!cont) { + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL); + } + } +} + + +/* + * Callback called by event framework when the xfer subscription state + * has changed. + */ +static void xfer_server_on_evsub_state( pjsip_evsub *sub, pjsip_event *event) +{ + + PJ_UNUSED_ARG(event); + + /* + * When subscription is terminated, clear the xfer_sub member of + * the inv_data. + */ + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + pjsua_call *call; + + call = pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); + if (!call) + return; + + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL); + call->xfer_sub = NULL; + + PJ_LOG(4,(THIS_FILE, "Xfer server subscription terminated")); } } @@ -2246,9 +2386,10 @@ static void on_call_transfered( pjsip_inv_session *inv, /* Notify callback */ code = PJSIP_SC_OK; - if (pjsua_var.ua_cfg.cb.on_call_transfered) - (*pjsua_var.ua_cfg.cb.on_call_transfered)(existing_call->index, - &refer_to->hvalue, &code); + if (pjsua_var.ua_cfg.cb.on_call_transfer_request) + (*pjsua_var.ua_cfg.cb.on_call_transfer_request)(existing_call->index, + &refer_to->hvalue, + &code); if (code < 200) code = 200; @@ -2304,7 +2445,7 @@ static void on_call_transfered( pjsip_inv_session *inv, /* Init callback */ pj_bzero(&xfer_cb, sizeof(xfer_cb)); - xfer_cb.on_evsub_state = &xfer_on_evsub_state; + xfer_cb.on_evsub_state = &xfer_server_on_evsub_state; /* Init additional header list to be sent with REFER response */ pj_list_init(&hdr_list); |