Ticket #385: Support for reliable provisional response (100rel, PRACK)

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@1463 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjsip/src/pjsip-ua/sip_inv.c b/pjsip/src/pjsip-ua/sip_inv.c
index 9f56176..0243452 100644
--- a/pjsip/src/pjsip-ua/sip_inv.c
+++ b/pjsip/src/pjsip-ua/sip_inv.c
@@ -17,6 +17,7 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
  */
 #include <pjsip-ua/sip_inv.h>
+#include <pjsip-ua/sip_100rel.h>
 #include <pjsip/sip_module.h>
 #include <pjsip/sip_endpoint.h>
 #include <pjsip/sip_event.h>
@@ -31,7 +32,7 @@
 #include <pj/log.h>
 
 
-#define THIS_FILE	"sip_invite_session.c"
+#define THIS_FILE	"sip_inv.c"
 
 static const char *inv_state_names[] =
 {
@@ -305,6 +306,10 @@
 	    pjsip_tsx_terminate(inv->invite_tsx, 
 				inv->invite_tsx->status_code);
 	    inv->invite_tsx = NULL;
+	    if (inv->last_answer) {
+		    pjsip_tx_data_dec_ref(inv->last_answer);
+		    inv->last_answer = NULL;
+	    }
 	}
 
 	/* On receipt of ACK, only set state to confirmed when state
@@ -395,8 +400,13 @@
      * terminated, but this didn't work when ACK has the same Via branch
      * value as the INVITE (see http://www.pjsip.org/trac/ticket/113)
      */
-    if (tsx->state>=PJSIP_TSX_STATE_CONFIRMED && tsx == inv->invite_tsx)
+    if (tsx->state>=PJSIP_TSX_STATE_CONFIRMED && tsx == inv->invite_tsx) {
         inv->invite_tsx = NULL;
+	if (inv->last_answer) {
+		pjsip_tx_data_dec_ref(inv->last_answer);
+		inv->last_answer = NULL;
+	}
+    }
 }
 
 
@@ -482,6 +492,14 @@
     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;
 
@@ -520,6 +538,11 @@
     /* 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;
 
@@ -829,6 +852,18 @@
 	goto on_return;
     }
 
+    /* If remote Require something that we support, make us Require
+     * that feature too.
+     */
+    if (rem_option & PJSIP_INV_REQUIRE_100REL) {
+	    pj_assert(*options & PJSIP_INV_SUPPORT_100REL);
+	    *options |= PJSIP_INV_REQUIRE_100REL;
+    }
+    if (rem_option & PJSIP_INV_REQUIRE_TIMER) {
+	    pj_assert(*options & PJSIP_INV_SUPPORT_TIMER);
+	    *options |= PJSIP_INV_REQUIRE_TIMER;
+    }
+
 on_return:
 
     /* Create response if necessary */
@@ -908,6 +943,14 @@
     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;
 
@@ -977,6 +1020,13 @@
     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);
     *p_inv = inv;
@@ -1168,7 +1218,15 @@
     }
 
     /* Add Require header. */
-    PJ_TODO(INVITE_ADD_REQUIRE_HEADER);
+    if (inv->options & PJSIP_INV_REQUIRE_100REL) {
+	    const pj_str_t HREQ = { "Require", 7 };
+	    const pj_str_t tag_100rel = { "100rel", 6 };
+	    pjsip_generic_string_hdr *hreq;
+
+	    hreq = pjsip_generic_string_hdr_create(tdata->pool, &HREQ, 
+						   &tag_100rel);
+	    pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) hreq);
+    }
 
     /* Done. */
     *p_tdata = tdata;
@@ -1452,6 +1510,12 @@
 	goto on_return;
     }
 
+    /* Save this answer */
+    inv->last_answer = tdata;
+    pjsip_tx_data_add_ref(inv->last_answer);
+    PJ_LOG(5,(inv->dlg->obj_name, "Initial answer %s",
+	      pjsip_tx_data_get_info(inv->last_answer)));
+
     *p_tdata = tdata;
 
 on_return:
@@ -1479,17 +1543,21 @@
     /* Must have INVITE transaction. */
     PJ_ASSERT_RETURN(inv->invite_tsx, PJ_EBUG);
 
-    /* INVITE transaction MUST have transmitted a response (e.g. 100) */
-    PJ_ASSERT_RETURN(inv->invite_tsx->last_tx, PJ_EINVALIDOP);
+    /* Must have created an answer before */
+    PJ_ASSERT_RETURN(inv->last_answer, PJ_EINVALIDOP);
 
     pjsip_dlg_inc_lock(inv->dlg);
 
     /* Modify last response. */
-    last_res = inv->invite_tsx->last_tx;
+    last_res = inv->last_answer;
     status = pjsip_dlg_modify_response(inv->dlg, last_res, st_code, st_text);
     if (status != PJ_SUCCESS)
 	goto on_return;
 
+    /* For non-2xx final response, strip message body */
+    if (st_code >= 300) {
+	last_res->msg->body = NULL;
+    }
 
     /* Process SDP in answer */
     status = process_answer(inv, st_code, last_res, local_sdp);
@@ -1764,7 +1832,15 @@
 			  && (cseq->cseq == inv->invite_tsx->cseq),
 			 PJ_EINVALIDOP);
 
-	status = pjsip_dlg_send_response(inv->dlg, inv->invite_tsx, tdata);
+#if PJSIP_HAS_100REL
+	if (inv->options & PJSIP_INV_REQUIRE_100REL) {
+		status = pjsip_100rel_tx_response(inv, tdata);
+	} else 
+#endif
+	{
+		status = pjsip_dlg_send_response(inv->dlg, inv->invite_tsx, tdata);
+	}
+
 	if (status != PJ_SUCCESS)
 	    return status;
     }
@@ -2042,6 +2118,10 @@
 		    /* Restart session. */
 		    inv->state = PJSIP_INV_STATE_NULL;
 		    inv->invite_tsx = NULL;
+		    if (inv->last_answer) {
+			pjsip_tx_data_dec_ref(inv->last_answer);
+			inv->last_answer = NULL;
+		    }
 
 		    /* Send the request. */
 		    status = pjsip_inv_send_msg(inv, tdata);