diff options
-rw-r--r-- | CHANGES | 1 | ||||
-rw-r--r-- | funcs/func_frame_trace.c | 365 | ||||
-rw-r--r-- | include/asterisk/channel.h | 2 | ||||
-rw-r--r-- | include/asterisk/framehook.h | 311 | ||||
-rw-r--r-- | main/channel.c | 61 | ||||
-rw-r--r-- | main/framehook.c | 184 |
6 files changed, 919 insertions, 5 deletions
@@ -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); +} |