Fixes #1047 (Don't send UPDATE if remote doesn't support it (thanks Bogdan Krakowski for the report)) and fixes #1097 (Support sending UPDATE without SDP). Details:
 - Session timer fixes:
    - will look at remote capability in Allow header
    - if UPDATE is supported, will send UPDATE without SDP first. 
      If this fails, will send UPDATE with SDP
    - otherwise will send re-INVITE
 - PJSUA-LIB will look at dialog's remote capability to determine 
   whether re-INVITE or UPDATE should be sent to change default 
   addresses after ICE negotiation.
 - pjsip_inv_update() now allows NULL offer, in which case the
   UPDATE will be sent without SDP.


git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@3215 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjsip/src/pjsip-ua/sip_inv.c b/pjsip/src/pjsip-ua/sip_inv.c
index ecfebec..c58a92f 100644
--- a/pjsip/src/pjsip-ua/sip_inv.c
+++ b/pjsip/src/pjsip-ua/sip_inv.c
@@ -2393,7 +2393,7 @@
     pj_status_t status = PJ_SUCCESS;
 
     /* Verify arguments. */
-    PJ_ASSERT_RETURN(inv && p_tdata && offer, PJ_EINVAL);
+    PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL);
 
     /* Dialog must have been established */
     PJ_ASSERT_RETURN(inv->dlg->state == PJSIP_DIALOG_STATE_ESTABLISHED,
@@ -2406,25 +2406,27 @@
     /* 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;
+    /* Process offer, if any */
+    if (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;
+	}
+
+	/* Notify negotiator about the new offer. This will fix the offer
+	 * with correct SDP origin.
+	 */
+	status = pjmedia_sdp_neg_modify_local_offer(inv->pool_prov, inv->neg,
+						    offer);
+	if (status != PJ_SUCCESS)
+	    goto on_error;
+
+	/* Retrieve the "fixed" offer from negotiator */
+	pjmedia_sdp_neg_get_neg_local(inv->neg, &offer);
     }
 
-    /* Notify negotiator about the new offer. This will fix the offer
-     * with correct SDP origin.
-     */
-    status = pjmedia_sdp_neg_modify_local_offer(inv->pool_prov, inv->neg,
-						offer);
-    if (status != PJ_SUCCESS)
-	goto on_error;
-
-    /* Retrieve the "fixed" offer from negotiator */
-    pjmedia_sdp_neg_get_neg_local(inv->neg, &offer);
-
     /* Update Contact if required */
     if (new_contact) {
 	pj_str_t tmp;
@@ -2449,8 +2451,10 @@
 	    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);
+    if (offer) {
+	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);
@@ -2879,6 +2883,16 @@
     /* Get/attach invite session's transaction data */
     else 
     {
+	/* Session-Timer needs to see any error responses, to determine
+	 * whether peer supports UPDATE with empty body.
+	 */
+	if (tsx->state == PJSIP_TSX_STATE_COMPLETED &&
+	    tsx->role == PJSIP_ROLE_UAC)
+	{
+	    status = handle_timer_response(inv, e->body.tsx_state.src.rdata,
+					   PJ_FALSE);
+	}
+
 	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);
diff --git a/pjsip/src/pjsip-ua/sip_timer.c b/pjsip/src/pjsip-ua/sip_timer.c
index 34676e0..bb97c3d 100644
--- a/pjsip/src/pjsip-ua/sip_timer.c
+++ b/pjsip/src/pjsip-ua/sip_timer.c
@@ -59,6 +59,7 @@
     pj_timer_entry		 timer;		/**< Timer entry	    */
     pj_bool_t			 use_update;	/**< Use UPDATE method to
 						     refresh the session    */
+    pj_bool_t		  	 with_sdp;	/**< SDP in UPDATE?	    */
     pjsip_role_e		 role;		/**< Role in last INVITE/
 						     UPDATE transaction.    */
 
@@ -321,7 +322,7 @@
  * the session if UA is the refresher, otherwise it is time to end 
  * the session.
  */
-void timer_cb(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry)
+static void timer_cb(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry)
 {
     pjsip_inv_session *inv = (pjsip_inv_session*) entry->user_data;
     pjsip_tx_data *tdata = NULL;
@@ -330,38 +331,55 @@
 
     pj_assert(inv);
 
+    inv->timer->timer.id = 0;
+
     PJ_UNUSED_ARG(timer_heap);
 
-    /* When there is a pending INVITE transaction, delay/reschedule this timer
-     * for five seconds to cover the case that pending INVITE fails and the
-     * previous session is still active. If the pending INVITE is successful, 
-     * timer state will be updated, i.e: restarted or stopped.
-     */
-    if (inv->invite_tsx != NULL) {
-	pj_time_val delay = {5};
-
-	inv->timer->timer.id = 1;
-	pjsip_endpt_schedule_timer(inv->dlg->endpt, &inv->timer->timer, &delay);
-	return;
-    }
-
     /* Lock dialog. */
     pjsip_dlg_inc_lock(inv->dlg);
 
     /* Check our role */
-    as_refresher = 
+    as_refresher =
 	(inv->timer->refresher == TR_UAC && inv->timer->role == PJSIP_ROLE_UAC) ||
 	(inv->timer->refresher == TR_UAS && inv->timer->role == PJSIP_ROLE_UAS);
 
     /* Do action based on role, refresher or refreshee */
     if (as_refresher) {
-
 	pj_time_val now;
 
+	/* As refresher, reshedule the refresh request on the following:
+	 *  - msut not send re-INVITE if another INVITE or SDP negotiation
+	 *    is in progress.
+	 *  - must not send UPDATE with SDP if SDP negotiation is in progress
+	 */
+	pjmedia_sdp_neg_state neg_state = pjmedia_sdp_neg_get_state(inv->neg);
+	if ( (!inv->timer->use_update && (
+			inv->invite_tsx != NULL ||
+			neg_state != PJMEDIA_SDP_NEG_STATE_DONE)
+             )
+	     ||
+	     (inv->timer->use_update && inv->timer->with_sdp &&
+		     neg_state != PJMEDIA_SDP_NEG_STATE_DONE
+	     )
+	   )
+	{
+	    pj_time_val delay = {1, 0};
+
+	    inv->timer->timer.id = 1;
+	    pjsip_endpt_schedule_timer(inv->dlg->endpt, &inv->timer->timer,
+				       &delay);
+	    pjsip_dlg_dec_lock(inv->dlg);
+	    return;
+	}
+
 	/* Refresher, refresh the session */
 	if (inv->timer->use_update) {
-	    /* Create UPDATE request without offer */
-	    status = pjsip_inv_update(inv, NULL, NULL, &tdata);
+	    const pjmedia_sdp_session *offer = NULL;
+
+	    if (inv->timer->with_sdp) {
+		pjmedia_sdp_neg_get_active_local(inv->neg, &offer);
+	    }
+	    status = pjsip_inv_update(inv, NULL, offer, &tdata);
 	} else {
 	    /* Create re-INVITE without modifying session */
 	    pjsip_msg_body *body;
@@ -384,8 +402,8 @@
 	}
 
 	pj_gettimeofday(&now);
-	PJ_LOG(4, (inv->pool->obj_name, 
-		   "Refresh session after %ds (expiration period=%ds)",
+	PJ_LOG(4, (inv->pool->obj_name,
+		   "Refreshing session after %ds (expiration period=%ds)",
 		   (now.sec-inv->timer->last_refresh.sec),
 		   inv->timer->setting.sess_expires));
     } else {
@@ -414,23 +432,19 @@
 
     /* Print error message, if any */
     if (status != PJ_SUCCESS) {
-	char errmsg[PJ_ERR_MSG_SIZE];
-
 	if (tdata)
 	    pjsip_tx_data_dec_ref(tdata);
 
-	pj_strerror(status, errmsg, sizeof(errmsg));
-	PJ_LOG(2, (inv->pool->obj_name, "Session timer fails in %s session, "
-					"err code=%d (%s)",
-					(as_refresher? "refreshing" : 
-						       "terminating"),
-					status, errmsg));					
+	PJ_PERROR(2, (inv->pool->obj_name, status,
+		      "Error in %s session timer",
+		      (as_refresher? "refreshing" : "terminating")));
     }
 }
 
 /* Start Session Timers */
 static void start_timer(pjsip_inv_session *inv)
 {
+    const pj_str_t UPDATE = { "UPDATE", 6 };
     pjsip_timer *timer = inv->timer;
     pj_time_val delay = {0};
 
@@ -438,6 +452,14 @@
 
     stop_timer(inv);
 
+    inv->timer->use_update =
+	    (pjsip_dlg_remote_has_cap(inv->dlg, PJSIP_H_ALLOW, NULL,
+				      &UPDATE) == PJSIP_DIALOG_CAP_SUPPORTED);
+    if (!inv->timer->use_update) {
+	/* INVITE always needs SDP */
+	inv->timer->with_sdp = PJ_TRUE;
+    }
+
     pj_timer_entry_init(&timer->timer,
 			1,		    /* id */
 			inv,		    /* user data */
@@ -837,14 +859,30 @@
 	     */
 	    inv->timer->refresher = TR_UAC;
 
-	PJ_TODO(CHECK_IF_REMOTE_SUPPORT_UPDATE);
-
 	/* Remember our role in this transaction */
 	inv->timer->role = PJSIP_ROLE_UAC;
 
 	/* Finally, set active flag and start the Session Timers */
 	inv->timer->active = PJ_TRUE;
 	start_timer(inv);
+
+    } else if (pjsip_method_cmp(&rdata->msg_info.cseq->method,
+				&pjsip_update_method) == 0 &&
+	       msg->line.status.code >= 400 && msg->line.status.code < 600)
+    {
+	/* This is to handle error response to previous UPDATE that was
+	 * sent without SDP. In this case, retry sending UPDATE but
+	 * with SDP this time.
+	 * Note: the additional expressions are to check that the
+	 *       UPDATE was really the one sent by us, not by other
+	 *       call components (e.g. to change codec)
+	 */
+	if (inv->timer->timer.id == 0 && inv->timer->use_update &&
+	    inv->timer->with_sdp == PJ_FALSE)
+	{
+	    inv->timer->with_sdp = PJ_TRUE;
+	    timer_cb(NULL, &inv->timer->timer);
+	}
     }
 
     return PJ_SUCCESS;
diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c
index 8401208..fd8163a 100644
--- a/pjsip/src/pjsua-lib/pjsua_media.c
+++ b/pjsip/src/pjsua-lib/pjsua_media.c
@@ -855,10 +855,25 @@
 		pj_sockaddr_cmp(&tpinfo.sock_info.rtp_addr_name,
 				&pjsua_var.calls[id].med_rtp_addr))
 	    {
+		pj_bool_t use_update;
+		const pj_str_t STR_UPDATE = { "UPDATE", 6 };
+		pjsip_dialog_cap_status support_update;
+		pjsip_dialog *dlg;
+
+		dlg = pjsua_var.calls[id].inv->dlg;
+		support_update = pjsip_dlg_remote_has_cap(dlg, PJSIP_H_ALLOW,
+							  NULL, &STR_UPDATE);
+		use_update = (support_update == PJSIP_DIALOG_CAP_SUPPORTED);
+
 		PJ_LOG(4,(THIS_FILE, 
 		          "ICE default transport address has changed for "
-			  "call %d, sending UPDATE", id));
-		pjsua_call_update(id, 0, NULL);
+			  "call %d, sending %s", id,
+			  (use_update ? "UPDATE" : "re-INVITE")));
+
+		if (use_update)
+		    pjsua_call_update(id, 0, NULL);
+		else
+		    pjsua_call_reinvite(id, 0, NULL);
 	    }
 	}
 	break;