[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