summaryrefslogtreecommitdiff
path: root/pjsip/src
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2006-11-11 16:16:04 +0000
committerBenny Prijono <bennylp@teluu.com>2006-11-11 16:16:04 +0000
commite6a5b770072500cf89a9cc10e4b99a972a6787b8 (patch)
treeec336215fc83344cec58d01dd30a74d7f6054e67 /pjsip/src
parente4fa39f3acb0b969dd1f952e81b33b2dff671002 (diff)
Attended call transfer implementation. The changes involves:
- Added support for SIP Replaces extension (RFC 3891) - Added pjsua_call_xfer_replaces() to perform attended call transfer. - PJSUA checks and process Replaces header in incoming calls - Added pjsip_ua_find_dialog() API. - Added pjsip_endpt_has_capability() API. - Added pjsip_endpt_send_response2() API. - etc. git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@797 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjsip/src')
-rw-r--r--pjsip/src/pjsip-ua/sip_inv.c17
-rw-r--r--pjsip/src/pjsip-ua/sip_replaces.c355
-rw-r--r--pjsip/src/pjsip/sip_dialog.c37
-rw-r--r--pjsip/src/pjsip/sip_endpoint.c26
-rw-r--r--pjsip/src/pjsip/sip_ua_layer.c75
-rw-r--r--pjsip/src/pjsip/sip_util.c25
-rw-r--r--pjsip/src/pjsua-lib/pjsua_call.c223
-rw-r--r--pjsip/src/pjsua-lib/pjsua_core.c5
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, &param->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 =