summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pjsip-apps/src/pjsua/pjsua_app.c62
-rw-r--r--pjsip/include/pjsip/sip_msg.h4
-rw-r--r--pjsip/include/pjsip/sip_parser.h15
-rw-r--r--pjsip/include/pjsua-lib/pjsua.h29
-rw-r--r--pjsip/src/pjsip/sip_dialog.c18
-rw-r--r--pjsip/src/pjsip/sip_parser.c30
-rw-r--r--pjsip/src/pjsua-lib/pjsua_call.c191
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);