[asterisk-commits] file: branch file/gulp_transfer r387612 - /team/file/gulp_transfer/res/

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Fri May 3 14:08:57 CDT 2013


Author: file
Date: Fri May  3 14:08:55 2013
New Revision: 387612

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=387612
Log:
Add a module which currently does blind transfers. It's not yet complete (BYE getting sent, some lack of error handling, and silly us sending a BYE)

What it does do though... is nifty.

You can actually do a blind transfer, and it will actually happen, and a frame hook will actually get attached and monitor the progress and send
NOTIFY messages.

Added:
    team/file/gulp_transfer/res/res_sip_refer.c   (with props)

Added: team/file/gulp_transfer/res/res_sip_refer.c
URL: http://svnview.digium.com/svn/asterisk/team/file/gulp_transfer/res/res_sip_refer.c?view=auto&rev=387612
==============================================================================
--- team/file/gulp_transfer/res/res_sip_refer.c (added)
+++ team/file/gulp_transfer/res/res_sip_refer.c Fri May  3 14:08:55 2013
@@ -1,0 +1,456 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Joshua Colp <jcolp at digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+	<depend>pjproject</depend>
+	<depend>res_sip</depend>
+	<depend>res_sip_session</depend>
+	<depend>res_sip_pubsub</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjsip_ua.h>
+
+#include "asterisk/res_sip.h"
+#include "asterisk/res_sip_session.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/taskprocessor.h"
+#include "asterisk/bridging.h"
+#include "asterisk/framehook.h"
+
+/*! \brief REFER Progress structure */
+struct refer_progress {
+	/*! \brief Subscription to provide updates on */
+	pjsip_evsub *sub;
+	/*! \brief Dialog for subscription */
+	pjsip_dialog *dlg;
+	/*! \brief Received packet, used to construct final response in case no subscription exists */
+	pjsip_rx_data *rdata;
+	/*! \brief Frame hook for monitoring REFER progress */
+	int framehook;
+	/*! \brief Last received subclass in frame hook */
+	int subclass;
+	/*! \brief Serializer for notifications */
+	struct ast_taskprocessor *serializer;
+};
+
+/*! \brief REFER Progress notification structure */
+struct refer_progress_notification {
+	/*! \brief Refer progress structure to send notification on */
+	struct refer_progress *progress;
+	/*! \brief SIP response code to send */
+	int response;
+	/*! \brief Subscription state */
+	pjsip_evsub_state state;
+};
+
+/*! \brief REFER Progress module, used to attach REFER progress structure to subscriptions */
+static pjsip_module refer_progress_module = {
+	.name = { "REFER Progress", 14 },
+	.id = -1,
+};
+
+/*! \brief Destructor for REFER Progress notification structure */
+static void refer_progress_notification_destroy(void *obj)
+{
+	struct refer_progress_notification *notification = obj;
+
+	ao2_cleanup(notification->progress);
+}
+
+/*! \brief Allocator for REFER Progress notification structure */
+static struct refer_progress_notification *refer_progress_notification_alloc(struct refer_progress *progress, int response,
+	pjsip_evsub_state state)
+{
+	struct refer_progress_notification *notification = ao2_alloc(sizeof(*notification), refer_progress_notification_destroy);
+
+	if (!notification) {
+		return NULL;
+	}
+
+	ao2_ref(progress, +1);
+	notification->progress = progress;
+	notification->response = response;
+	notification->state = state;
+
+	return notification;
+}
+
+/*! \brief Serialized callback for subscription notification */
+static int refer_progress_notify(void *data)
+{
+	RAII_VAR(struct refer_progress_notification *, notification, data, ao2_cleanup);
+	pjsip_evsub *sub;
+	pjsip_tx_data *tdata;
+
+	/* If the subscription has already been terminated we can't send a notification */
+	if (!(sub = notification->progress->sub)) {
+		return 0;
+	}
+
+	/* If the subscription is being terminated we want to actually remove the progress structure here to
+	 * stop a deadlock from occurring - basically terminated changes the state which queues a synchronous task
+	 * but we are already running a task... thus it would deadlock */
+	if (notification->state == PJSIP_EVSUB_STATE_TERMINATED) {
+		pjsip_dlg_inc_lock(notification->progress->dlg);
+		pjsip_evsub_set_mod_data(notification->progress->sub, refer_progress_module.id, NULL);
+		pjsip_dlg_dec_lock(notification->progress->dlg);
+
+		/* This is for dropping the reference on the subscription */
+		ao2_cleanup(notification->progress);
+
+		notification->progress->sub = NULL;
+	}
+
+	/* Actually send the notification */
+	if (pjsip_xfer_notify(sub, notification->state, notification->response, NULL, &tdata) == PJ_SUCCESS) {
+		pjsip_xfer_send_request(sub, tdata);
+	}
+
+	return 0;
+}
+
+/*! \brief Progress monitoring frame hook - examines frames to determine state of transfer */
+static struct ast_frame *refer_progress_framehook(struct ast_channel *chan, struct ast_frame *f, enum ast_framehook_event event, void *data)
+{
+	struct refer_progress *progress = data;
+	struct refer_progress_notification *notification = NULL;
+
+	/* We only care about frames *to* the channel */
+	if (!f || (event != AST_FRAMEHOOK_EVENT_WRITE)) {
+		return f;
+	}
+
+	/* Determine the state of the REFER based on the control frames (or voice frames) passing */
+	if (f->frametype == AST_FRAME_VOICE && !progress->subclass) {
+		/* Media is passing without progress, this means the call has been answered */
+		notification = refer_progress_notification_alloc(progress, 200, PJSIP_EVSUB_STATE_TERMINATED);
+	} else if (f->frametype == AST_FRAME_CONTROL) {
+		progress->subclass = f->subclass.integer;
+
+		/* Based on the control frame being written we can send a NOTIFY advising of the progress */
+		if ((f->subclass.integer == AST_CONTROL_RING) || (f->subclass.integer == AST_CONTROL_RINGING)) {
+			notification = refer_progress_notification_alloc(progress, 180, PJSIP_EVSUB_STATE_ACTIVE);
+		} else if (f->subclass.integer == AST_CONTROL_BUSY) {
+			notification = refer_progress_notification_alloc(progress, 486, PJSIP_EVSUB_STATE_TERMINATED);
+		} else if (f->subclass.integer == AST_CONTROL_CONGESTION) {
+			notification = refer_progress_notification_alloc(progress, 503, PJSIP_EVSUB_STATE_TERMINATED);
+		} else if (f->subclass.integer == AST_CONTROL_PROGRESS) {
+			notification = refer_progress_notification_alloc(progress, 183, PJSIP_EVSUB_STATE_ACTIVE);
+		} else if (f->subclass.integer == AST_CONTROL_PROCEEDING) {
+			notification = refer_progress_notification_alloc(progress, 100, PJSIP_EVSUB_STATE_ACTIVE);
+		}
+	}
+
+	/* If a notification is due to be sent push it to the thread pool */
+	if (notification) {
+		/* If the subscription is being terminated we don't need the frame hook any longer */
+		if (notification->state == PJSIP_EVSUB_STATE_TERMINATED) {
+			ast_framehook_detach(chan, progress->framehook);
+		}
+
+		if (ast_sip_push_task(progress->serializer, refer_progress_notify, notification)) {
+			ao2_cleanup(notification);
+		}
+	}
+
+	return f;
+}
+
+/*! \brief Destroy callback for monitoring framehook */
+static void refer_progress_framehook_destroy(void *data)
+{
+	ao2_cleanup(data);
+}
+
+/*! \brief Serialized callback for subscription termination */
+static int refer_progress_terminate(void *data)
+{
+	struct refer_progress *progress = data;
+
+	/* The subscription is no longer valid */
+	progress->sub = NULL;
+
+	return 0;
+}
+
+/*! \brief Callback for REFER subscription state changes */
+static void refer_progress_on_evsub_state(pjsip_evsub *sub, pjsip_event *event)
+{
+	struct refer_progress *progress = pjsip_evsub_get_mod_data(sub, refer_progress_module.id);
+
+	/* If being destroyed queue it up to the serializer */
+	if (progress && (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED)) {
+		/* To prevent a deadlock race condition we unlock the dialog so other serialized tasks can execute */
+		pjsip_dlg_dec_lock(progress->dlg);
+		ast_sip_push_task_synchronous(progress->serializer, refer_progress_terminate, progress);
+		pjsip_dlg_inc_lock(progress->dlg);
+
+		pjsip_evsub_set_mod_data(sub, refer_progress_module.id, NULL);
+		ao2_cleanup(progress);
+	}
+}
+
+/*! \brief Callback structure for subscription */
+static pjsip_evsub_user refer_progress_evsub_cb = {
+	.on_evsub_state = refer_progress_on_evsub_state,
+};
+
+/*! \brief Destructor for REFER progress sutrcture */
+static void refer_progress_destroy(void *obj)
+{
+	struct refer_progress *progress = obj;
+
+	ast_taskprocessor_unreference(progress->serializer);
+}
+
+/*! \brief Internal helper function which sets up a refer progress structure if needed */
+static struct refer_progress *refer_progress_alloc(struct ast_sip_session *session, pjsip_rx_data *rdata)
+{
+	struct refer_progress *progress;
+	const pj_str_t str_refer_sub = { "Refer-Sub", 9 };
+	pjsip_generic_string_hdr *refer_sub = NULL;
+	pjsip_tx_data *tdata;
+	pjsip_hdr hdr_list;
+
+	/* Grab the optional Refer-Sub header, it can be used to suppress the implicit subscription */
+	refer_sub = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_refer_sub, NULL);
+
+	if ((refer_sub && pj_strnicmp2(&refer_sub->hvalue, "true", 4)) ||
+		!(progress = ao2_alloc(sizeof(*progress), refer_progress_destroy))) {
+		return NULL;
+	}
+
+	progress->framehook = -1;
+
+	/* To prevent a potential deadlock we need the dialog so we can lock/unlock */
+	progress->dlg = session->inv_session->dlg;
+
+	if (!(progress->serializer = ast_sip_create_serializer())) {
+		return progress;
+	}
+
+	/* Create the implicit subscription for monitoring of this transfer */
+	if (pjsip_xfer_create_uas(session->inv_session->dlg, &refer_progress_evsub_cb, rdata, &progress->sub) != PJ_SUCCESS) {
+		return progress;
+	}
+
+	/* Associate the REFER progress structure with the subscription */
+	ao2_ref(progress, +1);
+	pjsip_evsub_set_mod_data(progress->sub, refer_progress_module.id, progress);
+
+	pj_list_init(&hdr_list);
+	if (refer_sub) {
+		const pj_str_t str_true = { "true", 4 };
+		pjsip_hdr *hdr = (pjsip_hdr*)pjsip_generic_string_hdr_create(session->inv_session->dlg->pool, &str_refer_sub, &str_true);
+
+		pj_list_push_back(&hdr_list, hdr);
+	}
+
+	/* Accept the REFER request */
+	pjsip_xfer_accept(progress->sub, rdata, 202, &hdr_list);
+
+	/* Send initial NOTIFY Request */
+	if (pjsip_xfer_notify(progress->sub, PJSIP_EVSUB_STATE_ACTIVE, 100, NULL, &tdata) == PJ_SUCCESS) {
+		pjsip_xfer_send_request(progress->sub, tdata);
+	}
+
+	return progress;
+}
+
+static int refer_incoming_attended_request(struct ast_sip_session *session, pjsip_rx_data *rdata, pjsip_param *replaces_param)
+{
+	return 0;
+}
+
+/*! \brief Structure for blind transfer callback details */
+struct refer_blind {
+	/*! \brief Optional progress structure */
+	struct refer_progress *progress;
+	/*! \brief REFER message */
+	pjsip_rx_data *rdata;
+};
+
+/*! \brief Blind transfer callback function */
+static void refer_blind_callback(struct ast_channel *chan, void *user_data)
+{
+	struct refer_blind *refer = user_data;
+
+	/* If progress monitoring is being done attach a frame hook so we can monitor it */
+	if (refer->progress) {
+		struct ast_framehook_interface hook = {
+			.version = AST_FRAMEHOOK_INTERFACE_VERSION,
+			.event_cb = refer_progress_framehook,
+			.destroy_cb = refer_progress_framehook_destroy,
+			.data = refer->progress,
+		};
+
+		/* We need to bump the reference count up on the progress structure since it is in the frame hook now */
+		ao2_ref(refer->progress, +1);
+
+		/* If we can't attach a frame hook for whatever reason send a notification of success immediately */
+		if ((refer->progress->framehook = ast_framehook_attach(chan, &hook)) < 0) {
+			struct refer_progress_notification *notification = refer_progress_notification_alloc(refer->progress, 200,
+				PJSIP_EVSUB_STATE_TERMINATED);
+
+			if (notification) {
+				refer_progress_notify(notification);
+			}
+
+			ao2_cleanup(refer->progress);
+		}
+	}
+
+	/* TODO: Place details from REFER message onto channel */
+}
+
+static int refer_incoming_blind_request(struct ast_sip_session *session, pjsip_rx_data *rdata, pjsip_sip_uri *target)
+{
+	const char *context = (session->channel ? pbx_builtin_getvar_helper(session->channel, "TRANSFER_CONTEXT") : "");
+	char exten[AST_MAX_EXTENSION];
+	RAII_VAR(struct refer_progress *, progress, NULL, ao2_cleanup);
+	struct refer_blind refer;
+	enum ast_transfer_result res;
+
+	/* If no explicit transfer context has been provided use their configured context */
+	if (ast_strlen_zero(context)) {
+		context = session->endpoint->context;
+	}
+
+	/* Using the user portion of the target URI see if it exists as a valid extension in their context */
+	ast_copy_pj_str(exten, &target->user, sizeof(exten));
+	if (!ast_exists_extension(NULL, context, exten, 1, NULL)) {
+		pjsip_dlg_respond(session->inv_session->dlg, rdata, 404, NULL, NULL, NULL);
+		return 0;
+	}
+
+	/* Set up REFER progress subscription if requested/possible */
+	progress = refer_progress_alloc(session, rdata);
+
+	/* If REFER progress monitoring *should* occur but an error occurred report it */
+	if (progress && !progress->sub) {
+		pjsip_dlg_respond(session->inv_session->dlg, rdata, 500, NULL, NULL, NULL);
+		return 0;
+	}
+
+	refer.progress = progress;
+	refer.rdata = rdata;
+	res = ast_bridge_transfer_blind(session->channel, exten, context, refer_blind_callback, &refer);
+
+	if (!progress) {
+		/* The transferer has requested no subscription, so send a final response immediately */
+		int response = (res != AST_BRIDGE_TRANSFER_SUCCESS ? 603 : 200);
+		pjsip_tx_data *tdata;
+		const pj_str_t str_refer_sub = { "Refer-Sub", 9 };
+		const pj_str_t str_false = { "false", 5 };
+		pjsip_hdr *hdr;
+
+		if (pjsip_dlg_create_response(session->inv_session->dlg, rdata, response, NULL, &tdata) != PJ_SUCCESS) {
+			pjsip_dlg_respond(session->inv_session->dlg, rdata, response, NULL, NULL, NULL);
+			return 0;
+		}
+
+		hdr = (pjsip_hdr*)pjsip_generic_string_hdr_create(tdata->pool, &str_refer_sub, &str_false);
+		pjsip_msg_add_hdr(tdata->msg, hdr);
+
+		pjsip_dlg_send_response(session->inv_session->dlg, pjsip_rdata_get_tsx(rdata), tdata);
+	} else if (res != AST_BRIDGE_TRANSFER_SUCCESS) {
+		/* Since this failed we can send a final NOTIFY now and terminate the subscription */
+		struct refer_progress_notification *notification = refer_progress_notification_alloc(progress, 603, PJSIP_EVSUB_STATE_TERMINATED);
+
+		if (notification) {
+			/* The refer_progress_notify function will call ao2_cleanup on this for us */
+			refer_progress_notify(notification);
+		}
+	}
+
+
+	return 0;
+}
+
+static int refer_incoming_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
+{
+	const pj_str_t str_refer_to = { "Refer-To", 8 };
+	pjsip_generic_string_hdr *refer_to;
+	char *uri;
+	const pj_str_t str_to = { "To", 2 };
+	pjsip_fromto_hdr *target;
+	pjsip_sip_uri *target_uri;
+	const pj_str_t str_replaces = { "Replaces", 8 };
+	pjsip_param *replaces;
+
+	/* A Refer-To header is required */
+	if (!(refer_to = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_refer_to, NULL))) {
+		pjsip_dlg_respond(session->inv_session->dlg, rdata, 400, NULL, NULL, NULL);
+		return 0;
+	}
+	uri = refer_to->hvalue.ptr;
+	uri[refer_to->hvalue.slen] = '\0';
+
+	/* Parse the provided URI string as a To header so we can get the target */
+	if (!(target = pjsip_parse_hdr(rdata->tp_info.pool, &str_to, refer_to->hvalue.ptr, refer_to->hvalue.slen, NULL)) ||
+		(!PJSIP_URI_SCHEME_IS_SIP(target->uri) && !PJSIP_URI_SCHEME_IS_SIPS(target->uri))) {
+		pjsip_dlg_respond(session->inv_session->dlg, rdata, 400, NULL, NULL, NULL);
+		return 0;
+	}
+	target_uri = pjsip_uri_get_uri(target->uri);
+
+	/* Determine if this is an attended or blind transfer */
+	if ((replaces = pjsip_param_find(&target_uri->header_param, &str_replaces))) {
+		return refer_incoming_attended_request(session, rdata, replaces);
+	} else {
+		return refer_incoming_blind_request(session, rdata, target_uri);
+	}
+}
+
+static struct ast_sip_session_supplement refer_supplement = {
+	.method = "REFER",
+	.incoming_request = refer_incoming_request,
+};
+
+static int load_module(void)
+{
+	const pj_str_t str_norefersub = { "norefersub", 10 };
+
+	pjsip_replaces_init_module(ast_sip_get_pjsip_endpoint());
+	pjsip_xfer_init_module(ast_sip_get_pjsip_endpoint());
+	pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), NULL, PJSIP_H_SUPPORTED, NULL, 1, &str_norefersub);
+
+	ast_sip_register_service(&refer_progress_module);
+	ast_sip_session_register_supplement(&refer_supplement);
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+	ast_sip_session_unregister_supplement(&refer_supplement);
+	ast_sip_unregister_service(&refer_progress_module);
+
+	return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP Blind and Attended Transfer Support",
+		.load = load_module,
+		.unload = unload_module,
+		.load_pri = AST_MODPRI_APP_DEPEND,
+		   );

Propchange: team/file/gulp_transfer/res/res_sip_refer.c
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: team/file/gulp_transfer/res/res_sip_refer.c
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: team/file/gulp_transfer/res/res_sip_refer.c
------------------------------------------------------------------------------
    svn:mime-type = text/plain




More information about the asterisk-commits mailing list