[asterisk-commits] kmoore: trunk r417361 - in /trunk: include/asterisk/ main/ tests/

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Thu Jun 26 07:43:52 CDT 2014


Author: kmoore
Date: Thu Jun 26 07:43:47 2014
New Revision: 417361

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=417361
Log:
Bridging: Allow channels to define bridging hooks

This patch allows the current owner of a channel to define various
feature hooks to be made available once the channel has entered a
bridge. This includes any hooks that are setup on the
ast_bridge_features struct such as DTMF hooks, bridge event hooks
(join, leave, etc.), and interval hooks.

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

Added:
    trunk/tests/test_channel_feature_hooks.c   (with props)
Modified:
    trunk/include/asterisk/bridge_features.h
    trunk/include/asterisk/channel.h
    trunk/main/bridge.c
    trunk/main/bridge_channel.c
    trunk/main/channel.c

Modified: trunk/include/asterisk/bridge_features.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/bridge_features.h?view=diff&rev=417361&r1=417360&r2=417361
==============================================================================
--- trunk/include/asterisk/bridge_features.h (original)
+++ trunk/include/asterisk/bridge_features.h Thu Jun 26 07:43:47 2014
@@ -722,6 +722,14 @@
 void ast_bridge_features_set_flag(struct ast_bridge_features *features, unsigned int flag);
 
 /*!
+ * \brief Merge one ast_bridge_features into another
+ *
+ * \param into The ast_bridge_features that will be merged into
+ * \param from The ast_bridge_features that will be merged from
+ */
+void ast_bridge_features_merge(struct ast_bridge_features *into, const struct ast_bridge_features *from);
+
+/*!
  * \brief Initialize bridge features structure
  *
  * \param features Bridge featues structure

Modified: trunk/include/asterisk/channel.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/channel.h?view=diff&rev=417361&r1=417360&r2=417361
==============================================================================
--- trunk/include/asterisk/channel.h (original)
+++ trunk/include/asterisk/channel.h Thu Jun 26 07:43:47 2014
@@ -4488,4 +4488,44 @@
  */
 void ast_channel_end_dtmf(struct ast_channel *chan, char digit, struct timeval start, const char *why);
 
+struct ast_bridge_features;
+
+/*!
+ * \brief Gets the channel-attached features a channel has access to upon being bridged.
+ *
+ * \note The channel must be locked when calling this function.
+ *
+ * \param chan Which channel to get features for
+ *
+ * \retval non-NULL The features currently set for this channel
+ * \retval NULL if the features have not been set
+ */
+struct ast_bridge_features *ast_channel_feature_hooks_get(struct ast_channel *chan);
+
+/*!
+ * \brief Appends to the channel-attached features a channel has access to upon being bridged.
+ *
+ * \note The channel must be locked when calling this function.
+ *
+ * \param chan Which channel to set features for
+ * \param features The feature set to append to the channel's features
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+int ast_channel_feature_hooks_append(struct ast_channel *chan, struct ast_bridge_features *features);
+
+/*!
+ * \brief Sets the channel-attached features a channel has access to upon being bridged.
+ *
+ * \note The channel must be locked when calling this function.
+ *
+ * \param chan Which channel to set features for
+ * \param features The feature set with which to replace the channel's features
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+int ast_channel_feature_hooks_replace(struct ast_channel *chan, struct ast_bridge_features *features);
+
 #endif /* _ASTERISK_CHANNEL_H */

Modified: trunk/main/bridge.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/bridge.c?view=diff&rev=417361&r1=417360&r2=417361
==============================================================================
--- trunk/main/bridge.c (original)
+++ trunk/main/bridge.c Thu Jun 26 07:43:47 2014
@@ -3327,6 +3327,64 @@
 	return cmp;
 }
 
+/*! \brief Callback for merging hook ao2_containers */
+static int merge_container_cb(void *obj, void *data, int flags)
+{
+	ao2_link(data, obj);
+	return 0;
+}
+
+/*! \brief Wrapper for interval hooks that calls into the wrapped hook */
+static int interval_wrapper_cb(struct ast_bridge_channel *bridge_channel, void *obj)
+{
+	struct ast_bridge_hook_timer *hook = obj;
+
+	return hook->generic.callback(bridge_channel, hook->generic.hook_pvt);
+}
+
+/*! \brief Destructor for the hook wrapper */
+static void interval_wrapper_pvt_dtor(void *obj)
+{
+	ao2_cleanup(obj);
+}
+
+/*! \brief Wrap the provided interval hook and add it to features */
+static void wrap_hook(struct ast_bridge_features *features, struct ast_bridge_hook_timer *hook)
+{
+	/* Break out of the current wrapper if it exists to avoid multiple layers */
+	if (hook->generic.callback == interval_wrapper_cb) {
+		hook = hook->generic.hook_pvt;
+	}
+
+	ast_bridge_interval_hook(features, hook->timer.flags, hook->timer.interval,
+		interval_wrapper_cb, ao2_bump(hook), interval_wrapper_pvt_dtor,
+		hook->generic.remove_flags.flags);
+}
+
+void ast_bridge_features_merge(struct ast_bridge_features *into, const struct ast_bridge_features *from)
+{
+	struct ast_bridge_hook_timer *hook;
+	int idx;
+
+	/* Merge hook containers */
+	ao2_callback(from->dtmf_hooks, 0, merge_container_cb, into->dtmf_hooks);
+	ao2_callback(from->other_hooks, 0, merge_container_cb, into->other_hooks);
+
+	/* Merge hook heaps */
+	ast_heap_wrlock(from->interval_hooks);
+	for (idx = 1; (hook = ast_heap_peek(from->interval_hooks, idx)); idx++) {
+		wrap_hook(into, hook);
+	}
+	ast_heap_unlock(from->interval_hooks);
+
+	/* Merge feature flags */
+	into->feature_flags.flags |= from->feature_flags.flags;
+	into->usable |= from->usable;
+
+	into->mute |= from->mute;
+	into->dtmf_passthrough |= from->dtmf_passthrough;
+}
+
 /* XXX ASTERISK-21271 make ast_bridge_features_init() static when make ast_bridge_join() requires features to be allocated. */
 int ast_bridge_features_init(struct ast_bridge_features *features)
 {

Modified: trunk/main/bridge_channel.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/bridge_channel.c?view=diff&rev=417361&r1=417360&r2=417361
==============================================================================
--- trunk/main/bridge_channel.c (original)
+++ trunk/main/bridge_channel.c Thu Jun 26 07:43:47 2014
@@ -2187,6 +2187,7 @@
 int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel)
 {
 	int res = 0;
+	struct ast_bridge_features *channel_features;
 
 	ast_format_copy(&bridge_channel->read_format, ast_channel_readformat(bridge_channel->chan));
 	ast_format_copy(&bridge_channel->write_format, ast_channel_writeformat(bridge_channel->chan));
@@ -2201,8 +2202,8 @@
 	 */
 	ast_bridge_lock(bridge_channel->bridge);
 
+	ast_channel_lock(bridge_channel->chan);
 	/* Make sure we're still good to be put into a bridge */
-	ast_channel_lock(bridge_channel->chan);
 	if (ast_channel_internal_bridge(bridge_channel->chan)
 		|| ast_test_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_ZOMBIE)) {
 		ast_channel_unlock(bridge_channel->chan);
@@ -2214,6 +2215,12 @@
 		return -1;
 	}
 	ast_channel_internal_bridge_set(bridge_channel->chan, bridge_channel->bridge);
+
+	/* Attach features requested by the channel */
+	channel_features = ast_channel_feature_hooks_get(bridge_channel->chan);
+	if (channel_features) {
+		ast_bridge_features_merge(bridge_channel->features, channel_features);
+	}
 	ast_channel_unlock(bridge_channel->chan);
 
 	/* Add the jitterbuffer if the channel requires it */

Modified: trunk/main/channel.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/channel.c?view=diff&rev=417361&r1=417360&r2=417361
==============================================================================
--- trunk/main/channel.c (original)
+++ trunk/main/channel.c Thu Jun 26 07:43:47 2014
@@ -10453,3 +10453,71 @@
 	ast_log(LOG_DTMF, "DTMF end '%c' simulated on %s due to %s, duration %ld ms\n",
 		digit, ast_channel_name(chan), why, duration);
 }
+
+static void features_destroy(void *obj)
+{
+	ast_bridge_features_destroy(obj);
+}
+
+static const struct ast_datastore_info bridge_features_info = {
+	.type = "bridge-features",
+	.destroy = features_destroy,
+};
+
+struct ast_bridge_features *ast_channel_feature_hooks_get(struct ast_channel *chan)
+{
+	struct ast_datastore *datastore;
+
+	datastore = ast_channel_datastore_find(chan, &bridge_features_info, NULL);
+	if (!datastore) {
+		return NULL;
+	}
+	return datastore->data;
+}
+
+static int channel_feature_hooks_set_full(struct ast_channel *chan, struct ast_bridge_features *features, int replace)
+{
+	struct ast_datastore *datastore;
+	struct ast_bridge_features *ds_features;
+
+	datastore = ast_channel_datastore_find(chan, &bridge_features_info, NULL);
+	if (datastore) {
+		ds_features = datastore->data;
+		if (replace) {
+			ast_bridge_features_cleanup(ds_features);
+			ast_bridge_features_init(ds_features);
+		}
+		if (features) {
+			ast_bridge_features_merge(ds_features, features);
+		}
+		return 0;
+	}
+
+	datastore = ast_datastore_alloc(&bridge_features_info, NULL);
+	if (!datastore) {
+		return -1;
+	}
+
+	ds_features = ast_bridge_features_new();
+	if (!ds_features) {
+		ast_datastore_free(datastore);
+		return -1;
+	}
+
+	if (features) {
+		ast_bridge_features_merge(ds_features, features);
+	}
+	datastore->data = ds_features;
+	ast_channel_datastore_add(chan, datastore);
+	return 0;
+}
+
+int ast_channel_feature_hooks_append(struct ast_channel *chan, struct ast_bridge_features *features)
+{
+	return channel_feature_hooks_set_full(chan, features, 0);
+}
+
+int ast_channel_feature_hooks_replace(struct ast_channel *chan, struct ast_bridge_features *features)
+{
+	return channel_feature_hooks_set_full(chan, features, 1);
+}

Added: trunk/tests/test_channel_feature_hooks.c
URL: http://svnview.digium.com/svn/asterisk/trunk/tests/test_channel_feature_hooks.c?view=auto&rev=417361
==============================================================================
--- trunk/tests/test_channel_feature_hooks.c (added)
+++ trunk/tests/test_channel_feature_hooks.c Thu Jun 26 07:43:47 2014
@@ -1,0 +1,324 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2014, Digium, Inc.
+ *
+ * Kinsey Moore <kmoore 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 Channel features unit tests
+ *
+ * \author Kinsey Moore <kmoore at digium.com>
+ *
+ */
+
+/*** MODULEINFO
+	<depend>TEST_FRAMEWORK</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/module.h"
+#include "asterisk/test.h"
+#include "asterisk/channel.h"
+#include "asterisk/time.h"
+#include "asterisk/bridge.h"
+#include "asterisk/bridge_basic.h"
+#include "asterisk/features.h"
+
+#define TEST_CATEGORY "/channels/features/"
+
+#define CHANNEL_TECH_NAME "FeaturesTestChannel"
+
+#define TEST_BACKEND_NAME "Features Test Logging"
+
+/*! \brief A channel technology used for the unit tests */
+static struct ast_channel_tech test_features_chan_tech = {
+	.type = CHANNEL_TECH_NAME,
+	.description = "Mock channel technology for Features tests",
+};
+
+static void test_nanosleep(int secs, long nanosecs)
+{
+	struct timespec sleep_time = {secs, nanosecs};
+
+	while ((nanosleep(&sleep_time, &sleep_time) == -1) && (errno == EINTR)) {
+	}
+}
+
+/*! \brief Wait until a channel is bridged */
+static void wait_for_bridged(struct ast_channel *channel)
+{
+	ast_channel_lock(channel);
+	while (!ast_channel_is_bridged(channel)) {
+		ast_channel_unlock(channel);
+		test_nanosleep(0, 1000000);
+		ast_channel_lock(channel);
+	}
+	ast_channel_unlock(channel);
+}
+
+/*! \brief Wait until a channel is not bridged */
+static void wait_for_unbridged(struct ast_channel *channel)
+{
+	struct timespec short_sleep = {0, 1000000};
+	ast_channel_lock(channel);
+	while (ast_channel_is_bridged(channel)) {
+		ast_channel_unlock(channel);
+		while ((nanosleep(&short_sleep, &short_sleep) == -1) && (errno == EINTR)) {
+		}
+		ast_channel_lock(channel);
+	}
+	ast_channel_unlock(channel);
+}
+
+/*! \brief Create a \ref test_features_chan_tech for Alice. */
+#define START_ALICE(channel) START_CHANNEL(channel, "Alice", "100")
+
+/*! \brief Create a \ref test_features_chan_tech for Bob. */
+#define START_BOB(channel) START_CHANNEL(channel, "Bob", "200")
+
+#define START_CHANNEL(channel, name, number) do { \
+	channel = ast_channel_alloc(0, AST_STATE_UP, number, name, number, number, \
+		"default", NULL, NULL, 0, CHANNEL_TECH_NAME "/" name); \
+	ast_channel_unlock(channel); \
+	} while (0)
+
+/*! \brief Hang up a test channel safely */
+#define HANGUP_CHANNEL(channel) do { \
+	ao2_ref(channel, +1); \
+	ast_hangup((channel)); \
+	ao2_cleanup(channel); \
+	channel = NULL; \
+	} while (0)
+
+static void safe_channel_release(struct ast_channel *chan)
+{
+	if (!chan) {
+		return;
+	}
+	ast_channel_release(chan);
+}
+
+static void safe_bridge_destroy(struct ast_bridge *bridge)
+{
+	if (!bridge) {
+		return;
+	}
+	ast_bridge_destroy(bridge, 0);
+}
+
+static int feature_callback(struct ast_bridge_channel *bridge_channel, void *obj)
+{
+	int *callback_executed = obj;
+	(*callback_executed)++;
+	return 0;
+}
+
+AST_TEST_DEFINE(test_features_channel_dtmf)
+{
+	RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release);
+	RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release);
+	RAII_VAR(struct ast_bridge *, bridge1, NULL, safe_bridge_destroy);
+	RAII_VAR(struct ast_bridge *, bridge2, NULL, safe_bridge_destroy);
+	struct ast_bridge_features features;
+	int callback_executed = 0;
+	struct ast_frame f = { AST_FRAME_DTMF, };
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test running DTMF hooks on a channel via the feature hooks mechanism";
+		info->description =
+			"This test creates two channels, adds a DTMF hook to one, places them into\n"
+			"a bridge, and verifies that the DTMF hook added to the channel feature\n"
+			"hooks can be triggered once the channel is bridged.\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	/* Create the bridges */
+	bridge1 = ast_bridge_basic_new();
+	ast_test_validate(test, bridge1 != NULL);
+	bridge2 = ast_bridge_basic_new();
+	ast_test_validate(test, bridge2 != NULL);
+
+	/* Create channels that will go into the bridge */
+	START_ALICE(chan_alice);
+	START_BOB(chan_bob);
+
+	/* Setup the features and add them to alice */
+	ast_bridge_features_init(&features);
+	ast_test_validate(test, !ast_bridge_dtmf_hook(&features, "##**", feature_callback, &callback_executed, NULL, 0));
+	ast_test_validate(test, !ast_channel_feature_hooks_append(chan_alice, &features));
+	ast_bridge_features_cleanup(&features);
+
+	/* Bridge the channels */
+	ast_test_validate(test, !ast_bridge_impart(bridge1, chan_alice, NULL, NULL, AST_BRIDGE_IMPART_CHAN_DEPARTABLE));
+	ast_test_validate(test, !ast_bridge_impart(bridge1, chan_bob, NULL, NULL, AST_BRIDGE_IMPART_CHAN_DEPARTABLE));
+
+	wait_for_bridged(chan_alice);
+
+	/* Execute the feature */
+	f.len = 100;
+	f.subclass.integer = '#';
+	ast_queue_frame(chan_alice, &f);
+	ast_queue_frame(chan_alice, &f);
+	f.subclass.integer = '*';
+	ast_queue_frame(chan_alice, &f);
+	ast_queue_frame(chan_alice, &f);
+
+	test_nanosleep(1, 0);
+
+	/* Remove the channels from the bridge */
+	ast_test_validate(test, !ast_bridge_depart(chan_alice));
+	ast_test_validate(test, !ast_bridge_depart(chan_bob));
+
+	wait_for_unbridged(chan_alice);
+
+	/* Bridge the channels again to ensure that the feature hook remains on the channel */
+	ast_test_validate(test, !ast_bridge_impart(bridge2, chan_alice, NULL, NULL, AST_BRIDGE_IMPART_CHAN_DEPARTABLE));
+	ast_test_validate(test, !ast_bridge_impart(bridge2, chan_bob, NULL, NULL, AST_BRIDGE_IMPART_CHAN_DEPARTABLE));
+
+	wait_for_bridged(chan_alice);
+
+	/* Execute the feature */
+	f.len = 100;
+	f.subclass.integer = '#';
+	ast_queue_frame(chan_alice, &f);
+	ast_queue_frame(chan_alice, &f);
+	f.subclass.integer = '*';
+	ast_queue_frame(chan_alice, &f);
+	ast_queue_frame(chan_alice, &f);
+
+	test_nanosleep(1, 0);
+
+	/* Remove the channels from the bridge */
+	ast_test_validate(test, !ast_bridge_depart(chan_alice));
+	ast_test_validate(test, !ast_bridge_depart(chan_bob));
+
+	/* Hangup the channels */
+	HANGUP_CHANNEL(chan_alice);
+	HANGUP_CHANNEL(chan_bob);
+
+	ast_test_validate(test, callback_executed == 2);
+
+	return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(test_features_channel_interval)
+{
+	RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release);
+	RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release);
+	RAII_VAR(struct ast_bridge *, bridge1, NULL, safe_bridge_destroy);
+	RAII_VAR(struct ast_bridge *, bridge2, NULL, safe_bridge_destroy);
+	struct ast_bridge_features features;
+	int callback_executed = 0;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test running interval hooks on a channel via the feature hooks mechanism";
+		info->description =
+			"This test creates two channels, adds an interval hook to one, places them\n"
+			"into a bridge, and verifies that the interval hook added to the channel\n"
+			"feature hooks is triggered once the channel is bridged.\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	/* Create the bridges */
+	bridge1 = ast_bridge_basic_new();
+	ast_test_validate(test, bridge1 != NULL);
+	bridge2 = ast_bridge_basic_new();
+	ast_test_validate(test, bridge2 != NULL);
+
+	/* Create channels that will go into the bridge */
+	START_ALICE(chan_alice);
+	START_BOB(chan_bob);
+
+	/* Setup the features and add them to alice */
+	ast_bridge_features_init(&features);
+	ast_test_validate(test, !ast_bridge_interval_hook(&features, 0, 1000, feature_callback, &callback_executed, NULL, 0));
+	ast_test_validate(test, !ast_channel_feature_hooks_append(chan_alice, &features));
+	ast_bridge_features_cleanup(&features);
+
+	/* Bridge the channels */
+	ast_test_validate(test, !ast_bridge_impart(bridge1, chan_alice, NULL, NULL, AST_BRIDGE_IMPART_CHAN_DEPARTABLE));
+	ast_test_validate(test, !ast_bridge_impart(bridge1, chan_bob, NULL, NULL, AST_BRIDGE_IMPART_CHAN_DEPARTABLE));
+
+	wait_for_bridged(chan_alice);
+
+	/* Let the interval hook execute once */
+	test_nanosleep(1, 500000000);
+
+	/* Remove the channels from the bridge */
+	ast_test_validate(test, !ast_bridge_depart(chan_alice));
+	ast_test_validate(test, !ast_bridge_depart(chan_bob));
+
+	wait_for_unbridged(chan_alice);
+
+	ast_test_validate(test, callback_executed >= 1);
+	callback_executed = 0;
+
+	/* Bridge the channels again to ensure that the feature hook remains on the channel */
+	ast_test_validate(test, !ast_bridge_impart(bridge2, chan_alice, NULL, NULL, AST_BRIDGE_IMPART_CHAN_DEPARTABLE));
+	ast_test_validate(test, !ast_bridge_impart(bridge2, chan_bob, NULL, NULL, AST_BRIDGE_IMPART_CHAN_DEPARTABLE));
+
+	wait_for_bridged(chan_alice);
+
+	/* Let the interval hook execute once */
+	test_nanosleep(1, 500000000);
+
+	/* Remove the channels from the bridge */
+	ast_test_validate(test, !ast_bridge_depart(chan_alice));
+	ast_test_validate(test, !ast_bridge_depart(chan_bob));
+
+	/* Hangup the channels */
+	HANGUP_CHANNEL(chan_alice);
+	HANGUP_CHANNEL(chan_bob);
+
+	ast_test_validate(test, callback_executed >= 1);
+
+	return AST_TEST_PASS;
+}
+
+static int unload_module(void)
+{
+	AST_TEST_UNREGISTER(test_features_channel_dtmf);
+	AST_TEST_UNREGISTER(test_features_channel_interval);
+
+	ast_channel_unregister(&test_features_chan_tech);
+
+	return 0;
+}
+
+static int load_module(void)
+{
+	ast_channel_register(&test_features_chan_tech);
+
+	AST_TEST_REGISTER(test_features_channel_dtmf);
+	AST_TEST_REGISTER(test_features_channel_interval);
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Bridge Features Unit Tests");

Propchange: trunk/tests/test_channel_feature_hooks.c
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: trunk/tests/test_channel_feature_hooks.c
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: trunk/tests/test_channel_feature_hooks.c
------------------------------------------------------------------------------
    svn:mime-type = text/plain




More information about the asterisk-commits mailing list