Implemented major feature: call hold and transfer

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@212 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjsip/src/pjsip-ua/sip_inv.c b/pjsip/src/pjsip-ua/sip_inv.c
index 63250e3..a41f7f6 100644
--- a/pjsip/src/pjsip-ua/sip_inv.c
+++ b/pjsip/src/pjsip-ua/sip_inv.c
@@ -200,10 +200,7 @@
     /* On receipt ACK request, when state is CONNECTING,
      * move state to CONFIRMED.
      */
-    if (method->id == PJSIP_ACK_METHOD && inv &&
-	inv->state != PJSIP_INV_STATE_CONFIRMED)
-    {
-	pjsip_event event;
+    if (method->id == PJSIP_ACK_METHOD && inv) {
 
 	/* Terminate INVITE transaction, if it's still present. */
 	if (inv->invite_tsx && 
@@ -214,8 +211,12 @@
 	    inv->invite_tsx = NULL;
 	}
 
-	PJSIP_EVENT_INIT_RX_MSG(event, rdata);
-	inv_set_state(inv, PJSIP_INV_STATE_CONFIRMED, &event);
+	if (inv->state != PJSIP_INV_STATE_CONFIRMED) {
+	    pjsip_event event;
+
+	    PJSIP_EVENT_INIT_RX_MSG(event, rdata);
+	    inv_set_state(inv, PJSIP_INV_STATE_CONFIRMED, &event);
+	}
     }
 
     return PJ_FALSE;
@@ -250,7 +251,9 @@
      * If it is, we need to send ACK.
      */
     if (msg->type == PJSIP_RESPONSE_MSG && msg->line.status.code/100==2 &&
-	rdata->msg_info.cseq->method.id == PJSIP_INVITE_METHOD) {
+	rdata->msg_info.cseq->method.id == PJSIP_INVITE_METHOD &&
+	inv->invite_tsx == NULL) 
+    {
 
 	inv_send_ack(inv, rdata);
 	return PJ_TRUE;
@@ -865,13 +868,16 @@
 {
     pjsip_tx_data *tdata;
     const pjsip_hdr *hdr;
+    pj_bool_t has_sdp;
     pj_status_t status;
 
     /* Verify arguments. */
     PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL);
 
-    /* State MUST be NULL. */
-    PJ_ASSERT_RETURN(inv->state == PJSIP_INV_STATE_NULL, PJ_EINVAL);
+    /* State MUST be NULL or CONFIRMED. */
+    PJ_ASSERT_RETURN(inv->state == PJSIP_INV_STATE_NULL ||
+		     inv->state == PJSIP_INV_STATE_CONFIRMED, 
+		     PJ_EINVALIDOP);
 
     /* Create the INVITE request. */
     status = pjsip_dlg_create_request(inv->dlg, &pjsip_invite_method, -1,
@@ -879,10 +885,37 @@
     if (status != PJ_SUCCESS)
 	return status;
 
+    /* If this is the first INVITE, then copy the headers from inv_hdr.
+     * These are the headers parsed from the request URI when the
+     * dialog was created.
+     */
+    if (inv->state == PJSIP_INV_STATE_NULL) {
+	hdr = inv->dlg->inv_hdr.next;
+
+	while (hdr != &inv->dlg->inv_hdr) {
+	    pjsip_msg_add_hdr(tdata->msg,
+			      pjsip_hdr_shallow_clone(tdata->pool, hdr));
+	    hdr = hdr->next;
+	}
+    }
+
+    /* See if we have SDP to send. */
+    if (inv->neg) {
+	pjmedia_sdp_neg_state neg_state;
+
+	neg_state = pjmedia_sdp_neg_get_state(inv->neg);
+
+	has_sdp = (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER ||
+		   (neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO &&
+		    pjmedia_sdp_neg_has_local_answer(inv->neg)));
+
+
+    } else {
+	has_sdp = PJ_FALSE;
+    }
+
     /* Add SDP, if any. */
-    if (inv->neg && 
-	pjmedia_sdp_neg_get_state(inv->neg)==PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER)
-    {
+    if (has_sdp) {
 	const pjmedia_sdp_session *offer;
 
 	status = pjmedia_sdp_neg_get_neg_local(inv->neg, &offer);
@@ -940,9 +973,9 @@
 /*
  * Check in incoming message for SDP offer/answer.
  */
-static void inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv,
-					   pjsip_transaction *tsx,
-					   pjsip_rx_data *rdata)
+static pj_status_t inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv,
+						  pjsip_transaction *tsx,
+						  pjsip_rx_data *rdata)
 {
     struct tsx_inv_data *tsx_inv_data;
     static const pj_str_t str_application = { "application", 11 };
@@ -963,21 +996,21 @@
      */
 
     if (tsx_inv_data->sdp_done)
-	return;
+	return PJ_SUCCESS;
 
     /* Check if SDP is present in the message. */
 
     msg = rdata->msg_info.msg;
     if (msg->body == NULL) {
 	/* Message doesn't have body. */
-	return;
+	return PJ_SUCCESS;
     }
 
     if (pj_stricmp(&msg->body->content_type.type, &str_application) ||
 	pj_stricmp(&msg->body->content_type.subtype, &str_sdp))
     {
 	/* Message body is not "application/sdp" */
-	return;
+	return PJMEDIA_SDP_EINSDP;
     }
 
     /* Parse the SDP body. */
@@ -989,7 +1022,7 @@
 	pj_strerror(status, errmsg, sizeof(errmsg));
 	PJ_LOG(4,(THIS_FILE, "Error parsing SDP in %s: %s",
 		  pjsip_rx_data_get_info(rdata), errmsg));
-	return;
+	return PJMEDIA_SDP_EINSDP;
     }
 
     /* The SDP can be an offer or answer, depending on negotiator's state */
@@ -1015,13 +1048,16 @@
 	    pj_strerror(status, errmsg, sizeof(errmsg));
 	    PJ_LOG(4,(THIS_FILE, "Error processing SDP offer in %s: %s",
 		      pjsip_rx_data_get_info(rdata), errmsg));
-	    return;
+	    return PJMEDIA_SDP_EINSDP;
 	}
 
 	/* Inform application about remote offer. */
 
-	if (mod_inv.cb.on_rx_offer)
-	    (*mod_inv.cb.on_rx_offer)(inv);
+	if (mod_inv.cb.on_rx_offer) {
+
+	    (*mod_inv.cb.on_rx_offer)(inv, sdp);
+
+	}
 
     } else if (pjmedia_sdp_neg_get_state(inv->neg) == 
 		PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) 
@@ -1041,7 +1077,7 @@
 	    pj_strerror(status, errmsg, sizeof(errmsg));
 	    PJ_LOG(4,(THIS_FILE, "Error processing SDP answer in %s: %s",
 		      pjsip_rx_data_get_info(rdata), errmsg));
-	    return;
+	    return PJMEDIA_SDP_EINSDP;
 	}
 
 	/* Negotiate SDP */
@@ -1059,12 +1095,68 @@
 	      pjmedia_sdp_neg_state_str(pjmedia_sdp_neg_get_state(inv->neg))));
     }
 
+    return PJ_SUCCESS;
 }
 
 
+/*
+ * Process INVITE answer, for both initial and subsequent re-INVITE
+ */
+static pj_status_t process_answer( pjsip_inv_session *inv,
+				   int st_code,
+				   pjsip_tx_data *tdata )
+{
+    pj_status_t status;
+    pjmedia_sdp_session *sdp = NULL;
+
+    /* Include SDP for 18x and 2xx response. 
+     * Also if SDP negotiator is ready, start negotiation.
+     */
+    if (st_code/10 == 18 || st_code/10 == 20) {
+
+	pjmedia_sdp_neg_state neg_state;
+
+	neg_state = inv->neg ? pjmedia_sdp_neg_get_state(inv->neg) :
+		    PJMEDIA_SDP_NEG_STATE_NULL;
+
+	if (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) {
+
+	    status = pjmedia_sdp_neg_get_neg_local(inv->neg, &sdp);
+
+	} else if (neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO &&
+		   pjmedia_sdp_neg_has_local_answer(inv->neg) )
+	{
+
+	    status = inv_negotiate_sdp(inv);
+	    if (status != PJ_SUCCESS)
+		return status;
+	    
+	    status = pjmedia_sdp_neg_get_active_local(inv->neg, &sdp);
+	}
+
+    }
+
+
+
+    /* Include SDP when it's available.
+     * Subsequent response will include this SDP.
+     */
+    if (sdp) {
+	tdata->msg->body = create_sdp_body(tdata->pool, sdp);
+    }
+
+    /* Remove message body if this is a non-2xx final response */
+    if (st_code >= 300)
+	tdata->msg->body = NULL;
+
+
+    return PJ_SUCCESS;
+}
+
 
 /*
- * Answer initial INVITE.
+ * Answer initial INVITE
+ * Re-INVITE will be answered automatically, and will not use this function.
  */ 
 PJ_DEF(pj_status_t) pjsip_inv_answer(	pjsip_inv_session *inv,
 					int st_code,
@@ -1087,7 +1179,7 @@
     /* If local_sdp is specified, then we MUST NOT have answered the
      * offer before. 
      */
-    if (local_sdp) {
+    if (local_sdp && (st_code/100==1 || st_code/100==2)) {
 
 	if (inv->neg == NULL) {
 	    status = pjmedia_sdp_neg_create_w_local_offer(inv->pool, local_sdp,
@@ -1108,42 +1200,20 @@
 	    return status;
     }
 
-    last_res = inv->invite_tsx->last_tx;
+
+
 
     /* Modify last response. */
+    last_res = inv->invite_tsx->last_tx;
     status = pjsip_dlg_modify_response(inv->dlg, last_res, st_code, st_text);
     if (status != PJ_SUCCESS)
 	return status;
 
-    /* Include SDP for 18x and 2xx response. 
-     * Also if SDP negotiator is ready, start negotiation.
-     */
-    if (st_code/10 == 18 || st_code/10 == 20) {
 
-	pjmedia_sdp_neg_state neg_state;
-
-	neg_state = inv->neg ? pjmedia_sdp_neg_get_state(inv->neg) :
-		    PJMEDIA_SDP_NEG_STATE_NULL;
-
-	if (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER ||
-	    neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO)
-	{
-	    const pjmedia_sdp_session *local;
-
-	    status = pjmedia_sdp_neg_get_neg_local(inv->neg, &local);
-	    if (status == PJ_SUCCESS)
-		last_res->msg->body = create_sdp_body(last_res->pool, local);
-	}
-
-	/* Start negotiation, if ready. */
-	if (neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO) {
-	    status = inv_negotiate_sdp(inv);
-	    if (status != PJ_SUCCESS) {
-		pjsip_tx_data_dec_ref(last_res);
-		return status;
-	    }
-	}
-    }
+    /* Process SDP in answer */
+    status = process_answer(inv, st_code, last_res);
+    if (status != PJ_SUCCESS)
+	return status;
 
 
     *p_tdata = last_res;
@@ -1153,6 +1223,24 @@
 
 
 /*
+ * Set SDP answer.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_set_sdp_answer( pjsip_inv_session *inv,
+					      const pjmedia_sdp_session *sdp )
+{
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(inv && sdp, PJ_EINVAL);
+
+    pjsip_dlg_inc_lock(inv->dlg);
+    status = pjmedia_sdp_neg_set_local_answer( inv->pool, inv->neg, sdp);
+    pjsip_dlg_dec_lock(inv->dlg);
+
+    return status;
+}
+
+
+/*
  * End session.
  */
 PJ_DEF(pj_status_t) pjsip_inv_end_session(  pjsip_inv_session *inv,
@@ -1205,8 +1293,9 @@
 	    tdata = inv->invite_tsx->last_tx;
 	    PJ_ASSERT_RETURN(tdata != NULL, PJ_EINVALIDOP);
 
-	    status = pjsip_dlg_modify_response(inv->dlg, tdata, st_code,
-					       st_text);
+	    //status = pjsip_dlg_modify_response(inv->dlg, tdata, st_code,
+	    //				       st_text);
+	    status = pjsip_inv_answer(inv, st_code, st_text, NULL, &tdata);
 	}
 	break;
 
@@ -1247,13 +1336,82 @@
 					const pjmedia_sdp_session *new_offer,
 					pjsip_tx_data **p_tdata )
 {
-    PJ_UNUSED_ARG(inv);
-    PJ_UNUSED_ARG(new_contact);
-    PJ_UNUSED_ARG(new_offer);
-    PJ_UNUSED_ARG(p_tdata);
+    pj_status_t status;
+    pjsip_contact_hdr *contact_hdr = NULL;
 
-    PJ_TODO(CREATE_REINVITE_REQUEST);
-    return PJ_ENOTSUP;
+    /* Check arguments. */
+    PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL);
+
+    /* Must NOT have a pending INVITE transaction */
+    PJ_ASSERT_RETURN(inv->invite_tsx==NULL, PJ_EINVALIDOP);
+
+
+    pjsip_dlg_inc_lock(inv->dlg);
+
+    if (new_contact) {
+	pj_str_t tmp;
+	const pj_str_t STR_CONTACT = { "Contact", 7 };
+
+	pj_strdup_with_null(inv->dlg->pool, &tmp, new_contact);
+	contact_hdr = pjsip_parse_hdr(inv->dlg->pool, &STR_CONTACT, 
+				      tmp.ptr, tmp.slen, NULL);
+	if (!contact_hdr) {
+	    status = PJSIP_EINVALIDURI;
+	    goto on_return;
+	}
+    }
+
+
+    if (new_offer) {
+	if (!inv->neg) {
+	    status = pjmedia_sdp_neg_create_w_local_offer(inv->pool, new_offer,
+							  &inv->neg);
+	    if (status != PJ_SUCCESS)
+		goto on_return;
+
+	} else switch (pjmedia_sdp_neg_get_state(inv->neg)) {
+
+	    case PJMEDIA_SDP_NEG_STATE_NULL:
+		pj_assert(!"Unexpected SDP neg state NULL");
+		status = PJ_EBUG;
+		goto on_return;
+
+	    case PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER:
+		PJ_LOG(4,(inv->obj_name, 
+			  "pjsip_inv_reinvite: already have an offer, new "
+			  "offer is ignored"));
+		break;
+
+	    case PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER:
+		status = pjmedia_sdp_neg_set_local_answer(inv->pool, inv->neg,
+							  new_offer);
+		if (status != PJ_SUCCESS)
+		    goto on_return;
+		break;
+
+	    case PJMEDIA_SDP_NEG_STATE_WAIT_NEGO:
+		PJ_LOG(4,(inv->obj_name, 
+			  "pjsip_inv_reinvite: SDP in WAIT_NEGO state, new "
+			  "offer is ignored"));
+		break;
+
+	    case PJMEDIA_SDP_NEG_STATE_DONE:
+		status = pjmedia_sdp_neg_modify_local_offer(inv->pool,inv->neg,
+							    new_offer);
+		if (status != PJ_SUCCESS)
+		    goto on_return;
+		break;
+	}
+    }
+
+    if (contact_hdr)
+	inv->dlg->local.contact = contact_hdr;
+
+    status = pjsip_inv_invite(inv, p_tdata);
+
+on_return:
+    pjsip_dlg_dec_lock(inv->dlg);
+    return status;
 }
 
 /*
@@ -1908,6 +2066,124 @@
 	inv_respond_incoming_bye( inv, tsx, e->body.tsx_state.src.rdata, e );
 
     }
+    else if (tsx->method.id == PJSIP_INVITE_METHOD &&
+	     tsx->role == PJSIP_ROLE_UAS)
+    {
+
+	/*
+	 * Handle incoming re-INVITE
+	 */
+	if (tsx->state == PJSIP_TSX_STATE_TRYING) {
+	    
+	    pjsip_rx_data *rdata = e->body.tsx_state.src.rdata;
+	    pjsip_tx_data *tdata;
+	    pj_status_t status;
+
+	    /* Check if we have INVITE pending. */
+	    if (inv->invite_tsx && inv->invite_tsx!=tsx) {
+
+		/* Can not receive re-INVITE while another one is pending. */
+		status = pjsip_dlg_create_response( inv->dlg, rdata, 500, NULL,
+						    &tdata);
+		if (status != PJ_SUCCESS)
+		    return;
+
+		status = pjsip_dlg_send_response( inv->dlg, tsx, tdata);
+		
+
+		return;
+	    }
+
+	    /* Save the invite transaction. */
+	    inv->invite_tsx = tsx;
+
+	    /* Process SDP in incoming message. */
+	    status = inv_check_sdp_in_incoming_msg(inv, tsx, rdata);
+
+	    if (status != PJ_SUCCESS) {
+
+		/* Not Acceptable */
+		const pjsip_hdr *accept;
+
+		status = pjsip_dlg_create_response(inv->dlg, rdata, 
+						   488, NULL, &tdata);
+		if (status != PJ_SUCCESS)
+		    return;
+
+
+		accept = pjsip_endpt_get_capability(dlg->endpt, PJSIP_H_ACCEPT,
+						    NULL);
+		if (accept) {
+		    pjsip_msg_add_hdr(tdata->msg,
+				      pjsip_hdr_clone(tdata->pool, accept));
+		}
+
+		status = pjsip_dlg_send_response(dlg, tsx, tdata);
+
+		return;
+	    }
+
+	    /* Create 2xx ANSWER */
+	    status = pjsip_dlg_create_response(dlg, rdata, 200, NULL, &tdata);
+	    if (status != PJ_SUCCESS)
+		return;
+
+	    /* Process SDP in the answer */
+	    status = process_answer(inv, 200, tdata);
+	    if (status != PJ_SUCCESS)
+		return;
+
+	    status = pjsip_inv_send_msg(inv, tdata, NULL);
+
+	}
+
+    }
+    else if (tsx->method.id == PJSIP_INVITE_METHOD &&
+	     tsx->role == PJSIP_ROLE_UAC)
+    {
+	/*
+	 * Handle outgoing re-INVITE
+	 */
+	if (tsx->state == PJSIP_TSX_STATE_TERMINATED &&
+	    tsx->status_code/100 == 2) 
+	{
+
+	    /* Re-INVITE was accepted. */
+
+	    /* Process SDP */
+	    inv_check_sdp_in_incoming_msg(inv, tsx, 
+					  e->body.tsx_state.src.rdata);
+
+	    /* Send ACK */
+	    inv_send_ack(inv, e->body.tsx_state.src.rdata);
+
+	} else if (tsx->state == PJSIP_TSX_STATE_COMPLETED &&
+		   (tsx->status_code==401 || tsx->status_code==407))
+	{
+	    pjsip_tx_data *tdata;
+	    pj_status_t status;
+
+	    /* Handle authentication challenge. */
+	    status = pjsip_auth_clt_reinit_req( &dlg->auth_sess,
+						e->body.tsx_state.src.rdata,
+						tsx->last_tx,
+						&tdata);
+	    if (status != PJ_SUCCESS)
+		return;
+
+	    /* Send re-INVITE */
+	    status = pjsip_inv_send_msg( inv, tdata, NULL);
+
+	} else if (tsx->status_code==PJSIP_SC_CALL_TSX_DOES_NOT_EXIST ||
+		   tsx->status_code==PJSIP_SC_REQUEST_TIMEOUT ||
+		   tsx->status_code >= 700)
+	{
+	    /*
+	     * Handle responses that terminates dialog.
+	     */
+	    inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
+	}
+    }
 }
 
 /*
diff --git a/pjsip/src/pjsip-ua/sip_xfer.c b/pjsip/src/pjsip-ua/sip_xfer.c
new file mode 100644
index 0000000..aadcd3f
--- /dev/null
+++ b/pjsip/src/pjsip-ua/sip_xfer.c
@@ -0,0 +1,614 @@
+/* $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_xfer.h>
+#include <pjsip-simple/evsub_msg.h>
+#include <pjsip/sip_dialog.h>
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_transport.h>
+#include <pj/assert.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+/*
+ * Refer module (mod-refer)
+ */
+static struct pjsip_module mod_xfer = 
+{
+    NULL, NULL,			    /* prev, next.			*/
+    { "mod-refer", 9 },		    /* Name.				*/
+    -1,				    /* Id				*/
+    PJSIP_MOD_PRIORITY_APPLICATION-1,	/* Priority			*/
+    NULL,			    /* User data.			*/
+    NULL,			    /* load()				*/
+    NULL,			    /* start()				*/
+    NULL,			    /* stop()				*/
+    NULL,			    /* unload()				*/
+    NULL,			    /* on_rx_request()			*/
+    NULL,			    /* on_rx_response()			*/
+    NULL,			    /* on_tx_request.			*/
+    NULL,			    /* on_tx_response()			*/
+    NULL,			    /* on_tsx_state()			*/
+};
+
+
+/* Declare PJSIP_REFER_METHOD, so that if somebody declares this in
+ * sip_msg.h we can catch the error here.
+ */
+enum
+{
+    PJSIP_REFER_METHOD = PJSIP_OTHER_METHOD
+};
+
+const pjsip_method pjsip_refer_method = {
+    PJSIP_REFER_METHOD,
+    { "REFER", 5}
+};
+
+
+/*
+ * String constants
+ */
+const pj_str_t STR_REFER = { "refer", 5 };
+const pj_str_t STR_MESSAGE = { "message", 7 };
+const pj_str_t STR_SIPFRAG = { "sipfrag", 7 };
+const pj_str_t STR_SIPFRAG_VERSION = {";version=2.0", 12 };
+
+
+/*
+ * Transfer struct.
+ */
+struct pjsip_xfer
+{
+    pjsip_evsub		*sub;		/**< Event subscribtion record.	    */
+    pjsip_dialog	*dlg;		/**< The dialog.		    */
+    pjsip_evsub_user	 user_cb;	/**< The user callback.		    */
+    pj_str_t		 refer_to_uri;	/**< The full Refer-To URI.	    */
+    int			 last_st_code;	/**< st_code sent in last NOTIFY    */
+    pj_str_t		 last_st_text;	/**< st_text sent in last NOTIFY    */
+};
+
+
+typedef struct pjsip_xfer pjsip_xfer;
+
+
+
+/*
+ * Forward decl for evsub callback.
+ */
+static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event);
+static void xfer_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx,
+				     pjsip_event *event);
+static void xfer_on_evsub_rx_refresh( 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);
+static void xfer_on_evsub_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);
+static void xfer_on_evsub_client_refresh(pjsip_evsub *sub);
+static void xfer_on_evsub_server_timeout(pjsip_evsub *sub);
+
+
+/*
+ * Event subscription callback for xference.
+ */
+static pjsip_evsub_user xfer_user = 
+{
+    &xfer_on_evsub_state,
+    &xfer_on_evsub_tsx_state,
+    &xfer_on_evsub_rx_refresh,
+    &xfer_on_evsub_rx_notify,
+    &xfer_on_evsub_client_refresh,
+    &xfer_on_evsub_server_timeout,
+};
+
+
+
+
+/*
+ * Initialize the REFER subsystem.
+ */
+PJ_DEF(pj_status_t) pjsip_xfer_init_module(pjsip_endpoint *endpt)
+{
+    const pj_str_t accept = { "message/sipfrag;version=2.0", 27 };
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(endpt != NULL, PJ_EINVAL);
+    PJ_ASSERT_RETURN(mod_xfer.id == -1, PJ_EINVALIDOP);
+
+    status = pjsip_endpt_register_module(endpt, &mod_xfer);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    status = pjsip_endpt_add_capability( endpt, &mod_xfer, PJSIP_H_ALLOW, 
+					 NULL, 1, &pjsip_refer_method.name);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    status = pjsip_evsub_register_pkg( &mod_xfer, &STR_REFER, 300, 1, &accept);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Create transferer (sender of REFER request).
+ *
+ */
+PJ_DEF(pj_status_t) pjsip_xfer_create_uac( pjsip_dialog *dlg,
+					   const pjsip_evsub_user *user_cb,
+					   pjsip_evsub **p_evsub )
+{
+    pj_status_t status;
+    pjsip_xfer *xfer;
+    pjsip_evsub *sub;
+
+    PJ_ASSERT_RETURN(dlg && p_evsub, PJ_EINVAL);
+
+    pjsip_dlg_inc_lock(dlg);
+
+    /* Create event subscription */
+    status = pjsip_evsub_create_uac( dlg,  &xfer_user, &STR_REFER, 
+				     PJSIP_EVSUB_NO_EVENT_ID, &sub);
+    if (status != PJ_SUCCESS)
+	goto on_return;
+
+    /* Create xfer session */
+    xfer = pj_pool_zalloc(dlg->pool, sizeof(pjsip_xfer));
+    xfer->dlg = dlg;
+    xfer->sub = sub;
+    if (user_cb)
+	pj_memcpy(&xfer->user_cb, user_cb, sizeof(pjsip_evsub_user));
+
+    /* Attach to evsub */
+    pjsip_evsub_set_mod_data(sub, mod_xfer.id, xfer);
+
+    *p_evsub = sub;
+
+on_return:
+    pjsip_dlg_dec_lock(dlg);
+    return status;
+
+}
+
+
+
+
+/*
+ * Create transferee (receiver of REFER request).
+ *
+ */
+PJ_DEF(pj_status_t) pjsip_xfer_create_uas( pjsip_dialog *dlg,
+					   const pjsip_evsub_user *user_cb,
+					   pjsip_rx_data *rdata,
+					   pjsip_evsub **p_evsub )
+{
+    pjsip_evsub *sub;
+    pjsip_xfer *xfer;
+    const pj_str_t STR_EVENT = {"Event", 5 };
+    pjsip_event_hdr *event_hdr;
+    pj_status_t status;
+
+    /* Check arguments */
+    PJ_ASSERT_RETURN(dlg && rdata && p_evsub, PJ_EINVAL);
+
+    /* Must be request message */
+    PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
+		     PJSIP_ENOTREQUESTMSG);
+
+    /* Check that request is REFER */
+    PJ_ASSERT_RETURN(pjsip_method_cmp(&rdata->msg_info.msg->line.req.method,
+				      &pjsip_refer_method)==0,
+		     PJSIP_ENOTREFER);
+
+    /* Lock dialog */
+    pjsip_dlg_inc_lock(dlg);
+
+    /* The evsub framework expects an Event header in the request,
+     * while a REFER request conveniently doesn't have one (pun intended!).
+     * So create a dummy Event header.
+     */
+    if (pjsip_msg_find_hdr_by_name(rdata->msg_info.msg,
+				   &STR_EVENT, NULL)==NULL)
+    {
+	event_hdr = pjsip_event_hdr_create(rdata->tp_info.pool);
+	event_hdr->event_type = STR_REFER;
+	pjsip_msg_add_hdr(rdata->msg_info.msg, (pjsip_hdr*)event_hdr);
+    }
+
+    /* Create server subscription */
+    status = pjsip_evsub_create_uas( dlg, &xfer_user, rdata, 
+				     PJSIP_EVSUB_NO_EVENT_ID, &sub);
+    if (status != PJ_SUCCESS)
+	goto on_return;
+
+    /* Create server xfer subscription */
+    xfer = pj_pool_zalloc(dlg->pool, sizeof(pjsip_xfer));
+    xfer->dlg = dlg;
+    xfer->sub = sub;
+    if (user_cb)
+	pj_memcpy(&xfer->user_cb, user_cb, sizeof(pjsip_evsub_user));
+
+    /* Attach to evsub */
+    pjsip_evsub_set_mod_data(sub, mod_xfer.id, xfer);
+
+    /* Done: */
+    *p_evsub = sub;
+
+on_return:
+    pjsip_dlg_dec_lock(dlg);
+    return status;
+}
+
+
+
+/*
+ * Call this function to create request to initiate REFER subscription.
+ *
+ */
+PJ_DEF(pj_status_t) pjsip_xfer_initiate( pjsip_evsub *sub,
+					 const pj_str_t *refer_to_uri,
+					 pjsip_tx_data **p_tdata)
+{
+    pjsip_xfer *xfer;
+    const pj_str_t refer_to = { "Refer-To", 8};
+    pjsip_tx_data *tdata;
+    pjsip_generic_string_hdr *hdr;
+    pj_status_t status;
+
+    /* sub and p_tdata argument must be valid.  */
+    PJ_ASSERT_RETURN(sub && p_tdata, PJ_EINVAL);
+
+
+    /* Get the xfer object. */
+    xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id);
+    PJ_ASSERT_RETURN(xfer != NULL, PJSIP_ENOREFERSESSION);
+
+    /* refer_to_uri argument MAY be NULL for subsequent REFER requests,
+     * but it MUST be specified in the first REFER.
+     */
+    PJ_ASSERT_RETURN((refer_to_uri || xfer->refer_to_uri.slen), PJ_EINVAL);
+
+    /* Lock dialog. */
+    pjsip_dlg_inc_lock(xfer->dlg);
+
+    /* Create basic REFER request */
+    status = pjsip_evsub_initiate(sub, &pjsip_refer_method, -1, 
+				  &tdata);
+    if (status != PJ_SUCCESS)
+	goto on_return;
+
+    /* Save Refer-To URI. */
+    if (refer_to_uri == NULL) {
+	refer_to_uri = &xfer->refer_to_uri;
+    } else {
+	pj_strdup(xfer->dlg->pool, &xfer->refer_to_uri, refer_to_uri);
+    }
+
+    /* Create and add Refer-To header. */
+    hdr = pjsip_generic_string_hdr_create(tdata->pool, &refer_to,
+					  refer_to_uri);
+    if (!hdr) {
+	pjsip_tx_data_dec_ref(tdata);
+	status = PJ_ENOMEM;
+	goto on_return;
+    }
+
+    pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr);
+
+
+    /* Done. */
+    *p_tdata = tdata;
+
+    status = PJ_SUCCESS;
+
+on_return:
+    pjsip_dlg_dec_lock(xfer->dlg);
+    return status;
+}
+
+
+/*
+ * Accept the incoming REFER request by sending 2xx response.
+ *
+ */
+PJ_DEF(pj_status_t) pjsip_xfer_accept( pjsip_evsub *sub,
+				       pjsip_rx_data *rdata,
+				       int st_code,
+				       const pjsip_hdr *hdr_list )
+{
+    /*
+     * Don't need to add custom headers, so just call basic
+     * evsub response.
+     */
+    return pjsip_evsub_accept( sub, rdata, st_code, hdr_list );
+}
+
+
+/*
+ * For notifier, create NOTIFY request to subscriber, and set the state 
+ * of the subscription. 
+ */
+PJ_DEF(pj_status_t) pjsip_xfer_notify( pjsip_evsub *sub,
+				       pjsip_evsub_state state,
+				       int xfer_st_code,
+				       const pj_str_t *xfer_st_text,
+				       pjsip_tx_data **p_tdata)
+{
+    pjsip_tx_data *tdata;
+    pjsip_xfer *xfer;
+    const pj_str_t reason = { "noresource", 10 };
+    char *body;
+    int bodylen;
+    pjsip_msg_body *msg_body;
+    pj_status_t status;
+    
+
+    /* Check arguments. */
+    PJ_ASSERT_RETURN(sub, PJ_EINVAL);
+
+    /* Get the xfer object. */
+    xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id);
+    PJ_ASSERT_RETURN(xfer != NULL, PJSIP_ENOREFERSESSION);
+
+
+    /* Lock object. */
+    pjsip_dlg_inc_lock(xfer->dlg);
+
+    /* Create the NOTIFY request. 
+     * Note that reason is only used when state is TERMINATED, and
+     * the defined termination reason for REFER is "noresource".
+     */
+    status = pjsip_evsub_notify( sub, state, NULL, &reason, &tdata);
+    if (status != PJ_SUCCESS)
+	goto on_return;
+
+
+    /* Check status text */
+    if (xfer_st_text==NULL || xfer_st_text->slen==0)
+	xfer_st_text = pjsip_get_status_text(xfer_st_code);
+
+    /* Save st_code and st_text, for current_notify() */
+    xfer->last_st_code = xfer_st_code;
+    pj_strdup(xfer->dlg->pool, &xfer->last_st_text, xfer_st_text);
+
+    /* Create sipfrag content. */
+    body = pj_pool_alloc(tdata->pool, 128);
+    bodylen = pj_ansi_snprintf(body, 128, "SIP/2.0 %u %.*s",
+			       xfer_st_code,
+			       (int)xfer_st_text->slen,
+			       xfer_st_text->ptr);
+    PJ_ASSERT_ON_FAIL(bodylen > 0 && bodylen < 128, 
+			{status=PJ_EBUG; pjsip_tx_data_dec_ref(tdata); 
+			 goto on_return; });
+
+
+    /* Create SIP message body. */
+    msg_body = pj_pool_zalloc(tdata->pool, sizeof(pjsip_msg_body));
+    msg_body->content_type.type = STR_MESSAGE;
+    msg_body->content_type.subtype = STR_SIPFRAG;
+    msg_body->content_type.param = STR_SIPFRAG_VERSION;
+    msg_body->data = body;
+    msg_body->len = bodylen;
+    msg_body->print_body = &pjsip_print_text_body;
+    msg_body->clone_data = &pjsip_clone_text_data;
+
+    /* Attach sipfrag body. */
+    tdata->msg->body = msg_body;
+
+
+    /* Done. */
+    *p_tdata = tdata;
+
+
+on_return:
+    pjsip_dlg_dec_lock(xfer->dlg);
+    return status;
+
+}
+
+
+/*
+ * Send current state and the last sipfrag body.
+ */
+PJ_DEF(pj_status_t) pjsip_xfer_current_notify( pjsip_evsub *sub,
+					       pjsip_tx_data **p_tdata )
+{
+    pjsip_xfer *xfer;
+    pj_status_t status;
+    
+
+    /* Check arguments. */
+    PJ_ASSERT_RETURN(sub, PJ_EINVAL);
+
+    /* Get the xfer object. */
+    xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id);
+    PJ_ASSERT_RETURN(xfer != NULL, PJSIP_ENOREFERSESSION);
+
+    pjsip_dlg_inc_lock(xfer->dlg);
+
+    status = pjsip_xfer_notify(sub, pjsip_evsub_get_state(sub),
+			       xfer->last_st_code, &xfer->last_st_text,
+			       p_tdata);
+
+    pjsip_dlg_dec_lock(xfer->dlg);
+
+    return status;
+}
+
+
+/*
+ * Send request message. 
+ */
+PJ_DEF(pj_status_t) pjsip_xfer_send_request( pjsip_evsub *sub,
+					     pjsip_tx_data *tdata)
+{
+    return pjsip_evsub_send_request(sub, tdata);
+}
+
+
+/*
+ * This callback is called by event subscription when subscription
+ * state has changed.
+ */
+static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event)
+{
+    pjsip_xfer *xfer;
+
+    xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id);
+    PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
+
+    if (xfer->user_cb.on_evsub_state)
+	(*xfer->user_cb.on_evsub_state)(sub, event);
+
+}
+
+/*
+ * Called when transaction state has changed.
+ */
+static void xfer_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx,
+				     pjsip_event *event)
+{
+    pjsip_xfer *xfer;
+
+    xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id);
+    PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
+
+    if (xfer->user_cb.on_tsx_state)
+	(*xfer->user_cb.on_tsx_state)(sub, tsx, event);
+}
+
+/*
+ * Called when REFER is received to refresh subscription.
+ */
+static void xfer_on_evsub_rx_refresh( 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)
+{
+    pjsip_xfer *xfer;
+
+    xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id);
+    PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
+
+    if (xfer->user_cb.on_rx_refresh) {
+	(*xfer->user_cb.on_rx_refresh)(sub, rdata, p_st_code, p_st_text,
+				       res_hdr, p_body);
+
+    } else {
+	/* Implementors MUST send NOTIFY if it implements on_rx_refresh
+	 * (implementor == "us" from evsub point of view.
+	 */
+	pjsip_tx_data *tdata;
+	pj_status_t status;
+
+	if (pjsip_evsub_get_state(sub)==PJSIP_EVSUB_STATE_TERMINATED) {
+	    status = pjsip_xfer_notify( sub, PJSIP_EVSUB_STATE_TERMINATED,
+					xfer->last_st_code,
+					&xfer->last_st_text, 
+					&tdata);
+	} else {
+	    status = pjsip_xfer_current_notify(sub, &tdata);
+	}
+
+	if (status == PJ_SUCCESS)
+	    pjsip_xfer_send_request(sub, tdata);
+    }
+}
+
+
+/*
+ * Called when NOTIFY is received.
+ */
+static void xfer_on_evsub_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)
+{
+    pjsip_xfer *xfer;
+
+    xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id);
+    PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
+
+    if (xfer->user_cb.on_rx_notify)
+	(*xfer->user_cb.on_rx_notify)(sub, rdata, p_st_code, p_st_text,
+				      res_hdr, p_body);
+}
+
+/*
+ * Called when it's time to send SUBSCRIBE.
+ */
+static void xfer_on_evsub_client_refresh(pjsip_evsub *sub)
+{
+    pjsip_xfer *xfer;
+
+    xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id);
+    PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
+
+    if (xfer->user_cb.on_client_refresh) {
+	(*xfer->user_cb.on_client_refresh)(sub);
+    } else {
+	pj_status_t status;
+	pjsip_tx_data *tdata;
+
+	status = pjsip_xfer_initiate(sub, NULL, &tdata);
+	if (status == PJ_SUCCESS)
+	    pjsip_xfer_send_request(sub, tdata);
+    }
+}
+
+
+/*
+ * Called when no refresh is received after the interval.
+ */
+static void xfer_on_evsub_server_timeout(pjsip_evsub *sub)
+{
+    pjsip_xfer *xfer;
+
+    xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id);
+    PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
+
+    if (xfer->user_cb.on_server_timeout) {
+	(*xfer->user_cb.on_server_timeout)(sub);
+    } else {
+	pj_status_t status;
+	pjsip_tx_data *tdata;
+
+	status = pjsip_xfer_notify(sub, PJSIP_EVSUB_STATE_TERMINATED,
+				   xfer->last_st_code, 
+				   &xfer->last_st_text, &tdata);
+	if (status == PJ_SUCCESS)
+	    pjsip_xfer_send_request(sub, tdata);
+    }
+}
+