aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGES1
-rw-r--r--funcs/func_frame_trace.c365
-rw-r--r--include/asterisk/channel.h2
-rw-r--r--include/asterisk/framehook.h311
-rw-r--r--main/channel.c61
-rw-r--r--main/framehook.c184
6 files changed, 919 insertions, 5 deletions
diff --git a/CHANGES b/CHANGES
index 60f297817..ca5dd223f 100644
--- a/CHANGES
+++ b/CHANGES
@@ -250,6 +250,7 @@ Dialplan Functions
allows for high resolution times for billsec and duration fields.
* FILE() now supports line-mode and writing.
* Added FIELDNUM(), which returns the 1-based offset of a field in a list.
+ * FRAME_TRACE(), for tracking internal ast_frames on a channel.
Dialplan Variables
------------------
diff --git a/funcs/func_frame_trace.c b/funcs/func_frame_trace.c
new file mode 100644
index 000000000..c4c8d3978
--- /dev/null
+++ b/funcs/func_frame_trace.c
@@ -0,0 +1,365 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2010, Digium, Inc.
+ *
+ * David Vossel <dvossel@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.
+ */
+
+/*! \file
+ *
+ * \brief Trace internal ast_frames on a channel.
+ *
+ * \author David Vossel <dvossel@digium.com>
+ *
+ * \ingroup functions
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/module.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/framehook.h"
+
+/*** DOCUMENTATION
+ <function name="FRAME_TRACE" language="en_US">
+ <synopsis>
+ View internal ast_frames as they are read and written on a channel.
+ </synopsis>
+ <syntax>
+ <parameter name="filter list type" required="true">
+ <para>A filter can be applied to the trace to limit what frames are viewed. This
+ filter can either be a <literal>white</literal> or <literal>black</literal> list
+ of frame types. When no filter type is present, <literal>white</literal> is
+ used. If no arguments are provided at all, all frames will be output.
+ </para>
+
+ <para>Below are the different types of frames that can be filtered.</para>
+ <enumlist>
+ <enum name = "DTMF_BEGIN" />
+ <enum name = "DTMF_END" />
+ <enum name = "VOICE" />
+ <enum name = "VIDEO" />
+ <enum name = "CONTROL" />
+ <enum name = "NULL" />
+ <enum name = "IAX" />
+ <enum name = "TEXT" />
+ <enum name = "IMAGE" />
+ <enum name = "HTML" />
+ <enum name = "CNG" />
+ <enum name = "MODEM" />
+ </enumlist>
+ </parameter>
+ </syntax>
+ <description>
+ <para>Examples:</para>
+ <para>exten => 1,1,Set(FRAME_TRACE(white)=DTMF_BEGIN,DTMF_END); view only DTMF frames. </para>
+ <para>exten => 1,1,Set(FRAME_TRACE()=DTMF_BEGIN,DTMF_END); view only DTMF frames. </para>
+ <para>exten => 1,1,Set(FRAME_TRACE(black)=DTMF_BEGIN,DTMF_END); view everything except DTMF frames. </para>
+ </description>
+ </function>
+ ***/
+
+static void print_frame(struct ast_frame *frame);
+static struct {
+ enum ast_frame_type type;
+ const char *str;
+} frametype2str[] = {
+ { AST_FRAME_DTMF_BEGIN, "DTMF_BEGIN" },
+ { AST_FRAME_DTMF_END, "DTMF_END" },
+ { AST_FRAME_VOICE, "VOICE" },
+ { AST_FRAME_VIDEO, "VIDEO" },
+ { AST_FRAME_CONTROL, "CONTROL" },
+ { AST_FRAME_NULL, "NULL" },
+ { AST_FRAME_IAX, "IAX" },
+ { AST_FRAME_TEXT, "TEXT" },
+ { AST_FRAME_IMAGE, "IMAGE" },
+ { AST_FRAME_HTML, "HTML" },
+ { AST_FRAME_CNG, "CNG" },
+ { AST_FRAME_MODEM, "MODEM" },
+};
+
+struct frame_trace_data {
+ int list_type; /* 0 = white, 1 = black */
+ int values[ARRAY_LEN(frametype2str)];
+};
+
+static void datastore_destroy_cb(void *data) {
+ ast_free(data);
+}
+
+static const struct ast_datastore_info frame_trace_datastore = {
+ .type = "frametrace",
+ .destroy = datastore_destroy_cb
+};
+
+static void hook_destroy_cb(void *framedata)
+{
+ ast_free(framedata);
+}
+
+static struct ast_frame *hook_event_cb(struct ast_channel *chan, struct ast_frame *frame, enum ast_framehook_event event, void *data)
+{
+ int i;
+ int show_frame = 0;
+ struct frame_trace_data *framedata = data;
+ if (!frame) {
+ return frame;
+ }
+
+ if ((event != AST_FRAMEHOOK_EVENT_WRITE) && (event != AST_FRAMEHOOK_EVENT_READ)) {
+ return frame;
+ }
+
+ for (i = 0; i < ARRAY_LEN(frametype2str); i++) {
+ if (frame->frametype == frametype2str[i].type) {
+ if ((framedata->list_type == 0) && (framedata->values[i])) { /* white list */
+ show_frame = 1;
+ } else if ((framedata->list_type == 1) && (!framedata->values[i])){ /* black list */
+ show_frame = 1;
+ }
+ break;
+ }
+ }
+
+ if (show_frame) {
+ ast_verbose("%s on Channel %s\n", event == AST_FRAMEHOOK_EVENT_READ ? "<--Read" : "--> Write", chan->name);
+ print_frame(frame);
+ }
+ return frame;
+}
+
+static int frame_trace_helper(struct ast_channel *chan, const char *cmd, char *data, const char *value)
+{
+ struct frame_trace_data *framedata;
+ struct ast_datastore *datastore = NULL;
+ struct ast_framehook_interface interface = {
+ .version = AST_FRAMEHOOK_INTERFACE_VERSION,
+ .event_cb = hook_event_cb,
+ .destroy_cb = hook_destroy_cb,
+ };
+ int i = 0;
+
+ if (!(framedata = ast_calloc(1, sizeof(*framedata)))) {
+ return 0;
+ }
+
+ interface.data = framedata;
+
+ if (!strcasecmp(data, "black")) {
+ framedata->list_type = 1;
+ }
+ for (i = 0; i < ARRAY_LEN(frametype2str); i++) {
+ if (strcasestr(value, frametype2str[i].str)) {
+ framedata->values[i] = 1;
+ }
+ }
+
+ ast_channel_lock(chan);
+ i = ast_framehook_attach(chan, &interface);
+ if (i >= 0) {
+ int *id;
+ if ((datastore = ast_channel_datastore_find(chan, &frame_trace_datastore, NULL))) {
+ id = datastore->data;
+ ast_framehook_detach(chan, *id);
+ ast_channel_datastore_remove(chan, datastore);
+ }
+
+ if (!(datastore = ast_datastore_alloc(&frame_trace_datastore, NULL))) {
+ ast_framehook_detach(chan, i);
+ ast_channel_unlock(chan);
+ return 0;
+ }
+
+ if (!(id = ast_calloc(1, sizeof(int)))) {
+ ast_datastore_free(datastore);
+ ast_framehook_detach(chan, i);
+ ast_channel_unlock(chan);
+ return 0;
+ }
+
+ *id = i; /* Store off the id. The channel is still locked so it is safe to access this ptr. */
+ datastore->data = id;
+ ast_channel_datastore_add(chan, datastore);
+ }
+ ast_channel_unlock(chan);
+
+ return 0;
+}
+
+static void print_frame(struct ast_frame *frame)
+{
+ switch (frame->frametype) {
+ case AST_FRAME_DTMF_END:
+ ast_verbose("FrameType: DTMF END\n");
+ ast_verbose("Digit: %d\n", frame->subclass.integer);
+ break;
+ case AST_FRAME_VOICE:
+ ast_verbose("FrameType: VOICE\n");
+ ast_verbose("Codec: %s\n", ast_getformatname(frame->subclass.codec));
+ ast_verbose("MS: %ld\n", frame->len);
+ ast_verbose("Samples: %d\n", frame->samples);
+ ast_verbose("Bytes: %d\n", frame->datalen);
+ break;
+ case AST_FRAME_VIDEO:
+ ast_verbose("FrameType: VIDEO\n");
+ ast_verbose("Codec: %s\n", ast_getformatname(frame->subclass.codec));
+ ast_verbose("MS: %ld\n", frame->len);
+ ast_verbose("Samples: %d\n", frame->samples);
+ ast_verbose("Bytes: %d\n", frame->datalen);
+ break;
+ case AST_FRAME_CONTROL:
+ ast_verbose("FrameType: CONTROL\n");
+ switch (frame->subclass.integer) {
+ case AST_CONTROL_HANGUP:
+ ast_verbose("SubClass: HANGUP\n");
+ break;
+ case AST_CONTROL_RING:
+ ast_verbose("SubClass: RING\n");
+ break;
+ case AST_CONTROL_RINGING:
+ ast_verbose("SubClass: RINGING\n");
+ break;
+ case AST_CONTROL_ANSWER:
+ ast_verbose("SubClass: ANSWER\n");
+ break;
+ case AST_CONTROL_BUSY:
+ ast_verbose("SubClass: BUSY\n");
+ break;
+ case AST_CONTROL_TAKEOFFHOOK:
+ ast_verbose("SubClass: TAKEOFFHOOK\n");
+ break;
+ case AST_CONTROL_OFFHOOK:
+ ast_verbose("SubClass: OFFHOOK\n");
+ break;
+ case AST_CONTROL_CONGESTION:
+ ast_verbose("SubClass: CONGESTION\n");
+ break;
+ case AST_CONTROL_FLASH:
+ ast_verbose("SubClass: FLASH\n");
+ break;
+ case AST_CONTROL_WINK:
+ ast_verbose("SubClass: WINK\n");
+ break;
+ case AST_CONTROL_OPTION:
+ ast_verbose("SubClass: OPTION\n");
+ break;
+ case AST_CONTROL_RADIO_KEY:
+ ast_verbose("SubClass: RADIO KEY\n");
+ break;
+ case AST_CONTROL_RADIO_UNKEY:
+ ast_verbose("SubClass: RADIO UNKEY\n");
+ break;
+ case AST_CONTROL_PROGRESS:
+ ast_verbose("SubClass: PROGRESS\n");
+ break;
+ case AST_CONTROL_PROCEEDING:
+ ast_verbose("SubClass: PROCEEDING\n");
+ break;
+ case AST_CONTROL_HOLD:
+ ast_verbose("SubClass: HOLD\n");
+ break;
+ case AST_CONTROL_UNHOLD:
+ ast_verbose("SubClass: UNHOLD\n");
+ break;
+ case AST_CONTROL_VIDUPDATE:
+ ast_verbose("SubClass: VIDUPDATE\n");
+ break;
+ case _XXX_AST_CONTROL_T38:
+ ast_verbose("SubClass: XXX T38\n");
+ break;
+ case AST_CONTROL_SRCUPDATE:
+ ast_verbose("SubClass: SRCUPDATE\n");
+ break;
+ case AST_CONTROL_TRANSFER:
+ ast_verbose("SubClass: TRANSFER\n");
+ break;
+ case AST_CONTROL_CONNECTED_LINE:
+ ast_verbose("SubClass: CONNECTED LINE\n");
+ break;
+ case AST_CONTROL_REDIRECTING:
+ ast_verbose("SubClass: REDIRECTING\n");
+ break;
+ case AST_CONTROL_T38_PARAMETERS:
+ ast_verbose("SubClass: T38 PARAMETERS\n");
+ break;
+ case AST_CONTROL_CC:
+ ast_verbose("SubClass: CC\n");
+ break;
+ case AST_CONTROL_SRCCHANGE:
+ ast_verbose("SubClass: SRCCHANGE\n");
+ break;
+ case AST_CONTROL_READ_ACTION:
+ ast_verbose("SubClass: READ ACTION\n");
+ break;
+ case AST_CONTROL_AOC:
+ ast_verbose("SubClass: AOC\n");
+ break;
+ }
+ if (frame->subclass.integer == -1) {
+ ast_verbose("SubClass: %d\n", frame->subclass.integer);
+ }
+ ast_verbose("Bytes: %d\n", frame->datalen);
+ break;
+ case AST_FRAME_NULL:
+ ast_verbose("FrameType: NULL\n");
+ break;
+ case AST_FRAME_IAX:
+ ast_verbose("FrameType: IAX\n");
+ break;
+ case AST_FRAME_TEXT:
+ ast_verbose("FrameType: TXT\n");
+ break;
+ case AST_FRAME_IMAGE:
+ ast_verbose("FrameType: IMAGE\n");
+ break;
+ case AST_FRAME_HTML:
+ ast_verbose("FrameType: HTML\n");
+ break;
+ case AST_FRAME_CNG:
+ ast_verbose("FrameType: CNG\n");
+ break;
+ case AST_FRAME_MODEM:
+ ast_verbose("FrameType: MODEM\n");
+ break;
+ case AST_FRAME_DTMF_BEGIN:
+ ast_verbose("FrameType: DTMF BEGIN\n");
+ ast_verbose("Digit: %d\n", frame->subclass.integer);
+ break;
+ }
+
+ ast_verbose("Src: %s\n", ast_strlen_zero(frame->src) ? "NOT PRESENT" : frame->src);
+ ast_verbose("\n");
+}
+
+static struct ast_custom_function frame_trace_function = {
+ .name = "FRAME_TRACE",
+ .write = frame_trace_helper,
+};
+
+static int unload_module(void)
+{
+ return ast_custom_function_unregister(&frame_trace_function);
+}
+
+static int load_module(void)
+{
+ int res = ast_custom_function_register(&frame_trace_function);
+ return res ? AST_MODULE_LOAD_DECLINE : AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Frame Trace for internal ast_frame debugging.");
+
diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h
index 8b8094e37..ed842b6d7 100644
--- a/include/asterisk/channel.h
+++ b/include/asterisk/channel.h
@@ -151,6 +151,7 @@ extern "C" {
#include "asterisk/data.h"
#include "asterisk/channelstate.h"
#include "asterisk/ccss.h"
+#include "asterisk/framehook.h"
#define DATASTORE_INHERIT_FOREVER INT_MAX
@@ -752,6 +753,7 @@ struct ast_channel {
struct ast_trans_pvt *writetrans; /*!< Write translation path */
struct ast_trans_pvt *readtrans; /*!< Read translation path */
struct ast_audiohook_list *audiohooks;
+ struct ast_framehook_list *framehooks;
struct ast_cdr *cdr; /*!< Call Detail Record */
struct ast_tone_zone *zone; /*!< Tone zone as set in indications.conf or
* in the CHANNEL dialplan function */
diff --git a/include/asterisk/framehook.h b/include/asterisk/framehook.h
new file mode 100644
index 000000000..0b2e6cd95
--- /dev/null
+++ b/include/asterisk/framehook.h
@@ -0,0 +1,311 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2010, Digium, Inc.
+ *
+ * David Vossel <dvossel@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.
+ */
+
+/*! \file
+ * \brief FrameHook Architecture
+ */
+
+/*!
+
+\page AstFrameHookAPI Asterisk FrameHook API
+
+\section FrameHookFunctionality How FrameHooks Work
+ FrameHooks work by intercepting all frames being written and read off
+ a channel and allowing those frames to be viewed and manipulated within a
+ call back function. Frame interception occurs before any processing is
+ done on the frame, which means this hook can be used to transparently
+ manipulate a frame before it is read from the channel or written
+ to the tech_pvt. This API can be thought of as a layer between the
+ channel API and the Asterisk core when going in the READ direction, and
+ as a layer between the Channel API and the tech_pvt when going in the
+ WRITE direction.
+
+\section FrameHookAPIUsage How to Use an FrameHook
+ Attaching and detaching an FrameHook to a channel is very simple. There are only
+ two functions involved, ast_framehook_attach() which will return an id representing
+ the new FrameHook on the channel, and ast_framehook_detach() which signals the
+ FrameHook for detachment and destruction. Below is detailed information each of these
+ functions and their usage.
+
+\code
+ struct ast_framehook_interface interface = {
+ .version = AST_FRAMEHOOK_INTERFACE_VERSION,
+ .event_cb = hook_event_cb,
+ .destroy_cb = hook_destroy_cb,
+ .data = data, // where the data ptr points to any custom data used later by the hook cb.
+ };
+ int id = ast_framehook_attach(channel, &interface);
+\endcode
+
+ The ast_framehook_attach() function creates and attaches a new FrameHook onto
+ a channel. Once attached to the channel, the FrameHook will call the event_callback
+ function each time a frame is written or read on the channel. A custom data
+ pointer can be provided to this function to store on the FrameHook as well. This
+ pointer can be used to keep up with any statefull information associated with the FrameHook
+ and is provided during the event_callback function. The destroy_callback function is optional.
+ This function exists so any custom data stored on the FrameHook can be destroyed before
+ the Framehook if destroyed.
+
+\code
+ ast_framehook_detach(channel, id);
+\endcode
+
+ The ast_framehook_detach() function signals the FrameHook represented by an id to
+ be detached and destroyed on a channel. Since it is possible this function may be called
+ during the FrameHook's event callback, it is impossible to synchronously detach the
+ FrameHook from the channel during this function call. It is guaranteed that the next
+ event proceeding the ast_framehook_detach() will be of type AST_FRAMEHOOK_EVENT_DETACH,
+ and that after that event occurs no other event will ever be issued for that FrameHook.
+ Once the FrameHook is destroyed, the destroy callback function will be called if it was
+ provided. Note that if this function is never called, the FrameHook will be detached
+ on channel destruction.
+
+\section FrameHookAPICodeExample FrameHook Example Code
+ The example code below attaches an FrameHook on a channel, and then detachs it when
+ the first ast_frame is read or written to the event callback function. The Framehook's id
+ is stored on the FrameHook's data pointer so it can be detached within the callback.
+
+\code
+ static void destroy_cb(void *data) {
+ ast_free(data);
+ }
+
+ static struct ast_frame *event_cb(struct ast_channel *chan,
+ struct ast_frame *frame,
+ enum ast_framehook_event event,
+ void *data) {
+
+ int *id = data;
+
+ if (!frame) {
+ return frame;
+ }
+
+ if (event == AST_FRAMEHOOK_EVENT_WRITE) {
+ ast_log(LOG_NOTICE, "YAY we received a frame in the write direction, Type: %d\n", frame->frametype)
+ ast_framehook_detach(chan, id); // the channel is guaranteed to be locked during this function call.
+ } else if (event == AST_FRAMEHOOK_EVENT_READ) {
+ ast_log(LOG_NOTICE, "YAY we received a frame in the read direction: Type: %d\n", frame->frametype);
+ ast_framehook_detach(chan, id); // the channel is guaranteed to be locked during this function call.
+ }
+
+ return frame;
+ {
+
+ int some_function()
+ {
+ struct ast_framehook_interface interface = {
+ .version = AST_FRAMEHOOK_INTERFACE_VERSION,
+ .event_cb = hook_event_cb,
+ .destroy_cb = hook_destroy_cb,
+ };
+ int *id = ast_calloc(1, sizeof(int));
+
+ if (!id) {
+ return -1;
+ }
+
+ interface.data = id; // This data will be returned to us in the callbacks.
+
+ ast_channel_lock(chan);
+ *id = ast_framehook_attach(chan, &interface);
+ ast_channel_unlock(chan);
+
+ if (*id < 0) {
+ // framehook attach failed, free data
+ ast_free(id);
+ return -1;
+ }
+ return 0;
+ }
+\endcode
+*/
+
+#ifndef _AST_FRAMEHOOK_H_
+#define _AST_FRAMEHOOK_H_
+#include "asterisk/linkedlists.h"
+#include "asterisk/frame.h"
+
+struct ast_framehook;
+struct ast_framehook_list;
+
+/*!
+ * \brief These are the types of events that the framehook's event callback can receive
+ * \since 1.8
+ */
+enum ast_framehook_event {
+ AST_FRAMEHOOK_EVENT_READ, /*!< frame is intercepted in the read direction on the channel. */
+ AST_FRAMEHOOK_EVENT_WRITE, /*!< frame is intercepted on the write direction on the channel. */
+ AST_FRAMEHOOK_EVENT_ATTACHED, /*!< framehook is attached and running on the channel, the first message sent to event_cb. */
+ AST_FRAMEHOOK_EVENT_DETACHED /*!< framehook is detached from the channel, last message sent to event_cb. */
+};
+
+/*!
+ * \brief This callback is called every time an event occurs on the framehook.
+ * \since 1.8
+ *
+ * \details Two events are guaranteed to occur once the ast_framehook_attach()
+ * function is called. These events are AST_FRAMEHOOK_EVENT_ATTACHED, which occurs
+ * immediately after the framehook is attached to a channel, and
+ * AST_FRAMEHOOK_EVENT_DETACHED, which occurs right after the framehook is
+ * detached.
+ *
+ * It is completely valid for the frame variable to be set to NULL. Always do a NULL
+ * check on the frame before attempted to access it. When the frame variable is present,
+ * it is safe to view and manipulate that frame in any way possible. It is even safe
+ * to return a completely different frame, but when that occurs this function is in
+ * charge of freeing the previous frame.
+ *
+ * The ast_channel will always be locked during this callback. Never attempt to unlock the
+ * channel for any reason.
+ *
+ * \param channel, The ast_channel this framehook is attached to
+ * \param frame, The ast_frame being intercepted for viewing and manipulation
+ * \param event, The type of event which is occurring
+ * \param data, The data pointer provided at framehook initilization.
+ *
+ * \retval the resulting frame.
+ */
+typedef struct ast_frame *(*ast_framehook_event_callback)(
+ struct ast_channel *chan,
+ struct ast_frame *frame,
+ enum ast_framehook_event event,
+ void *data);
+
+/*!
+ * \brief This callback is called immediately before the framehook is destroyed.
+ * \since 1.8
+ * \note This function should be used to clean up any pointers pointing to the
+ * framehook structure as the framehook will be freed immediately afterwards.
+ *
+ * \param data, The data pointer provided at framehook initialization. This
+ * is a good place to clean up any state data allocated for the framehook stored in this
+ * pointer.
+ */
+typedef void (*ast_framehook_destroy_callback)(void *data);
+
+#define AST_FRAMEHOOK_INTERFACE_VERSION 1
+/*! This interface is required for attaching a framehook to a channel. */
+struct ast_framehook_interface {
+ /*! framehook interface version number */
+ uint16_t version;
+ /*! event_cb represents the function that will be called everytime an event occurs on the framehook. */
+ ast_framehook_event_callback event_cb;
+ /*! destroy_cb is optional. This function is called immediately before the framehook
+ * is destroyed to allow for stored_data cleanup. */
+ ast_framehook_destroy_callback destroy_cb;
+ /*! This pointer can represent any custom data to be stored on the !framehook. This
+ * data pointer will be provided during each event callback which allows the framehook
+ * to store any stateful data associated with the application using the hook. */
+ void *data;
+};
+
+/*!
+ * \brief Attach an framehook onto a channel for frame interception.
+ * \since 1.8
+ *
+ * \param ast_channel, The channel to attach the hook on to.
+ * \param framehook interface, The framehook's callback functions and stored data.
+*
+ * \pre XXX The Channel must be locked during this function all.
+ *
+ * \note The data pointer is never touched by the framehook API except to
+ * provide it during the event and destruction callbacks. It is entirely up to the
+ * application using this API to manage the memory associated with the data pointer.
+ *
+ * \retval On success, positive id representing this hook on the channel
+ * \retval On failure, -1
+ */
+int ast_framehook_attach(struct ast_channel *chan, struct ast_framehook_interface *i);
+
+/*!
+ * \brief Detach an framehook from a channel.
+ * \since 1.8
+ *
+ * \pre XXX The Channel must be locked during this function all.
+ * If this function is never called after attaching an framehook,
+ * the framehook will be detached and destroyed during channel
+ * destruction.
+ *
+ * \param The channel the framehook is attached to
+ * \param The framehook's id
+ *
+ * \retval 0 success
+ * \retval -1 framehook did not exist on the channel. This means the
+ * framehook either never existed on the channel, or was already detached.
+ */
+int ast_framehook_detach(struct ast_channel *chan, int framehook_id);
+
+/*!
+ * \brief This is used by the channel API to detach and destroy all
+ * framehooks on a channel during channel destruction.
+ * \since 1.8
+ *
+ * \pre XXX The Channel must be locked during this function all.
+ *
+ * \param channel containing the framehook list to destroy.
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_framehook_list_destroy(struct ast_channel *chan);
+
+/*!
+ * \brief This is used by the channel API push a frame read event to a channel's framehook list.
+ * \since 1.8
+ *
+ * \details After this function completes, the resulting frame that is returned could be anything,
+ * even NULL. There is nothing to keep up with after this function. If the frame is modified, the
+ * framehook callback is in charge of any memory management associated with that modification.
+ *
+ * \pre XXX The Channel must be locked during this function all.
+ *
+ * \param framehook list to push event to.
+ * \param frame being pushed to the framehook list.
+ *
+ * \return The resulting frame after being viewed and modified by the framehook callbacks.
+ */
+struct ast_frame *ast_framehook_list_read_event(struct ast_framehook_list *framehooks, struct ast_frame *frame);
+
+/*!
+ * \brief This is used by the channel API push a frame write event to a channel's framehook list.
+ * \since 1.8
+ *
+ * \details After this function completes, the resulting frame that is returned could be anything,
+ * even NULL. There is nothing to keep up with after this function. If the frame is modified, the
+ * framehook callback is in charge of any memory management associated with that modification.
+ *
+ * \pre XXX The Channel must be locked during this function all.
+ *
+ * \param framehook list to push event to.
+ * \param frame being pushed to the framehook list.
+ *
+ * \return The resulting frame after being viewed and modified by the framehook callbacks.
+ */
+struct ast_frame *ast_framehook_list_write_event(struct ast_framehook_list *framehooks, struct ast_frame *frame);
+
+/*!
+ * \brief Determine if an framehook list is empty or not
+ * \since 1.8
+ * \pre XXX The Channel must be locked during this function all.
+ *
+ * \param the framehook list
+ * \retval 0, not empty
+ * \retval 1, is empty
+ */
+int ast_framehook_list_is_empty(struct ast_framehook_list *framehooks);
+#endif /* _AST_FRAMEHOOK_H */
diff --git a/main/channel.c b/main/channel.c
index 6f068384b..9954d6613 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -62,6 +62,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/threadstorage.h"
#include "asterisk/slinfactory.h"
#include "asterisk/audiohook.h"
+#include "asterisk/framehook.h"
#include "asterisk/timing.h"
#include "asterisk/autochan.h"
#include "asterisk/stringfields.h"
@@ -2633,6 +2634,8 @@ int ast_hangup(struct ast_channel *chan)
chan->audiohooks = NULL;
}
+ ast_framehook_list_destroy(chan);
+
ast_autoservice_stop(chan);
if (chan->masq) {
@@ -3750,6 +3753,10 @@ static struct ast_frame *__ast_read(struct ast_channel *chan, int dropaudio)
*/
chan->fdno = -1;
+ /* Perform the framehook read event here. After the frame enters the framehook list
+ * there is no telling what will happen, <insert mad scientist laugh here>!!! */
+ f = ast_framehook_list_read_event(chan->framehooks, f);
+
if (f) {
struct ast_frame *readq_tail = AST_LIST_LAST(&chan->readq);
struct ast_control_read_action_payload *read_action_payload;
@@ -4143,14 +4150,42 @@ int ast_indicate_data(struct ast_channel *chan, int _condition,
enum ast_control_frame_type condition = _condition;
struct ast_tone_zone_sound *ts = NULL;
int res;
+ /* this frame is used by framehooks. if it is set, we must free it at the end of this function */
+ struct ast_frame *awesome_frame = NULL;
ast_channel_lock(chan);
/* Don't bother if the channel is about to go away, anyway. */
if (ast_test_flag(chan, AST_FLAG_ZOMBIE) || ast_check_hangup(chan)) {
ast_channel_unlock(chan);
- return -1;
+ res = -1;
+ goto indicate_cleanup;
+ }
+
+ if (!ast_framehook_list_is_empty(chan->framehooks)) {
+ /* Do framehooks now, do it, go, go now */
+ struct ast_frame frame = {
+ .frametype = AST_FRAME_CONTROL,
+ .subclass.integer = condition,
+ .data.ptr = (void *) data, /* this cast from const is only okay because we do the ast_frdup below */
+ .datalen = datalen
+ };
+
+ /* we have now committed to freeing this frame */
+ awesome_frame = ast_frdup(&frame);
+
+ /* who knows what we will get back! the anticipation is killing me. */
+ if (!(awesome_frame = ast_framehook_list_read_event(chan->framehooks, &frame))) {
+ ast_channel_unlock(chan);
+ res = 0;
+ goto indicate_cleanup;
+ }
+
+ condition = awesome_frame->subclass.integer;
+ data = awesome_frame->data.ptr;
+ datalen = awesome_frame->datalen;
}
+
switch (condition) {
case AST_CONTROL_CONNECTED_LINE:
{
@@ -4196,7 +4231,8 @@ int ast_indicate_data(struct ast_channel *chan, int _condition,
if (is_visible_indication(condition)) {
chan->visible_indication = condition;
}
- return 0;
+ res = 0;
+ goto indicate_cleanup;
}
/* The channel driver does not support this indication, let's fake
@@ -4208,14 +4244,16 @@ int ast_indicate_data(struct ast_channel *chan, int _condition,
if (_condition < 0) {
/* Stop any tones that are playing */
ast_playtones_stop(chan);
- return 0;
+ res = 0;
+ goto indicate_cleanup;
}
/* Handle conditions that we have tones for. */
switch (condition) {
case _XXX_AST_CONTROL_T38:
/* deprecated T.38 control frame */
- return -1;
+ res = -1;
+ goto indicate_cleanup;
case AST_CONTROL_T38_PARAMETERS:
/* there is no way to provide 'default' behavior for these
* control frames, so we need to return failure, but there
@@ -4224,7 +4262,7 @@ int ast_indicate_data(struct ast_channel *chan, int _condition,
* so just return right now. in addition, we want to return
* whatever value the channel driver returned, in case it
* has some meaning.*/
- return res;
+ goto indicate_cleanup;
case AST_CONTROL_RINGING:
ts = ast_get_indication_tone(chan->zone, "ring");
/* It is common practice for channel drivers to return -1 if trying
@@ -4286,6 +4324,11 @@ int ast_indicate_data(struct ast_channel *chan, int _condition,
ast_log(LOG_WARNING, "Unable to handle indication %d for '%s'\n", condition, chan->name);
}
+indicate_cleanup:
+ if (awesome_frame) {
+ ast_frfree(awesome_frame);
+ }
+
return res;
}
@@ -4572,6 +4615,14 @@ int ast_write(struct ast_channel *chan, struct ast_frame *fr)
res = 0; /* XXX explain, why 0 ? */
goto done;
}
+
+ /* Perform the framehook write event here. After the frame enters the framehook list
+ * there is no telling what will happen, how awesome is that!!! */
+ if (!(fr = ast_framehook_list_write_event(chan->framehooks, fr))) {
+ res = 0;
+ goto done;
+ }
+
if (chan->generatordata && (!fr->src || strcasecmp(fr->src, "ast_prod"))) {
if (ast_test_flag(chan, AST_FLAG_WRITE_INT)) {
ast_deactivate_generator(chan);
diff --git a/main/framehook.c b/main/framehook.c
new file mode 100644
index 000000000..2d5fd5a47
--- /dev/null
+++ b/main/framehook.c
@@ -0,0 +1,184 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2010, Digium, Inc.
+ *
+ * David Vossel <dvossel@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.
+ */
+
+/*! \file
+ *
+ * \brief FrameHooks Architecture
+ *
+ * \author David Vossel <dvossel@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/channel.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/framehook.h"
+#include "asterisk/frame.h"
+
+struct ast_framehook {
+ struct ast_framehook_interface i;
+ /*! This pointer to ast_channel the framehook is attached to. */
+ struct ast_channel *chan;
+ /*! the id representing this framehook on a channel */
+ unsigned int id;
+ /*! when set, this signals the read and write function to detach the hook */
+ int detach_and_destroy_me;
+ /*! list entry for ast_framehook_list object */
+ AST_LIST_ENTRY(ast_framehook) list;
+};
+
+struct ast_framehook_list {
+ unsigned int id_count;
+ AST_LIST_HEAD_NOLOCK(, ast_framehook) list;
+};
+
+static void framehook_detach_and_destroy(struct ast_framehook *framehook)
+{
+ struct ast_frame *frame;
+ frame = framehook->i.event_cb(framehook->chan, NULL, AST_FRAMEHOOK_EVENT_DETACHED, framehook->i.data);
+ /* never assume anything about this function. If you can return a frame during
+ * the detached event, then assume someone will. */
+ if (frame) {
+ ast_frfree(frame);
+ }
+ framehook->chan = NULL;
+
+ if (framehook->i.destroy_cb) {
+ framehook->i.destroy_cb(framehook->i.data);
+ }
+ ast_free(framehook);
+}
+
+static struct ast_frame *framehook_list_push_event(struct ast_framehook_list *framehooks, struct ast_frame *frame, enum ast_framehook_event event)
+{
+ struct ast_framehook *framehook;
+
+ if (!framehooks) {
+ return frame;
+ }
+
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&framehooks->list, framehook, list) {
+ if (framehook->detach_and_destroy_me) {
+ /* this guy is signaled for destruction */
+ AST_LIST_REMOVE_CURRENT(list);
+ framehook_detach_and_destroy(framehook);
+ } else {
+ frame = framehook->i.event_cb(framehook->chan, frame, event, framehook->i.data);
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+ return frame;
+}
+
+int ast_framehook_attach(struct ast_channel *chan, struct ast_framehook_interface *i)
+{
+ struct ast_framehook *framehook;
+ struct ast_frame *frame;
+ if (i->version != AST_FRAMEHOOK_INTERFACE_VERSION) {
+ ast_log(LOG_ERROR, "Version '%hu' of framehook interface not what we compiled against (%hu)\n",
+ i->version, AST_FRAMEHOOK_INTERFACE_VERSION);
+ return -1;
+ }
+ if (!i->event_cb || !(framehook = ast_calloc(1, sizeof(*framehook)))) {
+ return -1;
+ }
+ framehook->i = *i;
+ framehook->chan = chan;
+
+ /* create the framehook list if it didn't already exist */
+ if (!chan->framehooks && !(chan->framehooks = ast_calloc(1, sizeof(*chan->framehooks)))) {
+ ast_free(framehook);
+ return -1;
+ }
+
+ framehook->id = ++chan->framehooks->id_count;
+ AST_LIST_INSERT_TAIL(&chan->framehooks->list, framehook, list);
+
+ /* Tell the event callback we're live and rocking */
+ frame = framehook->i.event_cb(framehook->chan, NULL, AST_FRAMEHOOK_EVENT_ATTACHED, framehook->i.data);
+
+ /* Never assume anything about this function. If you can return a frame during
+ * the attached event, then assume someone will. */
+ if (frame) {
+ ast_frfree(frame);
+ }
+
+ return framehook->id;
+}
+
+int ast_framehook_detach(struct ast_channel *chan, int id)
+{
+ struct ast_framehook *framehook;
+ int res = -1;
+
+ if (!chan->framehooks) {
+ return res;
+ }
+
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&chan->framehooks->list, framehook, list) {
+ if (framehook->id == id) {
+ /* we mark for detachment rather than doing explicitly here because
+ * it needs to be safe for this function to be called within the
+ * event callback. If we allowed the hook to actually be destroyed
+ * immediately here, the event callback would crash on exit. */
+ framehook->detach_and_destroy_me = 1;
+ res = 0;
+ break;
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+
+ return res;
+}
+
+int ast_framehook_list_destroy(struct ast_channel *chan)
+{
+ struct ast_framehook *framehook;
+
+ if (!chan->framehooks) {
+ return 0;
+ }
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&chan->framehooks->list, framehook, list) {
+ AST_LIST_REMOVE_CURRENT(list);
+ framehook_detach_and_destroy(framehook);
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+ ast_free(chan->framehooks);
+ chan->framehooks = NULL;
+ return 0;
+}
+
+int ast_framehook_list_is_empty(struct ast_framehook_list *framehooks)
+{
+ if (!framehooks) {
+ return 1;
+ }
+ return AST_LIST_EMPTY(&framehooks->list) ? 1 : 0;
+}
+
+struct ast_frame *ast_framehook_list_write_event(struct ast_framehook_list *framehooks, struct ast_frame *frame)
+{
+ return framehook_list_push_event(framehooks, frame, AST_FRAMEHOOK_EVENT_WRITE);
+}
+
+struct ast_frame *ast_framehook_list_read_event(struct ast_framehook_list *framehooks, struct ast_frame *frame)
+{
+ return framehook_list_push_event(framehooks, frame, AST_FRAMEHOOK_EVENT_READ);
+}