diff options
Diffstat (limited to 'pjsip/src')
-rw-r--r-- | pjsip/src/pjsip-ua/sip_inv.c | 17 | ||||
-rw-r--r-- | pjsip/src/pjsip-ua/sip_replaces.c | 355 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_dialog.c | 37 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_endpoint.c | 26 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_ua_layer.c | 75 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_util.c | 25 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_call.c | 223 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_core.c | 5 |
8 files changed, 755 insertions, 8 deletions
diff --git a/pjsip/src/pjsip-ua/sip_inv.c b/pjsip/src/pjsip-ua/sip_inv.c index 97507458..8c6b4ccd 100644 --- a/pjsip/src/pjsip-ua/sip_inv.c +++ b/pjsip/src/pjsip-ua/sip_inv.c @@ -386,8 +386,10 @@ PJ_DEF(pj_status_t) pjsip_inv_usage_init( pjsip_endpoint *endpt, /* Register the module. */ status = pjsip_endpt_register_module(endpt, &mod_inv.mod); + if (status != PJ_SUCCESS) + return status; - return status; + return PJ_SUCCESS; } /* @@ -680,8 +682,9 @@ PJ_DEF(pj_status_t) pjsip_inv_verify_request(pjsip_rx_data *rdata, req_hdr = pjsip_msg_find_hdr(msg, PJSIP_H_REQUIRE, NULL); if (req_hdr) { unsigned i; - pj_str_t STR_100REL = { "100rel", 6}; - pj_str_t STR_TIMER = { "timer", 5 }; + const pj_str_t STR_100REL = { "100rel", 6}; + const pj_str_t STR_TIMER = { "timer", 5 }; + const pj_str_t STR_REPLACES = { "replaces", 8 }; unsigned unsupp_cnt = 0; pj_str_t unsupp_tags[PJSIP_GENERIC_ARRAY_MAX_COUNT]; @@ -696,6 +699,14 @@ PJ_DEF(pj_status_t) pjsip_inv_verify_request(pjsip_rx_data *rdata, { rem_option |= PJSIP_INV_REQUIRE_TIMER; + } else if (pj_stricmp(&req_hdr->values[i], &STR_REPLACES)==0) { + pj_bool_t supp; + + supp = pjsip_endpt_has_capability(endpt, PJSIP_H_SUPPORTED, + NULL, &STR_REPLACES); + if (!supp) + unsupp_tags[unsupp_cnt++] = req_hdr->values[i]; + } else { /* Unknown/unsupported extension tag! */ unsupp_tags[unsupp_cnt++] = req_hdr->values[i]; diff --git a/pjsip/src/pjsip-ua/sip_replaces.c b/pjsip/src/pjsip-ua/sip_replaces.c new file mode 100644 index 00000000..acf8181b --- /dev/null +++ b/pjsip/src/pjsip-ua/sip_replaces.c @@ -0,0 +1,355 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 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 <pjsip-ua/sip_replaces.h> +#include <pjsip-ua/sip_inv.h> +#include <pjsip/print_util.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_errno.h> +#include <pjsip/sip_parser.h> +#include <pjsip/sip_transport.h> +#include <pjsip/sip_ua_layer.h> +#include <pjsip/sip_util.h> +#include <pj/assert.h> +#include <pj/pool.h> +#include <pj/string.h> + + +/* + * Replaces header vptr. + */ +static int replaces_hdr_print( pjsip_replaces_hdr *hdr, + char *buf, pj_size_t size); +static pjsip_replaces_hdr* replaces_hdr_clone( pj_pool_t *pool, + const pjsip_replaces_hdr *hdr); +static pjsip_replaces_hdr* replaces_hdr_shallow_clone( pj_pool_t *pool, + const pjsip_replaces_hdr*); + +static pjsip_hdr_vptr replaces_hdr_vptr = +{ + (pjsip_hdr_clone_fptr) &replaces_hdr_clone, + (pjsip_hdr_clone_fptr) &replaces_hdr_shallow_clone, + (pjsip_hdr_print_fptr) &replaces_hdr_print, +}; + +/* Globals */ +static pjsip_endpoint *the_endpt; + +PJ_DEF(pjsip_replaces_hdr*) pjsip_replaces_hdr_create(pj_pool_t *pool) +{ + pjsip_replaces_hdr *hdr = pj_pool_zalloc(pool, sizeof(*hdr)); + hdr->type = PJSIP_H_OTHER; + hdr->name.ptr = "Replaces"; + hdr->name.slen = 8; + hdr->vptr = &replaces_hdr_vptr; + pj_list_init(hdr); + pj_list_init(&hdr->other_param); + return hdr; +} + +static int replaces_hdr_print( pjsip_replaces_hdr *hdr, + char *buf, pj_size_t size) +{ + char *p = buf; + char *endbuf = buf+size; + int printed; + + copy_advance(p, hdr->name); + *p++ = ':'; + *p++ = ' '; + + copy_advance(p, hdr->call_id); + copy_advance_pair(p, ";to-tag=", 8, hdr->to_tag); + copy_advance_pair(p, ";from-tag=", 10, hdr->from_tag); + + if (hdr->early_only) { + const pj_str_t str_early_only = { ";early-only", 11 }; + copy_advance(p, str_early_only); + } + + printed = pjsip_param_print_on(&hdr->other_param, p, endbuf-p, + &pjsip_TOKEN_SPEC, + &pjsip_TOKEN_SPEC, ';'); + if (printed < 0) + return printed; + + p += printed; + return p - buf; +} + +static pjsip_replaces_hdr* replaces_hdr_clone( pj_pool_t *pool, + const pjsip_replaces_hdr *rhs) +{ + pjsip_replaces_hdr *hdr = pjsip_replaces_hdr_create(pool); + pj_strdup(pool, &hdr->call_id, &rhs->call_id); + pj_strdup(pool, &hdr->to_tag, &rhs->to_tag); + pj_strdup(pool, &hdr->from_tag, &rhs->from_tag); + hdr->early_only = rhs->early_only; + pjsip_param_clone(pool, &hdr->other_param, &rhs->other_param); + return hdr; +} + +static pjsip_replaces_hdr* +replaces_hdr_shallow_clone( pj_pool_t *pool, + const pjsip_replaces_hdr *rhs ) +{ + pjsip_replaces_hdr *hdr = pj_pool_alloc(pool, sizeof(*hdr)); + pj_memcpy(hdr, rhs, sizeof(*hdr)); + pjsip_param_shallow_clone(pool, &hdr->other_param, &rhs->other_param); + return hdr; +} + + +/* + * Parse Replaces header. + */ +static pjsip_hdr *parse_hdr_replaces(pjsip_parse_ctx *ctx) +{ + pjsip_replaces_hdr *hdr = pjsip_replaces_hdr_create(ctx->pool); + const pj_str_t to_tag = { "to-tag", 6 }; + const pj_str_t from_tag = { "from-tag", 8 }; + const pj_str_t early_only_tag = { "early-only", 10 }; + + pj_scan_get(ctx->scanner, &pjsip_TOKEN_SPEC, &hdr->call_id); + + while (*ctx->scanner->curptr == ';') { + pj_str_t pname, pvalue; + + pj_scan_get_char(ctx->scanner); + pjsip_parse_param_imp(ctx->scanner, ctx->pool, &pname, &pvalue, 0); + + if (pj_stricmp(&pname, &to_tag)==0) { + hdr->to_tag = pvalue; + } else if (pj_stricmp(&pname, &from_tag)==0) { + hdr->from_tag = pvalue; + } else if (pj_stricmp(&pname, &early_only_tag)==0) { + hdr->early_only = PJ_TRUE; + } else { + pjsip_param *param = pj_pool_alloc(ctx->pool, sizeof(pjsip_param)); + param->name = pname; + param->value = pvalue; + pj_list_push_back(&hdr->other_param, param); + } + } + pjsip_parse_end_hdr_imp( ctx->scanner ); + return (pjsip_hdr*)hdr; +} + +/* + * Initialize Replaces support in PJSIP. + */ +PJ_DEF(pj_status_t) pjsip_replaces_init_module(pjsip_endpoint *endpt) +{ + pj_status_t status; + const pj_str_t STR_REPLACES = { "replaces", 8 }; + static pj_bool_t is_initialized; + + the_endpt = endpt; + + if (is_initialized) + return PJ_SUCCESS; + + /* Register Replaces header parser */ + status = pjsip_register_hdr_parser( "Replaces", NULL, + &parse_hdr_replaces); + if (status != PJ_SUCCESS) + return status; + + /* Register "replaces" capability */ + status = pjsip_endpt_add_capability(endpt, NULL, PJSIP_H_SUPPORTED, NULL, + 1, &STR_REPLACES); + + is_initialized = PJ_TRUE; + return PJ_SUCCESS; +} + + +/* + * 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) { + 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_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; +} + + + diff --git a/pjsip/src/pjsip/sip_dialog.c b/pjsip/src/pjsip/sip_dialog.c index 8c2f287c..515b8616 100644 --- a/pjsip/src/pjsip/sip_dialog.c +++ b/pjsip/src/pjsip/sip_dialog.c @@ -145,9 +145,16 @@ PJ_DEF(pj_status_t) pjsip_dlg_create_uac( pjsip_user_agent *ua, param = uri->header_param.next; while (param != &uri->header_param) { pjsip_hdr *hdr; + int c; + + c = param->value.ptr[param->value.slen]; + param->value.ptr[param->value.slen] = '\0'; hdr = pjsip_parse_hdr(dlg->pool, ¶m->name, param->value.ptr, param->value.slen, NULL); + + param->value.ptr[param->value.slen] = (char)c; + if (hdr == NULL) { status = PJSIP_EINVALIDURI; goto on_error; @@ -207,6 +214,34 @@ PJ_DEF(pj_status_t) pjsip_dlg_create_uac( pjsip_user_agent *ua, goto on_error; } + /* Remove header param from remote.info_str, if any */ + if (PJSIP_URI_SCHEME_IS_SIP(dlg->remote.info->uri) || + PJSIP_URI_SCHEME_IS_SIPS(dlg->remote.info->uri)) + { + pjsip_sip_uri *sip_uri = pjsip_uri_get_uri(dlg->remote.info->uri); + if (!pj_list_empty(&sip_uri->header_param)) { + pj_str_t tmp; + + /* Remove all header param */ + pj_list_init(&sip_uri->header_param); + + /* Print URI */ + tmp.ptr = pj_pool_alloc(dlg->pool, dlg->remote.info_str.slen); + tmp.slen = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, + sip_uri, tmp.ptr, + dlg->remote.info_str.slen); + + if (tmp.slen < 1) { + status = PJSIP_EURITOOLONG; + goto on_error; + } + + /* Assign remote.info_str */ + dlg->remote.info_str = tmp; + } + } + + /* Initialize remote's CSeq to -1. */ dlg->remote.cseq = dlg->remote.first_cseq = -1; @@ -361,7 +396,7 @@ PJ_DEF(pj_status_t) pjsip_dlg_create_uas( pjsip_user_agent *ua, } else tmp.slen = len; - /* Save the local info. */ + /* Save the remote info. */ pj_strdup(dlg->pool, &dlg->remote.info_str, &tmp); diff --git a/pjsip/src/pjsip/sip_endpoint.c b/pjsip/src/pjsip/sip_endpoint.c index 571ee11c..18345725 100644 --- a/pjsip/src/pjsip/sip_endpoint.c +++ b/pjsip/src/pjsip/sip_endpoint.c @@ -320,6 +320,32 @@ PJ_DEF(const pjsip_hdr*) pjsip_endpt_get_capability( pjsip_endpoint *endpt, /* + * Check if the specified capability is supported. + */ +PJ_DEF(pj_bool_t) pjsip_endpt_has_capability( pjsip_endpoint *endpt, + int htype, + const pj_str_t *hname, + const pj_str_t *token) +{ + const pjsip_generic_array_hdr *hdr; + unsigned i; + + hdr = (const pjsip_generic_array_hdr*) + pjsip_endpt_get_capability(endpt, htype, hname); + if (!hdr) + return PJ_FALSE; + + PJ_ASSERT_RETURN(token != NULL, PJ_FALSE); + + for (i=0; i<hdr->count; ++i) { + if (!pj_stricmp(&hdr->values[i], token)) + return PJ_TRUE; + } + + return PJ_FALSE; +} + +/* * Add or register new capabilities as indicated by the tags to the * appropriate header fields in the endpoint. */ diff --git a/pjsip/src/pjsip/sip_ua_layer.c b/pjsip/src/pjsip/sip_ua_layer.c index 48764081..ad70ab73 100644 --- a/pjsip/src/pjsip/sip_ua_layer.c +++ b/pjsip/src/pjsip/sip_ua_layer.c @@ -409,6 +409,81 @@ PJ_DEF(pjsip_dialog*) pjsip_tsx_get_dlg( pjsip_transaction *tsx ) } +/* + * Find a dialog. + */ +PJ_DEF(pjsip_dialog*) pjsip_ua_find_dialog(const pj_str_t *call_id, + const pj_str_t *local_tag, + const pj_str_t *remote_tag, + pj_bool_t lock_dialog) +{ + struct dlg_set *dlg_set; + pjsip_dialog *dlg; + + PJ_ASSERT_RETURN(call_id && local_tag && remote_tag, NULL); + + /* Lock user agent. */ + pj_mutex_lock(mod_ua.mutex); + + /* Lookup the dialog set. */ + dlg_set = pj_hash_get(mod_ua.dlg_table, local_tag->ptr, local_tag->slen, + NULL); + if (dlg_set == NULL) { + /* Not found */ + pj_mutex_unlock(mod_ua.mutex); + return NULL; + } + + /* Dialog set is found, now find the matching dialog based on the + * remote tag. + */ + dlg = dlg_set->dlg_list.next; + while (dlg != (pjsip_dialog*)&dlg_set->dlg_list) { + if (pj_strcmp(&dlg->remote.info->tag, remote_tag) == 0) + break; + dlg = dlg->next; + } + + if (dlg == (pjsip_dialog*)&dlg_set->dlg_list) { + /* Not found */ + pj_mutex_unlock(mod_ua.mutex); + return NULL; + } + + /* Dialog has been found. It SHOULD have the right Call-ID!! */ + PJ_ASSERT_ON_FAIL(pj_strcmp(&dlg->call_id->id, call_id)==0, + {pj_mutex_unlock(mod_ua.mutex); return NULL;}); + + if (lock_dialog) { + if (pjsip_dlg_try_inc_lock(dlg) != PJ_SUCCESS) { + + /* + * Unable to acquire dialog's lock while holding the user + * agent's mutex. Release the UA mutex before retrying once + * more. + * + * THIS MAY CAUSE RACE CONDITION! + */ + + /* Unlock user agent. */ + pj_mutex_unlock(mod_ua.mutex); + /* Lock dialog */ + pjsip_dlg_inc_lock(dlg); + + } else { + /* Unlock user agent. */ + pj_mutex_unlock(mod_ua.mutex); + } + + } else { + /* Unlock user agent. */ + pj_mutex_unlock(mod_ua.mutex); + } + + return dlg; +} + + /* * Find the first dialog in dialog set in hash table for an incoming message. */ diff --git a/pjsip/src/pjsip/sip_util.c b/pjsip/src/pjsip/sip_util.c index 56928e6e..0b72e568 100644 --- a/pjsip/src/pjsip/sip_util.c +++ b/pjsip/src/pjsip/sip_util.c @@ -1195,6 +1195,31 @@ PJ_DEF(pj_status_t) pjsip_endpt_send_response( pjsip_endpoint *endpt, } /* + * Send response combo + */ +PJ_DEF(pj_status_t) pjsip_endpt_send_response2( pjsip_endpoint *endpt, + pjsip_rx_data *rdata, + pjsip_tx_data *tdata, + void *token, + void (*cb)(pjsip_send_state*, + pj_ssize_t sent, + pj_bool_t *cont)) +{ + pjsip_response_addr res_addr; + pj_status_t status; + + status = pjsip_get_response_addr(tdata->pool, rdata, &res_addr); + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + return PJ_SUCCESS; + } + + status = pjsip_endpt_send_response(endpt, &res_addr, tdata, token, cb); + return status; +} + + +/* * Send response */ PJ_DEF(pj_status_t) pjsip_endpt_respond_stateless( pjsip_endpoint *endpt, diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c index b93abefe..1df12e5c 100644 --- a/pjsip/src/pjsua-lib/pjsua_call.c +++ b/pjsip/src/pjsua-lib/pjsua_call.c @@ -372,6 +372,7 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) { pj_str_t contact; pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata); + pjsip_dialog *replaced_dlg = NULL; pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata); pjsip_msg *msg = rdata->msg_info.msg; pjsip_tx_data *response = NULL; @@ -419,6 +420,66 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) /* Mark call start time. */ pj_gettimeofday(&call->start_time); + /* Check INVITE request for Replaces header. If Replaces header is + * present, the function will make sure that we can handle the request. + */ + status = pjsip_replaces_verify_request(rdata, &replaced_dlg, PJ_FALSE, + &response); + if (status != PJ_SUCCESS) { + /* + * Something wrong with the Replaces header. + */ + if (response) { + pjsip_response_addr res_addr; + + pjsip_get_response_addr(response->pool, rdata, &res_addr); + pjsip_endpt_send_response(pjsua_var.endpt, &res_addr, response, + NULL, NULL); + + } else { + + /* Respond with 500 (Internal Server Error) */ + pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL, + NULL, NULL); + } + + PJSUA_UNLOCK(); + return PJ_TRUE; + } + + /* If this INVITE request contains Replaces header, notify application + * about the request so that application can do subsequent checking + * if it wants to. + */ + if (replaced_dlg != NULL && pjsua_var.ua_cfg.cb.on_call_replace_request) { + pjsua_call *replaced_call; + int st_code = 200; + pj_str_t st_text = { "OK", 2 }; + + /* Get the replaced call instance */ + replaced_call = replaced_dlg->mod_data[pjsua_var.mod.id]; + + /* Notify application */ + pjsua_var.ua_cfg.cb.on_call_replace_request(replaced_call->index, + rdata, &st_code, &st_text); + + /* Must specify final response */ + PJ_ASSERT_ON_FAIL(st_code >= 200, st_code = 200); + + /* Check if application rejects this request. */ + if (st_code >= 300) { + + if (st_text.slen == 2) + st_text = *pjsip_get_status_text(st_code); + + pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata, + st_code, &st_text, NULL, NULL, NULL); + PJSUA_UNLOCK(); + return PJ_TRUE; + } + } + + /* Get media capability from media endpoint: */ status = pjmedia_endpt_create_sdp( pjsua_var.med_endpt, rdata->tp_info.pool, 1, @@ -545,9 +606,56 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) ++pjsua_var.call_cnt; - /* Notify application */ - if (pjsua_var.ua_cfg.cb.on_incoming_call) - pjsua_var.ua_cfg.cb.on_incoming_call(acc_id, call_id, rdata); + /* Check if this request should replace existing call */ + if (replaced_dlg) { + pjsip_inv_session *replaced_inv; + struct pjsua_call *replaced_call; + pjsip_tx_data *tdata; + + /* Get the invite session in the dialog */ + replaced_inv = pjsip_dlg_get_inv_session(replaced_dlg); + + /* Get the replaced call instance */ + replaced_call = replaced_dlg->mod_data[pjsua_var.mod.id]; + + /* Notify application */ + if (pjsua_var.ua_cfg.cb.on_call_replaced) + pjsua_var.ua_cfg.cb.on_call_replaced(replaced_call->index, + call_id); + + PJ_LOG(4,(THIS_FILE, "Answering replacement call %d with 200/OK", + call_id)); + + /* Answer the new call with 200 response */ + status = pjsip_inv_answer(inv, 200, NULL, NULL, &tdata); + if (status == PJ_SUCCESS) + status = pjsip_inv_send_msg(inv, tdata); + + if (status != PJ_SUCCESS) + pjsua_perror(THIS_FILE, "Error answering session", status); + + + PJ_LOG(4,(THIS_FILE, "Disconnecting replaced call %d", + replaced_call->index)); + + /* Disconnect replaced invite session */ + status = pjsip_inv_end_session(replaced_inv, PJSIP_SC_GONE, NULL, + &tdata); + if (status == PJ_SUCCESS && tdata) + status = pjsip_inv_send_msg(replaced_inv, tdata); + + if (status != PJ_SUCCESS) + pjsua_perror(THIS_FILE, "Error terminating session", status); + + + } else { + + /* Notify application */ + if (pjsua_var.ua_cfg.cb.on_incoming_call) + pjsua_var.ua_cfg.cb.on_incoming_call(acc_id, call_id, rdata); + + } + /* This INVITE request has been handled. */ PJSUA_UNLOCK(); @@ -1065,6 +1173,8 @@ PJ_DEF(pj_status_t) pjsua_call_xfer( pjsua_call_id call_id, pjsip_tx_data *tdata; pjsua_call *call; pjsip_dialog *dlg; + pjsip_generic_string_hdr *gs_hdr; + const pj_str_t str_ref_by = { "Referred-By", 11 }; struct pjsip_evsub_user xfer_cb; pj_status_t status; @@ -1101,6 +1211,12 @@ PJ_DEF(pj_status_t) pjsua_call_xfer( pjsua_call_id call_id, return status; } + /* Add Referred-By header */ + gs_hdr = pjsip_generic_string_hdr_create(tdata->pool, &str_ref_by, + &dlg->local.info_str); + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)gs_hdr); + + /* Add additional headers etc */ pjsua_process_msg_data( tdata, msg_data); @@ -1125,6 +1241,86 @@ PJ_DEF(pj_status_t) pjsua_call_xfer( pjsua_call_id call_id, /* + * Initiate attended call transfer to the specified address. + */ +PJ_DEF(pj_status_t) pjsua_call_xfer_replaces( pjsua_call_id call_id, + pjsua_call_id dest_call_id, + unsigned options, + const pjsua_msg_data *msg_data) +{ + pjsua_call *dest_call; + pjsip_dialog *dest_dlg; + char str_dest_buf[512]; + pj_str_t str_dest; + int len; + pjsip_uri *uri; + pj_status_t status; + + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + PJ_ASSERT_RETURN(dest_call_id>=0 && + dest_call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + status = acquire_call("pjsua_call_xfer_replaces()", dest_call_id, + &dest_call, &dest_dlg); + if (status != PJ_SUCCESS) + return status; + + /* + * Create REFER destination URI with Replaces field. + */ + + /* Make sure we have sufficient buffer's length */ + PJ_ASSERT_RETURN( dest_dlg->remote.info_str.slen + + dest_dlg->call_id->id.slen + + dest_dlg->remote.info->tag.slen + + dest_dlg->local.info->tag.slen + 32 + < sizeof(str_dest_buf), PJSIP_EURITOOLONG); + + /* Print URI */ + str_dest_buf[0] = '<'; + str_dest.slen = 1; + + uri = pjsip_uri_get_uri(dest_dlg->remote.info->uri); + len = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, uri, + str_dest_buf+1, sizeof(str_dest_buf)-1); + if (len < 0) + return PJSIP_EURITOOLONG; + + str_dest.slen += len; + + + /* Build the URI */ + len = pj_ansi_snprintf(str_dest_buf + str_dest.slen, + sizeof(str_dest_buf) - str_dest.slen, + "?%s" + "Replaces=%.*s" + "%%3Bto-tag%%3D%.*s" + "%%3Bfrom-tag%%3D%.*s>", + ((options&PJSUA_XFER_NO_REQUIRE_REPLACES) ? + "" : "Require=replaces&"), + (int)dest_dlg->call_id->id.slen, + dest_dlg->call_id->id.ptr, + (int)dest_dlg->remote.info->tag.slen, + dest_dlg->remote.info->tag.ptr, + (int)dest_dlg->local.info->tag.slen, + dest_dlg->local.info->tag.ptr); + + PJ_ASSERT_RETURN(len > 0 && len <= (int)sizeof(str_dest_buf)-str_dest.slen, + PJSIP_EURITOOLONG); + + str_dest.ptr = str_dest_buf; + str_dest.slen += len; + + pjsip_dlg_dec_lock(dest_dlg); + + return pjsua_call_xfer(call_id, &str_dest, msg_data); +} + + +/* * Send DTMF digits to remote using RFC 2833 payload formats. */ PJ_DEF(pj_status_t) pjsua_call_dial_dtmf( pjsua_call_id call_id, @@ -2340,10 +2536,13 @@ static void on_call_transfered( pjsip_inv_session *inv, int new_call; const pj_str_t str_refer_to = { "Refer-To", 8}; const pj_str_t str_refer_sub = { "Refer-Sub", 9 }; + const pj_str_t str_ref_by = { "Referred-By", 11 }; pjsip_generic_string_hdr *refer_to; pjsip_generic_string_hdr *refer_sub; + pjsip_hdr *ref_by_hdr; pj_bool_t no_refer_sub = PJ_FALSE; char *uri; + pjsua_msg_data msg_data; pj_str_t tmp; pjsip_status_code code; pjsip_evsub *sub; @@ -2372,6 +2571,11 @@ static void on_call_transfered( pjsip_inv_session *inv, no_refer_sub = PJ_TRUE; } + /* Find optional Referred-By header (to be copied onto outgoing INVITE + * request. + */ + ref_by_hdr = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_ref_by, + NULL); /* Notify callback */ code = PJSIP_SC_OK; @@ -2490,10 +2694,21 @@ static void on_call_transfered( pjsip_inv_session *inv, uri = refer_to->hvalue.ptr; uri[refer_to->hvalue.slen] = '\0'; + /* Init msg_data */ + pjsua_msg_data_init(&msg_data); + + /* If Referred-By header is present in the REFER request, copy this + * to the outgoing INVITE request. + */ + if (ref_by_hdr != NULL) { + pjsip_hdr *dup = pjsip_hdr_clone(rdata->tp_info.pool, ref_by_hdr); + pj_list_push_back(&msg_data.hdr_list, dup); + } + /* Now make the outgoing call. */ tmp = pj_str(uri); status = pjsua_call_make_call(existing_call->acc_id, &tmp, 0, - existing_call->user_data, NULL, + existing_call->user_data, &msg_data, &new_call); if (status != PJ_SUCCESS) { diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c index 16c71ef9..33db0666 100644 --- a/pjsip/src/pjsua-lib/pjsua_core.c +++ b/pjsip/src/pjsua-lib/pjsua_core.c @@ -507,6 +507,11 @@ PJ_DEF(pj_status_t) pjsua_init( const pjsua_config *ua_cfg, PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + /* Initialize Replaces support. */ + status = pjsip_replaces_init_module( pjsua_var.endpt ); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + /* Initialize and register PJSUA application module. */ { const pjsip_module mod_initializer = |