[Asterisk-code-review] chan_pjsip: Transmit REFER waits for the REFER result setting TRANSF... (...asterisk[16])

George Joseph asteriskteam at digium.com
Tue Jul 23 09:18:32 CDT 2019


George Joseph has submitted this change and it was merged. ( https://gerrit.asterisk.org/c/asterisk/+/11243 )

Change subject: chan_pjsip:  Transmit REFER waits for the REFER result setting TRANSFERSTATUS
......................................................................

chan_pjsip:  Transmit REFER waits for the REFER result setting TRANSFERSTATUS

Previously, when a Transfer (REFER) was performed, chan_pjsip would set
the TRANSFERSTATUS to SUCCESS when the REFER was queued up.  This did not
reflect a successful/unsuccessful transfer the way chan_sip did.
Added a callback module to process the refer subscription information.

Now depends on res_pjsip_pubsub so call transfer progress can be monitored
and reported

ASTERISK-26968 #close
Reported-by: Dan Cropp

Change-Id: If6c27c757c66f71e8b75e3fe49da53ebe62395dc
---
M channels/chan_pjsip.c
A doc/UPGRADE-staging/chan_pjsip_refer_fix.txt
2 files changed, 143 insertions(+), 3 deletions(-)

Approvals:
  George Joseph: Looks good to me, approved; Approved for Submit



diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c
index d1f7b6a..8ab1549 100644
--- a/channels/chan_pjsip.c
+++ b/channels/chan_pjsip.c
@@ -28,6 +28,7 @@
 /*** MODULEINFO
 	<depend>pjproject</depend>
 	<depend>res_pjsip</depend>
+	<depend>res_pjsip_pubsub</depend>
 	<depend>res_pjsip_session</depend>
 	<support_level>core</support_level>
  ***/
@@ -1894,6 +1895,130 @@
 	ast_queue_control_data(session->channel, AST_CONTROL_TRANSFER, &message, sizeof(message));
 }
 
+/*! \brief REFER Callback module, used to attach session data structure to subscription */
+static pjsip_module refer_callback_module = {
+	.name = { "REFER Callback", 14 },
+	.id = -1,
+};
+
+/*!
+ * \brief Callback function to report status of implicit REFER-NOTIFY subscription.
+ *
+ * This function will be called on any state change in the REFER-NOTIFY subscription.
+ * Its primary purpose is to report SUCCESS/FAILURE of a transfer initiated via
+ * \ref transfer_refer as well as to terminate the subscription, if necessary.
+ */
+static void xfer_client_on_evsub_state(pjsip_evsub *sub, pjsip_event *event)
+{
+	struct ast_sip_session *session;
+	struct ast_channel *chan = NULL;
+	enum ast_control_transfer message = AST_TRANSFER_SUCCESS;
+	int res = 0;
+
+	if (!event) {
+		return;
+	}
+
+	session = pjsip_evsub_get_mod_data(sub, refer_callback_module.id);
+	if (!session) {
+		return;
+	}
+
+	chan = session->channel;
+	if (!chan) {
+		return;
+	}
+
+	if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_ACCEPTED) {
+		/* Check if subscription is suppressed and terminate and send completion code, if so. */
+		pjsip_rx_data *rdata;
+		pjsip_generic_string_hdr *refer_sub;
+		const pj_str_t REFER_SUB = { "Refer-Sub", 9 };
+
+		ast_debug(3, "Transfer accepted on channel %s\n", ast_channel_name(chan));
+
+		/* Check if response message */
+		if (event->type == PJSIP_EVENT_TSX_STATE && event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
+			rdata = event->body.tsx_state.src.rdata;
+
+			/* Find Refer-Sub header */
+			refer_sub = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &REFER_SUB, NULL);
+
+			/* Check if subscription is suppressed. If it is, the far end will not terminate it,
+			 * and the subscription will remain active until it times out.  Terminating it here
+			 * eliminates the unnecessary timeout.
+			 */
+			if (refer_sub && !pj_stricmp2(&refer_sub->hvalue, "false")) {
+				/* Since no subscription is desired, assume that call has been transferred successfully. */
+				/* Terminate subscription. */
+				pjsip_evsub_terminate(sub, PJ_TRUE);
+				res = -1;
+			}
+		}
+	} else if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_ACTIVE ||
+			pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+		/* Check for NOTIFY complete or error. */
+		pjsip_msg *msg;
+		pjsip_msg_body *body;
+		pjsip_status_line status_line = { .code = PJSIP_SC_NULL };
+		pj_bool_t is_last;
+		pj_status_t status;
+
+		if (event->type == PJSIP_EVENT_TSX_STATE && event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
+			pjsip_rx_data *rdata;
+
+			rdata = event->body.tsx_state.src.rdata;
+			msg = rdata->msg_info.msg;
+
+			if (!pjsip_method_cmp(&msg->line.req.method, pjsip_get_notify_method())) {
+				body = msg->body;
+				if (body && !pj_stricmp2(&body->content_type.type, "message")
+					&& !pj_stricmp2(&body->content_type.subtype, "sipfrag")) {
+					pjsip_parse_status_line((char *)body->data, body->len, &status_line);
+				}
+			}
+		} else {
+			status_line.code = 500;
+			status_line.reason = *pjsip_get_status_text(500);
+		}
+
+		is_last = (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED);
+		/* If the status code is >= 200, the subscription is finished. */
+		if (status_line.code >= 200 || is_last) {
+			res = -1;
+
+			/* If the subscription has terminated, return AST_TRANSFER_SUCCESS for 2XX.
+			 * Any other status code returns AST_TRANSFER_FAILED.
+			 * The subscription should not terminate for any code < 200,
+			 * but if it does, that constitutes a failure. */
+			if (status_line.code < 200 || status_line.code >= 300) {
+				message = AST_TRANSFER_FAILED;
+			}
+			/* If subscription not terminated and subscription is finished (status code >= 200)
+			 * terminate it */
+			if (!is_last) {
+				pjsip_tx_data *tdata;
+
+				status = pjsip_evsub_initiate(sub, pjsip_get_subscribe_method(), 0, &tdata);
+				if (status == PJ_SUCCESS) {
+					pjsip_evsub_send_request(sub, tdata);
+				}
+			}
+			/* Finished. Remove session from subscription */
+			pjsip_evsub_set_mod_data(sub, refer_callback_module.id, NULL);
+			ast_debug(3, "Transfer channel %s completed: %d %.*s (%s)\n",
+					ast_channel_name(chan),
+					status_line.code,
+					(int)status_line.reason.slen, status_line.reason.ptr,
+					(message == AST_TRANSFER_SUCCESS) ? "Success" : "Failure");
+		}
+	}
+
+	if (res) {
+		ast_queue_control_data(session->channel, AST_CONTROL_TRANSFER, &message, sizeof(message));
+	}
+}
+
 static void transfer_refer(struct ast_sip_session *session, const char *target)
 {
 	pjsip_evsub *sub;
@@ -1902,14 +2027,20 @@
 	pjsip_tx_data *packet;
 	const char *ref_by_val;
 	char local_info[pj_strlen(&session->inv_session->dlg->local.info_str) + 1];
+	struct pjsip_evsub_user xfer_cb;
 
-	if (pjsip_xfer_create_uac(session->inv_session->dlg, NULL, &sub) != PJ_SUCCESS) {
+	pj_bzero(&xfer_cb, sizeof(xfer_cb));
+	xfer_cb.on_evsub_state = &xfer_client_on_evsub_state;
+
+	if (pjsip_xfer_create_uac(session->inv_session->dlg, &xfer_cb, &sub) != PJ_SUCCESS) {
 		message = AST_TRANSFER_FAILED;
 		ast_queue_control_data(session->channel, AST_CONTROL_TRANSFER, &message, sizeof(message));
 
 		return;
 	}
 
+	pjsip_evsub_set_mod_data(sub, refer_callback_module.id, session);
+
 	if (pjsip_xfer_initiate(sub, pj_cstr(&tmp, target), &packet) != PJ_SUCCESS) {
 		message = AST_TRANSFER_FAILED;
 		ast_queue_control_data(session->channel, AST_CONTROL_TRANSFER, &message, sizeof(message));
@@ -1927,7 +2058,6 @@
 	}
 
 	pjsip_xfer_send_request(sub, packet);
-	ast_queue_control_data(session->channel, AST_CONTROL_TRANSFER, &message, sizeof(message));
 }
 
 static int transfer(void *data)
@@ -3144,6 +3274,8 @@
 		goto end;
 	}
 
+	ast_sip_register_service(&refer_callback_module);
+
 	ast_sip_session_register_supplement(&chan_pjsip_supplement);
 	ast_sip_session_register_supplement(&chan_pjsip_supplement_response);
 
@@ -3180,6 +3312,7 @@
 	ast_sip_session_unregister_supplement(&chan_pjsip_supplement_response);
 	ast_sip_session_unregister_supplement(&chan_pjsip_supplement);
 	ast_sip_session_unregister_supplement(&call_pickup_supplement);
+	ast_sip_unregister_service(&refer_callback_module);
 	ast_custom_function_unregister(&dtmf_mode_function);
 	ast_custom_function_unregister(&media_offer_function);
 	ast_custom_function_unregister(&chan_pjsip_dial_contacts_function);
@@ -3205,6 +3338,8 @@
 	ast_sip_session_unregister_supplement(&chan_pjsip_ack_supplement);
 	ast_sip_session_unregister_supplement(&call_pickup_supplement);
 
+	ast_sip_unregister_service(&refer_callback_module);
+
 	ast_custom_function_unregister(&dtmf_mode_function);
 	ast_custom_function_unregister(&media_offer_function);
 	ast_custom_function_unregister(&chan_pjsip_dial_contacts_function);
@@ -3223,5 +3358,5 @@
 	.load = load_module,
 	.unload = unload_module,
 	.load_pri = AST_MODPRI_CHANNEL_DRIVER,
-	.requires = "res_pjsip,res_pjsip_session",
+	.requires = "res_pjsip,res_pjsip_session,res_pjsip_pubsub",
 );
diff --git a/doc/UPGRADE-staging/chan_pjsip_refer_fix.txt b/doc/UPGRADE-staging/chan_pjsip_refer_fix.txt
new file mode 100644
index 0000000..301930f
--- /dev/null
+++ b/doc/UPGRADE-staging/chan_pjsip_refer_fix.txt
@@ -0,0 +1,5 @@
+Subject: chan_pjsip
+Subject: Core
+
+res_pjsip_pubsub is now required so call transfer progress can be monitored
+and reported in the channel variable TRANSFERSTATUS.

-- 
To view, visit https://gerrit.asterisk.org/c/asterisk/+/11243
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings

Gerrit-Project: asterisk
Gerrit-Branch: 16
Gerrit-Change-Id: If6c27c757c66f71e8b75e3fe49da53ebe62395dc
Gerrit-Change-Number: 11243
Gerrit-PatchSet: 6
Gerrit-Owner: Dan Cropp <dan at amtelco.com>
Gerrit-Reviewer: Dan Cropp <dan at amtelco.com>
Gerrit-Reviewer: Friendly Automation
Gerrit-Reviewer: George Joseph <gjoseph at digium.com>
Gerrit-Reviewer: Joshua Colp <jcolp at digium.com>
Gerrit-Reviewer: Kevin Harwell <kharwell at digium.com>
Gerrit-Reviewer: Matthew Fredrickson <creslin at digium.com>
Gerrit-MessageType: merged
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20190723/e68652cb/attachment-0001.html>


More information about the asterisk-code-review mailing list