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: https://svn.pjsip.org/repos/pjproject/trunk@797 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjsip/src/pjsip-ua/sip_inv.c b/pjsip/src/pjsip-ua/sip_inv.c
index 9750745..8c6b4cc 100644
--- a/pjsip/src/pjsip-ua/sip_inv.c
+++ b/pjsip/src/pjsip-ua/sip_inv.c
@@ -386,8 +386,10 @@
 
     /* 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 @@
     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 @@
 	    {
 		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 0000000..acf8181
--- /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 8c2f287..515b861 100644
--- a/pjsip/src/pjsip/sip_dialog.c
+++ b/pjsip/src/pjsip/sip_dialog.c
@@ -145,9 +145,16 @@
 	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 @@
 	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 @@
     } 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 571ee11..1834572 100644
--- a/pjsip/src/pjsip/sip_endpoint.c
+++ b/pjsip/src/pjsip/sip_endpoint.c
@@ -320,6 +320,32 @@
 
 
 /*
+ * 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 4876408..ad70ab7 100644
--- a/pjsip/src/pjsip/sip_ua_layer.c
+++ b/pjsip/src/pjsip/sip_ua_layer.c
@@ -409,6 +409,81 @@
 }
 
 
+/* 
+ * 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 56928e6..0b72e56 100644
--- a/pjsip/src/pjsip/sip_util.c
+++ b/pjsip/src/pjsip/sip_util.c
@@ -1195,6 +1195,31 @@
 }
 
 /*
+ * 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 b93abef..1df12e5 100644
--- a/pjsip/src/pjsua-lib/pjsua_call.c
+++ b/pjsip/src/pjsua-lib/pjsua_call.c
@@ -372,6 +372,7 @@
 {
     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 @@
     /* 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 @@
     ++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 @@
     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 @@
 	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 @@
 
 
 /*
+ * 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 @@
     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 @@
 	    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 @@
     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 16c71ef..33db066 100644
--- a/pjsip/src/pjsua-lib/pjsua_core.c
+++ b/pjsip/src/pjsua-lib/pjsua_core.c
@@ -507,6 +507,11 @@
     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 =