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/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);
+	}
     }
+
 }
 
 /*