[asterisk-commits] mjordan: branch mjordan/confbridgeactionexec r371177 - in /team/mjordan/confb...
SVN commits to the Asterisk project
asterisk-commits at lists.digium.com
Sun Aug 12 15:54:33 CDT 2012
Author: mjordan
Date: Sun Aug 12 15:54:27 2012
New Revision: 371177
URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=371177
Log:
Create branch for confbridgeactionexec
Added:
team/mjordan/confbridgeactionexec/
- copied from r371170, trunk/
team/mjordan/confbridgeactionexec/tests/test_bridging.c (with props)
Modified:
team/mjordan/confbridgeactionexec/CHANGES
team/mjordan/confbridgeactionexec/apps/app_confbridge.c
team/mjordan/confbridgeactionexec/apps/confbridge/conf_config_parser.c
team/mjordan/confbridgeactionexec/apps/confbridge/include/confbridge.h
team/mjordan/confbridgeactionexec/include/asterisk/bridging.h
team/mjordan/confbridgeactionexec/main/bridging.c
Modified: team/mjordan/confbridgeactionexec/CHANGES
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/confbridgeactionexec/CHANGES?view=diff&rev=371177&r1=371170&r2=371177
==============================================================================
--- team/mjordan/confbridgeactionexec/CHANGES (original)
+++ team/mjordan/confbridgeactionexec/CHANGES Sun Aug 12 15:54:27 2012
@@ -58,6 +58,7 @@
file also allows for the default sounds played to all conference users when
this occurs to be overriden using sound_participants_unmuted and
sound_participants_muted.
+ * Added AMI command ConfbridgeExecAction. See the AMI changes for more details.
* Added menu action participant_count. This will playback the number of
current participants in a conference.
@@ -633,7 +634,8 @@
CDR Adaptive ODBC Driver
-------------------
* Added schema option for databases that support specifying a schema.
-
+ * ConfbridgeExecAction executes any confbridge action on a specified channel
+ in a conference.
Resource Modules
-------------------
Modified: team/mjordan/confbridgeactionexec/apps/app_confbridge.c
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/confbridgeactionexec/apps/app_confbridge.c?view=diff&rev=371177&r1=371170&r2=371177
==============================================================================
--- team/mjordan/confbridgeactionexec/apps/app_confbridge.c (original)
+++ team/mjordan/confbridgeactionexec/apps/app_confbridge.c Sun Aug 12 15:54:27 2012
@@ -255,7 +255,31 @@
<description>
</description>
</manager>
-
+ <manager name="ConfbridgeExecAction" language="en_US">
+ <synopsis>
+ Execute a ConfBridge action on a particular channel.
+ </synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+ <parameter name="Conference" required="true">
+ <para>Conference Number.</para>
+ </parameter>
+ <parameter name="Channel" required="true">
+ <para>The channel to execute the actions on.</para>
+ </parameter>
+ <parameter name="Actions" required="true">
+ <para>A comma delineated list of ConfBridge actions to execute on the particular channel.</para>
+ </parameter>
+ </syntax>
+ <description>
+ <para>Executes a ConfBridge action on a specific channel. The available actions are defined in
+ <literal>confbridge.conf</literal>. The actions specified in the <literal>Actions</literal> parameter
+ are executed sequentially on the channel.</para>
+ <para>Executing ConfBridge actions through this mechanism does not lift any user profile
+ based restrictions. For example, an action requiring a profile with the 'admin' flag will not be
+ able to be executed on a user's channel whose profile does not have that flag.</para>
+ </description>
+ </manager>
***/
/*!
@@ -272,6 +296,18 @@
/* Number of buckets our conference bridges container can have */
#define CONFERENCE_BRIDGE_BUCKETS 53
+
+/*! \brief Object passed to the bridging layer during a deferred callback
+ */
+struct deferred_pvt {
+ /*! The user to perform an action for */
+ struct conference_bridge_user *conference_bridge_user;
+ /*! The menu entry defining the actions to execute */
+ struct conf_menu_entry menu_entry;
+ /*! The menu containing the menu entry */
+ struct conf_menu *menu;
+};
+
/*! \brief Container to hold all conference bridges in progress */
static struct ao2_container *conference_bridges;
@@ -2734,6 +2770,133 @@
return 0;
}
+/*! \internal \brief A callback function passed to the bridging layer that
+ * performs an action upon a channel in the conference
+ */
+static void deferred_action_callback(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *pvt_data)
+{
+ struct deferred_pvt *pvt = pvt_data;
+
+ /* Make sure the pvt has all the information required */
+ if (!pvt || !pvt->conference_bridge_user || !pvt->menu) {
+ return;
+ }
+ conf_handle_dtmf(bridge_channel, pvt->conference_bridge_user, &pvt->menu_entry, pvt->menu);
+}
+
+/*! \internal \brief Destructor for a deferred_pvt */
+static void deferred_action_private_destructor(void *pvt_data)
+{
+ struct deferred_pvt *pvt = pvt_data;
+
+ if (!pvt) {
+ return;
+ }
+
+ if (pvt->menu && pvt->menu->entries.first) {
+ conf_menu_entry_destroy(pvt->menu->entries.first);
+ }
+ ast_free(pvt->menu);
+ ast_free(pvt);
+}
+
+/*! \internal \brief Entry point for the ConfbridgeExecAction manager command */
+static int action_confbridgeexecaction(struct mansession *s, const struct message *m)
+{
+ struct conference_bridge_user *participant = NULL;
+ struct conference_bridge *conf_bridge = NULL;
+ struct conf_menu *menu = NULL;
+ struct deferred_pvt *pvt = NULL;
+ struct conference_bridge tmp;
+ const char *conference = astman_get_header(m, "Conference");
+ const char *channel = astman_get_header(m, "Channel");
+ const char *action = astman_get_header(m, "Actions");
+ int deferred_queued = 0;
+
+ if (ast_strlen_zero(conference)) {
+ astman_send_error(s, m, "No Conference name provided.");
+ return 0;
+ }
+ if (ast_strlen_zero(channel)) {
+ astman_send_error(s, m, "No channel name provided.");
+ return 0;
+ }
+ if (ast_strlen_zero(action)) {
+ astman_send_error(s, m, "No actions provided.");
+ return 0;
+ }
+ if (!ao2_container_count(conference_bridges)) {
+ astman_send_error(s, m, "No active conferences.");
+ return 0;
+ }
+
+ /* Build the dummy menu entry that will contain the actions */
+ if (!(menu = ast_calloc(1, sizeof(*menu)))) {
+ astman_send_error(s, m, "Internal error when building actions.");
+ goto action_exec_cleanup;
+ }
+ if (conf_add_menu_entry(menu, "0", action)) {
+ astman_send_error(s, m, "Cannot parse specified actions.");
+ goto action_exec_cleanup;
+ }
+ if (!(pvt = ast_calloc(1, sizeof(*pvt)))) {
+ astman_send_error(s, m, "Internal error when building actions.");
+ goto action_exec_cleanup;
+ }
+ pvt->menu = menu;
+ pvt->menu_entry = *(menu->entries.first);
+
+ /* Find the conference */
+ ast_copy_string(tmp.name, conference, sizeof(tmp.name));
+ conf_bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER);
+ if (!conf_bridge) {
+ astman_send_error(s, m, "No Conference by that name found.");
+ goto action_exec_cleanup;
+ }
+
+ /* Find the participant in the bridge and ask the bridge layer to execute
+ * the menu on that participant */
+ ao2_lock(conf_bridge);
+ AST_LIST_TRAVERSE(&conf_bridge->users_list, participant, list) {
+ if (!strncmp(channel, ast_channel_name(participant->chan), strlen(channel))) {
+ pvt->conference_bridge_user = participant;
+ if (ast_bridge_issue_deferred(conf_bridge->bridge,
+ participant->chan,
+ deferred_action_callback,
+ pvt,
+ deferred_action_private_destructor)) {
+ ao2_unlock(conf_bridge);
+ astman_send_error(s, m, "Internal error initiating action on participant.");
+ goto action_exec_cleanup;
+ }
+ deferred_queued = 1;
+ break;
+ }
+ }
+ ao2_unlock(conf_bridge);
+
+ if (!participant) {
+ astman_send_error(s, m, "No channel by that name found in Conference.");
+ goto action_exec_cleanup;
+ }
+
+ astman_send_ack(s, m, "Conference action successfully requested for participant.");
+
+action_exec_cleanup:
+ /* Destroy the pvt we created if we failed to queue it up for action */
+ if (!deferred_queued) {
+ if (menu && menu->entries.first) {
+ conf_menu_entry_destroy(menu->entries.first);
+ }
+ ast_free(menu);
+ ast_free(pvt);
+ }
+ if (conf_bridge) {
+ ao2_ref(conf_bridge, -1);
+ }
+ return 0;
+}
+
static int func_confbridge_info(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
{
char *parse = NULL;
@@ -2823,6 +2986,7 @@
res |= ast_manager_unregister("ConfbridgeLock");
res |= ast_manager_unregister("ConfbridgeStartRecord");
res |= ast_manager_unregister("ConfbridgeStopRecord");
+ res |= ast_manager_unregister("ConfbridgeExecAction");
return res;
}
@@ -2866,6 +3030,7 @@
res |= ast_manager_register_xml("ConfbridgeStartRecord", EVENT_FLAG_CALL, action_confbridgestartrecord);
res |= ast_manager_register_xml("ConfbridgeStopRecord", EVENT_FLAG_CALL, action_confbridgestoprecord);
res |= ast_manager_register_xml("ConfbridgeSetSingleVideoSrc", EVENT_FLAG_CALL, action_confbridgesetsinglevideosrc);
+ res |= ast_manager_register_xml("ConfbridgeExecAction", EVENT_FLAG_CALL, action_confbridgeexecaction);
if (res) {
return AST_MODULE_LOAD_FAILURE;
}
Modified: team/mjordan/confbridgeactionexec/apps/confbridge/conf_config_parser.c
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/confbridgeactionexec/apps/confbridge/conf_config_parser.c?view=diff&rev=371177&r1=371170&r2=371177
==============================================================================
--- team/mjordan/confbridgeactionexec/apps/confbridge/conf_config_parser.c (original)
+++ team/mjordan/confbridgeactionexec/apps/confbridge/conf_config_parser.c Sun Aug 12 15:54:27 2012
@@ -477,7 +477,7 @@
return 0;
}
-static int add_menu_entry(struct conf_menu *menu, const char *dtmf, const char *action_names)
+int conf_add_menu_entry(struct conf_menu *menu, const char *dtmf, const char *action_names)
{
struct conf_menu_entry *menu_entry = NULL, *cur = NULL;
int res = 0;
@@ -1249,7 +1249,7 @@
static int menu_option_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
{
- add_menu_entry(obj, var->name, var->value);
+ conf_add_menu_entry(obj, var->name, var->value);
return 0;
}
Modified: team/mjordan/confbridgeactionexec/apps/confbridge/include/confbridge.h
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/confbridgeactionexec/apps/confbridge/include/confbridge.h?view=diff&rev=371177&r1=371170&r2=371177
==============================================================================
--- team/mjordan/confbridgeactionexec/apps/confbridge/include/confbridge.h (original)
+++ team/mjordan/confbridgeactionexec/apps/confbridge/include/confbridge.h Sun Aug 12 15:54:27 2012
@@ -294,6 +294,21 @@
int conf_find_menu_entry_by_sequence(const char *dtmf_sequence, struct conf_menu *menu, struct conf_menu_entry *result);
/*!
+ * \brief Adds a sequence of actions to a DTMF key in a ConfBridge menu
+ *
+ * \param menu The menu to add an action to
+ * \param dtmf The DTMF key to associate with the menu actions
+ * \param action_names A comma delineated list of ConfBridge action names to execute
+ *
+ * \note If the menu can be accessed by a user when this method is called, the menu
+ * itself should be locked prior to calling this method.
+ *
+ * \retval 0 on success, actions were added to the menu under the associated DTMF key
+ * \retval -1 on error
+ */
+int conf_add_menu_entry(struct conf_menu *menu, const char *dtmf, const char *action_names);
+
+/*!
* \brief Destroys and frees all the actions stored in a menu_entry structure
*/
void conf_menu_entry_destroy(struct conf_menu_entry *menu_entry);
Modified: team/mjordan/confbridgeactionexec/include/asterisk/bridging.h
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/confbridgeactionexec/include/asterisk/bridging.h?view=diff&rev=371177&r1=371170&r2=371177
==============================================================================
--- team/mjordan/confbridgeactionexec/include/asterisk/bridging.h (original)
+++ team/mjordan/confbridgeactionexec/include/asterisk/bridging.h Sun Aug 12 15:54:27 2012
@@ -101,6 +101,8 @@
AST_BRIDGE_CHANNEL_STATE_START_TALKING,
/*! Bridged channel has stopped talking */
AST_BRIDGE_CHANNEL_STATE_STOP_TALKING,
+ /*! Bridged channel would like a deferred callback executed */
+ AST_BRIDGE_CHANNEL_STATE_EXECUTE_DEFERRED,
};
/*! \brief Return values for bridge technology write function */
@@ -132,6 +134,17 @@
unsigned int drop_silence:1;
};
+struct bridge_deferred_callback;
+
+/*!
+ * \brief Structure that holds states for bridge_channel transitions
+ */
+struct requested_bridge_state {
+ /*! The state to transition to */
+ enum ast_bridge_channel_state state;
+ AST_LIST_ENTRY(requested_bridge_state) entry;
+};
+
/*!
* \brief Structure that contains information regarding a channel in a bridge
*/
@@ -140,6 +153,8 @@
ast_mutex_t lock;
/*! Condition, used if we want to wake up a thread waiting on the bridged channel */
ast_cond_t cond;
+ /*! The next requested state to put the bridge in */
+ AST_LIST_HEAD_NOLOCK(,requested_bridge_state) requested_states;
/*! Current bridged channel state */
enum ast_bridge_channel_state state;
/*! Asterisk channel participating in the bridge */
@@ -160,6 +175,8 @@
unsigned int allow_impart_hangup:1;
/*! Features structure for features that are specific to this channel */
struct ast_bridge_features *features;
+ /*! A list that acts as a FIFO queue of deferred callbacks to execute */
+ AST_LIST_HEAD_NOLOCK(,bridge_deferred_callback) deferreds;
/*! Technology optimization parameters used by bridging technologies capable of
* optimizing based upon talk detection. */
struct ast_bridge_tech_optimizations tech_args;
@@ -251,6 +268,31 @@
AST_LIST_HEAD_NOLOCK(, ast_bridge_channel) channels;
};
+/*!
+ * \brief General purpose deferred callback
+ *
+ * \details This represents a general purpose callback function
+ * that will be safely executed in the context of a particular
+ * channel's thread.
+ *
+ * \param bridge The bridge that the channel is part of
+ * \param bridge_channel Channel whose thread the function is executed on
+ * \param pvt_data General data optionally passed to the callback function
+ */
+typedef void (*ast_bridge_deferred_callback)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *pvt);
+
+/*!
+ * \brief Dispose of any general purpose data passed to a deferred callback
+ *
+ * \details If the pvt_data object is provided, an optional destructor can be
+ * provided that will be called after the callback has completed. This allows
+ * for the memory allocated for the data to be disposed of.
+ *
+ * \param pvt_data General data that was optionally passed to the callback function
+ */
+typedef void (*ast_bridge_deferred_callback_destructor)(void *pvt_data);
+
+
/*! \brief Create a new bridge
*
* \param capabilities The capabilities that we require to be used on the bridge
@@ -557,6 +599,33 @@
*/
void ast_bridge_remove_video_src(struct ast_bridge *bridge, struct ast_channel *chan);
+/*!
+ * \brief Ask the bridging layer to execute a callback function on a particular channel's
+ * bridge channel thread
+ * \param bridge The bridge to issue the deferred callback on
+ * \param chan The channel in the bridge whose thread the callback will be executed on
+ * \param deferred_cb The function to call
+ * \param pvt_data Optional data to pass to the function
+ * \param pvt_destructor Destructor for the private data. Required if pvt_data is used.
+ *
+ * This method allows user's of the bridging API to request a callback function be executed
+ * in the context of a particular channel's bridge thread. This safely removes the channel
+ * from the bridge, allowing actions to be executed on the underlying ast_channel.
+ *
+ * \note Since the callback function will typically execute on a separate thread then the
+ * initiator of the request, there is no guarantee when the callback will be executed, or if
+ * it will be executed (for example, the channel can be hung up prior to execution of the
+ * deferred callback).
+ *
+ * \retval 0 if the callback was successfully queued for later execution
+ * \retval -1 on error
+ */
+int ast_bridge_issue_deferred(struct ast_bridge *bridge,
+ struct ast_channel *chan,
+ ast_bridge_deferred_callback deferred_cb,
+ void *pvt_data,
+ ast_bridge_deferred_callback_destructor pvt_destructor);
+
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif
Modified: team/mjordan/confbridgeactionexec/main/bridging.c
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/confbridgeactionexec/main/bridging.c?view=diff&rev=371177&r1=371170&r2=371177
==============================================================================
--- team/mjordan/confbridgeactionexec/main/bridging.c (original)
+++ team/mjordan/confbridgeactionexec/main/bridging.c Sun Aug 12 15:54:27 2012
@@ -55,6 +55,22 @@
/* Grow rate of bridge array of channels */
#define BRIDGE_ARRAY_GROW 32
+/*!
+ * \internal
+ * \brief An internally object used to hold information about deferred
+ * callbacks issued on a bridge channel
+ */
+struct bridge_deferred_callback {
+ /*! The callback function to execute on the bridge channel's thread */
+ ast_bridge_deferred_callback deferred_cb;
+ /*! Private data to pass to the deferred callback */
+ void *pvt_data;
+ /*! Optional destructor function to execute on the private data */
+ ast_bridge_deferred_callback_destructor pvt_destructor;
+ /* Deferreds exist in a FIFO queue on the bridge channel */
+ AST_LIST_ENTRY(bridge_deferred_callback) entry;
+};
+
static void cleanup_video_mode(struct ast_bridge *bridge);
/*! Default DTMF keys for built in features */
@@ -118,17 +134,41 @@
return current ? 0 : -1;
}
+static void enqueue_bridge_state_change(struct ast_bridge_channel *bridge_channel, struct requested_bridge_state *state)
+{
+ switch (state->state) {
+ /* States that result in the bridge being removed go on the front */
+ case AST_BRIDGE_CHANNEL_STATE_END:
+ case AST_BRIDGE_CHANNEL_STATE_HANGUP:
+ case AST_BRIDGE_CHANNEL_STATE_DEPART:
+ AST_LIST_INSERT_HEAD(&bridge_channel->requested_states, state, entry);
+ break;
+ default:
+ /* Everyone else - get in the back! */
+ AST_LIST_INSERT_TAIL(&bridge_channel->requested_states, state, entry);
+ break;
+ }
+}
+
void ast_bridge_change_state(struct ast_bridge_channel *bridge_channel, enum ast_bridge_channel_state new_state)
{
- /* Change the state on the bridge channel */
- bridge_channel->state = new_state;
+ struct requested_bridge_state *state;
+
+ if (!(state = ast_calloc(1, sizeof(*state)))) {
+ ast_log(AST_LOG_ERROR, "Failed to allocate memory to enqueue state %d\n", new_state);
+ return;
+ }
+ state->state = new_state;
/* Only poke the channel's thread if it is not us */
if (!pthread_equal(pthread_self(), bridge_channel->thread)) {
pthread_kill(bridge_channel->thread, SIGURG);
ao2_lock(bridge_channel);
+ enqueue_bridge_state_change(bridge_channel, state);
ast_cond_signal(&bridge_channel->cond);
ao2_unlock(bridge_channel);
+ } else {
+ enqueue_bridge_state_change(bridge_channel, state);
}
return;
@@ -733,6 +773,21 @@
return 0;
}
+/*! \internal \brief Get the latest state change request off of the queue
+ * \note The bridge_channel should be locked prior to calling this method */
+static enum ast_bridge_channel_state refresh_bridge_channel_state(struct ast_bridge_channel *bridge_channel)
+{
+ struct requested_bridge_state *requested_state = NULL;
+
+ requested_state = AST_LIST_REMOVE_HEAD(&bridge_channel->requested_states, entry);
+ if (requested_state) {
+ bridge_channel->state = requested_state->state;
+ }
+ ast_free(requested_state);
+
+ return bridge_channel->state;
+}
+
/*! \brief Run in a multithreaded model. Each joined channel does writing/reading in their own thread. TODO: Improve */
static enum ast_bridge_channel_state bridge_channel_join_multithreaded(struct ast_bridge_channel *bridge_channel)
{
@@ -763,10 +818,16 @@
}
ao2_lock(bridge_channel->bridge);
-
if (!bridge_channel->suspended) {
ast_bridge_handle_trip(bridge_channel->bridge, bridge_channel, chan, outfd);
}
+ ao2_unlock(bridge_channel->bridge);
+
+ ao2_lock(bridge_channel);
+ refresh_bridge_channel_state(bridge_channel);
+ ao2_unlock(bridge_channel);
+
+ ao2_lock(bridge_channel->bridge);
return bridge_channel->state;
}
@@ -780,6 +841,7 @@
ast_debug(1, "Going into a single threaded signal wait for bridge channel %p of bridge %p\n", bridge_channel, bridge_channel->bridge);
ast_cond_wait(&bridge_channel->cond, ao2_object_get_lockaddr(bridge_channel));
}
+ refresh_bridge_channel_state(bridge_channel);
ao2_unlock(bridge_channel);
ao2_lock(bridge_channel->bridge);
@@ -817,6 +879,44 @@
return;
+}
+
+/*!
+ * \internal
+ * \brief Internal function that executes a callback on a bridge channel
+ * \note Neither the bridge nor the bridge_channel locks should be held when entering
+ * this function.
+ */
+static void bridge_channel_call_deferred(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+ struct bridge_deferred_callback *deferred = NULL;
+
+ ao2_lock(bridge_channel);
+ deferred = AST_LIST_REMOVE_HEAD(&bridge_channel->deferreds, entry);
+ ao2_unlock(bridge_channel);
+
+ if (deferred) {
+ /* Execute the deferred */
+ deferred->deferred_cb(bridge, bridge_channel, deferred->pvt_data);
+
+ /* Clean up the private data */
+ if (deferred->pvt_destructor) {
+ deferred->pvt_destructor(deferred->pvt_data);
+ }
+ ast_free(deferred);
+
+ /* If we are handing the channel off to an external callback for ownership,
+ * we are not guaranteed what kind of state it will come back in. If
+ * the channel hungup, we need to detect that here. */
+ if (bridge_channel->chan && ast_check_hangup_locked(bridge_channel->chan)) {
+ ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+ }
+ }
+
+ /* if the channel is still in deferred state, revert it back to wait state */
+ if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_EXECUTE_DEFERRED) {
+ ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_WAIT);
+ }
}
/*!
@@ -1019,9 +1119,19 @@
bridge_channel_talking(bridge_channel->bridge, bridge_channel);
ao2_lock(bridge_channel->bridge);
break;
+ case AST_BRIDGE_CHANNEL_STATE_EXECUTE_DEFERRED:
+ bridge_channel_suspend(bridge_channel->bridge, bridge_channel);
+ ao2_unlock(bridge_channel->bridge);
+ bridge_channel_call_deferred(bridge_channel->bridge, bridge_channel);
+ ao2_lock(bridge_channel->bridge);
+ bridge_channel_unsuspend(bridge_channel->bridge, bridge_channel);
+ break;
default:
break;
}
+ ao2_lock(bridge_channel);
+ refresh_bridge_channel_state(bridge_channel);
+ ao2_unlock(bridge_channel);
}
ast_channel_internal_bridge_set(bridge_channel->chan, NULL);
@@ -1072,6 +1182,8 @@
static void bridge_channel_destroy(void *obj)
{
struct ast_bridge_channel *bridge_channel = obj;
+ struct bridge_deferred_callback *deferred;
+ struct requested_bridge_state *state;
if (bridge_channel->callid) {
bridge_channel->callid = ast_callid_unref(bridge_channel->callid);
@@ -1081,8 +1193,18 @@
ao2_ref(bridge_channel->bridge, -1);
bridge_channel->bridge = NULL;
}
+
/* Destroy elements of the bridge channel structure and the bridge channel structure itself */
ast_cond_destroy(&bridge_channel->cond);
+ while ((deferred = AST_LIST_REMOVE_HEAD(&bridge_channel->deferreds, entry))) {
+ if (deferred->pvt_destructor) {
+ deferred->pvt_destructor(deferred->pvt_data);
+ }
+ ast_free(deferred);
+ }
+ while ((state = AST_LIST_REMOVE_HEAD(&bridge_channel->requested_states, entry))) {
+ ast_free(state);
+ }
}
static struct ast_bridge_channel *bridge_channel_alloc(struct ast_bridge *bridge)
@@ -1497,6 +1619,49 @@
return 0;
}
+
+int ast_bridge_issue_deferred(struct ast_bridge *bridge,
+ struct ast_channel *chan,
+ ast_bridge_deferred_callback deferred_cb,
+ void *pvt_data,
+ ast_bridge_deferred_callback_destructor pvt_destructor)
+{
+ struct ast_bridge_channel *bridge_channel = NULL;
+ struct bridge_deferred_callback *deferred;
+
+ if (pvt_data && !pvt_destructor) {
+ ast_log(AST_LOG_WARNING, "Deferred callback being issued in bridge %p with private data but no destructor\n", bridge);
+ return -1;
+ }
+
+ if (!(deferred = ast_calloc(1, sizeof(*deferred)))) {
+ ast_log(AST_LOG_WARNING, "Failed to create deferred callback container\n");
+ return -1;
+ }
+ deferred->deferred_cb = deferred_cb;
+ deferred->pvt_data = pvt_data;
+ deferred->pvt_destructor = pvt_destructor;
+
+ ao2_lock(bridge);
+
+ if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
+ ao2_unlock(bridge);
+ ast_debug(1, "Unable to find channel %s in bridge %p\n", ast_channel_name(chan), bridge);
+ ast_free(deferred);
+ return -1;
+ }
+
+ ao2_lock(bridge_channel);
+ AST_LIST_INSERT_TAIL(&bridge_channel->deferreds, deferred, entry);
+ ao2_unlock(bridge_channel);
+
+ ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_EXECUTE_DEFERRED);
+
+ ao2_unlock(bridge);
+
+ return 0;
+}
+
void ast_bridge_set_mixing_interval(struct ast_bridge *bridge, unsigned int mixing_interval)
{
Added: team/mjordan/confbridgeactionexec/tests/test_bridging.c
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/confbridgeactionexec/tests/test_bridging.c?view=auto&rev=371177
==============================================================================
--- team/mjordan/confbridgeactionexec/tests/test_bridging.c (added)
+++ team/mjordan/confbridgeactionexec/tests/test_bridging.c Sun Aug 12 15:54:27 2012
@@ -1,0 +1,781 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012, Matt Jordan
+ *
+ * Matt Jordan <mjordan at digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief Unit tests for jitterbuf.c
+ *
+ * \author\verbatim Matt Jordan <mjordan at digium.com> \endverbatim
+ *
+ * \ingroup tests
+ */
+
+/*** MODULEINFO
+ <depend>TEST_FRAMEWORK</depend>
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/utils.h"
+#include "asterisk/module.h"
+#include "asterisk/test.h"
+#include "asterisk/channel.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_technology.h"
+
+/*! \internal \brief Verify the expected result from two integer values. Note
+ * that it assumes the mailbox snapshot object is test_mbox_snapshot */
+#define ASSERT_EQUAL(expected, actual) do { \
+ if ((expected) == (actual)) { \
+ ast_test_status_update(test, "Test failed for %s: Expected [%d], Actual [%d]\n", #actual, expected, actual); \
+ return AST_TEST_FAIL; \
+ } } while (0)
+
+#define AST_BRIDGE_CAPABILITY_TEST (1 << 31)
+
+static int mock_bridge_create(struct ast_bridge *bridge);
+static int mock_bridge_destroy(struct ast_bridge *bridge);
+static int mock_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
+static int mock_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
+static void mock_bridge_suspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
+static void mock_bridge_unsuspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
+static int mock_bridge_compatible(struct ast_bridge_channel *bridge_channel);
+static enum ast_bridge_write_result mock_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridged_channel, struct ast_frame *frame);
+
+static int mock_channel_write(struct ast_channel *ast, struct ast_frame *frame);
+static struct ast_frame *mock_channel_read(struct ast_channel *ast);
+
+struct bridge_callback_tracking {
+ unsigned int create_calls;
+ unsigned int destroy_calls;
+ unsigned int join_calls;
+ unsigned int leave_calls;
+ unsigned int suspend_calls;
+ unsigned int unsuspend_calls;
+ unsigned int compatible_calls;
+ unsigned int write_calls;
+};
+
+struct mock_channel_pvt {
+ struct ast_test *test;
+};
+
+static struct bridge_callback_tracking test_tracker;
+
+static struct ast_bridge_technology mock_bridge_tech = {
+ .name = "mock_bridge_tech",
+ .capabilities = 0, /* This should be set by the specific test */
+ .preference = AST_BRIDGE_PREFERENCE_HIGH,
+ .create = mock_bridge_create,
+ .destroy = mock_bridge_destroy,
+ .join = mock_bridge_join,
+ .leave = mock_bridge_leave,
+ .suspend = mock_bridge_suspend,
+ .unsuspend = mock_bridge_unsuspend,
+ .compatible = mock_bridge_compatible,
+ .write = mock_bridge_write,
+};
+
+static struct ast_channel_tech mock_channel_tech = {
+ .type = "mock",
+ .read = mock_channel_read,
+ .write = mock_channel_write,
+};
+
+static void reset_test_tracking(void)
+{
+ test_tracker.create_calls = 0;
+ test_tracker.destroy_calls = 0;
+ test_tracker.join_calls = 0;
+ test_tracker.leave_calls = 0;
+ test_tracker.suspend_calls = 0;
+ test_tracker.unsuspend_calls = 0;
+ test_tracker.compatible_calls = 0;
+ test_tracker.write_calls = 0;
+}
+
+static int mock_bridge_create(struct ast_bridge *bridge)
+{
+ test_tracker.create_calls++;
+ return 0;
+}
+
+static int mock_bridge_destroy(struct ast_bridge *bridge)
+{
+ test_tracker.destroy_calls++;
+ return 0;
+}
+
+static int mock_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+ test_tracker.join_calls++;
+ return 0;
+}
+
+static int mock_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+ test_tracker.leave_calls++;
+ return 0;
+}
+
+static void mock_bridge_suspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+ test_tracker.suspend_calls++;
+}
+
+static void mock_bridge_unsuspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+ test_tracker.unsuspend_calls++;
+}
+
+static int mock_bridge_compatible(struct ast_bridge_channel *bridge_channel)
+{
+ test_tracker.compatible_calls++;
+ return 0;
+}
+
+static enum ast_bridge_write_result mock_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridged_channel, struct ast_frame *frame)
+{
+ test_tracker.write_calls++;
+ return AST_BRIDGE_WRITE_SUCCESS;
+}
+
+static void mock_tech_cleanup(struct ast_bridge_technology *tech)
+{
+ if (!tech) {
+ return;
+ }
+
+ reset_test_tracking();
+
+ /* A test must set its capabilities - reset during cleanup */
+ tech->capabilities = 0;
+
+ ast_bridge_technology_unregister(tech);
+}
+
+static void bridge_destructor(struct ast_bridge *bridge)
+{
+ if (!bridge) {
+ return;
+ }
+ ast_bridge_destroy(bridge);
+}
+
+static struct ast_channel *create_mock_channel(struct ast_test *test)
+{
+ struct ast_channel *chan;
+ struct ast_format_cap *nativeformats;
+ struct mock_channel_pvt *pvt;
+
+ if (!(pvt = ast_calloc(1, sizeof(*pvt)))) {
+ return NULL;
+ }
+ pvt->test = test;
+
+ if (!(chan = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL,
+ NULL, NULL, 0, 0, "MockChannel"))) {
+ ast_free(pvt);
+ return NULL;
+ }
+ ast_channel_tech_set(chan, &mock_channel_tech);
+ ast_channel_tech_pvt_set(chan, pvt);
+
+ /* ast_format_set(ast_channel_writeformat(chan), AST_FORMAT_GSM, 0);
+ nativeformats = ast_channel_nativeformats(chan);
+ ast_format_cap_add(nativeformats, ast_channel_writeformat(chan));
+ ast_format_set(ast_channel_rawwriteformat(chan), AST_FORMAT_GSM, 0);
+ ast_format_set(ast_channel_readformat(chan), AST_FORMAT_GSM, 0);
+ ast_format_set(ast_channel_rawreadformat(chan), AST_FORMAT_GSM, 0);*/
+
+
+
+ return chan;
+}
+
+static int mock_channel_write(struct ast_channel *ast, struct ast_frame *frame)
+{
+ return 0;
+}
+
+static struct ast_frame *mock_channel_read(struct ast_channel *ast)
+{
+ struct ast_frame *frame;
+
+ frame = &ast_null_frame;
+
+ return frame;
+}
+
+/* Things to test
+ * ast_bridge_new - get back bridge that you expect based on capabilities.
+ * also test ast_bridge_destroy
+ * ast_bridge_check - check if bridge technologies exist. Nominal / off-nominal
+ * ast_bridge_join: BLOCKING OPERATIONS. Will need a specific way of doing this
+ * that does the join and returns success/failure on separate threads of execution.
+ * Simple test - make sure you can join in
+ * Swap test - join a channel, make another new channel, and swap it out
+ * *** IGNORE FEATURES FOR NOW - probably belong in separate tests ***
+ * ast_bridge_impart - same tests. Will also need to test ast_bridge_depart with this.
+ * ast_bridge_remove: test with:
+ * ast_bridge_join
+ * ast_bridge_impart
+ * ast_bridge_merge - test merging empty bridge into non-empty, non-empty into empty, and
+ * two populated
+ * ast_bridge_suspend/unsuspend
+ * ast_bridge_issue_deferred: callback. test with nominal / off-nominal data.
+
+ Note that more unit tests could be written, but blah.
+ */
+
+AST_TEST_DEFINE(bridging_issue_deferred_off_nominal)
+{
+ enum ast_test_result_state result = AST_TEST_FAIL;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "bridging_issue_deferred_off_nominal";
+ info->category = "/main/bridging/";
+ info->summary = "";
+ info->description =
+ "";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+ mock_channel_thread(NULL);
+
+ return result;
+}
+
+AST_TEST_DEFINE(bridging_issue_deferred_chain)
+{
+ enum ast_test_result_state result = AST_TEST_FAIL;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "bridging_issue_deferred_chain";
+ info->category = "/main/bridging/";
+ info->summary = "";
+ info->description =
+ "";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ return result;
+}
+
+AST_TEST_DEFINE(bridging_issue_deferred_nominal)
+{
+ enum ast_test_result_state result = AST_TEST_FAIL;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "bridging_issue_deferred_nominal";
+ info->category = "/main/bridging/";
+ info->summary = "";
+ info->description =
+ "";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ return result;
+}
+
+AST_TEST_DEFINE(bridging_suspend_unsuspend_off_nominal)
+{
+ enum ast_test_result_state result = AST_TEST_FAIL;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "bridging_suspend_unsuspend_off_nominal";
+ info->category = "/main/bridging/";
+ info->summary = "";
+ info->description =
+ "";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ return result;
+}
+
+AST_TEST_DEFINE(bridging_suspend_unsuspend_nominal)
+{
+ enum ast_test_result_state result = AST_TEST_FAIL;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "bridging_suspend_unsuspend_nominal";
+ info->category = "/main/bridging/";
+ info->summary = "";
+ info->description =
+ "";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ return result;
+}
+
+AST_TEST_DEFINE(bridging_merge_off_nominal)
+{
+ enum ast_test_result_state result = AST_TEST_FAIL;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "bridging_merge_off_nominal";
+ info->category = "/main/bridging/";
+ info->summary = "";
+ info->description =
+ "";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ return result;
+}
+
+AST_TEST_DEFINE(bridging_merge_nominal)
+{
+ enum ast_test_result_state result = AST_TEST_FAIL;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "bridging_merge_nominal";
+ info->category = "/main/bridging/";
+ info->summary = "";
+ info->description =
+ "";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ return result;
+}
+
+AST_TEST_DEFINE(bridging_remove_off_nominal)
+{
+ enum ast_test_result_state result = AST_TEST_FAIL;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "bridging_remove_off_nominal";
+ info->category = "/main/bridging/";
+ info->summary = "";
+ info->description =
+ "";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ return result;
+}
+
+AST_TEST_DEFINE(bridging_remove_imparted)
+{
+ enum ast_test_result_state result = AST_TEST_FAIL;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "bridging_remove_imparted";
+ info->category = "/main/bridging/";
+ info->summary = "";
+ info->description =
+ "";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ return result;
+}
+
+AST_TEST_DEFINE(bridging_remove_joined)
+{
+ enum ast_test_result_state result = AST_TEST_FAIL;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "bridging_remove_joined";
+ info->category = "/main/bridging/";
+ info->summary = "";
+ info->description =
+ "";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ return result;
+}
+
+AST_TEST_DEFINE(bridging_impart_off_nominal)
+{
+ enum ast_test_result_state result = AST_TEST_FAIL;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "bridging_impart_off_nominal";
+ info->category = "/main/bridging/";
+ info->summary = "";
+ info->description =
+ "";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ return result;
+}
+
+AST_TEST_DEFINE(bridging_impart_swap)
+{
+ enum ast_test_result_state result = AST_TEST_FAIL;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "bridging_impart_swap";
+ info->category = "/main/bridging/";
+ info->summary = "";
+ info->description =
+ "";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ return result;
+}
+
+AST_TEST_DEFINE(bridging_impart_simple)
+{
+ enum ast_test_result_state result = AST_TEST_FAIL;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "bridging_impart_simple";
+ info->category = "/main/bridging/";
+ info->summary = "";
+ info->description =
+ "";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ return result;
+}
+
+AST_TEST_DEFINE(bridging_join_off_nominal)
+{
+ enum ast_test_result_state result = AST_TEST_FAIL;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "bridging_join_off_nominal";
+ info->category = "/main/bridging/";
+ info->summary = "";
+ info->description =
+ "";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ return result;
+}
+
+AST_TEST_DEFINE(bridging_join_swap)
+{
+ enum ast_test_result_state result = AST_TEST_FAIL;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "bridging_join_swap";
+ info->category = "/main/bridging/";
+ info->summary = "";
+ info->description =
+ "";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ return result;
+}
+
+static void bridging_join_simple_mock_channel_thread(void *data)
+{
+ struct ast_channel *mock_channel = data;
+ struct mock_channel_pvt *pvt;
+ RAII_VAR(struct ast_bridge *, bridge, ast_bridge_new(AST_BRIDGE_CAPABILITY_TEST, 0), bridge_destructor);
+ enum ast_bridge_channel_state final_state;
+
+ if (!mock_channel) {
+ return;
+ }
+ pvt = ast_channel_tech_pvt(mock_channel);
+
+ if (!bridge) {
+ ast_test_status_update(pvt->test, "Unable to obtain bridge for bridging_join_simple\n");
+ return;
+ }
+
+ final_state = ast_bridge_join(bridge, mock_channel, NULL, NULL, NULL);
+}
+
+AST_TEST_DEFINE(bridging_join_simple)
+{
+ RAII_VAR(struct ast_bridge_technology *, mock_tech, &mock_bridge_tech, mock_tech_cleanup);
+ RAII_VAR(struct ast_channel *, mock_channel, NULL, ao2_cleanup);
+ struct mock_channel_pvt *pvt;
+ pthread_t test_thread;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "bridging_join_simple";
+ info->category = "/main/bridging/";
+ info->summary = "";
+ info->description =
+ "";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ mock_tech->capabilities = AST_BRIDGE_CAPABILITY_TEST;
+ ast_bridge_technology_register(mock_tech);
+
[... 218 lines stripped ...]
More information about the asterisk-commits
mailing list