Ticket #370: Implemented callback notification to application when ICE negotiation fails (via on_call_media_state callback)

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@1435 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjmedia/include/pjmedia/transport_ice.h b/pjmedia/include/pjmedia/transport_ice.h
index cf72886..2a51ae7 100644
--- a/pjmedia/include/pjmedia/transport_ice.h
+++ b/pjmedia/include/pjmedia/transport_ice.h
@@ -40,6 +40,22 @@
 
 
 /**
+ * Structure containing callbacks to receive ICE notifications.
+ */
+typedef struct pjmedia_ice_cb
+{
+    /**
+     * This callback will be called when ICE negotiation completes.
+     *
+     * @param tp	PJMEDIA ICE transport.
+     * @param status	ICE negotiation result, PJ_SUCCESS on success.
+     */
+    void    (*on_ice_complete)(pjmedia_transport *tp,
+			       pj_status_t status);
+
+} pjmedia_ice_cb;
+
+/**
  * Create the media transport.
  *
  * @param endpt		The media endpoint.
@@ -47,6 +63,7 @@
  *			for logging purposes.
  * @param comp_cnt	Number of components to be created.
  * @param stun_cfg	Pointer to STUN configuration settings.
+ * @param cb		Optional callbacks.
  * @param p_tp		Pointer to receive the media transport instance.
  *
  * @return		PJ_SUCCESS on success, or the appropriate error code.
@@ -55,6 +72,7 @@
 					const char *name,
 					unsigned comp_cnt,
 					pj_stun_config *stun_cfg,
+					const pjmedia_ice_cb *cb,
 					pjmedia_transport **p_tp);
 
 /**
diff --git a/pjmedia/src/pjmedia/transport_ice.c b/pjmedia/src/pjmedia/transport_ice.c
index 019ba04..4fa21c2 100644
--- a/pjmedia/src/pjmedia/transport_ice.c
+++ b/pjmedia/src/pjmedia/transport_ice.c
@@ -25,6 +25,7 @@
 {
     pjmedia_transport	 base;
     pj_ice_strans	*ice_st;
+    pjmedia_ice_cb	 cb;
 
     pj_time_val		 start_ice;
     
@@ -95,6 +96,7 @@
 				       const char *name,
 				       unsigned comp_cnt,
 				       pj_stun_config *stun_cfg,
+				       const pjmedia_ice_cb *cb,
 	    			       pjmedia_transport **p_tp)
 {
     pj_ice_strans *ice_st;
@@ -123,6 +125,9 @@
     tp_ice->base.op = &tp_ice_op;
     tp_ice->base.type = PJMEDIA_TRANSPORT_TYPE_ICE;
 
+    if (cb)
+	pj_memcpy(&tp_ice->cb, cb, sizeof(pjmedia_ice_cb));
+
     ice_st->user_data = (void*)tp_ice;
 
     /* Done */
@@ -686,7 +691,7 @@
 
 
 static void ice_on_ice_complete(pj_ice_strans *ice_st, 
-			        pj_status_t status)
+			        pj_status_t result)
 {
     struct transport_ice *tp_ice = (struct transport_ice*) ice_st->user_data;
     pj_time_val end_ice;
@@ -698,30 +703,33 @@
     pj_gettimeofday(&end_ice);
     PJ_TIME_VAL_SUB(end_ice, tp_ice->start_ice);
 
-    if (status != PJ_SUCCESS) {
+    if (result != PJ_SUCCESS) {
 	char errmsg[PJ_ERR_MSG_SIZE];
-	pj_strerror(status, errmsg, sizeof(errmsg));
+	pj_strerror(result, errmsg, sizeof(errmsg));
 	PJ_LOG(1,(ice_st->obj_name, 
 		  "ICE negotiation failed after %d:%03ds: %s", 
 		  (int)end_ice.sec, (int)end_ice.msec,
 		  errmsg));
-	return;
+    } else {
+	check = &ice_st->ice->valid_list.checks[0];
+    
+	lcand = check->lcand;
+	rcand = check->rcand;
+
+	pj_ansi_strcpy(src_addr, pj_inet_ntoa(lcand->addr.ipv4.sin_addr));
+	pj_ansi_strcpy(dst_addr, pj_inet_ntoa(rcand->addr.ipv4.sin_addr));
+
+	PJ_LOG(4,(ice_st->obj_name, 
+		  "ICE negotiation completed in %d.%03ds. Sending from "
+		  "%s:%d to %s:%d",
+		  (int)end_ice.sec, (int)end_ice.msec,
+		  src_addr, pj_ntohs(lcand->addr.ipv4.sin_port),
+		  dst_addr, pj_ntohs(rcand->addr.ipv4.sin_port)));
     }
 
-    check = &ice_st->ice->valid_list.checks[0];
-    
-    lcand = check->lcand;
-    rcand = check->rcand;
-
-    pj_ansi_strcpy(src_addr, pj_inet_ntoa(lcand->addr.ipv4.sin_addr));
-    pj_ansi_strcpy(dst_addr, pj_inet_ntoa(rcand->addr.ipv4.sin_addr));
-
-    PJ_LOG(3,(ice_st->obj_name, 
-	      "ICE negotiation completed in %d.%03ds. Sending from "
-	      "%s:%d to %s:%d",
-	      (int)end_ice.sec, (int)end_ice.msec,
-	      src_addr, pj_ntohs(lcand->addr.ipv4.sin_port),
-	      dst_addr, pj_ntohs(rcand->addr.ipv4.sin_port)));
+    /* Notify application */
+    if (tp_ice->cb.on_ice_complete)
+	(*tp_ice->cb.on_ice_complete)(&tp_ice->base, result);
 }
 
 
diff --git a/pjnath/include/pjnath/ice_session.h b/pjnath/include/pjnath/ice_session.h
index 14dba41..b601385 100644
--- a/pjnath/include/pjnath/ice_session.h
+++ b/pjnath/include/pjnath/ice_session.h
@@ -464,6 +464,7 @@
     pj_uint8_t		*prefs;			    /**< Type preference.   */
     pj_bool_t		 is_complete;		    /**< Complete?	    */
     pj_status_t		 ice_status;		    /**< Error status.	    */
+    pj_timer_entry	 completion_timer;	    /**< To call callback.  */
     pj_ice_sess_cb	 cb;			    /**< Callback.	    */
 
     pj_stun_config	 stun_cfg;		    /**< STUN settings.	    */
diff --git a/pjnath/src/pjnath/ice_session.c b/pjnath/src/pjnath/ice_session.c
index 78422af..2763444 100644
--- a/pjnath/src/pjnath/ice_session.c
+++ b/pjnath/src/pjnath/ice_session.c
@@ -336,6 +336,12 @@
 	LOG4((ice->obj_name, "Destroying ICE session"));
     }
 
+    if (ice->completion_timer.id) {
+	pj_timer_heap_cancel(ice->stun_cfg.timer_heap, 
+			     &ice->completion_timer);
+	ice->completion_timer.id = PJ_FALSE;
+    }
+
     for (i=0; i<ice->comp_cnt; ++i) {
 	if (ice->comp[i].stun_sess) {
 	    pj_stun_session_destroy(ice->comp[i].stun_sess);
@@ -945,6 +951,20 @@
     return PJ_SUCCESS;
 }
 
+/* Timer callback to call on_ice_complete() callback */
+static void on_completion_timer(pj_timer_heap_t *th, 
+			        pj_timer_entry *te)
+{
+    pj_ice_sess *ice = (pj_ice_sess*) te->user_data;
+
+    PJ_UNUSED_ARG(th);
+
+    te->id = PJ_FALSE;
+
+    if (ice->cb.on_ice_complete)
+	(*ice->cb.on_ice_complete)(ice, ice->ice_status);
+}
+
 /* This function is called when ICE processing completes */
 static void on_ice_complete(pj_ice_sess *ice, pj_status_t status)
 {
@@ -962,7 +982,15 @@
 
 	/* Call callback */
 	if (ice->cb.on_ice_complete) {
-	    (*ice->cb.on_ice_complete)(ice, status);
+	    pj_time_val delay = {0, 0};
+
+	    ice->completion_timer.cb = &on_completion_timer;
+	    ice->completion_timer.user_data = (void*) ice;
+	    ice->completion_timer.id = PJ_TRUE;
+
+	    pj_timer_heap_schedule(ice->stun_cfg.timer_heap, 
+				   &ice->completion_timer,
+				   &delay);
 	}
     }
 }
diff --git a/pjsip-apps/src/pjsua/pjsua_app.c b/pjsip-apps/src/pjsua/pjsua_app.c
index a586b4d..13f1dbd 100644
--- a/pjsip-apps/src/pjsua/pjsua_app.c
+++ b/pjsip-apps/src/pjsua/pjsua_app.c
@@ -1675,6 +1675,14 @@
 	PJ_LOG(3,(THIS_FILE, 
 		  "Media for call %d is suspended (hold) by remote",
 		  call_id));
+    } else if (call_info.media_status == PJSUA_CALL_MEDIA_ERROR) {
+	pj_str_t reason = pj_str("ICE negotiation failed");
+
+	PJ_LOG(1,(THIS_FILE,
+		  "Media has reported error, disconnecting call"));
+
+	pjsua_call_hangup(call_id, 500, &reason, NULL);
+
     } else {
 	PJ_LOG(3,(THIS_FILE, 
 		  "Media for call %d is inactive",
diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h
index a82141b..f493de5 100644
--- a/pjsip/include/pjsua-lib/pjsua.h
+++ b/pjsip/include/pjsua-lib/pjsua.h
@@ -2375,6 +2375,9 @@
     /** The media is currently put on hold by remote endpoint */
     PJSUA_CALL_MEDIA_REMOTE_HOLD,
 
+    /** The media has reported error (e.g. ICE negotiation) */
+    PJSUA_CALL_MEDIA_ERROR
+
 } pjsua_call_media_status;
 
 
diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c
index 8700bd5..0e58d10 100644
--- a/pjsip/src/pjsua-lib/pjsua_media.c
+++ b/pjsip/src/pjsua-lib/pjsua_media.c
@@ -537,6 +537,42 @@
 }
 
 
+/* This callback is called when ICE negotiation completes */
+static void on_ice_complete(pjmedia_transport *tp, pj_status_t result)
+{
+    unsigned id, c;
+    pj_bool_t found = PJ_FALSE;
+
+    /* We're only interested with failure case */
+    if (result == PJ_SUCCESS)
+	return;
+
+    /* Find call which has this media transport */
+
+    PJSUA_LOCK();
+
+    for (id=0, c=0; id<PJSUA_MAX_CALLS && c<pjsua_var.call_cnt; ++id) {
+	pjsua_call *call = &pjsua_var.calls[id];
+	if (call->inv) {
+	    ++c;
+
+	    if (call->med_tp == tp) {
+		call->media_st = PJSUA_CALL_MEDIA_ERROR;
+		call->media_dir = PJMEDIA_DIR_NONE;
+		found = PJ_TRUE;
+		break;
+	    }
+	}
+    }
+
+    PJSUA_UNLOCK();
+
+    if (found && pjsua_var.ua_cfg.cb.on_call_media_state) {
+	pjsua_var.ua_cfg.cb.on_call_media_state(id);
+    }
+}
+
+
 /* Create ICE media transports (when ice is enabled) */
 static pj_status_t create_ice_media_transports(pjsua_transport_config *cfg)
 {
@@ -556,6 +592,7 @@
     /* Create each media transport */
     for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
 	pj_ice_strans_comp comp;
+	pjmedia_ice_cb ice_cb;
 	int next_port;
 #if PJMEDIA_ADVERTISE_RTCP
 	enum { COMP_CNT=2 };
@@ -563,8 +600,11 @@
 	enum { COMP_CNT=1 };
 #endif
 
+	pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb));
+	ice_cb.on_ice_complete = &on_ice_complete;
+
 	status = pjmedia_ice_create(pjsua_var.med_endpt, NULL, COMP_CNT,
-				    &pjsua_var.stun_cfg, 
+				    &pjsua_var.stun_cfg, &ice_cb,
 				    &pjsua_var.calls[i].med_tp);
 	if (status != PJ_SUCCESS) {
 	    pjsua_perror(THIS_FILE, "Unable to create ICE media transport",