Ticket #635: Disconnect the other call leg when multiple 2xx/OK responses to INVITE are received due to forking

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@2315 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h
index e377e05..43d28a8 100644
--- a/pjsip/include/pjsua-lib/pjsua.h
+++ b/pjsip/include/pjsua-lib/pjsua.h
@@ -1230,6 +1230,20 @@
     int		     srtp_secure_signaling;
 #endif
 
+    /**
+     * Disconnect other call legs when more than one 2xx responses for 
+     * outgoing INVITE are received due to forking. Currently the library
+     * is not able to handle simultaneous forked media, so disconnecting
+     * the other call legs is necessary. 
+     *
+     * With this setting enabled, the library will handle only one of the
+     * connected call leg, and the other connected call legs will be
+     * disconnected. 
+     *
+     * Default: PJ_TRUE (only disable this setting for testing purposes).
+     */
+    pj_bool_t	     hangup_forked_call;
+
 } pjsua_config;
 
 
diff --git a/pjsip/include/pjsua-lib/pjsua_internal.h b/pjsip/include/pjsua-lib/pjsua_internal.h
index ef1487a..1332a5a 100644
--- a/pjsip/include/pjsua-lib/pjsua_internal.h
+++ b/pjsip/include/pjsua-lib/pjsua_internal.h
@@ -498,7 +498,7 @@
 void pjsua_init_tpselector(pjsua_transport_id tp_id,
 			   pjsip_tpselector *sel);
 
-
+pjsip_dialog* on_dlg_forked(pjsip_dialog *first_set, pjsip_rx_data *res);
 pj_status_t acquire_call(const char *title,
                          pjsua_call_id call_id,
                          pjsua_call **p_call,
diff --git a/pjsip/src/pjsip/sip_dialog.c b/pjsip/src/pjsip/sip_dialog.c
index 2b3203f..4489cb8 100644
--- a/pjsip/src/pjsip/sip_dialog.c
+++ b/pjsip/src/pjsip/sip_dialog.c
@@ -580,31 +580,39 @@
 				    pjsip_dialog **new_dlg )
 {
     pjsip_dialog *dlg;
-    const pjsip_route_hdr *r;
+    const pjsip_msg *msg = rdata->msg_info.msg;
+    const pjsip_hdr *end_hdr, *hdr;
+    const pjsip_contact_hdr *contact;
     pj_status_t status;
 
     /* Check arguments. */
     PJ_ASSERT_RETURN(first_dlg && rdata && new_dlg, PJ_EINVAL);
     
     /* rdata must be response message. */
-    PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_RESPONSE_MSG,
+    PJ_ASSERT_RETURN(msg->type == PJSIP_RESPONSE_MSG,
 		     PJSIP_ENOTRESPONSEMSG);
 
     /* Status code MUST be 1xx (but not 100), or 2xx */
-    status = rdata->msg_info.msg->line.status.code;
+    status = msg->line.status.code;
     PJ_ASSERT_RETURN( (status/100==1 && status!=100) ||
 		      (status/100==2), PJ_EBUG);
 
     /* To tag must present in the response. */
     PJ_ASSERT_RETURN(rdata->msg_info.to->tag.slen != 0, PJSIP_EMISSINGTAG);
 
+    /* Find Contact header in the response */
+    contact = (const pjsip_contact_hdr*)
+	      pjsip_msg_find_hdr(msg, PJSIP_H_CONTACT, NULL);
+    if (contact == NULL)
+	return PJSIP_EMISSINGHDR;
+
     /* Create the dialog. */
     status = create_dialog((pjsip_user_agent*)first_dlg->ua, &dlg);
     if (status != PJ_SUCCESS)
 	return status;
 
-    /* Clone remote target. */
-    dlg->target = (pjsip_uri*) pjsip_uri_clone(dlg->pool, first_dlg->target);
+    /* Set remote target from the response. */
+    dlg->target = (pjsip_uri*) pjsip_uri_clone(dlg->pool, contact->uri);
 
     /* Clone local info. */
     dlg->local.info = (pjsip_fromto_hdr*) 
@@ -636,7 +644,7 @@
     dlg->role = PJSIP_ROLE_UAC;
 
     /* Dialog state depends on the response. */
-    status = rdata->msg_info.msg->line.status.code/100;
+    status = msg->line.status.code/100;
     if (status == 1 || status == 2)
 	dlg->state = PJSIP_DIALOG_STATE_ESTABLISHED;
     else {
@@ -651,17 +659,18 @@
     dlg->call_id = (pjsip_cid_hdr*) 
     		   pjsip_hdr_clone(dlg->pool, first_dlg->call_id);
 
-    /* Duplicate Route-Set. */
+    /* Get route-set from the response. */
     pj_list_init(&dlg->route_set);
-    r = first_dlg->route_set.next;
-    while (r != &first_dlg->route_set) {
-	pjsip_route_hdr *h;
-
-	h = (pjsip_route_hdr*) pjsip_hdr_clone(dlg->pool, r);
-	pj_list_push_back(&dlg->route_set, h);
-
-	r = r->next;
+    end_hdr = &msg->hdr;
+    for (hdr=msg->hdr.prev; hdr!=end_hdr; hdr=hdr->prev) {
+	if (hdr->type == PJSIP_H_RECORD_ROUTE) {
+	    pjsip_route_hdr *r;
+	    r = (pjsip_route_hdr*) pjsip_hdr_clone(dlg->pool, hdr);
+	    pjsip_routing_hdr_set_route(r);
+	    pj_list_push_back(&dlg->route_set, r);
+	}
     }
+
     //dlg->route_set_frozen = PJ_TRUE;
 
     /* Clone client authentication session. */
@@ -1813,6 +1822,41 @@
 	    break;
     }
 
+    /* Handle the case of forked response, when the application creates
+     * the forked dialog but not the invite session. In this case, the
+     * forked 200/OK response will be unhandled, and we must send ACK
+     * here.
+     */
+    if (dlg->usage_cnt==0) {
+	pj_status_t status;
+
+	if (rdata->msg_info.cseq->method.id==PJSIP_INVITE_METHOD && 
+	    rdata->msg_info.msg->line.status.code/100 == 2) 
+	{
+	    pjsip_tx_data *ack;
+
+	    status = pjsip_dlg_create_request(dlg, &pjsip_ack_method,
+					      rdata->msg_info.cseq->cseq,
+					      &ack);
+	    if (status == PJ_SUCCESS)
+		status = pjsip_dlg_send_request(dlg, ack, -1, NULL);
+	} else if (rdata->msg_info.msg->line.status.code==401 ||
+		   rdata->msg_info.msg->line.status.code==407)
+	{
+	    pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata);
+	    pjsip_tx_data *tdata;
+	    
+	    status = pjsip_auth_clt_reinit_req( &dlg->auth_sess, 
+						rdata, tsx->last_tx,
+						&tdata);
+	    
+	    if (status == PJ_SUCCESS) {
+		/* Re-send request. */
+		status = pjsip_dlg_send_request(dlg, tdata, -1, NULL);
+	    }
+	}
+    }
+
     /* Unhandled response does not necessarily mean error because
        dialog usages may choose to process the transaction state instead.
     if (i==dlg->usage_cnt) {
diff --git a/pjsip/src/pjsip/sip_ua_layer.c b/pjsip/src/pjsip/sip_ua_layer.c
index 21ff5f3..a614d16 100644
--- a/pjsip/src/pjsip/sip_ua_layer.c
+++ b/pjsip/src/pjsip/sip_ua_layer.c
@@ -832,6 +832,10 @@
 	    if (mod_ua.param.on_dlg_forked) {
 		dlg = (*mod_ua.param.on_dlg_forked)(dlg_set->dlg_list.next, 
 						    rdata);
+		if (dlg == NULL) {
+		    pj_mutex_unlock(mod_ua.mutex);
+		    return PJ_TRUE;
+		}
 	    } else {
 		dlg = dlg_set->dlg_list.next;
 
diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c
index 7eb061c..546be66 100644
--- a/pjsip/src/pjsua-lib/pjsua_call.c
+++ b/pjsip/src/pjsua-lib/pjsua_call.c
@@ -2943,6 +2943,47 @@
 
 
 /*
+ * Callback from UA layer when forked dialog response is received.
+ */
+pjsip_dialog* on_dlg_forked(pjsip_dialog *dlg, pjsip_rx_data *res)
+{
+    if (dlg->uac_has_2xx && 
+	res->msg_info.cseq->method.id == PJSIP_INVITE_METHOD &&
+	pjsip_rdata_get_tsx(res) == NULL &&
+	res->msg_info.msg->line.status.code/100 == 2) 
+    {
+	pjsip_dialog *forked_dlg;
+	pjsip_tx_data *bye;
+	pj_status_t status;
+
+	/* Create forked dialog */
+	status = pjsip_dlg_fork(dlg, res, &forked_dlg);
+	if (status != PJ_SUCCESS)
+	    return NULL;
+
+	pjsip_dlg_inc_lock(forked_dlg);
+
+	/* Disconnect the call */
+	status = pjsip_dlg_create_request(forked_dlg, &pjsip_bye_method,
+					  -1, &bye);
+	if (status == PJ_SUCCESS) {
+	    status = pjsip_dlg_send_request(forked_dlg, bye, -1, NULL);
+	}
+
+	pjsip_dlg_dec_lock(forked_dlg);
+
+	if (status != PJ_SUCCESS) {
+	    return NULL;
+	}
+
+	return forked_dlg;
+
+    } else {
+	return dlg;
+    }
+}
+
+/*
  * Disconnect call upon error.
  */
 static void call_disconnect( pjsip_inv_session *inv, 
diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c
index 40830d7..650217a 100644
--- a/pjsip/src/pjsua-lib/pjsua_core.c
+++ b/pjsip/src/pjsua-lib/pjsua_core.c
@@ -96,6 +96,7 @@
     cfg->use_srtp = PJSUA_DEFAULT_USE_SRTP;
     cfg->srtp_secure_signaling = PJSUA_DEFAULT_SRTP_SECURE_SIGNALING;
 #endif
+    cfg->hangup_forked_call = PJ_TRUE;
 }
 
 PJ_DEF(void) pjsua_config_dup(pj_pool_t *pool,
@@ -622,6 +623,7 @@
     pjsua_config	 default_cfg;
     pjsua_media_config	 default_media_cfg;
     const pj_str_t	 STR_OPTIONS = { "OPTIONS", 7 };
+    pjsip_ua_init_param  ua_init_param;
     pj_status_t status;
 
 
@@ -694,7 +696,11 @@
 
 
     /* Initialize UA layer module: */
-    status = pjsip_ua_init_module( pjsua_var.endpt, NULL );
+    pj_bzero(&ua_init_param, sizeof(ua_init_param));
+    if (ua_cfg->hangup_forked_call) {
+	ua_init_param.on_dlg_forked = &on_dlg_forked;
+    }
+    status = pjsip_ua_init_module( pjsua_var.endpt, &ua_init_param);
     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);