Ticket #391: Added framework to send and receive arbitrary requests within call in PJSUA-LIB, with samples to send/receive DTMF with INFO in pjsua application

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@1477 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/build.symbian/pjsua_libU.def b/build.symbian/pjsua_libU.def
index 2769d65..10df6be 100644
--- a/build.symbian/pjsua_libU.def
+++ b/build.symbian/pjsua_libU.def
@@ -41,75 +41,77 @@
 	pjsua_call_make_call                     @ 40 NONAME
 	pjsua_call_reinvite                      @ 41 NONAME
 	pjsua_call_send_im                       @ 42 NONAME
-	pjsua_call_send_typing_ind               @ 43 NONAME
-	pjsua_call_set_hold                      @ 44 NONAME
-	pjsua_call_set_user_data                 @ 45 NONAME
-	pjsua_call_xfer                          @ 46 NONAME
-	pjsua_call_xfer_replaces                 @ 47 NONAME
-	pjsua_codec_get_param                    @ 48 NONAME
-	pjsua_codec_set_param                    @ 49 NONAME
-	pjsua_codec_set_priority                 @ 50 NONAME
-	pjsua_conf_add_port                      @ 51 NONAME
-	pjsua_conf_adjust_rx_level               @ 52 NONAME
-	pjsua_conf_adjust_tx_level               @ 53 NONAME
-	pjsua_conf_connect                       @ 54 NONAME
-	pjsua_conf_disconnect                    @ 55 NONAME
-	pjsua_conf_get_active_ports              @ 56 NONAME
-	pjsua_conf_get_max_ports                 @ 57 NONAME
-	pjsua_conf_get_port_info                 @ 58 NONAME
-	pjsua_conf_get_signal_level              @ 59 NONAME
-	pjsua_conf_remove_port                   @ 60 NONAME
-	pjsua_config_default                     @ 61 NONAME
-	pjsua_config_dup                         @ 62 NONAME
-	pjsua_create                             @ 63 NONAME
-	pjsua_destroy                            @ 64 NONAME
-	pjsua_dump                               @ 65 NONAME
-	pjsua_enum_accs                          @ 66 NONAME
-	pjsua_enum_buddies                       @ 67 NONAME
-	pjsua_enum_calls                         @ 68 NONAME
-	pjsua_enum_codecs                        @ 69 NONAME
-	pjsua_enum_conf_ports                    @ 70 NONAME
-	pjsua_enum_snd_devs                      @ 71 NONAME
-	pjsua_enum_transports                    @ 72 NONAME
-	pjsua_get_buddy_count                    @ 73 NONAME
-	pjsua_get_ec_tail                        @ 74 NONAME
-	pjsua_get_pjmedia_endpt                  @ 75 NONAME
-	pjsua_get_pjsip_endpt                    @ 76 NONAME
-	pjsua_get_pool_factory                   @ 77 NONAME
-	pjsua_get_snd_dev                        @ 78 NONAME
-	pjsua_handle_events                      @ 79 NONAME
-	pjsua_im_send                            @ 80 NONAME
-	pjsua_im_typing                          @ 81 NONAME
-	pjsua_init                               @ 82 NONAME
-	pjsua_logging_config_default             @ 83 NONAME
-	pjsua_logging_config_dup                 @ 84 NONAME
-	pjsua_media_config_default               @ 85 NONAME
-	pjsua_media_transports_create            @ 86 NONAME
-	pjsua_msg_data_init                      @ 87 NONAME
-	pjsua_perror                             @ 88 NONAME
-	pjsua_player_create                      @ 89 NONAME
-	pjsua_player_destroy                     @ 90 NONAME
-	pjsua_player_get_conf_port               @ 91 NONAME
-	pjsua_player_get_port                    @ 92 NONAME
-	pjsua_player_set_pos                     @ 93 NONAME
-	pjsua_playlist_create                    @ 94 NONAME
-	pjsua_pool_create                        @ 95 NONAME
-	pjsua_pres_dump                          @ 96 NONAME
-	pjsua_reconfigure_logging                @ 97 NONAME
-	pjsua_recorder_create                    @ 98 NONAME
-	pjsua_recorder_destroy                   @ 99 NONAME
-	pjsua_recorder_get_conf_port             @ 100 NONAME
-	pjsua_recorder_get_port                  @ 101 NONAME
-	pjsua_set_ec                             @ 102 NONAME
-	pjsua_set_no_snd_dev                     @ 103 NONAME
-	pjsua_set_null_snd_dev                   @ 104 NONAME
-	pjsua_set_snd_dev                        @ 105 NONAME
-	pjsua_start                              @ 106 NONAME
-	pjsua_transport_close                    @ 107 NONAME
-	pjsua_transport_config_default           @ 108 NONAME
-	pjsua_transport_config_dup               @ 109 NONAME
-	pjsua_transport_create                   @ 110 NONAME
-	pjsua_transport_get_info                 @ 111 NONAME
-	pjsua_transport_register                 @ 112 NONAME
-	pjsua_transport_set_enable               @ 113 NONAME
-	pjsua_verify_sip_url                     @ 114 NONAME
+	pjsua_call_send_request                  @ 43 NONAME
+	pjsua_call_send_typing_ind               @ 44 NONAME
+	pjsua_call_set_hold                      @ 45 NONAME
+	pjsua_call_set_user_data                 @ 46 NONAME
+	pjsua_call_update                        @ 47 NONAME
+	pjsua_call_xfer                          @ 48 NONAME
+	pjsua_call_xfer_replaces                 @ 49 NONAME
+	pjsua_codec_get_param                    @ 50 NONAME
+	pjsua_codec_set_param                    @ 51 NONAME
+	pjsua_codec_set_priority                 @ 52 NONAME
+	pjsua_conf_add_port                      @ 53 NONAME
+	pjsua_conf_adjust_rx_level               @ 54 NONAME
+	pjsua_conf_adjust_tx_level               @ 55 NONAME
+	pjsua_conf_connect                       @ 56 NONAME
+	pjsua_conf_disconnect                    @ 57 NONAME
+	pjsua_conf_get_active_ports              @ 58 NONAME
+	pjsua_conf_get_max_ports                 @ 59 NONAME
+	pjsua_conf_get_port_info                 @ 60 NONAME
+	pjsua_conf_get_signal_level              @ 61 NONAME
+	pjsua_conf_remove_port                   @ 62 NONAME
+	pjsua_config_default                     @ 63 NONAME
+	pjsua_config_dup                         @ 64 NONAME
+	pjsua_create                             @ 65 NONAME
+	pjsua_destroy                            @ 66 NONAME
+	pjsua_dump                               @ 67 NONAME
+	pjsua_enum_accs                          @ 68 NONAME
+	pjsua_enum_buddies                       @ 69 NONAME
+	pjsua_enum_calls                         @ 70 NONAME
+	pjsua_enum_codecs                        @ 71 NONAME
+	pjsua_enum_conf_ports                    @ 72 NONAME
+	pjsua_enum_snd_devs                      @ 73 NONAME
+	pjsua_enum_transports                    @ 74 NONAME
+	pjsua_get_buddy_count                    @ 75 NONAME
+	pjsua_get_ec_tail                        @ 76 NONAME
+	pjsua_get_pjmedia_endpt                  @ 77 NONAME
+	pjsua_get_pjsip_endpt                    @ 78 NONAME
+	pjsua_get_pool_factory                   @ 79 NONAME
+	pjsua_get_snd_dev                        @ 80 NONAME
+	pjsua_handle_events                      @ 81 NONAME
+	pjsua_im_send                            @ 82 NONAME
+	pjsua_im_typing                          @ 83 NONAME
+	pjsua_init                               @ 84 NONAME
+	pjsua_logging_config_default             @ 85 NONAME
+	pjsua_logging_config_dup                 @ 86 NONAME
+	pjsua_media_config_default               @ 87 NONAME
+	pjsua_media_transports_create            @ 88 NONAME
+	pjsua_msg_data_init                      @ 89 NONAME
+	pjsua_perror                             @ 90 NONAME
+	pjsua_player_create                      @ 91 NONAME
+	pjsua_player_destroy                     @ 92 NONAME
+	pjsua_player_get_conf_port               @ 93 NONAME
+	pjsua_player_get_port                    @ 94 NONAME
+	pjsua_player_set_pos                     @ 95 NONAME
+	pjsua_playlist_create                    @ 96 NONAME
+	pjsua_pool_create                        @ 97 NONAME
+	pjsua_pres_dump                          @ 98 NONAME
+	pjsua_reconfigure_logging                @ 99 NONAME
+	pjsua_recorder_create                    @ 100 NONAME
+	pjsua_recorder_destroy                   @ 101 NONAME
+	pjsua_recorder_get_conf_port             @ 102 NONAME
+	pjsua_recorder_get_port                  @ 103 NONAME
+	pjsua_set_ec                             @ 104 NONAME
+	pjsua_set_no_snd_dev                     @ 105 NONAME
+	pjsua_set_null_snd_dev                   @ 106 NONAME
+	pjsua_set_snd_dev                        @ 107 NONAME
+	pjsua_start                              @ 108 NONAME
+	pjsua_transport_close                    @ 109 NONAME
+	pjsua_transport_config_default           @ 110 NONAME
+	pjsua_transport_config_dup               @ 111 NONAME
+	pjsua_transport_create                   @ 112 NONAME
+	pjsua_transport_get_info                 @ 113 NONAME
+	pjsua_transport_register                 @ 114 NONAME
+	pjsua_transport_set_enable               @ 115 NONAME
+	pjsua_verify_sip_url                     @ 116 NONAME
diff --git a/pjsip-apps/src/pjsua/pjsua_app.c b/pjsip-apps/src/pjsua/pjsua_app.c
index ceb97e3..4b2b001 100644
--- a/pjsip-apps/src/pjsua/pjsua_app.c
+++ b/pjsip-apps/src/pjsua/pjsua_app.c
@@ -1596,6 +1596,72 @@
 
 
 /*
+ * Handler when a transaction within a call has changed state.
+ */
+static void on_call_tsx_state(pjsua_call_id call_id,
+			      pjsip_transaction *tsx,
+			      pjsip_event *e)
+{
+    const pjsip_method info_method = 
+    {
+	PJSIP_OTHER_METHOD,
+	{ "INFO", 4 }
+    };
+
+    if (pjsip_method_cmp(&tsx->method, &info_method)==0) {
+	/*
+	 * Handle INFO method.
+	 */
+	if (tsx->role == PJSIP_ROLE_UAC && 
+	    (tsx->state == PJSIP_TSX_STATE_COMPLETED ||
+	       tsx->state == PJSIP_TSX_STATE_TERMINATED &&
+	       e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED)) 
+	{
+	    /* Status of outgoing INFO request */
+	    if (tsx->status_code >= 200 && tsx->status_code < 300) {
+		PJ_LOG(4,(THIS_FILE, 
+			  "Call %d: DTMF sent successfully with INFO",
+			  call_id));
+	    } else if (tsx->status_code >= 300) {
+		PJ_LOG(4,(THIS_FILE, 
+			  "Call %d: Failed to send DTMF with INFO: %d/%.*s",
+			  call_id,
+		          tsx->status_code,
+			  (int)tsx->status_text.slen,
+			  tsx->status_text.ptr));
+	    }
+	} else if (tsx->role == PJSIP_ROLE_UAS &&
+		   tsx->state == PJSIP_TSX_STATE_TRYING)
+	{
+	    /* Answer incoming INFO with 200/OK */
+	    pjsip_rx_data *rdata;
+	    pjsip_tx_data *tdata;
+	    pj_status_t status;
+
+	    rdata = e->body.tsx_state.src.rdata;
+
+	    if (rdata->msg_info.msg->body) {
+		status = pjsip_endpt_create_response(tsx->endpt, rdata,
+						     200, NULL, &tdata);
+		if (status == PJ_SUCCESS)
+		    status = pjsip_tsx_send_msg(tsx, tdata);
+
+		PJ_LOG(3,(THIS_FILE, "Call %d: incoming INFO:\n%.*s", 
+			  call_id,
+			  (int)rdata->msg_info.msg->body->len,
+			  rdata->msg_info.msg->body->data));
+	    } else {
+		status = pjsip_endpt_create_response(tsx->endpt, rdata,
+						     400, NULL, &tdata);
+		if (status == PJ_SUCCESS)
+		    status = pjsip_tsx_send_msg(tsx, tdata);
+	    }
+	}
+    }
+}
+
+
+/*
  * Callback on media state changed event.
  * The action may connect the call to sound device, to file, or
  * to loop the call.
@@ -2862,6 +2928,53 @@
 	    }
 	    break;
 
+	case '*':
+	    /* Send DTMF with INFO */
+	    if (current_call == -1) {
+		
+		PJ_LOG(3,(THIS_FILE, "No current call"));
+
+	    } else {
+		const pj_str_t SIP_INFO = pj_str("INFO");
+		pj_str_t digits;
+		int call = current_call;
+		int i;
+		pj_status_t status;
+
+		if (!simple_input("DTMF strings to send (0-9*#A-B)", buf, 
+				  sizeof(buf)))
+		{
+			break;
+		}
+
+		if (call != current_call) {
+		    puts("Call has been disconnected");
+		    continue;
+		}
+
+		digits = pj_str(buf);
+		for (i=0; i<digits.slen; ++i) {
+		    pjsua_msg_data msg_data;
+		    char body[80];
+
+		    pjsua_msg_data_init(&msg_data);
+		    msg_data.content_type = pj_str("application/dtmf-relay");
+		    
+		    pj_ansi_snprintf(body, sizeof(body),
+				     "Signal=%c\r\n"
+				     "Duration=160",
+				     buf[i]);
+		    msg_data.msg_body = pj_str(body);
+
+		    status = pjsua_call_send_request(current_call, &SIP_INFO, 
+						     &msg_data);
+		    if (status != PJ_SUCCESS) {
+			break;
+		    }
+		}
+	    }
+	    break;
+
 	case 'S':
 	    /*
 	     * Send arbitrary request
@@ -3150,6 +3263,7 @@
     app_config.cfg.cb.on_call_state = &on_call_state;
     app_config.cfg.cb.on_call_media_state = &on_call_media_state;
     app_config.cfg.cb.on_incoming_call = &on_incoming_call;
+    app_config.cfg.cb.on_call_tsx_state = &on_call_tsx_state;
     app_config.cfg.cb.on_dtmf_digit = &call_on_dtmf_callback;
     app_config.cfg.cb.on_reg_state = &on_reg_state;
     app_config.cfg.cb.on_buddy_state = &on_buddy_state;
diff --git a/pjsip/docs/pjsua.jpg b/pjsip/docs/pjsua.jpg
index 479567b..c16a7a1 100644
--- a/pjsip/docs/pjsua.jpg
+++ b/pjsip/docs/pjsua.jpg
Binary files differ
diff --git a/pjsip/include/pjsip-ua/sip_inv.h b/pjsip/include/pjsip-ua/sip_inv.h
index a367fe1..77a499e 100644
--- a/pjsip/include/pjsip-ua/sip_inv.h
+++ b/pjsip/include/pjsip-ua/sip_inv.h
@@ -133,7 +133,8 @@
     /**
      * This callback is called whenever any transactions within the session
      * has changed their state. Application MAY implement this callback, 
-     * e.g. to monitor the progress of an outgoing request.
+     * e.g. to monitor the progress of an outgoing request, or to send
+     * response to unhandled incoming request (such as INFO).
      *
      * This callback is optional.
      *
diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h
index b727528..062943b 100644
--- a/pjsip/include/pjsua-lib/pjsua.h
+++ b/pjsip/include/pjsua-lib/pjsua.h
@@ -545,6 +545,21 @@
 			     pjsip_rx_data *rdata);
 
     /**
+     * This is a general notification callback which is called whenever
+     * a transaction within the call has changed state. Application can
+     * implement this callback for example to monitor the state of 
+     * outgoing requests, or to answer unhandled incoming requests 
+     * (such as INFO) with a final response.
+     *
+     * @param call_id	Call identification.
+     * @param tsx	The transaction which has changed state.
+     * @param e		Transaction event that caused the state change.
+     */
+    void (*on_call_tsx_state)(pjsua_call_id call_id, 
+			      pjsip_transaction *tsx,
+			      pjsip_event *e);
+
+    /**
      * Notify application when media state in the call has changed.
      * Normal application would need to implement this callback, e.g.
      * to connect the call's media to sound device.
@@ -2907,6 +2922,24 @@
 						const pjsua_msg_data*msg_data);
 
 /**
+ * Send arbitrary request with the call. This is useful for example to send
+ * INFO request. Note that application should not use this function to send
+ * requests which would change the invite session's state, such as re-INVITE,
+ * UPDATE, PRACK, and BYE.
+ *
+ * @param call_id	Call identification.
+ * @param method	SIP method of the request.
+ * @param msg_data	Optional message body and/or list of headers to be 
+ *			included in outgoing request.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_call_send_request(pjsua_call_id call_id,
+					     const pj_str_t *method,
+					     const pjsua_msg_data *msg_data);
+
+
+/**
  * Terminate all calls. This will initiate #pjsua_call_hangup() for all
  * currently active calls. 
  *
diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c
index e737c25..220e2b1 100644
--- a/pjsip/src/pjsua-lib/pjsua_call.c
+++ b/pjsip/src/pjsua-lib/pjsua_call.c
@@ -1662,6 +1662,52 @@
 
 
 /*
+ * Send arbitrary request.
+ */
+PJ_DEF(pj_status_t) pjsua_call_send_request(pjsua_call_id call_id,
+					    const pj_str_t *method_str,
+					    const pjsua_msg_data *msg_data)
+{
+    pjsua_call *call;
+    pjsip_dialog *dlg;
+    pjsip_method method;
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+		     PJ_EINVAL);
+
+    status = acquire_call("pjsua_call_send_request", call_id, &call, &dlg);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Init method */
+    pjsip_method_init_np(&method, (pj_str_t*)method_str);
+
+    /* Create request message. */
+    status = pjsip_dlg_create_request( call->inv->dlg, &method, -1, &tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create request", status);
+	goto on_return;
+    }
+
+    /* Add additional headers etc */
+    pjsua_process_msg_data( tdata, msg_data);
+
+    /* Send the request. */
+    status = pjsip_dlg_send_request( call->inv->dlg, tdata, -1, NULL);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to send request", status);
+	goto on_return;
+    }
+
+on_return:
+    pjsip_dlg_dec_lock(dlg);
+    return status;
+}
+
+
+/*
  * Terminate all calls.
  */
 PJ_DEF(void) pjsua_call_hangup_all(void)
@@ -2845,6 +2891,11 @@
 
     PJSUA_LOCK();
 
+    /* Notify application callback first */
+    if (pjsua_var.ua_cfg.cb.on_call_tsx_state) {
+	(*pjsua_var.ua_cfg.cb.on_call_tsx_state)(call->index, tsx, e);
+    }
+
     if (tsx->role==PJSIP_ROLE_UAS &&
 	tsx->state==PJSIP_TSX_STATE_TRYING &&
 	pjsip_method_cmp(&tsx->method, pjsip_get_refer_method())==0)