Ticket 5: Support for SIP UPDATE (RFC 3311) and fix the offer/answer negotiation

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@1469 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/build.symbian/pjsip_uaU.def b/build.symbian/pjsip_uaU.def
index b158bf9..61f9a9d 100644
--- a/build.symbian/pjsip_uaU.def
+++ b/build.symbian/pjsip_uaU.def
@@ -1,49 +1,56 @@
 EXPORTS
 	pjsip_100rel_attach                      @ 1 NONAME
-	pjsip_100rel_init_module                 @ 2 NONAME
-	pjsip_100rel_tx_response                 @ 3 NONAME
-	pjsip_create_sdp_body                    @ 4 NONAME
-	pjsip_dlg_get_inv_session                @ 5 NONAME
-	pjsip_get_refer_method                   @ 6 NONAME
-	pjsip_inv_answer                         @ 7 NONAME
-	pjsip_inv_create_uac                     @ 8 NONAME
-	pjsip_inv_create_uas                     @ 9 NONAME
-	pjsip_inv_end_session                    @ 10 NONAME
-	pjsip_inv_initial_answer                 @ 11 NONAME
-	pjsip_inv_invite                         @ 12 NONAME
-	pjsip_inv_reinvite                       @ 13 NONAME
-	pjsip_inv_send_msg                       @ 14 NONAME
-	pjsip_inv_set_sdp_answer                 @ 15 NONAME
-	pjsip_inv_state_name                     @ 16 NONAME
-	pjsip_inv_terminate                      @ 17 NONAME
-	pjsip_inv_update                         @ 18 NONAME
-	pjsip_inv_usage_init                     @ 19 NONAME
-	pjsip_inv_usage_instance                 @ 20 NONAME
-	pjsip_inv_verify_request                 @ 21 NONAME
-	pjsip_refer_method                       @ 22 NONAME
-	pjsip_regc_add_headers                   @ 23 NONAME
-	pjsip_regc_create                        @ 24 NONAME
-	pjsip_regc_destroy                       @ 25 NONAME
-	pjsip_regc_get_info                      @ 26 NONAME
-	pjsip_regc_get_pool                      @ 27 NONAME
-	pjsip_regc_init                          @ 28 NONAME
-	pjsip_regc_register                      @ 29 NONAME
-	pjsip_regc_send                          @ 30 NONAME
-	pjsip_regc_set_credentials               @ 31 NONAME
-	pjsip_regc_set_route_set                 @ 32 NONAME
-	pjsip_regc_set_transport                 @ 33 NONAME
-	pjsip_regc_unregister                    @ 34 NONAME
-	pjsip_regc_unregister_all                @ 35 NONAME
-	pjsip_regc_update_contact                @ 36 NONAME
-	pjsip_regc_update_expires                @ 37 NONAME
-	pjsip_replaces_hdr_create                @ 38 NONAME
-	pjsip_replaces_init_module               @ 39 NONAME
-	pjsip_replaces_verify_request            @ 40 NONAME
-	pjsip_xfer_accept                        @ 41 NONAME
-	pjsip_xfer_create_uac                    @ 42 NONAME
-	pjsip_xfer_create_uas                    @ 43 NONAME
-	pjsip_xfer_current_notify                @ 44 NONAME
-	pjsip_xfer_init_module                   @ 45 NONAME
-	pjsip_xfer_initiate                      @ 46 NONAME
-	pjsip_xfer_notify                        @ 47 NONAME
-	pjsip_xfer_send_request                  @ 48 NONAME
+	pjsip_100rel_create_prack                @ 2 NONAME
+	pjsip_100rel_end_session                 @ 3 NONAME
+	pjsip_100rel_init_module                 @ 4 NONAME
+	pjsip_100rel_is_reliable                 @ 5 NONAME
+	pjsip_100rel_on_rx_prack                 @ 6 NONAME
+	pjsip_100rel_send_prack                  @ 7 NONAME
+	pjsip_100rel_tx_response                 @ 8 NONAME
+	pjsip_create_sdp_body                    @ 9 NONAME
+	pjsip_dlg_get_inv_session                @ 10 NONAME
+	pjsip_get_prack_method                   @ 11 NONAME
+	pjsip_get_refer_method                   @ 12 NONAME
+	pjsip_inv_answer                         @ 13 NONAME
+	pjsip_inv_create_uac                     @ 14 NONAME
+	pjsip_inv_create_uas                     @ 15 NONAME
+	pjsip_inv_end_session                    @ 16 NONAME
+	pjsip_inv_initial_answer                 @ 17 NONAME
+	pjsip_inv_invite                         @ 18 NONAME
+	pjsip_inv_reinvite                       @ 19 NONAME
+	pjsip_inv_send_msg                       @ 20 NONAME
+	pjsip_inv_set_sdp_answer                 @ 21 NONAME
+	pjsip_inv_state_name                     @ 22 NONAME
+	pjsip_inv_terminate                      @ 23 NONAME
+	pjsip_inv_update                         @ 24 NONAME
+	pjsip_inv_usage_init                     @ 25 NONAME
+	pjsip_inv_usage_instance                 @ 26 NONAME
+	pjsip_inv_verify_request                 @ 27 NONAME
+	pjsip_prack_method                       @ 28 NONAME
+	pjsip_refer_method                       @ 29 NONAME
+	pjsip_regc_add_headers                   @ 30 NONAME
+	pjsip_regc_create                        @ 31 NONAME
+	pjsip_regc_destroy                       @ 32 NONAME
+	pjsip_regc_get_info                      @ 33 NONAME
+	pjsip_regc_get_pool                      @ 34 NONAME
+	pjsip_regc_init                          @ 35 NONAME
+	pjsip_regc_register                      @ 36 NONAME
+	pjsip_regc_send                          @ 37 NONAME
+	pjsip_regc_set_credentials               @ 38 NONAME
+	pjsip_regc_set_route_set                 @ 39 NONAME
+	pjsip_regc_set_transport                 @ 40 NONAME
+	pjsip_regc_unregister                    @ 41 NONAME
+	pjsip_regc_unregister_all                @ 42 NONAME
+	pjsip_regc_update_contact                @ 43 NONAME
+	pjsip_regc_update_expires                @ 44 NONAME
+	pjsip_replaces_hdr_create                @ 45 NONAME
+	pjsip_replaces_init_module               @ 46 NONAME
+	pjsip_replaces_verify_request            @ 47 NONAME
+	pjsip_xfer_accept                        @ 48 NONAME
+	pjsip_xfer_create_uac                    @ 49 NONAME
+	pjsip_xfer_create_uas                    @ 50 NONAME
+	pjsip_xfer_current_notify                @ 51 NONAME
+	pjsip_xfer_init_module                   @ 52 NONAME
+	pjsip_xfer_initiate                      @ 53 NONAME
+	pjsip_xfer_notify                        @ 54 NONAME
+	pjsip_xfer_send_request                  @ 55 NONAME
diff --git a/pjlib/include/pj/errno.h b/pjlib/include/pj/errno.h
index 9d2c504..10b15fe 100644
--- a/pjlib/include/pj/errno.h
+++ b/pjlib/include/pj/errno.h
@@ -304,7 +304,11 @@
  * Size is too small.
  */
 #define PJ_ETOOSMALL	    (PJ_ERRNO_START_STATUS + 19)/* 70019 */
-
+/**
+ * @hideinitializer
+ * Ignored
+ */
+#define PJ_EIGNORED	    (PJ_ERRNO_START_STATUS + 20)/* 70020 */
 
 /** @} */   /* pj_errnum */
 
diff --git a/pjlib/src/pj/errno.c b/pjlib/src/pj/errno.c
index a11f3b4..183ab50 100644
--- a/pjlib/src/pj/errno.c
+++ b/pjlib/src/pj/errno.c
@@ -70,6 +70,7 @@
     PJ_BUILD_ERR(PJ_ETOOBIG,	   "Size is too big"),
     PJ_BUILD_ERR(PJ_ERESOLVE,	   "gethostbyname() has returned error"),
     PJ_BUILD_ERR(PJ_ETOOSMALL,	   "Size is too short"),
+    PJ_BUILD_ERR(PJ_EIGNORED,	   "Ignored"),
 };
 #endif	/* PJ_HAS_ERROR_STRING */
 
diff --git a/pjsip/build/Makefile b/pjsip/build/Makefile
index a9c363b..6ddb014 100644
--- a/pjsip/build/Makefile
+++ b/pjsip/build/Makefile
@@ -88,7 +88,8 @@
 		    test.o transport_loop_test.o transport_tcp_test.o \
 		    transport_test.o transport_udp_test.o \
 		    tsx_basic_test.o tsx_bench.o tsx_uac_test.o \
-		    tsx_uas_test.o txdata_test.o uri_test.o
+		    tsx_uas_test.o txdata_test.o uri_test.o \
+		    inv_offer_answer_test.o
 export TEST_CFLAGS += $(_CFLAGS)
 export TEST_LDFLAGS += $(PJ_LDFLAGS) $(PJ_LDLIBS) $(LDFLAGS)
 export TEST_EXE := ../bin/pjsip-test-$(TARGET_NAME)$(HOST_EXE)
diff --git a/pjsip/build/test_pjsip.dsp b/pjsip/build/test_pjsip.dsp
index aa7de06..1429063 100644
--- a/pjsip/build/test_pjsip.dsp
+++ b/pjsip/build/test_pjsip.dsp
@@ -97,6 +97,10 @@
 # End Source File

 # Begin Source File

 

+SOURCE="..\src\test-pjsip\inv_offer_answer_test.c"

+# End Source File

+# Begin Source File

+

 SOURCE="..\src\test-pjsip\main.c"

 # End Source File

 # Begin Source File

diff --git a/pjsip/build/test_pjsip.vcproj b/pjsip/build/test_pjsip.vcproj
index 91208c4..dd6c8d3 100644
--- a/pjsip/build/test_pjsip.vcproj
+++ b/pjsip/build/test_pjsip.vcproj
@@ -44,7 +44,7 @@
 				Name="VCCLCompilerTool"

 				Optimization="2"

 				InlineFunctionExpansion="2"

-				AdditionalIncludeDirectories="../include,../../pjlib/include,../../pjlib-util/include"

+				AdditionalIncludeDirectories="../include,../../pjlib/include,../../pjlib-util/include,../../pjmedia/include"

 				PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;PJ_WIN32;PJ_M_I386"

 				StringPooling="true"

 				RuntimeLibrary="2"

@@ -139,7 +139,7 @@
 			<Tool

 				Name="VCCLCompilerTool"

 				Optimization="0"

-				AdditionalIncludeDirectories="../include,../../pjlib/include,../../pjlib-util/include"

+				AdditionalIncludeDirectories="../include,../../pjlib/include,../../pjlib-util/include,../../pjmedia/include"

 				PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;PJ_WIN32;PJ_M_I386"

 				MinimalRebuild="true"

 				BasicRuntimeChecks="3"

@@ -255,6 +255,10 @@
 				</FileConfiguration>

 			</File>

 			<File

+				RelativePath="..\src\test-pjsip\inv_offer_answer_test.c"

+				>

+			</File>

+			<File

 				RelativePath="..\src\test-pjsip\main.c"

 				>

 				<FileConfiguration

diff --git a/pjsip/include/pjsip-ua/sip_100rel.h b/pjsip/include/pjsip-ua/sip_100rel.h
index fc9e27b..6ba8485 100644
--- a/pjsip/include/pjsip-ua/sip_100rel.h
+++ b/pjsip/include/pjsip-ua/sip_100rel.h
@@ -45,12 +45,7 @@
  *
  * \subsection pjsip_100rel_init Initializing 100rel Module
  *
- * \a PRACK and \a 100rel extension support is built into the library when
- * #PJSIP_HAS_100REL macro is enabled. The default is yes. Application can
- * set this macro to zero if it does not wish to support reliable provisional
- * response extension.
- *
- * Application must also explicitly initialize 100rel module by calling
+ * Application must explicitly initialize 100rel module by calling
  * #pjsip_100rel_init_module() in application initialization function.
  *
  * Once the 100rel module is initialized, it will register \a PRACK method
@@ -59,7 +54,7 @@
  * \subsection pjsip_100rel_sess Using 100rel in a Session
  *
  * For UAC, \a 100rel support will be enabled in the session if \a 100rel
- * support is enabled in the library (with #PJSIP_HAS_100REL macro). 
+ * support is enabled in the library (default is yes). 
  * Outgoing INVITE request will include \a 100rel tag in \a Supported
  * header and \a PRACK method in \a Allow header. When callee endpoint
  * sends reliable provisional responses, the UAC will automatically send
@@ -86,9 +81,7 @@
  * \verbatim
     unsigned options = 0;
 
-#if PJSIP_HAS_100REL
     options |= PJSIP_INV_SUPPORT_100REL;
-#endif
 
     status = pjsip_inv_verify_request(rdata, &options, answer, NULL,
 				      endpt, &resp);
@@ -129,6 +122,20 @@
 
 PJ_BEGIN_DECL
 
+
+/** 
+ * PRACK method constant. 
+ * @see pjsip_get_prack_method() 
+  */
+PJ_DECL_DATA(const pjsip_method) pjsip_prack_method;
+
+
+/** 
+ * Get #pjsip_invite_method constant. 
+ */
+PJ_DECL(const pjsip_method*) pjsip_get_prack_method(void);
+
+
 /**
  * Initialize 100rel module. This function must be called once during
  * application initialization, to register 100rel module to SIP endpoint.
@@ -139,6 +146,7 @@
  */
 PJ_DECL(pj_status_t) pjsip_100rel_init_module(pjsip_endpoint *endpt);
 
+
 /**
  * Add 100rel support to the specified invite session. This function will
  * be called internally by the invite session if it detects that the
@@ -150,6 +158,56 @@
  */
 PJ_DECL(pj_status_t) pjsip_100rel_attach(pjsip_inv_session *inv);
 
+
+/**
+ * Check if incoming response has reliable provisional response feature.
+ *
+ * @param rdata		Receive data buffer containing the response.
+ *
+ * @return		PJ_TRUE if the provisional response is reliable.
+ */
+PJ_DECL(pj_bool_t) pjsip_100rel_is_reliable(pjsip_rx_data *rdata);
+
+
+/**
+ * Create PRACK request for the incoming reliable provisional response.
+ * Note that PRACK request MUST be sent using #pjsip_100rel_send_prack().
+ *
+ * @param inv		The invite session.
+ * @param rdata		The incoming reliable provisional response.
+ * @param p_tdata	Upon return, it will be initialized with the
+ *			PRACK request.
+ *
+ * @return		PJ_SUCCESS on successful.
+ */
+PJ_DECL(pj_status_t) pjsip_100rel_create_prack(pjsip_inv_session *inv,
+					       pjsip_rx_data *rdata,
+					       pjsip_tx_data **p_tdata);
+
+/**
+ * Send PRACK request.
+ *
+ * @param inv		The invite session.
+ * @param tdata		The PRACK request.
+ *
+ * @return		PJ_SUCCESS on successful.
+ */
+PJ_DECL(pj_status_t) pjsip_100rel_send_prack(pjsip_inv_session *inv,
+					     pjsip_tx_data *tdata);
+
+
+/**
+ * Handle incoming PRACK request.
+ *
+ * @param inv		The invite session.
+ * @param rdata		Incoming PRACK request.
+ *
+ * @return		PJ_SUCCESS on successful.
+ */
+PJ_DECL(pj_status_t) pjsip_100rel_on_rx_prack(pjsip_inv_session *inv,
+					      pjsip_rx_data *rdata);
+
+
 /**
  * Transmit INVITE response (provisional or final) reliably according to
  * 100rel specification. The 100rel module will take care of retransmitting
@@ -166,6 +224,16 @@
 					      pjsip_tx_data *tdata);
 
 
+/**
+ * Notify 100rel module that the invite session has been disconnected.
+ *
+ * @param inv		The invite session.
+ *
+ * @return		PJ_SUCCESS on successful.
+ */
+PJ_DECL(pj_status_t) pjsip_100rel_end_session(pjsip_inv_session *inv);
+
+
 PJ_END_DECL
 
 /**
diff --git a/pjsip/include/pjsip-ua/sip_inv.h b/pjsip/include/pjsip-ua/sip_inv.h
index f04f4ae..a367fe1 100644
--- a/pjsip/include/pjsip-ua/sip_inv.h
+++ b/pjsip/include/pjsip-ua/sip_inv.h
@@ -253,6 +253,8 @@
     pjmedia_sdp_neg	*neg;			    /**< Negotiator.	    */
     pjsip_transaction	*invite_tsx;		    /**< 1st invite tsx.    */
     pjsip_tx_data	*last_answer;		    /**< Last INVITE resp.  */
+    pjsip_tx_data	*last_ack;		    /**< Last ACK request   */
+    pj_int32_t		 last_ack_cseq;		    /**< CSeq of last ACK   */
     void		*mod_data[PJSIP_MAX_MODULE];/**< Modules data.	    */
 };
 
@@ -561,20 +563,15 @@
 
 
 /**
- * Create an UPDATE request. 
+ * Create an UPDATE request to initiate new SDP offer.
  *
  * @param inv		The invite session.
  * @param new_contact	If application wants to update its local contact
  *			and inform peer to perform target refresh with a new
  *			contact, it can specify the new contact in this 
  *			argument; otherwise this argument must be NULL.
- * @param new_offer	Application MAY initiate a new SDP offer/answer 
- *			session in the request when there is no pending answer
- *			to be sent or received. It can detect this condition
- *			by observing the state of the SDP negotiator of the 
- *			invite session. If new offer should be sent to remote,
- *			the offer must be specified in this argument; otherwise
- *			this argument must be NULL.
+ * @param offer		Offer to be sent to remote. This argument is
+ *			mandatory.
  * @param p_tdata	Pointer to receive the UPDATE request message to
  *			be created.
  *
@@ -584,7 +581,7 @@
  */
 PJ_DECL(pj_status_t) pjsip_inv_update (	pjsip_inv_session *inv,
 					const pj_str_t *new_contact,
-					const pjmedia_sdp_session *new_offer,
+					const pjmedia_sdp_session *offer,
 					pjsip_tx_data **p_tdata );
 
 
diff --git a/pjsip/include/pjsip/sip_config.h b/pjsip/include/pjsip/sip_config.h
index 3ad94c2..846c2a5 100644
--- a/pjsip/include/pjsip/sip_config.h
+++ b/pjsip/include/pjsip/sip_config.h
@@ -65,17 +65,6 @@
 
 
 /**
- * Specify whether support for reliable provisional response (100rel, PRACK)
- * should be built in the library.
- *
- * Default: 1
- */
-#ifndef PJSIP_HAS_100REL
-#    define PJSIP_HAS_100REL		1
-#endif
-
-
-/**
  * Specify maximum transaction count in transaction hash table.
  * Default value is 16*1024
  */
diff --git a/pjsip/src/pjsip-ua/sip_100rel.c b/pjsip/src/pjsip-ua/sip_100rel.c
index 4879825..cb8852d 100644
--- a/pjsip/src/pjsip-ua/sip_100rel.c
+++ b/pjsip/src/pjsip-ua/sip_100rel.c
@@ -28,17 +28,21 @@
 #include <pj/pool.h>
 #include <pj/rand.h>
 
-#if defined(PJSIP_HAS_100REL) && PJSIP_HAS_100REL!=0
-
 #define THIS_FILE	"sip_100rel.c"
 
+/* PRACK method */
+PJ_DEF_DATA(const pjsip_method) pjsip_prack_method =
+{
+    PJSIP_OTHER_METHOD,
+    { "PRACK", 5 }
+};
+
 typedef struct dlg_data dlg_data;
 
 /*
  * Static prototypes.
  */
 static pj_status_t mod_100rel_load(pjsip_endpoint *endpt);
-static void	   mod_100rel_on_tsx_state(pjsip_transaction*, pjsip_event*);
 
 static void handle_incoming_prack(dlg_data *dd, pjsip_transaction *tsx,
 				  pjsip_event *e);
@@ -48,13 +52,6 @@
 			  struct pj_timer_entry *entry);
 
 
-/* PRACK method */
-const pjsip_method pjsip_prack_method =
-{
-	PJSIP_OTHER_METHOD,
-	{ "PRACK", 5 }
-};
-
 const pj_str_t tag_100rel = { "100rel", 6 };
 const pj_str_t RSEQ = { "RSeq", 4 };
 const pj_str_t RACK = { "RAck", 4 };
@@ -80,7 +77,7 @@
 	NULL,				    /* on_rx_response()		*/
 	NULL,				    /* on_tx_request.		*/
 	NULL,				    /* on_tx_response()		*/
-	&mod_100rel_on_tsx_state,	    /* on_tsx_state()		*/
+	NULL,				    /* on_tsx_state()		*/
     }
 
 };
@@ -132,339 +129,62 @@
  */
 static pj_status_t mod_100rel_load(pjsip_endpoint *endpt)
 {
-	mod_100rel.endpt = endpt;
-	pjsip_endpt_add_capability(endpt, &mod_100rel.mod, 
-				   PJSIP_H_ALLOW, NULL,
-				   1, &pjsip_prack_method.name);
-	pjsip_endpt_add_capability(endpt, &mod_100rel.mod, 
-				   PJSIP_H_SUPPORTED, NULL,
-				   1, &tag_100rel);
+    mod_100rel.endpt = endpt;
+    pjsip_endpt_add_capability(endpt, &mod_100rel.mod, 
+			       PJSIP_H_ALLOW, NULL,
+			       1, &pjsip_prack_method.name);
+    pjsip_endpt_add_capability(endpt, &mod_100rel.mod, 
+			       PJSIP_H_SUPPORTED, NULL,
+			       1, &tag_100rel);
 
-	return PJ_SUCCESS;
+    return PJ_SUCCESS;
 }
 
 static pjsip_require_hdr *find_req_hdr(pjsip_msg *msg)
 {
-	pjsip_require_hdr *hreq;
+    pjsip_require_hdr *hreq;
+
+    hreq = (pjsip_require_hdr*)
+	    pjsip_msg_find_hdr(msg, PJSIP_H_REQUIRE, NULL);
+
+    while (hreq) {
+	unsigned i;
+	for (i=0; i<hreq->count; ++i) {
+	    if (!pj_stricmp(&hreq->values[i], &tag_100rel)) {
+		return hreq;
+	    }
+	}
+
+	if ((void*)hreq->next == (void*)&msg->hdr)
+	    return NULL;
 
 	hreq = (pjsip_require_hdr*)
-		pjsip_msg_find_hdr(msg, PJSIP_H_REQUIRE, NULL);
+		pjsip_msg_find_hdr(msg, PJSIP_H_REQUIRE, hreq->next);
 
-	while (hreq) {
-		unsigned i;
-		for (i=0; i<hreq->count; ++i) {
-			if (!pj_stricmp(&hreq->values[i], &tag_100rel)) {
-				return hreq;
-			}
-		}
+    }
 
-		if ((void*)hreq->next == (void*)&msg->hdr)
-			return NULL;
-
-		hreq = (pjsip_require_hdr*)
-			pjsip_msg_find_hdr(msg, PJSIP_H_REQUIRE, hreq->next);
-
-	}
-
-	return NULL;
-}
-
-static void mod_100rel_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e)
-{
-	pjsip_dialog *dlg;
-	dlg_data *dd;
-
-	dlg = pjsip_tsx_get_dlg(tsx);
-	if (!dlg)
-		return;
-
-	dd = (dlg_data*) dlg->mod_data[mod_100rel.mod.id];
-	if (!dd)
-		return;
-
-	if (tsx->role == PJSIP_ROLE_UAS &&
-	    tsx->state == PJSIP_TSX_STATE_TRYING &&
-	    pjsip_method_cmp(&tsx->method, &pjsip_prack_method)==0)
-	{
-		/* 
-		 * Handle incoming PRACK request.
-		 */
-		handle_incoming_prack(dd, tsx, e);
-
-	} else if (tsx->role == PJSIP_ROLE_UAC &&
-		   tsx->method.id == PJSIP_INVITE_METHOD &&
-		   e->type == PJSIP_EVENT_TSX_STATE &&
-		   e->body.tsx_state.type == PJSIP_EVENT_RX_MSG && 
-		   e->body.tsx_state.src.rdata->msg_info.msg->line.status.code > 100 &&
-		   e->body.tsx_state.src.rdata->msg_info.msg->line.status.code < 200 &&
-		   e->body.tsx_state.src.rdata->msg_info.require != NULL)
-	{
-		/*
-		 * Handle incoming provisional response which wants to 
-		 * be PRACK-ed
-		 */
-
-		if (find_req_hdr(e->body.tsx_state.src.rdata->msg_info.msg)) {
-			/* Received provisional response which needs to be 
-			 * PRACK-ed.
-			 */
-			handle_incoming_response(dd, tsx, e);
-		}
-
-	} else if (tsx->role == PJSIP_ROLE_UAC &&
-		   tsx->state == PJSIP_TSX_STATE_COMPLETED &&
-		   pjsip_method_cmp(&tsx->method, &pjsip_prack_method)==0)
-	{
-		/*
-		 * Handle the status of outgoing PRACK request.
-		 */
-		if (tsx->status_code == PJSIP_SC_CALL_TSX_DOES_NOT_EXIST ||
-		    tsx->status_code == PJSIP_SC_REQUEST_TIMEOUT ||
-		    tsx->status_code == PJSIP_SC_TSX_TIMEOUT ||
-		    tsx->status_code == PJSIP_SC_TSX_TRANSPORT_ERROR)
-		{
-			/* These are fatal errors which should terminate
-			 * the session AND dialog!
-			 */
-			PJ_TODO(TERMINATE_SESSION_ON_481);
-		}
-
-	} else if (tsx == dd->inv->invite_tsx &&
-		   tsx->role == PJSIP_ROLE_UAS &&
-		   tsx->state == PJSIP_TSX_STATE_TERMINATED)
-	{
-		/* Make sure we don't have pending transmission */
-		if (dd->uas_state) {
-			pj_assert(!dd->uas_state->retransmit_timer.id);
-			pj_assert(pj_list_empty(&dd->uas_state->tx_data_list));
-		}
-	}
-}
-
-static void parse_rack(const pj_str_t *rack,
-		       pj_uint32_t *p_rseq, pj_int32_t *p_seq,
-		       pj_str_t *p_method)
-{
-	const char *p = rack->ptr, *end = p + rack->slen;
-	pj_str_t token;
-
-	token.ptr = (char*)p;
-	while (p < end && pj_isdigit(*p))
-		++p;
-	token.slen = p - token.ptr;
-	*p_rseq = pj_strtoul(&token);
-
-	++p;
-	token.ptr = (char*)p;
-	while (p < end && pj_isdigit(*p))
-		++p;
-	token.slen = p - token.ptr;
-	*p_seq = pj_strtoul(&token);
-
-	++p;
-	if (p < end) {
-		p_method->ptr = (char*)p;
-		p_method->slen = end - p;
-	} else {
-		p_method->ptr = NULL;
-		p_method->slen = 0;
-	}
-}
-
-/* Clear all responses in the transmission list */
-static void clear_all_responses(dlg_data *dd)
-{
-	tx_data_list_t *tl;
-
-	tl = dd->uas_state->tx_data_list.next;
-	while (tl != &dd->uas_state->tx_data_list) {
-		pjsip_tx_data_dec_ref(tl->tdata);
-		tl = tl->next;
-	}
-	pj_list_init(&dd->uas_state->tx_data_list);
-}
-
-
-static void handle_incoming_prack(dlg_data *dd, pjsip_transaction *tsx,
-				  pjsip_event *e)
-{
-	pjsip_rx_data *rdata;
-	pjsip_msg *msg;
-	pjsip_generic_string_hdr *rack_hdr;
-	pjsip_tx_data *tdata;
-	pj_uint32_t rseq;
-	pj_int32_t cseq;
-	pj_str_t method;
-	pj_status_t status;
-
-
-	rdata = e->body.tsx_state.src.rdata;
-	msg = rdata->msg_info.msg;
-
-	/* Always reply with 200/OK for PRACK */
-	status = pjsip_endpt_create_response(tsx->endpt, rdata, 
-					     200, NULL, &tdata);
-	if (status == PJ_SUCCESS)
-		pjsip_tsx_send_msg(tsx, tdata);
-
-	/* Ignore if we don't have pending transmission */
-	if (dd->uas_state == NULL ||
-	    pj_list_empty(&dd->uas_state->tx_data_list))
-	{
-		PJ_LOG(4,(dd->inv->dlg->obj_name, 
-			  "PRACK ignored - no pending response"));
-		return;
-	}
-
-	/* Find RAck header */
-	rack_hdr = (pjsip_generic_string_hdr*)
-		   pjsip_msg_find_hdr_by_name(msg, &RACK, NULL);
-	if (!rack_hdr) {
-		/* RAck header not found */
-		PJ_LOG(4,(dd->inv->dlg->obj_name, "No RAck header"));
-		return;
-	}
-	parse_rack(&rack_hdr->hvalue, &rseq, &cseq, &method);
-
-	/* Match RAck against outgoing transmission */
-	if (rseq == dd->uas_state->tx_data_list.next->rseq &&
-	    cseq == dd->uas_state->cseq)
-	{
-		tx_data_list_t *tl = dd->uas_state->tx_data_list.next;
-
-		/* Yes it match! */
-		if (dd->uas_state->retransmit_timer.id) {
-			pjsip_endpt_cancel_timer(dd->inv->dlg->endpt,
-						 &dd->uas_state->retransmit_timer);
-			dd->uas_state->retransmit_timer.id = PJ_FALSE;
-		}
-
-		/* Remove from the list */
-		if (tl != &dd->uas_state->tx_data_list) {
-			pj_list_erase(tl);
-
-			/* Destroy the response */
-			pjsip_tx_data_dec_ref(tl->tdata);
-		}
-
-		/* Schedule next packet */
-		dd->uas_state->retransmit_count = 0;
-		if (!pj_list_empty(&dd->uas_state->tx_data_list)) {
-			on_retransmit(NULL, &dd->uas_state->retransmit_timer);
-		}
-
-	} else {
-		/* No it doesn't match */
-		PJ_LOG(4,(dd->inv->dlg->obj_name, 
-			 "Rx PRACK with no matching reliable response"));
-	}
+    return NULL;
 }
 
 
 /*
- * Handle incoming provisional response with 100rel requirement.
- * In this case we shall transmit PRACK request.
+ * Get PRACK method constant. 
  */
-static void handle_incoming_response(dlg_data *dd, pjsip_transaction *tsx,
-				     pjsip_event *e)
+PJ_DEF(const pjsip_method*) pjsip_get_prack_method(void)
 {
-	pjsip_rx_data *rdata;
-	pjsip_msg *msg;
-	pjsip_generic_string_hdr *rseq_hdr;
-	pjsip_generic_string_hdr *rack_hdr;
-	unsigned rseq;
-	pj_str_t rack;
-	char rack_buf[80];
-	pjsip_tx_data *tdata;
-	pj_status_t status;
-
-	rdata = e->body.tsx_state.src.rdata;
-	msg = rdata->msg_info.msg;
-
-	/* Check our assumptions */
-	pj_assert( tsx->role == PJSIP_ROLE_UAC &&
-		   tsx->method.id == PJSIP_INVITE_METHOD &&
-		   e->type == PJSIP_EVENT_TSX_STATE &&
-		   e->body.tsx_state.type == PJSIP_EVENT_RX_MSG && 
-		   msg->line.status.code > 100 &&
-		   msg->line.status.code < 200);
-
-
-	/* Get the RSeq header */
-	rseq_hdr = (pjsip_generic_string_hdr*)
-		   pjsip_msg_find_hdr_by_name(msg, &RSEQ, NULL);
-	if (rseq_hdr == NULL) {
-		PJ_LOG(4,(dd->inv->dlg->obj_name, 
-			 "Ignoring provisional response with no RSeq header"));
-		return;
-	}
-	rseq = (pj_uint32_t) pj_strtoul(&rseq_hdr->hvalue);
-
-	/* Create new UAC state if we don't have one */
-	if (dd->uac_state == NULL) {
-		dd->uac_state = PJ_POOL_ZALLOC_T(dd->inv->dlg->pool,
-						 uac_state_t);
-		dd->uac_state->cseq = rdata->msg_info.cseq->cseq;
-		dd->uac_state->rseq = rseq - 1;
-	}
-
-	/* If this is from new INVITE transaction, reset UAC state */
-	if (rdata->msg_info.cseq->cseq != dd->uac_state->cseq) {
-		dd->uac_state->cseq = rdata->msg_info.cseq->cseq;
-		dd->uac_state->rseq = rseq - 1;
-	}
-
-	/* Ignore provisional response retransmission */
-	if (rseq <= dd->uac_state->rseq) {
-		/* This should have been handled before */
-		return;
-
-	/* Ignore provisional response with out-of-order RSeq */
-	} else if (rseq != dd->uac_state->rseq + 1) {
-		PJ_LOG(4,(dd->inv->dlg->obj_name, 
-			 "Ignoring provisional response because RSeq jump "
-			 "(expecting %u, got %u)",
-			 dd->uac_state->rseq+1, rseq));
-		return;
-	}
-
-	/* Update our RSeq */
-	dd->uac_state->rseq = rseq;
-
-	/* Create PRACK */
-	status = pjsip_dlg_create_request(dd->inv->dlg, &pjsip_prack_method,
-					  -1, &tdata);
-	if (status != PJ_SUCCESS) {
-		PJ_LOG(4,(dd->inv->dlg->obj_name, 
-			 "Error creating PRACK request (status=%d)", status));
-		return;
-	}
-
-	/* Create RAck header */
-	rack.ptr = rack_buf;
-	rack.slen = pj_ansi_snprintf(rack.ptr, sizeof(rack_buf),
-				     "%u %u %.*s",
-				     rseq, rdata->msg_info.cseq->cseq,
-				     (int)tsx->method.name.slen,
-				     tsx->method.name.ptr);
-	PJ_ASSERT_ON_FAIL(rack.slen > 0 && rack.slen < (int)sizeof(rack_buf),
-			{ pjsip_tx_data_dec_ref(tdata); return; });
-	rack_hdr = pjsip_generic_string_hdr_create(tdata->pool, &RACK, &rack);
-	pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) rack_hdr);
-
-	/* Send PRACK */
-	pjsip_dlg_send_request(dd->inv->dlg, tdata, 
-			       mod_100rel.mod.id, (void*) dd);
-
+    return &pjsip_prack_method;
 }
 
 
 /*
- * API: init module
+ * init module
  */
 PJ_DEF(pj_status_t) pjsip_100rel_init_module(pjsip_endpoint *endpt)
 {
-	return pjsip_endpt_register_module(endpt, &mod_100rel.mod);
+    if (mod_100rel.mod.id != -1)
+	return PJ_SUCCESS;
+
+    return pjsip_endpt_register_module(endpt, &mod_100rel.mod);
 }
 
 
@@ -474,19 +194,304 @@
  */
 PJ_DEF(pj_status_t) pjsip_100rel_attach(pjsip_inv_session *inv)
 {
-	dlg_data *dd;
+    dlg_data *dd;
 
-	/* Check that 100rel module has been initialized */
-	PJ_ASSERT_RETURN(mod_100rel.mod.id >= 0, PJ_EINVALIDOP);
+    /* Check that 100rel module has been initialized */
+    PJ_ASSERT_RETURN(mod_100rel.mod.id >= 0, PJ_EINVALIDOP);
 
-	/* Create and attach as dialog usage */
-	dd = PJ_POOL_ZALLOC_T(inv->dlg->pool, dlg_data);
-	dd->inv = inv;
-	pjsip_dlg_add_usage(inv->dlg, &mod_100rel.mod, (void*)dd);
+    /* Create and attach as dialog usage */
+    dd = PJ_POOL_ZALLOC_T(inv->dlg->pool, dlg_data);
+    dd->inv = inv;
+    pjsip_dlg_add_usage(inv->dlg, &mod_100rel.mod, (void*)dd);
 
-	PJ_LOG(5,(dd->inv->dlg->obj_name, "100rel module attached"));
+    PJ_LOG(5,(dd->inv->dlg->obj_name, "100rel module attached"));
 
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Check if incoming response has reliable provisional response feature.
+ */
+PJ_DEF(pj_bool_t) pjsip_100rel_is_reliable(pjsip_rx_data *rdata)
+{
+    pjsip_msg *msg = rdata->msg_info.msg;
+
+    PJ_ASSERT_RETURN(msg->type == PJSIP_RESPONSE_MSG, PJ_FALSE);
+
+    return msg->line.status.code > 100 && msg->line.status.code < 200 &&
+	   rdata->msg_info.require != NULL &&
+	   find_req_hdr(msg) != NULL;
+}
+
+
+/*
+ * Create PRACK request for the incoming reliable provisional response.
+ */
+PJ_DEF(pj_status_t) pjsip_100rel_create_prack( pjsip_inv_session *inv,
+					       pjsip_rx_data *rdata,
+					       pjsip_tx_data **p_tdata)
+{
+    dlg_data *dd;
+    pjsip_transaction *tsx;
+    pjsip_msg *msg;
+    pjsip_generic_string_hdr *rseq_hdr;
+    pjsip_generic_string_hdr *rack_hdr;
+    unsigned rseq;
+    pj_str_t rack;
+    char rack_buf[80];
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+
+    *p_tdata = NULL;
+
+    dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id];
+    PJ_ASSERT_RETURN(dd != NULL, PJSIP_ENOTINITIALIZED);
+
+    tsx = pjsip_rdata_get_tsx(rdata);
+    msg = rdata->msg_info.msg;
+
+    /* Check our assumptions */
+    pj_assert( tsx->role == PJSIP_ROLE_UAC &&
+	       tsx->method.id == PJSIP_INVITE_METHOD &&
+	       msg->line.status.code > 100 &&
+	       msg->line.status.code < 200);
+
+
+    /* Get the RSeq header */
+    rseq_hdr = (pjsip_generic_string_hdr*)
+	       pjsip_msg_find_hdr_by_name(msg, &RSEQ, NULL);
+    if (rseq_hdr == NULL) {
+	PJ_LOG(4,(dd->inv->dlg->obj_name, 
+		 "Ignoring provisional response with no RSeq header"));
+	return PJSIP_EMISSINGHDR;
+    }
+    rseq = (pj_uint32_t) pj_strtoul(&rseq_hdr->hvalue);
+
+    /* Create new UAC state if we don't have one */
+    if (dd->uac_state == NULL) {
+	dd->uac_state = PJ_POOL_ZALLOC_T(dd->inv->dlg->pool,
+					 uac_state_t);
+	dd->uac_state->cseq = rdata->msg_info.cseq->cseq;
+	dd->uac_state->rseq = rseq - 1;
+    }
+
+    /* If this is from new INVITE transaction, reset UAC state */
+    if (rdata->msg_info.cseq->cseq != dd->uac_state->cseq) {
+	dd->uac_state->cseq = rdata->msg_info.cseq->cseq;
+	dd->uac_state->rseq = rseq - 1;
+    }
+
+    /* Ignore provisional response retransmission */
+    if (rseq <= dd->uac_state->rseq) {
+	/* This should have been handled before */
+	return PJ_EIGNORED;
+
+    /* Ignore provisional response with out-of-order RSeq */
+    } else if (rseq != dd->uac_state->rseq + 1) {
+	PJ_LOG(4,(dd->inv->dlg->obj_name, 
+		 "Ignoring provisional response because RSeq jump "
+		 "(expecting %u, got %u)",
+		 dd->uac_state->rseq+1, rseq));
+	return PJ_EIGNORED;
+    }
+
+    /* Update our RSeq */
+    dd->uac_state->rseq = rseq;
+
+    /* Create PRACK */
+    status = pjsip_dlg_create_request(dd->inv->dlg, &pjsip_prack_method,
+				      -1, &tdata);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Create RAck header */
+    rack.ptr = rack_buf;
+    rack.slen = pj_ansi_snprintf(rack.ptr, sizeof(rack_buf),
+				 "%u %u %.*s",
+				 rseq, rdata->msg_info.cseq->cseq,
+				 (int)tsx->method.name.slen,
+				 tsx->method.name.ptr);
+    rack_hdr = pjsip_generic_string_hdr_create(tdata->pool, &RACK, &rack);
+    pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) rack_hdr);
+
+    /* Done */
+    *p_tdata = tdata;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Send PRACK request.
+ */
+PJ_DEF(pj_status_t) pjsip_100rel_send_prack( pjsip_inv_session *inv,
+					     pjsip_tx_data *tdata)
+{
+    dlg_data *dd;
+
+    dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id];
+    PJ_ASSERT_ON_FAIL(dd != NULL, 
+    {pjsip_tx_data_dec_ref(tdata); return PJSIP_ENOTINITIALIZED; });
+
+    return pjsip_dlg_send_request(inv->dlg, tdata, 
+				  mod_100rel.mod.id, (void*) dd);
+
+}
+
+
+/*
+ * Notify 100rel module that the invite session has been disconnected.
+ */
+PJ_DEF(pj_status_t) pjsip_100rel_end_session(pjsip_inv_session *inv)
+{
+    dlg_data *dd;
+
+    dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id];
+    if (!dd)
 	return PJ_SUCCESS;
+
+    /* Make sure we don't have pending transmission */
+    if (dd->uas_state) {
+	pj_assert(!dd->uas_state->retransmit_timer.id);
+	pj_assert(pj_list_empty(&dd->uas_state->tx_data_list));
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+static void parse_rack(const pj_str_t *rack,
+		       pj_uint32_t *p_rseq, pj_int32_t *p_seq,
+		       pj_str_t *p_method)
+{
+    const char *p = rack->ptr, *end = p + rack->slen;
+    pj_str_t token;
+
+    token.ptr = (char*)p;
+    while (p < end && pj_isdigit(*p))
+	++p;
+    token.slen = p - token.ptr;
+    *p_rseq = pj_strtoul(&token);
+
+    ++p;
+    token.ptr = (char*)p;
+    while (p < end && pj_isdigit(*p))
+	++p;
+    token.slen = p - token.ptr;
+    *p_seq = pj_strtoul(&token);
+
+    ++p;
+    if (p < end) {
+	p_method->ptr = (char*)p;
+	p_method->slen = end - p;
+    } else {
+	p_method->ptr = NULL;
+	p_method->slen = 0;
+    }
+}
+
+/* Clear all responses in the transmission list */
+static void clear_all_responses(dlg_data *dd)
+{
+    tx_data_list_t *tl;
+
+    tl = dd->uas_state->tx_data_list.next;
+    while (tl != &dd->uas_state->tx_data_list) {
+	pjsip_tx_data_dec_ref(tl->tdata);
+	tl = tl->next;
+    }
+    pj_list_init(&dd->uas_state->tx_data_list);
+}
+
+
+/*
+ * Handle incoming PRACK request.
+ */
+PJ_DEF(pj_status_t) pjsip_100rel_on_rx_prack( pjsip_inv_session *inv,
+					      pjsip_rx_data *rdata)
+{
+    dlg_data *dd;
+    pjsip_transaction *tsx;
+    pjsip_msg *msg;
+    pjsip_generic_string_hdr *rack_hdr;
+    pjsip_tx_data *tdata;
+    pj_uint32_t rseq;
+    pj_int32_t cseq;
+    pj_str_t method;
+    pj_status_t status;
+
+    dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id];
+    PJ_ASSERT_RETURN(dd != NULL, PJSIP_ENOTINITIALIZED);
+
+    tsx = pjsip_rdata_get_tsx(rdata);
+    pj_assert(tsx != NULL);
+
+    msg = rdata->msg_info.msg;
+
+    /* Always reply with 200/OK for PRACK */
+    status = pjsip_dlg_create_response(inv->dlg, rdata, 200, NULL, &tdata);
+    if (status == PJ_SUCCESS) {
+	status = pjsip_dlg_send_response(inv->dlg, tsx, tdata);
+    }
+
+    /* Ignore if we don't have pending transmission */
+    if (dd->uas_state == NULL || pj_list_empty(&dd->uas_state->tx_data_list)) {
+	PJ_LOG(4,(dd->inv->dlg->obj_name, 
+		  "PRACK ignored - no pending response"));
+	return PJ_EIGNORED;
+    }
+
+    /* Find RAck header */
+    rack_hdr = (pjsip_generic_string_hdr*)
+	       pjsip_msg_find_hdr_by_name(msg, &RACK, NULL);
+    if (!rack_hdr) {
+	/* RAck header not found */
+	PJ_LOG(4,(dd->inv->dlg->obj_name, "No RAck header"));
+	return PJSIP_EMISSINGHDR;
+    }
+
+    /* Parse RAck header */
+    parse_rack(&rack_hdr->hvalue, &rseq, &cseq, &method);
+
+
+    /* Match RAck against outgoing transmission */
+    if (rseq == dd->uas_state->tx_data_list.next->rseq &&
+	cseq == dd->uas_state->cseq)
+    {
+	/* 
+	 * Yes this PRACK matches outgoing transmission.
+	 */
+	tx_data_list_t *tl = dd->uas_state->tx_data_list.next;
+
+	if (dd->uas_state->retransmit_timer.id) {
+	    pjsip_endpt_cancel_timer(dd->inv->dlg->endpt,
+				     &dd->uas_state->retransmit_timer);
+	    dd->uas_state->retransmit_timer.id = PJ_FALSE;
+	}
+
+	/* Remove from the list */
+	if (tl != &dd->uas_state->tx_data_list) {
+	    pj_list_erase(tl);
+
+	    /* Destroy the response */
+	    pjsip_tx_data_dec_ref(tl->tdata);
+	}
+
+	/* Schedule next packet */
+	dd->uas_state->retransmit_count = 0;
+	if (!pj_list_empty(&dd->uas_state->tx_data_list)) {
+	    on_retransmit(NULL, &dd->uas_state->retransmit_timer);
+	}
+
+    } else {
+	/* No it doesn't match */
+	PJ_LOG(4,(dd->inv->dlg->obj_name, 
+		 "Rx PRACK with no matching reliable response"));
+	return PJ_EIGNORED;
+    }
+
+    return PJ_SUCCESS;
 }
 
 
@@ -497,136 +502,138 @@
 static void on_retransmit(pj_timer_heap_t *timer_heap,
 			  struct pj_timer_entry *entry)
 {
-	dlg_data *dd;
-	tx_data_list_t *tl;
-	pjsip_tx_data *tdata;
-	pj_bool_t final;
-	pj_time_val delay;
+    dlg_data *dd;
+    tx_data_list_t *tl;
+    pjsip_tx_data *tdata;
+    pj_bool_t final;
+    pj_time_val delay;
 
-	PJ_UNUSED_ARG(timer_heap);
+    PJ_UNUSED_ARG(timer_heap);
 
-	dd = (dlg_data*) entry->user_data;
+    dd = (dlg_data*) entry->user_data;
 
-	entry->id = PJ_FALSE;
+    entry->id = PJ_FALSE;
 
-	++dd->uas_state->retransmit_count;
-	if (dd->uas_state->retransmit_count >= 7) {
-		/* If a reliable provisional response is retransmitted for
-		   64*T1 seconds  without reception of a corresponding PRACK,
-		   the UAS SHOULD reject the original request with a 5xx 
-		   response.
-		*/
-		pj_str_t reason = pj_str("Reliable response timed out");
-		pj_status_t status;
+    ++dd->uas_state->retransmit_count;
+    if (dd->uas_state->retransmit_count >= 7) {
+	/* If a reliable provisional response is retransmitted for
+	   64*T1 seconds  without reception of a corresponding PRACK,
+	   the UAS SHOULD reject the original request with a 5xx 
+	   response.
+	*/
+	pj_str_t reason = pj_str("Reliable response timed out");
+	pj_status_t status;
 
-		/* Clear all pending responses */
-		clear_all_responses(dd);
+	/* Clear all pending responses */
+	clear_all_responses(dd);
 
-		/* Send 500 response */
-		status = pjsip_inv_end_session(dd->inv, 500, &reason, &tdata);
-		if (status == PJ_SUCCESS) {
-			pjsip_dlg_send_response(dd->inv->dlg, 
-						dd->inv->invite_tsx,
-						tdata);
-		}
-		return;
+	/* Send 500 response */
+	status = pjsip_inv_end_session(dd->inv, 500, &reason, &tdata);
+	if (status == PJ_SUCCESS) {
+	    pjsip_dlg_send_response(dd->inv->dlg, 
+				    dd->inv->invite_tsx,
+				    tdata);
 	}
+	return;
+    }
 
-	pj_assert(!pj_list_empty(&dd->uas_state->tx_data_list));
-	tl = dd->uas_state->tx_data_list.next;
-	tdata = tl->tdata;
+    pj_assert(!pj_list_empty(&dd->uas_state->tx_data_list));
+    tl = dd->uas_state->tx_data_list.next;
+    tdata = tl->tdata;
 
-	pjsip_tx_data_add_ref(tdata);
-	final = tdata->msg->line.status.code >= 200;
+    pjsip_tx_data_add_ref(tdata);
+    final = tdata->msg->line.status.code >= 200;
 
-	if (dd->uas_state->retransmit_count == 1) {
-		pjsip_tsx_send_msg(dd->inv->invite_tsx, tdata);
-	} else {
-		pjsip_tsx_retransmit_no_state(dd->inv->invite_tsx, tdata);
-	}
+    if (dd->uas_state->retransmit_count == 1) {
+	pjsip_tsx_send_msg(dd->inv->invite_tsx, tdata);
+    } else {
+	pjsip_tsx_retransmit_no_state(dd->inv->invite_tsx, tdata);
+    }
 
-	if (final) {
-		/* This is final response, which will be retransmitted by
-		 * UA layer. There's no more task to do, so clear the
-		 * transmission list and bail out.
-		 */
-		clear_all_responses(dd);
-		return;
-	}
+    if (final) {
+	/* This is final response, which will be retransmitted by
+	 * UA layer. There's no more task to do, so clear the
+	 * transmission list and bail out.
+	 */
+	clear_all_responses(dd);
+	return;
+    }
 
-	/* Schedule next retransmission */
-	if (dd->uas_state->retransmit_count < 6) {
-		delay.sec = 0;
-		delay.msec = (1 << dd->uas_state->retransmit_count) * 
-			     PJSIP_T1_TIMEOUT;
-		pj_time_val_normalize(&delay);
-	} else {
-		delay.sec = 1;
-		delay.msec = 500;
-	}
+    /* Schedule next retransmission */
+    if (dd->uas_state->retransmit_count < 6) {
+	delay.sec = 0;
+	delay.msec = (1 << dd->uas_state->retransmit_count) * 
+		     PJSIP_T1_TIMEOUT;
+	pj_time_val_normalize(&delay);
+    } else {
+	delay.sec = 1;
+	delay.msec = 500;
+    }
 
 
-	pjsip_endpt_schedule_timer(dd->inv->dlg->endpt, 
-				   &dd->uas_state->retransmit_timer,
-				   &delay);
+    pjsip_endpt_schedule_timer(dd->inv->dlg->endpt, 
+			       &dd->uas_state->retransmit_timer,
+			       &delay);
 
-	entry->id = PJ_TRUE;
+    entry->id = PJ_TRUE;
 }
 
+
 /* Clone response. */
 static pjsip_tx_data *clone_tdata(dlg_data *dd,
 				  const pjsip_tx_data *src)
 {
-	pjsip_tx_data *dst;
-	const pjsip_hdr *hsrc;
-	pjsip_msg *msg;
-	pj_status_t status;
+    pjsip_tx_data *dst;
+    const pjsip_hdr *hsrc;
+    pjsip_msg *msg;
+    pj_status_t status;
 
-	status = pjsip_endpt_create_tdata(dd->inv->dlg->endpt, &dst);
-	if (status != PJ_SUCCESS)
-		return NULL;
+    status = pjsip_endpt_create_tdata(dd->inv->dlg->endpt, &dst);
+    if (status != PJ_SUCCESS)
+	return NULL;
 
-	msg = pjsip_msg_create(dst->pool, PJSIP_RESPONSE_MSG);
-	dst->msg = msg;
-	pjsip_tx_data_add_ref(dst);
+    msg = pjsip_msg_create(dst->pool, PJSIP_RESPONSE_MSG);
+    dst->msg = msg;
+    pjsip_tx_data_add_ref(dst);
 
-	/* Duplicate status line */
-	msg->line.status.code = src->msg->line.status.code;
-	pj_strdup(dst->pool, &msg->line.status.reason, 
-		  &src->msg->line.status.reason);
+    /* Duplicate status line */
+    msg->line.status.code = src->msg->line.status.code;
+    pj_strdup(dst->pool, &msg->line.status.reason, 
+	      &src->msg->line.status.reason);
 
-	/* Duplicate all headers */
-	hsrc = src->msg->hdr.next;
-	while (hsrc != &src->msg->hdr) {
-		pjsip_hdr *h = (pjsip_hdr*) pjsip_hdr_clone(dst->pool, hsrc);
-		pjsip_msg_add_hdr(msg, h);
-		hsrc = hsrc->next;
-	}
+    /* Duplicate all headers */
+    hsrc = src->msg->hdr.next;
+    while (hsrc != &src->msg->hdr) {
+	pjsip_hdr *h = (pjsip_hdr*) pjsip_hdr_clone(dst->pool, hsrc);
+	pjsip_msg_add_hdr(msg, h);
+	hsrc = hsrc->next;
+    }
 
-	/* Duplicate message body */
-	if (src->msg->body)
-		msg->body = pjsip_msg_body_clone(dst->pool, src->msg->body);
+    /* Duplicate message body */
+    if (src->msg->body)
+	msg->body = pjsip_msg_body_clone(dst->pool, src->msg->body);
 
-	PJ_LOG(5,(dd->inv->dlg->obj_name,
-		 "Reliable response %s created",
-		 pjsip_tx_data_get_info(dst)));
+    PJ_LOG(5,(dd->inv->dlg->obj_name,
+	     "Reliable response %s created",
+	     pjsip_tx_data_get_info(dst)));
 
-	return dst;
+    return dst;
 }
 
-/* Check if pending response has SDP */
+
+/* Check if any pending response in transmission list has SDP */
 static pj_bool_t has_sdp(dlg_data *dd)
 {
-	tx_data_list_t *tl;
+    tx_data_list_t *tl;
 
-	tl = dd->uas_state->tx_data_list.next;
-	while (tl != &dd->uas_state->tx_data_list) {
-		if (tl->tdata->msg->body)
-			return PJ_TRUE;
-		tl = tl->next;
-	}
+    tl = dd->uas_state->tx_data_list.next;
+    while (tl != &dd->uas_state->tx_data_list) {
+	    if (tl->tdata->msg->body)
+		    return PJ_TRUE;
+	    tl = tl->next;
+    }
 
-	return PJ_FALSE;
+    return PJ_FALSE;
 }
 
 
@@ -634,212 +641,221 @@
 PJ_DEF(pj_status_t) pjsip_100rel_tx_response(pjsip_inv_session *inv,
 					     pjsip_tx_data *tdata)
 {
-	pjsip_cseq_hdr *cseq_hdr;
-	pjsip_generic_string_hdr *rseq_hdr;
-	pjsip_require_hdr *req_hdr;
-	int status_code;
-	dlg_data *dd;
-	pjsip_tx_data *old_tdata;
-	pj_status_t status;
+    pjsip_cseq_hdr *cseq_hdr;
+    pjsip_generic_string_hdr *rseq_hdr;
+    pjsip_require_hdr *req_hdr;
+    int status_code;
+    dlg_data *dd;
+    pjsip_tx_data *old_tdata;
+    pj_status_t status;
+    
+    PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_RESPONSE_MSG,
+		     PJSIP_ENOTRESPONSEMSG);
+    
+    status_code = tdata->msg->line.status.code;
+    
+    /* 100 response doesn't need PRACK */
+    if (status_code == 100)
+	return pjsip_dlg_send_response(inv->dlg, inv->invite_tsx, tdata);
+    
 
-	PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_RESPONSE_MSG,
-			 PJ_EINVALIDOP);
+    /* Get the 100rel data attached to this dialog */
+    dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id];
+    PJ_ASSERT_RETURN(dd != NULL, PJ_EINVALIDOP);
+    
+    
+    /* Clone tdata.
+     * We need to clone tdata because we may need to keep it in our
+     * retransmission list, while the original dialog may modify it
+     * if it wants to send another response.
+     */
+    old_tdata = tdata;
+    tdata = clone_tdata(dd, old_tdata);
+    pjsip_tx_data_dec_ref(old_tdata);
+    
 
-	status_code = tdata->msg->line.status.code;
+    /* Get CSeq header, and make sure this is INVITE response */
+    cseq_hdr = (pjsip_cseq_hdr*)
+	        pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
+    PJ_ASSERT_RETURN(cseq_hdr != NULL, PJ_EBUG);
+    PJ_ASSERT_RETURN(cseq_hdr->method.id == PJSIP_INVITE_METHOD, 
+	PJ_EINVALIDOP);
+    
+    /* Remove existing Require header */
+    req_hdr = find_req_hdr(tdata->msg);
+    if (req_hdr) {
+	pj_list_erase(req_hdr);
+    }
+    
+    /* Remove existing RSeq header */
+    rseq_hdr = (pjsip_generic_string_hdr*)
+	pjsip_msg_find_hdr_by_name(tdata->msg, &RSEQ, NULL);
+    if (rseq_hdr)
+	pj_list_erase(rseq_hdr);
+    
+    /* Different treatment for provisional and final response */
+    if (status_code/100 == 2) {
+	
+	/* RFC 3262 Section 3: UAS Behavior:
+    
+	  The UAS MAY send a final response to the initial request 
+	  before having received PRACKs for all unacknowledged 
+	  reliable provisional responses, unless the final response 
+	  is 2xx and any of the unacknowledged reliable provisional 
+	  responses contained a session description.  In that case, 
+	  it MUST NOT send a final response until those provisional 
+	  responses are acknowledged.
+	*/
+	
+	if (dd->uas_state && has_sdp(dd)) {
+	    /* Yes we have transmitted 1xx with SDP reliably.
+	     * In this case, must queue the 2xx response.
+	     */
+	    tx_data_list_t *tl;
+	    
+	    tl = PJ_POOL_ZALLOC_T(tdata->pool, tx_data_list_t);
+	    tl->tdata = tdata;
+	    tl->rseq = (pj_uint32_t)-1;
+	    pj_list_push_back(&dd->uas_state->tx_data_list, tl);
+	    
+	    /* Will send later */
+	    status = PJ_SUCCESS;
+	    
+	    PJ_LOG(4,(dd->inv->dlg->obj_name, 
+		      "2xx response will be sent after PRACK"));
+	    
+	} else if (dd->uas_state) {
+	    /* 
+	    RFC 3262 Section 3: UAS Behavior:
 
-	/* 100 response doesn't need PRACK */
-	if (status_code == 100)
-		return pjsip_dlg_send_response(inv->dlg, inv->invite_tsx, tdata);
-
-	/* Get the dialog data */
-	dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id];
-	PJ_ASSERT_RETURN(dd != NULL, PJ_EINVALIDOP);
-
-
-	/* Clone tdata */
-	old_tdata = tdata;
-	tdata = clone_tdata(dd, old_tdata);
-	pjsip_tx_data_dec_ref(old_tdata);
-
-	/* Get CSeq header */
-	cseq_hdr = (pjsip_cseq_hdr*)
-		   pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
-	PJ_ASSERT_RETURN(cseq_hdr != NULL, PJ_EBUG);
-	PJ_ASSERT_RETURN(cseq_hdr->method.id == PJSIP_INVITE_METHOD, 
-			 PJ_EINVALIDOP);
-
-	/* Remove existing Require header */
-	req_hdr = find_req_hdr(tdata->msg);
-	if (req_hdr) {
-		pj_list_erase(req_hdr);
-	}
-
-	/* Remove existing RSeq header */
-	rseq_hdr = (pjsip_generic_string_hdr*)
-		   pjsip_msg_find_hdr_by_name(tdata->msg, &RSEQ, NULL);
-	if (rseq_hdr)
-		pj_list_erase(rseq_hdr);
-
-	/* Different treatment for provisional and final response */
-	if (status_code/100 == 2) {
-
-		/* RFC 3262 Section 3: UAS Behavior:
-
-		The UAS MAY send a final response to the initial request 
-		before having received PRACKs for all unacknowledged 
-		reliable provisional responses, unless the final response 
-		is 2xx and any of the unacknowledged reliable provisional 
-		responses contained a session description.  In that case, 
-		it MUST NOT send a final response until those provisional 
-		responses are acknowledged.
-		*/
-
-		if (dd->uas_state && has_sdp(dd)) {
-			/* Yes we have transmitted 1xx with SDP reliably.
-			 * In this case, must queue the 2xx response.
-			 */
-			tx_data_list_t *tl;
-
-			tl = PJ_POOL_ZALLOC_T(tdata->pool, tx_data_list_t);
-			tl->tdata = tdata;
-			tl->rseq = (pj_uint32_t)-1;
-			pj_list_push_back(&dd->uas_state->tx_data_list, tl);
-
-			/* Will send later */
-			status = PJ_SUCCESS;
-
-			PJ_LOG(4,(dd->inv->dlg->obj_name, 
-				  "2xx response will be sent after PRACK"));
-
-		} else if (dd->uas_state) {
-			/* 
-			If the UAS does send a final response when reliable
-			responses are still unacknowledged, it SHOULD NOT 
-			continue to retransmit the unacknowledged reliable
-			provisional responses, but it MUST be prepared to 
-			process PRACK requests for those outstanding 
-			responses.
-			*/
-			
-			PJ_LOG(4,(dd->inv->dlg->obj_name, 
-				  "No SDP sent so far, sending 2xx now"));
-
-			/* Cancel the retransmit timer */
-			if (dd->uas_state->retransmit_timer.id) {
-				pjsip_endpt_cancel_timer(dd->inv->dlg->endpt,
-							 &dd->uas_state->retransmit_timer);
-				dd->uas_state->retransmit_timer.id = PJ_FALSE;
-			}
-
-			/* Clear all pending responses (drop 'em) */
-			clear_all_responses(dd);
-
-			/* And transmit the 2xx response */
-			status=pjsip_dlg_send_response(inv->dlg, 
-						       inv->invite_tsx, tdata);
-
-		} else {
-			/* We didn't send any reliable provisional response */
-
-			/* Transmit the 2xx response */
-			status=pjsip_dlg_send_response(inv->dlg, 
-						       inv->invite_tsx, tdata);
-		}
-
-	} else if (status_code >= 300) {
-
-		/* 
-		If the UAS does send a final response when reliable
-		responses are still unacknowledged, it SHOULD NOT 
-		continue to retransmit the unacknowledged reliable
-		provisional responses, but it MUST be prepared to 
-		process PRACK requests for those outstanding 
-		responses.
-		*/
-
-		/* Cancel the retransmit timer */
-		if (dd->uas_state && dd->uas_state->retransmit_timer.id) {
-			pjsip_endpt_cancel_timer(dd->inv->dlg->endpt,
-						 &dd->uas_state->retransmit_timer);
-			dd->uas_state->retransmit_timer.id = PJ_FALSE;
-
-			/* Clear all pending responses (drop 'em) */
-			clear_all_responses(dd);
-		}
-
-		/* And transmit the 2xx response */
-		status=pjsip_dlg_send_response(inv->dlg, 
-					       inv->invite_tsx, tdata);
-
+	    If the UAS does send a final response when reliable
+	    responses are still unacknowledged, it SHOULD NOT 
+	    continue to retransmit the unacknowledged reliable
+	    provisional responses, but it MUST be prepared to 
+	    process PRACK requests for those outstanding 
+	    responses.
+	    */
+	    
+	    PJ_LOG(4,(dd->inv->dlg->obj_name, 
+		      "No SDP sent so far, sending 2xx now"));
+	    
+	    /* Cancel the retransmit timer */
+	    if (dd->uas_state->retransmit_timer.id) {
+		pjsip_endpt_cancel_timer(dd->inv->dlg->endpt,
+					 &dd->uas_state->retransmit_timer);
+		dd->uas_state->retransmit_timer.id = PJ_FALSE;
+	    }
+	    
+	    /* Clear all pending responses (drop 'em) */
+	    clear_all_responses(dd);
+	    
+	    /* And transmit the 2xx response */
+	    status=pjsip_dlg_send_response(inv->dlg, 
+					   inv->invite_tsx, tdata);
+	    
 	} else {
-		/*
-		 * This is provisional response.
-		 */
-		char rseq_str[32];
-		pj_str_t rseq;
-		tx_data_list_t *tl;
-
-		/* Create UAS state if we don't have one */
-		if (dd->uas_state == NULL) {
-			dd->uas_state = PJ_POOL_ZALLOC_T(inv->dlg->pool,
-							 uas_state_t);
-			dd->uas_state->cseq = cseq_hdr->cseq;
-			dd->uas_state->rseq = pj_rand() % 0x7FFF;
-			pj_list_init(&dd->uas_state->tx_data_list);
-			dd->uas_state->retransmit_timer.user_data = dd;
-			dd->uas_state->retransmit_timer.cb = &on_retransmit;
-		}
-
-		/* Check that CSeq match */
-		PJ_ASSERT_RETURN(cseq_hdr->cseq == dd->uas_state->cseq,
-				 PJ_EINVALIDOP);
-
-		/* Add Require header */
-		req_hdr = pjsip_require_hdr_create(tdata->pool);
-		req_hdr->count = 1;
-		req_hdr->values[0] = tag_100rel;
-		pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)req_hdr);
-
-		/* Add RSeq header */
-		pj_ansi_snprintf(rseq_str, sizeof(rseq_str), "%u",
-				 dd->uas_state->rseq);
-		rseq = pj_str(rseq_str);
-		rseq_hdr = pjsip_generic_string_hdr_create(tdata->pool, 
-							   &RSEQ, &rseq);
-		pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)rseq_hdr);
-
-		/* Create list entry for this response */
-		tl = PJ_POOL_ZALLOC_T(tdata->pool, tx_data_list_t);
-		tl->tdata = tdata;
-		tl->rseq = dd->uas_state->rseq++;
-
-		/* Add to queue if there's pending response, otherwise
-		 * transmit immediately.
-		 */
-		if (!pj_list_empty(&dd->uas_state->tx_data_list)) {
-			
-			int code = tdata->msg->line.status.code;
-
-			/* Will send later */
-			pj_list_push_back(&dd->uas_state->tx_data_list, tl);
-			status = PJ_SUCCESS;
-
-			PJ_LOG(4,(dd->inv->dlg->obj_name, 
-				  "Reliable %d response enqueued (%d pending)", 
-				  code, pj_list_size(&dd->uas_state->tx_data_list)));
-
-		} else {
-			pj_list_push_back(&dd->uas_state->tx_data_list, tl);
-
-			dd->uas_state->retransmit_count = 0;
-			on_retransmit(NULL, &dd->uas_state->retransmit_timer);
-			status = PJ_SUCCESS;
-		}
-
+	    /* We didn't send any reliable provisional response */
+	    
+	    /* Transmit the 2xx response */
+	    status=pjsip_dlg_send_response(inv->dlg, 
+					   inv->invite_tsx, tdata);
 	}
+	
+    } else if (status_code >= 300) {
+	
+	/* 
+	RFC 3262 Section 3: UAS Behavior:
 
-	return status;
+	If the UAS does send a final response when reliable
+	responses are still unacknowledged, it SHOULD NOT 
+	continue to retransmit the unacknowledged reliable
+	provisional responses, but it MUST be prepared to 
+	process PRACK requests for those outstanding 
+	responses.
+	*/
+	
+	/* Cancel the retransmit timer */
+	if (dd->uas_state && dd->uas_state->retransmit_timer.id) {
+	    pjsip_endpt_cancel_timer(dd->inv->dlg->endpt,
+				     &dd->uas_state->retransmit_timer);
+	    dd->uas_state->retransmit_timer.id = PJ_FALSE;
+	    
+	    /* Clear all pending responses (drop 'em) */
+	    clear_all_responses(dd);
+	}
+	
+	/* And transmit the 2xx response */
+	status=pjsip_dlg_send_response(inv->dlg, 
+				       inv->invite_tsx, tdata);
+	
+    } else {
+	/*
+	 * This is provisional response.
+	 */
+	char rseq_str[32];
+	pj_str_t rseq;
+	tx_data_list_t *tl;
+	
+	/* Create UAS state if we don't have one */
+	if (dd->uas_state == NULL) {
+	    dd->uas_state = PJ_POOL_ZALLOC_T(inv->dlg->pool,
+					     uas_state_t);
+	    dd->uas_state->cseq = cseq_hdr->cseq;
+	    dd->uas_state->rseq = pj_rand() % 0x7FFF;
+	    pj_list_init(&dd->uas_state->tx_data_list);
+	    dd->uas_state->retransmit_timer.user_data = dd;
+	    dd->uas_state->retransmit_timer.cb = &on_retransmit;
+	}
+	
+	/* Check that CSeq match */
+	PJ_ASSERT_RETURN(cseq_hdr->cseq == dd->uas_state->cseq,
+			 PJ_EINVALIDOP);
+	
+	/* Add Require header */
+	req_hdr = pjsip_require_hdr_create(tdata->pool);
+	req_hdr->count = 1;
+	req_hdr->values[0] = tag_100rel;
+	pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)req_hdr);
+	
+	/* Add RSeq header */
+	pj_ansi_snprintf(rseq_str, sizeof(rseq_str), "%u",
+			 dd->uas_state->rseq);
+	rseq = pj_str(rseq_str);
+	rseq_hdr = pjsip_generic_string_hdr_create(tdata->pool, 
+						   &RSEQ, &rseq);
+	pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)rseq_hdr);
+	
+	/* Create list entry for this response */
+	tl = PJ_POOL_ZALLOC_T(tdata->pool, tx_data_list_t);
+	tl->tdata = tdata;
+	tl->rseq = dd->uas_state->rseq++;
+	
+	/* Add to queue if there's pending response, otherwise
+	 * transmit immediately.
+	 */
+	if (!pj_list_empty(&dd->uas_state->tx_data_list)) {
+	    
+	    int code = tdata->msg->line.status.code;
+	    
+	    /* Will send later */
+	    pj_list_push_back(&dd->uas_state->tx_data_list, tl);
+	    status = PJ_SUCCESS;
+	    
+	    PJ_LOG(4,(dd->inv->dlg->obj_name, 
+		      "Reliable %d response enqueued (%d pending)", 
+		      code, pj_list_size(&dd->uas_state->tx_data_list)));
+	    
+	} else {
+	    pj_list_push_back(&dd->uas_state->tx_data_list, tl);
+	    
+	    dd->uas_state->retransmit_count = 0;
+	    on_retransmit(NULL, &dd->uas_state->retransmit_timer);
+	    status = PJ_SUCCESS;
+	}
+	
+    }
+    
+    return status;
 }
 
 
-#endif	/* PJSIP_HAS_100REL */
diff --git a/pjsip/src/pjsip-ua/sip_inv.c b/pjsip/src/pjsip-ua/sip_inv.c
index 0243452..6c7fe22 100644
--- a/pjsip/src/pjsip-ua/sip_inv.c
+++ b/pjsip/src/pjsip-ua/sip_inv.c
@@ -31,6 +31,28 @@
 #include <pj/os.h>
 #include <pj/log.h>
 
+/* 
+ * Note on offer/answer:
+ *
+ * The offer/answer framework in this implementation assumes the occurence
+ * of SDP in a particular request/response according to this table:
+
+		  offer   answer    Note:
+    ========================================================================
+    INVITE	    X		    INVITE may contain offer
+    18x/INVITE	    X	    X	    Response may contain offer or answer
+    2xx/INVITE	    X	    X	    Response may contain offer or answer
+    ACK			    X	    ACK may contain answer
+
+    PRACK		    X	    PRACK can only contain answer
+    2xx/PRACK	    		    Response may not have offer nor answer
+
+    UPDATE	    X		    UPDATE may only contain offer
+    2xx/UPDATE		    X	    Response may only contain answer
+    ========================================================================
+
+  *
+  */
 
 #define THIS_FILE	"sip_inv.c"
 
@@ -46,6 +68,13 @@
     "TERMINATED",
 };
 
+/* UPDATE method */
+const pjsip_method pjsip_update_method =
+{
+    PJSIP_OTHER_METHOD,
+    { "UPDATE", 6 }
+};
+
 /*
  * Static prototypes.
  */
@@ -66,6 +95,9 @@
 static pj_status_t inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv,
 						  pjsip_transaction *tsx,
 						  pjsip_rx_data *rdata);
+static pj_status_t inv_negotiate_sdp( pjsip_inv_session *inv );
+static pjsip_msg_body *create_sdp_body(pj_pool_t *pool,
+				       const pjmedia_sdp_session *c_sdp);
 static pj_status_t process_answer( pjsip_inv_session *inv,
 				   int st_code,
 				   pjsip_tx_data *tdata,
@@ -119,10 +151,11 @@
  */
 static pj_status_t mod_inv_load(pjsip_endpoint *endpt)
 {
-    pj_str_t allowed[] = {{"INVITE", 6}, {"ACK",3}, {"BYE",3}, {"CANCEL",6}};
+    pj_str_t allowed[] = {{"INVITE", 6}, {"ACK",3}, {"BYE",3}, {"CANCEL",6},
+			    { "UPDATE", 6}};
     pj_str_t accepted = { "application/sdp", 15 };
 
-    /* Register supported methods: INVITE, ACK, BYE, CANCEL */
+    /* Register supported methods: INVITE, ACK, BYE, CANCEL, UPDATE */
     pjsip_endpt_add_capability(endpt, &mod_inv.mod, PJSIP_H_ALLOW, NULL,
 			       PJ_ARRAY_SIZE(allowed), allowed);
 
@@ -186,6 +219,11 @@
     if (inv->state == PJSIP_INV_STATE_DISCONNECTED &&
 	prev_state != PJSIP_INV_STATE_DISCONNECTED) 
     {
+	if (inv->last_ack) {
+	    pjsip_tx_data_dec_ref(inv->last_ack);
+	    inv->last_ack = NULL;
+	}
+	pjsip_100rel_end_session(inv);
 	pjsip_dlg_dec_session(inv->dlg, &mod_inv.mod);
     }
 }
@@ -209,27 +247,103 @@
 }
 
 
+/*
+ * Check if outgoing request needs to have SDP answer.
+ * This applies for both ACK and PRACK requests.
+ */
+static pjmedia_sdp_session *inv_has_pending_answer(pjsip_inv_session *inv,
+						   pjsip_transaction *tsx)
+{
+    pjmedia_sdp_neg_state neg_state;
+    pjmedia_sdp_session *sdp = NULL;
+    pj_status_t status;
+
+    /* If SDP negotiator is ready, start negotiation. */
+
+    /* Start nego when appropriate. */
+    neg_state = inv->neg ? pjmedia_sdp_neg_get_state(inv->neg) :
+		PJMEDIA_SDP_NEG_STATE_NULL;
+
+    if (neg_state == PJMEDIA_SDP_NEG_STATE_DONE) {
+
+	/* Nothing to do */
+
+    } else if (neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO &&
+	       pjmedia_sdp_neg_has_local_answer(inv->neg) )
+    {
+	struct tsx_inv_data *tsx_inv_data;
+
+	/* Get invite session's transaction data */
+	tsx_inv_data = (struct tsx_inv_data*) tsx->mod_data[mod_inv.mod.id];
+
+	status = inv_negotiate_sdp(inv);
+	if (status != PJ_SUCCESS)
+	    return NULL;
+	
+	/* Mark this transaction has having SDP offer/answer done. */
+	tsx_inv_data->sdp_done = 1;
+
+	status = pjmedia_sdp_neg_get_active_local(inv->neg, &sdp);
+
+    } else {
+	/* This remark is only valid for ACK.
+	PJ_LOG(4,(inv->dlg->obj_name,
+		  "FYI, the SDP negotiator state (%s) is in a mess "
+		  "when sending this ACK/PRACK request",
+		  pjmedia_sdp_neg_state_str(neg_state)));
+	 */
+    }
+
+    return sdp;
+}
+
 
 /*
  * Send ACK for 2xx response.
  */
 static pj_status_t inv_send_ack(pjsip_inv_session *inv, pjsip_rx_data *rdata)
 {
-    pjsip_tx_data *tdata;
     pj_status_t status;
 
     PJ_LOG(5,(inv->obj_name, "Received %s, sending ACK",
 	      pjsip_rx_data_get_info(rdata)));
 
-    status = pjsip_dlg_create_request(inv->dlg, pjsip_get_ack_method(), 
-				      rdata->msg_info.cseq->cseq, &tdata);
-    if (status != PJ_SUCCESS) {
-	/* Better luck next time */
-	pj_assert(!"Unable to create ACK!");
-	return status;
+    /* Check if we have cached ACK request */
+    if (inv->last_ack && rdata->msg_info.cseq->cseq == inv->last_ack_cseq) {
+	pjsip_tx_data_add_ref(inv->last_ack);
+    } else {
+	pjmedia_sdp_session *sdp = NULL;
+
+	/* Destroy last_ack */
+	if (inv->last_ack) {
+	    pjsip_tx_data_dec_ref(inv->last_ack);
+	    inv->last_ack = NULL;
+	}
+
+	/* Create new ACK request */
+	status = pjsip_dlg_create_request(inv->dlg, pjsip_get_ack_method(), 
+					  rdata->msg_info.cseq->cseq, 
+					  &inv->last_ack);
+	if (status != PJ_SUCCESS) {
+	    /* Better luck next time */
+	    pj_assert(!"Unable to create ACK!");
+	    return status;
+	}
+
+	/* See if we have pending SDP answer to send */
+	sdp = inv_has_pending_answer(inv, inv->invite_tsx);
+	if (sdp) {
+	    inv->last_ack->msg->body=create_sdp_body(inv->last_ack->pool, sdp);
+	}
+
+
+	/* Keep this for subsequent response retransmission */
+	inv->last_ack_cseq = rdata->msg_info.cseq->cseq;
+	pjsip_tx_data_add_ref(inv->last_ack);
     }
 
-    status = pjsip_dlg_send_request(inv->dlg, tdata, -1, NULL);
+    /* Send ACK */
+    status = pjsip_dlg_send_request(inv->dlg, inv->last_ack, -1, NULL);
     if (status != PJ_SUCCESS) {
 	/* Better luck next time */
 	pj_assert(!"Unable to send ACK!");
@@ -492,14 +606,6 @@
     if (options & PJSIP_INV_REQUIRE_100REL)
 	options |= PJSIP_INV_SUPPORT_100REL;
 
-#if !PJSIP_HAS_100REL
-    /* options cannot specify 100rel if 100rel is disabled */
-    PJ_ASSERT_RETURN(
-	(options & (PJSIP_INV_REQUIRE_100REL | PJSIP_INV_SUPPORT_100REL))==0,
-	PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_EXTENSION));
-    
-#endif
-
     if (options & PJSIP_INV_REQUIRE_TIMER)
 	options |= PJSIP_INV_SUPPORT_TIMER;
 
@@ -538,10 +644,8 @@
     /* Increment dialog session */
     pjsip_dlg_inc_session(dlg, &mod_inv.mod);
 
-#if PJSIP_HAS_100REL
     /* Create 100rel handler */
     pjsip_100rel_attach(inv);
-#endif
 
     /* Done */
     *p_inv = inv;
@@ -943,14 +1047,6 @@
     if (options & PJSIP_INV_REQUIRE_100REL)
 	options |= PJSIP_INV_SUPPORT_100REL;
 
-#if !PJSIP_HAS_100REL
-    /* options cannot specify 100rel if 100rel is disabled */
-    PJ_ASSERT_RETURN(
-	(options & (PJSIP_INV_REQUIRE_100REL | PJSIP_INV_SUPPORT_100REL))==0,
-	PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_EXTENSION));
-    
-#endif
-
     if (options & PJSIP_INV_REQUIRE_TIMER)
 	options |= PJSIP_INV_SUPPORT_TIMER;
 
@@ -1020,12 +1116,10 @@
     tsx_inv_data->inv = inv;
     inv->invite_tsx->mod_data[mod_inv.mod.id] = tsx_inv_data;
 
-#if PJSIP_HAS_100REL
     /* Create 100rel handler */
     if (inv->options & PJSIP_INV_REQUIRE_100REL) {
 	    pjsip_100rel_attach(inv);
     }
-#endif
 
     /* Done */
     pjsip_dlg_dec_lock(dlg);
@@ -1239,7 +1333,7 @@
 
 
 /*
- * Negotiate SDP.
+ * Initiate SDP negotiation in the SDP negotiator.
  */
 static pj_status_t inv_negotiate_sdp( pjsip_inv_session *inv )
 {
@@ -1781,16 +1875,84 @@
  */
 PJ_DEF(pj_status_t) pjsip_inv_update (	pjsip_inv_session *inv,
 					const pj_str_t *new_contact,
-					const pjmedia_sdp_session *new_offer,
+					const pjmedia_sdp_session *offer,
 					pjsip_tx_data **p_tdata )
 {
-    PJ_UNUSED_ARG(inv);
-    PJ_UNUSED_ARG(new_contact);
-    PJ_UNUSED_ARG(new_offer);
-    PJ_UNUSED_ARG(p_tdata);
+    pjsip_contact_hdr *contact_hdr = NULL;
+    pjsip_tx_data *tdata = NULL;
+    pjmedia_sdp_session *sdp_copy;
+    pj_status_t status = PJ_SUCCESS;
 
-    PJ_TODO(CREATE_UPDATE_REQUEST);
-    return PJ_ENOTSUP;
+    /* Verify arguments. */
+    PJ_ASSERT_RETURN(inv && p_tdata && offer, PJ_EINVAL);
+
+    /* Dialog must have been established */
+    PJ_ASSERT_RETURN(inv->dlg->state == PJSIP_DIALOG_STATE_ESTABLISHED,
+		     PJ_EINVALIDOP);
+
+    /* Invite session must not have been disconnected */
+    PJ_ASSERT_RETURN(inv->state < PJSIP_INV_STATE_DISCONNECTED,
+		     PJ_EINVALIDOP);
+
+    /* Lock dialog. */
+    pjsip_dlg_inc_lock(inv->dlg);
+
+    /* Process offer */
+    if (pjmedia_sdp_neg_get_state(inv->neg)!=PJMEDIA_SDP_NEG_STATE_DONE) {
+	PJ_LOG(4,(inv->dlg->obj_name, 
+		  "Invalid SDP offer/answer state for UPDATE"));
+	status = PJ_EINVALIDOP;
+	goto on_error;
+    }
+
+    status = pjmedia_sdp_neg_modify_local_offer(inv->pool,inv->neg,
+						offer);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+
+    /* Update Contact if required */
+    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_contact_hdr*)
+		      pjsip_parse_hdr(inv->dlg->pool, &STR_CONTACT, 
+				      tmp.ptr, tmp.slen, NULL);
+	if (!contact_hdr) {
+	    status = PJSIP_EINVALIDURI;
+	    goto on_error;
+	}
+
+	inv->dlg->local.contact = contact_hdr;
+    }
+
+    /* Create request */
+    status = pjsip_dlg_create_request(inv->dlg, &pjsip_update_method,
+				      -1, &tdata);
+    if (status != PJ_SUCCESS)
+	    goto on_error;
+
+    /* Attach SDP body */
+    sdp_copy = pjmedia_sdp_session_clone(tdata->pool, offer);
+    pjsip_create_sdp_body(tdata->pool, sdp_copy, &tdata->msg->body);
+
+    /* Unlock dialog. */
+    pjsip_dlg_dec_lock(inv->dlg);
+
+    *p_tdata = tdata;
+
+    return PJ_SUCCESS;
+
+on_error:
+    if (tdata)
+	pjsip_tx_data_dec_ref(tdata);
+
+    /* Unlock dialog. */
+    pjsip_dlg_dec_lock(inv->dlg);
+
+    return status;
 }
 
 /*
@@ -1832,13 +1994,11 @@
 			  && (cseq->cseq == inv->invite_tsx->cseq),
 			 PJ_EINVALIDOP);
 
-#if PJSIP_HAS_100REL
 	if (inv->options & PJSIP_INV_REQUIRE_100REL) {
-		status = pjsip_100rel_tx_response(inv, tdata);
+	    status = pjsip_100rel_tx_response(inv, tdata);
 	} else 
-#endif
 	{
-		status = pjsip_dlg_send_response(inv->dlg, inv->invite_tsx, tdata);
+	    status = pjsip_dlg_send_response(inv->dlg, inv->invite_tsx, tdata);
 	}
 
 	if (status != PJ_SUCCESS)
@@ -1987,6 +2147,188 @@
 }
 
 /*
+ * Respond to incoming UPDATE request.
+ */
+static void inv_respond_incoming_update(pjsip_inv_session *inv,
+					pjsip_rx_data *rdata)
+{
+    pjmedia_sdp_neg_state neg_state;
+    pj_status_t status;
+    pjsip_tx_data *tdata = NULL;
+
+    neg_state = pjmedia_sdp_neg_get_state(inv->neg);
+
+    /* Send 491 if we receive UPDATE while we're waiting for an answer */
+    if (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) {
+	status = pjsip_dlg_create_response(inv->dlg, rdata, 
+					   PJSIP_SC_REQUEST_PENDING, NULL,
+					   &tdata);
+    }
+    /* Send 500 with Retry-After header set randomly between 0 and 10 if we 
+     * receive UPDATE while we haven't sent answer.
+     */
+    else if (neg_state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER ||
+	     neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO) {
+	status = pjsip_dlg_create_response(inv->dlg, rdata, 
+					   PJSIP_SC_INTERNAL_SERVER_ERROR,
+					   NULL, &tdata);
+
+    } else {
+	/* We receive new offer from remote */
+	inv_check_sdp_in_incoming_msg(inv, pjsip_rdata_get_tsx(rdata), rdata);
+
+	/* Application MUST have supplied the answer by now.
+	 * If so, negotiate the SDP.
+	 */
+	neg_state = pjmedia_sdp_neg_get_state(inv->neg);
+	if (neg_state != PJMEDIA_SDP_NEG_STATE_WAIT_NEGO ||
+	    (status=inv_negotiate_sdp(inv)) != PJ_SUCCESS)
+	{
+	    /* Negotiation has failed */
+	    status = pjsip_dlg_create_response(inv->dlg, rdata, 
+					       PJSIP_SC_NOT_ACCEPTABLE_HERE,
+					       NULL, &tdata);
+	} else {
+	    /* New media has been negotiated successfully, send 200/OK */
+	    status = pjsip_dlg_create_response(inv->dlg, rdata, 
+					       PJSIP_SC_OK, NULL, &tdata);
+	    if (status == PJ_SUCCESS) {
+		pjmedia_sdp_session *sdp;
+		status = pjmedia_sdp_neg_get_active_local(inv->neg, &sdp);
+		if (status == PJ_SUCCESS)
+		    tdata->msg->body = create_sdp_body(tdata->pool, sdp);
+	    }
+	}
+    }
+
+    if (status != PJ_SUCCESS) {
+	if (tdata != NULL) {
+	    pjsip_tx_data_dec_ref(tdata);
+	    tdata = NULL;
+	}
+	return;
+    }
+
+    pjsip_dlg_send_response(inv->dlg, pjsip_rdata_get_tsx(rdata), tdata);
+}
+
+
+/*
+ * Handle incoming response to UAC UPDATE request.
+ */
+static void inv_handle_update_response( pjsip_inv_session *inv,
+					pjsip_event *e)
+{
+    pjsip_transaction *tsx = e->body.tsx_state.tsx;
+    struct tsx_inv_data *tsx_inv_data = NULL;
+    pj_status_t status = -1;
+
+    /* Process 2xx response */
+    if (tsx->state == PJSIP_TSX_STATE_COMPLETED &&
+	tsx->status_code/100 == 2 &&
+	e->body.tsx_state.src.rdata->msg_info.msg->body)
+    {
+	status = inv_check_sdp_in_incoming_msg(inv, tsx, 
+					    e->body.tsx_state.src.rdata);
+
+    } else {
+	/* Get/attach invite session's transaction data */
+	tsx_inv_data = (struct tsx_inv_data*)tsx->mod_data[mod_inv.mod.id];
+	if (tsx_inv_data == NULL) {
+	    tsx_inv_data=PJ_POOL_ZALLOC_T(tsx->pool, struct tsx_inv_data);
+	    tsx_inv_data->inv = inv;
+	    tsx->mod_data[mod_inv.mod.id] = tsx_inv_data;
+	}
+    }
+
+    /* Otherwise if we don't get successful response, cancel
+     * our negotiator.
+     */
+    if (status != PJ_SUCCESS &&
+	pjmedia_sdp_neg_get_state(inv->neg)==PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER &&
+	tsx_inv_data && tsx_inv_data->sdp_done == PJ_FALSE) 
+    {
+	pjmedia_sdp_neg_cancel_offer(inv->neg);
+
+	/* Prevent from us cancelling different offer! */
+	tsx_inv_data->sdp_done = PJ_TRUE;
+    }
+}
+
+
+/*
+ * Handle incoming reliable response.
+ */
+static void inv_handle_incoming_reliable_response(pjsip_inv_session *inv,
+						  pjsip_rx_data *rdata)
+{
+    pjsip_tx_data *tdata;
+    pjmedia_sdp_session *sdp;
+    pj_status_t status;
+
+    /* Create PRACK */
+    status = pjsip_100rel_create_prack(inv, rdata, &tdata);
+    if (status != PJ_SUCCESS)
+	return;
+
+    /* See if we need to attach SDP answer on the PRACK request */
+    sdp = inv_has_pending_answer(inv, pjsip_rdata_get_tsx(rdata));
+    if (sdp) {
+	tdata->msg->body = create_sdp_body(tdata->pool, sdp);
+    }
+
+    /* Send PRACK (must be using 100rel module!) */
+    pjsip_100rel_send_prack(inv, tdata);
+}
+
+
+/*
+ * Handle incoming PRACK.
+ */
+static void inv_respond_incoming_prack(pjsip_inv_session *inv,
+				       pjsip_rx_data *rdata)
+{
+    pj_status_t status;
+
+    /* Run through 100rel module to see if we can accept this
+     * PRACK request. The 100rel will send 200/OK to PRACK request.
+     */
+    status = pjsip_100rel_on_rx_prack(inv, rdata);
+    if (status != PJ_SUCCESS)
+	return;
+
+    /* Now check for SDP answer in the PRACK request */
+    if (rdata->msg_info.msg->body) {
+	status = inv_check_sdp_in_incoming_msg(inv, 
+					pjsip_rdata_get_tsx(rdata), rdata);
+    } else {
+	/* No SDP body */
+	status = -1;
+    }
+
+    /* If SDP negotiation has been successful, also mark the
+     * SDP negotiation flag in the invite transaction to be
+     * done too.
+     */
+    if (status == PJ_SUCCESS && inv->invite_tsx) {
+	struct tsx_inv_data *tsx_inv_data;
+
+	/* Get/attach invite session's transaction data */
+	tsx_inv_data = (struct tsx_inv_data*) 
+		       inv->invite_tsx->mod_data[mod_inv.mod.id];
+	if (tsx_inv_data == NULL) {
+	    tsx_inv_data = PJ_POOL_ZALLOC_T(inv->invite_tsx->pool, 
+					    struct tsx_inv_data);
+	    tsx_inv_data->inv = inv;
+	    inv->invite_tsx->mod_data[mod_inv.mod.id] = tsx_inv_data;
+	}
+	
+	tsx_inv_data->sdp_done = PJ_TRUE;
+    }
+}
+
+
+/*
  * State NULL is before anything is sent/received.
  */
 static void inv_on_state_null( pjsip_inv_session *inv, pjsip_event *e)
@@ -2072,6 +2414,11 @@
 		inv_check_sdp_in_incoming_msg(inv, tsx, 
 					      e->body.tsx_state.src.rdata);
 
+		if (pjsip_100rel_is_reliable(e->body.tsx_state.src.rdata)) {
+		    inv_handle_incoming_reliable_response(
+			inv, e->body.tsx_state.src.rdata);
+		}
+
 	    } else {
 		/* Ignore 100 (Trying) response, as it doesn't change
 		 * session state. It only ceases retransmissions.
@@ -2166,12 +2513,9 @@
 	    break;
 	}
 
-    } else if (inv->role == PJSIP_ROLE_UAC &&
-	       tsx->role == PJSIP_ROLE_UAC &&
-	       tsx->method.id == PJSIP_CANCEL_METHOD)
-    {
+    } else if (tsx->role == PJSIP_ROLE_UAC) {
 	/*
-	 * Handle case when outgoing CANCEL is answered with 481 (Call/
+	 * Handle case when outgoing request is answered with 481 (Call/
 	 * Transaction Does Not Exist), 408, or when it's timed out. In these
 	 * cases, disconnect session (i.e. dialog usage only).
 	 */
@@ -2284,6 +2628,11 @@
 	    if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
 		inv_check_sdp_in_incoming_msg(inv, tsx, 
 					      e->body.tsx_state.src.rdata);
+
+		if (pjsip_100rel_is_reliable(e->body.tsx_state.src.rdata)) {
+		    inv_handle_incoming_reliable_response(
+			inv, e->body.tsx_state.src.rdata);
+		}
 	    }
 	    break;
 
@@ -2353,12 +2702,38 @@
 
 	inv_respond_incoming_cancel(inv, tsx, e->body.tsx_state.src.rdata);
 
-    } else if (inv->role == PJSIP_ROLE_UAC &&
-	       tsx->role == PJSIP_ROLE_UAC &&
-	       tsx->method.id == PJSIP_CANCEL_METHOD)
+    } else if (tsx->role == PJSIP_ROLE_UAS &&
+	       tsx->state == PJSIP_TSX_STATE_TRYING &&
+	       pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0)
     {
 	/*
-	 * Handle case when outgoing CANCEL is answered with 481 (Call/
+	 * Handle incoming UPDATE
+	 */
+	inv_respond_incoming_update(inv, e->body.tsx_state.src.rdata);
+
+
+    } else if (tsx->role == PJSIP_ROLE_UAC &&
+	       (tsx->state == PJSIP_TSX_STATE_COMPLETED ||
+	        tsx->state == PJSIP_TSX_STATE_TERMINATED) &&
+	       pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0)
+    {
+	/*
+	 * Handle response to outgoing UPDATE request.
+	 */
+	inv_handle_update_response(inv, e);
+
+    } else if (tsx->role == PJSIP_ROLE_UAS &&
+	       tsx->state == PJSIP_TSX_STATE_TRYING &&
+	       pjsip_method_cmp(&tsx->method, &pjsip_prack_method)==0)
+    {
+	/*
+	 * Handle incoming PRACK
+	 */
+	inv_respond_incoming_prack(inv, e->body.tsx_state.src.rdata);
+
+    } else if (tsx->role == PJSIP_ROLE_UAC) {
+	/*
+	 * Handle case when outgoing request is answered with 481 (Call/
 	 * Transaction Does Not Exist), 408, or when it's timed out. In these
 	 * cases, disconnect session (i.e. dialog usage only).
 	 */
@@ -2466,7 +2841,51 @@
 	status = pjsip_dlg_send_response(dlg, tsx, tdata);
 	if (status != PJ_SUCCESS) return;
 
+    } else if (tsx->role == PJSIP_ROLE_UAS &&
+	       tsx->state == PJSIP_TSX_STATE_TRYING &&
+	       pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0)
+    {
+	/*
+	 * Handle incoming UPDATE
+	 */
+	inv_respond_incoming_update(inv, e->body.tsx_state.src.rdata);
+
+
+    } else if (tsx->role == PJSIP_ROLE_UAC &&
+	       (tsx->state == PJSIP_TSX_STATE_COMPLETED ||
+	        tsx->state == PJSIP_TSX_STATE_TERMINATED) &&
+	       pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0)
+    {
+	/*
+	 * Handle response to outgoing UPDATE request.
+	 */
+	inv_handle_update_response(inv, e);
+
+    } else if (tsx->role == PJSIP_ROLE_UAS &&
+	       tsx->state == PJSIP_TSX_STATE_TRYING &&
+	       pjsip_method_cmp(&tsx->method, &pjsip_prack_method)==0)
+    {
+	/*
+	 * Handle incoming PRACK
+	 */
+	inv_respond_incoming_prack(inv, e->body.tsx_state.src.rdata);
+
+    } else if (tsx->role == PJSIP_ROLE_UAC) {
+	/*
+	 * Handle case when outgoing request is answered with 481 (Call/
+	 * Transaction Does Not Exist), 408, or when it's timed out. In these
+	 * cases, disconnect session (i.e. dialog usage only).
+	 */
+	if (tsx->status_code == PJSIP_SC_CALL_TSX_DOES_NOT_EXIST ||
+	    tsx->status_code == PJSIP_SC_REQUEST_TIMEOUT ||
+	    tsx->status_code == PJSIP_SC_TSX_TIMEOUT ||
+	    tsx->status_code == PJSIP_SC_TSX_TRANSPORT_ERROR)
+	{
+	    inv_set_cause(inv, tsx->status_code, &tsx->status_text);
+	    inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
+	}
     }
+
 }
 
 /*
@@ -2730,7 +3149,51 @@
 		pjmedia_sdp_neg_cancel_offer(inv->neg);
 	    }
 	}
+
+    } else if (tsx->role == PJSIP_ROLE_UAS &&
+	       tsx->state == PJSIP_TSX_STATE_TRYING &&
+	       pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0)
+    {
+	/*
+	 * Handle incoming UPDATE
+	 */
+	inv_respond_incoming_update(inv, e->body.tsx_state.src.rdata);
+
+    } else if (tsx->role == PJSIP_ROLE_UAC &&
+	       (tsx->state == PJSIP_TSX_STATE_COMPLETED ||
+	        tsx->state == PJSIP_TSX_STATE_TERMINATED) &&
+	       pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0)
+    {
+	/*
+	 * Handle response to outgoing UPDATE request.
+	 */
+	inv_handle_update_response(inv, e);
+
+    } else if (tsx->role == PJSIP_ROLE_UAS &&
+	       tsx->state == PJSIP_TSX_STATE_TRYING &&
+	       pjsip_method_cmp(&tsx->method, &pjsip_prack_method)==0)
+    {
+	/*
+	 * Handle strandled incoming PRACK
+	 */
+	inv_respond_incoming_prack(inv, e->body.tsx_state.src.rdata);
+
+    } else if (tsx->role == PJSIP_ROLE_UAC) {
+	/*
+	 * Handle case when outgoing request is answered with 481 (Call/
+	 * Transaction Does Not Exist), 408, or when it's timed out. In these
+	 * cases, disconnect session (i.e. dialog usage only).
+	 */
+	if (tsx->status_code == PJSIP_SC_CALL_TSX_DOES_NOT_EXIST ||
+	    tsx->status_code == PJSIP_SC_REQUEST_TIMEOUT ||
+	    tsx->status_code == PJSIP_SC_TSX_TIMEOUT ||
+	    tsx->status_code == PJSIP_SC_TSX_TRANSPORT_ERROR)
+	{
+	    inv_set_cause(inv, tsx->status_code, &tsx->status_text);
+	    inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
+	}
     }
+
 }
 
 /*
diff --git a/pjsip/src/pjsip/sip_dialog.c b/pjsip/src/pjsip/sip_dialog.c
index 99741a4..85212bb 100644
--- a/pjsip/src/pjsip/sip_dialog.c
+++ b/pjsip/src/pjsip/sip_dialog.c
@@ -1200,8 +1200,8 @@
 	    }
 	}
 
-	/* Add Allow header in 2xx and 405 response. */
-	if (((st_class==2 && dlg->add_allow)
+	/* Add Allow header in 18x, 2xx and 405 response. */
+	if ((((st_code/10==18 || st_class==2) && dlg->add_allow)
 	     || st_code==405) &&
 	    pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ALLOW, NULL)==NULL) 
 	{
diff --git a/pjsip/src/pjsip/sip_transaction.c b/pjsip/src/pjsip/sip_transaction.c
index e721044..dbbccf6 100644
--- a/pjsip/src/pjsip/sip_transaction.c
+++ b/pjsip/src/pjsip/sip_transaction.c
@@ -904,6 +904,7 @@
 
     pj_ansi_snprintf(tsx->obj_name, sizeof(tsx->obj_name), 
 		     "tsx%p", tsx);
+    pj_memcpy(pool->obj_name, tsx->obj_name, sizeof(pool->obj_name));
 
     tsx->handle_200resp = 1;
     tsx->retransmit_timer.id = 0;
diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c
index 22a1105..ac5db1d 100644
--- a/pjsip/src/pjsua-lib/pjsua_call.c
+++ b/pjsip/src/pjsua-lib/pjsua_call.c
@@ -195,6 +195,8 @@
 }
 
 
+#define LATE_SDP    0
+
 /*
  * Make outgoing call to the specified URI using the specified account.
  */
@@ -312,11 +314,15 @@
     }
 
     /* Create SDP offer */
+#if LATE_SDP
+    offer = NULL;
+#else
     status = pjsua_media_channel_create_sdp(call->index, dlg->pool, &offer);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "pjmedia unable to create SDP", status);
 	goto on_error;
     }
+#endif
 
     /* Create the INVITE session: */
 #if PJSIP_HAS_100REL
diff --git a/pjsip/src/test-pjsip/inv_offer_answer_test.c b/pjsip/src/test-pjsip/inv_offer_answer_test.c
new file mode 100644
index 0000000..9da8d25
--- /dev/null
+++ b/pjsip/src/test-pjsip/inv_offer_answer_test.c
@@ -0,0 +1,676 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2007 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 "test.h"
+#include <pjsip_ua.h>
+#include <pjsip.h>
+#include <pjlib.h>
+
+#define THIS_FILE   "inv_offer_answer_test.c"
+#define PORT	    5068
+#define CONTACT	    "sip:127.0.0.1:5068"
+#define TRACE_(x)   PJ_LOG(3,x)
+
+static struct oa_sdp_t
+{
+    const char *offer;
+    const char *answer;
+    unsigned	pt_result;
+} oa_sdp[] = 
+{
+    {
+	/* Offer: */
+	"v=0\r\n"
+	"o=alice 1 1 IN IP4 host.anywhere.com\r\n"
+	"s= \r\n"
+	"c=IN IP4 host.anywhere.com\r\n"
+	"t=0 0\r\n"
+	"m=audio 49170 RTP/AVP 0\r\n"
+	"a=rtpmap:0 PCMU/8000\r\n",
+
+	/* Answer: */
+	"v=0\r\n"
+	"o=bob 1 1 IN IP4 host.example.com\r\n"
+	"s= \r\n"
+	"c=IN IP4 host.example.com\r\n"
+	"t=0 0\r\n"
+	"m=audio 49920 RTP/AVP 0\r\n"
+	"a=rtpmap:0 PCMU/8000\r\n"
+	"m=video 0 RTP/AVP 31\r\n",
+
+	0
+      },
+
+      {
+	/* Offer: */
+	"v=0\r\n"
+	"o=alice 2 2 IN IP4 host.anywhere.com\r\n"
+	"s= \r\n"
+	"c=IN IP4 host.anywhere.com\r\n"
+	"t=0 0\r\n"
+	"m=audio 49170 RTP/AVP 8\r\n"
+	"a=rtpmap:0 PCMA/8000\r\n",
+
+	/* Answer: */
+	"v=0\r\n"
+	"o=bob 2 2 IN IP4 host.example.com\r\n"
+	"s= \r\n"
+	"c=IN IP4 host.example.com\r\n"
+	"t=0 0\r\n"
+	"m=audio 49920 RTP/AVP 8\r\n"
+	"a=rtpmap:0 PCMA/8000\r\n",
+
+	8
+      },
+
+      {
+	/* Offer: */
+	"v=0\r\n"
+	"o=alice 3 3 IN IP4 host.anywhere.com\r\n"
+	"s= \r\n"
+	"c=IN IP4 host.anywhere.com\r\n"
+	"t=0 0\r\n"
+	"m=audio 49170 RTP/AVP 3\r\n",
+
+	/* Answer: */
+	"v=0\r\n"
+	"o=bob 3 3 IN IP4 host.example.com\r\n"
+	"s= \r\n"
+	"c=IN IP4 host.example.com\r\n"
+	"t=0 0\r\n"
+	"m=audio 49920 RTP/AVP 3\r\n",
+
+	3
+      },
+
+      {
+	/* Offer: */
+	"v=0\r\n"
+	"o=alice 4 4 IN IP4 host.anywhere.com\r\n"
+	"s= \r\n"
+	"c=IN IP4 host.anywhere.com\r\n"
+	"t=0 0\r\n"
+	"m=audio 49170 RTP/AVP 4\r\n",
+
+	/* Answer: */
+	"v=0\r\n"
+	"o=bob 4 4 IN IP4 host.example.com\r\n"
+	"s= \r\n"
+	"c=IN IP4 host.example.com\r\n"
+	"t=0 0\r\n"
+	"m=audio 49920 RTP/AVP 4\r\n",
+
+	4
+    }
+};
+
+
+
+typedef enum oa_t
+{
+    OFFERER_NONE,
+    OFFERER_UAC,
+    OFFERER_UAS
+} oa_t;
+
+typedef struct inv_test_param_t
+{
+    char       *title;
+    unsigned	inv_option;
+    pj_bool_t	need_established;
+    unsigned	count;
+    oa_t	oa[4];
+} inv_test_param_t;
+
+typedef struct inv_test_t
+{
+    inv_test_param_t	param;
+    pjsip_inv_session  *uac;
+    pjsip_inv_session  *uas;
+
+    pj_bool_t		complete;
+    pj_bool_t		uas_complete,
+			uac_complete;
+
+    unsigned		oa_index;
+    unsigned		uac_update_cnt,
+			uas_update_cnt;
+} inv_test_t;
+
+
+/**************** GLOBALS ******************/
+static inv_test_t   inv_test;
+static unsigned	    job_cnt;
+
+typedef enum job_type
+{
+    SEND_OFFER,
+    ESTABLISH_CALL
+} job_type;
+
+typedef struct job_t
+{
+    job_type	    type;
+    pjsip_role_e    who;
+} job_t;
+
+static job_t jobs[128];
+
+
+/**************** UTILS ******************/
+static pjmedia_sdp_session *create_sdp(pj_pool_t *pool, const char *body)
+{
+    pjmedia_sdp_session *sdp;
+    pj_str_t dup;
+    pj_status_t status;
+    
+    pj_strdup2_with_null(pool, &dup, body);
+    status = pjmedia_sdp_parse(pool, dup.ptr, dup.slen, &sdp);
+    pj_assert(status == PJ_SUCCESS);
+
+    return sdp;
+}
+
+/**************** INVITE SESSION CALLBACKS ******************/
+static void on_rx_offer(pjsip_inv_session *inv,
+			const pjmedia_sdp_session *offer)
+{
+    pjmedia_sdp_session *sdp;
+
+    sdp = create_sdp(inv->dlg->pool, oa_sdp[inv_test.oa_index].answer);
+    pjsip_inv_set_sdp_answer(inv, sdp);
+
+    if (inv_test.oa_index == inv_test.param.count-1 &&
+	inv_test.param.need_established) 
+    {
+	jobs[job_cnt].type = ESTABLISH_CALL;
+	jobs[job_cnt].who = PJSIP_ROLE_UAS;
+	job_cnt++;
+    }
+}
+
+
+static void on_create_offer(pjsip_inv_session *inv,
+			    pjmedia_sdp_session **p_offer)
+{
+    pj_assert(!"Should not happen");
+}
+
+static void on_media_update(pjsip_inv_session *inv_ses, 
+			    pj_status_t status)
+{
+    if (inv_ses == inv_test.uas) {
+	inv_test.uas_update_cnt++;
+	pj_assert(inv_test.uas_update_cnt - inv_test.uac_update_cnt <= 1);
+	TRACE_((THIS_FILE, "      Callee media is established"));
+    } else if (inv_ses == inv_test.uac) {
+	inv_test.uac_update_cnt++;
+	pj_assert(inv_test.uac_update_cnt - inv_test.uas_update_cnt <= 1);
+	TRACE_((THIS_FILE, "      Caller media is established"));
+	
+    } else {
+	pj_assert(!"Unknown session!");
+    }
+
+    if (inv_test.uac_update_cnt == inv_test.uas_update_cnt) {
+	inv_test.oa_index++;
+
+	if (inv_test.oa_index < inv_test.param.count) {
+	    switch (inv_test.param.oa[inv_test.oa_index]) {
+	    case OFFERER_UAC:
+		jobs[job_cnt].type = SEND_OFFER;
+		jobs[job_cnt].who = PJSIP_ROLE_UAC;
+		job_cnt++;
+		break;
+	    case OFFERER_UAS:
+		jobs[job_cnt].type = SEND_OFFER;
+		jobs[job_cnt].who = PJSIP_ROLE_UAS;
+		job_cnt++;
+		break;
+	    default:
+		pj_assert(!"Invalid oa");
+	    }
+	}
+
+	pj_assert(job_cnt <= PJ_ARRAY_SIZE(jobs));
+    }
+}
+
+static void on_state_changed(pjsip_inv_session *inv, pjsip_event *e)
+{
+    const char *who = NULL;
+
+    if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
+	TRACE_((THIS_FILE, "      %s call disconnected",
+		(inv==inv_test.uas ? "Callee" : "Caller")));
+	return;
+    }
+
+    if (inv->state != PJSIP_INV_STATE_CONFIRMED)
+	return;
+
+    if (inv == inv_test.uas) {
+	inv_test.uas_complete = PJ_TRUE;
+	who = "Callee";
+    } else if (inv == inv_test.uac) {
+	inv_test.uac_complete = PJ_TRUE;
+	who = "Caller";
+    } else
+	pj_assert(!"No session");
+
+    TRACE_((THIS_FILE, "      %s call is confirmed", who));
+
+    if (inv_test.uac_complete && inv_test.uas_complete)
+	inv_test.complete = PJ_TRUE;
+}
+
+
+/**************** MODULE TO RECEIVE INITIAL INVITE ******************/
+
+static pj_bool_t on_rx_request(pjsip_rx_data *rdata)
+{
+    if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG &&
+	rdata->msg_info.msg->line.req.method.id == PJSIP_INVITE_METHOD)
+    {
+	pjsip_dialog *dlg;
+	pjmedia_sdp_session *sdp;
+	pj_str_t uri;
+	pjsip_tx_data *tdata;
+	pj_status_t status;
+
+	/*
+	 * Create UAS
+	 */
+	uri = pj_str(CONTACT);
+	status = pjsip_dlg_create_uas(pjsip_ua_instance(), rdata,
+				      &uri, &dlg);
+	pj_assert(status == PJ_SUCCESS);
+
+	if (inv_test.param.oa[0] == OFFERER_UAC)
+	    sdp = create_sdp(rdata->tp_info.pool, oa_sdp[0].answer);
+	else if (inv_test.param.oa[0] == OFFERER_UAS)
+	    sdp = create_sdp(rdata->tp_info.pool, oa_sdp[0].offer);
+	else
+	    pj_assert(!"Invalid offerer type");
+
+	status = pjsip_inv_create_uas(dlg, rdata, sdp, inv_test.param.inv_option, &inv_test.uas);
+	pj_assert(status == PJ_SUCCESS);
+
+	TRACE_((THIS_FILE, "    Sending 183 with SDP"));
+
+	/*
+	 * Answer with 183
+	 */
+	status = pjsip_inv_initial_answer(inv_test.uas, rdata, 183, NULL,
+					  NULL, &tdata);
+	pj_assert(status == PJ_SUCCESS);
+
+	status = pjsip_inv_send_msg(inv_test.uas, tdata);
+	pj_assert(status == PJ_SUCCESS);
+
+	return PJ_TRUE;
+    }
+
+    return PJ_FALSE;
+}
+
+static pjsip_module mod_inv_oa_test =
+{
+    NULL, NULL,			    /* prev, next.		*/
+    { "mod-inv-oa-test", 15 },	    /* Name.			*/
+    -1,				    /* Id			*/
+    PJSIP_MOD_PRIORITY_APPLICATION, /* Priority			*/
+    NULL,			    /* load()			*/
+    NULL,			    /* start()			*/
+    NULL,			    /* stop()			*/
+    NULL,			    /* unload()			*/
+    &on_rx_request,		    /* on_rx_request()		*/
+    NULL,			    /* on_rx_response()		*/
+    NULL,			    /* on_tx_request.		*/
+    NULL,			    /* on_tx_response()		*/
+    NULL,			    /* on_tsx_state()		*/
+};
+
+
+/**************** THE TEST ******************/
+static void run_job(job_t *j)
+{
+    pjsip_inv_session *inv;
+    pjsip_tx_data *tdata;
+    pjmedia_sdp_session *sdp;
+    pj_status_t status;
+
+    if (j->who == PJSIP_ROLE_UAC)
+	inv = inv_test.uac;
+    else
+	inv = inv_test.uas;
+
+    switch (j->type) {
+    case SEND_OFFER:
+	sdp = create_sdp(inv->dlg->pool, oa_sdp[inv_test.oa_index].offer);
+
+	TRACE_((THIS_FILE, "    Sending UPDATE with offer"));
+	status = pjsip_inv_update(inv, NULL, sdp, &tdata);
+	pj_assert(status == PJ_SUCCESS);
+
+	status = pjsip_inv_send_msg(inv, tdata);
+	pj_assert(status == PJ_SUCCESS);
+	break;
+    case ESTABLISH_CALL:
+	TRACE_((THIS_FILE, "    Sending 200/OK"));
+	status = pjsip_inv_answer(inv, 200, NULL, NULL, &tdata);
+	pj_assert(status == PJ_SUCCESS);
+
+	status = pjsip_inv_send_msg(inv, tdata);
+	pj_assert(status == PJ_SUCCESS);
+	break;
+    }
+}
+
+
+static int perform_test(inv_test_param_t *param)
+{
+    pj_str_t uri;
+    pjsip_dialog *dlg;
+    pjmedia_sdp_session *sdp;
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+
+    PJ_LOG(3,(THIS_FILE, "  %s", param->title));
+
+    pj_bzero(&inv_test, sizeof(inv_test));
+    pj_memcpy(&inv_test.param, param, sizeof(*param));
+    job_cnt = 0;
+
+    uri = pj_str(CONTACT);
+
+    /*  
+     * Create UAC
+     */
+    status = pjsip_dlg_create_uac(pjsip_ua_instance(), 
+				  &uri, &uri, &uri, &uri, &dlg);
+    PJ_ASSERT_RETURN(status==PJ_SUCCESS, -10);
+
+    if (inv_test.param.oa[0] == OFFERER_UAC)
+	sdp = create_sdp(dlg->pool, oa_sdp[0].offer);
+    else
+	sdp = NULL;
+
+    status = pjsip_inv_create_uac(dlg, sdp, inv_test.param.inv_option, &inv_test.uac);
+    PJ_ASSERT_RETURN(status==PJ_SUCCESS, -20);
+
+    TRACE_((THIS_FILE, "    Sending INVITE %s offer", (sdp ? "with" : "without")));
+
+    /*
+     * Make call!
+     */
+    status = pjsip_inv_invite(inv_test.uac, &tdata);
+    PJ_ASSERT_RETURN(status==PJ_SUCCESS, -30);
+
+    status = pjsip_inv_send_msg(inv_test.uac, tdata);
+    PJ_ASSERT_RETURN(status==PJ_SUCCESS, -30);
+
+    /*
+     * Wait until test completes
+     */
+    while (!inv_test.complete) {
+	pj_time_val delay = {0, 20};
+
+	pjsip_endpt_handle_events(endpt, &delay);
+
+	while (job_cnt) {
+	    job_t j;
+
+	    j = jobs[0];
+	    pj_array_erase(jobs, sizeof(jobs[0]), job_cnt, 0);
+	    --job_cnt;
+
+	    run_job(&j);
+	}
+    }
+
+    flush_events(100);
+
+    /*
+     * Hangup
+     */
+    TRACE_((THIS_FILE, "    Disconnecting call"));
+    status = pjsip_inv_end_session(inv_test.uas, PJSIP_SC_DECLINE, 0, &tdata);
+    pj_assert(status == PJ_SUCCESS);
+
+    status = pjsip_inv_send_msg(inv_test.uas, tdata);
+    pj_assert(status == PJ_SUCCESS);
+
+    flush_events(500);
+
+    return 0;
+}
+
+
+static pj_bool_t log_on_rx_msg(pjsip_rx_data *rdata)
+{
+    pjsip_msg *msg = rdata->msg_info.msg;
+    char info[80];
+
+    if (msg->type == PJSIP_REQUEST_MSG)
+	pj_ansi_snprintf(info, sizeof(info), "%.*s", 
+	    (int)msg->line.req.method.name.slen,
+	    msg->line.req.method.name.ptr);
+    else
+	pj_ansi_snprintf(info, sizeof(info), "%d/%.*s",
+	    msg->line.status.code,
+	    (int)rdata->msg_info.cseq->method.name.slen,
+	    rdata->msg_info.cseq->method.name.ptr);
+
+    TRACE_((THIS_FILE, "      Received %s %s sdp", info,
+	(msg->body ? "with" : "without")));
+
+    return PJ_FALSE;
+}
+
+
+/* Message logger module. */
+static pjsip_module mod_msg_logger = 
+{
+    NULL, NULL,				/* prev and next	*/
+    { "mod-msg-loggee", 14},		/* Name.		*/
+    -1,					/* Id			*/
+    PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority		*/
+    NULL,				/* load()		*/
+    NULL,				/* start()		*/
+    NULL,				/* stop()		*/
+    NULL,				/* unload()		*/
+    &log_on_rx_msg,			/* on_rx_request()	*/
+    &log_on_rx_msg,			/* on_rx_response()	*/
+    NULL,				/* on_tx_request()	*/
+    NULL,				/* on_tx_response()	*/
+    NULL,				/* on_tsx_state()	*/
+};
+
+static inv_test_param_t test_params[] =
+{
+/* Normal scenario:
+
+				UAC		UAS
+    INVITE (offer)	-->
+    200/INVITE (answer)	<--
+    ACK    		-->
+ */
+#if 0
+    {
+	"Standard INVITE with offer",
+	0,
+	PJ_TRUE,
+	1,
+	{ OFFERER_UAC }
+    },
+
+    {
+	"Standard INVITE with offer, with 100rel",
+	PJSIP_INV_REQUIRE_100REL,
+	PJ_TRUE,
+	1,
+	{ OFFERER_UAC }
+    },
+#endif
+
+/* Delayed offer:
+				UAC		UAS
+    INVITE (no SDP) 	-->
+    200/INVITE (offer) 	<--
+    ACK (answer)   	-->
+ */
+#if 1
+    {
+	"INVITE with no offer",
+	0,
+	PJ_TRUE,
+	1,
+	{ OFFERER_UAS }
+    },
+
+    {
+	"INVITE with no offer, with 100rel",
+	PJSIP_INV_REQUIRE_100REL,
+	PJ_TRUE,
+	1,
+	{ OFFERER_UAS }
+    },
+#endif
+
+/* Subsequent UAC offer with UPDATE:
+
+				UAC		UAS
+    INVITE (offer)	-->
+    180/rel (answer)	<--
+    UPDATE (offer)	-->	inv_update()	on_rx_offer()
+						set_sdp_answer()
+    200/UPDATE (answer)	<--
+    200/INVITE		<--
+    ACK	-->
+*/
+#if 1
+    {
+	"INVITE and UPDATE by UAC",
+	0,
+	PJ_TRUE,
+	2,
+	{ OFFERER_UAC, OFFERER_UAC }
+    },
+    {
+	"INVITE and UPDATE by UAC, with 100rel",
+	PJSIP_INV_REQUIRE_100REL,
+	PJ_TRUE,
+	2,
+	{ OFFERER_UAC, OFFERER_UAC }
+    },
+#endif
+
+/* Subsequent UAS offer with UPDATE:
+
+    INVITE (offer	-->
+    180/rel (answer)	<--
+    UPDATE (offer)	<--			inv_update()
+				on_rx_offer()
+				set_sdp_answer()
+    200/UPDATE (answer) -->
+    UPDATE (offer)	-->			on_rx_offer()
+						set_sdp_answer()
+    200/UPDATE (answer) <--
+    200/INVITE		<--
+    ACK			-->
+
+ */
+    {
+	"INVITE and many UPDATE by UAC and UAS",
+	0,
+	PJ_TRUE,
+	4,
+	{ OFFERER_UAC, OFFERER_UAS, OFFERER_UAC, OFFERER_UAS }
+    },
+
+};
+
+
+static pjsip_dialog* on_dlg_forked(pjsip_dialog *first_set, pjsip_rx_data *res)
+{
+    return NULL;
+}
+
+
+static void on_new_session(pjsip_inv_session *inv, pjsip_event *e)
+{
+}
+
+
+int inv_offer_answer_test(void)
+{
+    unsigned i;
+    int rc = 0;
+
+    /* Init UA layer */
+    if (pjsip_ua_instance()->id == -1) {
+	pjsip_ua_init_param ua_param;
+	pj_bzero(&ua_param, sizeof(ua_param));
+	ua_param.on_dlg_forked = &on_dlg_forked;
+	pjsip_ua_init_module(endpt, &ua_param);
+    }
+
+    /* Init inv-usage */
+    if (pjsip_inv_usage_instance()->id == -1) {
+	pjsip_inv_callback inv_cb;
+	pj_bzero(&inv_cb, sizeof(inv_cb));
+	inv_cb.on_media_update = &on_media_update;
+	inv_cb.on_rx_offer = &on_rx_offer;
+	inv_cb.on_create_offer = &on_create_offer;
+	inv_cb.on_state_changed = &on_state_changed;
+	inv_cb.on_new_session = &on_new_session;
+	pjsip_inv_usage_init(endpt, &inv_cb);
+    }
+
+    /* 100rel module */
+    pjsip_100rel_init_module(endpt);
+
+    /* Our module */
+    pjsip_endpt_register_module(endpt, &mod_inv_oa_test);
+    pjsip_endpt_register_module(endpt, &mod_msg_logger);
+
+    /* Create SIP UDP transport */
+    {
+	pj_sockaddr_in addr;
+	pjsip_transport *tp;
+	pj_status_t status;
+
+	pj_sockaddr_in_init(&addr, NULL, PORT);
+	status = pjsip_udp_transport_start(endpt, &addr, NULL, 1, &tp);
+	pj_assert(status == PJ_SUCCESS);
+    }
+
+    /* Do tests */
+    for (i=0; i<PJ_ARRAY_SIZE(test_params); ++i) {
+	rc = perform_test(&test_params[i]);
+	if (rc != 0)
+	    goto on_return;
+    }
+
+
+on_return:
+    return rc;
+}
+
diff --git a/pjsip/src/test-pjsip/test.c b/pjsip/src/test-pjsip/test.c
index 62e3aed..b02a8cf 100644
--- a/pjsip/src/test-pjsip/test.c
+++ b/pjsip/src/test-pjsip/test.c
@@ -355,6 +355,10 @@
     }
 #endif
 
+#if INCLUDE_INV_OA_TEST
+    DO_TEST(inv_offer_answer_test());
+#endif
+
 
 on_return:
     flush_events(500);
diff --git a/pjsip/src/test-pjsip/test.h b/pjsip/src/test-pjsip/test.h
index 956e361..b256651 100644
--- a/pjsip/src/test-pjsip/test.h
+++ b/pjsip/src/test-pjsip/test.h
@@ -39,6 +39,7 @@
 #define INCLUDE_MESSAGING_GROUP	    1
 #define INCLUDE_TRANSPORT_GROUP	    1
 #define INCLUDE_TSX_GROUP	    1
+#define INCLUDE_INV_GROUP	    1
 
 /*
  * Include tests that normally would fail under certain gcc
@@ -58,7 +59,7 @@
 #define INCLUDE_TCP_TEST	INCLUDE_TRANSPORT_GROUP
 #define INCLUDE_RESOLVE_TEST	INCLUDE_TRANSPORT_GROUP
 #define INCLUDE_TSX_TEST	INCLUDE_TSX_GROUP
-
+#define INCLUDE_INV_OA_TEST	INCLUDE_INV_GROUP
 
 
 /* The tests */
@@ -94,6 +95,9 @@
 		       char *target_url,
 		       int *pkt_lost);
 
+/* Invite session */
+int inv_offer_answer_test(void);
+
 /* Test main entry */
 int  test_main(void);