]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
Bridging: Allow channels to define bridging hooks
authorKinsey Moore <kmoore@digium.com>
Thu, 26 Jun 2014 12:43:47 +0000 (12:43 +0000)
committerKinsey Moore <kmoore@digium.com>
Thu, 26 Jun 2014 12:43:47 +0000 (12:43 +0000)
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/

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

include/asterisk/bridge_features.h
include/asterisk/channel.h
main/bridge.c
main/bridge_channel.c
main/channel.c
tests/test_channel_feature_hooks.c [new file with mode: 0644]

index 2f89be9b2193c284bb2968012553e80bd7bb9936..dddc9b0431a6893f5634953748d1e5dcc37ca84e 100644 (file)
@@ -721,6 +721,14 @@ int ast_bridge_features_set_limits(struct ast_bridge_features *features, struct
  */
 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
  *
index 505e4bac9e1f960e7bce680e69b35f8fc40bcd01..dec19cba0198784440b4facc24d328090aae1e8d 100644 (file)
@@ -4488,4 +4488,44 @@ int ast_channel_unsuppress(struct ast_channel *chan, unsigned int direction, enu
  */
 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 */
index 35287e278115ad2156ba64447e2245c0844c1f4b..41053eb8af6bc9b1cf1c234dde907503852e70a9 100644 (file)
@@ -3327,6 +3327,64 @@ static int bridge_dtmf_hook_sort(const void *obj_left, const void *obj_right, in
        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)
 {
index 9b1b5737e73f71f82e5ecb5c903826dd28610cf3..7d1c65367a82c41b5b356d1163433c33d4bee10c 100644 (file)
@@ -2187,6 +2187,7 @@ static void bridge_channel_event_join_leave(struct ast_bridge_channel *bridge_ch
 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 @@ int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel)
         */
        ast_bridge_lock(bridge_channel->bridge);
 
-       /* Make sure we're still good to be put into a bridge */
        ast_channel_lock(bridge_channel->chan);
+       /* Make sure we're still good to be put into a bridge */
        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 @@ int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel)
                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 */
index 04c396a7195a1bbce76f1c7c2ee936387115235e..7d9f048379adeb275366717c498b24d6b3fabc1a 100644 (file)
@@ -10453,3 +10453,71 @@ void ast_channel_end_dtmf(struct ast_channel *chan, char digit, struct timeval s
        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);
+}
diff --git a/tests/test_channel_feature_hooks.c b/tests/test_channel_feature_hooks.c
new file mode 100644 (file)
index 0000000..2fc9765
--- /dev/null
@@ -0,0 +1,324 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2014, Digium, Inc.
+ *
+ * Kinsey Moore <kmoore@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@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");