Index: /pjproject/trunk/pjsip/build/pjsip_ua.dsp
===================================================================
--- /pjproject/trunk/pjsip/build/pjsip_ua.dsp	(revision 211)
+++ /pjproject/trunk/pjsip/build/pjsip_ua.dsp	(revision 212)
@@ -94,4 +94,8 @@
 SOURCE="..\src\pjsip-ua\sip_reg.c"
 # End Source File
+# Begin Source File
+
+SOURCE="..\src\pjsip-ua\sip_xfer.c"
+# End Source File
 # End Group
 # Begin Group "Header Files"
@@ -110,4 +114,8 @@
 SOURCE="..\include\pjsip-ua\sip_regc.h"
 # End Source File
+# Begin Source File
+
+SOURCE="..\include\pjsip-ua\sip_xfer.h"
+# End Source File
 # End Group
 # End Target
Index: /pjproject/trunk/pjsip/include/pjsip-simple/errno.h
===================================================================
--- /pjproject/trunk/pjsip/include/pjsip-simple/errno.h	(revision 211)
+++ /pjproject/trunk/pjsip/include/pjsip-simple/errno.h	(revision 212)
@@ -24,13 +24,19 @@
 
 /**
+ * Start of error code relative to PJ_ERRNO_START_USER.
+ */
+#define PJSIP_SIMPLE_ERRNO_START  (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*2)
+
+
+/**
  * @hideinitializer
  * No event package with the specified name.
  */
-#define PJSIP_SIMPLE_ENOPKG	    -1
+#define PJSIP_SIMPLE_ENOPKG	    (PJSIP_SIMPLE_ERRNO_START+1)    /*270001*/
 /**
  * @hideinitializer
  * Event package already exists.
  */
-#define PJSIP_SIMPLE_EPKGEXISTS	    -1
+#define PJSIP_SIMPLE_EPKGEXISTS	    (PJSIP_SIMPLE_ERRNO_START+2)    /*270002*/
 
 
@@ -39,30 +45,30 @@
  * Expecting SUBSCRIBE request
  */
-#define PJSIP_SIMPLE_ENOTSUBSCRIBE  -1
+#define PJSIP_SIMPLE_ENOTSUBSCRIBE  (PJSIP_SIMPLE_ERRNO_START+20)   /*270020*/
 /**
  * @hideinitializer
  * No presence associated with subscription
  */
-#define PJSIP_SIMPLE_ENOPRESENCE    -1
+#define PJSIP_SIMPLE_ENOPRESENCE    (PJSIP_SIMPLE_ERRNO_START+21)   /*270021*/
 /**
  * @hideinitializer
  * No presence info in server subscription
  */
-#define PJSIP_SIMPLE_ENOPRESENCEINFO -1
+#define PJSIP_SIMPLE_ENOPRESENCEINFO (PJSIP_SIMPLE_ERRNO_START+22)  /*270022*/
 /**
  * @hideinitializer
  * Bad Content-Type
  */
-#define PJSIP_SIMPLE_EBADCONTENT    -1
+#define PJSIP_SIMPLE_EBADCONTENT    (PJSIP_SIMPLE_ERRNO_START+23)   /*270023*/
 /**
  * @hideinitializer
  * Bad PIDF Message
  */
-#define PJSIP_SIMPLE_EBADPIDF	    -1
+#define PJSIP_SIMPLE_EBADPIDF	    (PJSIP_SIMPLE_ERRNO_START+24)   /*270024*/
 /**
  * @hideinitializer
  * Bad XPIDF Message
  */
-#define PJSIP_SIMPLE_EBADXPIDF	    -1
+#define PJSIP_SIMPLE_EBADXPIDF	    (PJSIP_SIMPLE_ERRNO_START+25)   /*270025*/
 
 
Index: /pjproject/trunk/pjsip/include/pjsip-simple/evsub.h
===================================================================
--- /pjproject/trunk/pjsip/include/pjsip-simple/evsub.h	(revision 211)
+++ /pjproject/trunk/pjsip/include/pjsip-simple/evsub.h	(revision 212)
@@ -77,4 +77,17 @@
 
 
+/**
+ * Some options for the event subscription.
+ */
+enum
+{
+    /** 
+     * If this flag is set, then outgoing request to create subscription
+     * will not have id in the Event header (e.g. in REFER request). But if 
+     * there is an id in the incoming NOTIFY, that id will be used.
+     */
+    PJSIP_EVSUB_NO_EVENT_ID  = 1,
+};
+
 
 /**
@@ -245,4 +258,5 @@
  * @param user_cb	Callback to receive event subscription notifications.
  * @param event		Event name.
+ * @param option	Bitmask of options.
  * @param p_evsub	Pointer to receive event subscription instance.
  *
@@ -252,4 +266,5 @@
 					     const pjsip_evsub_user *user_cb,
 					     const pj_str_t *event,
+					     unsigned option,
 					     pjsip_evsub **p_evsub);
 
@@ -261,4 +276,5 @@
  * @param rdata		The incoming request that creates the event 
  *			subscription, such as SUBSCRIBE or REFER.
+ * @param option	Bitmask of options.
  * @param p_evsub	Pointer to receive event subscription instance.
  *
@@ -268,4 +284,5 @@
 					     const pjsip_evsub_user *user_cb,
 					     pjsip_rx_data *rdata,
+					     unsigned option,
 					     pjsip_evsub **p_evsub);
 
@@ -367,5 +384,8 @@
 
 /**
- * Send request message.
+ * Send request message that was previously created with initiate(), notify(),
+ * or current_notify(). Application may also send request created with other
+ * functions, e.g. authentication. But the request MUST be either request
+ * that creates/refresh subscription or NOTIFY request.
  *
  * @param sub		The event subscription object.
Index: /pjproject/trunk/pjsip/include/pjsip-simple/presence.h
===================================================================
--- /pjproject/trunk/pjsip/include/pjsip-simple/presence.h	(revision 211)
+++ /pjproject/trunk/pjsip/include/pjsip-simple/presence.h	(revision 212)
@@ -203,5 +203,8 @@
 
 /**
- * Send request.
+ * Send request message that was previously created with initiate(), notify(),
+ * or current_notify(). Application may also send request created with other
+ * functions, e.g. authentication. But the request MUST be either request
+ * that creates/refresh subscription or NOTIFY request.
  *
  * @param sub		The subscription object.
Index: /pjproject/trunk/pjsip/include/pjsip-ua/sip_inv.h
===================================================================
--- /pjproject/trunk/pjsip/include/pjsip-ua/sip_inv.h	(revision 211)
+++ /pjproject/trunk/pjsip/include/pjsip-ua/sip_inv.h	(revision 212)
@@ -98,14 +98,11 @@
      * This callback is called when the invite session has received 
      * new offer from peer. Application can inspect the remote offer 
-     * by calling negotiator's pjmedia_sdp_neg_get_neg_remote(), and 
-     * optionally specify a modified answer. 
-     *
-     * This callback is optional. When it's not specified, the default 
-     * behavior is nothing. After calling this callback, the negotiator 
-     * will negotiate remote offer with session's initial capability.
+     * in "offer". 
      *
      * @param inv	The invite session.
-     */
-    void (*on_rx_offer)(pjsip_inv_session *inv);
+     * @param offer	Remote offer.
+     */
+    void (*on_rx_offer)(pjsip_inv_session *inv,
+			const pjmedia_sdp_session *offer);
 
     /**
@@ -122,5 +119,4 @@
     void (*on_media_update)(pjsip_inv_session *inv_ses, 
 			    pj_status_t status);
-
 };
 
@@ -388,21 +384,18 @@
 
 
-#if 0
- If we implement this, we need to send SDP answer automatically on 
- subsequent request/response. Something like "has_answer" flag.
-
-/**
- * Set local answer to respond to remote SDP offer. By calling this function,
- * application can start SDP negotiation immediately without having to
- * send any request or response message. This function is normally called
- * when on_rx_offer() callback is called.
+/**
+ * Set local answer to respond to remote SDP offer, to be carried by 
+ * subsequent response (or request).
  *
  * @param inv		The invite session.
  * @param sdp		The SDP description which will be set as answer
  *			to remote.
- */
-PJ_DECL(pj_status_t) pjsip_inv_set_sdp_answer( pjsip_inv_session *inv,
-					       const pjmedia_sdp_session *sdp );
-#endif
+ *
+ * @return		PJ_SUCCESS if local answer can be accepted by
+ *			SDP negotiator.
+ */
+PJ_DECL(pj_status_t) pjsip_inv_set_sdp_answer(pjsip_inv_session *inv,
+					      const pjmedia_sdp_session *sdp );
+
 
 /**
Index: /pjproject/trunk/pjsip/include/pjsip-ua/sip_xfer.h
===================================================================
--- /pjproject/trunk/pjsip/include/pjsip-ua/sip_xfer.h	(revision 212)
+++ /pjproject/trunk/pjsip/include/pjsip-ua/sip_xfer.h	(revision 212)
@@ -0,0 +1,175 @@
+/* $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 
+ */
+#ifndef __PJSIP_XFER_H__
+#define __PJSIP_XFER_H__
+
+
+
+/**
+ * @file sip_xfer.h
+ * @brief SIP Transfer (REFER, RFC 3515)
+ */
+#include <pjsip-simple/evsub.h>
+#include <pjsip/sip_msg.h>
+
+
+
+PJ_BEGIN_DECL
+
+
+/**
+ * Declaration for REFER method constant.
+ */
+extern const pjsip_method pjsip_refer_method;
+
+
+/**
+ * Initialize the REFER subsystem.
+ * This currently does very little (only register REFER as supported method).
+ */
+PJ_DECL(pj_status_t) pjsip_xfer_init_module(pjsip_endpoint *endpt);
+
+
+
+/**
+ * Create transferer (sender of REFER request).
+ *
+ * @param dlg		The underlying dialog to use.
+ * @param user_cb	Pointer to callbacks to receive presence subscription
+ *			events.
+ * @param p_evsub	Pointer to receive the presence subscription
+ *			session.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsip_xfer_create_uac( pjsip_dialog *dlg,
+					    const pjsip_evsub_user *user_cb,
+					    pjsip_evsub **p_evsub );
+
+
+/**
+ * Create transferee (receiver of REFER request).
+ *
+ * @param dlg		The underlying dialog to use.
+ * @param user_cb	Pointer to callbacks to receive presence subscription
+ *			events.
+ * @param rdata		The incoming SUBSCRIBE request that creates the event 
+ *			subscription.
+ * @param p_evsub	Pointer to receive the presence subscription
+ *			session.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsip_xfer_create_uas( pjsip_dialog *dlg,
+					    const pjsip_evsub_user *user_cb,
+					    pjsip_rx_data *rdata,
+					    pjsip_evsub **p_evsub );
+
+/**
+ * Call this function to create request to initiate REFER subscription,
+ * to refresh subscription, or to unsubscribe. For request other than
+ * the initial REFER request, "refer_to_uri" argument may be NULL.
+ *
+ * @param sub		Client subscription instance.
+ * @param refer_to_uri	URI to be put to the Refer-To header. This argument
+ *			may be NULL for subsequent REFER requests.
+ * @param p_tdata	Pointer to receive the request.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsip_xfer_initiate( pjsip_evsub *sub,
+					  const pj_str_t *refer_to_uri,
+					  pjsip_tx_data **p_tdata);
+
+
+/**
+ * Accept the incoming REFER request by sending 2xx response.
+ *
+ * @param sub		Server subscription instance.
+ * @param rdata		The incoming subscription request message.
+ * @param st_code	Status code, which MUST be 2xx.
+ * @param hdr_list	Optional list of headers to be added in the response.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsip_xfer_accept( pjsip_evsub *sub,
+					pjsip_rx_data *rdata,
+				        int st_code,
+					const pjsip_hdr *hdr_list );
+
+
+/**
+ * For notifier, create NOTIFY request to subscriber, and set the state 
+ * of the subscription. 
+ *
+ * @param sub		The server subscription (notifier) instance.
+ * @param state		New state to set.
+ * @param xfer_st_code	The call status code to be reported with the NOTIFY
+ *			request.
+ * @param xfer_st_text	Optional call status text to be reported with the 
+ *			NOTIFY request. If the value is NULL, default 
+ *			status text will be used.
+ * @param p_tdata	Pointer to receive the request.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(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);
+
+
+/**
+ * Create NOTIFY request to reflect current subscription status. Application
+ * can only call this function after it has sent NOTIFY before.
+ * This will also re-send the last "message/sipfrag" body that was sent
+ * in the previous NOTIFY.
+ *
+ * @param sub		Server subscription object.
+ * @param p_tdata	Pointer to receive request.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsip_xfer_current_notify( pjsip_evsub *sub,
+					        pjsip_tx_data **p_tdata );
+
+
+
+/**
+ * Send request message that was previously created with initiate(), notify(),
+ * or current_notify(). Application may also send request created with other
+ * functions, e.g. authentication. But the request MUST be either request
+ * that creates/refresh subscription or NOTIFY request.
+ *
+ *
+ * @param sub		The event subscription object.
+ * @param tdata		Request message to be send.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsip_xfer_send_request( pjsip_evsub *sub,
+					      pjsip_tx_data *tdata);
+
+
+
+PJ_END_DECL
+
+#endif	/* __PJSIP_XFER_H__ */
+
Index: /pjproject/trunk/pjsip/include/pjsip/sip_dialog.h
===================================================================
--- /pjproject/trunk/pjsip/include/pjsip/sip_dialog.h	(revision 211)
+++ /pjproject/trunk/pjsip/include/pjsip/sip_dialog.h	(revision 212)
@@ -42,4 +42,5 @@
 {
     pjsip_fromto_hdr	*info;	    /**< From/To header, inc tag.	*/
+    pj_str_t		 info_str;  /**< String rep of info header.	*/
     pj_uint32_t		 tag_hval;  /**< Hashed value of the tag.	*/
     pjsip_contact_hdr	*contact;   /**< Contact header.		*/
@@ -77,6 +78,7 @@
 
     /* Dialog's session properties. */
-    enum pjsip_dialog_state	state;	    /**< Dialog state.			    */
+    pjsip_dialog_state	state;	    /**< Dialog state.			    */
     pjsip_uri	       *target;	    /**< Current target.		    */
+    pjsip_hdr	        inv_hdr;    /**< Headers from hparam in dest URL    */
     pjsip_dlg_party     local;	    /**< Local party info.		    */
     pjsip_dlg_party     remote;	    /**< Remote party info.		    */
Index: /pjproject/trunk/pjsip/include/pjsip/sip_errno.h
===================================================================
--- /pjproject/trunk/pjsip/include/pjsip/sip_errno.h	(revision 211)
+++ /pjproject/trunk/pjsip/include/pjsip/sip_errno.h	(revision 212)
@@ -172,4 +172,9 @@
  */
 #define PJSIP_EMISSINGBODY	(PJSIP_ERRNO_START_PJSIP + 54)	/* 171054 */
+/**
+ * @hideinitializer
+ * Invalid/unexpected method.
+ */
+#define PJSIP_EINVALIDMETHOD	(PJSIP_ERRNO_START_PJSIP + 55)	/* 171055 */
 
 
@@ -348,4 +353,15 @@
  */
 #define PJSIP_EMISSINGTAG	 (PJSIP_ERRNO_START_PJSIP+120)	/* 171120 */
+/**
+ * @hideinitializer
+ * Expecting REFER method
+ */
+#define PJSIP_ENOTREFER		 (PJSIP_ERRNO_START_PJSIP+121)	/* 171121 */
+/**
+ * @hideinitializer
+ * Not associated with REFER subscription
+ */
+#define PJSIP_ENOREFERSESSION	 (PJSIP_ERRNO_START_PJSIP+122)	/* 171122 */
+
 
 
Index: /pjproject/trunk/pjsip/include/pjsip/sip_msg.h
===================================================================
--- /pjproject/trunk/pjsip/include/pjsip/sip_msg.h	(revision 211)
+++ /pjproject/trunk/pjsip/include/pjsip/sip_msg.h	(revision 212)
@@ -585,4 +585,20 @@
 
 /**
+ * General purpose function to clone textual data in a SIP body. Attach this
+ * function as "clone_data" member of the SIP body only if the data type
+ * is a text (i.e. C string, not pj_str_t), and the length indicates the
+ * length of the text.
+ *
+ *  @param pool		Pool used to clone the data.
+ *  @param data		Textual data.
+ *  @param len		The length of the string.
+ *
+ *  @return		New text duplicated from the original text.
+ */
+PJ_DECL(void*) pjsip_clone_text_data( pj_pool_t *pool, const void *data,
+				      unsigned len);
+
+
+/**
  * Clone the message body in src_body to the dst_body. This will duplicate
  * the contents of the message body using the \a clone_data member of the
Index: /pjproject/trunk/pjsip/include/pjsip_ua.h
===================================================================
--- /pjproject/trunk/pjsip/include/pjsip_ua.h	(revision 211)
+++ /pjproject/trunk/pjsip/include/pjsip_ua.h	(revision 212)
@@ -22,4 +22,5 @@
 #include <pjsip-ua/sip_inv.h>
 #include <pjsip-ua/sip_regc.h>
+#include <pjsip-ua/sip_xfer.h>
 
 
Index: /pjproject/trunk/pjsip/src/pjsip-simple/errno.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsip-simple/errno.c	(revision 211)
+++ /pjproject/trunk/pjsip/src/pjsip-simple/errno.c	(revision 212)
@@ -18,3 +18,82 @@
  */
 #include <pjsip-simple/errno.h>
+#include <pj/string.h>
 
+/* PJSIP-SIMPLE's own error codes/messages 
+ * MUST KEEP THIS ARRAY SORTED!!
+ * Message must be limited to 64 chars!
+ */
+static const struct 
+{
+    int code;
+    const char *msg;
+} err_str[] = 
+{
+    { PJSIP_SIMPLE_ENOPKG,	    "No SIP event package with the specified name" },
+    { PJSIP_SIMPLE_EPKGEXISTS,	    "SIP event package already exist" },
+
+    { PJSIP_SIMPLE_ENOTSUBSCRIBE,   "Expecting SUBSCRIBE request" },
+    { PJSIP_SIMPLE_ENOPRESENCE,	    "No presence associated with the subscription" },
+    { PJSIP_SIMPLE_ENOPRESENCEINFO, "No presence info in the server subscription" },
+    { PJSIP_SIMPLE_EBADCONTENT,	    "Bad Content-Type for presence" },
+    { PJSIP_SIMPLE_EBADPIDF,	    "Bad PIDF content for presence" },
+    { PJSIP_SIMPLE_EBADXPIDF,	    "Bad XPIDF content for presence" },
+};
+
+
+
+/*
+ * pjsipsimple_strerror()
+ */
+PJ_DEF(pj_str_t) pjsipsimple_strerror( pj_status_t statcode, 
+				       char *buf, pj_size_t bufsize )
+{
+    pj_str_t errstr;
+
+    if (statcode >= PJSIP_SIMPLE_ERRNO_START && 
+	statcode < PJSIP_SIMPLE_ERRNO_START + PJ_ERRNO_SPACE_SIZE)
+    {
+	/* Find the error in the table.
+	 * Use binary search!
+	 */
+	int first = 0;
+	int n = PJ_ARRAY_SIZE(err_str);
+
+	while (n > 0) {
+	    int half = n/2;
+	    int mid = first + half;
+
+	    if (err_str[mid].code < statcode) {
+		first = mid+1;
+		n -= (half+1);
+	    } else if (err_str[mid].code > statcode) {
+		n = half;
+	    } else {
+		first = mid;
+		break;
+	    }
+	}
+
+
+	if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) {
+	    pj_str_t msg;
+	    
+	    msg.ptr = (char*)err_str[first].msg;
+	    msg.slen = pj_ansi_strlen(err_str[first].msg);
+
+	    errstr.ptr = buf;
+	    pj_strncpy_with_null(&errstr, &msg, bufsize);
+	    return errstr;
+
+	} 
+    }
+
+    /* Error not found. */
+    errstr.ptr = buf;
+    errstr.slen = pj_snprintf(buf, bufsize, 
+			      "Unknown error %d",
+			      statcode);
+
+    return errstr;
+}
+
Index: /pjproject/trunk/pjsip/src/pjsip-simple/evsub.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsip-simple/evsub.c	(revision 211)
+++ /pjproject/trunk/pjsip/src/pjsip-simple/evsub.c	(revision 212)
@@ -198,4 +198,5 @@
     pjsip_dialog	 *dlg;		/**< Underlying dialog.		    */
     struct evpkg	 *pkg;		/**< The event package.		    */
+    unsigned		  option;	/**< Options.			    */
     pjsip_evsub_user	  user;		/**< Callback.			    */
     pjsip_role_e	  role;		/**< UAC=subscriber, UAS=notifier   */
@@ -236,4 +237,5 @@
 static const pj_str_t STR_TIMEOUT    = { "timeout", 7};
 
+
 /*
  * On unload module.
@@ -246,4 +248,10 @@
     return PJ_SUCCESS;
 }
+
+/* Proto for pjsipsimple_strerror().
+ * Defined in errno.c
+ */
+PJ_DECL(pj_str_t) pjsipsimple_strerror( pj_status_t statcode, 
+				        char *buf, pj_size_t bufsize );
 
 /*
@@ -257,4 +265,7 @@
 	{ "NOTIFY", 6}
     };
+
+    pj_register_strerror(PJSIP_SIMPLE_ERRNO_START, PJ_ERRNO_SPACE_SIZE,
+			 &pjsipsimple_strerror);
 
     PJ_ASSERT_RETURN(endpt != NULL, PJ_EINVAL);
@@ -619,4 +630,5 @@
 				 const pjsip_evsub_user *user_cb,
 				 const pj_str_t *event,
+				 unsigned option,
 				 pjsip_evsub **p_evsub )
 {
@@ -641,4 +653,5 @@
     sub->pkg = pkg;
     sub->role = role;
+    sub->option = option;
     sub->state = PJSIP_EVSUB_STATE_NULL;
     sub->state_str = evsub_state_names[sub->state];
@@ -698,4 +711,5 @@
 					    const pjsip_evsub_user *user_cb,
 					    const pj_str_t *event,
+					    unsigned option,
 					    pjsip_evsub **p_evsub)
 {
@@ -706,10 +720,14 @@
 
     pjsip_dlg_inc_lock(dlg);
-    status = evsub_create(dlg, PJSIP_UAC_ROLE, user_cb, event, &sub);
+    status = evsub_create(dlg, PJSIP_UAC_ROLE, user_cb, event, option, &sub);
     if (status != PJ_SUCCESS)
 	goto on_return;
 
-    /* Add unique Id to Event header */
-    pj_create_unique_string(sub->pool, &sub->event->id_param);
+    /* Add unique Id to Event header, only when PJSIP_EVSUB_NO_EVENT_ID
+     * is not specified.
+     */
+    if ((option & PJSIP_EVSUB_NO_EVENT_ID) == 0) {
+	pj_create_unique_string(sub->pool, &sub->event->id_param);
+    }
 
     /* Increment dlg session. */
@@ -731,4 +749,5 @@
 					    const pjsip_evsub_user *user_cb,
 					    pjsip_rx_data *rdata,
+					    unsigned option,
 					    pjsip_evsub **p_evsub)
 {
@@ -758,6 +777,7 @@
     PJ_ASSERT_RETURN(user_cb->on_rx_refresh, PJ_EINVALIDOP);
 
-    /* Request MUST have "Event" header: */
-
+    /* Request MUST have "Event" header. We need the Event header to get 
+     * the package name (don't want to add more arguments in the function).
+     */
     event_hdr = (pjsip_event_hdr*) 
 	pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_EVENT, NULL);
@@ -773,5 +793,5 @@
 
     status = evsub_create(dlg, PJSIP_UAS_ROLE, user_cb, 
-			  &event_hdr->event_type, &sub);
+			  &event_hdr->event_type, option, &sub);
     if (status != PJ_SUCCESS)
 	goto on_return;
@@ -1148,4 +1168,5 @@
     }
 
+
     switch (event->body.tsx_state.type) {
     case PJSIP_EVENT_RX_MSG:
@@ -1164,5 +1185,9 @@
     
     if (!msg) {
-	pj_assert(!"First transaction event is not TX or RX!");
+	//Note:
+	// this transaction can be other transaction in the dialog.
+	// The assertion below probably only valid for dialog that
+	// only has one event subscription usage.
+	//pj_assert(!"First transaction event is not TX or RX!");
 	return NULL;
     }
@@ -1188,10 +1213,41 @@
     while (dlgsub != dlgsub_head) {
 
-	/* Match event type and Id */
-	if (pj_strcmp(&dlgsub->sub->event->id_param, &event_hdr->id_param)==0 &&
-	    pj_stricmp(&dlgsub->sub->event->event_type, &event_hdr->event_type)==0)
+	if (pj_stricmp(&dlgsub->sub->event->event_type, 
+		       &event_hdr->event_type)==0)
 	{
-	    break;
-	}
+	    /* Event type matched. 
+	     * Check if event ID matched too.
+	     */
+	    if (pj_strcmp(&dlgsub->sub->event->id_param, 
+			  &event_hdr->id_param)==0)
+	    {
+		
+		break;
+
+	    }
+	    /*
+	     * Otherwise if it is an UAC subscription, AND
+	     * PJSIP_EVSUB_NO_EVENT_ID flag is set, AND
+	     * the session's event id is NULL, AND
+	     * the incoming request is NOTIFY with event ID, then
+	     * we consider it as a match, and update the
+	     * session's event id.
+	     */
+	    else if (dlgsub->sub->role == PJSIP_ROLE_UAC &&
+		     (dlgsub->sub->option & PJSIP_EVSUB_NO_EVENT_ID)!=0 &&
+		     dlgsub->sub->event->id_param.slen==0 &&
+		     !pjsip_method_cmp(&tsx->method, &pjsip_notify_method))
+	    {
+		/* Update session's event id. */
+		pj_strdup(dlgsub->sub->pool, 
+			  &dlgsub->sub->event->id_param,
+			  &event_hdr->id_param);
+
+		break;
+	    }
+	}
+	
+
+
 	dlgsub = dlgsub->next;
     }
@@ -1201,4 +1257,6 @@
 	PJ_LOG(4,(THIS_FILE, 
 		  "Subscription not found for %.*s, event=%.*s;id=%.*s",
+		  (int)tsx->method.name.slen,
+		  tsx->method.name.ptr,
 		  (int)event_hdr->event_type.slen,
 		  event_hdr->event_type.ptr,
@@ -1738,5 +1796,16 @@
 		set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL);
 	    }
-	} 
+
+	}
+	/*
+	 * Terminate event usage if we receive 481, 408, and 7 class
+	 * responses.
+	 */
+	if (sub->state != PJSIP_EVSUB_STATE_TERMINATED &&
+	    (tsx->status_code==481 || tsx->status_code==408 ||
+	     tsx->status_code/100 == 7))
+	{
+	    set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, event);
+	}
 
     } else {
Index: /pjproject/trunk/pjsip/src/pjsip-simple/presence.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsip-simple/presence.c	(revision 211)
+++ /pjproject/trunk/pjsip/src/pjsip-simple/presence.c	(revision 212)
@@ -192,5 +192,5 @@
 
     /* Create event subscription */
-    status = pjsip_evsub_create_uac( dlg,  &pres_user, &STR_PRESENCE, &sub);
+    status = pjsip_evsub_create_uac( dlg,  &pres_user, &STR_PRESENCE, 0, &sub);
     if (status != PJ_SUCCESS)
 	goto on_return;
@@ -199,4 +199,5 @@
     pres = pj_pool_zalloc(dlg->pool, sizeof(pjsip_pres));
     pres->dlg = dlg;
+    pres->sub = sub;
     if (user_cb)
 	pj_memcpy(&pres->user_cb, user_cb, sizeof(pjsip_evsub_user));
@@ -298,5 +299,5 @@
 
     /* Create server subscription */
-    status = pjsip_evsub_create_uas( dlg, &pres_user, rdata, &sub);
+    status = pjsip_evsub_create_uas( dlg, &pres_user, rdata, 0, &sub);
     if (status != PJ_SUCCESS)
 	goto on_return;
Index: /pjproject/trunk/pjsip/src/pjsip-ua/sip_inv.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsip-ua/sip_inv.c	(revision 211)
+++ /pjproject/trunk/pjsip/src/pjsip-ua/sip_inv.c	(revision 212)
@@ -201,8 +201,5 @@
      * 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. */
@@ -215,6 +212,10 @@
 	}
 
-	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);
+	}
     }
 
@@ -251,5 +252,7 @@
      */
     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);
@@ -866,4 +869,5 @@
     pjsip_tx_data *tdata;
     const pjsip_hdr *hdr;
+    pj_bool_t has_sdp;
     pj_status_t status;
 
@@ -871,6 +875,8 @@
     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. */
@@ -880,8 +886,35 @@
 	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;
 
@@ -941,7 +974,7 @@
  * 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;
@@ -964,5 +997,5 @@
 
     if (tsx_inv_data->sdp_done)
-	return;
+	return PJ_SUCCESS;
 
     /* Check if SDP is present in the message. */
@@ -971,5 +1004,5 @@
     if (msg->body == NULL) {
 	/* Message doesn't have body. */
-	return;
+	return PJ_SUCCESS;
     }
 
@@ -978,5 +1011,5 @@
     {
 	/* Message body is not "application/sdp" */
-	return;
+	return PJMEDIA_SDP_EINSDP;
     }
 
@@ -990,5 +1023,5 @@
 	PJ_LOG(4,(THIS_FILE, "Error parsing SDP in %s: %s",
 		  pjsip_rx_data_get_info(rdata), errmsg));
-	return;
+	return PJMEDIA_SDP_EINSDP;
     }
 
@@ -1016,11 +1049,14 @@
 	    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) == 
@@ -1042,5 +1078,5 @@
 	    PJ_LOG(4,(THIS_FILE, "Error processing SDP answer in %s: %s",
 		      pjsip_rx_data_get_info(rdata), errmsg));
-	    return;
+	    return PJMEDIA_SDP_EINSDP;
 	}
 
@@ -1060,10 +1096,66 @@
     }
 
-}
-
-
-
-/*
- * Answer initial INVITE.
+    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
+ * Re-INVITE will be answered automatically, and will not use this function.
  */ 
 PJ_DEF(pj_status_t) pjsip_inv_answer(	pjsip_inv_session *inv,
@@ -1088,5 +1180,5 @@
      * offer before. 
      */
-    if (local_sdp) {
+    if (local_sdp && (st_code/100==1 || st_code/100==2)) {
 
 	if (inv->neg == NULL) {
@@ -1109,40 +1201,18 @@
     }
 
+
+
+
+    /* Modify last response. */
     last_res = inv->invite_tsx->last_tx;
-
-    /* Modify last response. */
     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;
 
 
@@ -1150,4 +1220,22 @@
 
     return PJ_SUCCESS;
+}
+
+
+/*
+ * 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;
 }
 
@@ -1206,6 +1294,7 @@
 	    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;
@@ -1248,11 +1337,80 @@
 					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_TODO(CREATE_REINVITE_REQUEST);
-    return PJ_ENOTSUP;
+    pj_status_t status;
+    pjsip_contact_hdr *contact_hdr = NULL;
+
+    /* 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;
 }
 
@@ -1909,4 +2067,122 @@
 
     }
+    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);
+	}
+    }
 }
 
Index: /pjproject/trunk/pjsip/src/pjsip-ua/sip_xfer.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsip-ua/sip_xfer.c	(revision 212)
+++ /pjproject/trunk/pjsip/src/pjsip-ua/sip_xfer.c	(revision 212)
@@ -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);
+    }
+}
+
Index: /pjproject/trunk/pjsip/src/pjsip/sip_dialog.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsip/sip_dialog.c	(revision 211)
+++ /pjproject/trunk/pjsip/src/pjsip/sip_dialog.c	(revision 212)
@@ -79,4 +79,6 @@
     dlg->state = PJSIP_DIALOG_STATE_NULL;
 
+    pj_list_init(&dlg->inv_hdr);
+
     status = pj_mutex_create_recursive(pool, "dlg%p", &dlg->mutex);
     if (status != PJ_SUCCESS)
@@ -132,8 +134,29 @@
     }
 
+    /* Put any header param in the target URI into INVITE header list. */
+    if (PJSIP_URI_SCHEME_IS_SIP(dlg->target) ||
+	PJSIP_URI_SCHEME_IS_SIPS(dlg->target))
+    {
+	pjsip_param *param;
+	pjsip_sip_uri *uri = (pjsip_sip_uri*)pjsip_uri_get_uri(dlg->target);
+
+	param = uri->header_param.next;
+	while (param != &uri->header_param) {
+	    pjsip_generic_string_hdr *req_hdr;
+
+	    req_hdr = pjsip_generic_string_hdr_create(dlg->pool, &param->name,
+						      &param->value);
+	    pj_list_push_back(&dlg->inv_hdr, req_hdr);
+
+	    param = param->next;
+	}
+    }
+
     /* Init local info. */
     dlg->local.info = pjsip_from_hdr_create(dlg->pool);
-    pj_strdup_with_null(dlg->pool, &tmp, local_uri);
-    dlg->local.info->uri = pjsip_parse_uri(dlg->pool, tmp.ptr, tmp.slen, 0);
+    pj_strdup_with_null(dlg->pool, &dlg->local.info_str, local_uri);
+    dlg->local.info->uri = pjsip_parse_uri(dlg->pool, 
+					   dlg->local.info_str.ptr, 
+					   dlg->local.info_str.slen, 0);
     if (!dlg->local.info->uri) {
 	status = PJSIP_EINVALIDURI;
@@ -165,6 +188,8 @@
     /* Init remote info. */
     dlg->remote.info = pjsip_to_hdr_create(dlg->pool);
-    pj_strdup_with_null(dlg->pool, &tmp, remote_uri);
-    dlg->remote.info->uri = pjsip_parse_uri(dlg->pool, tmp.ptr, tmp.slen, 0);
+    pj_strdup_with_null(dlg->pool, &dlg->remote.info_str, remote_uri);
+    dlg->remote.info->uri = pjsip_parse_uri(dlg->pool, 
+					    dlg->remote.info_str.ptr, 
+					    dlg->remote.info_str.slen, 0);
     if (!dlg->remote.info->uri) {
 	status = PJSIP_EINVALIDURI;
@@ -226,4 +251,7 @@
     pjsip_rr_hdr *rr;
     pjsip_transaction *tsx = NULL;
+    pj_str_t tmp;
+    enum { TMP_LEN=128};
+    pj_ssize_t len;
     pjsip_dialog *dlg;
 
@@ -250,4 +278,9 @@
 	return status;
 
+    /* Temprary string for getting the string representation of
+     * both local and remote URI.
+     */
+    tmp.ptr = pj_pool_alloc(rdata->tp_info.pool, TMP_LEN);
+
     /* Init local info from the To header. */
     dlg->local.info = pjsip_hdr_clone(dlg->pool, rdata->msg_info.to);
@@ -257,7 +290,33 @@
     pj_create_unique_string(dlg->pool, &dlg->local.info->tag);
 
+
+    /* Print the local info. */
+    len = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR,
+			  dlg->local.info->uri, tmp.ptr, TMP_LEN);
+    if (len < 1) {
+	pj_ansi_strcpy(tmp.ptr, "<-error: uri too long->");
+	tmp.slen = pj_ansi_strlen(tmp.ptr);
+    } else
+	tmp.slen = len;
+
+    /* Save the local info. */
+    pj_strdup(dlg->pool, &dlg->local.info_str, &tmp);
+
     /* Calculate hash value of local tag. */
     dlg->local.tag_hval = pj_hash_calc(0, dlg->local.info->tag.ptr,
 				       dlg->local.info->tag.slen);
+
+    /* Print the local info. */
+    len = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR,
+			  dlg->local.info->uri, tmp.ptr, TMP_LEN);
+    if (len < 1) {
+	pj_ansi_strcpy(tmp.ptr, "<-error: uri too long->");
+	tmp.slen = pj_ansi_strlen(tmp.ptr);
+    } else
+	tmp.slen = len;
+
+    /* Save the local info. */
+    pj_strdup(dlg->pool, &dlg->remote.info_str, &tmp);
+
 
     /* Randomize local cseq */
@@ -1086,4 +1145,6 @@
 					     pjsip_tx_data *tdata)
 {
+    pj_status_t status;
+
     /* Sanity check. */
     PJ_ASSERT_RETURN(dlg && tsx && tdata && tdata->msg, PJ_EINVAL);
@@ -1108,5 +1169,12 @@
 #endif
 
-    return pjsip_tsx_send_msg(tsx, tdata);
+    /* Must acquire dialog first, to prevent deadlock */
+    pjsip_dlg_inc_lock(dlg);
+
+    status = pjsip_tsx_send_msg(tsx, tdata);
+
+    pjsip_dlg_dec_lock(dlg);
+
+    return status;
 }
 
Index: /pjproject/trunk/pjsip/src/pjsip/sip_endpoint.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsip/sip_endpoint.c	(revision 211)
+++ /pjproject/trunk/pjsip/src/pjsip/sip_endpoint.c	(revision 212)
@@ -325,4 +325,6 @@
 		     htype==PJSIP_H_SUPPORTED,
 		     PJ_EINVAL);
+
+    PJ_UNUSED_ARG(mod);
 
     /* Find the header. */
Index: /pjproject/trunk/pjsip/src/pjsip/sip_msg.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsip/sip_msg.c	(revision 211)
+++ /pjproject/trunk/pjsip/src/pjsip/sip_msg.c	(revision 212)
@@ -1747,4 +1747,16 @@
 }
 
+PJ_DEF(void*) pjsip_clone_text_data( pj_pool_t *pool, const void *data,
+				     unsigned len)
+{
+    char *newdata = "";
+
+    if (len) {
+	newdata = pj_pool_alloc(pool, len);
+	pj_memcpy(newdata, data, len);
+    }
+    return newdata;
+}
+
 PJ_DEF(pj_status_t) pjsip_msg_body_clone( pj_pool_t *pool,
 					  pjsip_msg_body *dst_body,
Index: /pjproject/trunk/pjsip/src/pjsua/main.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua/main.c	(revision 211)
+++ /pjproject/trunk/pjsip/src/pjsua/main.c	(revision 212)
@@ -114,6 +114,6 @@
 		    "%s (%.*s;expires=%d)",
 		    pjsip_get_status_text(pjsua.regc_last_code)->ptr,
-		    (int)info.server_uri.slen,
-		    info.server_uri.ptr,
+		    (int)info.client_uri.slen,
+		    info.client_uri.ptr,
 		    info.next_reg);
 
@@ -132,14 +132,14 @@
     puts("|                              |                          |                   |");
     puts("|  m  Make new call            |  i  Send IM              |  o  Send OPTIONS  |");
-    puts("|  a  Answer call              |  s  Subscribe presence   |  R  (Re-)register |");
-    puts("|  h  Hangup call              |  u  Unsubscribe presence |  r  Unregister    |");
-    puts("|  ]  Select next dialog       |  t  Toggle Online status |  d  Dump status   |");
+    puts("|  a  Answer call              |  s  Subscribe presence   | rr  (Re-)register |");
+    puts("|  h  Hangup call              |  u  Unsubscribe presence | ru  Unregister    |");
+    puts("|  ]  Select next dialog       |  t  ToGgle Online status |  d  Dump status   |");
     puts("|  [  Select previous dialog   |                          |                   |");
-    puts("+-----------------------------------------------------------------------------+");
-    puts("|      Conference Command                                                     |");
-    puts("| cl  List ports                                                              |");
-    puts("| cc  Connect port                                                            |");
-    puts("| cd  Disconnect port                                                         |");
-    puts("+-----------------------------------------------------------------------------+");
+    puts("|                              +--------------------------+-------------------+");
+    puts("|  H  Hold call                |     Conference Command   |                   |");
+    puts("|  v  re-inVite (release hold) | cl  List ports           |                   |");
+    puts("|  x  Xfer call                | cc  Connect port         |                   |");
+    puts("|                              | cd  Disconnect port      |                   |");
+    puts("+------------------------------+--------------------------+-------------------+");
     puts("|  q  QUIT                                                                    |");
     puts("+=============================================================================+");
@@ -284,5 +284,4 @@
     char menuin[10];
     char buf[128];
-    pjsip_inv_session *inv;
     struct input_result result;
 
@@ -306,7 +305,7 @@
 		    puts("You can't do that with make call!");
 		else
-		    pjsua_invite(pjsua.buddies[result.nb_result].uri.ptr, &inv);
+		    pjsua_invite(pjsua.buddies[result.nb_result].uri.ptr, NULL);
 	    } else if (result.uri_result)
-		pjsua_invite(result.uri_result, &inv);
+		pjsua_invite(result.uri_result, NULL);
 	    
 	    break;
@@ -354,41 +353,98 @@
 
 	    } else {
-		pj_status_t status;
-		pjsip_tx_data *tdata;
-
-		status = pjsip_inv_end_session(inv_session->inv, 
-					       PJSIP_SC_DECLINE, NULL, &tdata);
-		if (status != PJ_SUCCESS) {
-		    pjsua_perror(THIS_FILE, 
-				 "Failed to create end session message", 
-				 status);
-		    continue;
+		pjsua_inv_hangup(inv_session, PJSIP_SC_DECLINE);
+	    }
+	    break;
+
+	case ']':
+	case '[':
+	    /*
+	     * Cycle next/prev dialog.
+	     */
+	    if (menuin[0] == ']') {
+		inv_session = inv_session->next;
+		if (inv_session == &pjsua.inv_list)
+		    inv_session = pjsua.inv_list.next;
+
+	    } else {
+		inv_session = inv_session->prev;
+		if (inv_session == &pjsua.inv_list)
+		    inv_session = pjsua.inv_list.prev;
+	    }
+
+	    if (inv_session != &pjsua.inv_list) {
+		char url[PJSIP_MAX_URL_SIZE];
+		int len;
+
+		len = pjsip_uri_print(0, inv_session->inv->dlg->remote.info->uri,
+				      url, sizeof(url)-1);
+		if (len < 1) {
+		    pj_ansi_strcpy(url, "<uri is too long>");
+		} else {
+		    url[len] = '\0';
 		}
 
-		status = pjsip_inv_send_msg(inv_session->inv, tdata, NULL);
-		if (status != PJ_SUCCESS) {
-		    pjsua_perror(THIS_FILE, 
-				 "Failed to send end session message", 
-				 status);
-		    continue;
+		PJ_LOG(3,(THIS_FILE,"Current dialog: %s", url));
+
+	    } else {
+		PJ_LOG(3,(THIS_FILE,"No current dialog"));
+	    }
+	    break;
+
+	case 'H':
+	    /*
+	     * Hold call.
+	     */
+	    if (inv_session != &pjsua.inv_list) {
+		
+		pjsua_inv_set_hold(inv_session);
+
+	    } else {
+		PJ_LOG(3,(THIS_FILE, "No current call"));
+	    }
+	    break;
+
+	case 'v':
+	    /*
+	     * Send re-INVITE (to release hold, etc).
+	     */
+	    if (inv_session != &pjsua.inv_list) {
+		
+		pjsua_inv_reinvite(inv_session);
+
+	    } else {
+		PJ_LOG(3,(THIS_FILE, "No current call"));
+	    }
+	    break;
+
+	case 'x':
+	    /*
+	     * Transfer call.
+	     */
+	    if (inv_session == &pjsua.inv_list) {
+		
+		PJ_LOG(3,(THIS_FILE, "No current call"));
+
+	    } else {
+		ui_input_url("Transfer to URL", buf, sizeof(buf), &result);
+		if (result.nb_result != NO_NB) {
+		    if (result.nb_result == -1) 
+			puts("You can't do that with transfer call!");
+		    else
+			pjsua_inv_xfer_call( inv_session,
+					     pjsua.buddies[result.nb_result].uri.ptr);
+
+		} else if (result.uri_result) {
+		    pjsua_inv_xfer_call( inv_session, result.uri_result);
 		}
 	    }
-	    break;
-
-	case ']':
-	    inv_session = inv_session->next;
-	    if (inv_session == &pjsua.inv_list)
-		inv_session = pjsua.inv_list.next;
-	    break;
-
-	case '[':
-	    inv_session = inv_session->prev;
-	    if (inv_session == &pjsua.inv_list)
-		inv_session = pjsua.inv_list.prev;
 	    break;
 
 	case 's':
 	case 'u':
-	    ui_input_url("Subscribe presence of", buf, sizeof(buf), &result);
+	    /*
+	     * Subscribe/unsubscribe presence.
+	     */
+	    ui_input_url("(un)Subscribe presence of", buf, sizeof(buf), &result);
 	    if (result.nb_result != NO_NB) {
 		if (result.nb_result == -1) {
@@ -403,17 +459,27 @@
 
 	    } else if (result.uri_result) {
-		puts("Sorry, can only subscribe to buddy's presence, not arbitrary URL (for now)");
-	    }
-
-	    break;
-
-	case 'R':
-	    pjsua_regc_update(PJ_TRUE);
+		puts("Sorry, can only subscribe to buddy's presence, "
+		     "not arbitrary URL (for now)");
+	    }
+
+	    break;
+
+	case 'r':
+	    switch (menuin[1]) {
+	    case 'r':
+		/*
+		 * Re-Register.
+		 */
+		pjsua_regc_update(PJ_TRUE);
+		break;
+	    case 'u':
+		/*
+		 * Unregister
+		 */
+		pjsua_regc_update(PJ_FALSE);
+		break;
+	    }
 	    break;
 	    
-	case 'r':
-	    pjsua_regc_update(PJ_FALSE);
-	    break;
-
 	case 't':
 	    pjsua.online_status = !pjsua.online_status;
@@ -431,14 +497,29 @@
 		    char src_port[10], dst_port[10];
 		    pj_status_t status;
-
-		    if (!simple_input("Connect src port #:", src_port, sizeof(src_port)))
+		    const char *src_title, *dst_title;
+
+		    conf_list();
+
+		    src_title = (menuin[1]=='c'?
+				 "Connect src port #":
+				 "Disconnect src port #");
+		    dst_title = (menuin[1]=='c'?
+				 "To dst port #":
+				 "From dst port #");
+
+		    if (!simple_input(src_title, src_port, sizeof(src_port)))
 			break;
-		    if (!simple_input("To dst port #:", dst_port, sizeof(dst_port)))
+
+		    if (!simple_input(dst_title, dst_port, sizeof(dst_port)))
 			break;
 
 		    if (menuin[1]=='c') {
-			status = pjmedia_conf_connect_port(pjsua.mconf, atoi(src_port), atoi(dst_port));
+			status = pjmedia_conf_connect_port(pjsua.mconf, 
+							   atoi(src_port), 
+							   atoi(dst_port));
 		    } else {
-			status = pjmedia_conf_disconnect_port(pjsua.mconf, atoi(src_port), atoi(dst_port));
+			status = pjmedia_conf_disconnect_port(pjsua.mconf, 
+							      atoi(src_port), 
+							      atoi(dst_port));
 		    }
 		    if (status == PJ_SUCCESS) {
Index: /pjproject/trunk/pjsip/src/pjsua/pjsua.h
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua/pjsua.h	(revision 211)
+++ /pjproject/trunk/pjsip/src/pjsua/pjsua.h	(revision 212)
@@ -69,4 +69,6 @@
     unsigned		 conf_slot; /**< Slot # in conference bridge.	    */
     unsigned		 call_slot; /**< RTP media index in med_sock_use[]  */
+    pjsip_evsub		*xfer_sub;  /**< Xfer server subscription, if this
+					 call was triggered by xfer.	    */
 };
 
@@ -255,5 +257,5 @@
  */
 pj_status_t pjsua_invite(const char *cstr_dest_uri,
-			 pjsip_inv_session **p_inv);
+			 struct pjsua_inv_data **p_inv_data);
 
 
@@ -262,4 +264,29 @@
  */
 pj_bool_t pjsua_inv_on_incoming(pjsip_rx_data *rdata);
+
+
+/**
+ * Hangup call.
+ */
+void pjsua_inv_hangup(struct pjsua_inv_data *inv_session, int code);
+
+
+/**
+ * Put call on-hold.
+ */
+void pjsua_inv_set_hold(struct pjsua_inv_data *inv_session);
+
+
+/**
+ * Send re-INVITE (to release hold).
+ */
+void pjsua_inv_reinvite(struct pjsua_inv_data *inv_session);
+
+
+/**
+ * Transfer call.
+ */
+void pjsua_inv_xfer_call(struct pjsua_inv_data *inv_session,
+			 const char *dest);
 
 
@@ -284,4 +311,17 @@
 void pjsua_inv_on_media_update(pjsip_inv_session *inv, pj_status_t status);
 
+/**
+ * Callback called when invite session received new offer.
+ */
+void pjsua_inv_on_rx_offer( pjsip_inv_session *inv,
+			    const pjmedia_sdp_session *offer);
+
+/**
+ * Callback to receive transaction state inside invite session or dialog
+ * (e.g. REFER, MESSAGE).
+ */
+void pjsua_inv_on_tsx_state_changed(pjsip_inv_session *inv,
+				    pjsip_transaction *tsx,
+				    pjsip_event *e);
 
 /**
Index: /pjproject/trunk/pjsip/src/pjsua/pjsua_core.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua/pjsua_core.c	(revision 211)
+++ /pjproject/trunk/pjsip/src/pjsua/pjsua_core.c	(revision 212)
@@ -142,5 +142,5 @@
 	RTP_START_PORT = 4000,
 	RTP_RANDOM_START = 2,
-	RTP_RETRY = 10 
+	RTP_RETRY = 20 
     };
     enum {
@@ -393,4 +393,7 @@
 	inv_cb.on_new_session = &pjsua_inv_on_new_session;
 	inv_cb.on_media_update = &pjsua_inv_on_media_update;
+	inv_cb.on_rx_offer = &pjsua_inv_on_rx_offer;
+	inv_cb.on_tsx_state_changed = &pjsua_inv_on_tsx_state_changed;
+
 
 	/* Initialize invite session module: */
@@ -480,4 +483,7 @@
     pjsip_pres_init_module( pjsua.endpt, pjsip_evsub_instance());
 
+    /* Init xfer/REFER module */
+
+    pjsip_xfer_init_module( pjsua.endpt );
 
     /* Init pjsua presence handler: */
@@ -752,4 +758,8 @@
     busy_sleep(1000);
 
+    /* Destroy conference bridge. */
+    if (pjsua.mconf)
+	pjmedia_conf_destroy(pjsua.mconf);
+
     /* Shutdown pjmedia-codec: */
     pjmedia_codec_deinit();
Index: /pjproject/trunk/pjsip/src/pjsua/pjsua_inv.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua/pjsua_inv.c	(revision 211)
+++ /pjproject/trunk/pjsip/src/pjsua/pjsua_inv.c	(revision 212)
@@ -34,5 +34,5 @@
  */
 pj_status_t pjsua_invite(const char *cstr_dest_uri,
-			 pjsip_inv_session **p_inv)
+			 struct pjsua_inv_data **p_inv_data)
 {
     pj_str_t dest_uri;
@@ -137,6 +137,6 @@
 
     /* Done. */
-
-    *p_inv = inv;
+    if (p_inv_data)
+	*p_inv_data = inv_data;
 
     return PJ_SUCCESS;
@@ -159,125 +159,143 @@
     pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata);
     pjsip_msg *msg = rdata->msg_info.msg;
+    pjsip_tx_data *response = NULL;
+    unsigned options = 0;
+    pjsip_inv_session *inv;
+    struct pjsua_inv_data *inv_data;
+    pjmedia_sdp_session *answer;
+    int med_sk_index;
+    pj_status_t status;
+
+    /* Don't want to handle anything but INVITE */
+    if (msg->line.req.method.id != PJSIP_INVITE_METHOD)
+	return PJ_FALSE;
+
+    /* Don't want to handle anything that's already associated with
+     * existing dialog or transaction.
+     */
+    if (dlg || tsx)
+	return PJ_FALSE;
+
+
+    /* Verify that we can handle the request. */
+    status = pjsip_inv_verify_request(rdata, &options, NULL, NULL,
+				      pjsua.endpt, &response);
+    if (status != PJ_SUCCESS) {
+
+	/*
+	 * No we can't handle the incoming INVITE request.
+	 */
+
+	if (response) {
+	    pjsip_response_addr res_addr;
+
+	    pjsip_get_response_addr(response->pool, rdata, &res_addr);
+	    pjsip_endpt_send_response(pjsua.endpt, &res_addr, response, 
+				      NULL, NULL);
+
+	} else {
+
+	    /* Respond with 500 (Internal Server Error) */
+	    pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL,
+					  NULL, NULL);
+	}
+
+	return PJ_TRUE;
+    } 
+
 
     /*
-     * Handle incoming INVITE outside dialog.
-     */
-    if (dlg == NULL && tsx == NULL &&
-	msg->line.req.method.id == PJSIP_INVITE_METHOD)
-    {
-	pj_status_t status;
-	pjsip_tx_data *response = NULL;
-	unsigned options = 0;
-
-	/* Verify that we can handle the request. */
-	status = pjsip_inv_verify_request(rdata, &options, NULL, NULL,
-					  pjsua.endpt, &response);
-	if (status != PJ_SUCCESS) {
-
-	    /*
-	     * No we can't handle the incoming INVITE request.
-	     */
-
-	    if (response) {
-		pjsip_response_addr res_addr;
-
-		pjsip_get_response_addr(response->pool, rdata, &res_addr);
-		pjsip_endpt_send_response(pjsua.endpt, &res_addr, response, 
-					  NULL, NULL);
-
-	    } else {
-
-		/* Respond with 500 (Internal Server Error) */
-		pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL,
-					      NULL, NULL);
-	    }
-
-	} else {
-	    /*
-	     * Yes we can handle the incoming INVITE request.
-	     */
-	    pjsip_inv_session *inv;
-	    struct pjsua_inv_data *inv_data;
-	    pjmedia_sdp_session *answer;
-	    int med_sk_index;
-
-
-	    /* Find free socket. */
-	    for (med_sk_index=0; med_sk_index<PJSUA_MAX_CALLS; ++med_sk_index) {
-		if (!pjsua.med_sock_use[med_sk_index])
-		    break;
-	    }
-
-	    if (med_sk_index == PJSUA_MAX_CALLS) {
-		PJ_LOG(3,(THIS_FILE, "Error: too many calls!"));
-		return PJ_TRUE;
-	    }
-
-
-	    pjsua.med_sock_use[med_sk_index] = 1;
-
-	    /* Get media capability from media endpoint: */
-
-	    status = pjmedia_endpt_create_sdp( pjsua.med_endpt, rdata->tp_info.pool,
-					       1, &pjsua.med_sock_info[med_sk_index], 
-					       &answer );
-	    if (status != PJ_SUCCESS) {
-
-		pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL,
-					      NULL, NULL);
-		pjsua.med_sock_use[med_sk_index] = 0;
-		return PJ_TRUE;
-	    }
-
-	    /* Create dialog: */
-
-	    status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
-					   &pjsua.contact_uri, &dlg);
-	    if (status != PJ_SUCCESS) {
-		pjsua.med_sock_use[med_sk_index] = 0;
-		return PJ_TRUE;
-	    }
-
-
-	    /* Create invite session: */
-
-	    status = pjsip_inv_create_uas( dlg, rdata, answer, 0, &inv);
-	    if (status != PJ_SUCCESS) {
-
-		status = pjsip_dlg_create_response( dlg, rdata, 500, NULL,
-						    &response);
-		if (status == PJ_SUCCESS)
-		    status = pjsip_dlg_send_response(dlg, 
-						     pjsip_rdata_get_tsx(rdata),
-						     response);
-		pjsua.med_sock_use[med_sk_index] = 0;
-		return PJ_TRUE;
-
-	    }
-
-
-	    /* Create and attach pjsua data to the dialog: */
-
-	    inv_data = pj_pool_zalloc(dlg->pool, sizeof(struct pjsua_inv_data));
-	    inv_data->inv = inv;
-	    inv_data->call_slot = inv_data->call_slot = med_sk_index;
-	    dlg->mod_data[pjsua.mod.id] = inv_data;
-	    inv->mod_data[pjsua.mod.id] = inv_data;
-
-	    pj_list_push_back(&pjsua.inv_list, inv_data);
-
-
-	    /* Answer with 100 (using the dialog, not invite): */
-
-	    status = pjsip_dlg_create_response(dlg, rdata, 100, NULL, &response);
-	    if (status == PJ_SUCCESS)
-		status = pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), response);
-	}
-
-	/* This INVITE request has been handled. */
+     * Yes we can handle the incoming INVITE request.
+     */
+
+    /* Find free call slot. */
+    for (med_sk_index=0; med_sk_index<PJSUA_MAX_CALLS; ++med_sk_index) {
+	if (!pjsua.med_sock_use[med_sk_index])
+	    break;
+    }
+
+    if (med_sk_index == PJSUA_MAX_CALLS) {
+	pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 
+				      PJSIP_SC_BUSY_HERE, NULL,
+				      NULL, NULL);
 	return PJ_TRUE;
     }
 
-    return PJ_FALSE;
+
+    pjsua.med_sock_use[med_sk_index] = 1;
+
+    /* Get media capability from media endpoint: */
+
+    status = pjmedia_endpt_create_sdp( pjsua.med_endpt, rdata->tp_info.pool,
+				       1, &pjsua.med_sock_info[med_sk_index], 
+				       &answer );
+    if (status != PJ_SUCCESS) {
+	pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL,
+				      NULL, NULL);
+
+	/* Free call socket. */
+	pjsua.med_sock_use[med_sk_index] = 0;
+	return PJ_TRUE;
+    }
+
+    /* Create dialog: */
+
+    status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
+				   &pjsua.contact_uri, &dlg);
+    if (status != PJ_SUCCESS) {
+	pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL,
+				      NULL, NULL);
+
+	/* Free call socket. */
+	pjsua.med_sock_use[med_sk_index] = 0;
+	return PJ_TRUE;
+    }
+
+
+    /* Create invite session: */
+
+    status = pjsip_inv_create_uas( dlg, rdata, answer, 0, &inv);
+    if (status != PJ_SUCCESS) {
+
+	pjsip_dlg_respond(dlg, rdata, 500, NULL);
+
+	/* Free call socket. */
+	pjsua.med_sock_use[med_sk_index] = 0;
+
+	// TODO: Need to delete dialog
+	return PJ_TRUE;
+    }
+
+
+    /* Create and attach pjsua data to the dialog: */
+
+    inv_data = pj_pool_zalloc(dlg->pool, sizeof(struct pjsua_inv_data));
+    inv_data->inv = inv;
+    inv_data->call_slot = inv_data->call_slot = med_sk_index;
+    dlg->mod_data[pjsua.mod.id] = inv_data;
+    inv->mod_data[pjsua.mod.id] = inv_data;
+
+    pj_list_push_back(&pjsua.inv_list, inv_data);
+
+
+    /* Answer with 100 (using the dialog, not invite): */
+
+    status = pjsip_dlg_create_response(dlg, rdata, 100, NULL, &response);
+    if (status != PJ_SUCCESS) {
+	
+	pjsip_dlg_respond(dlg, rdata, 500, NULL);
+
+	/* Free call socket. */
+	pjsua.med_sock_use[med_sk_index] = 0;
+
+	// TODO: Need to delete dialog
+
+    } else {
+	status = pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), 
+					 response);
+    }
+
+    /* This INVITE request has been handled. */
+    return PJ_TRUE;
 }
 
@@ -289,10 +307,64 @@
 void pjsua_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e)
 {
+    struct pjsua_inv_data *inv_data;
+
+    inv_data = inv->dlg->mod_data[pjsua.mod.id];
+
+    /* If this is an outgoing INVITE that was created because of
+     * REFER/transfer, send NOTIFY to transferer.
+     */
+    if (inv_data && inv_data->xfer_sub && e->type==PJSIP_EVENT_TSX_STATE) 
+    {
+	int st_code = -1;
+	pjsip_evsub_state ev_state = PJSIP_EVSUB_STATE_ACTIVE;
+	
+
+	switch (inv->state) {
+	case PJSIP_INV_STATE_NULL:
+	case PJSIP_INV_STATE_CALLING:
+	    /* Do nothing */
+	    break;
+
+	case PJSIP_INV_STATE_EARLY:
+	case PJSIP_INV_STATE_CONNECTING:
+	    st_code = e->body.tsx_state.tsx->status_code;
+	    ev_state = PJSIP_EVSUB_STATE_ACTIVE;
+	    break;
+
+	case PJSIP_INV_STATE_CONFIRMED:
+	    /* When state is confirmed, send the final 200/OK and terminate
+	     * subscription.
+	     */
+	    st_code = e->body.tsx_state.tsx->status_code;
+	    ev_state = PJSIP_EVSUB_STATE_TERMINATED;
+	    break;
+
+	case PJSIP_INV_STATE_DISCONNECTED:
+	    st_code = e->body.tsx_state.tsx->status_code;
+	    ev_state = PJSIP_EVSUB_STATE_TERMINATED;
+	    break;
+	}
+
+	if (st_code != -1) {
+	    pjsip_tx_data *tdata;
+	    pj_status_t status;
+
+	    status = pjsip_xfer_notify( inv_data->xfer_sub,
+					ev_state, st_code,
+					NULL, &tdata);
+	    if (status != PJ_SUCCESS) {
+		pjsua_perror(THIS_FILE, "Unable to create NOTIFY", status);
+	    } else {
+		status = pjsip_xfer_send_request(inv_data->xfer_sub, tdata);
+		if (status != PJ_SUCCESS) {
+		    pjsua_perror(THIS_FILE, "Unable to send NOTIFY", status);
+		}
+	    }
+	}
+    }
+
 
     /* Destroy media session when invite session is disconnected. */
     if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
-	struct pjsua_inv_data *inv_data;
-
-	inv_data = inv->dlg->mod_data[pjsua.mod.id];
 
 	pj_assert(inv_data != NULL);
@@ -319,4 +391,156 @@
 
 /*
+ * Callback called by event framework when the xfer subscription state
+ * has changed.
+ */
+static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event)
+{
+    
+    PJ_UNUSED_ARG(event);
+
+    /*
+     * We're only interested when subscription is terminated, to 
+     * clear the xfer_sub member of the inv_data.
+     */
+    if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+	struct pjsua_inv_data *inv_data;
+
+	inv_data = pjsip_evsub_get_mod_data(sub, pjsua.mod.id);
+	if (!inv_data)
+	    return;
+
+	pjsip_evsub_set_mod_data(sub, pjsua.mod.id, NULL);
+	inv_data->xfer_sub = NULL;
+
+	PJ_LOG(3,(THIS_FILE, "Xfer subscription terminated"));
+    }
+}
+
+
+/*
+ * Follow transfer (REFER) request.
+ */
+static void on_call_transfered( pjsip_inv_session *inv,
+			        pjsip_rx_data *rdata )
+{
+    pj_status_t status;
+    pjsip_tx_data *tdata;
+    struct pjsua_inv_data *inv_data;
+    const pj_str_t str_refer_to = { "Refer-To", 8};
+    pjsip_generic_string_hdr *refer_to;
+    char *uri;
+    struct pjsip_evsub_user xfer_cb;
+    pjsip_evsub *sub;
+
+    /* Find the Refer-To header */
+    refer_to = (pjsip_generic_string_hdr*)
+	pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_refer_to, NULL);
+
+    if (refer_to == NULL) {
+	/* Invalid Request.
+	 * No Refer-To header!
+	 */
+	PJ_LOG(4,(THIS_FILE, "Received REFER without Refer-To header!"));
+	pjsip_dlg_respond( inv->dlg, rdata, 400, NULL);
+	return;
+    }
+
+    PJ_LOG(3,(THIS_FILE, "Call to %.*s is being transfered to %.*s",
+	      (int)inv->dlg->remote.info_str.slen,
+	      inv->dlg->remote.info_str.ptr,
+	      (int)refer_to->hvalue.slen, 
+	      refer_to->hvalue.ptr));
+
+    /* Init callback */
+    pj_memset(&xfer_cb, 0, sizeof(xfer_cb));
+    xfer_cb.on_evsub_state = &xfer_on_evsub_state;
+
+    /* Create transferee event subscription */
+    status = pjsip_xfer_create_uas( inv->dlg, &xfer_cb, rdata, &sub);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create xfer uas", status);
+	pjsip_dlg_respond( inv->dlg, rdata, 500, NULL);
+	return;
+    }
+
+    /* Accept the REFER request, send 200 (OK). */
+    pjsip_xfer_accept(sub, rdata, 200, NULL);
+
+    /* Create initial NOTIFY request */
+    status = pjsip_xfer_notify( sub, PJSIP_EVSUB_STATE_ACTIVE,
+				100, NULL, &tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create NOTIFY to REFER", status);
+	return;
+    }
+
+    /* Send initial NOTIFY request */
+    status = pjsip_xfer_send_request( sub, tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to send NOTIFY to REFER", status);
+	return;
+    }
+
+    /* We're cheating here.
+     * We need to get a null terminated string from a pj_str_t.
+     * So grab the pointer from the hvalue and NULL terminate it, knowing
+     * that the NULL position will be occupied by a newline. 
+     */
+    uri = refer_to->hvalue.ptr;
+    uri[refer_to->hvalue.slen] = '\0';
+
+    /* Now make the outgoing call. */
+    status = pjsua_invite(uri, &inv_data);
+    if (status != PJ_SUCCESS) {
+
+	/* Notify xferer about the error */
+	status = pjsip_xfer_notify(sub, PJSIP_EVSUB_STATE_TERMINATED,
+				   500, NULL, &tdata);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Unable to create NOTIFY to REFER", 
+			  status);
+	    return;
+	}
+	status = pjsip_xfer_send_request(sub, tdata);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Unable to send NOTIFY to REFER", 
+			  status);
+	    return;
+	}
+	return;
+    }
+
+    /* Put the server subscription in inv_data.
+     * Subsequent state changed in pjsua_inv_on_state_changed() will be
+     * reported back to the server subscription.
+     */
+    inv_data->xfer_sub = sub;
+
+    /* Put the invite_data in the subscription. */
+    pjsip_evsub_set_mod_data(sub, pjsua.mod.id, inv_data);
+}
+
+
+/*
+ * This callback is called when transaction state has changed in INVITE
+ * session. We use this to trap incoming REFER request.
+ */
+void pjsua_inv_on_tsx_state_changed(pjsip_inv_session *inv,
+				    pjsip_transaction *tsx,
+				    pjsip_event *e)
+{
+    if (tsx->role==PJSIP_ROLE_UAS &&
+	tsx->state==PJSIP_TSX_STATE_TRYING &&
+	pjsip_method_cmp(&tsx->method, &pjsip_refer_method)==0)
+    {
+	/*
+	 * Incoming REFER request.
+	 */
+	on_call_transfered(inv, e->body.tsx_state.src.rdata);
+    }
+}
+
+
+/*
  * This callback is called by invite session framework when UAC session
  * has forked.
@@ -328,4 +552,107 @@
 
     PJ_TODO(HANDLE_FORKED_DIALOG);
+}
+
+
+/*
+ * Create inactive SDP for call hold.
+ */
+static pj_status_t create_inactive_sdp(struct pjsua_inv_data *inv_session,
+				       pjmedia_sdp_session **p_answer)
+{
+    pj_status_t status;
+    pjmedia_sdp_conn *conn;
+    pjmedia_sdp_attr *attr;
+    pjmedia_sdp_session *sdp;
+
+    /* Create new offer */
+    status = pjmedia_endpt_create_sdp(pjsua.med_endpt, pjsua.pool, 1,
+				      &pjsua.med_sock_info[inv_session->call_slot],
+				      &sdp);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create local SDP", status);
+	return status;
+    }
+
+    /* Get SDP media connection line */
+    conn = sdp->media[0]->conn;
+    if (!conn)
+	conn = sdp->conn;
+
+    /* Modify address */
+    conn->addr = pj_str("0.0.0.0");
+
+    /* Remove existing directions attributes */
+    pjmedia_sdp_media_remove_all_attr(sdp->media[0], "sendrecv");
+    pjmedia_sdp_media_remove_all_attr(sdp->media[0], "sendonly");
+    pjmedia_sdp_media_remove_all_attr(sdp->media[0], "recvonly");
+    pjmedia_sdp_media_remove_all_attr(sdp->media[0], "inactive");
+
+    /* Add inactive attribute */
+    attr = pjmedia_sdp_attr_create(pjsua.pool, "inactive", NULL);
+    pjmedia_sdp_media_add_attr(sdp->media[0], attr);
+
+    *p_answer = sdp;
+
+    return status;
+}
+
+/*
+ * Called when session received new offer.
+ */
+void pjsua_inv_on_rx_offer( pjsip_inv_session *inv,
+			    const pjmedia_sdp_session *offer)
+{
+    struct pjsua_inv_data *inv_data;
+    pjmedia_sdp_conn *conn;
+    pjmedia_sdp_session *answer;
+    pj_bool_t is_remote_active;
+    pj_status_t status;
+
+    inv_data = inv->dlg->mod_data[pjsua.mod.id];
+
+    /*
+     * See if remote is offering active media (i.e. not on-hold)
+     */
+    is_remote_active = PJ_TRUE;
+
+    conn = offer->media[0]->conn;
+    if (!conn)
+	conn = offer->conn;
+
+    if (pj_strcmp2(&conn->addr, "0.0.0.0")==0 ||
+	pj_strcmp2(&conn->addr, "0")==0)
+    {
+	is_remote_active = PJ_FALSE;
+
+    } 
+    else if (pjmedia_sdp_media_find_attr2(offer->media[0], "inactive", NULL))
+    {
+	is_remote_active = PJ_FALSE;
+    }
+
+    PJ_LOG(4,(THIS_FILE, "Received SDP offer, remote media is %s",
+	      (is_remote_active ? "active" : "inactive")));
+
+    /* Supply candidate answer */
+    if (is_remote_active) {
+	status = pjmedia_endpt_create_sdp( pjsua.med_endpt, inv->pool, 1,
+					   &pjsua.med_sock_info[inv_data->call_slot],
+					   &answer);
+    } else {
+	status = create_inactive_sdp( inv_data, &answer );
+    }
+
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create local SDP", status);
+	return;
+    }
+
+    status = pjsip_inv_set_sdp_answer(inv, answer);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to set answer", status);
+	return;
+    }
+
 }
 
@@ -341,4 +668,7 @@
     const pjmedia_sdp_session *local_sdp;
     const pjmedia_sdp_session *remote_sdp;
+    pjmedia_port *media_port;
+    pj_str_t port_name;
+    char tmp[PJSIP_MAX_URL_SIZE];
 
     if (status != PJ_SUCCESS) {
@@ -381,46 +711,238 @@
      * The media session is active immediately.
      */
-
-    if (!pjsua.null_audio) {
-	pjmedia_port *media_port;
-	pj_str_t port_name;
-	char tmp[PJSIP_MAX_URL_SIZE];
-
-	status = pjmedia_session_create( pjsua.med_endpt, 1, 
-					 &pjsua.med_sock_info[inv_data->call_slot],
-					 local_sdp, remote_sdp, 
-					 &inv_data->session );
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "Unable to create media session", 
-			 status);
-	    return;
+    if (pjsua.null_audio)
+	return;
+    
+    status = pjmedia_session_create( pjsua.med_endpt, 1, 
+				     &pjsua.med_sock_info[inv_data->call_slot],
+				     local_sdp, remote_sdp, 
+				     &inv_data->session );
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create media session", 
+		     status);
+	return;
+    }
+
+
+    /* Get the port interface of the first stream in the session.
+     * We need the port interface to add to the conference bridge.
+     */
+    pjmedia_session_get_port(inv_data->session, 0, &media_port);
+
+
+    /*
+     * Add the call to conference bridge.
+     */
+    port_name.ptr = tmp;
+    port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
+				     inv_data->inv->dlg->remote.info->uri,
+				     tmp, sizeof(tmp));
+    if (port_name.slen < 1) {
+	port_name = pj_str("call");
+    }
+    status = pjmedia_conf_add_port( pjsua.mconf, inv->pool,
+				    media_port, 
+				    &port_name,
+				    &inv_data->conf_slot);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create conference slot", 
+		     status);
+	pjmedia_session_destroy(inv_data->session);
+	inv_data->session = NULL;
+	return;
+    }
+
+    /* Connect new call to the sound device port (port zero) in the
+     * main conference bridge.
+     */
+    pjmedia_conf_connect_port( pjsua.mconf, 0, inv_data->conf_slot);
+    pjmedia_conf_connect_port( pjsua.mconf, inv_data->conf_slot, 0);
+
+    /* Done. */
+    {
+	struct pjmedia_session_info sess_info;
+	char info[80];
+	int info_len = 0;
+	unsigned i;
+
+	pjmedia_session_get_info(inv_data->session, &sess_info);
+	for (i=0; i<sess_info.stream_cnt; ++i) {
+	    int len;
+	    const char *dir;
+	    pjmedia_stream_info *strm_info = &sess_info.stream_info[i];
+
+	    switch (strm_info->dir) {
+	    case PJMEDIA_DIR_NONE:
+		dir = "inactive";
+		break;
+	    case PJMEDIA_DIR_ENCODING:
+		dir = "sendonly";
+		break;
+	    case PJMEDIA_DIR_DECODING:
+		dir = "recvonly";
+		break;
+	    case PJMEDIA_DIR_ENCODING_DECODING:
+		dir = "sendrecv";
+		break;
+	    default:
+		dir = "unknown";
+		break;
+	    }
+	    len = pj_ansi_sprintf( info+info_len,
+				   ", stream #%d: %.*s (%s)", i,
+				   (int)strm_info->fmt.encoding_name.slen,
+				   (int)strm_info->fmt.encoding_name.ptr,
+				   dir);
+	    if (len > 0)
+		info_len += len;
 	}
-
-	pjmedia_session_get_port(inv_data->session, 0, &media_port);
-
-	port_name.ptr = tmp;
-	port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
-					 inv_data->inv->dlg->remote.info->uri,
-					 tmp, sizeof(tmp));
-	if (port_name.slen < 1) {
-	    port_name = pj_str("call");
-	}
-	status = pjmedia_conf_add_port( pjsua.mconf, inv->pool,
-					media_port, 
-					&port_name,
-					&inv_data->conf_slot);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "Unable to create conference slot", 
-			 status);
-	    pjmedia_session_destroy(inv_data->session);
-	    inv_data->session = NULL;
-	    return;
-	}
-
-	pjmedia_conf_connect_port( pjsua.mconf, 0, inv_data->conf_slot);
-	pjmedia_conf_connect_port( pjsua.mconf, inv_data->conf_slot, 0);
-
-	PJ_LOG(3,(THIS_FILE,"Media has been started successfully"));
-    }
+	PJ_LOG(3,(THIS_FILE,"Media started%s", info));
+    }
+}
+
+
+/*
+ * Hangup call.
+ */
+void pjsua_inv_hangup(struct pjsua_inv_data *inv_session, int code)
+{
+    pj_status_t status;
+    pjsip_tx_data *tdata;
+
+    status = pjsip_inv_end_session(inv_session->inv, 
+				   code, NULL, &tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, 
+		     "Failed to create end session message", 
+		     status);
+	return;
+    }
+
+    status = pjsip_inv_send_msg(inv_session->inv, tdata, NULL);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, 
+		     "Failed to send end session message", 
+		     status);
+	return;
+    }
+}
+
+
+/*
+ * Put call on-Hold.
+ */
+void pjsua_inv_set_hold(struct pjsua_inv_data *inv_session)
+{
+    pjmedia_sdp_session *sdp;
+    pjsip_inv_session *inv = inv_session->inv;
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+
+    if (inv->state != PJSIP_INV_STATE_CONFIRMED) {
+	PJ_LOG(3,(THIS_FILE, "Can not hold call that is not confirmed"));
+	return;
+    }
+
+    status = create_inactive_sdp(inv_session, &sdp);
+    if (status != PJ_SUCCESS)
+	return;
+
+    /* Send re-INVITE with new offer */
+    status = pjsip_inv_reinvite( inv_session->inv, NULL, sdp, &tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status);
+	return;
+    }
+
+    status = pjsip_inv_send_msg( inv_session->inv, tdata, NULL);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status);
+	return;
+    }
+}
+
+
+/*
+ * re-INVITE.
+ */
+void pjsua_inv_reinvite(struct pjsua_inv_data *inv_session)
+{
+    pjmedia_sdp_session *sdp;
+    pjsip_tx_data *tdata;
+    pjsip_inv_session *inv = inv_session->inv;
+    pj_status_t status;
+
+
+    if (inv->state != PJSIP_INV_STATE_CONFIRMED) {
+	PJ_LOG(3,(THIS_FILE, "Can not re-INVITE call that is not confirmed"));
+	return;
+    }
+
+    /* Create SDP */
+    status = pjmedia_endpt_create_sdp( pjsua.med_endpt, inv->pool, 1,
+				       &pjsua.med_sock_info[inv_session->call_slot],
+				       &sdp);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to get SDP from media endpoint", status);
+	return;
+    }
+
+    /* Send re-INVITE with new offer */
+    status = pjsip_inv_reinvite( inv_session->inv, NULL, sdp, &tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status);
+	return;
+    }
+
+    status = pjsip_inv_send_msg( inv_session->inv, tdata, NULL);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status);
+	return;
+    }
+}
+
+
+/*
+ * Transfer call.
+ */
+void pjsua_inv_xfer_call(struct pjsua_inv_data *inv_session,
+			 const char *dest)
+{
+    pjsip_evsub *sub;
+    pjsip_tx_data *tdata;
+    pj_str_t tmp;
+    pj_status_t status;
+ 
+    
+    /* Create xfer client subscription.
+     * We're not interested in knowing the transfer result, so we
+     * put NULL as the callback.
+     */
+    status = pjsip_xfer_create_uac(inv_session->inv->dlg, NULL, &sub);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create xfer", status);
+	return;
+    }
+
+    /*
+     * Create REFER request.
+     */
+    status = pjsip_xfer_initiate(sub, pj_cstr(&tmp, dest), &tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create REFER request", status);
+	return;
+    }
+
+    /* Send. */
+    status = pjsip_xfer_send_request(sub, tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to send REFER request", status);
+	return;
+    }
+
+    /* For simplicity (that's what this program is intended to be!), 
+     * leave the original invite session as it is. More advanced application
+     * may want to hold the INVITE, or terminate the invite, or whatever.
+     */
 }
 
@@ -446,2 +968,3 @@
 }
 
+
