Merged revisions 287647 via svnmerge from
authorDavid Vossel <dvossel@digium.com>
Mon, 20 Sep 2010 22:16:37 +0000 (22:16 +0000)
committerDavid Vossel <dvossel@digium.com>
Mon, 20 Sep 2010 22:16:37 +0000 (22:16 +0000)
https://origsvn.digium.com/svn/asterisk/branches/1.8

........
  r287647 | dvossel | 2010-09-20 17:09:16 -0500 (Mon, 20 Sep 2010) | 21 lines

  Addition of the FrameHook API (AKA AwesomeHooks)

  So far all our tools for viewing and manipulating media streams
  within Asterisk have been entirely focused on audio.  That made
  sense then, but is not scalable now.  The FrameHook API lets us
  tap into and manipulate _ANY_ type of media or signaling passed
  on a channel present today or in the future.  This tool is a step
  in the direction of expanding Asterisk's boundaries and will help
  generate some rather interesting applications in the future.

  In addition to the FrameHook API, a simple dialplan function
  exercising the api has been included as well.  This function
  is called FRAME_TRACE().  FRAME_TRACE() allows for the internal
  ast_frames read and written to a channel to be output.  Filters
  can be placed on this function to debug only certain types of frames.
  This function could be thought of as an internal way of doing
  ast_frame packet captures.

  Review: https://reviewboard.asterisk.org/r/925/
........

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@287648 65c4cc65-6c06-0410-ace0-fbb531ad65f3

CHANGES
funcs/func_frame_trace.c [new file with mode: 0644]
include/asterisk/channel.h
include/asterisk/framehook.h [new file with mode: 0644]
main/channel.c
main/framehook.c [new file with mode: 0644]

diff --git a/CHANGES b/CHANGES
index 60f2978..ca5dd22 100644 (file)
--- 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 (file)
index 0000000..c4c8d39
--- /dev/null
@@ -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.");
+
index 8b8094e..ed842b6 100644 (file)
@@ -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 (file)
index 0000000..0b2e6cd
--- /dev/null
@@ -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 */
index 6f06838..9954d66 100644 (file)
@@ -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 (file)
index 0000000..2d5fd5a
--- /dev/null
@@ -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);
+}