summaryrefslogtreecommitdiff
path: root/main
diff options
context:
space:
mode:
authorRichard Mudgett <rmudgett@digium.com>2013-05-21 18:00:22 +0000
committerRichard Mudgett <rmudgett@digium.com>2013-05-21 18:00:22 +0000
commit3d63833bd6c869b7efa383e8dea14be1a6eff998 (patch)
tree34957dd051b8f67c7cc58a510e24ee3873a61ad4 /main
parente1e1cc2deefb92f8b43825f1f34e619354737842 (diff)
Merge in the bridge_construction branch to make the system use the Bridging API.
Breaks many things until they can be reworked. A partial list: chan_agent chan_dahdi, chan_misdn, chan_iax2 native bridging app_queue COLP updates DTMF attended transfers Protocol attended transfers git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@389378 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'main')
-rw-r--r--main/abstract_jb.c309
-rw-r--r--main/asterisk.c20
-rw-r--r--main/bridging.c5675
-rw-r--r--main/bridging_basic.c159
-rw-r--r--main/bridging_roles.c462
-rw-r--r--main/channel.c182
-rw-r--r--main/channel_internal_api.c13
-rw-r--r--main/cli.c45
-rw-r--r--main/config_options.c5
-rw-r--r--main/core_local.c775
-rw-r--r--main/core_unreal.c855
-rw-r--r--main/features.c1920
-rw-r--r--main/frame.c8
-rw-r--r--main/manager.c207
-rw-r--r--main/manager_bridging.c470
-rw-r--r--main/manager_channels.c93
-rw-r--r--main/parking.c191
-rw-r--r--main/pbx.c11
-rw-r--r--main/rtp_engine.c669
-rw-r--r--main/stasis_bridging.c358
-rw-r--r--main/strings.c1
21 files changed, 9275 insertions, 3153 deletions
diff --git a/main/abstract_jb.c b/main/abstract_jb.c
index 88a9b8e91..6e20b86cb 100644
--- a/main/abstract_jb.c
+++ b/main/abstract_jb.c
@@ -41,6 +41,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/channel.h"
#include "asterisk/term.h"
#include "asterisk/utils.h"
+#include "asterisk/pbx.h"
+#include "asterisk/timing.h"
#include "asterisk/abstract_jb.h"
#include "fixedjitterbuf.h"
@@ -567,6 +569,13 @@ int ast_jb_read_conf(struct ast_jb_conf *conf, const char *varname, const char *
return 0;
}
+void ast_jb_enable_for_channel(struct ast_channel *chan)
+{
+ struct ast_jb_conf conf = ast_channel_jb(chan)->conf;
+ if (ast_test_flag(&conf, AST_JB_ENABLED)) {
+ ast_jb_create_framehook(chan, &conf, 1);
+ }
+}
void ast_jb_configure(struct ast_channel *chan, const struct ast_jb_conf *conf)
{
@@ -800,3 +809,303 @@ const struct ast_jb_impl *ast_jb_get_impl(enum ast_jb_type type)
}
return NULL;
}
+
+#define DEFAULT_TIMER_INTERVAL 20
+#define DEFAULT_SIZE 200
+#define DEFAULT_TARGET_EXTRA 40
+#define DEFAULT_RESYNC 1000
+#define DEFAULT_TYPE AST_JB_FIXED
+
+struct jb_framedata {
+ const struct ast_jb_impl *jb_impl;
+ struct ast_jb_conf jb_conf;
+ struct timeval start_tv;
+ struct ast_format last_format;
+ struct ast_timer *timer;
+ int timer_interval; /* ms between deliveries */
+ int timer_fd;
+ int first;
+ void *jb_obj;
+};
+
+static void jb_framedata_destroy(struct jb_framedata *framedata)
+{
+ if (framedata->timer) {
+ ast_timer_close(framedata->timer);
+ framedata->timer = NULL;
+ }
+ if (framedata->jb_impl && framedata->jb_obj) {
+ struct ast_frame *f;
+ while (framedata->jb_impl->remove(framedata->jb_obj, &f) == AST_JB_IMPL_OK) {
+ ast_frfree(f);
+ }
+ framedata->jb_impl->destroy(framedata->jb_obj);
+ framedata->jb_obj = NULL;
+ }
+ ast_free(framedata);
+}
+
+void ast_jb_conf_default(struct ast_jb_conf *conf)
+{
+ conf->max_size = DEFAULT_SIZE;
+ conf->resync_threshold = DEFAULT_RESYNC;
+ ast_copy_string(conf->impl, "fixed", sizeof(conf->impl));
+ conf->target_extra = DEFAULT_TARGET_EXTRA;
+}
+
+static void datastore_destroy_cb(void *data) {
+ ast_free(data);
+ ast_debug(1, "JITTERBUFFER datastore destroyed\n");
+}
+
+static const struct ast_datastore_info jb_datastore = {
+ .type = "jitterbuffer",
+ .destroy = datastore_destroy_cb
+};
+
+static void hook_destroy_cb(void *framedata)
+{
+ ast_debug(1, "JITTERBUFFER hook destroyed\n");
+ jb_framedata_destroy((struct jb_framedata *) framedata);
+}
+
+static struct ast_frame *hook_event_cb(struct ast_channel *chan, struct ast_frame *frame, enum ast_framehook_event event, void *data)
+{
+ struct jb_framedata *framedata = data;
+ struct timeval now_tv;
+ unsigned long now;
+ int putframe = 0; /* signifies if audio frame was placed into the buffer or not */
+
+ switch (event) {
+ case AST_FRAMEHOOK_EVENT_READ:
+ break;
+ case AST_FRAMEHOOK_EVENT_ATTACHED:
+ case AST_FRAMEHOOK_EVENT_DETACHED:
+ case AST_FRAMEHOOK_EVENT_WRITE:
+ return frame;
+ }
+
+ if (ast_channel_fdno(chan) == AST_JITTERBUFFER_FD && framedata->timer) {
+ if (ast_timer_ack(framedata->timer, 1) < 0) {
+ ast_log(LOG_ERROR, "Failed to acknowledge timer in jitter buffer\n");
+ return frame;
+ }
+ }
+
+ if (!frame) {
+ return frame;
+ }
+
+ now_tv = ast_tvnow();
+ now = ast_tvdiff_ms(now_tv, framedata->start_tv);
+
+ if (frame->frametype == AST_FRAME_VOICE) {
+ int res;
+ struct ast_frame *jbframe;
+
+ if (!ast_test_flag(frame, AST_FRFLAG_HAS_TIMING_INFO) || frame->len < 2 || frame->ts < 0) {
+ /* only frames with timing info can enter the jitterbuffer */
+ return frame;
+ }
+
+ jbframe = ast_frisolate(frame);
+ ast_format_copy(&framedata->last_format, &frame->subclass.format);
+
+ if (frame->len && (frame->len != framedata->timer_interval)) {
+ framedata->timer_interval = frame->len;
+ ast_timer_set_rate(framedata->timer, 1000 / framedata->timer_interval);
+ }
+ if (!framedata->first) {
+ framedata->first = 1;
+ res = framedata->jb_impl->put_first(framedata->jb_obj, jbframe, now);
+ } else {
+ res = framedata->jb_impl->put(framedata->jb_obj, jbframe, now);
+ }
+ if (res == AST_JB_IMPL_OK) {
+ frame = &ast_null_frame;
+ }
+ putframe = 1;
+ }
+
+ if (frame->frametype == AST_FRAME_NULL) {
+ int res;
+ long next = framedata->jb_impl->next(framedata->jb_obj);
+
+ /* If now is earlier than the next expected output frame
+ * from the jitterbuffer we may choose to pass on retrieving
+ * a frame during this read iteration. The only exception
+ * to this rule is when an audio frame is placed into the buffer
+ * and the time for the next frame to come out of the buffer is
+ * at least within the timer_interval of the next output frame. By
+ * doing this we are able to feed off the timing of the input frames
+ * and only rely on our jitterbuffer timer when frames are dropped.
+ * During testing, this hybrid form of timing gave more reliable results. */
+ if (now < next) {
+ long int diff = next - now;
+ if (!putframe) {
+ return frame;
+ } else if (diff >= framedata->timer_interval) {
+ return frame;
+ }
+ }
+
+ res = framedata->jb_impl->get(framedata->jb_obj, &frame, now, framedata->timer_interval);
+ switch (res) {
+ case AST_JB_IMPL_OK:
+ /* got it, and pass it through */
+ break;
+ case AST_JB_IMPL_DROP:
+ ast_frfree(frame);
+ frame = &ast_null_frame;
+ break;
+ case AST_JB_IMPL_INTERP:
+ if (framedata->last_format.id) {
+ struct ast_frame tmp = { 0, };
+ tmp.frametype = AST_FRAME_VOICE;
+ ast_format_copy(&tmp.subclass.format, &framedata->last_format);
+ /* example: 8000hz / (1000 / 20ms) = 160 samples */
+ tmp.samples = ast_format_rate(&framedata->last_format) / (1000 / framedata->timer_interval);
+ tmp.delivery = ast_tvadd(framedata->start_tv, ast_samp2tv(next, 1000));
+ tmp.offset = AST_FRIENDLY_OFFSET;
+ tmp.src = "func_jitterbuffer interpolation";
+ frame = ast_frdup(&tmp);
+ break;
+ }
+ /* else fall through */
+ case AST_JB_IMPL_NOFRAME:
+ frame = &ast_null_frame;
+ break;
+ }
+ }
+
+ if (frame->frametype == AST_FRAME_CONTROL) {
+ switch(frame->subclass.integer) {
+ case AST_CONTROL_SRCUPDATE:
+ case AST_CONTROL_SRCCHANGE:
+ framedata->jb_impl->force_resync(framedata->jb_obj);
+ break;
+ default:
+ break;
+ }
+ }
+
+ return frame;
+}
+
+/* set defaults */
+static int jb_framedata_init(struct jb_framedata *framedata, struct ast_jb_conf *jb_conf)
+{
+ int jb_impl_type = DEFAULT_TYPE;
+ /* Initialize defaults */
+ framedata->timer_fd = -1;
+ memcpy(&framedata->jb_conf, jb_conf, sizeof(*jb_conf));
+
+ /* Figure out implementation type from the configuration implementation string */
+ if (!ast_strlen_zero(jb_conf->impl)) {
+ if (!strcasecmp(jb_conf->impl, "fixed")) {
+ jb_impl_type = AST_JB_FIXED;
+ } else if (!strcasecmp(jb_conf->impl, "adaptive")) {
+ jb_impl_type = AST_JB_ADAPTIVE;
+ } else {
+ ast_log(LOG_WARNING, "Unknown Jitterbuffer type %s. Failed to create jitterbuffer.\n", jb_conf->impl);
+ return -1;
+ }
+ }
+
+ if (!(framedata->jb_impl = ast_jb_get_impl(jb_impl_type))) {
+ return -1;
+ }
+
+ if (!(framedata->timer = ast_timer_open())) {
+ return -1;
+ }
+
+ framedata->timer_fd = ast_timer_fd(framedata->timer);
+ framedata->timer_interval = DEFAULT_TIMER_INTERVAL;
+ ast_timer_set_rate(framedata->timer, 1000 / framedata->timer_interval);
+ framedata->start_tv = ast_tvnow();
+
+ framedata->jb_obj = framedata->jb_impl->create(&framedata->jb_conf);
+ return 0;
+}
+
+
+void ast_jb_create_framehook(struct ast_channel *chan, struct ast_jb_conf *jb_conf, int prefer_existing)
+{
+ struct jb_framedata *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 disabled, strip any existing jitterbuffer and don't replace it. */
+ if (!strcasecmp(jb_conf->impl, "disabled")) {
+ int *id;
+ ast_channel_lock(chan);
+ if ((datastore = ast_channel_datastore_find(chan, &jb_datastore, NULL))) {
+ id = datastore->data;
+ ast_framehook_detach(chan, *id);
+ ast_channel_datastore_remove(chan, datastore);
+ }
+ ast_channel_unlock(chan);
+ return;
+ }
+
+ if (!(framedata = ast_calloc(1, sizeof(*framedata)))) {
+ return;
+ }
+
+ if (jb_framedata_init(framedata, jb_conf)) {
+ jb_framedata_destroy(framedata);
+ return;
+ }
+
+ interface.data = framedata;
+
+ ast_channel_lock(chan);
+ i = ast_framehook_attach(chan, &interface);
+ if (i >= 0) {
+ int *id;
+ if ((datastore = ast_channel_datastore_find(chan, &jb_datastore, NULL))) {
+ /* There is already a jitterbuffer on the channel. */
+ if (prefer_existing) {
+ /* We prefer the existing jitterbuffer, so remove the new one and keep the old one. */
+ ast_framehook_detach(chan, i);
+ ast_channel_unlock(chan);
+ return;
+ }
+ /* We prefer the new jitterbuffer, so strip the old one. */
+ id = datastore->data;
+ ast_framehook_detach(chan, *id);
+ ast_channel_datastore_remove(chan, datastore);
+ }
+
+ if (!(datastore = ast_datastore_alloc(&jb_datastore, NULL))) {
+ ast_framehook_detach(chan, i);
+ ast_channel_unlock(chan);
+ return;
+ }
+
+ if (!(id = ast_calloc(1, sizeof(int)))) {
+ ast_datastore_free(datastore);
+ ast_framehook_detach(chan, i);
+ ast_channel_unlock(chan);
+ return;
+ }
+
+ *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_set_fd(chan, AST_JITTERBUFFER_FD, framedata->timer_fd);
+ } else {
+ jb_framedata_destroy(framedata);
+ framedata = NULL;
+ }
+ ast_channel_unlock(chan);
+
+ return;
+}
diff --git a/main/asterisk.c b/main/asterisk.c
index 933aae63d..d8062d3b1 100644
--- a/main/asterisk.c
+++ b/main/asterisk.c
@@ -4277,11 +4277,6 @@ int main(int argc, char *argv[])
ast_http_init(); /* Start the HTTP server, if needed */
- if (init_manager()) {
- printf("%s", term_quit());
- exit(1);
- }
-
if (ast_cdr_engine_init()) {
printf("%s", term_quit());
exit(1);
@@ -4330,6 +4325,16 @@ int main(int argc, char *argv[])
exit(1);
}
+ if (ast_bridging_init()) {
+ printf("%s", term_quit());
+ exit(1);
+ }
+
+ if (init_manager()) {
+ printf("%s", term_quit());
+ exit(1);
+ }
+
if (ast_enum_init()) {
printf("%s", term_quit());
exit(1);
@@ -4340,6 +4345,11 @@ int main(int argc, char *argv[])
exit(1);
}
+ if (ast_local_init()) {
+ printf("%s", term_quit());
+ exit(1);
+ }
+
if ((moduleresult = load_modules(0))) { /* Load modules */
printf("%s", term_quit());
exit(moduleresult == -2 ? 2 : 1);
diff --git a/main/bridging.c b/main/bridging.c
index 875e8503c..f332dfab2 100644
--- a/main/bridging.c
+++ b/main/bridging.c
@@ -40,12 +40,28 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/lock.h"
#include "asterisk/linkedlists.h"
#include "asterisk/bridging.h"
+#include "asterisk/bridging_basic.h"
#include "asterisk/bridging_technology.h"
+#include "asterisk/stasis_bridging.h"
#include "asterisk/app.h"
#include "asterisk/file.h"
#include "asterisk/module.h"
#include "asterisk/astobj2.h"
+#include "asterisk/pbx.h"
#include "asterisk/test.h"
+#include "asterisk/_private.h"
+
+#include "asterisk/heap.h"
+#include "asterisk/say.h"
+#include "asterisk/timing.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/features.h"
+#include "asterisk/cli.h"
+#include "asterisk/parking.h"
+
+/*! All bridges container. */
+static struct ao2_container *bridges;
static AST_RWLIST_HEAD_STATIC(bridge_technologies, ast_bridge_technology);
@@ -56,6 +72,8 @@ static AST_RWLIST_HEAD_STATIC(bridge_technologies, ast_bridge_technology);
#define BRIDGE_ARRAY_GROW 32
static void cleanup_video_mode(struct ast_bridge *bridge);
+static int bridge_make_compatible(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
+static void bridge_features_remove_on_pull(struct ast_bridge_features *features);
/*! Default DTMF keys for built in features */
static char builtin_features_dtmf[AST_BRIDGE_BUILTIN_END][MAXIMUM_DTMF_FEATURE_STRING];
@@ -63,13 +81,76 @@ static char builtin_features_dtmf[AST_BRIDGE_BUILTIN_END][MAXIMUM_DTMF_FEATURE_S
/*! Function handlers for the built in features */
static void *builtin_features_handlers[AST_BRIDGE_BUILTIN_END];
+/*! Function handlers for built in interval features */
+static ast_bridge_builtin_set_limits_fn builtin_interval_handlers[AST_BRIDGE_BUILTIN_INTERVAL_END];
+
+/*! Bridge manager service request */
+struct bridge_manager_request {
+ /*! List of bridge service requests. */
+ AST_LIST_ENTRY(bridge_manager_request) node;
+ /*! Refed bridge requesting service. */
+ struct ast_bridge *bridge;
+};
+
+struct bridge_manager_controller {
+ /*! Condition, used to wake up the bridge manager thread. */
+ ast_cond_t cond;
+ /*! Queue of bridge service requests. */
+ AST_LIST_HEAD_NOLOCK(, bridge_manager_request) service_requests;
+ /*! Manager thread */
+ pthread_t thread;
+ /*! TRUE if the manager needs to stop. */
+ unsigned int stop:1;
+};
+
+/*! Bridge manager controller. */
+static struct bridge_manager_controller *bridge_manager;
+
+/*!
+ * \internal
+ * \brief Request service for a bridge from the bridge manager.
+ * \since 12.0.0
+ *
+ * \param bridge Requesting service.
+ *
+ * \return Nothing
+ */
+static void bridge_manager_service_req(struct ast_bridge *bridge)
+{
+ struct bridge_manager_request *request;
+
+ ao2_lock(bridge_manager);
+ if (bridge_manager->stop) {
+ ao2_unlock(bridge_manager);
+ return;
+ }
+
+ /* Create the service request. */
+ request = ast_calloc(1, sizeof(*request));
+ if (!request) {
+ /* Well. This isn't good. */
+ ao2_unlock(bridge_manager);
+ return;
+ }
+ ao2_ref(bridge, +1);
+ request->bridge = bridge;
+
+ /* Put request into the queue and wake the bridge manager. */
+ AST_LIST_INSERT_TAIL(&bridge_manager->service_requests, request, node);
+ ast_cond_signal(&bridge_manager->cond);
+ ao2_unlock(bridge_manager);
+}
+
int __ast_bridge_technology_register(struct ast_bridge_technology *technology, struct ast_module *module)
{
- struct ast_bridge_technology *current = NULL;
+ struct ast_bridge_technology *current;
/* Perform a sanity check to make sure the bridge technology conforms to our needed requirements */
- if (ast_strlen_zero(technology->name) || !technology->capabilities || !technology->write) {
- ast_log(LOG_WARNING, "Bridge technology %s failed registration sanity check.\n", technology->name);
+ if (ast_strlen_zero(technology->name)
+ || !technology->capabilities
+ || !technology->write) {
+ ast_log(LOG_WARNING, "Bridge technology %s failed registration sanity check.\n",
+ technology->name);
return -1;
}
@@ -78,7 +159,8 @@ int __ast_bridge_technology_register(struct ast_bridge_technology *technology, s
/* Look for duplicate bridge technology already using this name, or already registered */
AST_RWLIST_TRAVERSE(&bridge_technologies, current, entry) {
if ((!strcasecmp(current->name, technology->name)) || (current == technology)) {
- ast_log(LOG_WARNING, "A bridge technology of %s already claims to exist in our world.\n", technology->name);
+ ast_log(LOG_WARNING, "A bridge technology of %s already claims to exist in our world.\n",
+ technology->name);
AST_RWLIST_UNLOCK(&bridge_technologies);
return -1;
}
@@ -99,7 +181,7 @@ int __ast_bridge_technology_register(struct ast_bridge_technology *technology, s
int ast_bridge_technology_unregister(struct ast_bridge_technology *technology)
{
- struct ast_bridge_technology *current = NULL;
+ struct ast_bridge_technology *current;
AST_RWLIST_WRLOCK(&bridge_technologies);
@@ -118,127 +200,192 @@ int ast_bridge_technology_unregister(struct ast_bridge_technology *technology)
return current ? 0 : -1;
}
+void ast_bridge_channel_lock_bridge(struct ast_bridge_channel *bridge_channel)
+{
+ struct ast_bridge *bridge;
+
+ for (;;) {
+ /* Safely get the bridge pointer */
+ ast_bridge_channel_lock(bridge_channel);
+ bridge = bridge_channel->bridge;
+ ao2_ref(bridge, +1);
+ ast_bridge_channel_unlock(bridge_channel);
+
+ /* Lock the bridge and see if it is still the bridge we need to lock. */
+ ast_bridge_lock(bridge);
+ if (bridge == bridge_channel->bridge) {
+ ao2_ref(bridge, -1);
+ return;
+ }
+ ast_bridge_unlock(bridge);
+ ao2_ref(bridge, -1);
+ }
+}
+
static void bridge_channel_poke(struct ast_bridge_channel *bridge_channel)
{
- ao2_lock(bridge_channel);
- pthread_kill(bridge_channel->thread, SIGURG);
- ast_cond_signal(&bridge_channel->cond);
- ao2_unlock(bridge_channel);
+ if (!pthread_equal(pthread_self(), bridge_channel->thread)) {
+ while (bridge_channel->waiting) {
+ pthread_kill(bridge_channel->thread, SIGURG);
+ sched_yield();
+ }
+ }
}
-/*! \note This function assumes the bridge_channel is locked. */
-static void ast_bridge_change_state_nolock(struct ast_bridge_channel *bridge_channel, enum ast_bridge_channel_state new_state)
+void ast_bridge_change_state_nolock(struct ast_bridge_channel *bridge_channel, enum ast_bridge_channel_state new_state)
{
+/* BUGBUG need cause code for the bridge_channel leaving the bridge. */
+ if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+ return;
+ }
+
+ ast_debug(1, "Setting %p(%s) state from:%d to:%d\n",
+ bridge_channel, ast_channel_name(bridge_channel->chan), bridge_channel->state,
+ new_state);
+
/* Change the state on the bridge channel */
bridge_channel->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);
- ast_cond_signal(&bridge_channel->cond);
- }
+ bridge_channel_poke(bridge_channel);
}
void ast_bridge_change_state(struct ast_bridge_channel *bridge_channel, enum ast_bridge_channel_state new_state)
{
- ao2_lock(bridge_channel);
+ ast_bridge_channel_lock(bridge_channel);
ast_bridge_change_state_nolock(bridge_channel, new_state);
- ao2_unlock(bridge_channel);
-}
-
-/*!
- * \brief Helper function to poke the bridge thread
- *
- * \note This function assumes the bridge is locked.
- */
-static void bridge_poke(struct ast_bridge *bridge)
-{
- /* Poke the thread just in case */
- if (bridge->thread != AST_PTHREADT_NULL && bridge->thread != AST_PTHREADT_STOP) {
- pthread_kill(bridge->thread, SIGURG);
- }
+ ast_bridge_channel_unlock(bridge_channel);
}
/*!
* \internal
- * \brief Stop the bridge.
+ * \brief Put an action onto the specified bridge. Don't dup the action frame.
* \since 12.0.0
*
- * \note This function assumes the bridge is locked.
+ * \param bridge What to queue the action on.
+ * \param action What to do.
*
* \return Nothing
*/
-static void bridge_stop(struct ast_bridge *bridge)
+static void bridge_queue_action_nodup(struct ast_bridge *bridge, struct ast_frame *action)
{
- pthread_t thread = bridge->thread;
+ ast_debug(1, "Bridge %s: queueing action type:%d sub:%d\n",
+ bridge->uniqueid, action->frametype, action->subclass.integer);
- bridge->stop = 1;
- bridge_poke(bridge);
- ao2_unlock(bridge);
- pthread_join(thread, NULL);
- ao2_lock(bridge);
+ ast_bridge_lock(bridge);
+ AST_LIST_INSERT_TAIL(&bridge->action_queue, action, frame_list);
+ ast_bridge_unlock(bridge);
+ bridge_manager_service_req(bridge);
}
-/*!
- * \brief Helper function to add a channel to the bridge array
- *
- * \note This function assumes the bridge is locked.
- */
-static void bridge_array_add(struct ast_bridge *bridge, struct ast_channel *chan)
+int ast_bridge_queue_action(struct ast_bridge *bridge, struct ast_frame *action)
{
- /* We have to make sure the bridge thread is not using the bridge array before messing with it */
- while (bridge->waiting) {
- bridge_poke(bridge);
- sched_yield();
+ struct ast_frame *dup;
+
+ dup = ast_frdup(action);
+ if (!dup) {
+ return -1;
}
+ bridge_queue_action_nodup(bridge, dup);
+ return 0;
+}
- bridge->array[bridge->array_num++] = chan;
+int ast_bridge_channel_queue_frame(struct ast_bridge_channel *bridge_channel, struct ast_frame *fr)
+{
+ struct ast_frame *dup;
+ char nudge = 0;
+
+ if (bridge_channel->suspended
+ /* Also defer DTMF frames. */
+ && fr->frametype != AST_FRAME_DTMF_BEGIN
+ && fr->frametype != AST_FRAME_DTMF_END
+ && !ast_is_deferrable_frame(fr)) {
+ /* Drop non-deferable frames when suspended. */
+ return 0;
+ }
- ast_debug(1, "Added channel %s(%p) to bridge array on %p, new count is %d\n",
- ast_channel_name(chan), chan, bridge, (int) bridge->array_num);
+ dup = ast_frdup(fr);
+ if (!dup) {
+ return -1;
+ }
- /* If the next addition of a channel will exceed our array size grow it out */
- if (bridge->array_num == bridge->array_size) {
- struct ast_channel **new_array;
+ ast_bridge_channel_lock(bridge_channel);
+ if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+ /* Drop frames on channels leaving the bridge. */
+ ast_bridge_channel_unlock(bridge_channel);
+ ast_frfree(dup);
+ return 0;
+ }
- ast_debug(1, "Growing bridge array on %p from %d to %d\n",
- bridge, (int) bridge->array_size, (int) bridge->array_size + BRIDGE_ARRAY_GROW);
- new_array = ast_realloc(bridge->array,
- (bridge->array_size + BRIDGE_ARRAY_GROW) * sizeof(*bridge->array));
- if (!new_array) {
- return;
- }
- bridge->array = new_array;
- bridge->array_size += BRIDGE_ARRAY_GROW;
+ AST_LIST_INSERT_TAIL(&bridge_channel->wr_queue, dup, frame_list);
+ if (write(bridge_channel->alert_pipe[1], &nudge, sizeof(nudge)) != sizeof(nudge)) {
+ ast_log(LOG_ERROR, "We couldn't write alert pipe for %p(%s)... something is VERY wrong\n",
+ bridge_channel, ast_channel_name(bridge_channel->chan));
}
+ ast_bridge_channel_unlock(bridge_channel);
+ return 0;
}
-/*! \brief Helper function to remove a channel from the bridge array
- *
- * \note This function assumes the bridge is locked.
- */
-static void bridge_array_remove(struct ast_bridge *bridge, struct ast_channel *chan)
+void ast_bridge_channel_queue_action_data(struct ast_bridge_channel *bridge_channel, enum ast_bridge_action_type action, const void *data, size_t datalen)
{
- int idx;
+ struct ast_frame frame = {
+ .frametype = AST_FRAME_BRIDGE_ACTION,
+ .subclass.integer = action,
+ .datalen = datalen,
+ .data.ptr = (void *) data,
+ };
- /* We have to make sure the bridge thread is not using the bridge array before messing with it */
- while (bridge->waiting) {
- bridge_poke(bridge);
- sched_yield();
- }
+ ast_bridge_channel_queue_frame(bridge_channel, &frame);
+}
- for (idx = 0; idx < bridge->array_num; ++idx) {
- if (bridge->array[idx] == chan) {
- --bridge->array_num;
- bridge->array[idx] = bridge->array[bridge->array_num];
- ast_debug(1, "Removed channel %p from bridge array on %p, new count is %d\n",
- chan, bridge, (int) bridge->array_num);
- break;
+void ast_bridge_channel_queue_control_data(struct ast_bridge_channel *bridge_channel, enum ast_control_frame_type control, const void *data, size_t datalen)
+{
+ struct ast_frame frame = {
+ .frametype = AST_FRAME_CONTROL,
+ .subclass.integer = control,
+ .datalen = datalen,
+ .data.ptr = (void *) data,
+ };
+
+ ast_bridge_channel_queue_frame(bridge_channel, &frame);
+}
+
+void ast_bridge_channel_restore_formats(struct ast_bridge_channel *bridge_channel)
+{
+ /* Restore original formats of the channel as they came in */
+ if (ast_format_cmp(ast_channel_readformat(bridge_channel->chan), &bridge_channel->read_format) == AST_FORMAT_CMP_NOT_EQUAL) {
+ ast_debug(1, "Bridge is returning %p(%s) to read format %s\n",
+ bridge_channel, ast_channel_name(bridge_channel->chan),
+ ast_getformatname(&bridge_channel->read_format));
+ if (ast_set_read_format(bridge_channel->chan, &bridge_channel->read_format)) {
+ ast_debug(1, "Bridge failed to return %p(%s) to read format %s\n",
+ bridge_channel, ast_channel_name(bridge_channel->chan),
+ ast_getformatname(&bridge_channel->read_format));
+ }
+ }
+ if (ast_format_cmp(ast_channel_writeformat(bridge_channel->chan), &bridge_channel->write_format) == AST_FORMAT_CMP_NOT_EQUAL) {
+ ast_debug(1, "Bridge is returning %p(%s) to write format %s\n",
+ bridge_channel, ast_channel_name(bridge_channel->chan),
+ ast_getformatname(&bridge_channel->write_format));
+ if (ast_set_write_format(bridge_channel->chan, &bridge_channel->write_format)) {
+ ast_debug(1, "Bridge failed to return %p(%s) to write format %s\n",
+ bridge_channel, ast_channel_name(bridge_channel->chan),
+ ast_getformatname(&bridge_channel->write_format));
}
}
}
-/*! \brief Helper function to find a bridge channel given a channel */
+/*!
+ * \internal
+ * \brief Helper function to find a bridge channel given a channel.
+ *
+ * \param bridge What to search
+ * \param chan What to search for.
+ *
+ * \note On entry, bridge is already locked.
+ *
+ * \retval bridge_channel if channel is in the bridge.
+ * \retval NULL if not in bridge.
+ */
static struct ast_bridge_channel *find_bridge_channel(struct ast_bridge *bridge, struct ast_channel *chan)
{
struct ast_bridge_channel *bridge_channel;
@@ -254,241 +401,693 @@ static struct ast_bridge_channel *find_bridge_channel(struct ast_bridge *bridge,
/*!
* \internal
- * \brief Force out all channels that are not already going out of the bridge.
+ * \brief Dissolve the bridge.
* \since 12.0.0
*
* \param bridge Bridge to eject all channels
*
+ * \details
+ * Force out all channels that are not already going out of the
+ * bridge. Any new channels joining will leave immediately.
+ *
* \note On entry, bridge is already locked.
*
* \return Nothing
*/
-static void bridge_force_out_all(struct ast_bridge *bridge)
+static void bridge_dissolve(struct ast_bridge *bridge)
{
struct ast_bridge_channel *bridge_channel;
+ struct ast_frame action = {
+ .frametype = AST_FRAME_BRIDGE_ACTION,
+ .subclass.integer = AST_BRIDGE_ACTION_DEFERRED_DISSOLVING,
+ };
+
+ if (bridge->dissolved) {
+ return;
+ }
+ bridge->dissolved = 1;
+
+ ast_debug(1, "Bridge %s: dissolving bridge\n", bridge->uniqueid);
+/* BUGBUG need a cause code on the bridge for the later ejected channels. */
AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
- ao2_lock(bridge_channel);
- switch (bridge_channel->state) {
- case AST_BRIDGE_CHANNEL_STATE_END:
- case AST_BRIDGE_CHANNEL_STATE_HANGUP:
- case AST_BRIDGE_CHANNEL_STATE_DEPART:
- break;
- default:
- ast_bridge_change_state_nolock(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
- break;
+ ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+ }
+
+ /* Must defer dissolving bridge because it is already locked. */
+ ast_bridge_queue_action(bridge, &action);
+}
+
+/*!
+ * \internal
+ * \brief Check if a bridge should dissolve and do it.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel causing the check.
+ *
+ * \note On entry, bridge_channel->bridge is already locked.
+ *
+ * \return Nothing
+ */
+static void bridge_dissolve_check(struct ast_bridge_channel *bridge_channel)
+{
+ struct ast_bridge *bridge = bridge_channel->bridge;
+
+ if (bridge->dissolved) {
+ return;
+ }
+
+ if (!bridge->num_channels
+ && ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_DISSOLVE_EMPTY)) {
+ /* Last channel leaving the bridge turns off the lights. */
+ bridge_dissolve(bridge);
+ return;
+ }
+
+ switch (bridge_channel->state) {
+ case AST_BRIDGE_CHANNEL_STATE_END:
+ /* Do we need to dissolve the bridge because this channel hung up? */
+ if (ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_DISSOLVE_HANGUP)
+ || (bridge_channel->features->usable
+ && ast_test_flag(&bridge_channel->features->feature_flags,
+ AST_BRIDGE_CHANNEL_FLAG_DISSOLVE_HANGUP))) {
+ bridge_dissolve(bridge);
+ return;
}
- ao2_unlock(bridge_channel);
+ break;
+ default:
+ break;
}
+/* BUGBUG need to implement AST_BRIDGE_CHANNEL_FLAG_LONELY support here */
}
-/*! \brief Internal function to see whether a bridge should dissolve, and if so do it */
-static void bridge_check_dissolve(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+/*!
+ * \internal
+ * \brief Pull the bridge channel out of its current bridge.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel to pull.
+ *
+ * \note On entry, bridge_channel->bridge is already locked.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_pull(struct ast_bridge_channel *bridge_channel)
{
- if (!ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_DISSOLVE)
- && (!bridge_channel->features
- || !bridge_channel->features->usable
- || !ast_test_flag(&bridge_channel->features->feature_flags, AST_BRIDGE_FLAG_DISSOLVE))) {
+ struct ast_bridge *bridge = bridge_channel->bridge;
+
+ if (!bridge_channel->in_bridge) {
return;
}
+ bridge_channel->in_bridge = 0;
+
+ ast_debug(1, "Bridge %s: pulling %p(%s)\n",
+ bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan));
+
+/* BUGBUG This is where incoming HOLD/UNHOLD memory should write UNHOLD into bridge. (if not local optimizing) */
+/* BUGBUG This is where incoming DTMF begin/end memory should write DTMF end into bridge. (if not local optimizing) */
+ if (!bridge_channel->just_joined) {
+ /* Tell the bridge technology we are leaving so they tear us down */
+ ast_debug(1, "Bridge %s: %p(%s) is leaving %s technology\n",
+ bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan),
+ bridge->technology->name);
+ if (bridge->technology->leave) {
+ bridge->technology->leave(bridge, bridge_channel);
+ }
+ }
- ast_debug(1, "Dissolving bridge %p\n", bridge);
- bridge_force_out_all(bridge);
+ /* Remove channel from the bridge */
+ if (!bridge_channel->suspended) {
+ --bridge->num_active;
+ }
+ --bridge->num_channels;
+ AST_LIST_REMOVE(&bridge->channels, bridge_channel, entry);
+ bridge->v_table->pull(bridge, bridge_channel);
+
+ ast_bridge_channel_clear_roles(bridge_channel);
+
+ bridge_dissolve_check(bridge_channel);
+
+ bridge->reconfigured = 1;
+ ast_bridge_publish_leave(bridge, bridge_channel->chan);
}
-/*! \brief Internal function to handle DTMF from a channel */
-static struct ast_frame *bridge_handle_dtmf(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+/*!
+ * \internal
+ * \brief Push the bridge channel into its specified bridge.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel to push.
+ *
+ * \note On entry, bridge_channel->bridge is already locked.
+ *
+ * \retval 0 on success.
+ * \retval -1 on failure. The channel did not get pushed.
+ */
+static int bridge_channel_push(struct ast_bridge_channel *bridge_channel)
{
- struct ast_bridge_features *features = (bridge_channel->features ? bridge_channel->features : &bridge->features);
- struct ast_bridge_features_hook *hook;
+ struct ast_bridge *bridge = bridge_channel->bridge;
+ struct ast_bridge_channel *swap;
+
+ ast_assert(!bridge_channel->in_bridge);
+
+ swap = find_bridge_channel(bridge, bridge_channel->swap);
+ bridge_channel->swap = NULL;
- /* If the features structure we grabbed is not usable immediately return the frame */
- if (!features->usable) {
- return frame;
+ if (swap) {
+ ast_debug(1, "Bridge %s: pushing %p(%s) by swapping with %p(%s)\n",
+ bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan),
+ swap, ast_channel_name(swap->chan));
+ } else {
+ ast_debug(1, "Bridge %s: pushing %p(%s)\n",
+ bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan));
+ }
+
+ /* Add channel to the bridge */
+ if (bridge->dissolved
+ || bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT
+ || (swap && swap->state != AST_BRIDGE_CHANNEL_STATE_WAIT)
+ || bridge->v_table->push(bridge, bridge_channel, swap)
+ || ast_bridge_channel_establish_roles(bridge_channel)) {
+ ast_debug(1, "Bridge %s: pushing %p(%s) into bridge failed\n",
+ bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan));
+ return -1;
}
+ bridge_channel->in_bridge = 1;
+ bridge_channel->just_joined = 1;
+ AST_LIST_INSERT_TAIL(&bridge->channels, bridge_channel, entry);
+ ++bridge->num_channels;
+ if (!bridge_channel->suspended) {
+ ++bridge->num_active;
+ }
+ if (swap) {
+ ast_bridge_change_state(swap, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+ bridge_channel_pull(swap);
+ }
+
+ bridge->reconfigured = 1;
+ ast_bridge_publish_enter(bridge, bridge_channel->chan);
+ return 0;
+}
+
+/*! \brief Internal function to handle DTMF from a channel */
+static struct ast_frame *bridge_handle_dtmf(struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+{
+ struct ast_bridge_features *features = bridge_channel->features;
+ struct ast_bridge_hook *hook;
+ char dtmf[2];
+/* BUGBUG the feature hook matching needs to be done here. Any matching feature hook needs to be queued onto the bridge_channel. Also the feature hook digit timeout needs to be handled. */
+/* BUGBUG the AMI atxfer action just sends DTMF end events to initiate DTMF atxfer and dial the extension. Another reason the DTMF hook matching needs rework. */
/* See if this DTMF matches the beginnings of any feature hooks, if so we switch to the feature state to either execute the feature or collect more DTMF */
- AST_LIST_TRAVERSE(&features->hooks, hook, entry) {
- if (hook->dtmf[0] == frame->subclass.integer) {
- ast_frfree(frame);
- frame = NULL;
- ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_FEATURE);
- break;
- }
+ dtmf[0] = frame->subclass.integer;
+ dtmf[1] = '\0';
+ hook = ao2_find(features->dtmf_hooks, dtmf, OBJ_PARTIAL_KEY);
+ if (hook) {
+ struct ast_frame action = {
+ .frametype = AST_FRAME_BRIDGE_ACTION,
+ .subclass.integer = AST_BRIDGE_ACTION_FEATURE,
+ };
+
+ ast_frfree(frame);
+ frame = NULL;
+ ast_bridge_channel_queue_frame(bridge_channel, &action);
+ ao2_ref(hook, -1);
}
return frame;
}
-/*! \brief Internal function used to determine whether a control frame should be dropped or not */
-static int bridge_drop_control_frame(int subclass)
+/*!
+ * \internal
+ * \brief Handle bridge hangup event.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel is hanging up.
+ *
+ * \return Nothing
+ */
+static void bridge_handle_hangup(struct ast_bridge_channel *bridge_channel)
{
- switch (subclass) {
- case AST_CONTROL_ANSWER:
- case -1:
- return 1;
- default:
- return 0;
+ struct ast_bridge_features *features = bridge_channel->features;
+ struct ast_bridge_hook *hook;
+ struct ao2_iterator iter;
+
+ /* Run any hangup hooks. */
+ iter = ao2_iterator_init(features->hangup_hooks, 0);
+ for (; (hook = ao2_iterator_next(&iter)); ao2_ref(hook, -1)) {
+ int failed;
+
+ failed = hook->callback(bridge_channel->bridge, bridge_channel, hook->hook_pvt);
+ if (failed) {
+ ast_debug(1, "Hangup hook %p is being removed from %p(%s)\n",
+ hook, bridge_channel, ast_channel_name(bridge_channel->chan));
+ ao2_unlink(features->hangup_hooks, hook);
+ }
}
+ ao2_iterator_destroy(&iter);
+
+ /* Default hangup action. */
+ ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
}
-void ast_bridge_notify_talking(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, int started_talking)
+static int bridge_channel_interval_ready(struct ast_bridge_channel *bridge_channel)
{
- if (started_talking) {
- ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_START_TALKING);
- } else {
- ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_STOP_TALKING);
- }
+ struct ast_bridge_features *features = bridge_channel->features;
+ struct ast_bridge_hook *hook;
+ int ready;
+
+ ast_heap_wrlock(features->interval_hooks);
+ hook = ast_heap_peek(features->interval_hooks, 1);
+ ready = hook && ast_tvdiff_ms(hook->parms.timer.trip_time, ast_tvnow()) <= 0;
+ ast_heap_unlock(features->interval_hooks);
+
+ return ready;
}
-void ast_bridge_handle_trip(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_channel *chan, int outfd)
+void ast_bridge_notify_talking(struct ast_bridge_channel *bridge_channel, int started_talking)
{
- /* If no bridge channel has been provided and the actual channel has been provided find it */
- if (chan && !bridge_channel) {
- bridge_channel = find_bridge_channel(bridge, chan);
- }
+ struct ast_frame action = {
+ .frametype = AST_FRAME_BRIDGE_ACTION,
+ .subclass.integer = started_talking
+ ? AST_BRIDGE_ACTION_TALKING_START : AST_BRIDGE_ACTION_TALKING_STOP,
+ };
+
+ ast_bridge_channel_queue_frame(bridge_channel, &action);
+}
+
+static void bridge_channel_write_frame(struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+{
+ ast_bridge_channel_lock_bridge(bridge_channel);
+/*
+ * BUGBUG need to implement a deferred write queue for when there is no peer channel in the bridge (yet or it was kicked).
+ *
+ * The tech decides if a frame needs to be pushed back for deferral.
+ * simple_bridge/native_bridge are likely the only techs that will do this.
+ */
+ bridge_channel->bridge->technology->write(bridge_channel->bridge, bridge_channel, frame);
+ ast_bridge_unlock(bridge_channel->bridge);
+}
- /* If a bridge channel with actual channel is present read a frame and handle it */
- if (chan && bridge_channel) {
- struct ast_frame *frame;
+void ast_bridge_channel_write_action_data(struct ast_bridge_channel *bridge_channel, enum ast_bridge_action_type action, const void *data, size_t datalen)
+{
+ struct ast_frame frame = {
+ .frametype = AST_FRAME_BRIDGE_ACTION,
+ .subclass.integer = action,
+ .datalen = datalen,
+ .data.ptr = (void *) data,
+ };
+
+ bridge_channel_write_frame(bridge_channel, &frame);
+}
- if (bridge->features.mute
- || (bridge_channel->features && bridge_channel->features->mute)) {
- frame = ast_read_noaudio(chan);
+void ast_bridge_channel_write_control_data(struct ast_bridge_channel *bridge_channel, enum ast_control_frame_type control, const void *data, size_t datalen)
+{
+ struct ast_frame frame = {
+ .frametype = AST_FRAME_CONTROL,
+ .subclass.integer = control,
+ .datalen = datalen,
+ .data.ptr = (void *) data,
+ };
+
+ bridge_channel_write_frame(bridge_channel, &frame);
+}
+
+static int run_app_helper(struct ast_channel *chan, const char *app_name, const char *app_args)
+{
+ int res = 0;
+
+ if (!strcasecmp("Gosub", app_name)) {
+ ast_app_exec_sub(NULL, chan, app_args, 0);
+ } else if (!strcasecmp("Macro", app_name)) {
+ ast_app_exec_macro(NULL, chan, app_args);
+ } else {
+ struct ast_app *app;
+
+ app = pbx_findapp(app_name);
+ if (!app) {
+ ast_log(LOG_WARNING, "Could not find application (%s)\n", app_name);
} else {
- frame = ast_read(chan);
- }
- /* This is pretty simple... see if they hung up */
- if (!frame || (frame->frametype == AST_FRAME_CONTROL && frame->subclass.integer == AST_CONTROL_HANGUP)) {
- /* Signal the thread that is handling the bridged channel that it should be ended */
- ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
- } else if (frame->frametype == AST_FRAME_CONTROL && bridge_drop_control_frame(frame->subclass.integer)) {
- ast_debug(1, "Dropping control frame %d from bridge channel %p\n",
- frame->subclass.integer, bridge_channel);
- } else if (frame->frametype == AST_FRAME_DTMF_BEGIN || frame->frametype == AST_FRAME_DTMF_END) {
- int dtmf_passthrough = bridge_channel->features ?
- bridge_channel->features->dtmf_passthrough :
- bridge->features.dtmf_passthrough;
-
- if (frame->frametype == AST_FRAME_DTMF_BEGIN) {
- frame = bridge_handle_dtmf(bridge, bridge_channel, frame);
- }
+ res = pbx_exec(chan, app, app_args);
+ }
+ }
+ return res;
+}
- if (frame && dtmf_passthrough) {
- bridge->technology->write(bridge, bridge_channel, frame);
- }
+void ast_bridge_channel_run_app(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class)
+{
+ if (moh_class) {
+ if (ast_strlen_zero(moh_class)) {
+ ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_HOLD,
+ NULL, 0);
} else {
- /* Simply write the frame out to the bridge technology if it still exists */
- bridge->technology->write(bridge, bridge_channel, frame);
+ ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_HOLD,
+ moh_class, strlen(moh_class) + 1);
}
+ }
+ if (run_app_helper(bridge_channel->chan, app_name, S_OR(app_args, ""))) {
+ /* Break the bridge if the app returns non-zero. */
+ bridge_handle_hangup(bridge_channel);
+ }
+ if (moh_class) {
+ ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_UNHOLD,
+ NULL, 0);
+ }
+}
- if (frame) {
- ast_frfree(frame);
+struct bridge_run_app {
+ /*! Offset into app_name[] where the MOH class name starts. (zero if no MOH) */
+ int moh_offset;
+ /*! Offset into app_name[] where the application argument string starts. (zero if no arguments) */
+ int app_args_offset;
+ /*! Application name to run. */
+ char app_name[0];
+};
+
+/*!
+ * \internal
+ * \brief Handle the run application bridge action.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel to run the application on.
+ * \param data Action frame data to run the application.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_run_app(struct ast_bridge_channel *bridge_channel, struct bridge_run_app *data)
+{
+ ast_bridge_channel_run_app(bridge_channel, data->app_name,
+ data->app_args_offset ? &data->app_name[data->app_args_offset] : NULL,
+ data->moh_offset ? &data->app_name[data->moh_offset] : NULL);
+}
+
+static void payload_helper_app(ast_bridge_channel_post_action_data post_it,
+ struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class)
+{
+ struct bridge_run_app *app_data;
+ size_t len_name = strlen(app_name) + 1;
+ size_t len_args = ast_strlen_zero(app_args) ? 0 : strlen(app_args) + 1;
+ size_t len_moh = !moh_class ? 0 : strlen(moh_class) + 1;
+ size_t len_data = sizeof(*app_data) + len_name + len_args + len_moh;
+
+ /* Fill in application run frame data. */
+ app_data = alloca(len_data);
+ app_data->app_args_offset = len_args ? len_name : 0;
+ app_data->moh_offset = len_moh ? len_name + len_args : 0;
+ strcpy(app_data->app_name, app_name);/* Safe */
+ if (len_args) {
+ strcpy(&app_data->app_name[app_data->app_args_offset], app_args);/* Safe */
+ }
+ if (moh_class) {
+ strcpy(&app_data->app_name[app_data->moh_offset], moh_class);/* Safe */
+ }
+
+ post_it(bridge_channel, AST_BRIDGE_ACTION_RUN_APP, app_data, len_data);
+}
+
+void ast_bridge_channel_write_app(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class)
+{
+ payload_helper_app(ast_bridge_channel_write_action_data,
+ bridge_channel, app_name, app_args, moh_class);
+}
+
+void ast_bridge_channel_queue_app(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class)
+{
+ payload_helper_app(ast_bridge_channel_queue_action_data,
+ bridge_channel, app_name, app_args, moh_class);
+}
+
+void ast_bridge_channel_playfile(struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class)
+{
+ if (moh_class) {
+ if (ast_strlen_zero(moh_class)) {
+ ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_HOLD,
+ NULL, 0);
+ } else {
+ ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_HOLD,
+ moh_class, strlen(moh_class) + 1);
}
- return;
+ }
+ if (custom_play) {
+ custom_play(bridge_channel, playfile);
+ } else {
+ ast_stream_and_wait(bridge_channel->chan, playfile, AST_DIGIT_NONE);
+ }
+ if (moh_class) {
+ ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_UNHOLD,
+ NULL, 0);
}
- /* If a file descriptor actually tripped pass it off to the bridge technology */
- if (outfd > -1 && bridge->technology->fd) {
- bridge->technology->fd(bridge, bridge_channel, outfd);
- return;
+ /*
+ * It may be necessary to resume music on hold after we finish
+ * playing the announcment.
+ *
+ * XXX We have no idea what MOH class was in use before playing
+ * the file.
+ */
+ if (ast_test_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_MOH)) {
+ ast_moh_start(bridge_channel->chan, NULL, NULL);
}
+}
- /* If all else fails just poke the bridge */
- if (bridge->technology->poke && bridge_channel) {
- bridge->technology->poke(bridge, bridge_channel);
- return;
+struct bridge_playfile {
+ /*! Call this function to play the playfile. (NULL if normal sound file to play) */
+ ast_bridge_custom_play_fn custom_play;
+ /*! Offset into playfile[] where the MOH class name starts. (zero if no MOH)*/
+ int moh_offset;
+ /*! Filename to play. */
+ char playfile[0];
+};
+
+/*!
+ * \internal
+ * \brief Handle the playfile bridge action.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel to play a file on.
+ * \param payload Action frame payload to play a file.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_playfile(struct ast_bridge_channel *bridge_channel, struct bridge_playfile *payload)
+{
+ ast_bridge_channel_playfile(bridge_channel, payload->custom_play, payload->playfile,
+ payload->moh_offset ? &payload->playfile[payload->moh_offset] : NULL);
+}
+
+static void payload_helper_playfile(ast_bridge_channel_post_action_data post_it,
+ struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class)
+{
+ struct bridge_playfile *payload;
+ size_t len_name = strlen(playfile) + 1;
+ size_t len_moh = !moh_class ? 0 : strlen(moh_class) + 1;
+ size_t len_payload = sizeof(*payload) + len_name + len_moh;
+
+ /* Fill in play file frame data. */
+ payload = alloca(len_payload);
+ payload->custom_play = custom_play;
+ payload->moh_offset = len_moh ? len_name : 0;
+ strcpy(payload->playfile, playfile);/* Safe */
+ if (moh_class) {
+ strcpy(&payload->playfile[payload->moh_offset], moh_class);/* Safe */
}
+
+ post_it(bridge_channel, AST_BRIDGE_ACTION_PLAY_FILE, payload, len_payload);
}
-/*! \brief Generic thread loop, TODO: Rethink this/improve it */
-static int generic_thread_loop(struct ast_bridge *bridge)
+void ast_bridge_channel_write_playfile(struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class)
{
- while (!bridge->stop && !bridge->refresh && bridge->array_num) {
- struct ast_channel *winner;
- int to = -1;
+ payload_helper_playfile(ast_bridge_channel_write_action_data,
+ bridge_channel, custom_play, playfile, moh_class);
+}
- /* Move channels around for priority reasons if we have more than one channel in our array */
- if (bridge->array_num > 1) {
- struct ast_channel *first = bridge->array[0];
- memmove(bridge->array, bridge->array + 1, sizeof(struct ast_channel *) * (bridge->array_num - 1));
- bridge->array[(bridge->array_num - 1)] = first;
- }
+void ast_bridge_channel_queue_playfile(struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class)
+{
+ payload_helper_playfile(ast_bridge_channel_queue_action_data,
+ bridge_channel, custom_play, playfile, moh_class);
+}
- /* Wait on the channels */
- bridge->waiting = 1;
- ao2_unlock(bridge);
- winner = ast_waitfor_n(bridge->array, (int) bridge->array_num, &to);
- bridge->waiting = 0;
- ao2_lock(bridge);
+struct bridge_park {
+ int parker_uuid_offset;
+ int app_data_offset;
+ /* buffer used for holding those strings */
+ char parkee_uuid[0];
+};
+
+static void bridge_channel_park(struct ast_bridge_channel *bridge_channel, struct bridge_park *payload)
+{
+ ast_bridge_channel_park(bridge_channel, payload->parkee_uuid,
+ &payload->parkee_uuid[payload->parker_uuid_offset],
+ payload->app_data_offset ? &payload->parkee_uuid[payload->app_data_offset] : NULL);
+}
- /* Process whatever they did */
- ast_bridge_handle_trip(bridge, NULL, winner, -1);
+static void payload_helper_park(ast_bridge_channel_post_action_data post_it,
+ struct ast_bridge_channel *bridge_channel,
+ const char *parkee_uuid,
+ const char *parker_uuid,
+ const char *app_data)
+{
+ struct bridge_park *payload;
+ size_t len_parkee_uuid = strlen(parkee_uuid) + 1;
+ size_t len_parker_uuid = strlen(parker_uuid) + 1;
+ size_t len_app_data = !app_data ? 0 : strlen(app_data) + 1;
+ size_t len_payload = sizeof(*payload) + len_parker_uuid + len_parkee_uuid + len_app_data;
+
+ payload = alloca(len_payload);
+ payload->app_data_offset = len_app_data ? len_parkee_uuid + len_parker_uuid : 0;
+ payload->parker_uuid_offset = len_parkee_uuid;
+ strcpy(payload->parkee_uuid, parkee_uuid);
+ strcpy(&payload->parkee_uuid[payload->parker_uuid_offset], parker_uuid);
+ if (app_data) {
+ strcpy(&payload->parkee_uuid[payload->app_data_offset], app_data);
}
- return 0;
+ post_it(bridge_channel, AST_BRIDGE_ACTION_PARK, payload, len_payload);
}
-/*! \brief Bridge thread function */
-static void *bridge_thread(void *data)
+void ast_bridge_channel_write_park(struct ast_bridge_channel *bridge_channel, const char *parkee_uuid, const char *parker_uuid, const char *app_data)
{
- struct ast_bridge *bridge = data;
- int res = 0;
+ payload_helper_park(ast_bridge_channel_write_action_data,
+ bridge_channel, parkee_uuid, parker_uuid, app_data);
+}
- ao2_lock(bridge);
+/*!
+ * \internal
+ * \brief Feed notification that a frame is waiting on a channel into the bridging core
+ *
+ * \param bridge_channel Bridge channel the notification was received on
+ */
+static void bridge_handle_trip(struct ast_bridge_channel *bridge_channel)
+{
+ struct ast_frame *frame;
- if (bridge->callid) {
- ast_callid_threadassoc_add(bridge->callid);
+ if (bridge_channel->features->mute) {
+ frame = ast_read_noaudio(bridge_channel->chan);
+ } else {
+ frame = ast_read(bridge_channel->chan);
}
- ast_debug(1, "Started bridge thread for %p\n", bridge);
+ if (!frame) {
+ bridge_handle_hangup(bridge_channel);
+ return;
+ }
+ switch (frame->frametype) {
+ case AST_FRAME_NULL:
+ /* Just discard it. */
+ ast_frfree(frame);
+ return;
+ case AST_FRAME_CONTROL:
+ switch (frame->subclass.integer) {
+ case AST_CONTROL_HANGUP:
+ bridge_handle_hangup(bridge_channel);
+ ast_frfree(frame);
+ return;
+/* BUGBUG This is where incoming HOLD/UNHOLD memory should register. Write UNHOLD into bridge when this channel is pulled. */
+ default:
+ break;
+ }
+ break;
+ case AST_FRAME_DTMF_BEGIN:
+ frame = bridge_handle_dtmf(bridge_channel, frame);
+ if (!frame) {
+ return;
+ }
+ /* Fall through */
+ case AST_FRAME_DTMF_END:
+ if (!bridge_channel->features->dtmf_passthrough) {
+ ast_frfree(frame);
+ return;
+ }
+/* BUGBUG This is where incoming DTMF begin/end memory should register. Write DTMF end into bridge when this channel is pulled. */
+ break;
+ default:
+ break;
+ }
- /* Loop around until we are told to stop */
- while (!bridge->stop && bridge->array_num && !res) {
- /* In case the refresh bit was set simply set it back to off */
- bridge->refresh = 0;
+/* BUGBUG bridge join or impart needs to do CONNECTED_LINE updates if the channels are being swapped and it is a 1-1 bridge. */
- ast_debug(1, "Launching bridge thread function %p for bridge %p\n",
- bridge->technology->thread ? bridge->technology->thread : generic_thread_loop,
- bridge);
+ /* Simply write the frame out to the bridge technology. */
+/* BUGBUG The tech is where AST_CONTROL_ANSWER hook should go. (early bridge) */
+/* BUGBUG The tech is where incoming BUSY/CONGESTION hangup should happen? (early bridge) */
+ bridge_channel_write_frame(bridge_channel, frame);
+ ast_frfree(frame);
+}
+/*!
+ * \internal
+ * \brief Complete joining new channels to the bridge.
+ * \since 12.0.0
+ *
+ * \param bridge Check for new channels on this bridge.
+ *
+ * \note On entry, bridge is already locked.
+ *
+ * \return Nothing
+ */
+static void bridge_complete_join(struct ast_bridge *bridge)
+{
+ struct ast_bridge_channel *bridge_channel;
+
+ if (bridge->dissolved) {
/*
- * Execute the appropriate thread function. If the technology
- * does not provide one we use the generic one.
+ * No sense in completing the join on channels for a dissolved
+ * bridge. They are just going to be removed soon anyway.
+ * However, we do have reason to abort here because the bridge
+ * technology may not be able to handle the number of channels
+ * still in the bridge.
*/
- res = bridge->technology->thread
- ? bridge->technology->thread(bridge)
- : generic_thread_loop(bridge);
+ return;
}
- ast_debug(1, "Ending bridge thread for %p\n", bridge);
-
- /* Indicate the bridge thread is no longer active */
- bridge->thread = AST_PTHREADT_NULL;
- ao2_unlock(bridge);
+ AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+ if (!bridge_channel->just_joined) {
+ continue;
+ }
- ao2_ref(bridge, -1);
+ /* Make the channel compatible with the bridge */
+ bridge_make_compatible(bridge, bridge_channel);
+
+ /* Tell the bridge technology we are joining so they set us up */
+ ast_debug(1, "Bridge %s: %p(%s) is joining %s technology\n",
+ bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan),
+ bridge->technology->name);
+ if (bridge->technology->join
+ && bridge->technology->join(bridge, bridge_channel)) {
+ ast_debug(1, "Bridge %s: %p(%s) failed to join %s technology\n",
+ bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan),
+ bridge->technology->name);
+ }
- return NULL;
+ bridge_channel->just_joined = 0;
+ }
}
-/*! \brief Helper function used to find the "best" bridge technology given a specified capabilities */
-static struct ast_bridge_technology *find_best_technology(uint32_t capabilities)
+/*! \brief Helper function used to find the "best" bridge technology given specified capabilities */
+static struct ast_bridge_technology *find_best_technology(uint32_t capabilities, struct ast_bridge *bridge)
{
- struct ast_bridge_technology *current = NULL, *best = NULL;
+ struct ast_bridge_technology *current;
+ struct ast_bridge_technology *best = NULL;
AST_RWLIST_RDLOCK(&bridge_technologies);
AST_RWLIST_TRAVERSE(&bridge_technologies, current, entry) {
if (current->suspended) {
- ast_debug(1, "Bridge technology %s is suspended. Skipping.\n", current->name);
+ ast_debug(1, "Bridge technology %s is suspended. Skipping.\n",
+ current->name);
continue;
}
if (!(current->capabilities & capabilities)) {
- ast_debug(1, "Bridge technology %s does not have the capabilities we need.\n", current->name);
+ ast_debug(1, "Bridge technology %s does not have any capabilities we want.\n",
+ current->name);
continue;
}
- if (best && best->preference < current->preference) {
- ast_debug(1, "Bridge technology %s has preference %d while %s has preference %d. Skipping.\n", current->name, current->preference, best->name, best->preference);
+ if (best && current->preference <= best->preference) {
+ ast_debug(1, "Bridge technology %s has less preference than %s (%d <= %d). Skipping.\n",
+ current->name, best->name, current->preference, best->preference);
+ continue;
+ }
+ if (current->compatible && !current->compatible(bridge)) {
+ ast_debug(1, "Bridge technology %s is not compatible with properties of existing bridge.\n",
+ current->name);
continue;
}
best = current;
@@ -505,127 +1104,348 @@ static struct ast_bridge_technology *find_best_technology(uint32_t capabilities)
return best;
}
+struct tech_deferred_destroy {
+ struct ast_bridge_technology *tech;
+ void *tech_pvt;
+};
+
+/*!
+ * \internal
+ * \brief Deferred destruction of bridge tech private structure.
+ * \since 12.0.0
+ *
+ * \param bridge What to execute the action on.
+ * \param action Deferred bridge tech destruction.
+ *
+ * \note On entry, bridge must not be locked.
+ *
+ * \return Nothing
+ */
+static void bridge_tech_deferred_destroy(struct ast_bridge *bridge, struct ast_frame *action)
+{
+ struct tech_deferred_destroy *deferred = action->data.ptr;
+ struct ast_bridge dummy_bridge = {
+ .technology = deferred->tech,
+ .tech_pvt = deferred->tech_pvt,
+ };
+
+ ast_copy_string(dummy_bridge.uniqueid, bridge->uniqueid, sizeof(dummy_bridge.uniqueid));
+ ast_debug(1, "Bridge %s: calling %s technology destructor (deferred, dummy)\n",
+ dummy_bridge.uniqueid, dummy_bridge.technology->name);
+ dummy_bridge.technology->destroy(&dummy_bridge);
+ ast_module_unref(dummy_bridge.technology->mod);
+}
+
+/*!
+ * \internal
+ * \brief Handle bridge action frame.
+ * \since 12.0.0
+ *
+ * \param bridge What to execute the action on.
+ * \param action What to do.
+ *
+ * \note On entry, bridge is already locked.
+ * \note Can be called by the bridge destructor.
+ *
+ * \return Nothing
+ */
+static void bridge_action_bridge(struct ast_bridge *bridge, struct ast_frame *action)
+{
+#if 0 /* In case we need to know when the destructor is calling us. */
+ int in_destructor = !ao2_ref(bridge, 0);
+#endif
+
+ switch (action->subclass.integer) {
+ case AST_BRIDGE_ACTION_DEFERRED_TECH_DESTROY:
+ ast_bridge_unlock(bridge);
+ bridge_tech_deferred_destroy(bridge, action);
+ ast_bridge_lock(bridge);
+ break;
+ case AST_BRIDGE_ACTION_DEFERRED_DISSOLVING:
+ ast_bridge_unlock(bridge);
+ bridge->v_table->dissolving(bridge);
+ ast_bridge_lock(bridge);
+ break;
+ default:
+ /* Unexpected deferred action type. Should never happen. */
+ ast_assert(0);
+ break;
+ }
+}
+
+/*!
+ * \internal
+ * \brief Do any pending bridge actions.
+ * \since 12.0.0
+ *
+ * \param bridge What to do actions on.
+ *
+ * \note On entry, bridge is already locked.
+ * \note Can be called by the bridge destructor.
+ *
+ * \return Nothing
+ */
+static void bridge_handle_actions(struct ast_bridge *bridge)
+{
+ struct ast_frame *action;
+
+ while ((action = AST_LIST_REMOVE_HEAD(&bridge->action_queue, frame_list))) {
+ switch (action->frametype) {
+ case AST_FRAME_BRIDGE_ACTION:
+ bridge_action_bridge(bridge, action);
+ break;
+ default:
+ /* Unexpected deferred frame type. Should never happen. */
+ ast_assert(0);
+ break;
+ }
+ ast_frfree(action);
+ }
+}
+
static void destroy_bridge(void *obj)
{
struct ast_bridge *bridge = obj;
+ RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
- ast_debug(1, "Actually destroying bridge %p, nobody wants it anymore\n", bridge);
+ ast_debug(1, "Bridge %s: actually destroying %s bridge, nobody wants it anymore\n",
+ bridge->uniqueid, bridge->v_table->name);
+
+ msg = stasis_cache_clear_create(ast_bridge_snapshot_type(), bridge->uniqueid);
+ if (msg) {
+ stasis_publish(ast_bridge_topic(bridge), msg);
+ }
+
+ /* Do any pending actions in the context of destruction. */
+ ast_bridge_lock(bridge);
+ bridge_handle_actions(bridge);
+ ast_bridge_unlock(bridge);
/* There should not be any channels left in the bridge. */
ast_assert(AST_LIST_EMPTY(&bridge->channels));
+ ast_debug(1, "Bridge %s: calling %s bridge destructor\n",
+ bridge->uniqueid, bridge->v_table->name);
+ bridge->v_table->destroy(bridge);
+
/* Pass off the bridge to the technology to destroy if needed */
- if (bridge->technology->destroy) {
- ast_debug(1, "Giving bridge technology %s the bridge structure %p to destroy\n", bridge->technology->name, bridge);
- if (bridge->technology->destroy(bridge)) {
- ast_debug(1, "Bridge technology %s failed to destroy bridge structure %p... some memory may have leaked\n",
- bridge->technology->name, bridge);
+ if (bridge->technology) {
+ ast_debug(1, "Bridge %s: calling %s technology destructor\n",
+ bridge->uniqueid, bridge->technology->name);
+ if (bridge->technology->destroy) {
+ bridge->technology->destroy(bridge);
}
+ ast_module_unref(bridge->technology->mod);
+ bridge->technology = NULL;
}
- cleanup_video_mode(bridge);
-
- /* Clean up the features configuration */
- ast_bridge_features_cleanup(&bridge->features);
-
- /* We are no longer using the bridge technology so decrement the module reference count on it */
- ast_module_unref(bridge->technology->mod);
+ if (bridge->callid) {
+ bridge->callid = ast_callid_unref(bridge->callid);
+ }
- /* Drop the array of channels */
- ast_free(bridge->array);
+ cleanup_video_mode(bridge);
}
-struct ast_bridge *ast_bridge_new(uint32_t capabilities, int flags)
+struct ast_bridge *ast_bridge_register(struct ast_bridge *bridge)
{
- struct ast_bridge *bridge;
- struct ast_bridge_technology *bridge_technology;
-
- /* If we need to be a smart bridge see if we can move between 1to1 and multimix bridges */
- if (flags & AST_BRIDGE_FLAG_SMART) {
- if (!ast_bridge_check((capabilities & AST_BRIDGE_CAPABILITY_1TO1MIX)
- ? AST_BRIDGE_CAPABILITY_MULTIMIX : AST_BRIDGE_CAPABILITY_1TO1MIX)) {
- return NULL;
+ if (bridge) {
+ ast_bridge_publish_state(bridge);
+ if (!ao2_link(bridges, bridge)) {
+ ast_bridge_destroy(bridge);
+ bridge = NULL;
}
}
+ return bridge;
+}
- /*
- * If capabilities were provided use our helper function to find
- * the "best" bridge technology, otherwise we can just look for
- * the most basic capability needed, single 1to1 mixing.
- */
- bridge_technology = capabilities
- ? find_best_technology(capabilities)
- : find_best_technology(AST_BRIDGE_CAPABILITY_1TO1MIX);
+struct ast_bridge *ast_bridge_alloc(size_t size, const struct ast_bridge_methods *v_table)
+{
+ struct ast_bridge *bridge;
- /* If no bridge technology was found we can't possibly do bridging so fail creation of the bridge */
- if (!bridge_technology) {
+ /* Check v_table that all methods are present. */
+ if (!v_table
+ || !v_table->name
+ || !v_table->destroy
+ || !v_table->dissolving
+ || !v_table->push
+ || !v_table->pull
+ || !v_table->notify_masquerade
+ || !v_table->get_merge_priority) {
+ ast_log(LOG_ERROR, "Virtual method table for bridge class %s not complete.\n",
+ v_table && v_table->name ? v_table->name : "<unknown>");
+ ast_assert(0);
return NULL;
}
- /* We have everything we need to create this bridge... so allocate the memory, link things together, and fire her up! */
- bridge = ao2_alloc(sizeof(*bridge), destroy_bridge);
- if (!bridge) {
- ast_module_unref(bridge_technology->mod);
+ bridge = ao2_alloc(size, destroy_bridge);
+ if (bridge) {
+ bridge->v_table = v_table;
+ }
+ return bridge;
+}
+
+struct ast_bridge *ast_bridge_base_init(struct ast_bridge *self, uint32_t capabilities, unsigned int flags)
+{
+ if (!self) {
return NULL;
}
- bridge->technology = bridge_technology;
- bridge->thread = AST_PTHREADT_NULL;
+ ast_uuid_generate_str(self->uniqueid, sizeof(self->uniqueid));
+ ast_set_flag(&self->feature_flags, flags);
+ self->allowed_capabilities = capabilities;
- /* Create an array of pointers for the channels that will be joining us */
- bridge->array = ast_malloc(BRIDGE_ARRAY_START * sizeof(*bridge->array));
- if (!bridge->array) {
- ao2_ref(bridge, -1);
+ /* Use our helper function to find the "best" bridge technology. */
+ self->technology = find_best_technology(capabilities, self);
+ if (!self->technology) {
+ ast_debug(1, "Bridge %s: Could not create. No technology available to support it.\n",
+ self->uniqueid);
+ ao2_ref(self, -1);
return NULL;
}
- bridge->array_size = BRIDGE_ARRAY_START;
-
- ast_set_flag(&bridge->feature_flags, flags);
/* Pass off the bridge to the technology to manipulate if needed */
- if (bridge->technology->create) {
- ast_debug(1, "Giving bridge technology %s the bridge structure %p to setup\n", bridge->technology->name, bridge);
- if (bridge->technology->create(bridge)) {
- ast_debug(1, "Bridge technology %s failed to setup bridge structure %p\n", bridge->technology->name, bridge);
- ao2_ref(bridge, -1);
- return NULL;
- }
+ ast_debug(1, "Bridge %s: calling %s technology constructor\n",
+ self->uniqueid, self->technology->name);
+ if (self->technology->create && self->technology->create(self)) {
+ ast_debug(1, "Bridge %s: failed to setup %s technology\n",
+ self->uniqueid, self->technology->name);
+ ao2_ref(self, -1);
+ return NULL;
}
- return bridge;
+ if (!ast_bridge_topic(self)) {
+ ao2_ref(self, -1);
+ return NULL;
+ }
+
+ return self;
}
-int ast_bridge_check(uint32_t capabilities)
+/*!
+ * \internal
+ * \brief ast_bridge base class destructor.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ *
+ * \note Stub because of nothing to do.
+ *
+ * \return Nothing
+ */
+static void bridge_base_destroy(struct ast_bridge *self)
{
- struct ast_bridge_technology *bridge_technology;
-
- if (!(bridge_technology = find_best_technology(capabilities))) {
- return 0;
- }
+}
- ast_module_unref(bridge_technology->mod);
+/*!
+ * \internal
+ * \brief The bridge is being dissolved.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ *
+ * \return Nothing
+ */
+static void bridge_base_dissolving(struct ast_bridge *self)
+{
+ ao2_unlink(bridges, self);
+}
- return 1;
+/*!
+ * \internal
+ * \brief ast_bridge base push method.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ * \param bridge_channel Bridge channel to push.
+ * \param swap Bridge channel to swap places with if not NULL.
+ *
+ * \note On entry, self is already locked.
+ * \note Stub because of nothing to do.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+static int bridge_base_push(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap)
+{
+ return 0;
}
-int ast_bridge_destroy(struct ast_bridge *bridge)
+/*!
+ * \internal
+ * \brief ast_bridge base pull method.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ * \param bridge_channel Bridge channel to pull.
+ *
+ * \note On entry, self is already locked.
+ *
+ * \return Nothing
+ */
+static void bridge_base_pull(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel)
{
- ao2_lock(bridge);
+ bridge_features_remove_on_pull(bridge_channel->features);
+}
- if (bridge->callid) {
- bridge->callid = ast_callid_unref(bridge->callid);
- }
+/*!
+ * \internal
+ * \brief ast_bridge base notify_masquerade method.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ * \param bridge_channel Bridge channel that was masqueraded.
+ *
+ * \note On entry, self is already locked.
+ *
+ * \return Nothing
+ */
+static void bridge_base_notify_masquerade(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel)
+{
+ self->reconfigured = 1;
+}
- if (bridge->thread != AST_PTHREADT_NULL) {
- bridge_stop(bridge);
- }
+/*!
+ * \internal
+ * \brief Get the merge priority of this bridge.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ *
+ * \note On entry, self is already locked.
+ *
+ * \return Merge priority
+ */
+static int bridge_base_get_merge_priority(struct ast_bridge *self)
+{
+ return 0;
+}
- ast_debug(1, "Telling all channels in bridge %p to leave the party\n", bridge);
+struct ast_bridge_methods ast_bridge_base_v_table = {
+ .name = "base",
+ .destroy = bridge_base_destroy,
+ .dissolving = bridge_base_dissolving,
+ .push = bridge_base_push,
+ .pull = bridge_base_pull,
+ .notify_masquerade = bridge_base_notify_masquerade,
+ .get_merge_priority = bridge_base_get_merge_priority,
+};
+
+struct ast_bridge *ast_bridge_base_new(uint32_t capabilities, unsigned int flags)
+{
+ void *bridge;
- /* Drop every bridged channel, the last one will cause the bridge thread (if it exists) to exit */
- bridge_force_out_all(bridge);
+ bridge = ast_bridge_alloc(sizeof(struct ast_bridge), &ast_bridge_base_v_table);
+ bridge = ast_bridge_base_init(bridge, capabilities, flags);
+ bridge = ast_bridge_register(bridge);
+ return bridge;
+}
- ao2_unlock(bridge);
+int ast_bridge_destroy(struct ast_bridge *bridge)
+{
+ ast_debug(1, "Bridge %s: telling all channels to leave the party\n", bridge->uniqueid);
+ ast_bridge_lock(bridge);
+ bridge_dissolve(bridge);
+ ast_bridge_unlock(bridge);
ao2_ref(bridge, -1);
@@ -634,266 +1454,439 @@ int ast_bridge_destroy(struct ast_bridge *bridge)
static int bridge_make_compatible(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
{
- struct ast_format formats[2];
- ast_format_copy(&formats[0], ast_channel_readformat(bridge_channel->chan));
- ast_format_copy(&formats[1], ast_channel_writeformat(bridge_channel->chan));
+ struct ast_format read_format;
+ struct ast_format write_format;
+ struct ast_format best_format;
+ char codec_buf[512];
+
+ ast_format_copy(&read_format, ast_channel_readformat(bridge_channel->chan));
+ ast_format_copy(&write_format, ast_channel_writeformat(bridge_channel->chan));
/* Are the formats currently in use something this bridge can handle? */
if (!ast_format_cap_iscompatible(bridge->technology->format_capabilities, ast_channel_readformat(bridge_channel->chan))) {
- struct ast_format best_format;
-
ast_best_codec(bridge->technology->format_capabilities, &best_format);
/* Read format is a no go... */
- if (option_debug) {
- char codec_buf[512];
- ast_debug(1, "Bridge technology %s wants to read any of formats %s but channel has %s\n", bridge->technology->name,
- ast_getformatname_multiple(codec_buf, sizeof(codec_buf), bridge->technology->format_capabilities),
- ast_getformatname(&formats[0]));
- }
+ ast_debug(1, "Bridge technology %s wants to read any of formats %s but channel has %s\n",
+ bridge->technology->name,
+ ast_getformatname_multiple(codec_buf, sizeof(codec_buf), bridge->technology->format_capabilities),
+ ast_getformatname(&read_format));
+
/* Switch read format to the best one chosen */
if (ast_set_read_format(bridge_channel->chan, &best_format)) {
- ast_log(LOG_WARNING, "Failed to set channel %s to read format %s\n", ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format));
+ ast_log(LOG_WARNING, "Failed to set channel %s to read format %s\n",
+ ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format));
return -1;
}
- ast_debug(1, "Bridge %p put channel %s into read format %s\n", bridge, ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format));
+ ast_debug(1, "Bridge %s put channel %s into read format %s\n",
+ bridge->uniqueid, ast_channel_name(bridge_channel->chan),
+ ast_getformatname(&best_format));
} else {
- ast_debug(1, "Bridge %p is happy that channel %s already has read format %s\n", bridge, ast_channel_name(bridge_channel->chan), ast_getformatname(&formats[0]));
+ ast_debug(1, "Bridge %s is happy that channel %s already has read format %s\n",
+ bridge->uniqueid, ast_channel_name(bridge_channel->chan),
+ ast_getformatname(&read_format));
}
- if (!ast_format_cap_iscompatible(bridge->technology->format_capabilities, &formats[1])) {
- struct ast_format best_format;
-
+ if (!ast_format_cap_iscompatible(bridge->technology->format_capabilities, &write_format)) {
ast_best_codec(bridge->technology->format_capabilities, &best_format);
/* Write format is a no go... */
- if (option_debug) {
- char codec_buf[512];
- ast_debug(1, "Bridge technology %s wants to write any of formats %s but channel has %s\n", bridge->technology->name,
- ast_getformatname_multiple(codec_buf, sizeof(codec_buf), bridge->technology->format_capabilities),
- ast_getformatname(&formats[1]));
- }
+ ast_debug(1, "Bridge technology %s wants to write any of formats %s but channel has %s\n",
+ bridge->technology->name,
+ ast_getformatname_multiple(codec_buf, sizeof(codec_buf), bridge->technology->format_capabilities),
+ ast_getformatname(&write_format));
+
/* Switch write format to the best one chosen */
if (ast_set_write_format(bridge_channel->chan, &best_format)) {
- ast_log(LOG_WARNING, "Failed to set channel %s to write format %s\n", ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format));
+ ast_log(LOG_WARNING, "Failed to set channel %s to write format %s\n",
+ ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format));
return -1;
}
- ast_debug(1, "Bridge %p put channel %s into write format %s\n", bridge, ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format));
+ ast_debug(1, "Bridge %s put channel %s into write format %s\n",
+ bridge->uniqueid, ast_channel_name(bridge_channel->chan),
+ ast_getformatname(&best_format));
} else {
- ast_debug(1, "Bridge %p is happy that channel %s already has write format %s\n", bridge, ast_channel_name(bridge_channel->chan), ast_getformatname(&formats[1]));
+ ast_debug(1, "Bridge %s is happy that channel %s already has write format %s\n",
+ bridge->uniqueid, ast_channel_name(bridge_channel->chan),
+ ast_getformatname(&write_format));
}
return 0;
}
-/*! \brief Perform the smart bridge operation. Basically sees if a new bridge technology should be used instead of the current one. */
-static int smart_bridge_operation(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, int count)
+/*!
+ * \internal
+ * \brief Perform the smart bridge operation.
+ * \since 12.0.0
+ *
+ * \param bridge Work on this bridge.
+ *
+ * \details
+ * Basically see if a new bridge technology should be used instead
+ * of the current one.
+ *
+ * \note On entry, bridge is already locked.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int smart_bridge_operation(struct ast_bridge *bridge)
{
- uint32_t new_capabilities = 0;
+ uint32_t new_capabilities;
struct ast_bridge_technology *new_technology;
struct ast_bridge_technology *old_technology = bridge->technology;
- struct ast_bridge temp_bridge = {
+ struct ast_bridge_channel *bridge_channel;
+ struct ast_frame *deferred_action;
+ struct ast_bridge dummy_bridge = {
.technology = bridge->technology,
- .bridge_pvt = bridge->bridge_pvt,
+ .tech_pvt = bridge->tech_pvt,
};
- struct ast_bridge_channel *bridge_channel2;
- /* Based on current feature determine whether we want to change bridge technologies or not */
- if (bridge->technology->capabilities & AST_BRIDGE_CAPABILITY_1TO1MIX) {
- if (count <= 2) {
- ast_debug(1, "Bridge %p channel count (%d) is within limits for bridge technology %s, not performing smart bridge operation.\n",
- bridge, count, bridge->technology->name);
- return 0;
- }
+ if (bridge->dissolved) {
+ ast_debug(1, "Bridge %s is dissolved, not performing smart bridge operation.\n",
+ bridge->uniqueid);
+ return 0;
+ }
+
+ /* Determine new bridge technology capabilities needed. */
+ if (2 < bridge->num_channels) {
new_capabilities = AST_BRIDGE_CAPABILITY_MULTIMIX;
- } else if (bridge->technology->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX) {
- if (count > 2) {
- ast_debug(1, "Bridge %p channel count (%d) is within limits for bridge technology %s, not performing smart bridge operation.\n",
- bridge, count, bridge->technology->name);
- return 0;
+ new_capabilities &= bridge->allowed_capabilities;
+ } else {
+ new_capabilities = AST_BRIDGE_CAPABILITY_NATIVE | AST_BRIDGE_CAPABILITY_1TO1MIX;
+ new_capabilities &= bridge->allowed_capabilities;
+ if (!new_capabilities
+ && (bridge->allowed_capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX)) {
+ /* Allow switching between different multimix bridge technologies. */
+ new_capabilities = AST_BRIDGE_CAPABILITY_MULTIMIX;
}
- new_capabilities = AST_BRIDGE_CAPABILITY_1TO1MIX;
}
- if (!new_capabilities) {
- ast_debug(1, "Bridge '%p' has no new capabilities, not performing smart bridge operation.\n", bridge);
- return 0;
- }
+ /* Find a bridge technology to satisfy the new capabilities. */
+ new_technology = find_best_technology(new_capabilities, bridge);
+ if (!new_technology) {
+ int is_compatible = 0;
+
+ if (old_technology->compatible) {
+ is_compatible = old_technology->compatible(bridge);
+ } else if (old_technology->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX) {
+ is_compatible = 1;
+ } else if (bridge->num_channels <= 2
+ && (old_technology->capabilities & AST_BRIDGE_CAPABILITY_1TO1MIX)) {
+ is_compatible = 1;
+ }
- /* Attempt to find a new bridge technology to satisfy the capabilities */
- if (!(new_technology = find_best_technology(new_capabilities))) {
+ if (is_compatible) {
+ ast_debug(1, "Bridge %s could not get a new technology, staying with old technology.\n",
+ bridge->uniqueid);
+ return 0;
+ }
+ ast_log(LOG_WARNING, "Bridge %s has no technology available to support it.\n",
+ bridge->uniqueid);
return -1;
}
+ if (new_technology == old_technology) {
+ ast_debug(1, "Bridge %s is already using the new technology.\n",
+ bridge->uniqueid);
+ ast_module_unref(old_technology->mod);
+ return 0;
+ }
+
+ ast_copy_string(dummy_bridge.uniqueid, bridge->uniqueid, sizeof(dummy_bridge.uniqueid));
- ast_debug(1, "Performing smart bridge operation on bridge %p, moving from bridge technology %s to %s\n",
- bridge, old_technology->name, new_technology->name);
+ if (old_technology->destroy) {
+ struct tech_deferred_destroy deferred_tech_destroy = {
+ .tech = dummy_bridge.technology,
+ .tech_pvt = dummy_bridge.tech_pvt,
+ };
+ struct ast_frame action = {
+ .frametype = AST_FRAME_BRIDGE_ACTION,
+ .subclass.integer = AST_BRIDGE_ACTION_DEFERRED_TECH_DESTROY,
+ .data.ptr = &deferred_tech_destroy,
+ .datalen = sizeof(deferred_tech_destroy),
+ };
- /* If a thread is currently executing for the current technology tell it to stop */
- if (bridge->thread != AST_PTHREADT_NULL) {
/*
- * If the new bridge technology also needs a thread simply tell
- * the bridge thread to refresh itself. This has the benefit of
- * not incurring the cost/time of tearing down and bringing up a
- * new thread.
+ * We need to defer the bridge technology destroy callback
+ * because we have the bridge locked.
*/
- if (new_technology->capabilities & AST_BRIDGE_CAPABILITY_THREAD) {
- ast_debug(1, "Telling current bridge thread for bridge %p to refresh\n", bridge);
- bridge->refresh = 1;
- bridge_poke(bridge);
- } else {
- ast_debug(1, "Telling current bridge thread for bridge %p to stop\n", bridge);
- bridge_stop(bridge);
+ deferred_action = ast_frdup(&action);
+ if (!deferred_action) {
+ ast_module_unref(new_technology->mod);
+ return -1;
}
+ } else {
+ deferred_action = NULL;
}
/*
+ * We are now committed to changing the bridge technology. We
+ * must not release the bridge lock until we have installed the
+ * new bridge technology.
+ */
+ ast_debug(1, "Bridge %s: switching %s technology to %s\n",
+ bridge->uniqueid, old_technology->name, new_technology->name);
+
+ /*
* Since we are soon going to pass this bridge to a new
- * technology we need to NULL out the bridge_pvt pointer but
- * don't worry as it still exists in temp_bridge, ditto for the
+ * technology we need to NULL out the tech_pvt pointer but
+ * don't worry as it still exists in dummy_bridge, ditto for the
* old technology.
*/
- bridge->bridge_pvt = NULL;
+ bridge->tech_pvt = NULL;
bridge->technology = new_technology;
- /* Pass the bridge to the new bridge technology so it can set it up */
- if (new_technology->create) {
- ast_debug(1, "Giving bridge technology %s the bridge structure %p to setup\n",
- new_technology->name, bridge);
- if (new_technology->create(bridge)) {
- ast_debug(1, "Bridge technology %s failed to setup bridge structure %p\n",
- new_technology->name, bridge);
- }
+ /* Setup the new bridge technology. */
+ ast_debug(1, "Bridge %s: calling %s technology constructor\n",
+ bridge->uniqueid, new_technology->name);
+ if (new_technology->create && new_technology->create(bridge)) {
+ ast_log(LOG_WARNING, "Bridge %s: failed to setup bridge technology %s\n",
+ bridge->uniqueid, new_technology->name);
+ bridge->tech_pvt = dummy_bridge.tech_pvt;
+ bridge->technology = dummy_bridge.technology;
+ ast_module_unref(new_technology->mod);
+ return -1;
}
- /* Move existing channels over to the new technology, while taking them away from the old one */
- AST_LIST_TRAVERSE(&bridge->channels, bridge_channel2, entry) {
- /* Skip over channel that initiated the smart bridge operation */
- if (bridge_channel == bridge_channel2) {
+ /* Move existing channels over to the new technology. */
+ AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+ if (bridge_channel->just_joined) {
+ /*
+ * This channel has not completed joining the bridge so it is
+ * not in the old bridge technology.
+ */
continue;
}
/* First we part them from the old technology */
+ ast_debug(1, "Bridge %s: %p(%s) is leaving %s technology (dummy)\n",
+ dummy_bridge.uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan),
+ old_technology->name);
if (old_technology->leave) {
- ast_debug(1, "Giving bridge technology %s notification that %p is leaving bridge %p (really %p)\n",
- old_technology->name, bridge_channel2, &temp_bridge, bridge);
- if (old_technology->leave(&temp_bridge, bridge_channel2)) {
- ast_debug(1, "Bridge technology %s failed to allow %p (really %p) to leave bridge %p\n",
- old_technology->name, bridge_channel2, &temp_bridge, bridge);
- }
+ old_technology->leave(&dummy_bridge, bridge_channel);
}
/* Second we make them compatible again with the bridge */
- bridge_make_compatible(bridge, bridge_channel2);
+ bridge_make_compatible(bridge, bridge_channel);
/* Third we join them to the new technology */
- if (new_technology->join) {
- ast_debug(1, "Giving bridge technology %s notification that %p is joining bridge %p\n",
- new_technology->name, bridge_channel2, bridge);
- if (new_technology->join(bridge, bridge_channel2)) {
- ast_debug(1, "Bridge technology %s failed to join %p to bridge %p\n",
- new_technology->name, bridge_channel2, bridge);
- }
+ ast_debug(1, "Bridge %s: %p(%s) is joining %s technology\n",
+ bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan),
+ new_technology->name);
+ if (new_technology->join && new_technology->join(bridge, bridge_channel)) {
+ ast_debug(1, "Bridge %s: %p(%s) failed to join %s technology\n",
+ bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan),
+ new_technology->name);
}
-
- /* Fourth we tell them to wake up so they become aware that the above has happened */
- bridge_channel_poke(bridge_channel2);
}
- /* Now that all the channels have been moved over we need to get rid of all the information the old technology may have left around */
+ /*
+ * Now that all the channels have been moved over we need to get
+ * rid of all the information the old technology may have left
+ * around.
+ */
if (old_technology->destroy) {
- ast_debug(1, "Giving bridge technology %s the bridge structure %p (really %p) to destroy\n",
- old_technology->name, &temp_bridge, bridge);
- if (old_technology->destroy(&temp_bridge)) {
- ast_debug(1, "Bridge technology %s failed to destroy bridge structure %p (really %p)... some memory may have leaked\n",
- old_technology->name, &temp_bridge, bridge);
- }
+ ast_debug(1, "Bridge %s: deferring %s technology destructor\n",
+ bridge->uniqueid, old_technology->name);
+ bridge_queue_action_nodup(bridge, deferred_action);
+ } else {
+ ast_debug(1, "Bridge %s: calling %s technology destructor\n",
+ bridge->uniqueid, old_technology->name);
+ ast_module_unref(old_technology->mod);
}
- /* Finally if the old technology has module referencing remove our reference, we are no longer going to use it */
- ast_module_unref(old_technology->mod);
-
return 0;
}
-/*! \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)
+/*!
+ * \internal
+ * \brief Notify the bridge that it has been reconfigured.
+ * \since 12.0.0
+ *
+ * \param bridge Reconfigured bridge.
+ *
+ * \details
+ * After a series of bridge_channel_push and
+ * bridge_channel_pull calls, you need to call this function
+ * to cause the bridge to complete restruturing for the change
+ * in the channel makeup of the bridge.
+ *
+ * \note On entry, the bridge is already locked.
+ *
+ * \return Nothing
+ */
+static void bridge_reconfigured(struct ast_bridge *bridge)
{
- int fds[4] = { -1, }, nfds = 0, i = 0, outfd = -1, ms = -1;
- struct ast_channel *chan;
-
- /* Add any file descriptors we may want to monitor */
- if (bridge_channel->bridge->technology->fd) {
- for (i = 0; i < 4; i ++) {
- if (bridge_channel->fds[i] >= 0) {
- fds[nfds++] = bridge_channel->fds[i];
- }
- }
+ if (!bridge->reconfigured) {
+ return;
}
-
- ao2_unlock(bridge_channel->bridge);
-
- ao2_lock(bridge_channel);
- /* Wait for data to either come from the channel or us to be signalled */
- if (!bridge_channel->suspended) {
- ao2_unlock(bridge_channel);
- ast_debug(10, "Going into a multithreaded waitfor for bridge channel %p of bridge %p\n", bridge_channel, bridge_channel->bridge);
- chan = ast_waitfor_nandfds(&bridge_channel->chan, 1, fds, nfds, NULL, &outfd, &ms);
- } else {
- ast_debug(10, "Going into a multithreaded 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));
- ao2_unlock(bridge_channel);
- chan = NULL;
+ bridge->reconfigured = 0;
+ if (ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_SMART)
+ && smart_bridge_operation(bridge)) {
+ /* Smart bridge failed. */
+ bridge_dissolve(bridge);
+ return;
}
+ bridge_complete_join(bridge);
+}
- ao2_lock(bridge_channel->bridge);
+/*!
+ * \internal
+ * \brief Suspend a channel from a bridge.
+ *
+ * \param bridge_channel Channel to suspend.
+ *
+ * \note This function assumes bridge_channel->bridge is locked.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_suspend_nolock(struct ast_bridge_channel *bridge_channel)
+{
+ bridge_channel->suspended = 1;
+ if (bridge_channel->in_bridge) {
+ --bridge_channel->bridge->num_active;
+ }
- if (!bridge_channel->suspended) {
- ast_bridge_handle_trip(bridge_channel->bridge, bridge_channel, chan, outfd);
+ /* Get technology bridge threads off of the channel. */
+ if (bridge_channel->bridge->technology->suspend) {
+ bridge_channel->bridge->technology->suspend(bridge_channel->bridge, bridge_channel);
}
+}
- return bridge_channel->state;
+/*!
+ * \internal
+ * \brief Suspend a channel from a bridge.
+ *
+ * \param bridge_channel Channel to suspend.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_suspend(struct ast_bridge_channel *bridge_channel)
+{
+ ast_bridge_channel_lock_bridge(bridge_channel);
+ bridge_channel_suspend_nolock(bridge_channel);
+ ast_bridge_unlock(bridge_channel->bridge);
}
-/*! \brief Run in a singlethreaded model. Each joined channel yields itself to the main bridge thread. TODO: Improve */
-static enum ast_bridge_channel_state bridge_channel_join_singlethreaded(struct ast_bridge_channel *bridge_channel)
+/*!
+ * \internal
+ * \brief Unsuspend a channel from a bridge.
+ *
+ * \param bridge_channel Channel to unsuspend.
+ *
+ * \note This function assumes bridge_channel->bridge is locked.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_unsuspend_nolock(struct ast_bridge_channel *bridge_channel)
{
- ao2_unlock(bridge_channel->bridge);
- ao2_lock(bridge_channel);
- if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
- 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));
+ bridge_channel->suspended = 0;
+ if (bridge_channel->in_bridge) {
+ ++bridge_channel->bridge->num_active;
}
- ao2_unlock(bridge_channel);
- ao2_lock(bridge_channel->bridge);
- return bridge_channel->state;
+ /* Wake technology bridge threads to take care of channel again. */
+ if (bridge_channel->bridge->technology->unsuspend) {
+ bridge_channel->bridge->technology->unsuspend(bridge_channel->bridge, bridge_channel);
+ }
+
+ /* Wake suspended channel. */
+ ast_bridge_channel_lock(bridge_channel);
+ ast_cond_signal(&bridge_channel->cond);
+ ast_bridge_channel_unlock(bridge_channel);
}
-/*! \brief Internal function that suspends a channel from a bridge */
-static void bridge_channel_suspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+/*!
+ * \internal
+ * \brief Unsuspend a channel from a bridge.
+ *
+ * \param bridge_channel Channel to unsuspend.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_unsuspend(struct ast_bridge_channel *bridge_channel)
{
- ao2_lock(bridge_channel);
- bridge_channel->suspended = 1;
- bridge_array_remove(bridge, bridge_channel->chan);
- ao2_unlock(bridge_channel);
-
- if (bridge->technology->suspend) {
- bridge->technology->suspend(bridge, bridge_channel);
- }
+ ast_bridge_channel_lock_bridge(bridge_channel);
+ bridge_channel_unsuspend_nolock(bridge_channel);
+ ast_bridge_unlock(bridge_channel->bridge);
}
-/*! \brief Internal function that unsuspends a channel from a bridge */
-static void bridge_channel_unsuspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+/*! \brief Internal function that activates interval hooks on a bridge channel */
+static void bridge_channel_interval(struct ast_bridge_channel *bridge_channel)
{
- ao2_lock(bridge_channel);
- bridge_channel->suspended = 0;
- bridge_array_add(bridge, bridge_channel->chan);
- ast_cond_signal(&bridge_channel->cond);
- ao2_unlock(bridge_channel);
+ struct ast_bridge_hook *hook;
+ struct timeval start;
+
+ ast_heap_wrlock(bridge_channel->features->interval_hooks);
+ start = ast_tvnow();
+ while ((hook = ast_heap_peek(bridge_channel->features->interval_hooks, 1))) {
+ int interval;
+ unsigned int execution_time;
+
+ if (ast_tvdiff_ms(hook->parms.timer.trip_time, start) > 0) {
+ ast_debug(1, "Hook %p on %p(%s) wants to happen in the future, stopping our traversal\n",
+ hook, bridge_channel, ast_channel_name(bridge_channel->chan));
+ break;
+ }
+ ao2_ref(hook, +1);
+ ast_heap_unlock(bridge_channel->features->interval_hooks);
+
+ ast_debug(1, "Executing hook %p on %p(%s)\n",
+ hook, bridge_channel, ast_channel_name(bridge_channel->chan));
+ interval = hook->callback(bridge_channel->bridge, bridge_channel, hook->hook_pvt);
+
+ ast_heap_wrlock(bridge_channel->features->interval_hooks);
+ if (ast_heap_peek(bridge_channel->features->interval_hooks,
+ hook->parms.timer.heap_index) != hook
+ || !ast_heap_remove(bridge_channel->features->interval_hooks, hook)) {
+ /* Interval hook is already removed from the bridge_channel. */
+ ao2_ref(hook, -1);
+ continue;
+ }
+ ao2_ref(hook, -1);
- if (bridge->technology->unsuspend) {
- bridge->technology->unsuspend(bridge, bridge_channel);
+ if (interval < 0) {
+ ast_debug(1, "Removed interval hook %p from %p(%s)\n",
+ hook, bridge_channel, ast_channel_name(bridge_channel->chan));
+ ao2_ref(hook, -1);
+ continue;
+ }
+ if (interval) {
+ /* Set new interval for the hook. */
+ hook->parms.timer.interval = interval;
+ }
+
+ ast_debug(1, "Updating interval hook %p with interval %u on %p(%s)\n",
+ hook, hook->parms.timer.interval, bridge_channel,
+ ast_channel_name(bridge_channel->chan));
+
+ /* resetting start */
+ start = ast_tvnow();
+
+ /*
+ * Resetup the interval hook for the next interval. We may need
+ * to skip over any missed intervals because the hook was
+ * delayed or took too long.
+ */
+ execution_time = ast_tvdiff_ms(start, hook->parms.timer.trip_time);
+ while (hook->parms.timer.interval < execution_time) {
+ execution_time -= hook->parms.timer.interval;
+ }
+ hook->parms.timer.trip_time = ast_tvadd(start, ast_samp2tv(hook->parms.timer.interval - execution_time, 1000));
+ hook->parms.timer.seqno = ast_atomic_fetchadd_int((int *) &bridge_channel->features->interval_sequence, +1);
+
+ if (ast_heap_push(bridge_channel->features->interval_hooks, hook)) {
+ /* Could not push the hook back onto the heap. */
+ ao2_ref(hook, -1);
+ }
}
+ ast_heap_unlock(bridge_channel->features->interval_hooks);
+}
+
+static void bridge_channel_write_dtmf_stream(struct ast_bridge_channel *bridge_channel, const char *dtmf)
+{
+ ast_bridge_channel_write_action_data(bridge_channel,
+ AST_BRIDGE_ACTION_DTMF_STREAM, dtmf, strlen(dtmf) + 1);
}
/*!
@@ -901,64 +1894,72 @@ static void bridge_channel_unsuspend(struct ast_bridge *bridge, struct ast_bridg
* \note Neither the bridge nor the bridge_channel locks should be held when entering
* this function.
*/
-static void bridge_channel_feature(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+static void bridge_channel_feature(struct ast_bridge_channel *bridge_channel)
{
- struct ast_bridge_features *features = (bridge_channel->features ? bridge_channel->features : &bridge->features);
- struct ast_bridge_features_hook *hook = NULL;
+ struct ast_bridge_features *features = bridge_channel->features;
+ struct ast_bridge_hook *hook = NULL;
char dtmf[MAXIMUM_DTMF_FEATURE_STRING] = "";
- int look_for_dtmf = 1, dtmf_len = 0;
+ size_t dtmf_len = 0;
/* The channel is now under our control and we don't really want any begin frames to do our DTMF matching so disable 'em at the core level */
ast_set_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_END_DTMF_ONLY);
/* Wait for DTMF on the channel and put it into a buffer. If the buffer matches any feature hook execute the hook. */
- while (look_for_dtmf) {
- int res = ast_waitfordigit(bridge_channel->chan, 3000);
+ do {
+ int res;
/* If the above timed out simply exit */
+ res = ast_waitfordigit(bridge_channel->chan, 3000);
if (!res) {
- ast_debug(1, "DTMF feature string collection on bridge channel %p timed out\n", bridge_channel);
+ ast_debug(1, "DTMF feature string collection on %p(%s) timed out\n",
+ bridge_channel, ast_channel_name(bridge_channel->chan));
break;
- } else if (res < 0) {
- ast_debug(1, "DTMF feature string collection failed on bridge channel %p for some reason\n", bridge_channel);
+ }
+ if (res < 0) {
+ ast_debug(1, "DTMF feature string collection failed on %p(%s) for some reason\n",
+ bridge_channel, ast_channel_name(bridge_channel->chan));
break;
}
+/* BUGBUG need to record the duration of DTMF digits so when the string is played back, they are reproduced. */
/* Add the above DTMF into the DTMF string so we can do our matching */
dtmf[dtmf_len++] = res;
-
- ast_debug(1, "DTMF feature string on bridge channel %p is now '%s'\n", bridge_channel, dtmf);
-
- /* Assume that we do not want to look for DTMF any longer */
- look_for_dtmf = 0;
+ ast_debug(1, "DTMF feature string on %p(%s) is now '%s'\n",
+ bridge_channel, ast_channel_name(bridge_channel->chan), dtmf);
/* See if a DTMF feature hook matches or can match */
- AST_LIST_TRAVERSE(&features->hooks, hook, entry) {
- /* If this hook matches just break out now */
- if (!strcmp(hook->dtmf, dtmf)) {
- ast_debug(1, "DTMF feature hook %p matched DTMF string '%s' on bridge channel %p\n", hook, dtmf, bridge_channel);
- look_for_dtmf = 0;
- break;
- } else if (!strncmp(hook->dtmf, dtmf, dtmf_len)) {
- ast_debug(1, "DTMF feature hook %p can match DTMF string '%s', it wants '%s', on bridge channel %p\n", hook, dtmf, hook->dtmf, bridge_channel);
- look_for_dtmf = 1;
- } else {
- ast_debug(1, "DTMF feature hook %p does not match DTMF string '%s', it wants '%s', on bridge channel %p\n", hook, dtmf, hook->dtmf, bridge_channel);
- }
+ hook = ao2_find(features->dtmf_hooks, dtmf, OBJ_PARTIAL_KEY);
+ if (!hook) {
+ ast_debug(1, "No DTMF feature hooks on %p(%s) match '%s'\n",
+ bridge_channel, ast_channel_name(bridge_channel->chan), dtmf);
+ break;
}
-
- /* If we have reached the maximum length of a DTMF feature string bail out */
- if (dtmf_len == MAXIMUM_DTMF_FEATURE_STRING) {
+ if (strlen(hook->parms.dtmf.code) == dtmf_len) {
+ ast_debug(1, "DTMF feature hook %p matched DTMF string '%s' on %p(%s)\n",
+ hook, dtmf, bridge_channel, ast_channel_name(bridge_channel->chan));
break;
}
- }
+ ao2_ref(hook, -1);
+ hook = NULL;
+
+ /* Stop if we have reached the maximum length of a DTMF feature string. */
+ } while (dtmf_len < ARRAY_LEN(dtmf) - 1);
/* Since we are done bringing DTMF in return to using both begin and end frames */
ast_clear_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_END_DTMF_ONLY);
/* If a hook was actually matched execute it on this channel, otherwise stream up the DTMF to the other channels */
if (hook) {
- hook->callback(bridge, bridge_channel, hook->hook_pvt);
+ int failed;
+
+ failed = hook->callback(bridge_channel->bridge, bridge_channel, hook->hook_pvt);
+ if (failed) {
+ ast_debug(1, "DTMF hook %p is being removed from %p(%s)\n",
+ hook, bridge_channel, ast_channel_name(bridge_channel->chan));
+ ao2_unlink(features->dtmf_hooks, hook);
+ }
+ ao2_ref(hook, -1);
+
/*
* If we are handing the channel off to an external hook for
* ownership, we are not guaranteed what kind of state it will
@@ -966,205 +1967,551 @@ static void bridge_channel_feature(struct ast_bridge *bridge, struct ast_bridge_
* here if the hook did not already change the state.
*/
if (bridge_channel->chan && ast_check_hangup_locked(bridge_channel->chan)) {
- ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+ bridge_handle_hangup(bridge_channel);
}
- } else {
- ast_bridge_dtmf_stream(bridge, dtmf, bridge_channel->chan);
+ } else if (features->dtmf_passthrough) {
+ bridge_channel_write_dtmf_stream(bridge_channel, dtmf);
}
}
-static void bridge_channel_talking(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+static void bridge_channel_talking(struct ast_bridge_channel *bridge_channel, int talking)
{
- struct ast_bridge_features *features = (bridge_channel->features ? bridge_channel->features : &bridge->features);
+ struct ast_bridge_features *features = bridge_channel->features;
- if (features && features->talker_cb) {
- features->talker_cb(bridge, bridge_channel, features->talker_pvt_data);
+ if (features->talker_cb) {
+ features->talker_cb(bridge_channel, features->talker_pvt_data, talking);
}
}
/*! \brief Internal function that plays back DTMF on a bridge channel */
-static void bridge_channel_dtmf_stream(struct ast_bridge_channel *bridge_channel)
+static void bridge_channel_dtmf_stream(struct ast_bridge_channel *bridge_channel, const char *dtmf)
+{
+ ast_debug(1, "Playing DTMF stream '%s' out to %p(%s)\n",
+ dtmf, bridge_channel, ast_channel_name(bridge_channel->chan));
+ ast_dtmf_stream(bridge_channel->chan, NULL, dtmf, 0, 0);
+}
+
+struct blind_transfer_data {
+ char exten[AST_MAX_EXTENSION];
+ char context[AST_MAX_CONTEXT];
+};
+
+static void bridge_channel_blind_transfer(struct ast_bridge_channel *bridge_channel,
+ struct blind_transfer_data *blind_data)
+{
+ ast_async_goto(bridge_channel->chan, blind_data->context, blind_data->exten, 1);
+ bridge_handle_hangup(bridge_channel);
+}
+
+/*!
+ * \internal
+ * \brief Handle bridge channel bridge action frame.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel to execute the action on.
+ * \param action What to do.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_handle_action(struct ast_bridge_channel *bridge_channel, struct ast_frame *action)
+{
+ switch (action->subclass.integer) {
+ case AST_BRIDGE_ACTION_INTERVAL:
+ bridge_channel_suspend(bridge_channel);
+ ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+ bridge_channel_interval(bridge_channel);
+ ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+ bridge_channel_unsuspend(bridge_channel);
+ break;
+ case AST_BRIDGE_ACTION_FEATURE:
+ bridge_channel_suspend(bridge_channel);
+ ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+ bridge_channel_feature(bridge_channel);
+ ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+ bridge_channel_unsuspend(bridge_channel);
+ break;
+ case AST_BRIDGE_ACTION_DTMF_STREAM:
+ bridge_channel_suspend(bridge_channel);
+ ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+ bridge_channel_dtmf_stream(bridge_channel, action->data.ptr);
+ ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+ bridge_channel_unsuspend(bridge_channel);
+ break;
+ case AST_BRIDGE_ACTION_TALKING_START:
+ case AST_BRIDGE_ACTION_TALKING_STOP:
+ bridge_channel_talking(bridge_channel,
+ action->subclass.integer == AST_BRIDGE_ACTION_TALKING_START);
+ break;
+ case AST_BRIDGE_ACTION_PLAY_FILE:
+ bridge_channel_suspend(bridge_channel);
+ ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+ bridge_channel_playfile(bridge_channel, action->data.ptr);
+ ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+ bridge_channel_unsuspend(bridge_channel);
+ break;
+ case AST_BRIDGE_ACTION_PARK:
+ bridge_channel_suspend(bridge_channel);
+ ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+ bridge_channel_park(bridge_channel, action->data.ptr);
+ ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+ bridge_channel_unsuspend(bridge_channel);
+ break;
+ case AST_BRIDGE_ACTION_RUN_APP:
+ bridge_channel_suspend(bridge_channel);
+ ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+ bridge_channel_run_app(bridge_channel, action->data.ptr);
+ ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+ bridge_channel_unsuspend(bridge_channel);
+ break;
+ case AST_BRIDGE_ACTION_BLIND_TRANSFER:
+ bridge_channel_blind_transfer(bridge_channel, action->data.ptr);
+ break;
+ default:
+ break;
+ }
+}
+
+/*!
+ * \internal
+ * \brief Handle bridge channel control frame action.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel to execute the control frame action on.
+ * \param fr Control frame to handle.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_handle_control(struct ast_bridge_channel *bridge_channel, struct ast_frame *fr)
+{
+ struct ast_channel *chan;
+ struct ast_option_header *aoh;
+ int is_caller;
+ int intercept_failed;
+
+ chan = bridge_channel->chan;
+ switch (fr->subclass.integer) {
+ case AST_CONTROL_REDIRECTING:
+ is_caller = !ast_test_flag(ast_channel_flags(chan), AST_FLAG_OUTGOING);
+ bridge_channel_suspend(bridge_channel);
+ intercept_failed = ast_channel_redirecting_sub(NULL, chan, fr, 1)
+ && ast_channel_redirecting_macro(NULL, chan, fr, is_caller, 1);
+ bridge_channel_unsuspend(bridge_channel);
+ if (intercept_failed) {
+ ast_indicate_data(chan, fr->subclass.integer, fr->data.ptr, fr->datalen);
+ }
+ break;
+ case AST_CONTROL_CONNECTED_LINE:
+ is_caller = !ast_test_flag(ast_channel_flags(chan), AST_FLAG_OUTGOING);
+ bridge_channel_suspend(bridge_channel);
+ intercept_failed = ast_channel_connected_line_sub(NULL, chan, fr, 1)
+ && ast_channel_connected_line_macro(NULL, chan, fr, is_caller, 1);
+ bridge_channel_unsuspend(bridge_channel);
+ if (intercept_failed) {
+ ast_indicate_data(chan, fr->subclass.integer, fr->data.ptr, fr->datalen);
+ }
+ break;
+ case AST_CONTROL_HOLD:
+ case AST_CONTROL_UNHOLD:
+/*
+ * BUGBUG bridge_channels should remember sending/receiving an outstanding HOLD to/from the bridge
+ *
+ * When the sending channel is pulled from the bridge it needs to write into the bridge an UNHOLD before being pulled.
+ * When the receiving channel is pulled from the bridge it needs to generate its own UNHOLD.
+ * Something similar needs to be done for DTMF begin/end.
+ */
+ case AST_CONTROL_VIDUPDATE:
+ case AST_CONTROL_SRCUPDATE:
+ case AST_CONTROL_SRCCHANGE:
+ case AST_CONTROL_T38_PARAMETERS:
+/* BUGBUG may have to do something with a jitter buffer for these. */
+ ast_indicate_data(chan, fr->subclass.integer, fr->data.ptr, fr->datalen);
+ break;
+ case AST_CONTROL_OPTION:
+ /*
+ * Forward option Requests, but only ones we know are safe These
+ * are ONLY sent by chan_iax2 and I'm not convinced that they
+ * are useful. I haven't deleted them entirely because I just am
+ * not sure of the ramifications of removing them.
+ */
+ aoh = fr->data.ptr;
+ if (aoh && aoh->flag == AST_OPTION_FLAG_REQUEST) {
+ switch (ntohs(aoh->option)) {
+ case AST_OPTION_TONE_VERIFY:
+ case AST_OPTION_TDD:
+ case AST_OPTION_RELAXDTMF:
+ case AST_OPTION_AUDIO_MODE:
+ case AST_OPTION_DIGIT_DETECT:
+ case AST_OPTION_FAX_DETECT:
+ ast_channel_setoption(chan, ntohs(aoh->option), aoh->data,
+ fr->datalen - sizeof(*aoh), 0);
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ case AST_CONTROL_ANSWER:
+ if (ast_channel_state(chan) != AST_STATE_UP) {
+ ast_answer(chan);
+ } else {
+ ast_indicate(chan, -1);
+ }
+ break;
+ default:
+ ast_indicate_data(chan, fr->subclass.integer, fr->data.ptr, fr->datalen);
+ break;
+ }
+}
+
+/*!
+ * \internal
+ * \brief Handle bridge channel write frame to channel.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel to write outgoing frame.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_handle_write(struct ast_bridge_channel *bridge_channel)
+{
+ struct ast_frame *fr;
+ char nudge;
+
+ ast_bridge_channel_lock(bridge_channel);
+ if (read(bridge_channel->alert_pipe[0], &nudge, sizeof(nudge)) < 0) {
+ if (errno != EINTR && errno != EAGAIN) {
+ ast_log(LOG_WARNING, "read() failed for alert pipe on %p(%s): %s\n",
+ bridge_channel, ast_channel_name(bridge_channel->chan), strerror(errno));
+ }
+ }
+ fr = AST_LIST_REMOVE_HEAD(&bridge_channel->wr_queue, frame_list);
+ ast_bridge_channel_unlock(bridge_channel);
+ if (!fr) {
+ return;
+ }
+ switch (fr->frametype) {
+ case AST_FRAME_BRIDGE_ACTION:
+ bridge_channel_handle_action(bridge_channel, fr);
+ break;
+ case AST_FRAME_CONTROL:
+ bridge_channel_handle_control(bridge_channel, fr);
+ break;
+ case AST_FRAME_NULL:
+ break;
+ default:
+ /* Write the frame to the channel. */
+ bridge_channel->activity = AST_BRIDGE_CHANNEL_THREAD_SIMPLE;
+ ast_write(bridge_channel->chan, fr);
+ break;
+ }
+ ast_frfree(fr);
+}
+
+/*!
+ * \internal
+ * \brief Handle bridge channel interval expiration.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel to check interval on.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_handle_interval(struct ast_bridge_channel *bridge_channel)
+{
+ struct ast_timer *interval_timer;
+
+ interval_timer = bridge_channel->features->interval_timer;
+ if (interval_timer) {
+ if (ast_wait_for_input(ast_timer_fd(interval_timer), 0) == 1) {
+ ast_timer_ack(interval_timer, 1);
+ if (bridge_channel_interval_ready(bridge_channel)) {
+/* BUGBUG since this is now only run by the channel thread, there is no need to queue the action once this intervals become a first class wait item in bridge_channel_wait(). */
+ struct ast_frame interval_action = {
+ .frametype = AST_FRAME_BRIDGE_ACTION,
+ .subclass.integer = AST_BRIDGE_ACTION_INTERVAL,
+ };
+
+ ast_bridge_channel_queue_frame(bridge_channel, &interval_action);
+ }
+ }
+ }
+}
+
+/*!
+ * \internal
+ * \brief Wait for something to happen on the bridge channel and handle it.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel to wait.
+ *
+ * \note Each channel does writing/reading in their own thread.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_wait(struct ast_bridge_channel *bridge_channel)
+{
+ int ms = -1;
+ int outfd;
+ struct ast_channel *chan;
+
+ /* Wait for data to either come from the channel or us to be signaled */
+ ast_bridge_channel_lock(bridge_channel);
+ if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+ } else if (bridge_channel->suspended) {
+/* BUGBUG the external party use of suspended will go away as will these references because this is the bridge channel thread */
+ ast_debug(1, "Bridge %s: %p(%s) is going into a signal wait\n",
+ bridge_channel->bridge->uniqueid, bridge_channel,
+ ast_channel_name(bridge_channel->chan));
+ ast_cond_wait(&bridge_channel->cond, ao2_object_get_lockaddr(bridge_channel));
+ } else {
+ ast_debug(10, "Bridge %s: %p(%s) is going into a waitfor\n",
+ bridge_channel->bridge->uniqueid, bridge_channel,
+ ast_channel_name(bridge_channel->chan));
+ bridge_channel->waiting = 1;
+ ast_bridge_channel_unlock(bridge_channel);
+ outfd = -1;
+/* BUGBUG need to make the next expiring active interval setup ms timeout rather than holding up the chan reads. */
+ chan = ast_waitfor_nandfds(&bridge_channel->chan, 1,
+ &bridge_channel->alert_pipe[0], 1, NULL, &outfd, &ms);
+ bridge_channel->waiting = 0;
+ if (ast_channel_softhangup_internal_flag(bridge_channel->chan) & AST_SOFTHANGUP_UNBRIDGE) {
+ ast_channel_clear_softhangup(bridge_channel->chan, AST_SOFTHANGUP_UNBRIDGE);
+ ast_bridge_channel_lock_bridge(bridge_channel);
+ bridge_channel->bridge->reconfigured = 1;
+ bridge_reconfigured(bridge_channel->bridge);
+ ast_bridge_unlock(bridge_channel->bridge);
+ }
+ ast_bridge_channel_lock(bridge_channel);
+ bridge_channel->activity = AST_BRIDGE_CHANNEL_THREAD_FRAME;
+ ast_bridge_channel_unlock(bridge_channel);
+ if (!bridge_channel->suspended
+ && bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
+ if (chan) {
+ bridge_channel_handle_interval(bridge_channel);
+ bridge_handle_trip(bridge_channel);
+ } else if (-1 < outfd) {
+ bridge_channel_handle_write(bridge_channel);
+ }
+ }
+ bridge_channel->activity = AST_BRIDGE_CHANNEL_THREAD_IDLE;
+ return;
+ }
+ ast_bridge_channel_unlock(bridge_channel);
+}
+
+/*!
+ * \internal
+ * \brief Handle bridge channel join event.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel is joining.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_handle_join(struct ast_bridge_channel *bridge_channel)
{
- char dtmf_q[8] = "";
+ struct ast_bridge_features *features = bridge_channel->features;
+ struct ast_bridge_hook *hook;
+ struct ao2_iterator iter;
- ast_copy_string(dtmf_q, bridge_channel->dtmf_stream_q, sizeof(dtmf_q));
- bridge_channel->dtmf_stream_q[0] = '\0';
+ /* Run any join hooks. */
+ iter = ao2_iterator_init(features->join_hooks, AO2_ITERATOR_UNLINK);
+ hook = ao2_iterator_next(&iter);
+ if (hook) {
+ bridge_channel_suspend(bridge_channel);
+ ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+ do {
+ hook->callback(bridge_channel->bridge, bridge_channel, hook->hook_pvt);
+ ao2_ref(hook, -1);
+ } while ((hook = ao2_iterator_next(&iter)));
+ ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+ bridge_channel_unsuspend(bridge_channel);
+ }
+ ao2_iterator_destroy(&iter);
+}
- ast_debug(1, "Playing DTMF stream '%s' out to bridge channel %p\n", dtmf_q, bridge_channel);
- ast_dtmf_stream(bridge_channel->chan, NULL, dtmf_q, 250, 0);
+/*!
+ * \internal
+ * \brief Handle bridge channel leave event.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel is leaving.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_handle_leave(struct ast_bridge_channel *bridge_channel)
+{
+ struct ast_bridge_features *features = bridge_channel->features;
+ struct ast_bridge_hook *hook;
+ struct ao2_iterator iter;
+
+ /* Run any leave hooks. */
+ iter = ao2_iterator_init(features->leave_hooks, AO2_ITERATOR_UNLINK);
+ hook = ao2_iterator_next(&iter);
+ if (hook) {
+ bridge_channel_suspend(bridge_channel);
+ ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+ do {
+ hook->callback(bridge_channel->bridge, bridge_channel, hook->hook_pvt);
+ ao2_ref(hook, -1);
+ } while ((hook = ao2_iterator_next(&iter)));
+ ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+ bridge_channel_unsuspend(bridge_channel);
+ }
+ ao2_iterator_destroy(&iter);
}
/*! \brief Join a channel to a bridge and handle anything the bridge may want us to do */
-static enum ast_bridge_channel_state bridge_channel_join(struct ast_bridge_channel *bridge_channel)
+static void bridge_channel_join(struct ast_bridge_channel *bridge_channel)
{
- struct ast_format formats[2];
- enum ast_bridge_channel_state state;
- ast_format_copy(&formats[0], ast_channel_readformat(bridge_channel->chan));
- ast_format_copy(&formats[1], ast_channel_writeformat(bridge_channel->chan));
+ 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));
- /* Record the thread that will be the owner of us */
- bridge_channel->thread = pthread_self();
+ ast_debug(1, "Bridge %s: %p(%s) is joining\n",
+ bridge_channel->bridge->uniqueid,
+ bridge_channel, ast_channel_name(bridge_channel->chan));
+
+ /*
+ * Get "in the bridge" before pushing the channel for any
+ * masquerades on the channel to happen before bridging.
+ */
+ ast_channel_lock(bridge_channel->chan);
+ ast_channel_internal_bridge_set(bridge_channel->chan, bridge_channel->bridge);
+ ast_channel_unlock(bridge_channel->chan);
- ast_debug(1, "Joining bridge channel %p to bridge %p\n", bridge_channel, bridge_channel->bridge);
+ /* Add the jitterbuffer if the channel requires it */
+ ast_jb_enable_for_channel(bridge_channel->chan);
- ao2_lock(bridge_channel->bridge);
+ /*
+ * Directly locking the bridge is safe here because nobody else
+ * knows about this bridge_channel yet.
+ */
+ ast_bridge_lock(bridge_channel->bridge);
if (!bridge_channel->bridge->callid) {
bridge_channel->bridge->callid = ast_read_threadstorage_callid();
}
- /* Add channel into the bridge */
- AST_LIST_INSERT_TAIL(&bridge_channel->bridge->channels, bridge_channel, entry);
- bridge_channel->bridge->num++;
-
- bridge_array_add(bridge_channel->bridge, bridge_channel->chan);
-
- if (bridge_channel->swap) {
- struct ast_bridge_channel *bridge_channel2;
+ if (bridge_channel_push(bridge_channel)) {
+ ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+ }
+ bridge_reconfigured(bridge_channel->bridge);
+ if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
/*
- * If we are performing a swap operation we do not need to
- * execute the smart bridge operation as the actual number of
- * channels involved will not have changed, we just need to tell
- * the other channel to leave.
+ * Indicate a source change since this channel is entering the
+ * bridge system only if the bridge technology is not MULTIMIX
+ * capable. The MULTIMIX technology has already done it.
*/
- bridge_channel2 = find_bridge_channel(bridge_channel->bridge, bridge_channel->swap);
- bridge_channel->swap = NULL;
- if (bridge_channel2) {
- ast_debug(1, "Swapping bridge channel %p out from bridge %p so bridge channel %p can slip in\n", bridge_channel2, bridge_channel->bridge, bridge_channel);
- ast_bridge_change_state(bridge_channel2, AST_BRIDGE_CHANNEL_STATE_HANGUP);
- }
- } else if (ast_test_flag(&bridge_channel->bridge->feature_flags, AST_BRIDGE_FLAG_SMART)) {
- /* Perform the smart bridge operation, basically see if we need to move around between technologies */
- smart_bridge_operation(bridge_channel->bridge, bridge_channel, bridge_channel->bridge->num);
- }
-
- /* Make the channel compatible with the bridge */
- bridge_make_compatible(bridge_channel->bridge, bridge_channel);
-
- /* Tell the bridge technology we are joining so they set us up */
- if (bridge_channel->bridge->technology->join) {
- ast_debug(1, "Giving bridge technology %s notification that %p is joining bridge %p\n", bridge_channel->bridge->technology->name, bridge_channel, bridge_channel->bridge);
- if (bridge_channel->bridge->technology->join(bridge_channel->bridge, bridge_channel)) {
- ast_debug(1, "Bridge technology %s failed to join %p to bridge %p\n", bridge_channel->bridge->technology->name, bridge_channel, bridge_channel->bridge);
- }
- }
-
- /* Actually execute the respective threading model, and keep our bridge thread alive */
- while (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
- /* Update bridge pointer on channel */
- ast_channel_internal_bridge_set(bridge_channel->chan, bridge_channel->bridge);
- /* If the technology requires a thread and one is not running, start it up */
- if (bridge_channel->bridge->thread == AST_PTHREADT_NULL
- && (bridge_channel->bridge->technology->capabilities & AST_BRIDGE_CAPABILITY_THREAD)) {
- bridge_channel->bridge->stop = 0;
- ast_debug(1, "Starting a bridge thread for bridge %p\n", bridge_channel->bridge);
- ao2_ref(bridge_channel->bridge, +1);
- if (ast_pthread_create(&bridge_channel->bridge->thread, NULL, bridge_thread, bridge_channel->bridge)) {
- ast_debug(1, "Failed to create a bridge thread for bridge %p, giving it another go.\n", bridge_channel->bridge);
- ao2_ref(bridge_channel->bridge, -1);
- continue;
- }
+ if (!(bridge_channel->bridge->technology->capabilities
+ & AST_BRIDGE_CAPABILITY_MULTIMIX)) {
+ ast_indicate(bridge_channel->chan, AST_CONTROL_SRCCHANGE);
}
- /* Execute the threading model */
- state = (bridge_channel->bridge->technology->capabilities & AST_BRIDGE_CAPABILITY_MULTITHREADED)
- ? bridge_channel_join_multithreaded(bridge_channel)
- : bridge_channel_join_singlethreaded(bridge_channel);
- /* Depending on the above state see what we need to do */
- switch (state) {
- case AST_BRIDGE_CHANNEL_STATE_FEATURE:
- bridge_channel_suspend(bridge_channel->bridge, bridge_channel);
- ao2_unlock(bridge_channel->bridge);
- bridge_channel_feature(bridge_channel->bridge, bridge_channel);
- ao2_lock(bridge_channel->bridge);
- ao2_lock(bridge_channel);
- if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_FEATURE) {
- ast_bridge_change_state_nolock(bridge_channel, AST_BRIDGE_CHANNEL_STATE_WAIT);
- }
- ao2_unlock(bridge_channel);
- bridge_channel_unsuspend(bridge_channel->bridge, bridge_channel);
- break;
- case AST_BRIDGE_CHANNEL_STATE_DTMF:
- bridge_channel_suspend(bridge_channel->bridge, bridge_channel);
- ao2_unlock(bridge_channel->bridge);
- bridge_channel_dtmf_stream(bridge_channel);
- ao2_lock(bridge_channel->bridge);
- ao2_lock(bridge_channel);
- if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_DTMF) {
- ast_bridge_change_state_nolock(bridge_channel, AST_BRIDGE_CHANNEL_STATE_WAIT);
- }
- ao2_unlock(bridge_channel);
- bridge_channel_unsuspend(bridge_channel->bridge, bridge_channel);
- break;
- case AST_BRIDGE_CHANNEL_STATE_START_TALKING:
- case AST_BRIDGE_CHANNEL_STATE_STOP_TALKING:
- ao2_unlock(bridge_channel->bridge);
- bridge_channel_talking(bridge_channel->bridge, bridge_channel);
- ao2_lock(bridge_channel->bridge);
- ao2_lock(bridge_channel);
- switch (bridge_channel->state) {
- case AST_BRIDGE_CHANNEL_STATE_START_TALKING:
- case AST_BRIDGE_CHANNEL_STATE_STOP_TALKING:
- ast_bridge_change_state_nolock(bridge_channel, AST_BRIDGE_CHANNEL_STATE_WAIT);
- break;
- default:
- break;
- }
- ao2_unlock(bridge_channel);
- break;
- default:
- break;
+
+ ast_bridge_unlock(bridge_channel->bridge);
+ bridge_channel_handle_join(bridge_channel);
+ while (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
+ /* Wait for something to do. */
+ bridge_channel_wait(bridge_channel);
}
+ bridge_channel_handle_leave(bridge_channel);
+ ast_bridge_channel_lock_bridge(bridge_channel);
}
- ast_channel_internal_bridge_set(bridge_channel->chan, NULL);
+ bridge_channel_pull(bridge_channel);
+ bridge_reconfigured(bridge_channel->bridge);
- /* See if we need to dissolve the bridge itself if they hung up */
- if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_END) {
- bridge_check_dissolve(bridge_channel->bridge, bridge_channel);
- }
+ ast_bridge_unlock(bridge_channel->bridge);
- /* Tell the bridge technology we are leaving so they tear us down */
- if (bridge_channel->bridge->technology->leave) {
- ast_debug(1, "Giving bridge technology %s notification that %p is leaving bridge %p\n", bridge_channel->bridge->technology->name, bridge_channel, bridge_channel->bridge);
- if (bridge_channel->bridge->technology->leave(bridge_channel->bridge, bridge_channel)) {
- ast_debug(1, "Bridge technology %s failed to leave %p from bridge %p\n", bridge_channel->bridge->technology->name, bridge_channel, bridge_channel->bridge);
- }
+ /* Indicate a source change since this channel is leaving the bridge system. */
+ ast_indicate(bridge_channel->chan, AST_CONTROL_SRCCHANGE);
+
+/* BUGBUG Revisit in regards to moving channels between bridges and local channel optimization. */
+/* BUGBUG This is where outgoing HOLD/UNHOLD memory should write UNHOLD to channel. */
+ /* Complete any partial DTMF digit before exiting the bridge. */
+ if (ast_channel_sending_dtmf_digit(bridge_channel->chan)) {
+ ast_bridge_end_dtmf(bridge_channel->chan,
+ ast_channel_sending_dtmf_digit(bridge_channel->chan),
+ ast_channel_sending_dtmf_tv(bridge_channel->chan), "bridge end");
}
- /* Remove channel from the bridge */
- bridge_channel->bridge->num--;
- AST_LIST_REMOVE(&bridge_channel->bridge->channels, bridge_channel, entry);
+ /*
+ * Wait for any dual redirect to complete.
+ *
+ * Must be done while "still in the bridge" for ast_async_goto()
+ * to work right.
+ */
+ while (ast_test_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT)) {
+ sched_yield();
+ }
+ ast_channel_lock(bridge_channel->chan);
+ ast_channel_internal_bridge_set(bridge_channel->chan, NULL);
+ ast_channel_unlock(bridge_channel->chan);
- bridge_array_remove(bridge_channel->bridge, bridge_channel->chan);
+ ast_bridge_channel_restore_formats(bridge_channel);
+}
- /* Perform the smart bridge operation if needed since a channel has left */
- if (ast_test_flag(&bridge_channel->bridge->feature_flags, AST_BRIDGE_FLAG_SMART)) {
- smart_bridge_operation(bridge_channel->bridge, NULL, bridge_channel->bridge->num);
+/*!
+ * \internal
+ * \brief Close a pipe.
+ * \since 12.0.0
+ *
+ * \param my_pipe What to close.
+ *
+ * \return Nothing
+ */
+static void pipe_close(int *my_pipe)
+{
+ if (my_pipe[0] > -1) {
+ close(my_pipe[0]);
+ my_pipe[0] = -1;
+ }
+ if (my_pipe[1] > -1) {
+ close(my_pipe[1]);
+ my_pipe[1] = -1;
}
+}
- ao2_unlock(bridge_channel->bridge);
+/*!
+ * \internal
+ * \brief Initialize a pipe as non-blocking.
+ * \since 12.0.0
+ *
+ * \param my_pipe What to initialize.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int pipe_init_nonblock(int *my_pipe)
+{
+ int flags;
- /* Restore original formats of the channel as they came in */
- if (ast_format_cmp(ast_channel_readformat(bridge_channel->chan), &formats[0]) == AST_FORMAT_CMP_NOT_EQUAL) {
- ast_debug(1, "Bridge is returning %p to read format %s(%d)\n", bridge_channel, ast_getformatname(&formats[0]), formats[0].id);
- if (ast_set_read_format(bridge_channel->chan, &formats[0])) {
- ast_debug(1, "Bridge failed to return channel %p to read format %s(%d)\n", bridge_channel, ast_getformatname(&formats[0]), formats[0].id);
- }
+ my_pipe[0] = -1;
+ my_pipe[1] = -1;
+ if (pipe(my_pipe)) {
+ ast_log(LOG_WARNING, "Can't create pipe! Try increasing max file descriptors with ulimit -n\n");
+ return -1;
}
- if (ast_format_cmp(ast_channel_writeformat(bridge_channel->chan), &formats[1]) == AST_FORMAT_CMP_NOT_EQUAL) {
- ast_debug(1, "Bridge is returning %p to write format %s(%d)\n", bridge_channel, ast_getformatname(&formats[1]), formats[1].id);
- if (ast_set_write_format(bridge_channel->chan, &formats[1])) {
- ast_debug(1, "Bridge failed to return channel %p to write format %s(%d)\n", bridge_channel, ast_getformatname(&formats[1]), formats[1].id);
- }
+ flags = fcntl(my_pipe[0], F_GETFL);
+ if (fcntl(my_pipe[0], F_SETFL, flags | O_NONBLOCK) < 0) {
+ ast_log(LOG_WARNING, "Unable to set read pipe nonblocking! (%d: %s)\n",
+ errno, strerror(errno));
+ return -1;
}
-
- return bridge_channel->state;
+ flags = fcntl(my_pipe[1], F_GETFL);
+ if (fcntl(my_pipe[1], F_SETFL, flags | O_NONBLOCK) < 0) {
+ ast_log(LOG_WARNING, "Unable to set write pipe nonblocking! (%d: %s)\n",
+ errno, strerror(errno));
+ return -1;
+ }
+ return 0;
}
+/* Destroy elements of the bridge channel structure and the bridge channel structure itself */
static void bridge_channel_destroy(void *obj)
{
struct ast_bridge_channel *bridge_channel = obj;
+ struct ast_frame *fr;
if (bridge_channel->callid) {
bridge_channel->callid = ast_callid_unref(bridge_channel->callid);
@@ -1174,7 +2521,13 @@ static void bridge_channel_destroy(void *obj)
ao2_ref(bridge_channel->bridge, -1);
bridge_channel->bridge = NULL;
}
- /* Destroy elements of the bridge channel structure and the bridge channel structure itself */
+
+ /* Flush any unhandled wr_queue frames. */
+ while ((fr = AST_LIST_REMOVE_HEAD(&bridge_channel->wr_queue, frame_list))) {
+ ast_frfree(fr);
+ }
+ pipe_close(bridge_channel->alert_pipe);
+
ast_cond_destroy(&bridge_channel->cond);
}
@@ -1187,94 +2540,667 @@ static struct ast_bridge_channel *bridge_channel_alloc(struct ast_bridge *bridge
return NULL;
}
ast_cond_init(&bridge_channel->cond, NULL);
+ if (pipe_init_nonblock(bridge_channel->alert_pipe)) {
+ ao2_ref(bridge_channel, -1);
+ return NULL;
+ }
if (bridge) {
bridge_channel->bridge = bridge;
ao2_ref(bridge_channel->bridge, +1);
}
+
return bridge_channel;
}
+struct after_bridge_cb_ds {
+ /*! Desired callback function. */
+ ast_after_bridge_cb callback;
+ /*! After bridge callback will not be called and destroy any resources data may contain. */
+ ast_after_bridge_cb_failed failed;
+ /*! Extra data to pass to the callback. */
+ void *data;
+};
+
+/*!
+ * \internal
+ * \brief Destroy the after bridge callback datastore.
+ * \since 12.0.0
+ *
+ * \param data After bridge callback data to destroy.
+ *
+ * \return Nothing
+ */
+static void after_bridge_cb_destroy(void *data)
+{
+ struct after_bridge_cb_ds *after_bridge = data;
+
+ if (after_bridge->failed) {
+ after_bridge->failed(AST_AFTER_BRIDGE_CB_REASON_DESTROY, after_bridge->data);
+ after_bridge->failed = NULL;
+ }
+}
+
+/*!
+ * \internal
+ * \brief Fixup the after bridge callback datastore.
+ * \since 12.0.0
+ *
+ * \param data After bridge callback data to fixup.
+ * \param old_chan The datastore is moving from this channel.
+ * \param new_chan The datastore is moving to this channel.
+ *
+ * \return Nothing
+ */
+static void after_bridge_cb_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
+{
+ /* There can be only one. Discard any already on the new channel. */
+ ast_after_bridge_callback_discard(new_chan, AST_AFTER_BRIDGE_CB_REASON_MASQUERADE);
+}
+
+static const struct ast_datastore_info after_bridge_cb_info = {
+ .type = "after-bridge-cb",
+ .destroy = after_bridge_cb_destroy,
+ .chan_fixup = after_bridge_cb_fixup,
+};
+
+/*!
+ * \internal
+ * \brief Remove channel after the bridge callback and return it.
+ * \since 12.0.0
+ *
+ * \param chan Channel to remove after bridge callback.
+ *
+ * \retval datastore on success.
+ * \retval NULL on error or not found.
+ */
+static struct ast_datastore *after_bridge_cb_remove(struct ast_channel *chan)
+{
+ struct ast_datastore *datastore;
+
+ ast_channel_lock(chan);
+ datastore = ast_channel_datastore_find(chan, &after_bridge_cb_info, NULL);
+ if (datastore && ast_channel_datastore_remove(chan, datastore)) {
+ datastore = NULL;
+ }
+ ast_channel_unlock(chan);
+
+ return datastore;
+}
+
+void ast_after_bridge_callback_discard(struct ast_channel *chan, enum ast_after_bridge_cb_reason reason)
+{
+ struct ast_datastore *datastore;
+
+ datastore = after_bridge_cb_remove(chan);
+ if (datastore) {
+ struct after_bridge_cb_ds *after_bridge = datastore->data;
+
+ if (after_bridge && after_bridge->failed) {
+ after_bridge->failed(reason, after_bridge->data);
+ after_bridge->failed = NULL;
+ }
+ ast_datastore_free(datastore);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Run any after bridge callback if possible.
+ * \since 12.0.0
+ *
+ * \param chan Channel to run after bridge callback.
+ *
+ * \return Nothing
+ */
+static void after_bridge_callback_run(struct ast_channel *chan)
+{
+ struct ast_datastore *datastore;
+ struct after_bridge_cb_ds *after_bridge;
+
+ if (ast_check_hangup(chan)) {
+ return;
+ }
+
+ /* Get after bridge goto datastore. */
+ datastore = after_bridge_cb_remove(chan);
+ if (!datastore) {
+ return;
+ }
+
+ after_bridge = datastore->data;
+ if (after_bridge) {
+ after_bridge->failed = NULL;
+ after_bridge->callback(chan, after_bridge->data);
+ }
+
+ /* Discard after bridge callback datastore. */
+ ast_datastore_free(datastore);
+}
+
+int ast_after_bridge_callback_set(struct ast_channel *chan, ast_after_bridge_cb callback, ast_after_bridge_cb_failed failed, void *data)
+{
+ struct ast_datastore *datastore;
+ struct after_bridge_cb_ds *after_bridge;
+
+ /* Sanity checks. */
+ ast_assert(chan != NULL);
+ if (!chan || !callback) {
+ return -1;
+ }
+
+ /* Create a new datastore. */
+ datastore = ast_datastore_alloc(&after_bridge_cb_info, NULL);
+ if (!datastore) {
+ return -1;
+ }
+ after_bridge = ast_calloc(1, sizeof(*after_bridge));
+ if (!after_bridge) {
+ ast_datastore_free(datastore);
+ return -1;
+ }
+
+ /* Initialize it. */
+ after_bridge->callback = callback;
+ after_bridge->failed = failed;
+ after_bridge->data = data;
+ datastore->data = after_bridge;
+
+ /* Put it on the channel replacing any existing one. */
+ ast_channel_lock(chan);
+ ast_after_bridge_callback_discard(chan, AST_AFTER_BRIDGE_CB_REASON_REPLACED);
+ ast_channel_datastore_add(chan, datastore);
+ ast_channel_unlock(chan);
+
+ return 0;
+}
+
+struct after_bridge_goto_ds {
+ /*! Goto string that can be parsed by ast_parseable_goto(). */
+ const char *parseable_goto;
+ /*! Specific goto context or default context for parseable_goto. */
+ const char *context;
+ /*! Specific goto exten or default exten for parseable_goto. */
+ const char *exten;
+ /*! Specific goto priority or default priority for parseable_goto. */
+ int priority;
+ /*! TRUE if the peer should run the h exten. */
+ unsigned int run_h_exten:1;
+ /*! Specific goto location */
+ unsigned int specific:1;
+};
+
+/*!
+ * \internal
+ * \brief Destroy the after bridge goto datastore.
+ * \since 12.0.0
+ *
+ * \param data After bridge goto data to destroy.
+ *
+ * \return Nothing
+ */
+static void after_bridge_goto_destroy(void *data)
+{
+ struct after_bridge_goto_ds *after_bridge = data;
+
+ ast_free((char *) after_bridge->parseable_goto);
+ ast_free((char *) after_bridge->context);
+ ast_free((char *) after_bridge->exten);
+}
+
+/*!
+ * \internal
+ * \brief Fixup the after bridge goto datastore.
+ * \since 12.0.0
+ *
+ * \param data After bridge goto data to fixup.
+ * \param old_chan The datastore is moving from this channel.
+ * \param new_chan The datastore is moving to this channel.
+ *
+ * \return Nothing
+ */
+static void after_bridge_goto_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
+{
+ /* There can be only one. Discard any already on the new channel. */
+ ast_after_bridge_goto_discard(new_chan);
+}
+
+static const struct ast_datastore_info after_bridge_goto_info = {
+ .type = "after-bridge-goto",
+ .destroy = after_bridge_goto_destroy,
+ .chan_fixup = after_bridge_goto_fixup,
+};
+
+/*!
+ * \internal
+ * \brief Remove channel goto location after the bridge and return it.
+ * \since 12.0.0
+ *
+ * \param chan Channel to remove after bridge goto location.
+ *
+ * \retval datastore on success.
+ * \retval NULL on error or not found.
+ */
+static struct ast_datastore *after_bridge_goto_remove(struct ast_channel *chan)
+{
+ struct ast_datastore *datastore;
+
+ ast_channel_lock(chan);
+ datastore = ast_channel_datastore_find(chan, &after_bridge_goto_info, NULL);
+ if (datastore && ast_channel_datastore_remove(chan, datastore)) {
+ datastore = NULL;
+ }
+ ast_channel_unlock(chan);
+
+ return datastore;
+}
+
+void ast_after_bridge_goto_discard(struct ast_channel *chan)
+{
+ struct ast_datastore *datastore;
+
+ datastore = after_bridge_goto_remove(chan);
+ if (datastore) {
+ ast_datastore_free(datastore);
+ }
+}
+
+int ast_after_bridge_goto_setup(struct ast_channel *chan)
+{
+ struct ast_datastore *datastore;
+ struct after_bridge_goto_ds *after_bridge;
+ int goto_failed = -1;
+
+ /* Determine if we are going to setup a dialplan location and where. */
+ if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) {
+ /* An async goto has already setup a location. */
+ ast_channel_clear_softhangup(chan, AST_SOFTHANGUP_ASYNCGOTO);
+ if (!ast_check_hangup(chan)) {
+ goto_failed = 0;
+ }
+ return goto_failed;
+ }
+
+ /* Get after bridge goto datastore. */
+ datastore = after_bridge_goto_remove(chan);
+ if (!datastore) {
+ return goto_failed;
+ }
+
+ after_bridge = datastore->data;
+ if (after_bridge->run_h_exten) {
+ if (ast_exists_extension(chan, after_bridge->context, "h", 1,
+ S_COR(ast_channel_caller(chan)->id.number.valid,
+ ast_channel_caller(chan)->id.number.str, NULL))) {
+ ast_debug(1, "Running after bridge goto h exten %s,h,1\n",
+ ast_channel_context(chan));
+ ast_pbx_h_exten_run(chan, after_bridge->context);
+ }
+ } else if (!ast_check_hangup(chan)) {
+ if (after_bridge->specific) {
+ goto_failed = ast_explicit_goto(chan, after_bridge->context,
+ after_bridge->exten, after_bridge->priority);
+ } else if (!ast_strlen_zero(after_bridge->parseable_goto)) {
+ char *context;
+ char *exten;
+ int priority;
+
+ /* Option F(x) for Bridge(), Dial(), and Queue() */
+
+ /* Save current dialplan location in case of failure. */
+ context = ast_strdupa(ast_channel_context(chan));
+ exten = ast_strdupa(ast_channel_exten(chan));
+ priority = ast_channel_priority(chan);
+
+ /* Set current dialplan position to default dialplan position */
+ ast_explicit_goto(chan, after_bridge->context, after_bridge->exten,
+ after_bridge->priority);
+
+ /* Then perform the goto */
+ goto_failed = ast_parseable_goto(chan, after_bridge->parseable_goto);
+ if (goto_failed) {
+ /* Restore original dialplan location. */
+ ast_channel_context_set(chan, context);
+ ast_channel_exten_set(chan, exten);
+ ast_channel_priority_set(chan, priority);
+ }
+ } else {
+ /* Option F() for Bridge(), Dial(), and Queue() */
+ goto_failed = ast_goto_if_exists(chan, after_bridge->context,
+ after_bridge->exten, after_bridge->priority + 1);
+ }
+ if (!goto_failed) {
+ ast_debug(1, "Setup after bridge goto location to %s,%s,%d.\n",
+ ast_channel_context(chan),
+ ast_channel_exten(chan),
+ ast_channel_priority(chan));
+ }
+ }
+
+ /* Discard after bridge goto datastore. */
+ ast_datastore_free(datastore);
+
+ return goto_failed;
+}
+
+void ast_after_bridge_goto_run(struct ast_channel *chan)
+{
+ int goto_failed;
+
+ goto_failed = ast_after_bridge_goto_setup(chan);
+ if (goto_failed || ast_pbx_run(chan)) {
+ ast_hangup(chan);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Set after bridge goto location of channel.
+ * \since 12.0.0
+ *
+ * \param chan Channel to setup after bridge goto location.
+ * \param run_h_exten TRUE if the h exten should be run.
+ * \param specific TRUE if the context/exten/priority is exactly specified.
+ * \param context Context to goto after bridge.
+ * \param exten Exten to goto after bridge. (Could be NULL if run_h_exten)
+ * \param priority Priority to goto after bridge.
+ * \param parseable_goto User specified goto string. (Could be NULL)
+ *
+ * \details Add a channel datastore to setup the goto location
+ * when the channel leaves the bridge and run a PBX from there.
+ *
+ * If run_h_exten then execute the h exten found in the given context.
+ * Else if specific then goto the given context/exten/priority.
+ * Else if parseable_goto then use the given context/exten/priority
+ * as the relative position for the parseable_goto.
+ * Else goto the given context/exten/priority+1.
+ *
+ * \return Nothing
+ */
+static void __after_bridge_set_goto(struct ast_channel *chan, int run_h_exten, int specific, const char *context, const char *exten, int priority, const char *parseable_goto)
+{
+ struct ast_datastore *datastore;
+ struct after_bridge_goto_ds *after_bridge;
+
+ /* Sanity checks. */
+ ast_assert(chan != NULL);
+ if (!chan) {
+ return;
+ }
+ if (run_h_exten) {
+ ast_assert(run_h_exten && context);
+ if (!context) {
+ return;
+ }
+ } else {
+ ast_assert(context && exten && 0 < priority);
+ if (!context || !exten || priority < 1) {
+ return;
+ }
+ }
+
+ /* Create a new datastore. */
+ datastore = ast_datastore_alloc(&after_bridge_goto_info, NULL);
+ if (!datastore) {
+ return;
+ }
+ after_bridge = ast_calloc(1, sizeof(*after_bridge));
+ if (!after_bridge) {
+ ast_datastore_free(datastore);
+ return;
+ }
+
+ /* Initialize it. */
+ after_bridge->parseable_goto = ast_strdup(parseable_goto);
+ after_bridge->context = ast_strdup(context);
+ after_bridge->exten = ast_strdup(exten);
+ after_bridge->priority = priority;
+ after_bridge->run_h_exten = run_h_exten ? 1 : 0;
+ after_bridge->specific = specific ? 1 : 0;
+ datastore->data = after_bridge;
+ if ((parseable_goto && !after_bridge->parseable_goto)
+ || (context && !after_bridge->context)
+ || (exten && !after_bridge->exten)) {
+ ast_datastore_free(datastore);
+ return;
+ }
+
+ /* Put it on the channel replacing any existing one. */
+ ast_channel_lock(chan);
+ ast_after_bridge_goto_discard(chan);
+ ast_channel_datastore_add(chan, datastore);
+ ast_channel_unlock(chan);
+}
+
+void ast_after_bridge_set_goto(struct ast_channel *chan, const char *context, const char *exten, int priority)
+{
+ __after_bridge_set_goto(chan, 0, 1, context, exten, priority, NULL);
+}
+
+void ast_after_bridge_set_h(struct ast_channel *chan, const char *context)
+{
+ __after_bridge_set_goto(chan, 1, 0, context, NULL, 1, NULL);
+}
+
+void ast_after_bridge_set_go_on(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *parseable_goto)
+{
+ char *p_goto;
+
+ if (!ast_strlen_zero(parseable_goto)) {
+ p_goto = ast_strdupa(parseable_goto);
+ ast_replace_subargument_delimiter(p_goto);
+ } else {
+ p_goto = NULL;
+ }
+ __after_bridge_set_goto(chan, 0, 0, context, exten, priority, p_goto);
+}
+
+void ast_bridge_notify_masquerade(struct ast_channel *chan)
+{
+ struct ast_bridge_channel *bridge_channel;
+ struct ast_bridge *bridge;
+
+ /* Safely get the bridge_channel pointer for the chan. */
+ ast_channel_lock(chan);
+ bridge_channel = ast_channel_get_bridge_channel(chan);
+ ast_channel_unlock(chan);
+ if (!bridge_channel) {
+ /* Not in a bridge */
+ return;
+ }
+
+ ast_bridge_channel_lock_bridge(bridge_channel);
+ bridge = bridge_channel->bridge;
+ if (bridge_channel == find_bridge_channel(bridge, chan)) {
+/* BUGBUG this needs more work. The channels need to be made compatible again if the formats change. The bridge_channel thread needs to monitor for this case. */
+ /* The channel we want to notify is still in a bridge. */
+ bridge->v_table->notify_masquerade(bridge, bridge_channel);
+ bridge_reconfigured(bridge);
+ }
+ ast_bridge_unlock(bridge);
+ ao2_ref(bridge_channel, -1);
+}
+
+/*
+ * BUGBUG make ast_bridge_join() require features to be allocated just like ast_bridge_impart() and not expect the struct back.
+ *
+ * This change is really going to break ConfBridge. All other
+ * users are easily changed. However, it is needed so the
+ * bridging code can manipulate features on all channels
+ * consistently no matter how they joined.
+ *
+ * Need to update the features parameter doxygen when this
+ * change is made to be like ast_bridge_impart().
+ */
enum ast_bridge_channel_state ast_bridge_join(struct ast_bridge *bridge,
struct ast_channel *chan,
struct ast_channel *swap,
struct ast_bridge_features *features,
- struct ast_bridge_tech_optimizations *tech_args)
+ struct ast_bridge_tech_optimizations *tech_args,
+ int pass_reference)
{
struct ast_bridge_channel *bridge_channel;
enum ast_bridge_channel_state state;
bridge_channel = bridge_channel_alloc(bridge);
+ if (pass_reference) {
+ ao2_ref(bridge, -1);
+ }
if (!bridge_channel) {
- return AST_BRIDGE_CHANNEL_STATE_HANGUP;
+ state = AST_BRIDGE_CHANNEL_STATE_HANGUP;
+ goto join_exit;
+ }
+/* BUGBUG features cannot be NULL when passed in. When it is changed to allocated we can do like ast_bridge_impart() and allocate one. */
+ ast_assert(features != NULL);
+ if (!features) {
+ ao2_ref(bridge_channel, -1);
+ state = AST_BRIDGE_CHANNEL_STATE_HANGUP;
+ goto join_exit;
}
if (tech_args) {
bridge_channel->tech_args = *tech_args;
}
/* Initialize various other elements of the bridge channel structure that we can't do above */
+ ast_channel_lock(chan);
+ ast_channel_internal_bridge_channel_set(chan, bridge_channel);
+ ast_channel_unlock(chan);
+ bridge_channel->thread = pthread_self();
bridge_channel->chan = chan;
bridge_channel->swap = swap;
bridge_channel->features = features;
- state = bridge_channel_join(bridge_channel);
+ bridge_channel_join(bridge_channel);
+ state = bridge_channel->state;
/* Cleanup all the data in the bridge channel after it leaves the bridge. */
+ ast_channel_lock(chan);
+ ast_channel_internal_bridge_channel_set(chan, NULL);
+ ast_channel_unlock(chan);
bridge_channel->chan = NULL;
bridge_channel->swap = NULL;
bridge_channel->features = NULL;
ao2_ref(bridge_channel, -1);
+
+join_exit:;
+/* BUGBUG this is going to cause problems for DTMF atxfer attended bridge between B & C. Maybe an ast_bridge_join_internal() that does not do the after bridge goto for this case. */
+ after_bridge_callback_run(chan);
+ if (!(ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO)
+ && !ast_after_bridge_goto_setup(chan)) {
+ /* Claim the after bridge goto is an async goto destination. */
+ ast_channel_lock(chan);
+ ast_softhangup_nolock(chan, AST_SOFTHANGUP_ASYNCGOTO);
+ ast_channel_unlock(chan);
+ }
return state;
}
-/*! \brief Thread responsible for imparted bridged channels */
-static void *bridge_channel_thread(void *data)
+/*! \brief Thread responsible for imparted bridged channels to be departed */
+static void *bridge_channel_depart_thread(void *data)
{
struct ast_bridge_channel *bridge_channel = data;
- enum ast_bridge_channel_state state;
if (bridge_channel->callid) {
ast_callid_threadassoc_add(bridge_channel->callid);
}
- state = bridge_channel_join(bridge_channel);
+ bridge_channel_join(bridge_channel);
+
+ /* cleanup */
+ bridge_channel->swap = NULL;
+ ast_bridge_features_destroy(bridge_channel->features);
+ bridge_channel->features = NULL;
+
+ ast_after_bridge_callback_discard(bridge_channel->chan, AST_AFTER_BRIDGE_CB_REASON_DEPART);
+ ast_after_bridge_goto_discard(bridge_channel->chan);
+
+ return NULL;
+}
- /* If no other thread is going to take the channel then hang it up, or else we would have to service it until something else came along */
- if (bridge_channel->allow_impart_hangup && (state == AST_BRIDGE_CHANNEL_STATE_END || state == AST_BRIDGE_CHANNEL_STATE_HANGUP)) {
- ast_hangup(bridge_channel->chan);
+/*! \brief Thread responsible for independent imparted bridged channels */
+static void *bridge_channel_ind_thread(void *data)
+{
+ struct ast_bridge_channel *bridge_channel = data;
+ struct ast_channel *chan;
+
+ if (bridge_channel->callid) {
+ ast_callid_threadassoc_add(bridge_channel->callid);
}
+ bridge_channel_join(bridge_channel);
+ chan = bridge_channel->chan;
+
/* cleanup */
- ao2_lock(bridge_channel);
+ ast_channel_lock(chan);
+ ast_channel_internal_bridge_channel_set(chan, NULL);
+ ast_channel_unlock(chan);
bridge_channel->chan = NULL;
bridge_channel->swap = NULL;
+ ast_bridge_features_destroy(bridge_channel->features);
bridge_channel->features = NULL;
- ao2_unlock(bridge_channel);
ao2_ref(bridge_channel, -1);
+ after_bridge_callback_run(chan);
+ ast_after_bridge_goto_run(chan);
return NULL;
}
-int ast_bridge_impart(struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap, struct ast_bridge_features *features, int allow_hangup)
+int ast_bridge_impart(struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap, struct ast_bridge_features *features, int independent)
{
+ int res;
struct ast_bridge_channel *bridge_channel;
+ /* Supply an empty features structure if the caller did not. */
+ if (!features) {
+ features = ast_bridge_features_new();
+ if (!features) {
+ return -1;
+ }
+ }
+
/* Try to allocate a structure for the bridge channel */
bridge_channel = bridge_channel_alloc(bridge);
if (!bridge_channel) {
+ ast_bridge_features_destroy(features);
return -1;
}
/* Setup various parameters */
+ ast_channel_lock(chan);
+ ast_channel_internal_bridge_channel_set(chan, bridge_channel);
+ ast_channel_unlock(chan);
bridge_channel->chan = chan;
bridge_channel->swap = swap;
bridge_channel->features = features;
- bridge_channel->allow_impart_hangup = allow_hangup;
+ bridge_channel->depart_wait = independent ? 0 : 1;
bridge_channel->callid = ast_read_threadstorage_callid();
/* Actually create the thread that will handle the channel */
- if (ast_pthread_create(&bridge_channel->thread, NULL, bridge_channel_thread, bridge_channel)) {
+ if (independent) {
+ /* Independently imparted channels cannot have a PBX. */
+ ast_assert(!ast_channel_pbx(chan));
+
+ res = ast_pthread_create_detached(&bridge_channel->thread, NULL,
+ bridge_channel_ind_thread, bridge_channel);
+ } else {
+ /* Imparted channels to be departed should not have a PBX either. */
+ ast_assert(!ast_channel_pbx(chan));
+
+ res = ast_pthread_create(&bridge_channel->thread, NULL,
+ bridge_channel_depart_thread, bridge_channel);
+ }
+
+ if (res) {
+ /* cleanup */
+ ast_channel_lock(chan);
+ ast_channel_internal_bridge_channel_set(chan, NULL);
+ ast_channel_unlock(chan);
+ bridge_channel->chan = NULL;
+ bridge_channel->swap = NULL;
+ ast_bridge_features_destroy(bridge_channel->features);
+ bridge_channel->features = NULL;
+
ao2_ref(bridge_channel, -1);
return -1;
}
@@ -1282,26 +3208,46 @@ int ast_bridge_impart(struct ast_bridge *bridge, struct ast_channel *chan, struc
return 0;
}
-int ast_bridge_depart(struct ast_bridge *bridge, struct ast_channel *chan)
+int ast_bridge_depart(struct ast_channel *chan)
{
struct ast_bridge_channel *bridge_channel;
- pthread_t thread;
-
- ao2_lock(bridge);
-
- /* Try to find the channel that we want to depart */
- if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
- ao2_unlock(bridge);
+ int departable;
+
+ ast_channel_lock(chan);
+ bridge_channel = ast_channel_internal_bridge_channel(chan);
+ departable = bridge_channel && bridge_channel->depart_wait;
+ ast_channel_unlock(chan);
+ if (!departable) {
+ ast_log(LOG_ERROR, "Channel %s cannot be departed.\n",
+ ast_channel_name(chan));
+ /*
+ * Should never happen. It likely means that
+ * ast_bridge_depart() is called by two threads for the same
+ * channel, the channel was never imparted to be departed, or it
+ * has already been departed.
+ */
+ ast_assert(0);
return -1;
}
- ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_DEPART);
- thread = bridge_channel->thread;
+ /*
+ * We are claiming the reference held by the depart bridge
+ * channel thread.
+ */
+
+ ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
- ao2_unlock(bridge);
+ /* Wait for the depart thread to die */
+ ast_debug(1, "Waiting for %p(%s) bridge thread to die.\n",
+ bridge_channel, ast_channel_name(bridge_channel->chan));
+ pthread_join(bridge_channel->thread, NULL);
- pthread_join(thread, NULL);
+ ast_channel_lock(chan);
+ ast_channel_internal_bridge_channel_set(chan, NULL);
+ ast_channel_unlock(chan);
+ /* We can get rid of the bridge_channel after the depart thread has died. */
+ ao2_ref(bridge_channel, -1);
return 0;
}
@@ -1309,115 +3255,816 @@ int ast_bridge_remove(struct ast_bridge *bridge, struct ast_channel *chan)
{
struct ast_bridge_channel *bridge_channel;
- ao2_lock(bridge);
+ ast_bridge_lock(bridge);
/* Try to find the channel that we want to remove */
if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
return -1;
}
ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
return 0;
}
-int ast_bridge_merge(struct ast_bridge *bridge0, struct ast_bridge *bridge1)
+/*!
+ * \internal
+ * \brief Point the bridge_channel to a new bridge.
+ * \since 12.0.0
+ *
+ * \param bridge_channel What is to point to a new bridge.
+ * \param new_bridge Where the bridge channel should point.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_change_bridge(struct ast_bridge_channel *bridge_channel, struct ast_bridge *new_bridge)
+{
+ struct ast_bridge *old_bridge;
+
+ ao2_ref(new_bridge, +1);
+ ast_bridge_channel_lock(bridge_channel);
+ ast_channel_lock(bridge_channel->chan);
+ old_bridge = bridge_channel->bridge;
+ bridge_channel->bridge = new_bridge;
+ ast_channel_internal_bridge_set(bridge_channel->chan, new_bridge);
+ ast_channel_unlock(bridge_channel->chan);
+ ast_bridge_channel_unlock(bridge_channel);
+ ao2_ref(old_bridge, -1);
+}
+
+/*!
+ * \internal
+ * \brief Do the merge of two bridges.
+ * \since 12.0.0
+ *
+ * \param dst_bridge Destination bridge of merge.
+ * \param src_bridge Source bridge of merge.
+ * \param kick_me Array of channels to kick from the bridges.
+ * \param num_kick Number of channels in the kick_me array.
+ *
+ * \return Nothing
+ *
+ * \note The two bridges are assumed already locked.
+ *
+ * This moves the channels in src_bridge into the bridge pointed
+ * to by dst_bridge.
+ */
+static void bridge_merge_do(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, struct ast_bridge_channel **kick_me, unsigned int num_kick)
{
struct ast_bridge_channel *bridge_channel;
+ unsigned int idx;
+
+ ast_debug(1, "Merging bridge %s into bridge %s\n",
+ src_bridge->uniqueid, dst_bridge->uniqueid);
+
+ ast_bridge_publish_merge(dst_bridge, src_bridge);
+
+ /*
+ * Move channels from src_bridge over to dst_bridge.
+ *
+ * We must use AST_LIST_TRAVERSE_SAFE_BEGIN() because
+ * bridge_channel_pull() alters the list we are traversing.
+ */
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&src_bridge->channels, bridge_channel, entry) {
+ if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+ /*
+ * The channel is already leaving let it leave normally because
+ * pulling it may delete hooks that should run for this channel.
+ */
+ continue;
+ }
+ if (ast_test_flag(&bridge_channel->features->feature_flags,
+ AST_BRIDGE_CHANNEL_FLAG_IMMOVABLE)) {
+ continue;
+ }
+
+ if (kick_me) {
+ for (idx = 0; idx < num_kick; ++idx) {
+ if (bridge_channel == kick_me[idx]) {
+ ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+ break;
+ }
+ }
+ }
+ bridge_channel_pull(bridge_channel);
+ if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+ /*
+ * The channel died as a result of being pulled or it was
+ * kicked. Leave it pointing to the original bridge.
+ */
+ continue;
+ }
+
+ /* Point to new bridge.*/
+ bridge_channel_change_bridge(bridge_channel, dst_bridge);
+
+ if (bridge_channel_push(bridge_channel)) {
+ ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+
+ if (kick_me) {
+ /*
+ * Now we can kick any channels in the dst_bridge without
+ * potentially dissolving the bridge.
+ */
+ for (idx = 0; idx < num_kick; ++idx) {
+ bridge_channel = kick_me[idx];
+ ast_bridge_channel_lock(bridge_channel);
+ if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
+ ast_bridge_change_state_nolock(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+ bridge_channel_pull(bridge_channel);
+ }
+ ast_bridge_channel_unlock(bridge_channel);
+ }
+ }
+
+ bridge_reconfigured(dst_bridge);
+ bridge_reconfigured(src_bridge);
+
+ ast_debug(1, "Merged bridge %s into bridge %s\n",
+ src_bridge->uniqueid, dst_bridge->uniqueid);
+}
+
+struct merge_direction {
+ /*! Destination merge bridge. */
+ struct ast_bridge *dest;
+ /*! Source merge bridge. */
+ struct ast_bridge *src;
+};
+
+/*!
+ * \internal
+ * \brief Determine which bridge should merge into the other.
+ * \since 12.0.0
+ *
+ * \param bridge1 A bridge for merging
+ * \param bridge2 A bridge for merging
+ *
+ * \note The two bridges are assumed already locked.
+ *
+ * \return Which bridge merges into which or NULL bridges if cannot merge.
+ */
+static struct merge_direction bridge_merge_determine_direction(struct ast_bridge *bridge1, struct ast_bridge *bridge2)
+{
+ struct merge_direction merge = { NULL, NULL };
+ int bridge1_priority;
+ int bridge2_priority;
+
+ if (!ast_test_flag(&bridge1->feature_flags,
+ AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM)
+ && !ast_test_flag(&bridge2->feature_flags,
+ AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM)) {
+ /*
+ * Can merge either way. Merge to the higher priority merge
+ * bridge. Otherwise merge to the larger bridge.
+ */
+ bridge1_priority = bridge1->v_table->get_merge_priority(bridge1);
+ bridge2_priority = bridge2->v_table->get_merge_priority(bridge2);
+ if (bridge2_priority < bridge1_priority) {
+ merge.dest = bridge1;
+ merge.src = bridge2;
+ } else if (bridge1_priority < bridge2_priority) {
+ merge.dest = bridge2;
+ merge.src = bridge1;
+ } else {
+ /* Merge to the larger bridge. */
+ if (bridge2->num_channels <= bridge1->num_channels) {
+ merge.dest = bridge1;
+ merge.src = bridge2;
+ } else {
+ merge.dest = bridge2;
+ merge.src = bridge1;
+ }
+ }
+ } else if (!ast_test_flag(&bridge1->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_TO)
+ && !ast_test_flag(&bridge2->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM)) {
+ /* Can merge only one way. */
+ merge.dest = bridge1;
+ merge.src = bridge2;
+ } else if (!ast_test_flag(&bridge2->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_TO)
+ && !ast_test_flag(&bridge1->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM)) {
+ /* Can merge only one way. */
+ merge.dest = bridge2;
+ merge.src = bridge1;
+ }
- ao2_lock(bridge0);
- ao2_lock(bridge1);
+ return merge;
+}
+
+/*!
+ * \internal
+ * \brief Merge two bridges together
+ * \since 12.0.0
+ *
+ * \param dst_bridge Destination bridge of merge.
+ * \param src_bridge Source bridge of merge.
+ * \param merge_best_direction TRUE if don't care about which bridge merges into the other.
+ * \param kick_me Array of channels to kick from the bridges.
+ * \param num_kick Number of channels in the kick_me array.
+ *
+ * \note The dst_bridge and src_bridge are assumed already locked.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+static int bridge_merge_locked(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, int merge_best_direction, struct ast_channel **kick_me, unsigned int num_kick)
+{
+ struct merge_direction merge;
+ struct ast_bridge_channel **kick_them = NULL;
- /* If the first bridge currently has 2 channels and is not capable of becoming a multimixing bridge we can not merge */
- if (bridge0->num + bridge1->num > 2
- && !(bridge0->technology->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX)
- && !ast_test_flag(&bridge0->feature_flags, AST_BRIDGE_FLAG_SMART)) {
- ao2_unlock(bridge1);
- ao2_unlock(bridge0);
- ast_debug(1, "Can't merge bridge %p into bridge %p, multimix is needed and it could not be acquired.\n", bridge1, bridge0);
+ /* Sanity check. */
+ ast_assert(dst_bridge && src_bridge && dst_bridge != src_bridge && (!num_kick || kick_me));
+
+ if (dst_bridge->dissolved || src_bridge->dissolved) {
+ ast_debug(1, "Can't merge bridges %s and %s, at least one bridge is dissolved.\n",
+ src_bridge->uniqueid, dst_bridge->uniqueid);
+ return -1;
+ }
+ if (ast_test_flag(&dst_bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY)
+ || ast_test_flag(&src_bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY)) {
+ ast_debug(1, "Can't merge bridges %s and %s, masquerade only.\n",
+ src_bridge->uniqueid, dst_bridge->uniqueid);
+ return -1;
+ }
+ if (dst_bridge->inhibit_merge || src_bridge->inhibit_merge) {
+ ast_debug(1, "Can't merge bridges %s and %s, merging temporarily inhibited.\n",
+ src_bridge->uniqueid, dst_bridge->uniqueid);
return -1;
}
- ast_debug(1, "Merging channels from bridge %p into bridge %p\n", bridge1, bridge0);
+ if (merge_best_direction) {
+ merge = bridge_merge_determine_direction(dst_bridge, src_bridge);
+ } else {
+ merge.dest = dst_bridge;
+ merge.src = src_bridge;
+ }
+
+ if (!merge.dest
+ || ast_test_flag(&merge.dest->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_TO)
+ || ast_test_flag(&merge.src->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM)) {
+ ast_debug(1, "Can't merge bridges %s and %s, merging inhibited.\n",
+ src_bridge->uniqueid, dst_bridge->uniqueid);
+ return -1;
+ }
+ if (merge.src->num_channels < 2) {
+ /*
+ * For a two party bridge, a channel may be temporarily removed
+ * from the source bridge or the initial bridge members have not
+ * joined yet.
+ */
+ ast_debug(1, "Can't merge bridge %s into bridge %s, not enough channels in source bridge.\n",
+ merge.src->uniqueid, merge.dest->uniqueid);
+ return -1;
+ }
+ if (2 + num_kick < merge.dest->num_channels + merge.src->num_channels
+ && !(merge.dest->technology->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX)
+ && (!ast_test_flag(&merge.dest->feature_flags, AST_BRIDGE_FLAG_SMART)
+ || !(merge.dest->allowed_capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX))) {
+ ast_debug(1, "Can't merge bridge %s into bridge %s, multimix is needed and it cannot be acquired.\n",
+ merge.src->uniqueid, merge.dest->uniqueid);
+ return -1;
+ }
+
+ if (num_kick) {
+ unsigned int num_to_kick = 0;
+ unsigned int idx;
+
+ kick_them = ast_alloca(num_kick * sizeof(*kick_them));
+ for (idx = 0; idx < num_kick; ++idx) {
+ kick_them[num_to_kick] = find_bridge_channel(merge.src, kick_me[idx]);
+ if (!kick_them[num_to_kick]) {
+ kick_them[num_to_kick] = find_bridge_channel(merge.dest, kick_me[idx]);
+ }
+ if (kick_them[num_to_kick]) {
+ ++num_to_kick;
+ }
+ }
- /* Perform smart bridge operation on bridge we are merging into so it can change bridge technology if needed */
- if (smart_bridge_operation(bridge0, NULL, bridge0->num + bridge1->num)) {
- ao2_unlock(bridge1);
- ao2_unlock(bridge0);
- ast_debug(1, "Can't merge bridge %p into bridge %p, tried to perform smart bridge operation and failed.\n", bridge1, bridge0);
+ if (num_to_kick != num_kick) {
+ ast_debug(1, "Can't merge bridge %s into bridge %s, at least one kicked channel is not in either bridge.\n",
+ merge.src->uniqueid, merge.dest->uniqueid);
+ return -1;
+ }
+ }
+
+ bridge_merge_do(merge.dest, merge.src, kick_them, num_kick);
+ return 0;
+}
+
+int ast_bridge_merge(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, int merge_best_direction, struct ast_channel **kick_me, unsigned int num_kick)
+{
+ int res;
+
+ /* Sanity check. */
+ ast_assert(dst_bridge && src_bridge);
+
+ ast_bridge_lock_both(dst_bridge, src_bridge);
+ res = bridge_merge_locked(dst_bridge, src_bridge, merge_best_direction, kick_me, num_kick);
+ ast_bridge_unlock(src_bridge);
+ ast_bridge_unlock(dst_bridge);
+ return res;
+}
+
+/*!
+ * \internal
+ * \brief Move a bridge channel from one bridge to another.
+ * \since 12.0.0
+ *
+ * \param dst_bridge Destination bridge of bridge channel move.
+ * \param bridge_channel Channel moving from one bridge to another.
+ * \param attempt_recovery TRUE if failure attempts to push channel back into original bridge.
+ *
+ * \note The dst_bridge and bridge_channel->bridge are assumed already locked.
+ *
+ * \retval 0 on success.
+ * \retval -1 on failure.
+ */
+static int bridge_move_do(struct ast_bridge *dst_bridge, struct ast_bridge_channel *bridge_channel, int attempt_recovery)
+{
+ struct ast_bridge *orig_bridge;
+ int was_in_bridge;
+ int res = 0;
+
+/* BUGBUG need bridge move stasis event and a success/fail event. */
+ if (bridge_channel->swap) {
+ ast_debug(1, "Moving %p(%s) into bridge %s swapping with %s\n",
+ bridge_channel, ast_channel_name(bridge_channel->chan), dst_bridge->uniqueid,
+ ast_channel_name(bridge_channel->swap));
+ } else {
+ ast_debug(1, "Moving %p(%s) into bridge %s\n",
+ bridge_channel, ast_channel_name(bridge_channel->chan), dst_bridge->uniqueid);
+ }
+
+ orig_bridge = bridge_channel->bridge;
+ was_in_bridge = bridge_channel->in_bridge;
+
+ bridge_channel_pull(bridge_channel);
+ if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+ /*
+ * The channel died as a result of being pulled. Leave it
+ * pointing to the original bridge.
+ */
+ bridge_reconfigured(orig_bridge);
+ return -1;
+ }
+
+ /* Point to new bridge.*/
+ ao2_ref(orig_bridge, +1);/* Keep a ref in case the push fails. */
+ bridge_channel_change_bridge(bridge_channel, dst_bridge);
+
+ if (bridge_channel_push(bridge_channel)) {
+ /* Try to put the channel back into the original bridge. */
+ if (attempt_recovery && was_in_bridge) {
+ /* Point back to original bridge. */
+ bridge_channel_change_bridge(bridge_channel, orig_bridge);
+
+ if (bridge_channel_push(bridge_channel)) {
+ ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+ }
+ } else {
+ ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+ }
+ res = -1;
+ }
+
+ bridge_reconfigured(dst_bridge);
+ bridge_reconfigured(orig_bridge);
+ ao2_ref(orig_bridge, -1);
+ return res;
+}
+
+/*!
+ * \internal
+ * \brief Move a channel from one bridge to another.
+ * \since 12.0.0
+ *
+ * \param dst_bridge Destination bridge of bridge channel move.
+ * \param src_bridge Source bridge of bridge channel move.
+ * \param chan Channel to move.
+ * \param swap Channel to replace in dst_bridge.
+ * \param attempt_recovery TRUE if failure attempts to push channel back into original bridge.
+ *
+ * \note The dst_bridge and src_bridge are assumed already locked.
+ *
+ * \retval 0 on success.
+ * \retval -1 on failure.
+ */
+static int bridge_move_locked(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, struct ast_channel *chan, struct ast_channel *swap, int attempt_recovery)
+{
+ struct ast_bridge_channel *bridge_channel;
+
+ if (dst_bridge->dissolved || src_bridge->dissolved) {
+ ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, at least one bridge is dissolved.\n",
+ ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid);
+ return -1;
+ }
+ if (ast_test_flag(&dst_bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY)
+ || ast_test_flag(&src_bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY)) {
+ ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, masquerade only.\n",
+ ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid);
+ return -1;
+ }
+ if (dst_bridge->inhibit_merge || src_bridge->inhibit_merge) {
+ ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, temporarily inhibited.\n",
+ ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid);
+ return -1;
+ }
+
+ bridge_channel = find_bridge_channel(src_bridge, chan);
+ if (!bridge_channel) {
+ ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, channel not in bridge.\n",
+ ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid);
+ return -1;
+ }
+ if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+ ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, channel leaving bridge.\n",
+ ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid);
+ return -1;
+ }
+ if (ast_test_flag(&bridge_channel->features->feature_flags,
+ AST_BRIDGE_CHANNEL_FLAG_IMMOVABLE)) {
+ ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, channel immovable.\n",
+ ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid);
return -1;
}
- /* If a thread is currently executing on bridge1 tell it to stop */
- if (bridge1->thread) {
- ast_debug(1, "Telling bridge thread on bridge %p to stop as it is being merged into %p\n", bridge1, bridge0);
- bridge1->thread = AST_PTHREADT_STOP;
+ if (swap) {
+ struct ast_bridge_channel *bridge_channel_swap;
+
+ bridge_channel_swap = find_bridge_channel(dst_bridge, swap);
+ if (!bridge_channel_swap) {
+ ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, swap channel %s not in bridge.\n",
+ ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid,
+ ast_channel_name(swap));
+ return -1;
+ }
+ if (bridge_channel_swap->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+ ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, swap channel %s leaving bridge.\n",
+ ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid,
+ ast_channel_name(swap));
+ return -1;
+ }
}
- /* Move channels from bridge1 over to bridge0 */
- while ((bridge_channel = AST_LIST_REMOVE_HEAD(&bridge1->channels, entry))) {
- /* Tell the technology handling bridge1 that the bridge channel is leaving */
- if (bridge1->technology->leave) {
- ast_debug(1, "Giving bridge technology %s notification that %p is leaving bridge %p\n", bridge1->technology->name, bridge_channel, bridge1);
- if (bridge1->technology->leave(bridge1, bridge_channel)) {
- ast_debug(1, "Bridge technology %s failed to allow %p to leave bridge %p\n", bridge1->technology->name, bridge_channel, bridge1);
+ bridge_channel->swap = swap;
+ return bridge_move_do(dst_bridge, bridge_channel, attempt_recovery);
+}
+
+int ast_bridge_move(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, struct ast_channel *chan, struct ast_channel *swap, int attempt_recovery)
+{
+ int res;
+
+ ast_bridge_lock_both(dst_bridge, src_bridge);
+ res = bridge_move_locked(dst_bridge, src_bridge, chan, swap, attempt_recovery);
+ ast_bridge_unlock(src_bridge);
+ ast_bridge_unlock(dst_bridge);
+ return res;
+}
+
+struct ast_bridge_channel *ast_bridge_channel_peer(struct ast_bridge_channel *bridge_channel)
+{
+ struct ast_bridge *bridge = bridge_channel->bridge;
+ struct ast_bridge_channel *other = NULL;
+
+ if (bridge_channel->in_bridge && bridge->num_channels == 2) {
+ AST_LIST_TRAVERSE(&bridge->channels, other, entry) {
+ if (other != bridge_channel) {
+ break;
}
}
+ }
- /* Drop channel count and reference count on the bridge they are leaving */
- bridge1->num--;
- ao2_ref(bridge1, -1);
+ return other;
+}
- bridge_array_remove(bridge1, bridge_channel->chan);
+/*!
+ * \internal
+ * \brief Lock the unreal channel stack for chan and prequalify it.
+ * \since 12.0.0
+ *
+ * \param chan Unreal channel writing a frame into the channel driver.
+ *
+ * \note It is assumed that chan is already locked.
+ *
+ * \retval bridge on success with bridge and bridge_channel locked.
+ * \retval NULL if cannot do optimization now.
+ */
+static struct ast_bridge *optimize_lock_chan_stack(struct ast_channel *chan)
+{
+ struct ast_bridge *bridge;
+ struct ast_bridge_channel *bridge_channel;
- /* Now add them into the bridge they are joining, increase channel count, and bump up reference count */
- bridge_channel->bridge = bridge0;
- AST_LIST_INSERT_TAIL(&bridge0->channels, bridge_channel, entry);
- bridge0->num++;
- ao2_ref(bridge0, +1);
+ if (!AST_LIST_EMPTY(ast_channel_readq(chan))) {
+ return NULL;
+ }
+ bridge_channel = ast_channel_internal_bridge_channel(chan);
+ if (!bridge_channel || ast_bridge_channel_trylock(bridge_channel)) {
+ return NULL;
+ }
+ bridge = bridge_channel->bridge;
+ if (bridge_channel->activity != AST_BRIDGE_CHANNEL_THREAD_SIMPLE
+ || bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT
+ || ast_bridge_trylock(bridge)) {
+ ast_bridge_channel_unlock(bridge_channel);
+ return NULL;
+ }
+ if (bridge->inhibit_merge
+ || bridge->dissolved
+ || ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY)
+ || !bridge_channel->in_bridge
+ || !AST_LIST_EMPTY(&bridge_channel->wr_queue)) {
+ ast_bridge_unlock(bridge);
+ ast_bridge_channel_unlock(bridge_channel);
+ return NULL;
+ }
+ return bridge;
+}
- bridge_array_add(bridge0, bridge_channel->chan);
+/*!
+ * \internal
+ * \brief Lock the unreal channel stack for peer and prequalify it.
+ * \since 12.0.0
+ *
+ * \param peer Other unreal channel in the pair.
+ *
+ * \retval bridge on success with bridge, bridge_channel, and peer locked.
+ * \retval NULL if cannot do optimization now.
+ */
+static struct ast_bridge *optimize_lock_peer_stack(struct ast_channel *peer)
+{
+ struct ast_bridge *bridge;
+ struct ast_bridge_channel *bridge_channel;
- /* Make the channel compatible with the new bridge it is joining or else formats would go amuck */
- bridge_make_compatible(bridge0, bridge_channel);
+ if (ast_channel_trylock(peer)) {
+ return NULL;
+ }
+ if (!AST_LIST_EMPTY(ast_channel_readq(peer))) {
+ ast_channel_unlock(peer);
+ return NULL;
+ }
+ bridge_channel = ast_channel_internal_bridge_channel(peer);
+ if (!bridge_channel || ast_bridge_channel_trylock(bridge_channel)) {
+ ast_channel_unlock(peer);
+ return NULL;
+ }
+ bridge = bridge_channel->bridge;
+ if (bridge_channel->activity != AST_BRIDGE_CHANNEL_THREAD_IDLE
+ || bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT
+ || ast_bridge_trylock(bridge)) {
+ ast_bridge_channel_unlock(bridge_channel);
+ ast_channel_unlock(peer);
+ return NULL;
+ }
+ if (bridge->inhibit_merge
+ || bridge->dissolved
+ || ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY)
+ || !bridge_channel->in_bridge
+ || !AST_LIST_EMPTY(&bridge_channel->wr_queue)) {
+ ast_bridge_unlock(bridge);
+ ast_bridge_channel_unlock(bridge_channel);
+ ast_channel_unlock(peer);
+ return NULL;
+ }
+ return bridge;
+}
+
+/*!
+ * \internal
+ * \brief Check and attempt to swap optimize out the unreal channels.
+ * \since 12.0.0
+ *
+ * \param chan_bridge
+ * \param chan_bridge_channel
+ * \param peer_bridge
+ * \param peer_bridge_channel
+ *
+ * \retval 1 if unreal channels failed to optimize out.
+ * \retval 0 if unreal channels were not optimized out.
+ * \retval -1 if unreal channels were optimized out.
+ */
+static int check_swap_optimize_out(struct ast_bridge *chan_bridge,
+ struct ast_bridge_channel *chan_bridge_channel, struct ast_bridge *peer_bridge,
+ struct ast_bridge_channel *peer_bridge_channel)
+{
+ struct ast_bridge *dst_bridge = NULL;
+ struct ast_bridge_channel *dst_bridge_channel = NULL;
+ struct ast_bridge_channel *src_bridge_channel = NULL;
+ int peer_priority;
+ int chan_priority;
+ int res = 0;
- /* Tell the technology handling bridge0 that the bridge channel is joining */
- if (bridge0->technology->join) {
- ast_debug(1, "Giving bridge technology %s notification that %p is joining bridge %p\n", bridge0->technology->name, bridge_channel, bridge0);
- if (bridge0->technology->join(bridge0, bridge_channel)) {
- ast_debug(1, "Bridge technology %s failed to join %p to bridge %p\n", bridge0->technology->name, bridge_channel, bridge0);
+ if (!ast_test_flag(&chan_bridge->feature_flags,
+ AST_BRIDGE_FLAG_SWAP_INHIBIT_TO | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM)
+ && !ast_test_flag(&peer_bridge->feature_flags,
+ AST_BRIDGE_FLAG_SWAP_INHIBIT_TO | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM)) {
+ /*
+ * Can swap either way. Swap to the higher priority merge
+ * bridge.
+ */
+ chan_priority = chan_bridge->v_table->get_merge_priority(chan_bridge);
+ peer_priority = peer_bridge->v_table->get_merge_priority(peer_bridge);
+ if (chan_bridge->num_channels == 2
+ && chan_priority <= peer_priority) {
+ dst_bridge = peer_bridge;
+ dst_bridge_channel = peer_bridge_channel;
+ src_bridge_channel = chan_bridge_channel;
+ } else if (peer_bridge->num_channels == 2
+ && peer_priority <= chan_priority) {
+ dst_bridge = chan_bridge;
+ dst_bridge_channel = chan_bridge_channel;
+ src_bridge_channel = peer_bridge_channel;
+ }
+ } else if (chan_bridge->num_channels == 2
+ && !ast_test_flag(&chan_bridge->feature_flags, AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM)
+ && !ast_test_flag(&peer_bridge->feature_flags, AST_BRIDGE_FLAG_SWAP_INHIBIT_TO)) {
+ /* Can swap optimize only one way. */
+ dst_bridge = peer_bridge;
+ dst_bridge_channel = peer_bridge_channel;
+ src_bridge_channel = chan_bridge_channel;
+ } else if (peer_bridge->num_channels == 2
+ && !ast_test_flag(&peer_bridge->feature_flags, AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM)
+ && !ast_test_flag(&chan_bridge->feature_flags, AST_BRIDGE_FLAG_SWAP_INHIBIT_TO)) {
+ /* Can swap optimize only one way. */
+ dst_bridge = chan_bridge;
+ dst_bridge_channel = chan_bridge_channel;
+ src_bridge_channel = peer_bridge_channel;
+ }
+ if (dst_bridge) {
+ struct ast_bridge_channel *other;
+
+ res = 1;
+ other = ast_bridge_channel_peer(src_bridge_channel);
+ if (other && other->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
+ ast_debug(1, "Move-swap optimizing %s <-- %s.\n",
+ ast_channel_name(dst_bridge_channel->chan),
+ ast_channel_name(other->chan));
+
+ other->swap = dst_bridge_channel->chan;
+ if (!bridge_move_do(dst_bridge, other, 1)) {
+ ast_bridge_change_state(src_bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+ res = -1;
}
}
+ }
+ return res;
+}
- /* Poke the bridge channel, this will cause it to wake up and execute the proper threading model for the new bridge it is in */
- bridge_channel_poke(bridge_channel);
+/*!
+ * \internal
+ * \brief Check and attempt to merge optimize out the unreal channels.
+ * \since 12.0.0
+ *
+ * \param chan_bridge
+ * \param chan_bridge_channel
+ * \param peer_bridge
+ * \param peer_bridge_channel
+ *
+ * \retval 0 if unreal channels were not optimized out.
+ * \retval -1 if unreal channels were optimized out.
+ */
+static int check_merge_optimize_out(struct ast_bridge *chan_bridge,
+ struct ast_bridge_channel *chan_bridge_channel, struct ast_bridge *peer_bridge,
+ struct ast_bridge_channel *peer_bridge_channel)
+{
+ struct merge_direction merge;
+ int res = 0;
+
+ merge = bridge_merge_determine_direction(chan_bridge, peer_bridge);
+ if (!merge.dest) {
+ return res;
}
+ if (merge.src->num_channels < 2) {
+ ast_debug(4, "Can't optimize %s -- %s out, not enough channels in bridge %s.\n",
+ ast_channel_name(chan_bridge_channel->chan),
+ ast_channel_name(peer_bridge_channel->chan),
+ merge.src->uniqueid);
+ } else if ((2 + 2) < merge.dest->num_channels + merge.src->num_channels
+ && !(merge.dest->technology->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX)
+ && (!ast_test_flag(&merge.dest->feature_flags, AST_BRIDGE_FLAG_SMART)
+ || !(merge.dest->allowed_capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX))) {
+ ast_debug(4, "Can't optimize %s -- %s out, multimix is needed and it cannot be acquired.\n",
+ ast_channel_name(chan_bridge_channel->chan),
+ ast_channel_name(peer_bridge_channel->chan));
+ } else {
+ struct ast_bridge_channel *kick_me[] = {
+ chan_bridge_channel,
+ peer_bridge_channel,
+ };
- ast_debug(1, "Merged channels from bridge %p into bridge %p\n", bridge1, bridge0);
+ ast_debug(1, "Merge optimizing %s -- %s out.\n",
+ ast_channel_name(chan_bridge_channel->chan),
+ ast_channel_name(peer_bridge_channel->chan));
- ao2_unlock(bridge1);
- ao2_unlock(bridge0);
+ bridge_merge_do(merge.dest, merge.src, kick_me, ARRAY_LEN(kick_me));
+ res = -1;
+ }
- return 0;
+ return res;
+}
+
+int ast_bridge_unreal_optimized_out(struct ast_channel *chan, struct ast_channel *peer)
+{
+ struct ast_bridge *chan_bridge;
+ struct ast_bridge *peer_bridge;
+ struct ast_bridge_channel *chan_bridge_channel;
+ struct ast_bridge_channel *peer_bridge_channel;
+ int res = 0;
+
+ chan_bridge = optimize_lock_chan_stack(chan);
+ if (!chan_bridge) {
+ return res;
+ }
+ chan_bridge_channel = ast_channel_internal_bridge_channel(chan);
+
+ peer_bridge = optimize_lock_peer_stack(peer);
+ if (peer_bridge) {
+ peer_bridge_channel = ast_channel_internal_bridge_channel(peer);
+
+ res = check_swap_optimize_out(chan_bridge, chan_bridge_channel,
+ peer_bridge, peer_bridge_channel);
+ if (!res) {
+ res = check_merge_optimize_out(chan_bridge, chan_bridge_channel,
+ peer_bridge, peer_bridge_channel);
+ } else if (0 < res) {
+ res = 0;
+ }
+
+ /* Release peer locks. */
+ ast_bridge_unlock(peer_bridge);
+ ast_bridge_channel_unlock(peer_bridge_channel);
+ ast_channel_unlock(peer);
+ }
+
+ /* Release chan locks. */
+ ast_bridge_unlock(chan_bridge);
+ ast_bridge_channel_unlock(chan_bridge_channel);
+
+ return res;
+}
+
+/*!
+ * \internal
+ * \brief Adjust the bridge merge inhibit request count.
+ * \since 12.0.0
+ *
+ * \param bridge What to operate on.
+ * \param request Inhibit request increment.
+ * (Positive to add requests. Negative to remove requests.)
+ *
+ * \note This function assumes bridge is locked.
+ *
+ * \return Nothing
+ */
+static void bridge_merge_inhibit_nolock(struct ast_bridge *bridge, int request)
+{
+ int new_request;
+
+ new_request = bridge->inhibit_merge + request;
+ ast_assert(0 <= new_request);
+ bridge->inhibit_merge = new_request;
+}
+
+void ast_bridge_merge_inhibit(struct ast_bridge *bridge, int request)
+{
+ ast_bridge_lock(bridge);
+ bridge_merge_inhibit_nolock(bridge, request);
+ ast_bridge_unlock(bridge);
+}
+
+struct ast_bridge *ast_bridge_channel_merge_inhibit(struct ast_bridge_channel *bridge_channel, int request)
+{
+ struct ast_bridge *bridge;
+
+ ast_bridge_channel_lock_bridge(bridge_channel);
+ bridge = bridge_channel->bridge;
+ ao2_ref(bridge, +1);
+ bridge_merge_inhibit_nolock(bridge, request);
+ ast_bridge_unlock(bridge);
+ return bridge;
}
int ast_bridge_suspend(struct ast_bridge *bridge, struct ast_channel *chan)
{
struct ast_bridge_channel *bridge_channel;
+/* BUGBUG the case of a disolved bridge while channel is suspended is not handled. */
+/* BUGBUG suspend/unsuspend needs to be rethought. The caller must block until it has successfully suspended the channel for temporary control. */
+/* BUGBUG external suspend/unsuspend needs to be eliminated. The channel may be playing a file at the time and stealing it then is not good. */
- ao2_lock(bridge);
+ ast_bridge_lock(bridge);
if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
return -1;
}
- bridge_channel_suspend(bridge, bridge_channel);
+ bridge_channel_suspend_nolock(bridge_channel);
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
return 0;
}
@@ -1425,17 +4072,18 @@ int ast_bridge_suspend(struct ast_bridge *bridge, struct ast_channel *chan)
int ast_bridge_unsuspend(struct ast_bridge *bridge, struct ast_channel *chan)
{
struct ast_bridge_channel *bridge_channel;
+/* BUGBUG the case of a disolved bridge while channel is suspended is not handled. */
- ao2_lock(bridge);
+ ast_bridge_lock(bridge);
if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
return -1;
}
- bridge_channel_unsuspend(bridge, bridge_channel);
+ bridge_channel_unsuspend_nolock(bridge_channel);
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
return 0;
}
@@ -1447,10 +4095,11 @@ void ast_bridge_technology_suspend(struct ast_bridge_technology *technology)
void ast_bridge_technology_unsuspend(struct ast_bridge_technology *technology)
{
+/* BUGBUG unsuspending a bridge technology probably needs to prod all existing bridges to see if they should start using it. */
technology->suspended = 0;
}
-int ast_bridge_features_register(enum ast_bridge_builtin_feature feature, ast_bridge_features_hook_callback callback, const char *dtmf)
+int ast_bridge_features_register(enum ast_bridge_builtin_feature feature, ast_bridge_hook_callback callback, const char *dtmf)
{
if (ARRAY_LEN(builtin_features_handlers) <= feature
|| builtin_features_handlers[feature]) {
@@ -1478,30 +4127,175 @@ int ast_bridge_features_unregister(enum ast_bridge_builtin_feature feature)
return 0;
}
-int ast_bridge_features_hook(struct ast_bridge_features *features,
+int ast_bridge_interval_register(enum ast_bridge_builtin_interval interval, ast_bridge_builtin_set_limits_fn callback)
+{
+ if (ARRAY_LEN(builtin_interval_handlers) <= interval
+ || builtin_interval_handlers[interval]) {
+ return -1;
+ }
+
+ builtin_interval_handlers[interval] = callback;
+
+ return 0;
+}
+
+int ast_bridge_interval_unregister(enum ast_bridge_builtin_interval interval)
+{
+ if (ARRAY_LEN(builtin_interval_handlers) <= interval
+ || !builtin_interval_handlers[interval]) {
+ return -1;
+ }
+
+ builtin_interval_handlers[interval] = NULL;
+
+ return 0;
+
+}
+
+/*!
+ * \internal
+ * \brief Bridge hook destructor.
+ * \since 12.0.0
+ *
+ * \param vhook Object to destroy.
+ *
+ * \return Nothing
+ */
+static void bridge_hook_destroy(void *vhook)
+{
+ struct ast_bridge_hook *hook = vhook;
+
+ if (hook->destructor) {
+ hook->destructor(hook->hook_pvt);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Allocate and setup a generic bridge hook.
+ * \since 12.0.0
+ *
+ * \param size How big an object to allocate.
+ * \param callback Function to execute upon activation
+ * \param hook_pvt Unique data
+ * \param destructor Optional destructor callback for hook_pvt data
+ * \param remove_on_pull TRUE if remove the hook when the channel is pulled from the bridge.
+ *
+ * \retval hook on success.
+ * \retval NULL on error.
+ */
+static struct ast_bridge_hook *bridge_hook_generic(size_t size,
+ ast_bridge_hook_callback callback,
+ void *hook_pvt,
+ ast_bridge_hook_pvt_destructor destructor,
+ int remove_on_pull)
+{
+ struct ast_bridge_hook *hook;
+
+ /* Allocate new hook and setup it's basic variables */
+ hook = ao2_alloc_options(size, bridge_hook_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
+ if (hook) {
+ hook->callback = callback;
+ hook->destructor = destructor;
+ hook->hook_pvt = hook_pvt;
+ hook->remove_on_pull = remove_on_pull;
+ }
+
+ return hook;
+}
+
+int ast_bridge_dtmf_hook(struct ast_bridge_features *features,
const char *dtmf,
- ast_bridge_features_hook_callback callback,
+ ast_bridge_hook_callback callback,
void *hook_pvt,
- ast_bridge_features_hook_pvt_destructor destructor)
+ ast_bridge_hook_pvt_destructor destructor,
+ int remove_on_pull)
{
- struct ast_bridge_features_hook *hook;
+ struct ast_bridge_hook *hook;
+ int res;
- /* Allocate new memory and setup it's various variables */
- hook = ast_calloc(1, sizeof(*hook));
+ /* Allocate new hook and setup it's various variables */
+ hook = bridge_hook_generic(sizeof(*hook), callback, hook_pvt, destructor,
+ remove_on_pull);
if (!hook) {
return -1;
}
- ast_copy_string(hook->dtmf, dtmf, sizeof(hook->dtmf));
- hook->callback = callback;
- hook->destructor = destructor;
- hook->hook_pvt = hook_pvt;
+ ast_copy_string(hook->parms.dtmf.code, dtmf, sizeof(hook->parms.dtmf.code));
- /* Once done we add it onto the list. Now it will be picked up when DTMF is used */
- AST_LIST_INSERT_TAIL(&features->hooks, hook, entry);
+ /* Once done we put it in the container. */
+ res = ao2_link(features->dtmf_hooks, hook) ? 0 : -1;
+ ao2_ref(hook, -1);
- features->usable = 1;
+ return res;
+}
- return 0;
+int ast_bridge_hangup_hook(struct ast_bridge_features *features,
+ ast_bridge_hook_callback callback,
+ void *hook_pvt,
+ ast_bridge_hook_pvt_destructor destructor,
+ int remove_on_pull)
+{
+ struct ast_bridge_hook *hook;
+ int res;
+
+ /* Allocate new hook and setup it's various variables */
+ hook = bridge_hook_generic(sizeof(*hook), callback, hook_pvt, destructor,
+ remove_on_pull);
+ if (!hook) {
+ return -1;
+ }
+
+ /* Once done we put it in the container. */
+ res = ao2_link(features->hangup_hooks, hook) ? 0 : -1;
+ ao2_ref(hook, -1);
+
+ return res;
+}
+
+int ast_bridge_join_hook(struct ast_bridge_features *features,
+ ast_bridge_hook_callback callback,
+ void *hook_pvt,
+ ast_bridge_hook_pvt_destructor destructor,
+ int remove_on_pull)
+{
+ struct ast_bridge_hook *hook;
+ int res;
+
+ /* Allocate new hook and setup it's various variables */
+ hook = bridge_hook_generic(sizeof(*hook), callback, hook_pvt, destructor,
+ remove_on_pull);
+ if (!hook) {
+ return -1;
+ }
+
+ /* Once done we put it in the container. */
+ res = ao2_link(features->join_hooks, hook) ? 0 : -1;
+ ao2_ref(hook, -1);
+
+ return res;
+}
+
+int ast_bridge_leave_hook(struct ast_bridge_features *features,
+ ast_bridge_hook_callback callback,
+ void *hook_pvt,
+ ast_bridge_hook_pvt_destructor destructor,
+ int remove_on_pull)
+{
+ struct ast_bridge_hook *hook;
+ int res;
+
+ /* Allocate new hook and setup it's various variables */
+ hook = bridge_hook_generic(sizeof(*hook), callback, hook_pvt, destructor,
+ remove_on_pull);
+ if (!hook) {
+ return -1;
+ }
+
+ /* Once done we put it in the container. */
+ res = ao2_link(features->leave_hooks, hook) ? 0 : -1;
+ ao2_ref(hook, -1);
+
+ return res;
}
void ast_bridge_features_set_talk_detector(struct ast_bridge_features *features,
@@ -1514,7 +4308,57 @@ void ast_bridge_features_set_talk_detector(struct ast_bridge_features *features,
features->talker_pvt_data = pvt_data;
}
-int ast_bridge_features_enable(struct ast_bridge_features *features, enum ast_bridge_builtin_feature feature, const char *dtmf, void *config)
+int ast_bridge_interval_hook(struct ast_bridge_features *features,
+ unsigned int interval,
+ ast_bridge_hook_callback callback,
+ void *hook_pvt,
+ ast_bridge_hook_pvt_destructor destructor,
+ int remove_on_pull)
+{
+ struct ast_bridge_hook *hook;
+ int res;
+
+ if (!features ||!interval || !callback) {
+ return -1;
+ }
+
+ if (!features->interval_timer) {
+ if (!(features->interval_timer = ast_timer_open())) {
+ ast_log(LOG_ERROR, "Failed to open a timer when adding a timed bridging feature.\n");
+ return -1;
+ }
+ ast_timer_set_rate(features->interval_timer, BRIDGE_FEATURES_INTERVAL_RATE);
+ }
+
+ /* Allocate new hook and setup it's various variables */
+ hook = bridge_hook_generic(sizeof(*hook), callback, hook_pvt, destructor,
+ remove_on_pull);
+ if (!hook) {
+ return -1;
+ }
+ hook->parms.timer.interval = interval;
+ hook->parms.timer.trip_time = ast_tvadd(ast_tvnow(), ast_samp2tv(hook->parms.timer.interval, 1000));
+ hook->parms.timer.seqno = ast_atomic_fetchadd_int((int *) &features->interval_sequence, +1);
+
+ ast_debug(1, "Putting interval hook %p with interval %u in the heap on features %p\n",
+ hook, hook->parms.timer.interval, features);
+ ast_heap_wrlock(features->interval_hooks);
+ res = ast_heap_push(features->interval_hooks, hook);
+ if (res) {
+ /* Could not push the hook onto the heap. */
+ ao2_ref(hook, -1);
+ }
+ ast_heap_unlock(features->interval_hooks);
+
+ return res ? -1 : 0;
+}
+
+int ast_bridge_features_enable(struct ast_bridge_features *features,
+ enum ast_bridge_builtin_feature feature,
+ const char *dtmf,
+ void *config,
+ ast_bridge_hook_pvt_destructor destructor,
+ int remove_on_pull)
{
if (ARRAY_LEN(builtin_features_handlers) <= feature
|| !builtin_features_handlers[feature]) {
@@ -1526,81 +4370,323 @@ int ast_bridge_features_enable(struct ast_bridge_features *features, enum ast_br
dtmf = builtin_features_dtmf[feature];
/* If no DTMF is still available (ie: it has been disabled) then error out now */
if (ast_strlen_zero(dtmf)) {
- ast_debug(1, "Failed to enable built in feature %d on %p, no DTMF string is available for it.\n", feature, features);
+ ast_debug(1, "Failed to enable built in feature %d on %p, no DTMF string is available for it.\n",
+ feature, features);
return -1;
}
}
- /* The rest is basically pretty easy. We create another hook using the built in feature's callback and DTMF, easy as pie. */
- return ast_bridge_features_hook(features, dtmf, builtin_features_handlers[feature], config, NULL);
+ /*
+ * The rest is basically pretty easy. We create another hook
+ * using the built in feature's DTMF callback. Easy as pie.
+ */
+ return ast_bridge_dtmf_hook(features, dtmf, builtin_features_handlers[feature],
+ config, destructor, remove_on_pull);
+}
+
+int ast_bridge_features_limits_construct(struct ast_bridge_features_limits *limits)
+{
+ memset(limits, 0, sizeof(*limits));
+
+ if (ast_string_field_init(limits, 256)) {
+ ast_free(limits);
+ return -1;
+ }
+
+ return 0;
+}
+
+void ast_bridge_features_limits_destroy(struct ast_bridge_features_limits *limits)
+{
+ ast_string_field_free_memory(limits);
}
-void ast_bridge_features_set_flag(struct ast_bridge_features *features, enum ast_bridge_feature_flags flag)
+int ast_bridge_features_set_limits(struct ast_bridge_features *features, struct ast_bridge_features_limits *limits, int remove_on_pull)
+{
+ if (builtin_interval_handlers[AST_BRIDGE_BUILTIN_INTERVAL_LIMITS]) {
+ ast_bridge_builtin_set_limits_fn bridge_features_set_limits_callback;
+
+ bridge_features_set_limits_callback = builtin_interval_handlers[AST_BRIDGE_BUILTIN_INTERVAL_LIMITS];
+ return bridge_features_set_limits_callback(features, limits, remove_on_pull);
+ }
+
+ ast_log(LOG_ERROR, "Attempted to set limits without an AST_BRIDGE_BUILTIN_INTERVAL_LIMITS callback registered.\n");
+ return -1;
+}
+
+void ast_bridge_features_set_flag(struct ast_bridge_features *features, unsigned int flag)
{
ast_set_flag(&features->feature_flags, flag);
features->usable = 1;
}
+/*!
+ * \internal
+ * \brief ao2 object match remove_on_pull hooks.
+ * \since 12.0.0
+ *
+ * \param obj Feature hook object.
+ * \param arg Not used
+ * \param flags Not used
+ *
+ * \retval CMP_MATCH if hook has remove_on_pull set.
+ * \retval 0 if not match.
+ */
+static int hook_remove_on_pull_match(void *obj, void *arg, int flags)
+{
+ struct ast_bridge_hook *hook = obj;
+
+ if (hook->remove_on_pull) {
+ return CMP_MATCH;
+ } else {
+ return 0;
+ }
+}
+
+/*!
+ * \internal
+ * \brief Remove all remove_on_pull hooks in the container.
+ * \since 12.0.0
+ *
+ * \param hooks Hooks container to work on.
+ *
+ * \return Nothing
+ */
+static void hooks_remove_on_pull_container(struct ao2_container *hooks)
+{
+ ao2_callback(hooks, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE,
+ hook_remove_on_pull_match, NULL);
+}
+
+/*!
+ * \internal
+ * \brief Remove all remove_on_pull hooks in the heap.
+ * \since 12.0.0
+ *
+ * \param hooks Hooks heap to work on.
+ *
+ * \return Nothing
+ */
+static void hooks_remove_on_pull_heap(struct ast_heap *hooks)
+{
+ struct ast_bridge_hook *hook;
+ int changed;
+
+ ast_heap_wrlock(hooks);
+ do {
+ int idx;
+
+ changed = 0;
+ for (idx = ast_heap_size(hooks); idx; --idx) {
+ hook = ast_heap_peek(hooks, idx);
+ if (hook->remove_on_pull) {
+ ast_heap_remove(hooks, hook);
+ ao2_ref(hook, -1);
+ changed = 1;
+ }
+ }
+ } while (changed);
+ ast_heap_unlock(hooks);
+}
+
+/*!
+ * \internal
+ * \brief Remove marked bridge channel feature hooks.
+ * \since 12.0.0
+ *
+ * \param features Bridge featues structure
+ *
+ * \return Nothing
+ */
+static void bridge_features_remove_on_pull(struct ast_bridge_features *features)
+{
+ hooks_remove_on_pull_container(features->dtmf_hooks);
+ hooks_remove_on_pull_container(features->hangup_hooks);
+ hooks_remove_on_pull_container(features->join_hooks);
+ hooks_remove_on_pull_container(features->leave_hooks);
+ hooks_remove_on_pull_heap(features->interval_hooks);
+}
+
+static int interval_hook_time_cmp(void *a, void *b)
+{
+ struct ast_bridge_hook *hook_a = a;
+ struct ast_bridge_hook *hook_b = b;
+ int cmp;
+
+ cmp = ast_tvcmp(hook_b->parms.timer.trip_time, hook_a->parms.timer.trip_time);
+ if (cmp) {
+ return cmp;
+ }
+
+ cmp = hook_b->parms.timer.seqno - hook_a->parms.timer.seqno;
+ return cmp;
+}
+
+/*!
+ * \internal
+ * \brief DTMF hook container sort comparison function.
+ * \since 12.0.0
+ *
+ * \param obj_left pointer to the (user-defined part) of an object.
+ * \param obj_right pointer to the (user-defined part) of an object.
+ * \param flags flags from ao2_callback()
+ * OBJ_POINTER - if set, 'obj_right', is an object.
+ * OBJ_KEY - if set, 'obj_right', is a search key item that is not an object.
+ * OBJ_PARTIAL_KEY - if set, 'obj_right', is a partial search key item that is not an object.
+ *
+ * \retval <0 if obj_left < obj_right
+ * \retval =0 if obj_left == obj_right
+ * \retval >0 if obj_left > obj_right
+ */
+static int bridge_dtmf_hook_sort(const void *obj_left, const void *obj_right, int flags)
+{
+ const struct ast_bridge_hook *hook_left = obj_left;
+ const struct ast_bridge_hook *hook_right = obj_right;
+ const char *right_key = obj_right;
+ int cmp;
+
+ switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+ default:
+ case OBJ_POINTER:
+ right_key = hook_right->parms.dtmf.code;
+ /* Fall through */
+ case OBJ_KEY:
+ cmp = strcasecmp(hook_left->parms.dtmf.code, right_key);
+ break;
+ case OBJ_PARTIAL_KEY:
+ cmp = strncasecmp(hook_left->parms.dtmf.code, right_key, strlen(right_key));
+ break;
+ }
+ return cmp;
+}
+
+/* BUGBUG 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)
{
/* Zero out the structure */
memset(features, 0, sizeof(*features));
- /* Initialize the hooks list, just in case */
- AST_LIST_HEAD_INIT_NOLOCK(&features->hooks);
+ /* Initialize the DTMF hooks container */
+ features->dtmf_hooks = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX,
+ AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE, bridge_dtmf_hook_sort, NULL);
+ if (!features->dtmf_hooks) {
+ return -1;
+ }
+
+ /* Initialize the hangup hooks container */
+ features->hangup_hooks = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL,
+ NULL);
+ if (!features->hangup_hooks) {
+ return -1;
+ }
+
+ /* Initialize the join hooks container */
+ features->join_hooks = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL,
+ NULL);
+ if (!features->join_hooks) {
+ return -1;
+ }
+
+ /* Initialize the leave hooks container */
+ features->leave_hooks = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL,
+ NULL);
+ if (!features->leave_hooks) {
+ return -1;
+ }
+
+ /* Initialize the interval hooks heap */
+ features->interval_hooks = ast_heap_create(8, interval_hook_time_cmp,
+ offsetof(struct ast_bridge_hook, parms.timer.heap_index));
+ if (!features->interval_hooks) {
+ return -1;
+ }
return 0;
}
+/* BUGBUG make ast_bridge_features_cleanup() static when make ast_bridge_join() requires features to be allocated. */
void ast_bridge_features_cleanup(struct ast_bridge_features *features)
{
- struct ast_bridge_features_hook *hook;
+ struct ast_bridge_hook *hook;
- /* This is relatively simple, hooks are kept as a list on the features structure so we just pop them off and free them */
- while ((hook = AST_LIST_REMOVE_HEAD(&features->hooks, entry))) {
- if (hook->destructor) {
- hook->destructor(hook->hook_pvt);
+ /* Destroy the interval hooks heap. */
+ if (features->interval_hooks) {
+ while ((hook = ast_heap_pop(features->interval_hooks))) {
+ ao2_ref(hook, -1);
}
- ast_free(hook);
+ features->interval_hooks = ast_heap_destroy(features->interval_hooks);
+ }
+
+ if (features->interval_timer) {
+ ast_timer_close(features->interval_timer);
+ features->interval_timer = NULL;
}
+
+ /* If the features contains a limits pvt, destroy that as well. */
+ if (features->limits) {
+ ast_bridge_features_limits_destroy(features->limits);
+ ast_free(features->limits);
+ features->limits = NULL;
+ }
+
if (features->talker_destructor_cb && features->talker_pvt_data) {
features->talker_destructor_cb(features->talker_pvt_data);
features->talker_pvt_data = NULL;
}
+
+ /* Destroy the leave hooks container. */
+ ao2_cleanup(features->leave_hooks);
+ features->leave_hooks = NULL;
+
+ /* Destroy the join hooks container. */
+ ao2_cleanup(features->join_hooks);
+ features->join_hooks = NULL;
+
+ /* Destroy the hangup hooks container. */
+ ao2_cleanup(features->hangup_hooks);
+ features->hangup_hooks = NULL;
+
+ /* Destroy the DTMF hooks container. */
+ ao2_cleanup(features->dtmf_hooks);
+ features->dtmf_hooks = NULL;
}
-int ast_bridge_dtmf_stream(struct ast_bridge *bridge, const char *dtmf, struct ast_channel *chan)
+void ast_bridge_features_destroy(struct ast_bridge_features *features)
{
- struct ast_bridge_channel *bridge_channel;
+ if (!features) {
+ return;
+ }
+ ast_bridge_features_cleanup(features);
+ ast_free(features);
+}
- ao2_lock(bridge);
+struct ast_bridge_features *ast_bridge_features_new(void)
+{
+ struct ast_bridge_features *features;
- AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
- if (bridge_channel->chan == chan) {
- continue;
+ features = ast_malloc(sizeof(*features));
+ if (features) {
+ if (ast_bridge_features_init(features)) {
+ ast_bridge_features_destroy(features);
+ features = NULL;
}
- ast_copy_string(bridge_channel->dtmf_stream_q, dtmf, sizeof(bridge_channel->dtmf_stream_q));
- ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_DTMF);
}
- ao2_unlock(bridge);
-
- return 0;
+ return features;
}
void ast_bridge_set_mixing_interval(struct ast_bridge *bridge, unsigned int mixing_interval)
{
- ao2_lock(bridge);
+ ast_bridge_lock(bridge);
bridge->internal_mixing_interval = mixing_interval;
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
}
void ast_bridge_set_internal_sample_rate(struct ast_bridge *bridge, unsigned int sample_rate)
{
-
- ao2_lock(bridge);
+ ast_bridge_lock(bridge);
bridge->internal_sample_rate = sample_rate;
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
}
static void cleanup_video_mode(struct ast_bridge *bridge)
@@ -1626,22 +4712,22 @@ static void cleanup_video_mode(struct ast_bridge *bridge)
void ast_bridge_set_single_src_video_mode(struct ast_bridge *bridge, struct ast_channel *video_src_chan)
{
- ao2_lock(bridge);
+ ast_bridge_lock(bridge);
cleanup_video_mode(bridge);
bridge->video_mode.mode = AST_BRIDGE_VIDEO_MODE_SINGLE_SRC;
bridge->video_mode.mode_data.single_src_data.chan_vsrc = ast_channel_ref(video_src_chan);
ast_test_suite_event_notify("BRIDGE_VIDEO_MODE", "Message: video mode set to single source\r\nVideo Mode: %d\r\nVideo Channel: %s", bridge->video_mode.mode, ast_channel_name(video_src_chan));
ast_indicate(video_src_chan, AST_CONTROL_VIDUPDATE);
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
}
void ast_bridge_set_talker_src_video_mode(struct ast_bridge *bridge)
{
- ao2_lock(bridge);
+ ast_bridge_lock(bridge);
cleanup_video_mode(bridge);
bridge->video_mode.mode = AST_BRIDGE_VIDEO_MODE_TALKER_SRC;
ast_test_suite_event_notify("BRIDGE_VIDEO_MODE", "Message: video mode set to talker source\r\nVideo Mode: %d", bridge->video_mode.mode);
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
}
void ast_bridge_update_talker_src_video_mode(struct ast_bridge *bridge, struct ast_channel *chan, int talker_energy, int is_keyframe)
@@ -1652,7 +4738,7 @@ void ast_bridge_update_talker_src_video_mode(struct ast_bridge *bridge, struct a
return;
}
- ao2_lock(bridge);
+ ast_bridge_lock(bridge);
data = &bridge->video_mode.mode_data.talker_src_data;
if (data->chan_vsrc == chan) {
@@ -1680,14 +4766,14 @@ void ast_bridge_update_talker_src_video_mode(struct ast_bridge *bridge, struct a
data->chan_old_vsrc = ast_channel_ref(chan);
ast_indicate(chan, AST_CONTROL_VIDUPDATE);
}
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
}
int ast_bridge_number_video_src(struct ast_bridge *bridge)
{
int res = 0;
- ao2_lock(bridge);
+ ast_bridge_lock(bridge);
switch (bridge->video_mode.mode) {
case AST_BRIDGE_VIDEO_MODE_NONE:
break;
@@ -1704,7 +4790,7 @@ int ast_bridge_number_video_src(struct ast_bridge *bridge)
res++;
}
}
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
return res;
}
@@ -1712,7 +4798,7 @@ int ast_bridge_is_video_src(struct ast_bridge *bridge, struct ast_channel *chan)
{
int res = 0;
- ao2_lock(bridge);
+ ast_bridge_lock(bridge);
switch (bridge->video_mode.mode) {
case AST_BRIDGE_VIDEO_MODE_NONE:
break;
@@ -1729,13 +4815,13 @@ int ast_bridge_is_video_src(struct ast_bridge *bridge, struct ast_channel *chan)
}
}
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
return res;
}
void ast_bridge_remove_video_src(struct ast_bridge *bridge, struct ast_channel *chan)
{
- ao2_lock(bridge);
+ ast_bridge_lock(bridge);
switch (bridge->video_mode.mode) {
case AST_BRIDGE_VIDEO_MODE_NONE:
break;
@@ -1762,5 +4848,996 @@ void ast_bridge_remove_video_src(struct ast_bridge *bridge, struct ast_channel *
bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc = NULL;
}
}
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
+}
+
+static int channel_hash(const void *obj, int flags)
+{
+ const struct ast_channel *chan = obj;
+ const char *name = obj;
+ int hash;
+
+ switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+ default:
+ case OBJ_POINTER:
+ name = ast_channel_name(chan);
+ /* Fall through */
+ case OBJ_KEY:
+ hash = ast_str_hash(name);
+ break;
+ case OBJ_PARTIAL_KEY:
+ /* Should never happen in hash callback. */
+ ast_assert(0);
+ hash = 0;
+ break;
+ }
+ return hash;
+}
+
+static int channel_cmp(void *obj, void *arg, int flags)
+{
+ const struct ast_channel *left = obj;
+ const struct ast_channel *right = arg;
+ const char *right_name = arg;
+ int cmp;
+
+ switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+ default:
+ case OBJ_POINTER:
+ right_name = ast_channel_name(right);
+ /* Fall through */
+ case OBJ_KEY:
+ cmp = strcmp(ast_channel_name(left), right_name);
+ break;
+ case OBJ_PARTIAL_KEY:
+ cmp = strncmp(ast_channel_name(left), right_name, strlen(right_name));
+ break;
+ }
+ return cmp ? 0 : CMP_MATCH;
+}
+
+struct ao2_container *ast_bridge_peers_nolock(struct ast_bridge *bridge)
+{
+ struct ao2_container *channels;
+ struct ast_bridge_channel *iter;
+
+ channels = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK,
+ 13, channel_hash, channel_cmp);
+ if (!channels) {
+ return NULL;
+ }
+
+ AST_LIST_TRAVERSE(&bridge->channels, iter, entry) {
+ ao2_link(channels, iter->chan);
+ }
+
+ return channels;
+}
+
+struct ao2_container *ast_bridge_peers(struct ast_bridge *bridge)
+{
+ struct ao2_container *channels;
+
+ ast_bridge_lock(bridge);
+ channels = ast_bridge_peers_nolock(bridge);
+ ast_bridge_unlock(bridge);
+
+ return channels;
+}
+
+struct ast_channel *ast_bridge_peer_nolock(struct ast_bridge *bridge, struct ast_channel *chan)
+{
+ struct ast_channel *peer = NULL;
+ struct ast_bridge_channel *iter;
+
+ /* Asking for the peer channel only makes sense on a two-party bridge. */
+ if (bridge->num_channels == 2
+ && bridge->technology->capabilities
+ & (AST_BRIDGE_CAPABILITY_NATIVE | AST_BRIDGE_CAPABILITY_1TO1MIX)) {
+ int in_bridge = 0;
+
+ AST_LIST_TRAVERSE(&bridge->channels, iter, entry) {
+ if (iter->chan != chan) {
+ peer = iter->chan;
+ } else {
+ in_bridge = 1;
+ }
+ }
+ if (in_bridge && peer) {
+ ast_channel_ref(peer);
+ } else {
+ peer = NULL;
+ }
+ }
+
+ return peer;
+}
+
+struct ast_channel *ast_bridge_peer(struct ast_bridge *bridge, struct ast_channel *chan)
+{
+ struct ast_channel *peer;
+
+ ast_bridge_lock(bridge);
+ peer = ast_bridge_peer_nolock(bridge, chan);
+ ast_bridge_unlock(bridge);
+
+ return peer;
+}
+
+/*!
+ * \internal
+ * \brief Transfer an entire bridge to a specific destination.
+ *
+ * This creates a local channel to dial out and swaps the called local channel
+ * with the transferer channel. By doing so, all participants in the bridge are
+ * connected to the specified destination.
+ *
+ * While this means of transferring would work for both two-party and multi-party
+ * bridges, this method is only used for multi-party bridges since this method would
+ * be less efficient for two-party bridges.
+ *
+ * \param transferer The channel performing a transfer
+ * \param bridge The bridge where the transfer is being performed
+ * \param exten The destination extension for the blind transfer
+ * \param context The destination context for the blind transfer
+ * \param hook Framehook to attach to local channel
+ * \return The success or failure of the operation
+ */
+static enum ast_transfer_result blind_transfer_bridge(struct ast_channel *transferer,
+ struct ast_bridge *bridge, const char *exten, const char *context,
+ transfer_channel_cb new_channel_cb, void *user_data)
+{
+ struct ast_channel *local;
+ char chan_name[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 2];
+ int cause;
+
+ snprintf(chan_name, sizeof(chan_name), "%s@%s", exten, context);
+ local = ast_request("Local", ast_channel_nativeformats(transferer), transferer,
+ chan_name, &cause);
+ if (!local) {
+ return AST_BRIDGE_TRANSFER_FAIL;
+ }
+
+ if (new_channel_cb) {
+ new_channel_cb(local, user_data);
+ }
+
+ if (ast_call(local, chan_name, 0)) {
+ ast_hangup(local);
+ return AST_BRIDGE_TRANSFER_FAIL;
+ }
+ if (ast_bridge_impart(bridge, local, transferer, NULL, 1)) {
+ ast_hangup(local);
+ return AST_BRIDGE_TRANSFER_FAIL;
+ }
+ return AST_BRIDGE_TRANSFER_SUCCESS;
+}
+
+/*!
+ * \internal
+ * \brief Get the transferee channel
+ *
+ * This is only applicable to cases where a transfer is occurring on a
+ * two-party bridge. The channels container passed in is expected to only
+ * contain two channels, the transferer and the transferee. The transferer
+ * channel is passed in as a parameter to ensure we don't return it as
+ * the transferee channel.
+ *
+ * \param channels A two-channel container containing the transferer and transferee
+ * \param transferer The party that is transfering the call
+ * \return The party that is being transferred
+ */
+static struct ast_channel *get_transferee(struct ao2_container *channels, struct ast_channel *transferer)
+{
+ struct ao2_iterator channel_iter;
+ struct ast_channel *transferee;
+
+ for (channel_iter = ao2_iterator_init(channels, 0);
+ (transferee = ao2_iterator_next(&channel_iter));
+ ao2_cleanup(transferee)) {
+ if (transferee != transferer) {
+ break;
+ }
+ }
+
+ ao2_iterator_destroy(&channel_iter);
+ return transferee;
+}
+
+/*!
+ * \internal
+ * \brief Queue a blind transfer action on a transferee bridge channel
+ *
+ * This is only relevant for when a blind transfer is performed on a two-party
+ * bridge. The transferee's bridge channel will have a blind transfer bridge
+ * action queued onto it, resulting in the party being redirected to a new
+ * destination
+ *
+ * \param transferee The channel to have the action queued on
+ * \param exten The destination extension for the transferee
+ * \param context The destination context for the transferee
+ * \param hook Frame hook to attach to transferee
+ * \retval 0 Successfully queued the action
+ * \retval non-zero Failed to queue the action
+ */
+static int bridge_channel_queue_blind_transfer(struct ast_channel *transferee,
+ const char *exten, const char *context,
+ transfer_channel_cb new_channel_cb, void *user_data)
+{
+ RAII_VAR(struct ast_bridge_channel *, transferee_bridge_channel, NULL, ao2_cleanup);
+ struct blind_transfer_data blind_data;
+
+ ast_channel_lock(transferee);
+ transferee_bridge_channel = ast_channel_get_bridge_channel(transferee);
+ ast_channel_unlock(transferee);
+
+ if (!transferee_bridge_channel) {
+ return -1;
+ }
+
+ if (new_channel_cb) {
+ new_channel_cb(transferee, user_data);
+ }
+
+ ast_copy_string(blind_data.exten, exten, sizeof(blind_data.exten));
+ ast_copy_string(blind_data.context, context, sizeof(blind_data.context));
+
+/* BUGBUG Why doesn't this function return success/failure? */
+ ast_bridge_channel_queue_action_data(transferee_bridge_channel,
+ AST_BRIDGE_ACTION_BLIND_TRANSFER, &blind_data, sizeof(blind_data));
+
+ return 0;
+}
+
+enum try_parking_result {
+ PARKING_SUCCESS,
+ PARKING_FAILURE,
+ PARKING_NOT_APPLICABLE,
+};
+
+static enum try_parking_result try_parking(struct ast_bridge *bridge, struct ast_channel *transferer,
+ const char *exten, const char *context)
+{
+ /* BUGBUG The following is all commented out because the functionality is not
+ * present yet. The functions referenced here are available at team/jrose/bridge_projects.
+ * Once the code there has been merged into team/group/bridge_construction,
+ * this can be uncommented and tested
+ */
+
+#if 0
+ RAII_VAR(struct ast_bridge_channel *, transferer_bridge_channel, NULL, ao2_cleanup);
+ struct ast_exten *parking_exten;
+
+ ast_channel_lock(transferer);
+ transfer_bridge_channel = ast_channel_get_bridge_channel(transferer);
+ ast_channel_unlock(transferer);
+
+ if (!transfer_bridge_channel) {
+ return PARKING_FAILURE;
+ }
+
+ parking_exten = ast_get_parking_exten(exten, NULL, context);
+ if (parking_exten) {
+ return ast_park_blind_xfer(bridge, transferer, parking_exten) == 0 ?
+ PARKING_SUCCESS : PARKING_FAILURE;
+ }
+#endif
+
+ return PARKING_NOT_APPLICABLE;
+}
+
+/*!
+ * \internal
+ * \brief Set the BLINDTRANSFER variable as appropriate on channels involved in the transfer
+ *
+ * The transferer channel will have its BLINDTRANSFER variable set the same as its BRIDGEPEER
+ * variable. This will account for all channels that it is bridged to. The other channels
+ * involved in the transfer will have their BLINDTRANSFER variable set to the transferer
+ * channel's name.
+ *
+ * \param transferer The channel performing the blind transfer
+ * \param channels The channels belonging to the bridge
+ */
+static void set_blind_transfer_variables(struct ast_channel *transferer, struct ao2_container *channels)
+{
+ struct ao2_iterator iter;
+ struct ast_channel *chan;
+ const char *transferer_name;
+ const char *transferer_bridgepeer;
+
+ ast_channel_lock(transferer);
+ transferer_name = ast_strdupa(ast_channel_name(transferer));
+ transferer_bridgepeer = ast_strdupa(S_OR(pbx_builtin_getvar_helper(transferer, "BRIDGEPEER"), ""));
+ ast_channel_unlock(transferer);
+
+ for (iter = ao2_iterator_init(channels, 0);
+ (chan = ao2_iterator_next(&iter));
+ ao2_cleanup(chan)) {
+ if (chan == transferer) {
+ pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", transferer_bridgepeer);
+ } else {
+ pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", transferer_name);
+ }
+ }
+
+ ao2_iterator_destroy(&iter);
+}
+
+enum ast_transfer_result ast_bridge_transfer_blind(struct ast_channel *transferer,
+ const char *exten, const char *context,
+ transfer_channel_cb new_channel_cb, void *user_data)
+{
+ RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+ RAII_VAR(struct ao2_container *, channels, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_channel *, transferee, NULL, ao2_cleanup);
+ int do_bridge_transfer;
+ int transfer_prohibited;
+ enum try_parking_result parking_result;
+
+ ast_channel_lock(transferer);
+ bridge = ast_channel_get_bridge(transferer);
+ ast_channel_unlock(transferer);
+
+ if (!bridge) {
+ return AST_BRIDGE_TRANSFER_INVALID;
+ }
+
+ parking_result = try_parking(bridge, transferer, exten, context);
+ switch (parking_result) {
+ case PARKING_SUCCESS:
+ return AST_BRIDGE_TRANSFER_SUCCESS;
+ case PARKING_FAILURE:
+ return AST_BRIDGE_TRANSFER_FAIL;
+ case PARKING_NOT_APPLICABLE:
+ default:
+ break;
+ }
+
+ {
+ SCOPED_LOCK(lock, bridge, ast_bridge_lock, ast_bridge_unlock);
+ channels = ast_bridge_peers_nolock(bridge);
+ if (!channels) {
+ return AST_BRIDGE_TRANSFER_FAIL;
+ }
+ if (ao2_container_count(channels) <= 1) {
+ return AST_BRIDGE_TRANSFER_INVALID;
+ }
+ transfer_prohibited = ast_test_flag(&bridge->feature_flags,
+ AST_BRIDGE_FLAG_TRANSFER_PROHIBITED);
+ do_bridge_transfer = ast_test_flag(&bridge->feature_flags,
+ AST_BRIDGE_FLAG_TRANSFER_BRIDGE_ONLY) ||
+ ao2_container_count(channels) > 2;
+ }
+
+ if (transfer_prohibited) {
+ return AST_BRIDGE_TRANSFER_NOT_PERMITTED;
+ }
+
+ set_blind_transfer_variables(transferer, channels);
+
+ if (do_bridge_transfer) {
+ return blind_transfer_bridge(transferer, bridge, exten, context,
+ new_channel_cb, user_data);
+ }
+
+ /* Reaching this portion means that we're dealing with a two-party bridge */
+
+ transferee = get_transferee(channels, transferer);
+ if (!transferee) {
+ return AST_BRIDGE_TRANSFER_FAIL;
+ }
+
+ if (bridge_channel_queue_blind_transfer(transferee, exten, context,
+ new_channel_cb, user_data)) {
+ return AST_BRIDGE_TRANSFER_FAIL;
+ }
+
+ ast_bridge_remove(bridge, transferer);
+ return AST_BRIDGE_TRANSFER_SUCCESS;
+}
+
+enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_transferee,
+ struct ast_channel *to_transfer_target, struct ast_framehook *hook)
+{
+ /* First, check the validity of scenario. If invalid, return AST_BRIDGE_TRANSFER_INVALID. The following are invalid:
+ * 1) atxfer of an unbridged call to an unbridged call
+ * 2) atxfer of an unbridged call to a multi-party (n > 2) bridge
+ * 3) atxfer of a multi-party (n > 2) bridge to an unbridged call
+ * Second, check that the bridge(s) involved allows transfers. If not, return AST_BRIDGE_TRANSFER_NOT_PERMITTED.
+ * Third, break into different scenarios for different bridge situations:
+ * If both channels are bridged, perform a bridge merge. Direction of the merge is TBD.
+ * If channel A is bridged, and channel B is not (e.g. transferring to IVR or blond transfer)
+ * Some manner of masquerading is necessary. Presumably, you'd want to move channel A's bridge peer
+ * into where channel B is. However, it may be possible to do something a bit different, where a
+ * local channel is created and put into channel A's bridge. The local channel's ;2 channel
+ * is then masqueraded with channel B in some way.
+ * If channel A is not bridged and channel B is, then:
+ * This is similar to what is done in the previous scenario. Create a local channel and place it
+ * into B's bridge. Then masquerade the ;2 leg of the local channel.
+ */
+
+ /* XXX STUB */
+ return AST_BRIDGE_TRANSFER_SUCCESS;
+}
+
+/*!
+ * \internal
+ * \brief Service the bridge manager request.
+ * \since 12.0.0
+ *
+ * \param bridge requesting service.
+ *
+ * \return Nothing
+ */
+static void bridge_manager_service(struct ast_bridge *bridge)
+{
+ ast_bridge_lock(bridge);
+ if (bridge->callid) {
+ ast_callid_threadassoc_change(bridge->callid);
+ }
+
+ /* Do any pending bridge actions. */
+ bridge_handle_actions(bridge);
+ ast_bridge_unlock(bridge);
+}
+
+/*!
+ * \internal
+ * \brief Bridge manager service thread.
+ * \since 12.0.0
+ *
+ * \return Nothing
+ */
+static void *bridge_manager_thread(void *data)
+{
+ struct bridge_manager_controller *manager = data;
+ struct bridge_manager_request *request;
+
+ ao2_lock(manager);
+ while (!manager->stop) {
+ request = AST_LIST_REMOVE_HEAD(&manager->service_requests, node);
+ if (!request) {
+ ast_cond_wait(&manager->cond, ao2_object_get_lockaddr(manager));
+ continue;
+ }
+ ao2_unlock(manager);
+
+ /* Service the bridge. */
+ bridge_manager_service(request->bridge);
+ ao2_ref(request->bridge, -1);
+ ast_free(request);
+
+ ao2_lock(manager);
+ }
+ ao2_unlock(manager);
+
+ return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Destroy the bridge manager controller.
+ * \since 12.0.0
+ *
+ * \param obj Bridge manager to destroy.
+ *
+ * \return Nothing
+ */
+static void bridge_manager_destroy(void *obj)
+{
+ struct bridge_manager_controller *manager = obj;
+ struct bridge_manager_request *request;
+
+ if (manager->thread != AST_PTHREADT_NULL) {
+ /* Stop the manager thread. */
+ ao2_lock(manager);
+ manager->stop = 1;
+ ast_cond_signal(&manager->cond);
+ ao2_unlock(manager);
+ ast_debug(1, "Waiting for bridge manager thread to die.\n");
+ pthread_join(manager->thread, NULL);
+ }
+
+ /* Destroy the service request queue. */
+ while ((request = AST_LIST_REMOVE_HEAD(&manager->service_requests, node))) {
+ ao2_ref(request->bridge, -1);
+ ast_free(request);
+ }
+
+ ast_cond_destroy(&manager->cond);
+}
+
+/*!
+ * \internal
+ * \brief Create the bridge manager controller.
+ * \since 12.0.0
+ *
+ * \retval manager on success.
+ * \retval NULL on error.
+ */
+static struct bridge_manager_controller *bridge_manager_create(void)
+{
+ struct bridge_manager_controller *manager;
+
+ manager = ao2_alloc(sizeof(*manager), bridge_manager_destroy);
+ if (!manager) {
+ /* Well. This isn't good. */
+ return NULL;
+ }
+ ast_cond_init(&manager->cond, NULL);
+ AST_LIST_HEAD_INIT_NOLOCK(&manager->service_requests);
+
+ /* Create the bridge manager thread. */
+ if (ast_pthread_create(&manager->thread, NULL, bridge_manager_thread, manager)) {
+ /* Well. This isn't good either. */
+ manager->thread = AST_PTHREADT_NULL;
+ ao2_ref(manager, -1);
+ manager = NULL;
+ }
+
+ return manager;
+}
+
+/*!
+ * \internal
+ * \brief Bridge ao2 container sort function.
+ * \since 12.0.0
+ *
+ * \param obj_left pointer to the (user-defined part) of an object.
+ * \param obj_right pointer to the (user-defined part) of an object.
+ * \param flags flags from ao2_callback()
+ * OBJ_POINTER - if set, 'obj_right', is an object.
+ * OBJ_KEY - if set, 'obj_right', is a search key item that is not an object.
+ * OBJ_PARTIAL_KEY - if set, 'obj_right', is a partial search key item that is not an object.
+ *
+ * \retval <0 if obj_left < obj_right
+ * \retval =0 if obj_left == obj_right
+ * \retval >0 if obj_left > obj_right
+ */
+static int bridge_sort_cmp(const void *obj_left, const void *obj_right, int flags)
+{
+ const struct ast_bridge *bridge_left = obj_left;
+ const struct ast_bridge *bridge_right = obj_right;
+ const char *right_key = obj_right;
+ int cmp;
+
+ switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+ default:
+ case OBJ_POINTER:
+ right_key = bridge_right->uniqueid;
+ /* Fall through */
+ case OBJ_KEY:
+ cmp = strcmp(bridge_left->uniqueid, right_key);
+ break;
+ case OBJ_PARTIAL_KEY:
+ cmp = strncmp(bridge_left->uniqueid, right_key, strlen(right_key));
+ break;
+ }
+ return cmp;
+}
+
+struct bridge_complete {
+ /*! Nth match to return. */
+ int state;
+ /*! Which match currently on. */
+ int which;
+};
+
+static int complete_bridge_search(void *obj, void *arg, void *data, int flags)
+{
+ struct bridge_complete *search = data;
+
+ if (++search->which > search->state) {
+ return CMP_MATCH;
+ }
+ return 0;
+}
+
+static char *complete_bridge(const char *word, int state)
+{
+ char *ret;
+ struct ast_bridge *bridge;
+ struct bridge_complete search = {
+ .state = state,
+ };
+
+ bridge = ao2_callback_data(bridges, ast_strlen_zero(word) ? 0 : OBJ_PARTIAL_KEY,
+ complete_bridge_search, (char *) word, &search);
+ if (!bridge) {
+ return NULL;
+ }
+ ret = ast_strdup(bridge->uniqueid);
+ ao2_ref(bridge, -1);
+ return ret;
+}
+
+static char *handle_bridge_show_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+#define FORMAT_HDR "%-36s %5s %-15s %s\n"
+#define FORMAT_ROW "%-36s %5u %-15s %s\n"
+
+ struct ao2_iterator iter;
+ struct ast_bridge *bridge;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "bridge show all";
+ e->usage =
+ "Usage: bridge show all\n"
+ " List all bridges\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+/* BUGBUG this command may need to be changed to look at the stasis cache. */
+ ast_cli(a->fd, FORMAT_HDR, "Bridge-ID", "Chans", "Type", "Technology");
+ iter = ao2_iterator_init(bridges, 0);
+ for (; (bridge = ao2_iterator_next(&iter)); ao2_ref(bridge, -1)) {
+ ast_bridge_lock(bridge);
+ ast_cli(a->fd, FORMAT_ROW,
+ bridge->uniqueid,
+ bridge->num_channels,
+ bridge->v_table ? bridge->v_table->name : "<unknown>",
+ bridge->technology ? bridge->technology->name : "<unknown>");
+ ast_bridge_unlock(bridge);
+ }
+ ao2_iterator_destroy(&iter);
+ return CLI_SUCCESS;
+
+#undef FORMAT_HDR
+#undef FORMAT_ROW
+}
+
+static char *handle_bridge_show_specific(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct ast_bridge *bridge;
+ struct ast_bridge_channel *bridge_channel;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "bridge show";
+ e->usage =
+ "Usage: bridge show <bridge-id>\n"
+ " Show information about the <bridge-id> bridge\n";
+ return NULL;
+ case CLI_GENERATE:
+ if (a->pos == 2) {
+ return complete_bridge(a->word, a->n);
+ }
+ return NULL;
+ }
+
+/* BUGBUG this command may need to be changed to look at the stasis cache. */
+ if (a->argc != 3) {
+ return CLI_SHOWUSAGE;
+ }
+
+ bridge = ao2_find(bridges, a->argv[2], OBJ_KEY);
+ if (!bridge) {
+ ast_cli(a->fd, "Bridge '%s' not found\n", a->argv[2]);
+ return CLI_SUCCESS;
+ }
+
+ ast_bridge_lock(bridge);
+ ast_cli(a->fd, "Id: %s\n", bridge->uniqueid);
+ ast_cli(a->fd, "Type: %s\n", bridge->v_table ? bridge->v_table->name : "<unknown>");
+ ast_cli(a->fd, "Technology: %s\n",
+ bridge->technology ? bridge->technology->name : "<unknown>");
+ ast_cli(a->fd, "Num-Channels: %u\n", bridge->num_channels);
+ AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+ ast_cli(a->fd, "Channel: %s\n", ast_channel_name(bridge_channel->chan));
+ }
+ ast_bridge_unlock(bridge);
+ ao2_ref(bridge, -1);
+
+ return CLI_SUCCESS;
+}
+
+static char *handle_bridge_destroy_specific(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct ast_bridge *bridge;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "bridge destroy";
+ e->usage =
+ "Usage: bridge destroy <bridge-id>\n"
+ " Destroy the <bridge-id> bridge\n";
+ return NULL;
+ case CLI_GENERATE:
+ if (a->pos == 2) {
+ return complete_bridge(a->word, a->n);
+ }
+ return NULL;
+ }
+
+ if (a->argc != 3) {
+ return CLI_SHOWUSAGE;
+ }
+
+ bridge = ao2_find(bridges, a->argv[2], OBJ_KEY);
+ if (!bridge) {
+ ast_cli(a->fd, "Bridge '%s' not found\n", a->argv[2]);
+ return CLI_SUCCESS;
+ }
+
+ ast_cli(a->fd, "Destroying bridge '%s'\n", a->argv[2]);
+ ast_bridge_destroy(bridge);
+
+ return CLI_SUCCESS;
+}
+
+static char *complete_bridge_participant(const char *bridge_name, const char *line, const char *word, int pos, int state)
+{
+ RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+ struct ast_bridge_channel *bridge_channel;
+ int which;
+ int wordlen;
+
+ bridge = ao2_find(bridges, bridge_name, OBJ_KEY);
+ if (!bridge) {
+ return NULL;
+ }
+
+ {
+ SCOPED_LOCK(bridge_lock, bridge, ast_bridge_lock, ast_bridge_unlock);
+
+ which = 0;
+ wordlen = strlen(word);
+ AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+ if (!strncasecmp(ast_channel_name(bridge_channel->chan), word, wordlen)
+ && ++which > state) {
+ return ast_strdup(ast_channel_name(bridge_channel->chan));
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static char *handle_bridge_kick_channel(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_channel *, chan, NULL, ao2_cleanup);
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "bridge kick";
+ e->usage =
+ "Usage: bridge kick <bridge-id> <channel-name>\n"
+ " Kick the <channel-name> channel out of the <bridge-id> bridge\n";
+ return NULL;
+ case CLI_GENERATE:
+ if (a->pos == 2) {
+ return complete_bridge(a->word, a->n);
+ }
+ if (a->pos == 3) {
+ return complete_bridge_participant(a->argv[2], a->line, a->word, a->pos, a->n);
+ }
+ return NULL;
+ }
+
+ if (a->argc != 4) {
+ return CLI_SHOWUSAGE;
+ }
+
+ bridge = ao2_find(bridges, a->argv[2], OBJ_KEY);
+ if (!bridge) {
+ ast_cli(a->fd, "Bridge '%s' not found\n", a->argv[2]);
+ return CLI_SUCCESS;
+ }
+
+ chan = ast_channel_get_by_name_prefix(a->argv[3], strlen(a->argv[3]));
+ if (!chan) {
+ ast_cli(a->fd, "Channel '%s' not found\n", a->argv[3]);
+ return CLI_SUCCESS;
+ }
+
+/*
+ * BUGBUG the CLI kick needs to get the bridge to decide if it should dissolve.
+ *
+ * Likely the best way to do this is to add a kick method. The
+ * basic bridge class can then decide to dissolve the bridge if
+ * one of two channels is kicked.
+ *
+ * SIP/foo -- Local;1==Local;2 -- .... -- Local;1==Local;2 -- SIP/bar
+ * Kick a ;1 channel and the chain toward SIP/foo goes away.
+ * Kick a ;2 channel and the chain toward SIP/bar goes away.
+ *
+ * This can leave a local channel chain between the kicked ;1
+ * and ;2 channels that are orphaned until you manually request
+ * one of those channels to hangup or request the bridge to
+ * dissolve.
+ */
+ ast_cli(a->fd, "Kicking channel '%s' from bridge '%s'\n",
+ ast_channel_name(chan), a->argv[2]);
+ ast_bridge_remove(bridge, chan);
+
+ return CLI_SUCCESS;
+}
+
+/*! Bridge technology capabilities to string. */
+static const char *tech_capability2str(uint32_t capabilities)
+{
+ const char *type;
+
+ if (capabilities & AST_BRIDGE_CAPABILITY_HOLDING) {
+ type = "Holding";
+ } else if (capabilities & AST_BRIDGE_CAPABILITY_EARLY) {
+ type = "Early";
+ } else if (capabilities & AST_BRIDGE_CAPABILITY_NATIVE) {
+ type = "Native";
+ } else if (capabilities & AST_BRIDGE_CAPABILITY_1TO1MIX) {
+ type = "1to1Mix";
+ } else if (capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX) {
+ type = "MultiMix";
+ } else {
+ type = "<Unknown>";
+ }
+ return type;
+}
+
+static char *handle_bridge_technology_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+#define FORMAT_HDR "%-20s %-20s %8s %s\n"
+#define FORMAT_ROW "%-20s %-20s %8d %s\n"
+
+ struct ast_bridge_technology *cur;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "bridge technology show";
+ e->usage =
+ "Usage: bridge technology show\n"
+ " List registered bridge technologies\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ ast_cli(a->fd, FORMAT_HDR, "Name", "Type", "Priority", "Suspended");
+ AST_RWLIST_RDLOCK(&bridge_technologies);
+ AST_RWLIST_TRAVERSE(&bridge_technologies, cur, entry) {
+ const char *type;
+
+ /* Decode type for display */
+ type = tech_capability2str(cur->capabilities);
+
+ ast_cli(a->fd, FORMAT_ROW, cur->name, type, cur->preference,
+ AST_CLI_YESNO(cur->suspended));
+ }
+ AST_RWLIST_UNLOCK(&bridge_technologies);
+ return CLI_SUCCESS;
+
+#undef FORMAT
+}
+
+static char *complete_bridge_technology(const char *word, int state)
+{
+ struct ast_bridge_technology *cur;
+ char *res;
+ int which;
+ int wordlen;
+
+ which = 0;
+ wordlen = strlen(word);
+ AST_RWLIST_RDLOCK(&bridge_technologies);
+ AST_RWLIST_TRAVERSE(&bridge_technologies, cur, entry) {
+ if (!strncasecmp(cur->name, word, wordlen) && ++which > state) {
+ res = ast_strdup(cur->name);
+ AST_RWLIST_UNLOCK(&bridge_technologies);
+ return res;
+ }
+ }
+ AST_RWLIST_UNLOCK(&bridge_technologies);
+ return NULL;
+}
+
+static char *handle_bridge_technology_suspend(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct ast_bridge_technology *cur;
+ int suspend;
+ int successful;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "bridge technology {suspend|unsuspend}";
+ e->usage =
+ "Usage: bridge technology {suspend|unsuspend} <technology-name>\n"
+ " Suspend or unsuspend a bridge technology.\n";
+ return NULL;
+ case CLI_GENERATE:
+ if (a->pos == 3) {
+ return complete_bridge_technology(a->word, a->n);
+ }
+ return NULL;
+ }
+
+ if (a->argc != 4) {
+ return CLI_SHOWUSAGE;
+ }
+
+ suspend = !strcasecmp(a->argv[2], "suspend");
+ successful = 0;
+ AST_RWLIST_WRLOCK(&bridge_technologies);
+ AST_RWLIST_TRAVERSE(&bridge_technologies, cur, entry) {
+ if (!strcasecmp(cur->name, a->argv[3])) {
+ successful = 1;
+ if (suspend) {
+ ast_bridge_technology_suspend(cur);
+ } else {
+ ast_bridge_technology_unsuspend(cur);
+ }
+ break;
+ }
+ }
+ AST_RWLIST_UNLOCK(&bridge_technologies);
+
+ if (successful) {
+ if (suspend) {
+ ast_cli(a->fd, "Suspended bridge technology '%s'\n", a->argv[3]);
+ } else {
+ ast_cli(a->fd, "Unsuspended bridge technology '%s'\n", a->argv[3]);
+ }
+ } else {
+ ast_cli(a->fd, "Bridge technology '%s' not found\n", a->argv[3]);
+ }
+
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry bridge_cli[] = {
+ AST_CLI_DEFINE(handle_bridge_show_all, "List all bridges"),
+ AST_CLI_DEFINE(handle_bridge_show_specific, "Show information about a bridge"),
+ AST_CLI_DEFINE(handle_bridge_destroy_specific, "Destroy a bridge"),
+ AST_CLI_DEFINE(handle_bridge_kick_channel, "Kick a channel from a bridge"),
+ AST_CLI_DEFINE(handle_bridge_technology_show, "List registered bridge technologies"),
+ AST_CLI_DEFINE(handle_bridge_technology_suspend, "Suspend/unsuspend a bridge technology"),
+};
+
+/*!
+ * \internal
+ * \brief Shutdown the bridging system.
+ * \since 12.0.0
+ *
+ * \return Nothing
+ */
+static void bridge_shutdown(void)
+{
+ ast_cli_unregister_multiple(bridge_cli, ARRAY_LEN(bridge_cli));
+ ao2_cleanup(bridges);
+ bridges = NULL;
+ ao2_cleanup(bridge_manager);
+ bridge_manager = NULL;
+ ast_stasis_bridging_shutdown();
+}
+
+int ast_bridging_init(void)
+{
+ if (ast_stasis_bridging_init()) {
+ bridge_shutdown();
+ return -1;
+ }
+
+ bridge_manager = bridge_manager_create();
+ if (!bridge_manager) {
+ bridge_shutdown();
+ return -1;
+ }
+
+ bridges = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_MUTEX,
+ AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE, bridge_sort_cmp, NULL);
+ if (!bridges) {
+ bridge_shutdown();
+ return -1;
+ }
+
+ ast_bridging_init_basic();
+
+/* BUGBUG need AMI action equivalents to the CLI commands. */
+ ast_cli_register_multiple(bridge_cli, ARRAY_LEN(bridge_cli));
+
+ ast_register_atexit(bridge_shutdown);
+ return 0;
}
diff --git a/main/bridging_basic.c b/main/bridging_basic.c
new file mode 100644
index 000000000..00daf7710
--- /dev/null
+++ b/main/bridging_basic.c
@@ -0,0 +1,159 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013 Digium, Inc.
+ *
+ * Richard Mudgett <rmudgett@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 Basic bridge class. It is a subclass of struct ast_bridge.
+ *
+ * \author Richard Mudgett <rmudgett@digium.com>
+ *
+ * See Also:
+ * \arg \ref AstCREDITS
+ */
+
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/channel.h"
+#include "asterisk/utils.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_basic.h"
+#include "asterisk/astobj2.h"
+
+/* ------------------------------------------------------------------- */
+
+static const struct ast_datastore_info dtmf_features_info = {
+ .type = "bridge-dtmf-features",
+ .destroy = ast_free_ptr,
+};
+
+int ast_bridge_features_ds_set(struct ast_channel *chan, struct ast_flags *flags)
+{
+ struct ast_datastore *datastore;
+ struct ast_flags *ds_flags;
+
+ datastore = ast_channel_datastore_find(chan, &dtmf_features_info, NULL);
+ if (datastore) {
+ ds_flags = datastore->data;
+ *ds_flags = *flags;
+ return 0;
+ }
+
+ datastore = ast_datastore_alloc(&dtmf_features_info, NULL);
+ if (!datastore) {
+ return -1;
+ }
+
+ ds_flags = ast_malloc(sizeof(*ds_flags));
+ if (!ds_flags) {
+ ast_datastore_free(datastore);
+ return -1;
+ }
+
+ *ds_flags = *flags;
+ datastore->data = ds_flags;
+ ast_channel_datastore_add(chan, datastore);
+ return 0;
+}
+
+struct ast_flags *ast_bridge_features_ds_get(struct ast_channel *chan)
+{
+ struct ast_datastore *datastore;
+
+ datastore = ast_channel_datastore_find(chan, &dtmf_features_info, NULL);
+ if (!datastore) {
+ return NULL;
+ }
+ return datastore->data;
+}
+
+/*!
+ * \internal
+ * \brief Determine if we should dissolve the bridge from a hangup.
+ * \since 12.0.0
+ *
+ * \param bridge The bridge that the channel is part of
+ * \param bridge_channel Channel executing the feature
+ * \param hook_pvt Private data passed in when the hook was created
+ *
+ * \retval 0 Keep the callback hook.
+ * \retval -1 Remove the callback hook.
+ */
+static int basic_hangup_hook(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+/* BUGBUG Race condition. If all parties but one hangup at the same time, the bridge may not be dissolved on the remaining party. */
+ ast_bridge_channel_lock_bridge(bridge_channel);
+ if (2 < bridge_channel->bridge->num_channels) {
+ /* Just allow this channel to leave the multi-party bridge. */
+ ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+ }
+ ast_bridge_unlock(bridge_channel->bridge);
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief ast_bridge basic push method.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ * \param bridge_channel Bridge channel to push.
+ * \param swap Bridge channel to swap places with if not NULL.
+ *
+ * \note On entry, self is already locked.
+ * \note Stub because of nothing to do.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+static int bridge_basic_push(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap)
+{
+ if (ast_bridge_hangup_hook(bridge_channel->features, basic_hangup_hook, NULL, NULL, 1)
+ || ast_bridge_channel_setup_features(bridge_channel)) {
+ return -1;
+ }
+
+ return ast_bridge_base_v_table.push(self, bridge_channel, swap);
+}
+
+struct ast_bridge_methods ast_bridge_basic_v_table;
+
+struct ast_bridge *ast_bridge_basic_new(void)
+{
+ void *bridge;
+
+ bridge = ast_bridge_alloc(sizeof(struct ast_bridge), &ast_bridge_basic_v_table);
+ bridge = ast_bridge_base_init(bridge,
+ AST_BRIDGE_CAPABILITY_NATIVE | AST_BRIDGE_CAPABILITY_1TO1MIX
+ | AST_BRIDGE_CAPABILITY_MULTIMIX,
+ AST_BRIDGE_FLAG_DISSOLVE_HANGUP | AST_BRIDGE_FLAG_DISSOLVE_EMPTY
+ | AST_BRIDGE_FLAG_SMART);
+ bridge = ast_bridge_register(bridge);
+ return bridge;
+}
+
+void ast_bridging_init_basic(void)
+{
+ /* Setup bridge basic subclass v_table. */
+ ast_bridge_basic_v_table = ast_bridge_base_v_table;
+ ast_bridge_basic_v_table.name = "basic";
+ ast_bridge_basic_v_table.push = bridge_basic_push;
+}
diff --git a/main/bridging_roles.c b/main/bridging_roles.c
new file mode 100644
index 000000000..079cbdb33
--- /dev/null
+++ b/main/bridging_roles.c
@@ -0,0 +1,462 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Jonathan Rose <jrose@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 Bridging Roles API
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ *
+ * \ingroup bridges
+ */
+
+/*** MODULEINFO
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <signal.h>
+
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/datastore.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_roles.h"
+#include "asterisk/stringfields.h"
+
+struct bridge_role_option {
+ AST_LIST_ENTRY(bridge_role_option) list;
+ AST_DECLARE_STRING_FIELDS(
+ AST_STRING_FIELD(option);
+ AST_STRING_FIELD(value);
+ );
+};
+
+struct bridge_role {
+ AST_LIST_ENTRY(bridge_role) list;
+ AST_LIST_HEAD(, bridge_role_option) options;
+ char role[AST_ROLE_LEN];
+};
+
+struct bridge_roles_datastore {
+ AST_LIST_HEAD(, bridge_role) role_list;
+};
+
+/*!
+ * \internal
+ * \brief Destructor function for a bridge role
+ * \since 12.0.0
+ *
+ * \param role bridge_role being destroyed
+ *
+ * \return Nothing
+ */
+static void bridge_role_destroy(struct bridge_role *role)
+{
+ struct bridge_role_option *role_option;
+ while ((role_option = AST_LIST_REMOVE_HEAD(&role->options, list))) {
+ ast_string_field_free_memory(role_option);
+ ast_free(role_option);
+ }
+ ast_free(role);
+}
+
+/*!
+ * \internal
+ * \brief Destructor function for bridge role datastores
+ * \since 12.0.0
+ *
+ * \param data Pointer to the datastore being destroyed
+ *
+ * \return Nothing
+ */
+static void bridge_role_datastore_destroy(void *data)
+{
+ struct bridge_roles_datastore *roles_datastore = data;
+ struct bridge_role *role;
+
+ while ((role = AST_LIST_REMOVE_HEAD(&roles_datastore->role_list, list))) {
+ bridge_role_destroy(role);
+ }
+
+ ast_free(roles_datastore);
+}
+
+static const struct ast_datastore_info bridge_role_info = {
+ .type = "bridge roles",
+ .destroy = bridge_role_datastore_destroy,
+};
+
+/*!
+ * \internal
+ * \brief Setup a bridge role datastore on a channel
+ * \since 12.0.0
+ *
+ * \param chan Chan the datastore is being setup on
+ *
+ * \retval NULL if failed
+ * \retval pointer to the newly created datastore
+ */
+static struct bridge_roles_datastore *setup_bridge_roles_datastore(struct ast_channel *chan)
+{
+ struct ast_datastore *datastore = NULL;
+ struct bridge_roles_datastore *roles_datastore = NULL;
+
+ if (!(datastore = ast_datastore_alloc(&bridge_role_info, NULL))) {
+ return NULL;
+ }
+
+ if (!(roles_datastore = ast_calloc(1, sizeof(*roles_datastore)))) {
+ ast_datastore_free(datastore);
+ return NULL;
+ }
+
+ datastore->data = roles_datastore;
+ ast_channel_datastore_add(chan, datastore);
+ return roles_datastore;
+}
+
+/*!
+ * \internal
+ * \brief Get the bridge_roles_datastore from a channel if it exists. Don't create one if it doesn't.
+ * \since 12.0.0
+ *
+ * \param chan Channel we want the bridge_roles_datastore from
+ *
+ * \retval NULL if we can't find the datastore
+ * \retval pointer to the bridge_roles_datastore
+ */
+static struct bridge_roles_datastore *fetch_bridge_roles_datastore(struct ast_channel *chan)
+{
+ struct ast_datastore *datastore = NULL;
+
+ ast_channel_lock(chan);
+ if (!(datastore = ast_channel_datastore_find(chan, &bridge_role_info, NULL))) {
+ ast_channel_unlock(chan);
+ return NULL;
+ }
+ ast_channel_unlock(chan);
+
+ return datastore->data;
+}
+
+/*!
+ * \internal
+ * \brief Get the bridge_roles_datastore from a channel if it exists. If not, create one.
+ * \since 12.0.0
+ *
+ * \param chan Channel we want the bridge_roles_datastore from
+ *
+ * \retval NULL If we can't find and can't create the datastore
+ * \retval pointer to the bridge_roles_datastore
+ */
+static struct bridge_roles_datastore *fetch_or_create_bridge_roles_datastore(struct ast_channel *chan)
+{
+ struct bridge_roles_datastore *roles_datastore;
+
+ ast_channel_lock(chan);
+ roles_datastore = fetch_bridge_roles_datastore(chan);
+ if (!roles_datastore) {
+ roles_datastore = setup_bridge_roles_datastore(chan);
+ }
+ ast_channel_unlock(chan);
+
+ return roles_datastore;
+}
+
+/*!
+ * \internal
+ * \brief Obtain a role from a bridge_roles_datastore if the datastore has it
+ * \since 12.0.0
+ *
+ * \param roles_datastore The bridge_roles_datastore we are looking for the role of
+ * \param role_name Name of the role being sought
+ *
+ * \retval NULL if the datastore does not have the requested role
+ * \retval pointer to the requested role
+ */
+static struct bridge_role *get_role_from_datastore(struct bridge_roles_datastore *roles_datastore, const char *role_name)
+{
+ struct bridge_role *role;
+
+ AST_LIST_TRAVERSE(&roles_datastore->role_list, role, list) {
+ if (!strcmp(role->role, role_name)) {
+ return role;
+ }
+ }
+
+ return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Obtain a role from a channel structure if the channel's datastore has it
+ * \since 12.0.0
+ *
+ * \param channel The channel we are checking the role of
+ * \param role_name Name of the role sought
+ *
+ * \retval NULL if the channel's datastore does not have the requested role
+ * \retval pointer to the requested role
+ */
+static struct bridge_role *get_role_from_channel(struct ast_channel *channel, const char *role_name)
+{
+ struct bridge_roles_datastore *roles_datastore = fetch_bridge_roles_datastore(channel);
+ return roles_datastore ? get_role_from_datastore(roles_datastore, role_name) : NULL;
+}
+
+/*!
+ * \internal
+ * \brief Obtain a role option from a bridge role if it exists in the bridge role's option list
+ * \since 12.0.0
+ *
+ * \param role a pointer to the bridge role wea re searching for the option of
+ * \param option Name of the option sought
+ *
+ * \retval NULL if the bridge role doesn't have the requested option
+ * \retval pointer to the requested option
+ */
+static struct bridge_role_option *get_role_option(struct bridge_role *role, const char *option)
+{
+ struct bridge_role_option *role_option = NULL;
+ AST_LIST_TRAVERSE(&role->options, role_option, list) {
+ if (!strcmp(role_option->option, option)) {
+ return role_option;
+ }
+ }
+ return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Setup a bridge role on an existing bridge role datastore
+ * \since 12.0.0
+ *
+ * \param roles_datastore bridge_roles_datastore receiving the new role
+ * \param role_name Name of the role being received
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+static int setup_bridge_role(struct bridge_roles_datastore *roles_datastore, const char *role_name)
+{
+ struct bridge_role *role;
+ role = ast_calloc(1, sizeof(*role));
+
+ if (!role) {
+ return -1;
+ }
+
+ ast_copy_string(role->role, role_name, sizeof(role->role));
+
+ AST_LIST_INSERT_TAIL(&roles_datastore->role_list, role, list);
+ ast_debug(3, "Set role '%s'\n", role_name);
+
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Setup a bridge role option on an existing bridge role
+ * \since 12.0.0
+ *
+ * \param role The role receiving the option
+ * \param option Name of the option
+ * \param value the option's value
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+static int setup_bridge_role_option(struct bridge_role *role, const char *option, const char *value)
+{
+ struct bridge_role_option *role_option;
+
+ if (!value) {
+ value = "";
+ }
+
+ role_option = ast_calloc(1, sizeof(*role_option));
+ if (!role_option) {
+ return -1;
+ }
+
+ if (ast_string_field_init(role_option, 32)) {
+ ast_free(role_option);
+ return -1;
+ }
+
+ ast_string_field_set(role_option, option, option);
+ ast_string_field_set(role_option, value, value);
+
+ AST_LIST_INSERT_TAIL(&role->options, role_option, list);
+
+ return 0;
+}
+
+int ast_channel_add_bridge_role(struct ast_channel *chan, const char *role_name)
+{
+ struct bridge_roles_datastore *roles_datastore = fetch_or_create_bridge_roles_datastore(chan);
+
+ if (!roles_datastore) {
+ ast_log(LOG_WARNING, "Unable to set up bridge role datastore on channel %s\n", ast_channel_name(chan));
+ return -1;
+ }
+
+ /* Check to make sure we aren't adding a redundant role */
+ if (get_role_from_datastore(roles_datastore, role_name)) {
+ ast_debug(2, "Bridge role %s is already applied to the channel %s\n", role_name, ast_channel_name(chan));
+ return 0;
+ }
+
+ /* It wasn't already there, so we can just finish setting it up now. */
+ return setup_bridge_role(roles_datastore, role_name);
+}
+
+void ast_channel_remove_bridge_role(struct ast_channel *chan, const char *role_name)
+{
+ struct bridge_roles_datastore *roles_datastore = fetch_bridge_roles_datastore(chan);
+ struct bridge_role *role;
+
+ if (!roles_datastore) {
+ /* The roles datastore didn't already exist, so there is no need to remove a role */
+ ast_debug(2, "Role %s did not exist on channel %s\n", role_name, ast_channel_name(chan));
+ return;
+ }
+
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&roles_datastore->role_list, role, list) {
+ if (!strcmp(role->role, role_name)) {
+ ast_debug(2, "Removing bridge role %s from channel %s\n", role_name, ast_channel_name(chan));
+ AST_LIST_REMOVE_CURRENT(list);
+ bridge_role_destroy(role);
+ return;
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+
+ ast_debug(2, "Role %s did not exist on channel %s\n", role_name, ast_channel_name(chan));
+}
+
+int ast_channel_set_bridge_role_option(struct ast_channel *channel, const char *role_name, const char *option, const char *value)
+{
+ struct bridge_role *role = get_role_from_channel(channel, role_name);
+ struct bridge_role_option *role_option;
+
+ if (!role) {
+ return -1;
+ }
+
+ role_option = get_role_option(role, option);
+
+ if (role_option) {
+ ast_string_field_set(role_option, value, value);
+ return 0;
+ }
+
+ setup_bridge_role_option(role, option, value);
+
+ return 0;
+}
+
+int ast_bridge_channel_has_role(struct ast_bridge_channel *bridge_channel, const char *role_name)
+{
+ if (!bridge_channel->bridge_roles) {
+ return 0;
+ }
+
+ return get_role_from_datastore(bridge_channel->bridge_roles, role_name) ? 1 : 0;
+}
+
+const char *ast_bridge_channel_get_role_option(struct ast_bridge_channel *bridge_channel, const char *role_name, const char *option)
+{
+ struct bridge_role *role;
+ struct bridge_role_option *role_option = NULL;
+
+ if (!bridge_channel->bridge_roles) {
+ return NULL;
+ }
+
+ role = get_role_from_datastore(bridge_channel->bridge_roles, role_name);
+
+ if (!role) {
+ return NULL;
+ }
+
+ role_option = get_role_option(role, option);
+
+ return role_option ? role_option->value : NULL;
+}
+
+int ast_bridge_channel_establish_roles(struct ast_bridge_channel *bridge_channel)
+{
+ struct bridge_roles_datastore *roles_datastore;
+ struct bridge_role *role = NULL;
+ struct bridge_role_option *role_option;
+
+ if (!bridge_channel->chan) {
+ ast_debug(2, "Attempted to set roles on a bridge channel that has no associated channel. That's a bad idea.\n");
+ return -1;
+ }
+
+ if (bridge_channel->bridge_roles) {
+ ast_debug(2, "Attempted to reset roles while roles were already established. Purge existing roles first.\n");
+ return -1;
+ }
+
+ roles_datastore = fetch_bridge_roles_datastore(bridge_channel->chan);
+ if (!roles_datastore) {
+ /* No roles to establish. */
+ return 0;
+ }
+
+ if (!(bridge_channel->bridge_roles = ast_calloc(1, sizeof(*bridge_channel->bridge_roles)))) {
+ return -1;
+ }
+
+ AST_LIST_TRAVERSE(&roles_datastore->role_list, role, list) {
+ struct bridge_role *this_role_copy;
+
+ if (setup_bridge_role(bridge_channel->bridge_roles, role->role)) {
+ /* We need to abandon the copy because we couldn't setup a role */
+ ast_bridge_channel_clear_roles(bridge_channel);
+ return -1;
+ }
+ this_role_copy = AST_LIST_LAST(&bridge_channel->bridge_roles->role_list);
+
+ AST_LIST_TRAVERSE(&role->options, role_option, list) {
+ if (setup_bridge_role_option(this_role_copy, role_option->option, role_option->value)) {
+ /* We need to abandon the copy because we couldn't setup a role option */
+ ast_bridge_channel_clear_roles(bridge_channel);
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+void ast_bridge_channel_clear_roles(struct ast_bridge_channel *bridge_channel)
+{
+ if (bridge_channel->bridge_roles) {
+ bridge_role_datastore_destroy(bridge_channel->bridge_roles);
+ bridge_channel->bridge_roles = NULL;
+ }
+}
diff --git a/main/channel.c b/main/channel.c
index 0b2dedd63..aec43edf7 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -73,6 +73,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/data.h"
#include "asterisk/channel_internal.h"
#include "asterisk/features.h"
+#include "asterisk/bridging.h"
#include "asterisk/test.h"
#include "asterisk/stasis_channels.h"
@@ -1634,6 +1635,7 @@ int ast_is_deferrable_frame(const struct ast_frame *frame)
* be queued up or not.
*/
switch (frame->frametype) {
+ case AST_FRAME_BRIDGE_ACTION:
case AST_FRAME_CONTROL:
case AST_FRAME_TEXT:
case AST_FRAME_IMAGE:
@@ -3013,6 +3015,7 @@ int __ast_answer(struct ast_channel *chan, unsigned int delay, int cdr_answer)
break;
case AST_FRAME_CONTROL:
case AST_FRAME_IAX:
+ case AST_FRAME_BRIDGE_ACTION:
case AST_FRAME_NULL:
case AST_FRAME_CNG:
break;
@@ -6237,87 +6240,21 @@ int ast_channel_make_compatible(struct ast_channel *chan, struct ast_channel *pe
static int __ast_channel_masquerade(struct ast_channel *original, struct ast_channel *clonechan, struct ast_datastore *xfer_ds)
{
int res = -1;
- struct ast_channel *final_orig, *final_clone, *base;
- for (;;) {
- final_orig = original;
- final_clone = clonechan;
-
- ast_channel_lock_both(original, clonechan);
-
- if (ast_test_flag(ast_channel_flags(original), AST_FLAG_ZOMBIE)
- || ast_test_flag(ast_channel_flags(clonechan), AST_FLAG_ZOMBIE)) {
- /* Zombies! Run! */
- ast_log(LOG_WARNING,
- "Can't setup masquerade. One or both channels is dead. (%s <-- %s)\n",
- ast_channel_name(original), ast_channel_name(clonechan));
- ast_channel_unlock(clonechan);
- ast_channel_unlock(original);
- return -1;
- }
-
- /*
- * Each of these channels may be sitting behind a channel proxy
- * (i.e. chan_agent) and if so, we don't really want to
- * masquerade it, but its proxy
- */
- if (ast_channel_internal_bridged_channel(original)
- && (ast_channel_internal_bridged_channel(original) != ast_bridged_channel(original))
- && (ast_channel_internal_bridged_channel(ast_channel_internal_bridged_channel(original)) != original)) {
- final_orig = ast_channel_internal_bridged_channel(original);
- }
- if (ast_channel_internal_bridged_channel(clonechan)
- && (ast_channel_internal_bridged_channel(clonechan) != ast_bridged_channel(clonechan))
- && (ast_channel_internal_bridged_channel(ast_channel_internal_bridged_channel(clonechan)) != clonechan)) {
- final_clone = ast_channel_internal_bridged_channel(clonechan);
- }
- if (ast_channel_tech(final_clone)->get_base_channel
- && (base = ast_channel_tech(final_clone)->get_base_channel(final_clone))) {
- final_clone = base;
- }
-
- if ((final_orig != original) || (final_clone != clonechan)) {
- /*
- * Lots and lots of deadlock avoidance. The main one we're
- * competing with is ast_write(), which locks channels
- * recursively, when working with a proxy channel.
- */
- if (ast_channel_trylock(final_orig)) {
- ast_channel_unlock(clonechan);
- ast_channel_unlock(original);
-
- /* Try again */
- continue;
- }
- if (ast_channel_trylock(final_clone)) {
- ast_channel_unlock(final_orig);
- ast_channel_unlock(clonechan);
- ast_channel_unlock(original);
-
- /* Try again */
- continue;
- }
- ast_channel_unlock(clonechan);
- ast_channel_unlock(original);
- original = final_orig;
- clonechan = final_clone;
-
- if (ast_test_flag(ast_channel_flags(original), AST_FLAG_ZOMBIE)
- || ast_test_flag(ast_channel_flags(clonechan), AST_FLAG_ZOMBIE)) {
- /* Zombies! Run! */
- ast_log(LOG_WARNING,
- "Can't setup masquerade. One or both channels is dead. (%s <-- %s)\n",
- ast_channel_name(original), ast_channel_name(clonechan));
- ast_channel_unlock(clonechan);
- ast_channel_unlock(original);
- return -1;
- }
- }
- break;
+ if (original == clonechan) {
+ ast_log(LOG_WARNING, "Can't masquerade channel '%s' into itself!\n",
+ ast_channel_name(original));
+ return -1;
}
- if (original == clonechan) {
- ast_log(LOG_WARNING, "Can't masquerade channel '%s' into itself!\n", ast_channel_name(original));
+ ast_channel_lock_both(original, clonechan);
+
+ if (ast_test_flag(ast_channel_flags(original), AST_FLAG_ZOMBIE)
+ || ast_test_flag(ast_channel_flags(clonechan), AST_FLAG_ZOMBIE)) {
+ /* Zombies! Run! */
+ ast_log(LOG_WARNING,
+ "Can't setup masquerade. One or both channels is dead. (%s <-- %s)\n",
+ ast_channel_name(original), ast_channel_name(clonechan));
ast_channel_unlock(clonechan);
ast_channel_unlock(original);
return -1;
@@ -6638,15 +6575,33 @@ static void ast_channel_change_linkedid(struct ast_channel *chan, const char *li
ast_cel_linkedid_ref(linkedid);
}
-/*!
- \brief Propagate the oldest linkedid between associated channels
-
-*/
+/*! \brief Propagate the oldest linkedid between associated channels */
void ast_channel_set_linkgroup(struct ast_channel *chan, struct ast_channel *peer)
{
const char* linkedid=NULL;
struct ast_channel *bridged;
+/*
+ * BUGBUG this needs to be updated to not use ast_channel_internal_bridged_channel().
+ * BUGBUG this needs to be updated to not use ast_bridged_channel().
+ *
+ * We may be better off making this a function of the bridging
+ * framework. Essentially, as each channel joins a bridge, the
+ * oldest linkedid should be propagated between all pairs of
+ * channels. This should be handled by bridging (unless you're
+ * in an infinite wait bridge...) just like the BRIDGEPEER
+ * channel variable.
+ *
+ * This is currently called in two places:
+ *
+ * (1) In channel masquerade. To some extent this shouldn't
+ * really be done any longer - we don't really want a channel to
+ * have its linkedid change, even if it replaces a channel that
+ * had an older linkedid. The two channels aren't really
+ * 'related', they're simply swapping with each other.
+ *
+ * (2) In features.c as two channels are bridged.
+ */
linkedid = oldest_linkedid(ast_channel_linkedid(chan), ast_channel_linkedid(peer));
linkedid = oldest_linkedid(linkedid, ast_channel_uniqueid(chan));
linkedid = oldest_linkedid(linkedid, ast_channel_uniqueid(peer));
@@ -6829,7 +6784,7 @@ void ast_do_masquerade(struct ast_channel *original)
* and new masquerade attempts, the channels container must be
* locked for the entire masquerade. The original and clonechan
* need to be unlocked earlier to avoid potential deadlocks with
- * the chan_local deadlock avoidance method.
+ * the unreal/local channel deadlock avoidance method.
*
* The container lock blocks competing masquerade attempts from
* starting as well as being necessary for proper locking order
@@ -7146,11 +7101,6 @@ void ast_do_masquerade(struct ast_channel *original)
/* copy over accuntcode and set peeraccount across the bridge */
ast_channel_accountcode_set(original, S_OR(ast_channel_accountcode(clonechan), ""));
- if (ast_channel_internal_bridged_channel(original)) {
- /* XXX - should we try to lock original's bridged channel here? */
- ast_channel_peeraccount_set(ast_channel_internal_bridged_channel(original), S_OR(ast_channel_accountcode(clonechan), ""));
- ast_cel_report_event(original, AST_CEL_BRIDGE_UPDATE, NULL, NULL, NULL);
- }
ast_debug(1, "Putting channel %s in %s/%s formats\n", ast_channel_name(original),
ast_getformatname(&wformat), ast_getformatname(&rformat));
@@ -7196,6 +7146,8 @@ void ast_do_masquerade(struct ast_channel *original)
ast_channel_unlock(original);
ast_channel_unlock(clonechan);
+ ast_bridge_notify_masquerade(original);
+
if (clone_sending_dtmf_digit) {
/*
* The clonechan was sending a DTMF digit that was not completed
@@ -7348,14 +7300,10 @@ int ast_setstate(struct ast_channel *chan, enum ast_channel_state state)
return 0;
}
-/*! \brief Find bridged channel */
+/*! BUGBUG ast_bridged_channel() is to be removed. */
struct ast_channel *ast_bridged_channel(struct ast_channel *chan)
{
- struct ast_channel *bridged;
- bridged = ast_channel_internal_bridged_channel(chan);
- if (bridged && ast_channel_tech(bridged)->bridged_channel)
- bridged = ast_channel_tech(bridged)->bridged_channel(chan, bridged);
- return bridged;
+ return NULL;
}
static void bridge_playfile(struct ast_channel *chan, struct ast_channel *peer, const char *sound, int remain)
@@ -7723,6 +7671,7 @@ static void bridge_play_sounds(struct ast_channel *c0, struct ast_channel *c1)
}
}
+/* BUGBUG ast_channel_bridge() and anything that only it calls will be removed. */
/*! \brief Bridge two channels together */
enum ast_bridge_result ast_channel_bridge(struct ast_channel *c0, struct ast_channel *c1,
struct ast_bridge_config *config, struct ast_frame **fo, struct ast_channel **rc)
@@ -11232,3 +11181,48 @@ void ast_channel_unlink(struct ast_channel *chan)
{
ao2_unlink(channels, chan);
}
+
+struct ast_bridge *ast_channel_get_bridge(const struct ast_channel *chan)
+{
+ struct ast_bridge *bridge;
+
+ bridge = ast_channel_internal_bridge(chan);
+ if (bridge) {
+ ao2_ref(bridge, +1);
+ }
+ return bridge;
+}
+
+int ast_channel_is_bridged(const struct ast_channel *chan)
+{
+ return ast_channel_internal_bridge(chan) != NULL;
+}
+
+struct ast_channel *ast_channel_bridge_peer(struct ast_channel *chan)
+{
+ struct ast_channel *peer;
+ struct ast_bridge *bridge;
+
+ /* Get the bridge the channel is in. */
+ ast_channel_lock(chan);
+ bridge = ast_channel_get_bridge(chan);
+ ast_channel_unlock(chan);
+ if (!bridge) {
+ return NULL;
+ }
+
+ peer = ast_bridge_peer(bridge, chan);
+ ao2_ref(bridge, -1);
+ return peer;
+}
+
+struct ast_bridge_channel *ast_channel_get_bridge_channel(struct ast_channel *chan)
+{
+ struct ast_bridge_channel *bridge_channel;
+
+ bridge_channel = ast_channel_internal_bridge_channel(chan);
+ if (bridge_channel) {
+ ao2_ref(bridge_channel, +1);
+ }
+ return bridge_channel;
+}
diff --git a/main/channel_internal_api.c b/main/channel_internal_api.c
index f3293d59b..42aaada6d 100644
--- a/main/channel_internal_api.c
+++ b/main/channel_internal_api.c
@@ -65,6 +65,7 @@ struct ast_channel {
void *music_state; /*!< Music State*/
void *generatordata; /*!< Current generator data if there is any */
struct ast_generator *generator; /*!< Current active data generator */
+/* BUGBUG bridged_channel must be eliminated from ast_channel */
struct ast_channel * bridged_channel; /*!< Who are we bridged to, if we're bridged.
* Who is proxying for us, if we are proxied (i.e. chan_agent).
* Do not access directly, use ast_bridged_channel(chan) */
@@ -188,7 +189,9 @@ struct ast_channel {
unsigned short transfercapability; /*!< ISDN Transfer Capability - AST_FLAG_DIGITAL is not enough */
+/* BUGBUG the bridge pointer must change to an ast_channel_bridge pointer because it will never change while the channel is in the bridging system whereas the bridge could change. */
struct ast_bridge *bridge; /*!< Bridge this channel is participating in */
+ struct ast_bridge_channel *bridge_channel;/*!< The bridge_channel this channel is linked with. */
struct ast_timer *timer; /*!< timer object that provided timingfd */
char context[AST_MAX_CONTEXT]; /*!< Dialplan: Current extension context */
@@ -267,7 +270,6 @@ static void channel_data_add_flags(struct ast_data *tree,
ast_data_add_bool(tree, "END_DTMF_ONLY", ast_test_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY));
ast_data_add_bool(tree, "MASQ_NOSTREAM", ast_test_flag(ast_channel_flags(chan), AST_FLAG_MASQ_NOSTREAM));
ast_data_add_bool(tree, "BRIDGE_HANGUP_RUN", ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_RUN));
- ast_data_add_bool(tree, "BRIDGE_HANGUP_DONT", ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT));
ast_data_add_bool(tree, "DISABLE_WORKAROUNDS", ast_test_flag(ast_channel_flags(chan), AST_FLAG_DISABLE_WORKAROUNDS));
ast_data_add_bool(tree, "DISABLE_DEVSTATE_CACHE", ast_test_flag(ast_channel_flags(chan), AST_FLAG_DISABLE_DEVSTATE_CACHE));
}
@@ -1257,6 +1259,15 @@ void ast_channel_internal_bridge_set(struct ast_channel *chan, struct ast_bridge
chan->bridge = value;
}
+struct ast_bridge_channel *ast_channel_internal_bridge_channel(const struct ast_channel *chan)
+{
+ return chan->bridge_channel;
+}
+void ast_channel_internal_bridge_channel_set(struct ast_channel *chan, struct ast_bridge_channel *value)
+{
+ chan->bridge_channel = value;
+}
+
struct ast_channel *ast_channel_internal_bridged_channel(const struct ast_channel *chan)
{
return chan->bridged_channel;
diff --git a/main/cli.c b/main/cli.c
index 7782b2279..22232acbc 100644
--- a/main/cli.c
+++ b/main/cli.c
@@ -60,6 +60,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/lock.h"
#include "asterisk/threadstorage.h"
#include "asterisk/translate.h"
+#include "asterisk/bridging.h"
/*!
* \brief List of restrictions per user.
@@ -899,7 +900,7 @@ static char *handle_chanlist(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
ast_cli(a->fd, FORMAT_STRING2, "Channel", "Location", "State", "Application(Data)");
else if (verbose)
ast_cli(a->fd, VERBOSE_FORMAT_STRING2, "Channel", "Context", "Extension", "Priority", "State", "Application", "Data",
- "CallerID", "Duration", "Accountcode", "PeerAccount", "BridgedTo");
+ "CallerID", "Duration", "Accountcode", "PeerAccount", "BridgeID");
}
if (!count && !(iter = ast_channel_iterator_all_new())) {
@@ -907,12 +908,12 @@ static char *handle_chanlist(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
}
for (; iter && (c = ast_channel_iterator_next(iter)); ast_channel_unref(c)) {
- struct ast_channel *bc;
+ struct ast_bridge *bridge;
char durbuf[10] = "-";
ast_channel_lock(c);
- bc = ast_bridged_channel(c);
+ bridge = ast_channel_get_bridge(c);
if (!count) {
if ((concise || verbose) && ast_channel_cdr(c) && !ast_tvzero(ast_channel_cdr(c)->start)) {
@@ -935,7 +936,7 @@ static char *handle_chanlist(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
S_OR(ast_channel_peeraccount(c), ""),
ast_channel_amaflags(c),
durbuf,
- bc ? ast_channel_name(bc) : "(None)",
+ bridge ? bridge->uniqueid : "(Not bridged)",
ast_channel_uniqueid(c));
} else if (verbose) {
ast_cli(a->fd, VERBOSE_FORMAT_STRING, ast_channel_name(c), ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c), ast_state2str(ast_channel_state(c)),
@@ -945,7 +946,7 @@ static char *handle_chanlist(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
durbuf,
S_OR(ast_channel_accountcode(c), ""),
S_OR(ast_channel_peeraccount(c), ""),
- bc ? ast_channel_name(bc) : "(None)");
+ bridge ? bridge->uniqueid : "(Not bridged)");
} else {
char locbuf[40] = "(None)";
char appdata[40] = "(None)";
@@ -958,6 +959,7 @@ static char *handle_chanlist(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
}
}
ast_channel_unlock(c);
+ ao2_cleanup(bridge);
}
if (iter) {
@@ -1412,6 +1414,7 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
#ifdef CHANNEL_TRACE
int trace_enabled;
#endif
+ struct ast_bridge *bridge;
switch (cmd) {
case CLI_INIT:
@@ -1463,6 +1466,7 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
}
effective_connected_id = ast_channel_connected_effective_id(c);
+ bridge = ast_channel_get_bridge(c);
ast_str_append(&output, 0,
" -- General --\n"
@@ -1490,8 +1494,7 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
" Frames out: %d%s\n"
" Time to Hangup: %ld\n"
" Elapsed Time: %s\n"
- " Direct Bridge: %s\n"
- "Indirect Bridge: %s\n"
+ " Bridge ID: %s\n"
" -- PBX --\n"
" Context: %s\n"
" Extension: %s\n"
@@ -1502,7 +1505,10 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
" Data: %s\n"
" Blocking in: %s\n"
" Call Identifer: %s\n",
- ast_channel_name(c), ast_channel_tech(c)->type, ast_channel_uniqueid(c), ast_channel_linkedid(c),
+ ast_channel_name(c),
+ ast_channel_tech(c)->type,
+ ast_channel_uniqueid(c),
+ ast_channel_linkedid(c),
S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, "(N/A)"),
S_COR(ast_channel_caller(c)->id.name.valid, ast_channel_caller(c)->id.name.str, "(N/A)"),
S_COR(ast_channel_connected(c)->id.number.valid, ast_channel_connected(c)->id.number.str, "(N/A)"),
@@ -1511,7 +1517,9 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
S_COR(effective_connected_id.name.valid, effective_connected_id.name.str, "(N/A)"),
S_OR(ast_channel_dialed(c)->number.str, "(N/A)"),
ast_channel_language(c),
- ast_state2str(ast_channel_state(c)), ast_channel_state(c), ast_channel_rings(c),
+ ast_state2str(ast_channel_state(c)),
+ ast_channel_state(c),
+ ast_channel_rings(c),
ast_getformatname_multiple(nf, sizeof(nf), ast_channel_nativeformats(c)),
ast_getformatname(ast_channel_writeformat(c)),
ast_getformatname(ast_channel_readformat(c)),
@@ -1520,11 +1528,19 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
ast_channel_readtrans(c) ? "Yes" : "No",
ast_translate_path_to_str(ast_channel_readtrans(c), &read_transpath),
ast_channel_fd(c, 0),
- ast_channel_fin(c) & ~DEBUGCHAN_FLAG, (ast_channel_fin(c) & DEBUGCHAN_FLAG) ? " (DEBUGGED)" : "",
- ast_channel_fout(c) & ~DEBUGCHAN_FLAG, (ast_channel_fout(c) & DEBUGCHAN_FLAG) ? " (DEBUGGED)" : "",
- (long)ast_channel_whentohangup(c)->tv_sec,
- cdrtime, ast_channel_internal_bridged_channel(c) ? ast_channel_name(ast_channel_internal_bridged_channel(c)) : "<none>", ast_bridged_channel(c) ? ast_channel_name(ast_bridged_channel(c)) : "<none>",
- ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c), ast_channel_callgroup(c), ast_channel_pickupgroup(c), (ast_channel_appl(c) ? ast_channel_appl(c) : "(N/A)" ),
+ ast_channel_fin(c) & ~DEBUGCHAN_FLAG,
+ (ast_channel_fin(c) & DEBUGCHAN_FLAG) ? " (DEBUGGED)" : "",
+ ast_channel_fout(c) & ~DEBUGCHAN_FLAG,
+ (ast_channel_fout(c) & DEBUGCHAN_FLAG) ? " (DEBUGGED)" : "",
+ (long) ast_channel_whentohangup(c)->tv_sec,
+ cdrtime,
+ bridge ? bridge->uniqueid : "(Not bridged)",
+ ast_channel_context(c),
+ ast_channel_exten(c),
+ ast_channel_priority(c),
+ ast_channel_callgroup(c),
+ ast_channel_pickupgroup(c),
+ (ast_channel_appl(c) ? ast_channel_appl(c) : "(N/A)" ),
(ast_channel_data(c) ? S_OR(ast_channel_data(c), "(Empty)") : "(None)"),
(ast_test_flag(ast_channel_flags(c), AST_FLAG_BLOCKING) ? ast_channel_blockproc(c) : "(Not Blocking)"),
S_OR(call_identifier_str, "(None)"));
@@ -1548,6 +1564,7 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
ast_channel_unlock(c);
c = ast_channel_unref(c);
+ ao2_cleanup(bridge);
ast_cli(a->fd, "%s", ast_str_buffer(output));
ast_free(output);
diff --git a/main/config_options.c b/main/config_options.c
index 06b452131..39a3fbe61 100644
--- a/main/config_options.c
+++ b/main/config_options.c
@@ -222,6 +222,11 @@ int aco_option_register_deprecated(struct aco_info *info, const char *name, stru
return 0;
}
+unsigned int aco_option_get_flags(const struct aco_option *option)
+{
+ return option->flags;
+}
+
#ifdef AST_XML_DOCS
/*! \internal
* \brief Find a particular ast_xml_doc_item from it's parent config_info, types, and name
diff --git a/main/core_local.c b/main/core_local.c
new file mode 100644
index 000000000..2e0bcc48a
--- /dev/null
+++ b/main/core_local.c
@@ -0,0 +1,775 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013 Digium, Inc.
+ *
+ * Richard Mudgett <rmudgett@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 Local proxy channel driver.
+ *
+ * \author Richard Mudgett <rmudgett@digium.com>
+ *
+ * See Also:
+ * \arg \ref AstCREDITS
+ */
+
+/*** MODULEINFO
+ <support_level>core</support_level>
+ ***/
+
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+/* ------------------------------------------------------------------- */
+
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/cli.h"
+#include "asterisk/manager.h"
+#include "asterisk/devicestate.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/bridging.h"
+#include "asterisk/core_unreal.h"
+#include "asterisk/core_local.h"
+#include "asterisk/_private.h"
+
+/*** DOCUMENTATION
+ <manager name="LocalOptimizeAway" language="en_US">
+ <synopsis>
+ Optimize away a local channel when possible.
+ </synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+ <parameter name="Channel" required="true">
+ <para>The channel name to optimize away.</para>
+ </parameter>
+ </syntax>
+ <description>
+ <para>A local channel created with "/n" will not automatically optimize away.
+ Calling this command on the local channel will clear that flag and allow
+ it to optimize away if it's bridged or when it becomes bridged.</para>
+ </description>
+ </manager>
+ ***/
+
+static const char tdesc[] = "Local Proxy Channel Driver";
+
+static struct ao2_container *locals;
+
+static struct ast_channel *local_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause);
+static int local_call(struct ast_channel *ast, const char *dest, int timeout);
+static int local_hangup(struct ast_channel *ast);
+static int local_devicestate(const char *data);
+
+/* PBX interface structure for channel registration */
+static struct ast_channel_tech local_tech = {
+ .type = "Local",
+ .description = tdesc,
+ .requester = local_request,
+ .send_digit_begin = ast_unreal_digit_begin,
+ .send_digit_end = ast_unreal_digit_end,
+ .call = local_call,
+ .hangup = local_hangup,
+ .answer = ast_unreal_answer,
+ .read = ast_unreal_read,
+ .write = ast_unreal_write,
+ .write_video = ast_unreal_write,
+ .exception = ast_unreal_read,
+ .indicate = ast_unreal_indicate,
+ .fixup = ast_unreal_fixup,
+ .send_html = ast_unreal_sendhtml,
+ .send_text = ast_unreal_sendtext,
+ .devicestate = local_devicestate,
+ .queryoption = ast_unreal_queryoption,
+ .setoption = ast_unreal_setoption,
+};
+
+/*! What to do with the ;2 channel when ast_call() happens. */
+enum local_call_action {
+ /* The ast_call() will run dialplan on the ;2 channel. */
+ LOCAL_CALL_ACTION_DIALPLAN,
+ /* The ast_call() will impart the ;2 channel into a bridge. */
+ LOCAL_CALL_ACTION_BRIDGE,
+ /* The ast_call() will masquerade the ;2 channel into a channel. */
+ LOCAL_CALL_ACTION_MASQUERADE,
+};
+
+/*! Join a bridge on ast_call() parameters. */
+struct local_bridge {
+ /*! Bridge to join. */
+ struct ast_bridge *join;
+ /*! Channel to swap with when joining bridge. */
+ struct ast_channel *swap;
+ /*! Features that are specific to this channel when pushed into the bridge. */
+ struct ast_bridge_features *features;
+};
+
+/*!
+ * \brief the local pvt structure for all channels
+ *
+ * The local channel pvt has two ast_chan objects - the "owner" and the "next channel", the outbound channel
+ *
+ * ast_chan owner -> local_pvt -> ast_chan chan
+ */
+struct local_pvt {
+ /*! Unreal channel driver base class values. */
+ struct ast_unreal_pvt base;
+ /*! Additional action arguments */
+ union {
+ /*! Make ;2 join a bridge on ast_call(). */
+ struct local_bridge bridge;
+ /*! Make ;2 masquerade into this channel on ast_call(). */
+ struct ast_channel *masq;
+ } action;
+ /*! What to do with the ;2 channel on ast_call(). */
+ enum local_call_action type;
+ /*! Context to call */
+ char context[AST_MAX_CONTEXT];
+ /*! Extension to call */
+ char exten[AST_MAX_EXTENSION];
+};
+
+struct ast_channel *ast_local_get_peer(struct ast_channel *ast)
+{
+ struct local_pvt *p = ast_channel_tech_pvt(ast);
+ struct local_pvt *found;
+ struct ast_channel *peer;
+
+ if (!p) {
+ return NULL;
+ }
+
+ found = p ? ao2_find(locals, p, 0) : NULL;
+ if (!found) {
+ /* ast is either not a local channel or it has alredy been hungup */
+ return NULL;
+ }
+ ao2_lock(found);
+ if (ast == p->base.owner) {
+ peer = p->base.chan;
+ } else if (ast == p->base.chan) {
+ peer = p->base.owner;
+ } else {
+ peer = NULL;
+ }
+ if (peer) {
+ ast_channel_ref(peer);
+ }
+ ao2_unlock(found);
+ ao2_ref(found, -1);
+ return peer;
+}
+
+/*! \brief Adds devicestate to local channels */
+static int local_devicestate(const char *data)
+{
+ int is_inuse = 0;
+ int res = AST_DEVICE_INVALID;
+ char *exten = ast_strdupa(data);
+ char *context;
+ char *opts;
+ struct local_pvt *lp;
+ struct ao2_iterator it;
+
+ /* Strip options if they exist */
+ opts = strchr(exten, '/');
+ if (opts) {
+ *opts = '\0';
+ }
+
+ context = strchr(exten, '@');
+ if (!context) {
+ ast_log(LOG_WARNING,
+ "Someone used Local/%s somewhere without a @context. This is bad.\n", data);
+ return AST_DEVICE_INVALID;
+ }
+ *context++ = '\0';
+
+ it = ao2_iterator_init(locals, 0);
+ for (; (lp = ao2_iterator_next(&it)); ao2_ref(lp, -1)) {
+ ao2_lock(lp);
+ if (!strcmp(exten, lp->exten)
+ && !strcmp(context, lp->context)) {
+ res = AST_DEVICE_NOT_INUSE;
+ if (lp->base.owner
+ && ast_test_flag(&lp->base, AST_UNREAL_CARETAKER_THREAD)) {
+ is_inuse = 1;
+ }
+ }
+ ao2_unlock(lp);
+ if (is_inuse) {
+ res = AST_DEVICE_INUSE;
+ ao2_ref(lp, -1);
+ break;
+ }
+ }
+ ao2_iterator_destroy(&it);
+
+ if (res == AST_DEVICE_INVALID) {
+ ast_debug(3, "Checking if extension %s@%s exists (devicestate)\n", exten, context);
+ if (ast_exists_extension(NULL, context, exten, 1, NULL)) {
+ res = AST_DEVICE_NOT_INUSE;
+ }
+ }
+
+ return res;
+}
+
+/*!
+ * \internal
+ * \brief Post the LocalBridge AMI event.
+ * \since 12.0.0
+ *
+ * \param p local_pvt to raise the bridge event.
+ *
+ * \return Nothing
+ */
+static void local_bridge_event(struct local_pvt *p)
+{
+ ao2_lock(p);
+ /*** DOCUMENTATION
+ <managerEventInstance>
+ <synopsis>Raised when two halves of a Local Channel form a bridge.</synopsis>
+ <syntax>
+ <parameter name="Channel1">
+ <para>The name of the Local Channel half that bridges to another channel.</para>
+ </parameter>
+ <parameter name="Channel2">
+ <para>The name of the Local Channel half that executes the dialplan.</para>
+ </parameter>
+ <parameter name="Context">
+ <para>The context in the dialplan that Channel2 starts in.</para>
+ </parameter>
+ <parameter name="Exten">
+ <para>The extension in the dialplan that Channel2 starts in.</para>
+ </parameter>
+ <parameter name="LocalOptimization">
+ <enumlist>
+ <enum name="Yes"/>
+ <enum name="No"/>
+ </enumlist>
+ </parameter>
+ </syntax>
+ </managerEventInstance>
+ ***/
+ manager_event(EVENT_FLAG_CALL, "LocalBridge",
+ "Channel1: %s\r\n"
+ "Channel2: %s\r\n"
+ "Uniqueid1: %s\r\n"
+ "Uniqueid2: %s\r\n"
+ "Context: %s\r\n"
+ "Exten: %s\r\n"
+ "LocalOptimization: %s\r\n",
+ ast_channel_name(p->base.owner), ast_channel_name(p->base.chan),
+ ast_channel_uniqueid(p->base.owner), ast_channel_uniqueid(p->base.chan),
+ p->context, p->exten,
+ ast_test_flag(&p->base, AST_UNREAL_NO_OPTIMIZATION) ? "Yes" : "No");
+ ao2_unlock(p);
+}
+
+int ast_local_setup_bridge(struct ast_channel *ast, struct ast_bridge *bridge, struct ast_channel *swap, struct ast_bridge_features *features)
+{
+ struct local_pvt *p;
+ struct local_pvt *found;
+ int res = -1;
+
+ /* Sanity checks. */
+ if (!ast || !bridge) {
+ ast_bridge_features_destroy(features);
+ return -1;
+ }
+
+ ast_channel_lock(ast);
+ p = ast_channel_tech_pvt(ast);
+ ast_channel_unlock(ast);
+
+ found = p ? ao2_find(locals, p, 0) : NULL;
+ if (found) {
+ ao2_lock(found);
+ if (found->type == LOCAL_CALL_ACTION_DIALPLAN
+ && found->base.owner
+ && found->base.chan
+ && !ast_test_flag(&found->base, AST_UNREAL_CARETAKER_THREAD)) {
+ ao2_ref(bridge, +1);
+ if (swap) {
+ ast_channel_ref(swap);
+ }
+ found->type = LOCAL_CALL_ACTION_BRIDGE;
+ found->action.bridge.join = bridge;
+ found->action.bridge.swap = swap;
+ found->action.bridge.features = features;
+ res = 0;
+ } else {
+ ast_bridge_features_destroy(features);
+ }
+ ao2_unlock(found);
+ ao2_ref(found, -1);
+ }
+
+ return res;
+}
+
+int ast_local_setup_masquerade(struct ast_channel *ast, struct ast_channel *masq)
+{
+ struct local_pvt *p;
+ struct local_pvt *found;
+ int res = -1;
+
+ /* Sanity checks. */
+ if (!ast || !masq) {
+ return -1;
+ }
+
+ ast_channel_lock(ast);
+ p = ast_channel_tech_pvt(ast);
+ ast_channel_unlock(ast);
+
+ found = p ? ao2_find(locals, p, 0) : NULL;
+ if (found) {
+ ao2_lock(found);
+ if (found->type == LOCAL_CALL_ACTION_DIALPLAN
+ && found->base.owner
+ && found->base.chan
+ && !ast_test_flag(&found->base, AST_UNREAL_CARETAKER_THREAD)) {
+ ast_channel_ref(masq);
+ found->type = LOCAL_CALL_ACTION_MASQUERADE;
+ found->action.masq = masq;
+ res = 0;
+ }
+ ao2_unlock(found);
+ ao2_ref(found, -1);
+ }
+
+ return res;
+}
+
+/*! \brief Initiate new call, part of PBX interface
+ * dest is the dial string */
+static int local_call(struct ast_channel *ast, const char *dest, int timeout)
+{
+ struct local_pvt *p = ast_channel_tech_pvt(ast);
+ int pvt_locked = 0;
+
+ struct ast_channel *owner = NULL;
+ struct ast_channel *chan = NULL;
+ int res;
+ char *reduced_dest = ast_strdupa(dest);
+ char *slash;
+ const char *chan_cid;
+
+ if (!p) {
+ return -1;
+ }
+
+ /* since we are letting go of channel locks that were locked coming into
+ * this function, then we need to give the tech pvt a ref */
+ ao2_ref(p, 1);
+ ast_channel_unlock(ast);
+
+ ast_unreal_lock_all(&p->base, &chan, &owner);
+ pvt_locked = 1;
+
+ if (owner != ast) {
+ res = -1;
+ goto return_cleanup;
+ }
+
+ if (!owner || !chan) {
+ res = -1;
+ goto return_cleanup;
+ }
+
+ ast_unreal_call_setup(owner, chan);
+
+ /*
+ * If the local channel has /n on the end of it, we need to lop
+ * that off for our argument to setting up the CC_INTERFACES
+ * variable.
+ */
+ if ((slash = strrchr(reduced_dest, '/'))) {
+ *slash = '\0';
+ }
+ ast_set_cc_interfaces_chanvar(chan, reduced_dest);
+
+ ao2_unlock(p);
+ pvt_locked = 0;
+
+ ast_channel_unlock(owner);
+
+ chan_cid = S_COR(ast_channel_caller(chan)->id.number.valid,
+ ast_channel_caller(chan)->id.number.str, NULL);
+ if (chan_cid) {
+ chan_cid = ast_strdupa(chan_cid);
+ }
+ ast_channel_unlock(chan);
+
+ res = -1;
+ switch (p->type) {
+ case LOCAL_CALL_ACTION_DIALPLAN:
+ if (!ast_exists_extension(NULL, p->context, p->exten, 1, chan_cid)) {
+ ast_log(LOG_NOTICE, "No such extension/context %s@%s while calling Local channel\n",
+ p->exten, p->context);
+ } else {
+ local_bridge_event(p);
+
+ /* Start switch on sub channel */
+ res = ast_pbx_start(chan);
+ }
+ break;
+ case LOCAL_CALL_ACTION_BRIDGE:
+ local_bridge_event(p);
+ ast_answer(chan);
+ res = ast_bridge_impart(p->action.bridge.join, chan, p->action.bridge.swap,
+ p->action.bridge.features, 1);
+ ao2_ref(p->action.bridge.join, -1);
+ p->action.bridge.join = NULL;
+ ao2_cleanup(p->action.bridge.swap);
+ p->action.bridge.swap = NULL;
+ p->action.bridge.features = NULL;
+ break;
+ case LOCAL_CALL_ACTION_MASQUERADE:
+ local_bridge_event(p);
+ ast_answer(chan);
+ res = ast_channel_masquerade(p->action.masq, chan);
+ if (!res) {
+ ast_do_masquerade(p->action.masq);
+ /* Chan is now an orphaned zombie. Destroy it. */
+ ast_hangup(chan);
+ }
+ p->action.masq = ast_channel_unref(p->action.masq);
+ break;
+ }
+ if (!res) {
+ ao2_lock(p);
+ ast_set_flag(&p->base, AST_UNREAL_CARETAKER_THREAD);
+ ao2_unlock(p);
+ }
+
+ /* we already unlocked them, clear them here so the cleanup label won't touch them. */
+ owner = ast_channel_unref(owner);
+ chan = ast_channel_unref(chan);
+
+return_cleanup:
+ if (p) {
+ if (pvt_locked) {
+ ao2_unlock(p);
+ }
+ ao2_ref(p, -1);
+ }
+ if (chan) {
+ ast_channel_unlock(chan);
+ ast_channel_unref(chan);
+ }
+
+ /*
+ * owner is supposed to be == to ast, if it is, don't unlock it
+ * because ast must exit locked
+ */
+ if (owner) {
+ if (owner != ast) {
+ ast_channel_unlock(owner);
+ ast_channel_lock(ast);
+ }
+ ast_channel_unref(owner);
+ } else {
+ /* we have to exit with ast locked */
+ ast_channel_lock(ast);
+ }
+
+ return res;
+}
+
+/*! \brief Hangup a call through the local proxy channel */
+static int local_hangup(struct ast_channel *ast)
+{
+ struct local_pvt *p = ast_channel_tech_pvt(ast);
+ int res;
+
+ if (!p) {
+ return -1;
+ }
+
+ /* give the pvt a ref to fulfill calling requirements. */
+ ao2_ref(p, +1);
+ res = ast_unreal_hangup(&p->base, ast);
+ if (!res) {
+ int unlink;
+
+ ao2_lock(p);
+ unlink = !p->base.owner && !p->base.chan;
+ ao2_unlock(p);
+ if (unlink) {
+ ao2_unlink(locals, p);
+ }
+ }
+ ao2_ref(p, -1);
+
+ return res;
+}
+
+/*!
+ * \internal
+ * \brief struct local_pvt destructor.
+ *
+ * \param vdoomed Object to destroy.
+ *
+ * \return Nothing
+ */
+static void local_pvt_destructor(void *vdoomed)
+{
+ struct local_pvt *doomed = vdoomed;
+
+ switch (doomed->type) {
+ case LOCAL_CALL_ACTION_DIALPLAN:
+ break;
+ case LOCAL_CALL_ACTION_BRIDGE:
+ ao2_cleanup(doomed->action.bridge.join);
+ ao2_cleanup(doomed->action.bridge.swap);
+ ast_bridge_features_destroy(doomed->action.bridge.features);
+ break;
+ case LOCAL_CALL_ACTION_MASQUERADE:
+ ao2_cleanup(doomed->action.masq);
+ break;
+ }
+ ast_unreal_destructor(&doomed->base);
+}
+
+/*! \brief Create a call structure */
+static struct local_pvt *local_alloc(const char *data, struct ast_format_cap *cap)
+{
+ struct local_pvt *pvt;
+ char *parse;
+ char *context;
+ char *opts;
+
+ pvt = (struct local_pvt *) ast_unreal_alloc(sizeof(*pvt), local_pvt_destructor, cap);
+ if (!pvt) {
+ return NULL;
+ }
+
+ parse = ast_strdupa(data);
+
+ /*
+ * Local channels intercept MOH by default.
+ *
+ * This is a silly default because it represents state held by
+ * the local channels. Unless local channel optimization is
+ * disabled, the state will dissapear when the local channels
+ * optimize out.
+ */
+ ast_set_flag(&pvt->base, AST_UNREAL_MOH_INTERCEPT);
+
+ /* Look for options */
+ if ((opts = strchr(parse, '/'))) {
+ *opts++ = '\0';
+ if (strchr(opts, 'n')) {
+ ast_set_flag(&pvt->base, AST_UNREAL_NO_OPTIMIZATION);
+ }
+ if (strchr(opts, 'j')) {
+ if (ast_test_flag(&pvt->base, AST_UNREAL_NO_OPTIMIZATION)) {
+ ast_set_flag(&pvt->base.jb_conf, AST_JB_ENABLED);
+ } else {
+ ast_log(LOG_ERROR, "You must use the 'n' option with the 'j' option to enable the jitter buffer\n");
+ }
+ }
+ if (strchr(opts, 'm')) {
+ ast_clear_flag(&pvt->base, AST_UNREAL_MOH_INTERCEPT);
+ }
+ }
+
+ /* Look for a context */
+ if ((context = strchr(parse, '@'))) {
+ *context++ = '\0';
+ }
+
+ ast_copy_string(pvt->context, S_OR(context, "default"), sizeof(pvt->context));
+ ast_copy_string(pvt->exten, parse, sizeof(pvt->exten));
+ snprintf(pvt->base.name, sizeof(pvt->base.name), "%s@%s", pvt->exten, pvt->context);
+
+ return pvt; /* this is returned with a ref */
+}
+
+/*! \brief Part of PBX interface */
+static struct ast_channel *local_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause)
+{
+ struct local_pvt *p;
+ struct ast_channel *chan;
+ struct ast_callid *callid;
+
+ /* Allocate a new private structure and then Asterisk channels */
+ p = local_alloc(data, cap);
+ if (!p) {
+ return NULL;
+ }
+ callid = ast_read_threadstorage_callid();
+ chan = ast_unreal_new_channels(&p->base, &local_tech, AST_STATE_DOWN, AST_STATE_RING,
+ p->exten, p->context, requestor, callid);
+ if (chan) {
+ ao2_link(locals, p);
+ }
+ if (callid) {
+ ast_callid_unref(callid);
+ }
+ ao2_ref(p, -1); /* kill the ref from the alloc */
+
+ return chan;
+}
+
+/*! \brief CLI command "local show channels" */
+static char *locals_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct local_pvt *p;
+ struct ao2_iterator it;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "local show channels";
+ e->usage =
+ "Usage: local show channels\n"
+ " Provides summary information on active local proxy channels.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3) {
+ return CLI_SHOWUSAGE;
+ }
+
+ if (ao2_container_count(locals) == 0) {
+ ast_cli(a->fd, "No local channels in use\n");
+ return RESULT_SUCCESS;
+ }
+
+ it = ao2_iterator_init(locals, 0);
+ while ((p = ao2_iterator_next(&it))) {
+ ao2_lock(p);
+ ast_cli(a->fd, "%s -- %s\n",
+ p->base.owner ? ast_channel_name(p->base.owner) : "<unowned>",
+ p->base.name);
+ ao2_unlock(p);
+ ao2_ref(p, -1);
+ }
+ ao2_iterator_destroy(&it);
+
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_local[] = {
+ AST_CLI_DEFINE(locals_show, "List status of local channels"),
+};
+
+static int manager_optimize_away(struct mansession *s, const struct message *m)
+{
+ const char *channel;
+ struct local_pvt *p;
+ struct local_pvt *found;
+ struct ast_channel *chan;
+
+ channel = astman_get_header(m, "Channel");
+ if (ast_strlen_zero(channel)) {
+ astman_send_error(s, m, "'Channel' not specified.");
+ return 0;
+ }
+
+ chan = ast_channel_get_by_name(channel);
+ if (!chan) {
+ astman_send_error(s, m, "Channel does not exist.");
+ return 0;
+ }
+
+ p = ast_channel_tech_pvt(chan);
+ ast_channel_unref(chan);
+
+ found = p ? ao2_find(locals, p, 0) : NULL;
+ if (found) {
+ ao2_lock(found);
+ ast_clear_flag(&found->base, AST_UNREAL_NO_OPTIMIZATION);
+ ao2_unlock(found);
+ ao2_ref(found, -1);
+ astman_send_ack(s, m, "Queued channel to be optimized away");
+ } else {
+ astman_send_error(s, m, "Unable to find channel");
+ }
+
+ return 0;
+}
+
+
+static int locals_cmp_cb(void *obj, void *arg, int flags)
+{
+ return (obj == arg) ? CMP_MATCH : 0;
+}
+
+/*!
+ * \internal
+ * \brief Shutdown the local proxy channel.
+ * \since 12.0.0
+ *
+ * \return Nothing
+ */
+static void local_shutdown(void)
+{
+ struct local_pvt *p;
+ struct ao2_iterator it;
+
+ /* First, take us out of the channel loop */
+ ast_cli_unregister_multiple(cli_local, ARRAY_LEN(cli_local));
+ ast_manager_unregister("LocalOptimizeAway");
+ ast_channel_unregister(&local_tech);
+
+ it = ao2_iterator_init(locals, 0);
+ while ((p = ao2_iterator_next(&it))) {
+ if (p->base.owner) {
+ ast_softhangup(p->base.owner, AST_SOFTHANGUP_APPUNLOAD);
+ }
+ ao2_ref(p, -1);
+ }
+ ao2_iterator_destroy(&it);
+ ao2_ref(locals, -1);
+ locals = NULL;
+
+ ast_format_cap_destroy(local_tech.capabilities);
+}
+
+int ast_local_init(void)
+{
+ if (!(local_tech.capabilities = ast_format_cap_alloc())) {
+ return -1;
+ }
+ ast_format_cap_add_all(local_tech.capabilities);
+
+ locals = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, locals_cmp_cb);
+ if (!locals) {
+ ast_format_cap_destroy(local_tech.capabilities);
+ return -1;
+ }
+
+ /* Make sure we can register our channel type */
+ if (ast_channel_register(&local_tech)) {
+ ast_log(LOG_ERROR, "Unable to register channel class 'Local'\n");
+ ao2_ref(locals, -1);
+ ast_format_cap_destroy(local_tech.capabilities);
+ return -1;
+ }
+ ast_cli_register_multiple(cli_local, ARRAY_LEN(cli_local));
+ ast_manager_register_xml_core("LocalOptimizeAway", EVENT_FLAG_SYSTEM|EVENT_FLAG_CALL, manager_optimize_away);
+
+ ast_register_atexit(local_shutdown);
+ return 0;
+}
diff --git a/main/core_unreal.c b/main/core_unreal.c
new file mode 100644
index 000000000..d5e588111
--- /dev/null
+++ b/main/core_unreal.c
@@ -0,0 +1,855 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013 Digium, Inc.
+ *
+ * Richard Mudgett <rmudgett@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 Unreal channel derivatives framework for channel drivers like local channels.
+ *
+ * \author Richard Mudgett <rmudgett@digium.com>
+ *
+ * See Also:
+ * \arg \ref AstCREDITS
+ */
+
+/*** MODULEINFO
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/causes.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/bridging.h"
+#include "asterisk/core_unreal.h"
+
+static unsigned int name_sequence = 0;
+
+void ast_unreal_lock_all(struct ast_unreal_pvt *p, struct ast_channel **outchan, struct ast_channel **outowner)
+{
+ struct ast_channel *chan = NULL;
+ struct ast_channel *owner = NULL;
+
+ ao2_lock(p);
+ for (;;) {
+ if (p->chan) {
+ chan = p->chan;
+ ast_channel_ref(chan);
+ }
+ if (p->owner) {
+ owner = p->owner;
+ ast_channel_ref(owner);
+ }
+ ao2_unlock(p);
+
+ /* if we don't have both channels, then this is very easy */
+ if (!owner || !chan) {
+ if (owner) {
+ ast_channel_lock(owner);
+ } else if(chan) {
+ ast_channel_lock(chan);
+ }
+ } else {
+ /* lock both channels first, then get the pvt lock */
+ ast_channel_lock_both(chan, owner);
+ }
+ ao2_lock(p);
+
+ /* Now that we have all the locks, validate that nothing changed */
+ if (p->owner != owner || p->chan != chan) {
+ if (owner) {
+ ast_channel_unlock(owner);
+ owner = ast_channel_unref(owner);
+ }
+ if (chan) {
+ ast_channel_unlock(chan);
+ chan = ast_channel_unref(chan);
+ }
+ continue;
+ }
+
+ break;
+ }
+ *outowner = p->owner;
+ *outchan = p->chan;
+}
+
+/* Called with ast locked */
+int ast_unreal_setoption(struct ast_channel *ast, int option, void *data, int datalen)
+{
+ int res = 0;
+ struct ast_unreal_pvt *p;
+ struct ast_channel *otherchan = NULL;
+ ast_chan_write_info_t *write_info;
+
+ if (option != AST_OPTION_CHANNEL_WRITE) {
+ return -1;
+ }
+
+ write_info = data;
+
+ if (write_info->version != AST_CHAN_WRITE_INFO_T_VERSION) {
+ ast_log(LOG_ERROR, "The chan_write_info_t type has changed, and this channel hasn't been updated!\n");
+ return -1;
+ }
+
+ if (!strcmp(write_info->function, "CHANNEL")
+ && !strncasecmp(write_info->data, "hangup_handler_", 15)) {
+ /* Block CHANNEL(hangup_handler_xxx) writes to the other unreal channel. */
+ return 0;
+ }
+
+ /* get the tech pvt */
+ if (!(p = ast_channel_tech_pvt(ast))) {
+ return -1;
+ }
+ ao2_ref(p, 1);
+ ast_channel_unlock(ast); /* Held when called, unlock before locking another channel */
+
+ /* get the channel we are supposed to write to */
+ ao2_lock(p);
+ otherchan = (write_info->chan == p->owner) ? p->chan : p->owner;
+ if (!otherchan || otherchan == write_info->chan) {
+ res = -1;
+ otherchan = NULL;
+ ao2_unlock(p);
+ goto setoption_cleanup;
+ }
+ ast_channel_ref(otherchan);
+
+ /* clear the pvt lock before grabbing the channel */
+ ao2_unlock(p);
+
+ ast_channel_lock(otherchan);
+ res = write_info->write_fn(otherchan, write_info->function, write_info->data, write_info->value);
+ ast_channel_unlock(otherchan);
+
+setoption_cleanup:
+ ao2_ref(p, -1);
+ if (otherchan) {
+ ast_channel_unref(otherchan);
+ }
+ ast_channel_lock(ast); /* Lock back before we leave */
+ return res;
+}
+
+/* Called with ast locked */
+int ast_unreal_queryoption(struct ast_channel *ast, int option, void *data, int *datalen)
+{
+ struct ast_unreal_pvt *p;
+ struct ast_channel *peer;
+ struct ast_channel *other;
+ int res = 0;
+
+ if (option != AST_OPTION_T38_STATE) {
+ /* AST_OPTION_T38_STATE is the only supported option at this time */
+ return -1;
+ }
+
+ /* for some reason the channel is not locked in channel.c when this function is called */
+ if (!(p = ast_channel_tech_pvt(ast))) {
+ return -1;
+ }
+
+ ao2_lock(p);
+ other = AST_UNREAL_IS_OUTBOUND(ast, p) ? p->owner : p->chan;
+ if (!other) {
+ ao2_unlock(p);
+ return -1;
+ }
+ ast_channel_ref(other);
+ ao2_unlock(p);
+ ast_channel_unlock(ast); /* Held when called, unlock before locking another channel */
+
+ peer = ast_channel_bridge_peer(other);
+ if (peer) {
+ res = ast_channel_queryoption(peer, option, data, datalen, 0);
+ ast_channel_unref(peer);
+ }
+ ast_channel_unref(other);
+ ast_channel_lock(ast); /* Lock back before we leave */
+
+ return res;
+}
+
+/*!
+ * \brief queue a frame onto either the p->owner or p->chan
+ *
+ * \note the ast_unreal_pvt MUST have it's ref count bumped before entering this function and
+ * decremented after this function is called. This is a side effect of the deadlock
+ * avoidance that is necessary to lock 2 channels and a tech_pvt. Without a ref counted
+ * ast_unreal_pvt, it is impossible to guarantee it will not be destroyed by another thread
+ * during deadlock avoidance.
+ */
+static int unreal_queue_frame(struct ast_unreal_pvt *p, int isoutbound, struct ast_frame *f,
+ struct ast_channel *us, int us_locked)
+{
+ struct ast_channel *other;
+
+ /* Recalculate outbound channel */
+ other = isoutbound ? p->owner : p->chan;
+ if (!other) {
+ return 0;
+ }
+
+ /* do not queue frame if generator is on both unreal channels */
+ if (us && ast_channel_generator(us) && ast_channel_generator(other)) {
+ return 0;
+ }
+
+ /* grab a ref on the channel before unlocking the pvt,
+ * other can not go away from us now regardless of locking */
+ ast_channel_ref(other);
+ if (us && us_locked) {
+ ast_channel_unlock(us);
+ }
+ ao2_unlock(p);
+
+ if (f->frametype == AST_FRAME_CONTROL && f->subclass.integer == AST_CONTROL_RINGING) {
+ ast_setstate(other, AST_STATE_RINGING);
+ }
+ ast_queue_frame(other, f);
+
+ other = ast_channel_unref(other);
+ if (us && us_locked) {
+ ast_channel_lock(us);
+ }
+ ao2_lock(p);
+
+ return 0;
+}
+
+int ast_unreal_answer(struct ast_channel *ast)
+{
+ struct ast_unreal_pvt *p = ast_channel_tech_pvt(ast);
+ int isoutbound;
+ int res = -1;
+
+ if (!p) {
+ return -1;
+ }
+
+ ao2_ref(p, 1);
+ ao2_lock(p);
+ isoutbound = AST_UNREAL_IS_OUTBOUND(ast, p);
+ if (isoutbound) {
+ /* Pass along answer since somebody answered us */
+ struct ast_frame answer = { AST_FRAME_CONTROL, { AST_CONTROL_ANSWER } };
+
+ res = unreal_queue_frame(p, isoutbound, &answer, ast, 1);
+ } else {
+ ast_log(LOG_WARNING, "Huh? %s is being asked to answer?\n",
+ ast_channel_name(ast));
+ }
+ ao2_unlock(p);
+ ao2_ref(p, -1);
+ return res;
+}
+
+/*!
+ * \internal
+ * \brief Check and optimize out the unreal channels between bridges.
+ * \since 12.0.0
+ *
+ * \param ast Channel writing a frame into the unreal channels.
+ * \param p Unreal channel private.
+ *
+ * \note It is assumed that ast is locked.
+ * \note It is assumed that p is locked.
+ *
+ * \retval 0 if unreal channels were not optimized out.
+ * \retval non-zero if unreal channels were optimized out.
+ */
+static int got_optimized_out(struct ast_channel *ast, struct ast_unreal_pvt *p)
+{
+ /* Do a few conditional checks early on just to see if this optimization is possible */
+ if (ast_test_flag(p, AST_UNREAL_NO_OPTIMIZATION) || !p->chan || !p->owner) {
+ return 0;
+ }
+ if (ast == p->owner) {
+ return ast_bridge_unreal_optimized_out(p->owner, p->chan);
+ }
+ if (ast == p->chan) {
+ return ast_bridge_unreal_optimized_out(p->chan, p->owner);
+ }
+ /* ast is not valid to optimize. */
+ return 0;
+}
+
+struct ast_frame *ast_unreal_read(struct ast_channel *ast)
+{
+ return &ast_null_frame;
+}
+
+int ast_unreal_write(struct ast_channel *ast, struct ast_frame *f)
+{
+ struct ast_unreal_pvt *p = ast_channel_tech_pvt(ast);
+ int res = -1;
+
+ if (!p) {
+ return -1;
+ }
+
+ /* Just queue for delivery to the other side */
+ ao2_ref(p, 1);
+ ao2_lock(p);
+ switch (f->frametype) {
+ case AST_FRAME_VOICE:
+ case AST_FRAME_VIDEO:
+ if (got_optimized_out(ast, p)) {
+ break;
+ }
+ /* fall through */
+ default:
+ res = unreal_queue_frame(p, AST_UNREAL_IS_OUTBOUND(ast, p), f, ast, 1);
+ break;
+ }
+ ao2_unlock(p);
+ ao2_ref(p, -1);
+
+ return res;
+}
+
+int ast_unreal_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
+{
+ struct ast_unreal_pvt *p = ast_channel_tech_pvt(newchan);
+ struct ast_bridge *bridge_owner;
+ struct ast_bridge *bridge_chan;
+
+ if (!p) {
+ return -1;
+ }
+
+ ao2_lock(p);
+
+ if ((p->owner != oldchan) && (p->chan != oldchan)) {
+ ast_log(LOG_WARNING, "Old channel %p wasn't %p or %p\n", oldchan, p->owner, p->chan);
+ ao2_unlock(p);
+ return -1;
+ }
+ if (p->owner == oldchan) {
+ p->owner = newchan;
+ } else {
+ p->chan = newchan;
+ }
+
+ if (ast_check_hangup(newchan) || !p->owner || !p->chan) {
+ ao2_unlock(p);
+ return 0;
+ }
+
+ /* Do not let a masquerade cause an unreal channel to be bridged to itself! */
+ bridge_owner = ast_channel_internal_bridge(p->owner);
+ bridge_chan = ast_channel_internal_bridge(p->chan);
+ if (bridge_owner && bridge_owner == bridge_chan) {
+ ast_log(LOG_WARNING, "You can not bridge an unreal channel (%s) to itself!\n",
+ ast_channel_name(newchan));
+ ao2_unlock(p);
+ ast_queue_hangup(newchan);
+ return -1;
+ }
+
+ ao2_unlock(p);
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Queue up a frame representing the indication as a control frame.
+ * \since 12.0.0
+ *
+ * \param p Unreal private structure.
+ * \param ast Channel indicating the condition.
+ * \param condition What is being indicated.
+ * \param data Extra data.
+ * \param datalen Length of extra data.
+ *
+ * \retval 0 on success.
+ * \retval AST_T38_REQUEST_PARMS if successful and condition is AST_CONTROL_T38_PARAMETERS.
+ * \retval -1 on error.
+ */
+static int unreal_queue_indicate(struct ast_unreal_pvt *p, struct ast_channel *ast, int condition, const void *data, size_t datalen)
+{
+ int res = 0;
+ int isoutbound;
+
+ ao2_lock(p);
+ /*
+ * Block -1 stop tones events if we are to be optimized out. We
+ * don't need a flurry of these events on an unreal channel chain
+ * when initially connected to slow the optimization process.
+ */
+ if (0 <= condition || ast_test_flag(p, AST_UNREAL_NO_OPTIMIZATION)) {
+ struct ast_frame f = {
+ .frametype = AST_FRAME_CONTROL,
+ .subclass.integer = condition,
+ .data.ptr = (void *) data,
+ .datalen = datalen,
+ };
+
+ isoutbound = AST_UNREAL_IS_OUTBOUND(ast, p);
+ res = unreal_queue_frame(p, isoutbound, &f, ast, 1);
+ if (!res
+ && condition == AST_CONTROL_T38_PARAMETERS
+ && datalen == sizeof(struct ast_control_t38_parameters)) {
+ const struct ast_control_t38_parameters *parameters = data;
+
+ if (parameters->request_response == AST_T38_REQUEST_PARMS) {
+ res = AST_T38_REQUEST_PARMS;
+ }
+ }
+ } else {
+ ast_debug(4, "Blocked indication %d\n", condition);
+ }
+ ao2_unlock(p);
+
+ return res;
+}
+
+/*!
+ * \internal
+ * \brief Handle COLP and redirecting conditions.
+ * \since 12.0.0
+ *
+ * \param p Unreal private structure.
+ * \param ast Channel indicating the condition.
+ * \param condition What is being indicated.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int unreal_colp_redirect_indicate(struct ast_unreal_pvt *p, struct ast_channel *ast, int condition)
+{
+ struct ast_channel *this_channel;
+ struct ast_channel *the_other_channel;
+ int isoutbound;
+ int res = 0;
+
+ /*
+ * A connected line update frame may only contain a partial
+ * amount of data, such as just a source, or just a ton, and not
+ * the full amount of information. However, the collected
+ * information is all stored in the outgoing channel's
+ * connectedline structure, so when receiving a connected line
+ * update on an outgoing unreal channel, we need to transmit the
+ * collected connected line information instead of whatever
+ * happens to be in this control frame. The same applies for
+ * redirecting information, which is why it is handled here as
+ * well.
+ */
+ ao2_lock(p);
+ isoutbound = AST_UNREAL_IS_OUTBOUND(ast, p);
+ if (isoutbound) {
+ this_channel = p->chan;
+ the_other_channel = p->owner;
+ } else {
+ this_channel = p->owner;
+ the_other_channel = p->chan;
+ }
+ if (the_other_channel) {
+ unsigned char frame_data[1024];
+ struct ast_frame f = {
+ .frametype = AST_FRAME_CONTROL,
+ .subclass.integer = condition,
+ .data.ptr = frame_data,
+ };
+
+ if (condition == AST_CONTROL_CONNECTED_LINE) {
+ ast_connected_line_copy_to_caller(ast_channel_caller(the_other_channel),
+ ast_channel_connected(this_channel));
+ f.datalen = ast_connected_line_build_data(frame_data, sizeof(frame_data),
+ ast_channel_connected(this_channel), NULL);
+ } else {
+ f.datalen = ast_redirecting_build_data(frame_data, sizeof(frame_data),
+ ast_channel_redirecting(this_channel), NULL);
+ }
+ res = unreal_queue_frame(p, isoutbound, &f, ast, 1);
+ }
+ ao2_unlock(p);
+
+ return res;
+}
+
+int ast_unreal_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)
+{
+ struct ast_unreal_pvt *p = ast_channel_tech_pvt(ast);
+ int res = 0;
+
+ if (!p) {
+ return -1;
+ }
+
+ ao2_ref(p, 1); /* ref for unreal_queue_frame */
+
+ switch (condition) {
+ case AST_CONTROL_CONNECTED_LINE:
+ case AST_CONTROL_REDIRECTING:
+ res = unreal_colp_redirect_indicate(p, ast, condition);
+ break;
+ case AST_CONTROL_HOLD:
+ if (ast_test_flag(p, AST_UNREAL_MOH_INTERCEPT)) {
+ ast_moh_start(ast, data, NULL);
+ break;
+ }
+ res = unreal_queue_indicate(p, ast, condition, data, datalen);
+ break;
+ case AST_CONTROL_UNHOLD:
+ if (ast_test_flag(p, AST_UNREAL_MOH_INTERCEPT)) {
+ ast_moh_stop(ast);
+ break;
+ }
+ res = unreal_queue_indicate(p, ast, condition, data, datalen);
+ break;
+ default:
+ res = unreal_queue_indicate(p, ast, condition, data, datalen);
+ break;
+ }
+
+ ao2_ref(p, -1);
+ return res;
+}
+
+int ast_unreal_digit_begin(struct ast_channel *ast, char digit)
+{
+ struct ast_unreal_pvt *p = ast_channel_tech_pvt(ast);
+ int res = -1;
+ struct ast_frame f = { AST_FRAME_DTMF_BEGIN, };
+ int isoutbound;
+
+ if (!p) {
+ return -1;
+ }
+
+ ao2_ref(p, 1); /* ref for unreal_queue_frame */
+ ao2_lock(p);
+ isoutbound = AST_UNREAL_IS_OUTBOUND(ast, p);
+ f.subclass.integer = digit;
+ res = unreal_queue_frame(p, isoutbound, &f, ast, 0);
+ ao2_unlock(p);
+ ao2_ref(p, -1);
+
+ return res;
+}
+
+int ast_unreal_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
+{
+ struct ast_unreal_pvt *p = ast_channel_tech_pvt(ast);
+ int res = -1;
+ struct ast_frame f = { AST_FRAME_DTMF_END, };
+ int isoutbound;
+
+ if (!p) {
+ return -1;
+ }
+
+ ao2_ref(p, 1); /* ref for unreal_queue_frame */
+ ao2_lock(p);
+ isoutbound = AST_UNREAL_IS_OUTBOUND(ast, p);
+ f.subclass.integer = digit;
+ f.len = duration;
+ res = unreal_queue_frame(p, isoutbound, &f, ast, 0);
+ ao2_unlock(p);
+ ao2_ref(p, -1);
+
+ return res;
+}
+
+int ast_unreal_sendtext(struct ast_channel *ast, const char *text)
+{
+ struct ast_unreal_pvt *p = ast_channel_tech_pvt(ast);
+ int res = -1;
+ struct ast_frame f = { AST_FRAME_TEXT, };
+ int isoutbound;
+
+ if (!p) {
+ return -1;
+ }
+
+ ao2_ref(p, 1); /* ref for unreal_queue_frame */
+ ao2_lock(p);
+ isoutbound = AST_UNREAL_IS_OUTBOUND(ast, p);
+ f.data.ptr = (char *) text;
+ f.datalen = strlen(text) + 1;
+ res = unreal_queue_frame(p, isoutbound, &f, ast, 0);
+ ao2_unlock(p);
+ ao2_ref(p, -1);
+ return res;
+}
+
+int ast_unreal_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen)
+{
+ struct ast_unreal_pvt *p = ast_channel_tech_pvt(ast);
+ int res = -1;
+ struct ast_frame f = { AST_FRAME_HTML, };
+ int isoutbound;
+
+ if (!p) {
+ return -1;
+ }
+
+ ao2_ref(p, 1); /* ref for unreal_queue_frame */
+ ao2_lock(p);
+ isoutbound = AST_UNREAL_IS_OUTBOUND(ast, p);
+ f.subclass.integer = subclass;
+ f.data.ptr = (char *)data;
+ f.datalen = datalen;
+ res = unreal_queue_frame(p, isoutbound, &f, ast, 0);
+ ao2_unlock(p);
+ ao2_ref(p, -1);
+
+ return res;
+}
+
+void ast_unreal_call_setup(struct ast_channel *semi1, struct ast_channel *semi2)
+{
+ struct ast_var_t *varptr;
+ struct ast_var_t *clone_var;
+
+ /*
+ * Note that cid_num and cid_name aren't passed in the
+ * ast_channel_alloc calls in ast_unreal_new_channels(). It's
+ * done here instead.
+ */
+ ast_party_redirecting_copy(ast_channel_redirecting(semi2), ast_channel_redirecting(semi1));
+
+ ast_party_dialed_copy(ast_channel_dialed(semi2), ast_channel_dialed(semi1));
+
+ ast_connected_line_copy_to_caller(ast_channel_caller(semi2), ast_channel_connected(semi1));
+ ast_connected_line_copy_from_caller(ast_channel_connected(semi2), ast_channel_caller(semi1));
+
+ ast_channel_language_set(semi2, ast_channel_language(semi1));
+ ast_channel_accountcode_set(semi2, ast_channel_accountcode(semi1));
+ ast_channel_musicclass_set(semi2, ast_channel_musicclass(semi1));
+
+ ast_channel_cc_params_init(semi2, ast_channel_get_cc_config_params(semi1));
+
+ /*
+ * Make sure we inherit the AST_CAUSE_ANSWERED_ELSEWHERE if it's
+ * set on the queue/dial call request in the dialplan.
+ */
+ if (ast_channel_hangupcause(semi1) == AST_CAUSE_ANSWERED_ELSEWHERE) {
+ ast_channel_hangupcause_set(semi2, AST_CAUSE_ANSWERED_ELSEWHERE);
+ }
+
+ /*
+ * Copy the channel variables from the semi1 channel to the
+ * outgoing channel.
+ *
+ * Note that due to certain assumptions, they MUST be in the
+ * same order.
+ */
+ AST_LIST_TRAVERSE(ast_channel_varshead(semi1), varptr, entries) {
+ clone_var = ast_var_assign(varptr->name, varptr->value);
+ if (clone_var) {
+ AST_LIST_INSERT_TAIL(ast_channel_varshead(semi2), clone_var, entries);
+ }
+ }
+ ast_channel_datastore_inherit(semi1, semi2);
+}
+
+int ast_unreal_hangup(struct ast_unreal_pvt *p, struct ast_channel *ast)
+{
+ int hangup_chan = 0;
+ int res = 0;
+ int cause;
+ struct ast_channel *owner = NULL;
+ struct ast_channel *chan = NULL;
+
+ /* the pvt isn't going anywhere, it has a ref */
+ ast_channel_unlock(ast);
+
+ /* lock everything */
+ ast_unreal_lock_all(p, &chan, &owner);
+
+ if (ast != chan && ast != owner) {
+ res = -1;
+ goto unreal_hangup_cleanup;
+ }
+
+ cause = ast_channel_hangupcause(ast);
+
+ if (ast == p->chan) {
+ /* Outgoing side is hanging up. */
+ ast_clear_flag(p, AST_UNREAL_CARETAKER_THREAD);
+ p->chan = NULL;
+ if (p->owner) {
+ const char *status = pbx_builtin_getvar_helper(p->chan, "DIALSTATUS");
+
+ if (status) {
+ ast_channel_hangupcause_set(p->owner, cause);
+ pbx_builtin_setvar_helper(p->owner, "CHANLOCALSTATUS", status);
+ }
+ ast_queue_hangup_with_cause(p->owner, cause);
+ }
+ } else {
+ /* Owner side is hanging up. */
+ p->owner = NULL;
+ if (p->chan) {
+ if (cause == AST_CAUSE_ANSWERED_ELSEWHERE) {
+ ast_channel_hangupcause_set(p->chan, AST_CAUSE_ANSWERED_ELSEWHERE);
+ ast_debug(2, "%s has AST_CAUSE_ANSWERED_ELSEWHERE set.\n",
+ ast_channel_name(p->chan));
+ }
+ if (!ast_test_flag(p, AST_UNREAL_CARETAKER_THREAD)) {
+ /*
+ * Need to actually hangup p->chan since nothing else is taking
+ * care of it.
+ */
+ hangup_chan = 1;
+ } else {
+ ast_queue_hangup_with_cause(p->chan, cause);
+ }
+ }
+ }
+
+ /* this is one of our locked channels, doesn't matter which */
+ ast_channel_tech_pvt_set(ast, NULL);
+ ao2_ref(p, -1);
+
+unreal_hangup_cleanup:
+ ao2_unlock(p);
+ if (owner) {
+ ast_channel_unlock(owner);
+ ast_channel_unref(owner);
+ }
+ if (chan) {
+ ast_channel_unlock(chan);
+ if (hangup_chan) {
+ ast_hangup(chan);
+ }
+ ast_channel_unref(chan);
+ }
+
+ /* leave with the channel locked that came in */
+ ast_channel_lock(ast);
+
+ return res;
+}
+
+void ast_unreal_destructor(void *vdoomed)
+{
+ struct ast_unreal_pvt *doomed = vdoomed;
+
+ doomed->reqcap = ast_format_cap_destroy(doomed->reqcap);
+}
+
+struct ast_unreal_pvt *ast_unreal_alloc(size_t size, ao2_destructor_fn destructor, struct ast_format_cap *cap)
+{
+ struct ast_unreal_pvt *unreal;
+
+ static const struct ast_jb_conf jb_conf = {
+ .flags = 0,
+ .max_size = -1,
+ .resync_threshold = -1,
+ .impl = "",
+ .target_extra = -1,
+ };
+
+ unreal = ao2_alloc(size, destructor);
+ if (!unreal) {
+ return NULL;
+ }
+ unreal->reqcap = ast_format_cap_dup(cap);
+ if (!unreal->reqcap) {
+ ao2_ref(unreal, -1);
+ return NULL;
+ }
+
+ memcpy(&unreal->jb_conf, &jb_conf, sizeof(unreal->jb_conf));
+
+ return unreal;
+}
+
+struct ast_channel *ast_unreal_new_channels(struct ast_unreal_pvt *p,
+ const struct ast_channel_tech *tech, int semi1_state, int semi2_state,
+ const char *exten, const char *context, const struct ast_channel *requestor,
+ struct ast_callid *callid)
+{
+ struct ast_channel *owner;
+ struct ast_channel *chan;
+ const char *linkedid = requestor ? ast_channel_linkedid(requestor) : NULL;
+ struct ast_format fmt;
+ int generated_seqno = ast_atomic_fetchadd_int((int *) &name_sequence, +1);
+
+ /*
+ * Allocate two new Asterisk channels
+ *
+ * Make sure that the ;2 channel gets the same linkedid as ;1.
+ * You can't pass linkedid to both allocations since if linkedid
+ * isn't set, then each channel will generate its own linkedid.
+ */
+ if (!(owner = ast_channel_alloc(1, semi1_state, NULL, NULL, NULL,
+ exten, context, linkedid, 0,
+ "%s/%s-%08x;1", tech->type, p->name, generated_seqno))
+ || !(chan = ast_channel_alloc(1, semi2_state, NULL, NULL, NULL,
+ exten, context, ast_channel_linkedid(owner), 0,
+ "%s/%s-%08x;2", tech->type, p->name, generated_seqno))) {
+ if (owner) {
+ owner = ast_channel_release(owner);
+ }
+ ast_log(LOG_WARNING, "Unable to allocate channel structure(s)\n");
+ return NULL;
+ }
+
+ if (callid) {
+ ast_channel_callid_set(owner, callid);
+ ast_channel_callid_set(chan, callid);
+ }
+
+ ast_channel_tech_set(owner, tech);
+ ast_channel_tech_set(chan, tech);
+ ast_channel_tech_pvt_set(owner, p);
+ ast_channel_tech_pvt_set(chan, p);
+
+ ast_format_cap_copy(ast_channel_nativeformats(owner), p->reqcap);
+ ast_format_cap_copy(ast_channel_nativeformats(chan), p->reqcap);
+
+ /* Determine our read/write format and set it on each channel */
+ ast_best_codec(p->reqcap, &fmt);
+ ast_format_copy(ast_channel_writeformat(owner), &fmt);
+ ast_format_copy(ast_channel_writeformat(chan), &fmt);
+ ast_format_copy(ast_channel_rawwriteformat(owner), &fmt);
+ ast_format_copy(ast_channel_rawwriteformat(chan), &fmt);
+ ast_format_copy(ast_channel_readformat(owner), &fmt);
+ ast_format_copy(ast_channel_readformat(chan), &fmt);
+ ast_format_copy(ast_channel_rawreadformat(owner), &fmt);
+ ast_format_copy(ast_channel_rawreadformat(chan), &fmt);
+
+ ast_set_flag(ast_channel_flags(owner), AST_FLAG_DISABLE_DEVSTATE_CACHE);
+ ast_set_flag(ast_channel_flags(chan), AST_FLAG_DISABLE_DEVSTATE_CACHE);
+
+ ast_jb_configure(owner, &p->jb_conf);
+
+ if (ast_channel_cc_params_init(owner, requestor
+ ? ast_channel_get_cc_config_params((struct ast_channel *) requestor) : NULL)) {
+ ast_channel_release(owner);
+ ast_channel_release(chan);
+ return NULL;
+ }
+
+ /* Give the private a ref for each channel. */
+ ao2_ref(p, +2);
+ p->owner = owner;
+ p->chan = chan;
+
+ return owner;
+}
diff --git a/main/features.c b/main/features.c
index b6cc19164..be872a80d 100644
--- a/main/features.c
+++ b/main/features.c
@@ -72,6 +72,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/astobj2.h"
#include "asterisk/cel.h"
#include "asterisk/test.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_basic.h"
/*
* Party A - transferee
@@ -248,129 +250,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
</variablelist>
</description>
</application>
- <application name="ParkedCall" language="en_US">
- <synopsis>
- Retrieve a parked call.
- </synopsis>
- <syntax>
- <parameter name="exten">
- <para>Parking space extension to retrieve a parked call.
- If not provided then the first available parked call in the
- parking lot will be retrieved.</para>
- </parameter>
- <parameter name="parking_lot_name">
- <para>Specify from which parking lot to retrieve a parked call.</para>
- <para>The parking lot used is selected in the following order:</para>
- <para>1) parking_lot_name option</para>
- <para>2) <variable>PARKINGLOT</variable> variable</para>
- <para>3) <literal>CHANNEL(parkinglot)</literal> function
- (Possibly preset by the channel driver.)</para>
- <para>4) Default parking lot.</para>
- </parameter>
- </syntax>
- <description>
- <para>Used to retrieve a parked call from a parking lot.</para>
- <note>
- <para>Parking lots automatically create and manage dialplan extensions in
- the parking lot context. You do not need to explicitly use this
- application in your dialplan. Instead, all you should do is include the
- parking lot context in your dialplan.</para>
- </note>
- </description>
- <see-also>
- <ref type="application">Park</ref>
- <ref type="application">ParkAndAnnounce</ref>
- </see-also>
- </application>
- <application name="Park" language="en_US">
- <synopsis>
- Park yourself.
- </synopsis>
- <syntax>
- <parameter name="timeout">
- <para>A custom parking timeout for this parked call. Value in milliseconds.</para>
- </parameter>
- <parameter name="return_context">
- <para>The context to return the call to after it times out.</para>
- </parameter>
- <parameter name="return_exten">
- <para>The extension to return the call to after it times out.</para>
- </parameter>
- <parameter name="return_priority">
- <para>The priority to return the call to after it times out.</para>
- </parameter>
- <parameter name="options">
- <para>A list of options for this parked call.</para>
- <optionlist>
- <option name="r">
- <para>Send ringing instead of MOH to the parked call.</para>
- </option>
- <option name="R">
- <para>Randomize the selection of a parking space.</para>
- </option>
- <option name="s">
- <para>Silence announcement of the parking space number.</para>
- </option>
- </optionlist>
- </parameter>
- <parameter name="parking_lot_name">
- <para>Specify in which parking lot to park a call.</para>
- <para>The parking lot used is selected in the following order:</para>
- <para>1) parking_lot_name option</para>
- <para>2) <variable>PARKINGLOT</variable> variable</para>
- <para>3) <literal>CHANNEL(parkinglot)</literal> function
- (Possibly preset by the channel driver.)</para>
- <para>4) Default parking lot.</para>
- </parameter>
- </syntax>
- <description>
- <para>Used to park yourself (typically in combination with a supervised
- transfer to know the parking space).</para>
- <para>If you set the <variable>PARKINGEXTEN</variable> variable to a
- parking space extension in the parking lot, Park() will attempt to park the call
- on that extension. If the extension is already is in use then execution
- will continue at the next priority.</para>
- <para>If the <literal>parkeddynamic</literal> option is enabled in <filename>features.conf</filename>
- the following variables can be used to dynamically create new parking lots.</para>
- <para>If you set the <variable>PARKINGDYNAMIC</variable> variable and this parking lot
- exists then it will be used as a template for the newly created dynamic lot. Otherwise,
- the default parking lot will be used.</para>
- <para>If you set the <variable>PARKINGDYNCONTEXT</variable> variable then the newly created dynamic
- parking lot will use this context.</para>
- <para>If you set the <variable>PARKINGDYNEXTEN</variable> variable then the newly created dynamic
- parking lot will use this extension to access the parking lot.</para>
- <para>If you set the <variable>PARKINGDYNPOS</variable> variable then the newly created dynamic parking lot
- will use those parking postitions.</para>
- <note>
- <para>This application must be used as the first extension priority
- to be recognized as a parking access extension. DTMF transfers
- and some channel drivers need this distinction to operate properly.
- The parking access extension in this case is treated like a dialplan
- hint.</para>
- </note>
- <note>
- <para>Parking lots automatically create and manage dialplan extensions in
- the parking lot context. You do not need to explicitly use this
- application in your dialplan. Instead, all you should do is include the
- parking lot context in your dialplan.</para>
- </note>
- </description>
- <see-also>
- <ref type="application">ParkAndAnnounce</ref>
- <ref type="application">ParkedCall</ref>
- </see-also>
- </application>
- <manager name="ParkedCalls" language="en_US">
- <synopsis>
- List parked calls.
- </synopsis>
- <syntax>
- <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
- </syntax>
- <description>
- <para>List parked calls.</para>
- </description>
- </manager>
<manager name="Park" language="en_US">
<synopsis>
Park a channel.
@@ -418,17 +297,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
<para>Bridge together two channels already in the PBX.</para>
</description>
</manager>
- <manager name="Parkinglots" language="en_US">
- <synopsis>
- Get a list of parking lots
- </synopsis>
- <syntax>
- <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
- </syntax>
- <description>
- <para>List all parking lots as a series of AMI events</para>
- </description>
- </manager>
<function name="FEATURE" language="en_US">
<synopsis>
Get or set a feature option on a channel.
@@ -538,6 +406,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#define AST_MAX_WATCHERS 256
#define MAX_DIAL_FEATURE_OPTIONS 30
+/* TODO Scrape all of the parking stuff out of features.c */
+
struct feature_group_exten {
AST_LIST_ENTRY(feature_group_exten) entry;
AST_DECLARE_STRING_FIELDS(
@@ -1134,65 +1004,40 @@ static void *bridge_call_thread(void *data)
ast_channel_data_set(tobj->peer, "(Empty)");
}
- ast_bridge_call(tobj->peer, tobj->chan, &tobj->bconfig);
-
if (tobj->return_to_pbx) {
- if (!ast_check_hangup(tobj->peer)) {
- ast_log(LOG_VERBOSE, "putting peer %s into PBX again\n", ast_channel_name(tobj->peer));
- if (ast_pbx_start(tobj->peer)) {
- ast_log(LOG_WARNING, "FAILED continuing PBX on peer %s\n", ast_channel_name(tobj->peer));
- ast_autoservice_chan_hangup_peer(tobj->chan, tobj->peer);
- }
- } else {
- ast_autoservice_chan_hangup_peer(tobj->chan, tobj->peer);
- }
- if (!ast_check_hangup(tobj->chan)) {
- ast_log(LOG_VERBOSE, "putting chan %s into PBX again\n", ast_channel_name(tobj->chan));
- if (ast_pbx_start(tobj->chan)) {
- ast_log(LOG_WARNING, "FAILED continuing PBX on chan %s\n", ast_channel_name(tobj->chan));
- ast_hangup(tobj->chan);
- }
- } else {
- ast_hangup(tobj->chan);
- }
- } else {
- ast_hangup(tobj->chan);
- ast_hangup(tobj->peer);
+ ast_after_bridge_set_goto(tobj->chan, ast_channel_context(tobj->chan),
+ ast_channel_exten(tobj->chan), ast_channel_priority(tobj->chan));
+ ast_after_bridge_set_goto(tobj->peer, ast_channel_context(tobj->peer),
+ ast_channel_exten(tobj->peer), ast_channel_priority(tobj->peer));
}
+ ast_bridge_call(tobj->chan, tobj->peer, &tobj->bconfig);
+
+ ast_after_bridge_goto_run(tobj->chan);
+
ast_free(tobj);
return NULL;
}
/*!
- * \brief create thread for the parked call
- * \param data
- *
- * Create thread and attributes, call bridge_call_thread
+ * \brief create thread for the bridging call
+ * \param tobj
*/
-static void bridge_call_thread_launch(struct ast_bridge_thread_obj *data)
+static void bridge_call_thread_launch(struct ast_bridge_thread_obj *tobj)
{
pthread_t thread;
- pthread_attr_t attr;
- struct sched_param sched;
/* This needs to be unreffed once it has been associated with the new thread. */
- data->callid = ast_read_threadstorage_callid();
-
- pthread_attr_init(&attr);
- pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
- if (ast_pthread_create(&thread, &attr, bridge_call_thread, data)) {
- /* Failed to create thread. Ditch the reference to callid. */
- ast_callid_unref(data->callid);
- ast_hangup(data->chan);
- ast_hangup(data->peer);
+ tobj->callid = ast_read_threadstorage_callid();
+
+ if (ast_pthread_create_detached(&thread, NULL, bridge_call_thread, tobj)) {
ast_log(LOG_ERROR, "Failed to create bridge_call_thread.\n");
- return;
+ ast_callid_unref(tobj->callid);
+ ast_hangup(tobj->chan);
+ ast_hangup(tobj->peer);
+ ast_free(tobj);
}
- pthread_attr_destroy(&attr);
- memset(&sched, 0, sizeof(sched));
- pthread_setschedparam(thread, SCHED_RR, &sched);
}
/*!
@@ -2583,11 +2428,6 @@ static int builtin_blindtransfer(struct ast_channel *chan, struct ast_channel *p
if (ast_async_goto(transferee, transferer_real_context, xferto, 1)) {
ast_log(LOG_WARNING, "Async goto failed :-(\n");
res = -1;
- } else if (res == AST_FEATURE_RETURN_SUCCESSBREAK) {
- /* Don't let the after-bridge code run the h-exten */
- ast_channel_lock(transferee);
- ast_set_flag(ast_channel_flags(transferee), AST_FLAG_BRIDGE_HANGUP_DONT);
- ast_channel_unlock(transferee);
}
check_goto_on_transfer(transferer);
return res;
@@ -2774,8 +2614,6 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
ast_debug(2, "Dial party C result: newchan:%d, outstate:%d\n", !!newchan, outstate);
if (!ast_check_hangup(transferer)) {
- int hangup_dont = 0;
-
/* Transferer (party B) is up */
ast_debug(1, "Actually doing an attended transfer.\n");
@@ -2815,31 +2653,11 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
ast_set_flag(&(bconfig.features_callee), AST_FEATURE_DISCONNECT);
/*
- * ast_bridge_call clears AST_FLAG_BRIDGE_HANGUP_DONT, but we
- * don't want that to happen here because the transferer is in
- * another bridge already.
- */
- if (ast_test_flag(ast_channel_flags(transferer), AST_FLAG_BRIDGE_HANGUP_DONT)) {
- hangup_dont = 1;
- }
-
- /*
- * Don't let the after-bridge code run the h-exten. It is the
- * wrong bridge to run the h-exten after.
- */
- ast_set_flag(ast_channel_flags(transferer), AST_FLAG_BRIDGE_HANGUP_DONT);
-
- /*
* Let party B and C talk as long as they want while party A
* languishes in autoservice listening to MOH.
*/
ast_bridge_call(transferer, newchan, &bconfig);
- if (hangup_dont) {
- /* Restore the AST_FLAG_BRIDGE_HANGUP_DONT flag */
- ast_set_flag(ast_channel_flags(transferer), AST_FLAG_BRIDGE_HANGUP_DONT);
- }
-
if (ast_check_hangup(newchan) || !ast_check_hangup(transferer)) {
ast_autoservice_chan_hangup_peer(transferer, newchan);
if (ast_stream_and_wait(transferer, xfersound, "")) {
@@ -3731,6 +3549,7 @@ static int feature_interpret_helper(struct ast_channel *chan, struct ast_channel
return res;
}
+#if 0//BUGBUG
/*!
* \brief Check the dynamic features
* \param chan,peer,config,code,sense
@@ -3777,12 +3596,14 @@ static int feature_interpret(struct ast_channel *chan, struct ast_channel *peer,
return res;
}
+#endif
int ast_feature_detect(struct ast_channel *chan, struct ast_flags *features, const char *code, struct ast_call_feature *feature) {
return feature_interpret_helper(chan, NULL, NULL, code, 0, NULL, features, FEATURE_INTERPRET_DETECT, feature);
}
+#if 0//BUGBUG
/*! \brief Check if a feature exists */
static int feature_check(struct ast_channel *chan, struct ast_flags *features, char *code) {
struct ast_str *chan_dynamic_features;
@@ -3801,11 +3622,15 @@ static int feature_check(struct ast_channel *chan, struct ast_flags *features, c
return res;
}
+#endif
static void set_config_flags(struct ast_channel *chan, struct ast_bridge_config *config)
{
int x;
+/* BUGBUG there is code that checks AST_BRIDGE_IGNORE_SIGS but no code to set it. */
+/* BUGBUG there is code that checks AST_BRIDGE_REC_CHANNEL_0 but no code to set it. */
+/* BUGBUG there is code that checks AST_BRIDGE_REC_CHANNEL_1 but no code to set it. */
ast_clear_flag(config, AST_FLAGS_ALL);
ast_rdlock_call_features();
@@ -4208,37 +4033,22 @@ void ast_channel_log(char *title, struct ast_channel *chan) /* for debug, this i
{
ast_log(LOG_NOTICE, "______ %s (%lx)______\n", title, (unsigned long) chan);
ast_log(LOG_NOTICE, "CHAN: name: %s; appl: %s; data: %s; contxt: %s; exten: %s; pri: %d;\n",
- ast_channel_name(chan), ast_channel_appl(chan), ast_channel_data(chan), ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan));
+ ast_channel_name(chan), ast_channel_appl(chan), ast_channel_data(chan),
+ ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan));
ast_log(LOG_NOTICE, "CHAN: acctcode: %s; dialcontext: %s; amaflags: %x; maccontxt: %s; macexten: %s; macpri: %d;\n",
- ast_channel_accountcode(chan), ast_channel_dialcontext(chan), ast_channel_amaflags(chan), ast_channel_macrocontext(chan), ast_channel_macroexten(chan), ast_channel_macropriority(chan));
- ast_log(LOG_NOTICE, "CHAN: masq: %p; masqr: %p; _bridge: %p; uniqueID: %s; linkedID:%s\n",
+ ast_channel_accountcode(chan), ast_channel_dialcontext(chan), ast_channel_amaflags(chan),
+ ast_channel_macrocontext(chan), ast_channel_macroexten(chan), ast_channel_macropriority(chan));
+ ast_log(LOG_NOTICE, "CHAN: masq: %p; masqr: %p; uniqueID: %s; linkedID:%s\n",
ast_channel_masq(chan), ast_channel_masqr(chan),
- ast_channel_internal_bridged_channel(chan), ast_channel_uniqueid(chan), ast_channel_linkedid(chan));
+ ast_channel_uniqueid(chan), ast_channel_linkedid(chan));
if (ast_channel_masqr(chan)) {
ast_log(LOG_NOTICE, "CHAN: masquerading as: %s; cdr: %p;\n",
ast_channel_name(ast_channel_masqr(chan)), ast_channel_cdr(ast_channel_masqr(chan)));
}
- if (ast_channel_internal_bridged_channel(chan)) {
- ast_log(LOG_NOTICE, "CHAN: Bridged to %s\n", ast_channel_name(ast_channel_internal_bridged_channel(chan)));
- }
ast_log(LOG_NOTICE, "===== done ====\n");
}
-/*!
- * \brief return the first unlocked cdr in a possible chain
- */
-static struct ast_cdr *pick_unlocked_cdr(struct ast_cdr *cdr)
-{
- struct ast_cdr *cdr_orig = cdr;
- while (cdr) {
- if (!ast_test_flag(cdr,AST_CDR_FLAG_LOCKED))
- return cdr;
- cdr = cdr->next;
- }
- return cdr_orig; /* everybody LOCKED or some other weirdness, like a NULL */
-}
-
static void set_bridge_features_on_config(struct ast_bridge_config *config, const char *features)
{
const char *feature;
@@ -4249,17 +4059,14 @@ static void set_bridge_features_on_config(struct ast_bridge_config *config, cons
for (feature = features; *feature; feature++) {
struct ast_flags *party;
- char this_feature;
if (isupper(*feature)) {
- party = &(config->features_caller);
+ party = &config->features_caller;
} else {
- party = &(config->features_callee);
+ party = &config->features_callee;
}
- this_feature = tolower(*feature);
-
- switch (this_feature) {
+ switch (tolower(*feature)) {
case 't' :
ast_set_flag(party, AST_FEATURE_REDIRECT);
break;
@@ -4277,6 +4084,7 @@ static void set_bridge_features_on_config(struct ast_bridge_config *config, cons
break;
default :
ast_log(LOG_WARNING, "Skipping unknown feature code '%c'\n", *feature);
+ break;
}
}
}
@@ -4334,6 +4142,279 @@ void ast_bridge_end_dtmf(struct ast_channel *chan, char digit, struct timeval st
/*!
* \internal
+ * \brief Setup bridge builtin features.
+ * \since 12.0.0
+ *
+ * \param features Bridge features to setup.
+ * \param chan Get features from this channel.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int setup_bridge_features_builtin(struct ast_bridge_features *features, struct ast_channel *chan)
+{
+ struct ast_flags *flags;
+ char dtmf[FEATURE_MAX_LEN];
+ int res;
+
+ ast_channel_lock(chan);
+ flags = ast_bridge_features_ds_get(chan);
+ ast_channel_unlock(chan);
+ if (!flags) {
+ return 0;
+ }
+
+ res = 0;
+ ast_rdlock_call_features();
+ if (ast_test_flag(flags, AST_FEATURE_REDIRECT)) {
+ /* Add atxfer and blind transfer. */
+ builtin_feature_get_exten(chan, "blindxfer", dtmf, sizeof(dtmf));
+ if (!ast_strlen_zero(dtmf)) {
+/* BUGBUG need to supply a blind transfer structure and destructor to use other than defaults */
+ res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_BLINDTRANSFER, dtmf, NULL, NULL, 1);
+ }
+ builtin_feature_get_exten(chan, "atxfer", dtmf, sizeof(dtmf));
+ if (!ast_strlen_zero(dtmf)) {
+/* BUGBUG need to supply an attended transfer structure and destructor to use other than defaults */
+ res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_ATTENDEDTRANSFER, dtmf, NULL, NULL, 1);
+ }
+ }
+ if (ast_test_flag(flags, AST_FEATURE_DISCONNECT)) {
+ builtin_feature_get_exten(chan, "disconnect", dtmf, sizeof(dtmf));
+ if (ast_strlen_zero(dtmf)) {
+ res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_HANGUP, dtmf, NULL, NULL, 1);
+ }
+ }
+ if (ast_test_flag(flags, AST_FEATURE_PARKCALL)) {
+ builtin_feature_get_exten(chan, "parkcall", dtmf, sizeof(dtmf));
+ if (!ast_strlen_zero(dtmf)) {
+ res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_PARKCALL, dtmf, NULL, NULL, 1);
+ }
+ }
+ if (ast_test_flag(flags, AST_FEATURE_AUTOMON)) {
+ builtin_feature_get_exten(chan, "automon", dtmf, sizeof(dtmf));
+ if (!ast_strlen_zero(dtmf)) {
+ res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_AUTOMON, dtmf, NULL, NULL, 1);
+ }
+ }
+ if (ast_test_flag(flags, AST_FEATURE_AUTOMIXMON)) {
+ builtin_feature_get_exten(chan, "automixmon", dtmf, sizeof(dtmf));
+ if (!ast_strlen_zero(dtmf)) {
+ res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_AUTOMIXMON, dtmf, NULL, NULL, 1);
+ }
+ }
+ ast_unlock_call_features();
+
+#if 0 /* BUGBUG don't report errors untill all of the builtin features are supported. */
+ return res ? -1 : 0;
+#else
+ return 0;
+#endif
+}
+
+struct dtmf_hook_run_app {
+ /*! Which side of bridge to run app (AST_FEATURE_FLAG_ONSELF/AST_FEATURE_FLAG_ONPEER) */
+ unsigned int flags;
+ /*! Offset into app_name[] where the MOH class name starts. (zero if no MOH) */
+ int moh_offset;
+ /*! Offset into app_name[] where the application argument string starts. (zero if no arguments) */
+ int app_args_offset;
+ /*! Application name to run. */
+ char app_name[0];
+};
+
+/*!
+ * \internal
+ * \brief Setup bridge dynamic features.
+ * \since 12.0.0
+ *
+ * \param bridge The bridge that the channel is part of
+ * \param bridge_channel Channel executing the feature
+ * \param hook_pvt Private data passed in when the hook was created
+ *
+ * \retval 0 Keep the callback hook.
+ * \retval -1 Remove the callback hook.
+ */
+static int app_dtmf_feature_hook(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+ struct dtmf_hook_run_app *pvt = hook_pvt;
+ void (*run_it)(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class);
+
+ if (ast_test_flag(pvt, AST_FEATURE_FLAG_ONPEER)) {
+ run_it = ast_bridge_channel_write_app;
+ } else {
+ run_it = ast_bridge_channel_run_app;
+ }
+
+/*
+ * BUGBUG need to pass to run_it the triggering channel name so DYNAMIC_WHO_TRIGGERED can be set on the channel when it is run.
+ *
+ * This would replace DYNAMIC_PEERNAME which is redundant with
+ * BRIDGEPEER anyway. The value of DYNAMIC_WHO_TRIGGERED is
+ * really useful in the case of a multi-party bridge.
+ */
+ run_it(bridge_channel, pvt->app_name,
+ pvt->app_args_offset ? &pvt->app_name[pvt->app_args_offset] : NULL,
+ pvt->moh_offset ? &pvt->app_name[pvt->moh_offset] : NULL);
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Add a dynamic DTMF feature hook to the bridge features.
+ * \since 12.0.0
+ *
+ * \param features Bridge features to setup.
+ * \param flags Which side of bridge to run app (AST_FEATURE_FLAG_ONSELF/AST_FEATURE_FLAG_ONPEER).
+ * \param dtmf DTMF trigger sequence.
+ * \param app_name Dialplan application name to run.
+ * \param app_args Dialplan application arguments. (Empty or NULL if no arguments)
+ * \param moh_class MOH class to play to peer. (Empty or NULL if no MOH played)
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int add_dynamic_dtmf_hook(struct ast_bridge_features *features, unsigned int flags, const char *dtmf, const char *app_name, const char *app_args, const char *moh_class)
+{
+ struct dtmf_hook_run_app *app_data;
+ size_t len_name = strlen(app_name) + 1;
+ size_t len_args = ast_strlen_zero(app_args) ? 0 : strlen(app_args) + 1;
+ size_t len_moh = ast_strlen_zero(moh_class) ? 0 : strlen(moh_class) + 1;
+ size_t len_data = sizeof(*app_data) + len_name + len_args + len_moh;
+
+ /* Fill in application run hook data. */
+ app_data = ast_malloc(len_data);
+ if (!app_data) {
+ return -1;
+ }
+ app_data->flags = flags;
+ app_data->app_args_offset = len_args ? len_name : 0;
+ app_data->moh_offset = len_moh ? len_name + len_args : 0;
+ strcpy(app_data->app_name, app_name);/* Safe */
+ if (len_args) {
+ strcpy(&app_data->app_name[app_data->app_args_offset], app_args);/* Safe */
+ }
+ if (len_moh) {
+ strcpy(&app_data->app_name[app_data->moh_offset], moh_class);/* Safe */
+ }
+
+ return ast_bridge_dtmf_hook(features, dtmf, app_dtmf_feature_hook,
+ app_data, ast_free_ptr, 1);
+}
+
+/*!
+ * \internal
+ * \brief Setup bridge dynamic features.
+ * \since 12.0.0
+ *
+ * \param features Bridge features to setup.
+ * \param chan Get features from this channel.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int setup_bridge_features_dynamic(struct ast_bridge_features *features, struct ast_channel *chan)
+{
+ const char *feat;
+ char *dynamic_features = NULL;
+ char *tok;
+ int res;
+
+ ast_channel_lock(chan);
+ feat = pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES");
+ if (!ast_strlen_zero(feat)) {
+ dynamic_features = ast_strdupa(feat);
+ }
+ ast_channel_unlock(chan);
+ if (!dynamic_features) {
+ return 0;
+ }
+
+/* BUGBUG need to pass to add_dynamic_dtmf_hook the applicationmap name (feature->sname) so the DYNAMIC_FEATURENAME can be set on the channel when it is run. */
+ res = 0;
+ while ((tok = strsep(&dynamic_features, "#"))) {
+ struct feature_group *fg;
+ struct ast_call_feature *feature;
+
+ AST_RWLIST_RDLOCK(&feature_groups);
+ fg = find_group(tok);
+ if (fg) {
+ struct feature_group_exten *fge;
+
+ AST_LIST_TRAVERSE(&fg->features, fge, entry) {
+ res |= add_dynamic_dtmf_hook(features, fge->feature->flags, fge->exten,
+ fge->feature->app, fge->feature->app_args, fge->feature->moh_class);
+ }
+ }
+ AST_RWLIST_UNLOCK(&feature_groups);
+
+ ast_rdlock_call_features();
+ feature = find_dynamic_feature(tok);
+ if (feature) {
+ res |= add_dynamic_dtmf_hook(features, feature->flags, feature->exten,
+ feature->app, feature->app_args, feature->moh_class);
+ }
+ ast_unlock_call_features();
+ }
+ return res;
+}
+
+/* BUGBUG struct ast_call_feature needs to be made an ao2 object so the basic bridge class can own the code setting up it's DTMF hooks. */
+/* BUGBUG this really should be made a private function of bridging_basic.c after struct ast_call_feature is made an ao2 object. */
+int ast_bridge_channel_setup_features(struct ast_bridge_channel *bridge_channel)
+{
+ int res = 0;
+
+ /* Always pass through any DTMF digits. */
+ bridge_channel->features->dtmf_passthrough = 1;
+
+ res |= setup_bridge_features_builtin(bridge_channel->features, bridge_channel->chan);
+ res |= setup_bridge_features_dynamic(bridge_channel->features, bridge_channel->chan);
+
+ return res;
+}
+
+static void bridge_config_set_limits_warning_values(struct ast_bridge_config *config, struct ast_bridge_features_limits *limits)
+{
+ if (config->end_sound) {
+ ast_string_field_set(limits, duration_sound, config->end_sound);
+ }
+
+ if (config->warning_sound) {
+ ast_string_field_set(limits, warning_sound, config->warning_sound);
+ }
+
+ if (config->start_sound) {
+ ast_string_field_set(limits, connect_sound, config->start_sound);
+ }
+
+ limits->frequency = config->warning_freq;
+ limits->warning = config->play_warning;
+}
+
+/*!
+ * \internal brief Setup limit hook structures on calls that need limits
+ *
+ * \param config ast_bridge_config which provides the limit data
+ * \param caller_limits pointer to an ast_bridge_features_limits struct which will store the caller side limits
+ * \param callee_limits pointer to an ast_bridge_features_limits struct which will store the callee side limits
+ */
+static void bridge_config_set_limits(struct ast_bridge_config *config, struct ast_bridge_features_limits *caller_limits, struct ast_bridge_features_limits *callee_limits)
+{
+ if (ast_test_flag(&config->features_caller, AST_FEATURE_PLAY_WARNING)) {
+ bridge_config_set_limits_warning_values(config, caller_limits);
+ }
+
+ if (ast_test_flag(&config->features_callee, AST_FEATURE_PLAY_WARNING)) {
+ bridge_config_set_limits_warning_values(config, callee_limits);
+ }
+
+ caller_limits->duration = config->timelimit;
+ callee_limits->duration = config->timelimit;
+}
+
+/*!
+ * \internal
* \brief Check if Monitor needs to be started on a channel.
* \since 12.0.0
*
@@ -4375,6 +4456,24 @@ static void bridge_check_monitor(struct ast_channel *chan, struct ast_channel *p
}
/*!
+ * \internal
+ * \brief Send the peer channel on its way on bridge start failure.
+ * \since 12.0.0
+ *
+ * \param chan Chan to put into autoservice.
+ * \param peer Chan to send to after bridge goto or run hangup handlers and hangup.
+ *
+ * \return Nothing
+ */
+static void bridge_failed_peer_goto(struct ast_channel *chan, struct ast_channel *peer)
+{
+ if (ast_after_bridge_goto_setup(peer)
+ || ast_pbx_start(peer)) {
+ ast_autoservice_chan_hangup_peer(chan, peer);
+ }
+}
+
+/*!
* \brief bridge the call and set CDR
*
* \param chan The bridge considers this channel the caller.
@@ -4388,33 +4487,16 @@ static void bridge_check_monitor(struct ast_channel *chan, struct ast_channel *p
*/
int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config)
{
- /* Copy voice back and forth between the two channels. Give the peer
- the ability to transfer calls with '#<extension' syntax. */
- struct ast_frame *f;
- struct ast_channel *who;
- char chan_featurecode[FEATURE_MAX_LEN + 1]="";
- char peer_featurecode[FEATURE_MAX_LEN + 1]="";
- char orig_channame[AST_CHANNEL_NAME];
- char orig_peername[AST_CHANNEL_NAME];
int res;
- int diff;
- int hasfeatures=0;
- int hadfeatures=0;
- int sendingdtmfdigit = 0;
- int we_disabled_peer_cdr = 0;
- struct ast_option_header *aoh;
- struct ast_cdr *bridge_cdr = NULL;
- struct ast_cdr *chan_cdr = ast_channel_cdr(chan); /* the proper chan cdr, if there are forked cdrs */
- struct ast_cdr *peer_cdr = ast_channel_cdr(peer); /* the proper chan cdr, if there are forked cdrs */
- struct ast_cdr *new_chan_cdr = NULL; /* the proper chan cdr, if there are forked cdrs */
- struct ast_cdr *new_peer_cdr = NULL; /* the proper chan cdr, if there are forked cdrs */
- struct ast_silence_generator *silgen = NULL;
- /*! TRUE if h-exten or hangup handlers run. */
- int hangup_run = 0;
+ struct ast_bridge *bridge;
+ struct ast_bridge_features chan_features;
+ struct ast_bridge_features *peer_features;
+/* BUGBUG these channel vars may need to be made dynamic so they update when transfers happen. */
pbx_builtin_setvar_helper(chan, "BRIDGEPEER", ast_channel_name(peer));
pbx_builtin_setvar_helper(peer, "BRIDGEPEER", ast_channel_name(chan));
+/* BUGBUG revisit how BLINDTRANSFER operates with the new bridging model. */
/* Clear any BLINDTRANSFER since the transfer has completed. */
pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", NULL);
pbx_builtin_setvar_helper(peer, "BLINDTRANSFER", NULL);
@@ -4422,10 +4504,15 @@ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct a
set_bridge_features_on_config(config, pbx_builtin_getvar_helper(chan, "BRIDGE_FEATURES"));
add_features_datastores(chan, peer, config);
- /* This is an interesting case. One example is if a ringing channel gets redirected to
- * an extension that picks up a parked call. This will make sure that the call taken
- * out of parking gets told that the channel it just got bridged to is still ringing. */
- if (ast_channel_state(chan) == AST_STATE_RINGING && ast_channel_visible_indication(peer) != AST_CONTROL_RINGING) {
+ /*
+ * This is an interesting case. One example is if a ringing
+ * channel gets redirected to an extension that picks up a
+ * parked call. This will make sure that the call taken out of
+ * parking gets told that the channel it just got bridged to is
+ * still ringing.
+ */
+ if (ast_channel_state(chan) == AST_STATE_RINGING
+ && ast_channel_visible_indication(peer) != AST_CONTROL_RINGING) {
ast_indicate(peer, AST_CONTROL_RINGING);
}
@@ -4436,6 +4523,7 @@ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct a
/* Answer if need be */
if (ast_channel_state(chan) != AST_STATE_UP) {
if (ast_raw_answer(chan, 1)) {
+ bridge_failed_peer_goto(chan, peer);
return -1;
}
}
@@ -4446,571 +4534,123 @@ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct a
ast_channel_log("Pre-bridge PEER Channel info", peer);
#endif
/* two channels are being marked as linked here */
- ast_channel_set_linkgroup(chan,peer);
+ ast_channel_set_linkgroup(chan, peer);
- /* copy the userfield from the B-leg to A-leg if applicable */
- if (ast_channel_cdr(chan) && ast_channel_cdr(peer) && !ast_strlen_zero(ast_channel_cdr(peer)->userfield)) {
- char tmp[256];
+ /*
+ * If we are bridging a call, stop worrying about forwarding
+ * loops. We presume that if a call is being bridged, that the
+ * humans in charge know what they're doing. If they don't,
+ * well, what can we do about that?
+ */
+ clear_dialed_interfaces(chan);
+ clear_dialed_interfaces(peer);
- ast_channel_lock(chan);
- if (!ast_strlen_zero(ast_channel_cdr(chan)->userfield)) {
- snprintf(tmp, sizeof(tmp), "%s;%s", ast_channel_cdr(chan)->userfield, ast_channel_cdr(peer)->userfield);
- ast_cdr_appenduserfield(chan, tmp);
- } else {
- ast_cdr_setuserfield(chan, ast_channel_cdr(peer)->userfield);
- }
- ast_channel_unlock(chan);
- /* Don't delete the CDR; just disable it. */
- ast_set_flag(ast_channel_cdr(peer), AST_CDR_FLAG_POST_DISABLED);
- we_disabled_peer_cdr = 1;
- }
- ast_copy_string(orig_channame,ast_channel_name(chan),sizeof(orig_channame));
- ast_copy_string(orig_peername,ast_channel_name(peer),sizeof(orig_peername));
-
- if (!chan_cdr || (chan_cdr && !ast_test_flag(chan_cdr, AST_CDR_FLAG_POST_DISABLED))) {
- ast_channel_lock_both(chan, peer);
- if (chan_cdr) {
- ast_set_flag(chan_cdr, AST_CDR_FLAG_MAIN);
- ast_cdr_update(chan);
- bridge_cdr = ast_cdr_dup_unique_swap(chan_cdr);
- /* rip any forked CDR's off of the chan_cdr and attach
- * them to the bridge_cdr instead */
- bridge_cdr->next = chan_cdr->next;
- chan_cdr->next = NULL;
- ast_copy_string(bridge_cdr->lastapp, S_OR(ast_channel_appl(chan), ""), sizeof(bridge_cdr->lastapp));
- ast_copy_string(bridge_cdr->lastdata, S_OR(ast_channel_data(chan), ""), sizeof(bridge_cdr->lastdata));
- if (peer_cdr && !ast_strlen_zero(peer_cdr->userfield)) {
- ast_copy_string(bridge_cdr->userfield, peer_cdr->userfield, sizeof(bridge_cdr->userfield));
- }
- ast_cdr_setaccount(peer, ast_channel_accountcode(chan));
- } else {
- /* better yet, in a xfer situation, find out why the chan cdr got zapped (pun unintentional) */
- bridge_cdr = ast_cdr_alloc(); /* this should be really, really rare/impossible? */
- ast_copy_string(bridge_cdr->channel, ast_channel_name(chan), sizeof(bridge_cdr->channel));
- ast_copy_string(bridge_cdr->dstchannel, ast_channel_name(peer), sizeof(bridge_cdr->dstchannel));
- ast_copy_string(bridge_cdr->uniqueid, ast_channel_uniqueid(chan), sizeof(bridge_cdr->uniqueid));
- ast_copy_string(bridge_cdr->lastapp, S_OR(ast_channel_appl(chan), ""), sizeof(bridge_cdr->lastapp));
- ast_copy_string(bridge_cdr->lastdata, S_OR(ast_channel_data(chan), ""), sizeof(bridge_cdr->lastdata));
- ast_cdr_setcid(bridge_cdr, chan);
- bridge_cdr->disposition = (ast_channel_state(chan) == AST_STATE_UP) ? AST_CDR_ANSWERED : AST_CDR_NULL;
- bridge_cdr->amaflags = ast_channel_amaflags(chan) ? ast_channel_amaflags(chan) : ast_default_amaflags;
- ast_copy_string(bridge_cdr->accountcode, ast_channel_accountcode(chan), sizeof(bridge_cdr->accountcode));
- /* Destination information */
- ast_copy_string(bridge_cdr->dst, ast_channel_exten(chan), sizeof(bridge_cdr->dst));
- ast_copy_string(bridge_cdr->dcontext, ast_channel_context(chan), sizeof(bridge_cdr->dcontext));
- if (peer_cdr) {
- bridge_cdr->start = peer_cdr->start;
- ast_copy_string(bridge_cdr->userfield, peer_cdr->userfield, sizeof(bridge_cdr->userfield));
- } else {
- ast_cdr_start(bridge_cdr);
- }
- }
- ast_channel_unlock(chan);
- ast_channel_unlock(peer);
+ res = 0;
+ ast_channel_lock(chan);
+ res |= ast_bridge_features_ds_set(chan, &config->features_caller);
+ ast_channel_unlock(chan);
+ ast_channel_lock(peer);
+ res |= ast_bridge_features_ds_set(peer, &config->features_callee);
+ ast_channel_unlock(peer);
+ if (res) {
+ bridge_failed_peer_goto(chan, peer);
+ return -1;
+ }
- ast_debug(4, "bridge answer set, chan answer set\n");
- /* peer_cdr->answer will be set when a macro runs on the peer;
- in that case, the bridge answer will be delayed while the
- macro plays on the peer channel. The peer answered the call
- before the macro started playing. To the phone system,
- this is billable time for the call, even tho the caller
- hears nothing but ringing while the macro does its thing. */
-
- /* Another case where the peer cdr's time will be set, is when
- A self-parks by pickup up phone and dialing 700, then B
- picks up A by dialing its parking slot; there may be more
- practical paths that get the same result, tho... in which
- case you get the previous answer time from the Park... which
- is before the bridge's start time, so I added in the
- tvcmp check to the if below */
-
- if (peer_cdr && !ast_tvzero(peer_cdr->answer) && ast_tvcmp(peer_cdr->answer, bridge_cdr->start) >= 0) {
- ast_cdr_setanswer(bridge_cdr, peer_cdr->answer);
- ast_cdr_setdisposition(bridge_cdr, peer_cdr->disposition);
- if (chan_cdr) {
- ast_cdr_setanswer(chan_cdr, peer_cdr->answer);
- ast_cdr_setdisposition(chan_cdr, peer_cdr->disposition);
- }
- } else {
- ast_cdr_answer(bridge_cdr);
- if (chan_cdr) {
- ast_cdr_answer(chan_cdr); /* for the sake of cli status checks */
- }
- }
- if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT) && (chan_cdr || peer_cdr)) {
- if (chan_cdr) {
- ast_set_flag(chan_cdr, AST_CDR_FLAG_BRIDGED);
- }
- if (peer_cdr) {
- ast_set_flag(peer_cdr, AST_CDR_FLAG_BRIDGED);
- }
- }
- /* the DIALED flag may be set if a dialed channel is transfered
- * and then bridged to another channel. In order for the
- * bridge CDR to be written, the DIALED flag must not be
- * present. */
- ast_clear_flag(bridge_cdr, AST_CDR_FLAG_DIALED);
+ /* Setup features. */
+ res = ast_bridge_features_init(&chan_features);
+ peer_features = ast_bridge_features_new();
+ if (res || !peer_features) {
+ ast_bridge_features_destroy(peer_features);
+ ast_bridge_features_cleanup(&chan_features);
+ bridge_failed_peer_goto(chan, peer);
+ return -1;
}
- ast_cel_report_event(chan, AST_CEL_BRIDGE_START, NULL, NULL, peer);
- /* If we are bridging a call, stop worrying about forwarding loops. We presume that if
- * a call is being bridged, that the humans in charge know what they're doing. If they
- * don't, well, what can we do about that? */
- clear_dialed_interfaces(chan);
- clear_dialed_interfaces(peer);
+ if (config->timelimit) {
+ struct ast_bridge_features_limits call_duration_limits_chan;
+ struct ast_bridge_features_limits call_duration_limits_peer;
+ int abandon_call = 0; /* TRUE if set limits fails so we can abandon the call. */
- for (;;) {
- struct ast_channel *other; /* used later */
+ if (ast_bridge_features_limits_construct(&call_duration_limits_chan)) {
+ ast_log(LOG_ERROR, "Could not construct caller duration limits. Bridge canceled.\n");
- res = ast_channel_bridge(chan, peer, config, &f, &who);
+ ast_bridge_features_destroy(peer_features);
+ ast_bridge_features_cleanup(&chan_features);
+ bridge_failed_peer_goto(chan, peer);
+ return -1;
+ }
- if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE)
- || ast_test_flag(ast_channel_flags(peer), AST_FLAG_ZOMBIE)) {
- /* Zombies are present time to leave! */
- res = -1;
- if (f) {
- ast_frfree(f);
- }
- goto before_you_go;
- }
-
- /* When frame is not set, we are probably involved in a situation
- where we've timed out.
- When frame is set, we'll come this code twice; once for DTMF_BEGIN
- and also for DTMF_END. If we flow into the following 'if' for both, then
- our wait times are cut in half, as both will subtract from the
- feature_timer. Not good!
- */
- if (config->feature_timer && (!f || f->frametype == AST_FRAME_DTMF_END)) {
- /* Update feature timer for next pass */
- diff = ast_tvdiff_ms(ast_tvnow(), config->feature_start_time);
- if (res == AST_BRIDGE_RETRY) {
- /* The feature fully timed out but has not been updated. Skip
- * the potential round error from the diff calculation and
- * explicitly set to expired. */
- config->feature_timer = -1;
- } else {
- config->feature_timer -= diff;
- }
+ if (ast_bridge_features_limits_construct(&call_duration_limits_peer)) {
+ ast_log(LOG_ERROR, "Could not construct callee duration limits. Bridge canceled.\n");
+ ast_bridge_features_limits_destroy(&call_duration_limits_chan);
- if (hasfeatures) {
- if (config->feature_timer <= 0) {
- /* Not *really* out of time, just out of time for
- digits to come in for features. */
- ast_debug(1, "Timed out for feature!\n");
- if (!ast_strlen_zero(peer_featurecode)) {
- ast_dtmf_stream(chan, peer, peer_featurecode, 0, f ? f->len : 0);
- memset(peer_featurecode, 0, sizeof(peer_featurecode));
- }
- if (!ast_strlen_zero(chan_featurecode)) {
- ast_dtmf_stream(peer, chan, chan_featurecode, 0, f ? f->len : 0);
- memset(chan_featurecode, 0, sizeof(chan_featurecode));
- }
- if (f)
- ast_frfree(f);
- hasfeatures = !ast_strlen_zero(chan_featurecode) || !ast_strlen_zero(peer_featurecode);
- if (!hasfeatures) {
- /* No more digits expected - reset the timer */
- config->feature_timer = 0;
- }
- hadfeatures = hasfeatures;
- /* Continue as we were */
- continue;
- } else if (!f) {
- /* The bridge returned without a frame and there is a feature in progress.
- * However, we don't think the feature has quite yet timed out, so just
- * go back into the bridge. */
- continue;
- }
- } else {
- if (config->feature_timer <=0) {
- /* We ran out of time */
- config->feature_timer = 0;
- who = chan;
- if (f)
- ast_frfree(f);
- f = NULL;
- res = 0;
- }
- }
- }
- if (res < 0) {
- if (!ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE) && !ast_test_flag(ast_channel_flags(peer), AST_FLAG_ZOMBIE) && !ast_check_hangup(chan) && !ast_check_hangup(peer)) {
- ast_log(LOG_WARNING, "Bridge failed on channels %s and %s\n", ast_channel_name(chan), ast_channel_name(peer));
- }
- goto before_you_go;
+ ast_bridge_features_destroy(peer_features);
+ ast_bridge_features_cleanup(&chan_features);
+ bridge_failed_peer_goto(chan, peer);
+ return -1;
}
- if (!f || (f->frametype == AST_FRAME_CONTROL &&
- (f->subclass.integer == AST_CONTROL_HANGUP || f->subclass.integer == AST_CONTROL_BUSY ||
- f->subclass.integer == AST_CONTROL_CONGESTION))) {
- res = -1;
- break;
- }
- /* many things should be sent to the 'other' channel */
- other = (who == chan) ? peer : chan;
- if (f->frametype == AST_FRAME_CONTROL) {
- switch (f->subclass.integer) {
- case AST_CONTROL_RINGING:
- case AST_CONTROL_FLASH:
- case AST_CONTROL_MCID:
- case -1:
- ast_indicate(other, f->subclass.integer);
- break;
- case AST_CONTROL_CONNECTED_LINE:
- if (ast_channel_connected_line_sub(who, other, f, 1) &&
- ast_channel_connected_line_macro(who, other, f, who != chan, 1)) {
- ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen);
- }
- break;
- case AST_CONTROL_REDIRECTING:
- if (ast_channel_redirecting_sub(who, other, f, 1) &&
- ast_channel_redirecting_macro(who, other, f, who != chan, 1)) {
- ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen);
- }
- break;
- case AST_CONTROL_PVT_CAUSE_CODE:
- case AST_CONTROL_AOC:
- case AST_CONTROL_HOLD:
- case AST_CONTROL_UNHOLD:
- ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen);
- break;
- case AST_CONTROL_OPTION:
- aoh = f->data.ptr;
- /* Forward option Requests, but only ones we know are safe
- * These are ONLY sent by chan_iax2 and I'm not convinced that
- * they are useful. I haven't deleted them entirely because I
- * just am not sure of the ramifications of removing them. */
- if (aoh && aoh->flag == AST_OPTION_FLAG_REQUEST) {
- switch (ntohs(aoh->option)) {
- case AST_OPTION_TONE_VERIFY:
- case AST_OPTION_TDD:
- case AST_OPTION_RELAXDTMF:
- case AST_OPTION_AUDIO_MODE:
- case AST_OPTION_DIGIT_DETECT:
- case AST_OPTION_FAX_DETECT:
- ast_channel_setoption(other, ntohs(aoh->option), aoh->data,
- f->datalen - sizeof(struct ast_option_header), 0);
- }
- }
- break;
- }
- } else if (f->frametype == AST_FRAME_DTMF_BEGIN) {
- struct ast_flags *cfg;
- char dtmfcode[2] = { f->subclass.integer, };
- size_t featurelen;
-
- if (who == chan) {
- featurelen = strlen(chan_featurecode);
- cfg = &(config->features_caller);
- } else {
- featurelen = strlen(peer_featurecode);
- cfg = &(config->features_callee);
- }
- /* Take a peek if this (possibly) matches a feature. If not, just pass this
- * DTMF along untouched. If this is not the first digit of a multi-digit code
- * then we need to fall through and stream the characters if it matches */
- if (featurelen == 0
- && feature_check(chan, cfg, &dtmfcode[0]) == AST_FEATURE_RETURN_PASSDIGITS) {
- if (option_debug > 3) {
- ast_log(LOG_DEBUG, "Passing DTMF through, since it is not a feature code\n");
- }
- ast_write(other, f);
- sendingdtmfdigit = 1;
- } else {
- /* If ast_opt_transmit_silence is set, then we need to make sure we are
- * transmitting something while we hold on to the DTMF waiting for a
- * feature. */
- if (!silgen && ast_opt_transmit_silence) {
- silgen = ast_channel_start_silence_generator(other);
- }
- if (option_debug > 3) {
- ast_log(LOG_DEBUG, "Not passing DTMF through, since it may be a feature code\n");
- }
- }
- } else if (f->frametype == AST_FRAME_DTMF_END) {
- char *featurecode;
- int sense;
- unsigned int dtmfduration = f->len;
-
- hadfeatures = hasfeatures;
- /* This cannot overrun because the longest feature is one shorter than our buffer */
- if (who == chan) {
- sense = FEATURE_SENSE_CHAN;
- featurecode = chan_featurecode;
- } else {
- sense = FEATURE_SENSE_PEER;
- featurecode = peer_featurecode;
- }
+ bridge_config_set_limits(config, &call_duration_limits_chan, &call_duration_limits_peer);
- if (sendingdtmfdigit == 1) {
- /* We let the BEGIN go through happily, so let's not bother with the END,
- * since we already know it's not something we bother with */
- ast_write(other, f);
- sendingdtmfdigit = 0;
- } else {
- /*! append the event to featurecode. we rely on the string being zero-filled, and
- * not overflowing it.
- * \todo XXX how do we guarantee the latter ?
- */
- featurecode[strlen(featurecode)] = f->subclass.integer;
- /* Get rid of the frame before we start doing "stuff" with the channels */
- ast_frfree(f);
- f = NULL;
- if (silgen) {
- ast_channel_stop_silence_generator(other, silgen);
- silgen = NULL;
- }
- config->feature_timer = 0;
- res = feature_interpret(chan, peer, config, featurecode, sense);
- switch(res) {
- case AST_FEATURE_RETURN_PASSDIGITS:
- ast_dtmf_stream(other, who, featurecode, 0, dtmfduration);
- /* Fall through */
- case AST_FEATURE_RETURN_SUCCESS:
- memset(featurecode, 0, sizeof(chan_featurecode));
- break;
- }
- if (res >= AST_FEATURE_RETURN_PASSDIGITS) {
- res = 0;
- } else {
- break;
- }
- hasfeatures = !ast_strlen_zero(chan_featurecode) || !ast_strlen_zero(peer_featurecode);
- if (hadfeatures && !hasfeatures) {
- /* Feature completed or timed out */
- config->feature_timer = 0;
- } else if (hasfeatures) {
- if (config->timelimit) {
- /* No warning next time - we are waiting for feature code */
- ast_set_flag(config, AST_FEATURE_WARNING_ACTIVE);
- }
- config->feature_start_time = ast_tvnow();
- config->feature_timer = featuredigittimeout;
- ast_debug(1, "Set feature timer to %ld ms\n", config->feature_timer);
- }
- }
+ if (ast_bridge_features_set_limits(&chan_features, &call_duration_limits_chan, 0)) {
+ abandon_call = 1;
+ }
+ if (ast_bridge_features_set_limits(peer_features, &call_duration_limits_peer, 0)) {
+ abandon_call = 1;
}
- if (f)
- ast_frfree(f);
- }
- ast_cel_report_event(chan, AST_CEL_BRIDGE_END, NULL, NULL, peer);
-before_you_go:
- if (ast_channel_sending_dtmf_digit(chan)) {
- ast_bridge_end_dtmf(chan, ast_channel_sending_dtmf_digit(chan),
- ast_channel_sending_dtmf_tv(chan), "bridge end");
- }
- if (ast_channel_sending_dtmf_digit(peer)) {
- ast_bridge_end_dtmf(peer, ast_channel_sending_dtmf_digit(peer),
- ast_channel_sending_dtmf_tv(peer), "bridge end");
- }
+ /* At this point we are done with the limits structs since they have been copied to the individual feature sets. */
+ ast_bridge_features_limits_destroy(&call_duration_limits_chan);
+ ast_bridge_features_limits_destroy(&call_duration_limits_peer);
- /* Just in case something weird happened and we didn't clean up the silence generator... */
- if (silgen) {
- ast_channel_stop_silence_generator(who == chan ? peer : chan, silgen);
- silgen = NULL;
+ if (abandon_call) {
+ ast_log(LOG_ERROR, "Could not set duration limits on one or more sides of the call. Bridge canceled.\n");
+ ast_bridge_features_destroy(peer_features);
+ ast_bridge_features_cleanup(&chan_features);
+ bridge_failed_peer_goto(chan, peer);
+ return -1;
+ }
}
- /* Wait for any dual redirect to complete. */
- while (ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT)) {
- sched_yield();
+ /* Create bridge */
+ bridge = ast_bridge_basic_new();
+ if (!bridge) {
+ ast_bridge_features_destroy(peer_features);
+ ast_bridge_features_cleanup(&chan_features);
+ bridge_failed_peer_goto(chan, peer);
+ return -1;
}
- if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT)) {
- ast_clear_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT); /* its job is done */
- if (bridge_cdr) {
- ast_cdr_discard(bridge_cdr);
- /* QUESTION: should we copy bridge_cdr fields to the peer before we throw it away? */
- }
- return res; /* if we shouldn't do the h-exten, we shouldn't do the bridge cdr, either! */
+ /* Put peer into the bridge */
+ if (ast_bridge_impart(bridge, peer, NULL, peer_features, 1)) {
+ ast_bridge_destroy(bridge);
+ ast_bridge_features_cleanup(&chan_features);
+ bridge_failed_peer_goto(chan, peer);
+ return -1;
}
- if (config->end_bridge_callback) {
- config->end_bridge_callback(config->end_bridge_callback_data);
- }
+ /* Join bridge */
+ ast_bridge_join(bridge, chan, NULL, &chan_features, NULL, 1);
- /* run the hangup exten on the chan object IFF it was NOT involved in a parking situation
- * if it were, then chan belongs to a different thread now, and might have been hung up long
- * ago.
+ /*
+ * If the bridge was broken for a hangup that isn't real, then
+ * don't run the h extension, because the channel isn't really
+ * hung up. This should really only happen with
+ * AST_SOFTHANGUP_ASYNCGOTO.
*/
- if (!(ast_channel_softhangup_internal_flag(chan) & (AST_SOFTHANGUP_ASYNCGOTO | AST_SOFTHANGUP_UNBRIDGE))
- && !ast_test_flag(&config->features_caller, AST_FEATURE_NO_H_EXTEN)) {
- struct ast_cdr *swapper = NULL;
- char savelastapp[AST_MAX_EXTENSION];
- char savelastdata[AST_MAX_EXTENSION];
- char save_context[AST_MAX_CONTEXT];
- char save_exten[AST_MAX_EXTENSION];
- int save_prio;
-
- ast_channel_lock(chan);
- if (bridge_cdr) {
- /*
- * Swap the bridge_cdr and the chan cdr for a moment, and let
- * the hangup dialplan code operate on it.
- */
- swapper = ast_channel_cdr(chan);
- ast_channel_cdr_set(chan, bridge_cdr);
-
- /* protect the lastapp/lastdata against the effects of the hangup/dialplan code */
- ast_copy_string(savelastapp, bridge_cdr->lastapp, sizeof(bridge_cdr->lastapp));
- ast_copy_string(savelastdata, bridge_cdr->lastdata, sizeof(bridge_cdr->lastdata));
- }
- ast_copy_string(save_context, ast_channel_context(chan), sizeof(save_context));
- ast_copy_string(save_exten, ast_channel_exten(chan), sizeof(save_exten));
- save_prio = ast_channel_priority(chan);
- ast_channel_unlock(chan);
-
- ast_autoservice_start(peer);
- if (ast_exists_extension(chan, ast_channel_context(chan), "h", 1,
- S_COR(ast_channel_caller(chan)->id.number.valid,
- ast_channel_caller(chan)->id.number.str, NULL))) {
- ast_pbx_h_exten_run(chan, ast_channel_context(chan));
- hangup_run = 1;
- } else if (!ast_strlen_zero(ast_channel_macrocontext(chan))
- && ast_exists_extension(chan, ast_channel_macrocontext(chan), "h", 1,
- S_COR(ast_channel_caller(chan)->id.number.valid,
- ast_channel_caller(chan)->id.number.str, NULL))) {
- ast_pbx_h_exten_run(chan, ast_channel_macrocontext(chan));
- hangup_run = 1;
- }
- if (ast_pbx_hangup_handler_run(chan)) {
- /* Indicate hangup handlers were run. */
- hangup_run = 1;
- }
- ast_autoservice_stop(peer);
-
- ast_channel_lock(chan);
-
- /* swap it back */
- ast_channel_context_set(chan, save_context);
- ast_channel_exten_set(chan, save_exten);
- ast_channel_priority_set(chan, save_prio);
- if (bridge_cdr) {
- if (ast_channel_cdr(chan) == bridge_cdr) {
- ast_channel_cdr_set(chan, swapper);
-
- /* Restore the lastapp/lastdata */
- ast_copy_string(bridge_cdr->lastapp, savelastapp, sizeof(bridge_cdr->lastapp));
- ast_copy_string(bridge_cdr->lastdata, savelastdata, sizeof(bridge_cdr->lastdata));
- } else {
- bridge_cdr = NULL;
- }
- }
- ast_channel_unlock(chan);
+ res = -1;
+ ast_channel_lock(chan);
+ if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) {
+ res = 0;
}
+ ast_channel_unlock(chan);
- /* obey the NoCDR() wishes. -- move the DISABLED flag to the bridge CDR if it was set on the channel during the bridge... */
- new_chan_cdr = pick_unlocked_cdr(ast_channel_cdr(chan)); /* the proper chan cdr, if there are forked cdrs */
+ ast_bridge_features_cleanup(&chan_features);
- /*
- * If the channel CDR has been modified during the call, record
- * the changes in the bridge cdr, BUT, if hangup_run, the CDR
- * got swapped so don't overwrite what was done in the
- * h-extension or hangup handlers. What a mess. This is why
- * you never touch CDR code.
- */
- if (new_chan_cdr && bridge_cdr && !hangup_run) {
- ast_cdr_copy_vars(bridge_cdr, new_chan_cdr);
- ast_copy_string(bridge_cdr->userfield, new_chan_cdr->userfield, sizeof(bridge_cdr->userfield));
- bridge_cdr->amaflags = new_chan_cdr->amaflags;
- ast_copy_string(bridge_cdr->accountcode, new_chan_cdr->accountcode, sizeof(bridge_cdr->accountcode));
- if (ast_test_flag(new_chan_cdr, AST_CDR_FLAG_POST_DISABLED)) {
- ast_set_flag(bridge_cdr, AST_CDR_FLAG_POST_DISABLED);
- }
- }
-
- /* we can post the bridge CDR at this point */
- if (bridge_cdr) {
- ast_cdr_end(bridge_cdr);
- ast_cdr_detach(bridge_cdr);
- }
-
- /* do a specialized reset on the beginning channel
- CDR's, if they still exist, so as not to mess up
- issues in future bridges;
-
- Here are the rules of the game:
- 1. The chan and peer channel pointers will not change
- during the life of the bridge.
- 2. But, in transfers, the channel names will change.
- between the time the bridge is started, and the
- time the channel ends.
- Usually, when a channel changes names, it will
- also change CDR pointers.
- 3. Usually, only one of the two channels (chan or peer)
- will change names.
- 4. Usually, if a channel changes names during a bridge,
- it is because of a transfer. Usually, in these situations,
- it is normal to see 2 bridges running simultaneously, and
- it is not unusual to see the two channels that change
- swapped between bridges.
- 5. After a bridge occurs, we have 2 or 3 channels' CDRs
- to attend to; if the chan or peer changed names,
- we have the before and after attached CDR's.
- */
-
- if (new_chan_cdr) {
- struct ast_channel *chan_ptr = NULL;
-
- if (strcasecmp(orig_channame, ast_channel_name(chan)) != 0) {
- /* old channel */
- if ((chan_ptr = ast_channel_get_by_name(orig_channame))) {
- ast_channel_lock(chan_ptr);
- if (!ast_bridged_channel(chan_ptr)) {
- struct ast_cdr *cur;
- for (cur = ast_channel_cdr(chan_ptr); cur; cur = cur->next) {
- if (cur == chan_cdr) {
- break;
- }
- }
- if (cur) {
- ast_cdr_specialized_reset(chan_cdr, 0);
- }
- }
- ast_channel_unlock(chan_ptr);
- chan_ptr = ast_channel_unref(chan_ptr);
- }
- /* new channel */
- ast_cdr_specialized_reset(new_chan_cdr, 0);
- } else {
- ast_cdr_specialized_reset(ast_channel_cdr(chan), 0); /* nothing changed, reset the chan cdr */
- }
- }
-
- {
- struct ast_channel *chan_ptr = NULL;
- new_peer_cdr = pick_unlocked_cdr(ast_channel_cdr(peer)); /* the proper chan cdr, if there are forked cdrs */
- if (new_chan_cdr && ast_test_flag(new_chan_cdr, AST_CDR_FLAG_POST_DISABLED) && new_peer_cdr && !ast_test_flag(new_peer_cdr, AST_CDR_FLAG_POST_DISABLED))
- ast_set_flag(new_peer_cdr, AST_CDR_FLAG_POST_DISABLED); /* DISABLED is viral-- it will propagate across a bridge */
- if (strcasecmp(orig_peername, ast_channel_name(peer)) != 0) {
- /* old channel */
- if ((chan_ptr = ast_channel_get_by_name(orig_peername))) {
- ast_channel_lock(chan_ptr);
- if (!ast_bridged_channel(chan_ptr)) {
- struct ast_cdr *cur;
- for (cur = ast_channel_cdr(chan_ptr); cur; cur = cur->next) {
- if (cur == peer_cdr) {
- break;
- }
- }
- if (cur) {
- ast_cdr_specialized_reset(peer_cdr, 0);
- }
- }
- ast_channel_unlock(chan_ptr);
- chan_ptr = ast_channel_unref(chan_ptr);
- }
- /* new channel */
- if (new_peer_cdr) {
- ast_cdr_specialized_reset(new_peer_cdr, 0);
- }
- } else {
- if (we_disabled_peer_cdr) {
- ast_clear_flag(ast_channel_cdr(peer), AST_CDR_FLAG_POST_DISABLED);
- }
- ast_cdr_specialized_reset(ast_channel_cdr(peer), 0); /* nothing changed, reset the peer cdr */
- }
+/* BUGBUG this is used by Dial and FollowMe for CDR information. By Queue for Queue stats like CDRs. */
+ if (res && config->end_bridge_callback) {
+ config->end_bridge_callback(config->end_bridge_callback_data);
}
return res;
@@ -5456,395 +5096,6 @@ AST_APP_OPTIONS(park_call_options, BEGIN_OPTIONS
AST_APP_OPTION('s', AST_PARK_OPT_SILENCE),
END_OPTIONS );
-/*! \brief Park a call */
-static int park_call_exec(struct ast_channel *chan, const char *data)
-{
- struct ast_park_call_args args = { 0, };
- struct ast_flags flags = { 0 };
- char orig_exten[AST_MAX_EXTENSION];
- int orig_priority;
- int res;
- const char *pl_name;
- char *parse;
- struct park_app_args app_args;
-
- /*
- * Cache the original channel name because we are going to
- * masquerade the channel. Prefer the BLINDTRANSFER channel
- * name over this channel name. BLINDTRANSFER could be set if
- * the parking access extension did not get detected and we are
- * executing the Park application from the dialplan.
- *
- * The orig_chan_name is used to return the call to the
- * originator on parking timeout.
- */
- args.orig_chan_name = ast_strdupa(S_OR(
- pbx_builtin_getvar_helper(chan, "BLINDTRANSFER"), ast_channel_name(chan)));
-
- /* Answer if call is not up */
- if (ast_channel_state(chan) != AST_STATE_UP) {
- if (ast_answer(chan)) {
- return -1;
- }
-
- /* Sleep to allow VoIP streams to settle down */
- if (ast_safe_sleep(chan, 1000)) {
- return -1;
- }
- }
-
- /* Process the dialplan application options. */
- parse = ast_strdupa(data);
- AST_STANDARD_APP_ARGS(app_args, parse);
-
- if (!ast_strlen_zero(app_args.timeout)) {
- if (sscanf(app_args.timeout, "%30d", &args.timeout) != 1) {
- ast_log(LOG_WARNING, "Invalid timeout '%s' provided\n", app_args.timeout);
- args.timeout = 0;
- }
- }
- if (!ast_strlen_zero(app_args.return_con)) {
- args.return_con = app_args.return_con;
- }
- if (!ast_strlen_zero(app_args.return_ext)) {
- args.return_ext = app_args.return_ext;
- }
- if (!ast_strlen_zero(app_args.return_pri)) {
- if (sscanf(app_args.return_pri, "%30d", &args.return_pri) != 1) {
- ast_log(LOG_WARNING, "Invalid priority '%s' specified\n", app_args.return_pri);
- args.return_pri = 0;
- }
- }
-
- ast_app_parse_options(park_call_options, &flags, NULL, app_args.options);
- args.flags = flags.flags;
-
- /*
- * Setup the exten/priority to be s/1 since we don't know where
- * this call should return.
- */
- ast_copy_string(orig_exten, ast_channel_exten(chan), sizeof(orig_exten));
- orig_priority = ast_channel_priority(chan);
- ast_channel_exten_set(chan, "s");
- ast_channel_priority_set(chan, 1);
-
- /* Park the call */
- if (!ast_strlen_zero(app_args.pl_name)) {
- pl_name = app_args.pl_name;
- } else {
- pl_name = findparkinglotname(chan);
- }
- if (ast_strlen_zero(pl_name)) {
- /* Parking lot is not specified, so use the default parking lot. */
- args.parkinglot = parkinglot_addref(default_parkinglot);
- } else {
- args.parkinglot = find_parkinglot(pl_name);
- if (!args.parkinglot && parkeddynamic) {
- args.parkinglot = create_dynamic_parkinglot(pl_name, chan);
- }
- }
- if (args.parkinglot) {
- res = masq_park_call(chan, chan, &args);
- parkinglot_unref(args.parkinglot);
- } else {
- /* Parking failed because the parking lot does not exist. */
- if (!ast_test_flag(&args, AST_PARK_OPT_SILENCE)) {
- ast_stream_and_wait(chan, "pbx-parkingfailed", "");
- }
- res = -1;
- }
- if (res) {
- /* Park failed, try to continue in the dialplan. */
- ast_channel_exten_set(chan, orig_exten);
- ast_channel_priority_set(chan, orig_priority);
- res = 0;
- } else {
- /* Park succeeded. */
- res = -1;
- }
-
- return res;
-}
-
-/*! \brief Pickup parked call */
-static int parked_call_exec(struct ast_channel *chan, const char *data)
-{
- int res;
- struct ast_channel *peer = NULL;
- struct parkeduser *pu;
- struct ast_context *con;
- char *parse;
- const char *pl_name;
- int park = 0;
- struct ast_bridge_config config;
- struct ast_parkinglot *parkinglot;
- AST_DECLARE_APP_ARGS(app_args,
- AST_APP_ARG(pl_space); /*!< Parking lot space to retrieve if present. */
- AST_APP_ARG(pl_name); /*!< Parking lot name to use if present. */
- AST_APP_ARG(dummy); /*!< Place to put any remaining args string. */
- );
-
- parse = ast_strdupa(data);
- AST_STANDARD_APP_ARGS(app_args, parse);
-
- if (!ast_strlen_zero(app_args.pl_space)) {
- if (sscanf(app_args.pl_space, "%30u", &park) != 1) {
- ast_log(LOG_WARNING, "Specified parking extension not a number: %s\n",
- app_args.pl_space);
- park = -1;
- }
- }
-
- if (!ast_strlen_zero(app_args.pl_name)) {
- pl_name = app_args.pl_name;
- } else {
- pl_name = findparkinglotname(chan);
- }
- if (ast_strlen_zero(pl_name)) {
- /* Parking lot is not specified, so use the default parking lot. */
- parkinglot = parkinglot_addref(default_parkinglot);
- } else {
- parkinglot = find_parkinglot(pl_name);
- if (!parkinglot) {
- /* It helps to answer the channel if not already up. :) */
- if (ast_channel_state(chan) != AST_STATE_UP) {
- ast_answer(chan);
- }
- if (ast_stream_and_wait(chan, "pbx-invalidpark", "")) {
- ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n",
- "pbx-invalidpark", ast_channel_name(chan));
- }
- ast_log(LOG_WARNING,
- "Channel %s tried to retrieve parked call from unknown parking lot '%s'\n",
- ast_channel_name(chan), pl_name);
- return -1;
- }
- }
-
- AST_LIST_LOCK(&parkinglot->parkings);
- AST_LIST_TRAVERSE_SAFE_BEGIN(&parkinglot->parkings, pu, list) {
- if ((ast_strlen_zero(app_args.pl_space) || pu->parkingnum == park)
- && !pu->notquiteyet && !ast_channel_pbx(pu->chan)) {
- /* The parking space has a call and can be picked up now. */
- AST_LIST_REMOVE_CURRENT(list);
- break;
- }
- }
- AST_LIST_TRAVERSE_SAFE_END;
- if (pu) {
- struct ast_callid *callid = ast_read_threadstorage_callid();
-
- /* Found a parked call to pickup. */
- peer = pu->chan;
-
- /* We need to map the call id we have from this thread to the channel we found. */
- if (callid) {
- ast_channel_callid_set(peer, callid);
- callid = ast_callid_unref(callid);
- }
-
- con = ast_context_find(parkinglot->cfg.parking_con);
- if (con) {
- if (ast_context_remove_extension2(con, pu->parkingexten, 1, NULL, 0)) {
- ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n");
- } else {
- notify_metermaids(pu->parkingexten, parkinglot->cfg.parking_con, AST_DEVICE_NOT_INUSE);
- }
- } else {
- ast_log(LOG_WARNING, "Whoa, no parking context?\n");
- }
-
- ast_cel_report_event(pu->chan, AST_CEL_PARK_END, NULL, "UnParkedCall", chan);
- /*** DOCUMENTATION
- <managerEventInstance>
- <synopsis>Raised when a call has been unparked.</synopsis>
- <syntax>
- <xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter[@name='Exten'])" />
- <xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter[@name='Parkinglot'])" />
- <xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter[@name='From'])" />
- </syntax>
- <see-also>
- <ref type="application">ParkedCall</ref>
- <ref type="managerEvent">ParkedCall</ref>
- </see-also>
- </managerEventInstance>
- ***/
- ast_manager_event(pu->chan, EVENT_FLAG_CALL, "UnParkedCall",
- "Exten: %s\r\n"
- "Channel: %s\r\n"
- "Parkinglot: %s\r\n"
- "From: %s\r\n"
- "CallerIDNum: %s\r\n"
- "CallerIDName: %s\r\n"
- "ConnectedLineNum: %s\r\n"
- "ConnectedLineName: %s\r\n"
- "Uniqueid: %s\r\n",
- pu->parkingexten, ast_channel_name(pu->chan), pu->parkinglot->name,
- ast_channel_name(chan),
- S_COR(ast_channel_caller(pu->chan)->id.number.valid, ast_channel_caller(pu->chan)->id.number.str, "<unknown>"),
- S_COR(ast_channel_caller(pu->chan)->id.name.valid, ast_channel_caller(pu->chan)->id.name.str, "<unknown>"),
- S_COR(ast_channel_connected(pu->chan)->id.number.valid, ast_channel_connected(pu->chan)->id.number.str, "<unknown>"),
- S_COR(ast_channel_connected(pu->chan)->id.name.valid, ast_channel_connected(pu->chan)->id.name.str, "<unknown>"),
- ast_channel_uniqueid(pu->chan)
- );
-
- /* Stop entertaining the caller. */
- switch (pu->hold_method) {
- case AST_CONTROL_HOLD:
- ast_indicate(pu->chan, AST_CONTROL_UNHOLD);
- break;
- case AST_CONTROL_RINGING:
- ast_indicate(pu->chan, -1);
- break;
- default:
- break;
- }
- pu->hold_method = 0;
-
- parkinglot_unref(pu->parkinglot);
- ast_free(pu);
- }
- AST_LIST_UNLOCK(&parkinglot->parkings);
-
- if (peer) {
- /* Update connected line between retrieving call and parked call. */
- struct ast_party_connected_line connected;
-
- ast_party_connected_line_init(&connected);
-
- /* Send our caller-id to peer. */
- ast_channel_lock(chan);
- ast_connected_line_copy_from_caller(&connected, ast_channel_caller(chan));
- ast_channel_unlock(chan);
- connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER;
- if (ast_channel_connected_line_sub(chan, peer, &connected, 0) &&
- ast_channel_connected_line_macro(chan, peer, &connected, 0, 0)) {
- ast_channel_update_connected_line(peer, &connected, NULL);
- }
-
- /*
- * Get caller-id from peer.
- *
- * Update the retrieving call before it is answered if possible
- * for best results. Some phones do not support updating the
- * connected line information after connection.
- */
- ast_channel_lock(peer);
- ast_connected_line_copy_from_caller(&connected, ast_channel_caller(peer));
- ast_channel_unlock(peer);
- connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER;
- if (ast_channel_connected_line_sub(peer, chan, &connected, 0) &&
- ast_channel_connected_line_macro(peer, chan, &connected, 1, 0)) {
- ast_channel_update_connected_line(chan, &connected, NULL);
- }
-
- ast_party_connected_line_free(&connected);
- }
-
- /* JK02: it helps to answer the channel if not already up */
- if (ast_channel_state(chan) != AST_STATE_UP) {
- ast_answer(chan);
- }
-
- if (peer) {
- struct ast_datastore *features_datastore;
- struct ast_dial_features *dialfeatures;
-
- /* Play a courtesy to the source(s) configured to prefix the bridge connecting */
- if (!ast_strlen_zero(courtesytone)) {
- static const char msg[] = "courtesy tone";
-
- switch (parkedplay) {
- case 0:/* Courtesy tone to pickup chan */
- res = play_message_to_chans(chan, peer, -1, msg, courtesytone);
- break;
- case 1:/* Courtesy tone to parked chan */
- res = play_message_to_chans(chan, peer, 1, msg, courtesytone);
- break;
- case 2:/* Courtesy tone to both chans */
- res = play_message_to_chans(chan, peer, 0, msg, courtesytone);
- break;
- default:
- res = 0;
- break;
- }
- if (res) {
- ast_autoservice_chan_hangup_peer(chan, peer);
- parkinglot_unref(parkinglot);
- return -1;
- }
- }
-
- res = ast_channel_make_compatible(chan, peer);
- if (res < 0) {
- ast_log(LOG_WARNING, "Could not make channels %s and %s compatible for bridge\n", ast_channel_name(chan), ast_channel_name(peer));
- ast_autoservice_chan_hangup_peer(chan, peer);
- parkinglot_unref(parkinglot);
- return -1;
- }
- /* This runs sorta backwards, since we give the incoming channel control, as if it
- were the person called. */
- ast_verb(3, "Channel %s connected to parked call %d\n", ast_channel_name(chan), park);
-
- pbx_builtin_setvar_helper(chan, "PARKEDCHANNEL", ast_channel_name(peer));
- ast_cdr_setdestchan(ast_channel_cdr(chan), ast_channel_name(peer));
- memset(&config, 0, sizeof(struct ast_bridge_config));
-
- /* Get datastore for peer and apply it's features to the callee side of the bridge config */
- ast_channel_lock(peer);
- features_datastore = ast_channel_datastore_find(peer, &dial_features_info, NULL);
- if (features_datastore && (dialfeatures = features_datastore->data)) {
- ast_copy_flags(&config.features_callee, &dialfeatures->my_features,
- AST_FLAGS_ALL);
- }
- ast_channel_unlock(peer);
-
- if ((parkinglot->cfg.parkedcalltransfers == AST_FEATURE_FLAG_BYCALLEE) || (parkinglot->cfg.parkedcalltransfers == AST_FEATURE_FLAG_BYBOTH)) {
- ast_set_flag(&(config.features_callee), AST_FEATURE_REDIRECT);
- }
- if ((parkinglot->cfg.parkedcalltransfers == AST_FEATURE_FLAG_BYCALLER) || (parkinglot->cfg.parkedcalltransfers == AST_FEATURE_FLAG_BYBOTH)) {
- ast_set_flag(&(config.features_caller), AST_FEATURE_REDIRECT);
- }
- if ((parkinglot->cfg.parkedcallreparking == AST_FEATURE_FLAG_BYCALLEE) || (parkinglot->cfg.parkedcallreparking == AST_FEATURE_FLAG_BYBOTH)) {
- ast_set_flag(&(config.features_callee), AST_FEATURE_PARKCALL);
- }
- if ((parkinglot->cfg.parkedcallreparking == AST_FEATURE_FLAG_BYCALLER) || (parkinglot->cfg.parkedcallreparking == AST_FEATURE_FLAG_BYBOTH)) {
- ast_set_flag(&(config.features_caller), AST_FEATURE_PARKCALL);
- }
- if ((parkinglot->cfg.parkedcallhangup == AST_FEATURE_FLAG_BYCALLEE) || (parkinglot->cfg.parkedcallhangup == AST_FEATURE_FLAG_BYBOTH)) {
- ast_set_flag(&(config.features_callee), AST_FEATURE_DISCONNECT);
- }
- if ((parkinglot->cfg.parkedcallhangup == AST_FEATURE_FLAG_BYCALLER) || (parkinglot->cfg.parkedcallhangup == AST_FEATURE_FLAG_BYBOTH)) {
- ast_set_flag(&(config.features_caller), AST_FEATURE_DISCONNECT);
- }
- if ((parkinglot->cfg.parkedcallrecording == AST_FEATURE_FLAG_BYCALLEE) || (parkinglot->cfg.parkedcallrecording == AST_FEATURE_FLAG_BYBOTH)) {
- ast_set_flag(&(config.features_callee), AST_FEATURE_AUTOMON);
- }
- if ((parkinglot->cfg.parkedcallrecording == AST_FEATURE_FLAG_BYCALLER) || (parkinglot->cfg.parkedcallrecording == AST_FEATURE_FLAG_BYBOTH)) {
- ast_set_flag(&(config.features_caller), AST_FEATURE_AUTOMON);
- }
-
- res = ast_bridge_call(chan, peer, &config);
-
- pbx_builtin_setvar_helper(chan, "PARKEDCHANNEL", ast_channel_name(peer));
- ast_cdr_setdestchan(ast_channel_cdr(chan), ast_channel_name(peer));
-
- /* Simulate the PBX hanging up */
- ast_autoservice_chan_hangup_peer(chan, peer);
- } else {
- if (ast_stream_and_wait(chan, "pbx-invalidpark", "")) {
- ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", "pbx-invalidpark",
- ast_channel_name(chan));
- }
- ast_verb(3, "Channel %s tried to retrieve nonexistent parked call %d\n",
- ast_channel_name(chan), park);
- res = -1;
- }
-
- parkinglot_unref(parkinglot);
- return res;
-}
-
/*!
* \brief Unreference parkinglot object.
*/
@@ -6282,6 +5533,10 @@ static void process_applicationmap_line(struct ast_variable *var)
return;
}
+ /*
+ * We will parse and require correct syntax for the ActivatedBy
+ * option, but the ActivatedBy option is not honored anymore.
+ */
if (ast_strlen_zero(args.activatedby)) {
ast_set_flag(feature, AST_FEATURE_FLAG_BYBOTH);
} else if (!strcasecmp(args.activatedby, "caller")) {
@@ -7235,8 +6490,6 @@ static char *handle_feature_show(struct ast_cli_entry *e, int cmd, struct ast_cl
{
int i;
struct ast_call_feature *feature;
- struct ao2_iterator iter;
- struct ast_parkinglot *curlot;
#define HFS_FORMAT "%-25s %-7s %-7s\n"
switch (cmd) {
@@ -7292,28 +6545,7 @@ static char *handle_feature_show(struct ast_cli_entry *e, int cmd, struct ast_cl
}
AST_RWLIST_UNLOCK(&feature_groups);
- iter = ao2_iterator_init(parkinglots, 0);
- while ((curlot = ao2_iterator_next(&iter))) {
- ast_cli(a->fd, "\nCall parking (Parking lot: %s)\n", curlot->name);
- ast_cli(a->fd, "------------\n");
- ast_cli(a->fd,"%-22s: %s\n", "Parking extension", curlot->cfg.parkext);
- ast_cli(a->fd,"%-22s: %s\n", "Parking context", curlot->cfg.parking_con);
- ast_cli(a->fd,"%-22s: %d-%d\n", "Parked call extensions",
- curlot->cfg.parking_start, curlot->cfg.parking_stop);
- ast_cli(a->fd,"%-22s: %u ms\n", "Parkingtime", curlot->cfg.parkingtime);
- ast_cli(a->fd,"%-22s: %s\n", "Comeback to origin",
- (curlot->cfg.comebacktoorigin ? "yes" : "no"));
- ast_cli(a->fd,"%-22s: %s%s\n", "Comeback context",
- curlot->cfg.comebackcontext, (curlot->cfg.comebacktoorigin ?
- " (comebacktoorigin=yes, not used)" : ""));
- ast_cli(a->fd,"%-22s: %d\n", "Comeback dial time",
- curlot->cfg.comebackdialtime);
- ast_cli(a->fd,"%-22s: %s\n", "MusicOnHold class", curlot->cfg.mohclass);
- ast_cli(a->fd,"%-22s: %s\n", "Enabled", AST_CLI_YESNO(!curlot->disabled));
- ast_cli(a->fd,"\n");
- ao2_ref(curlot, -1);
- }
- ao2_iterator_destroy(&iter);
+ ast_cli(a->fd, "\n");
return CLI_SUCCESS;
}
@@ -7511,8 +6743,8 @@ static int action_bridge(struct mansession *s, const struct message *m)
return 0;
}
- tobj->chan = tmpchana;
- tobj->peer = tmpchanb;
+ tobj->chan = tmpchanb;
+ tobj->peer = tmpchana;
tobj->return_to_pbx = 1;
if (ast_true(playtone)) {
@@ -7545,6 +6777,7 @@ static int action_bridge(struct mansession *s, const struct message *m)
"Channel1: %s\r\n"
"Channel2: %s\r\n", ast_channel_name(tmpchana), ast_channel_name(tmpchanb));
+/* BUGBUG there seems to be no COLP update here. */
bridge_call_thread_launch(tobj);
astman_send_ack(s, m, "Launched bridge thread with success");
@@ -7552,180 +6785,11 @@ static int action_bridge(struct mansession *s, const struct message *m)
return 0;
}
-/*!
- * \brief CLI command to list parked calls
- * \param e
- * \param cmd
- * \param a
- *
- * Check right usage, lock parking lot, display parked calls, unlock parking lot list.
- * \retval CLI_SUCCESS on success.
- * \retval CLI_SHOWUSAGE on incorrect number of arguments.
- * \retval NULL when tab completion is used.
- */
-static char *handle_parkedcalls(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
- struct parkeduser *cur;
- int numparked = 0;
- struct ao2_iterator iter;
- struct ast_parkinglot *curlot;
-
- switch (cmd) {
- case CLI_INIT:
- e->command = "parkedcalls show";
- e->usage =
- "Usage: parkedcalls show\n"
- " List currently parked calls\n";
- return NULL;
- case CLI_GENERATE:
- return NULL;
- }
-
- if (a->argc > e->args)
- return CLI_SHOWUSAGE;
-
- ast_cli(a->fd, "%-10s %-25s (%-15s %-12s %4s) %s\n", "Num", "Channel",
- "Context", "Extension", "Pri", "Timeout");
-
- iter = ao2_iterator_init(parkinglots, 0);
- while ((curlot = ao2_iterator_next(&iter))) {
- int lotparked = 0;
-
- /* subtract ref for iterator and for configured parking lot */
- ast_cli(a->fd, "*** Parking lot: %s (%d)\n", curlot->name,
- ao2_ref(curlot, 0) - 2 - (curlot == default_parkinglot));
-
- AST_LIST_LOCK(&curlot->parkings);
- AST_LIST_TRAVERSE(&curlot->parkings, cur, list) {
- ast_cli(a->fd, "%-10.10s %-25s (%-15s %-12s %4d) %6lds\n",
- cur->parkingexten, ast_channel_name(cur->chan), cur->context, cur->exten,
- cur->priority,
- (long) (cur->start.tv_sec + (cur->parkingtime / 1000) - time(NULL)));
- ++lotparked;
- }
- AST_LIST_UNLOCK(&curlot->parkings);
- if (lotparked) {
- numparked += lotparked;
- ast_cli(a->fd, " %d parked call%s in parking lot %s\n", lotparked,
- ESS(lotparked), curlot->name);
- }
-
- ao2_ref(curlot, -1);
- }
- ao2_iterator_destroy(&iter);
-
- ast_cli(a->fd, "---\n%d parked call%s in total.\n", numparked, ESS(numparked));
-
- return CLI_SUCCESS;
-}
-
static struct ast_cli_entry cli_features[] = {
AST_CLI_DEFINE(handle_feature_show, "Lists configured features"),
AST_CLI_DEFINE(handle_features_reload, "Reloads configured features"),
- AST_CLI_DEFINE(handle_parkedcalls, "List currently parked calls"),
};
-static int manager_parkinglot_list(struct mansession *s, const struct message *m)
-{
- const char *id = astman_get_header(m, "ActionID");
- char idText[256] = "";
- struct ao2_iterator iter;
- struct ast_parkinglot *curlot;
-
- if (!ast_strlen_zero(id))
- snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
-
- astman_send_ack(s, m, "Parking lots will follow");
-
- iter = ao2_iterator_init(parkinglots, 0);
- while ((curlot = ao2_iterator_next(&iter))) {
- astman_append(s, "Event: Parkinglot\r\n"
- "Name: %s\r\n"
- "StartExten: %d\r\n"
- "StopExten: %d\r\n"
- "Timeout: %d\r\n"
- "\r\n",
- curlot->name,
- curlot->cfg.parking_start,
- curlot->cfg.parking_stop,
- curlot->cfg.parkingtime ? curlot->cfg.parkingtime / 1000 : curlot->cfg.parkingtime);
- ao2_ref(curlot, -1);
- }
-
- astman_append(s,
- "Event: ParkinglotsComplete\r\n"
- "%s"
- "\r\n",idText);
-
- return RESULT_SUCCESS;
-}
-
-/*!
- * \brief Dump parking lot status
- * \param s
- * \param m
- *
- * Lock parking lot, iterate list and append parked calls status, unlock parking lot.
- * \return Always RESULT_SUCCESS
- */
-static int manager_parking_status(struct mansession *s, const struct message *m)
-{
- struct parkeduser *cur;
- const char *id = astman_get_header(m, "ActionID");
- char idText[256] = "";
- struct ao2_iterator iter;
- struct ast_parkinglot *curlot;
- int numparked = 0;
- long now = time(NULL);
-
- if (!ast_strlen_zero(id))
- snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
-
- astman_send_ack(s, m, "Parked calls will follow");
-
- iter = ao2_iterator_init(parkinglots, 0);
- while ((curlot = ao2_iterator_next(&iter))) {
- AST_LIST_LOCK(&curlot->parkings);
- AST_LIST_TRAVERSE(&curlot->parkings, cur, list) {
- astman_append(s, "Event: ParkedCall\r\n"
- "Parkinglot: %s\r\n"
- "Exten: %d\r\n"
- "Channel: %s\r\n"
- "From: %s\r\n"
- "Timeout: %ld\r\n"
- "Duration: %ld\r\n"
- "CallerIDNum: %s\r\n"
- "CallerIDName: %s\r\n"
- "ConnectedLineNum: %s\r\n"
- "ConnectedLineName: %s\r\n"
- "%s"
- "\r\n",
- curlot->name,
- cur->parkingnum, ast_channel_name(cur->chan), cur->peername,
- (long) cur->start.tv_sec + (long) (cur->parkingtime / 1000) - now,
- now - (long) cur->start.tv_sec,
- S_COR(ast_channel_caller(cur->chan)->id.number.valid, ast_channel_caller(cur->chan)->id.number.str, ""), /* XXX in other places it is <unknown> */
- S_COR(ast_channel_caller(cur->chan)->id.name.valid, ast_channel_caller(cur->chan)->id.name.str, ""),
- S_COR(ast_channel_connected(cur->chan)->id.number.valid, ast_channel_connected(cur->chan)->id.number.str, ""), /* XXX in other places it is <unknown> */
- S_COR(ast_channel_connected(cur->chan)->id.name.valid, ast_channel_connected(cur->chan)->id.name.str, ""),
- idText);
- ++numparked;
- }
- AST_LIST_UNLOCK(&curlot->parkings);
- ao2_ref(curlot, -1);
- }
- ao2_iterator_destroy(&iter);
-
- astman_append(s,
- "Event: ParkedCallsComplete\r\n"
- "Total: %d\r\n"
- "%s"
- "\r\n",
- numparked, idText);
-
- return RESULT_SUCCESS;
-}
-
/*!
* \brief Create manager event for parked calls
* \param s
@@ -8210,8 +7274,11 @@ int ast_bridge_timelimit(struct ast_channel *chan, struct ast_bridge_config *con
calldurationlimit->tv_usec = (config->timelimit % 1000) * 1000;
ast_verb(3, "Setting call duration limit to %.3lf seconds.\n",
calldurationlimit->tv_sec + calldurationlimit->tv_usec / 1000000.0);
- config->timelimit = play_to_caller = play_to_callee =
- config->play_warning = config->warning_freq = 0;
+ play_to_caller = 0;
+ play_to_callee = 0;
+ config->timelimit = 0;
+ config->play_warning = 0;
+ config->warning_freq = 0;
} else {
ast_verb(4, "Limit Data for this call:\n");
ast_verb(4, "timelimit = %ld ms (%.3lf s)\n", config->timelimit, config->timelimit / 1000.0);
@@ -8242,12 +7309,17 @@ int ast_bridge_timelimit(struct ast_channel *chan, struct ast_bridge_config *con
*/
static int bridge_exec(struct ast_channel *chan, const char *data)
{
- struct ast_channel *current_dest_chan, *final_dest_chan, *chans[2];
+ struct ast_channel *current_dest_chan;
+ struct ast_channel *final_dest_chan;
+ struct ast_channel *chans[2];
char *tmp_data = NULL;
struct ast_flags opts = { 0, };
struct ast_bridge_config bconfig = { { 0, }, };
char *opt_args[OPT_ARG_ARRAY_SIZE];
struct timeval calldurationlimit = { 0, };
+ const char *context;
+ const char *extension;
+ int priority;
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(dest_chan);
@@ -8357,8 +7429,7 @@ static int bridge_exec(struct ast_channel *chan, const char *data)
"Channel1: %s\r\n"
"Channel2: %s\r\n", ast_channel_name(chan), ast_channel_name(final_dest_chan));
- /* Maybe we should return this channel to the PBX? */
- ast_autoservice_chan_hangup_peer(chan, final_dest_chan);
+ bridge_failed_peer_goto(chan, final_dest_chan);
pbx_builtin_setvar_helper(chan, "BRIDGERESULT", "INCOMPATIBLE");
current_dest_chan = ast_channel_unref(current_dest_chan);
@@ -8406,60 +7477,28 @@ static int bridge_exec(struct ast_channel *chan, const char *data)
if (ast_test_flag(&opts, OPT_CALLER_PARK))
ast_set_flag(&(bconfig.features_caller), AST_FEATURE_PARKCALL);
- /*
- * Don't let the after-bridge code run the h-exten. We want to
- * continue in the dialplan.
- */
- ast_set_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT);
+ /* Setup after bridge goto location. */
+ if (ast_test_flag(&opts, OPT_CALLEE_GO_ON)) {
+ ast_channel_lock(chan);
+ context = ast_strdupa(ast_channel_context(chan));
+ extension = ast_strdupa(ast_channel_exten(chan));
+ priority = ast_channel_priority(chan);
+ ast_channel_unlock(chan);
+ ast_after_bridge_set_go_on(final_dest_chan, context, extension, priority,
+ opt_args[OPT_ARG_CALLEE_GO_ON]);
+ } else if (!ast_test_flag(&opts, OPT_CALLEE_KILL)) {
+ ast_channel_lock(final_dest_chan);
+ context = ast_strdupa(ast_channel_context(final_dest_chan));
+ extension = ast_strdupa(ast_channel_exten(final_dest_chan));
+ priority = ast_channel_priority(final_dest_chan);
+ ast_channel_unlock(final_dest_chan);
+ ast_after_bridge_set_goto(final_dest_chan, context, extension, priority);
+ }
+
ast_bridge_call(chan, final_dest_chan, &bconfig);
/* The bridge has ended, set BRIDGERESULT to SUCCESS. */
pbx_builtin_setvar_helper(chan, "BRIDGERESULT", "SUCCESS");
-
- /* If the other channel has not been hung up, return it to the PBX */
- if (!ast_check_hangup(final_dest_chan)) {
- if (ast_test_flag(&opts, OPT_CALLEE_GO_ON)) {
- char *caller_context;
- char *caller_extension;
- int caller_priority;
- int goto_opt;
-
- ast_channel_lock(chan);
- caller_context = ast_strdupa(ast_channel_context(chan));
- caller_extension = ast_strdupa(ast_channel_exten(chan));
- caller_priority = ast_channel_priority(chan);
- ast_channel_unlock(chan);
-
- if (!ast_strlen_zero(opt_args[OPT_ARG_CALLEE_GO_ON])) {
- ast_replace_subargument_delimiter(opt_args[OPT_ARG_CALLEE_GO_ON]);
- /* Set current dialplan position to bridger dialplan position */
- goto_opt = ast_goto_if_exists(final_dest_chan, caller_context, caller_extension, caller_priority)
- /* Then perform the goto */
- || ast_parseable_goto(final_dest_chan, opt_args[OPT_ARG_CALLEE_GO_ON]);
- } else { /* F() */
- goto_opt = ast_goto_if_exists(final_dest_chan, caller_context, caller_extension, caller_priority + 1);
- }
- if (goto_opt || ast_pbx_start(final_dest_chan)) {
- ast_autoservice_chan_hangup_peer(chan, final_dest_chan);
- }
- } else if (!ast_test_flag(&opts, OPT_CALLEE_KILL)) {
- ast_debug(1, "starting new PBX in %s,%s,%d for chan %s\n",
- ast_channel_context(final_dest_chan), ast_channel_exten(final_dest_chan),
- ast_channel_priority(final_dest_chan), ast_channel_name(final_dest_chan));
-
- if (ast_pbx_start(final_dest_chan)) {
- ast_log(LOG_WARNING, "FAILED continuing PBX on dest chan %s\n", ast_channel_name(final_dest_chan));
- ast_autoservice_chan_hangup_peer(chan, final_dest_chan);
- } else {
- ast_debug(1, "SUCCESS continuing PBX on chan %s\n", ast_channel_name(final_dest_chan));
- }
- } else {
- ast_autoservice_chan_hangup_peer(chan, final_dest_chan);
- }
- } else {
- ast_debug(1, "chan %s was hungup\n", ast_channel_name(final_dest_chan));
- ast_autoservice_chan_hangup_peer(chan, final_dest_chan);
- }
done:
ast_free((char *) bconfig.warning_sound);
ast_free((char *) bconfig.end_sound);
@@ -9130,10 +8169,7 @@ static void features_shutdown(void)
ast_custom_function_unregister(&feature_function);
ast_manager_unregister("Bridge");
ast_manager_unregister("Park");
- ast_manager_unregister("Parkinglots");
- ast_manager_unregister("ParkedCalls");
- ast_unregister_application(parkcall);
- ast_unregister_application(parkedcall);
+
ast_unregister_application(app_bridge);
pthread_cancel(parking_thread);
@@ -9161,12 +8197,7 @@ int ast_features_init(void)
return -1;
}
ast_register_application2(app_bridge, bridge_exec, NULL, NULL, NULL);
- res = ast_register_application2(parkedcall, parked_call_exec, NULL, NULL, NULL);
- if (!res)
- res = ast_register_application2(parkcall, park_call_exec, NULL, NULL, NULL);
if (!res) {
- ast_manager_register_xml_core("ParkedCalls", 0, manager_parking_status);
- ast_manager_register_xml_core("Parkinglots", 0, manager_parkinglot_list);
ast_manager_register_xml_core("Park", EVENT_FLAG_CALL, manager_park);
ast_manager_register_xml_core("Bridge", EVENT_FLAG_CALL, action_bridge);
}
@@ -9182,4 +8213,3 @@ int ast_features_init(void)
return res;
}
-
diff --git a/main/frame.c b/main/frame.c
index b559d552d..8822261f6 100644
--- a/main/frame.c
+++ b/main/frame.c
@@ -635,6 +635,10 @@ void ast_frame_subclass2str(struct ast_frame *f, char *subclass, size_t slen, ch
/* Should never happen */
snprintf(subclass, slen, "IAX Frametype %d", f->subclass.integer);
break;
+ case AST_FRAME_BRIDGE_ACTION:
+ /* Should never happen */
+ snprintf(subclass, slen, "Bridge Frametype %d", f->subclass.integer);
+ break;
case AST_FRAME_TEXT:
ast_copy_string(subclass, "N/A", slen);
if (moreinfo) {
@@ -722,6 +726,10 @@ void ast_frame_type2str(enum ast_frame_type frame_type, char *ftype, size_t len)
/* Should never happen */
ast_copy_string(ftype, "IAX Specific", len);
break;
+ case AST_FRAME_BRIDGE_ACTION:
+ /* Should never happen */
+ ast_copy_string(ftype, "Bridge Specific", len);
+ break;
case AST_FRAME_TEXT:
ast_copy_string(ftype, "Text", len);
break;
diff --git a/main/manager.c b/main/manager.c
index c9b2fbe1e..c28e6169b 100644
--- a/main/manager.c
+++ b/main/manager.c
@@ -94,6 +94,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/stasis.h"
#include "asterisk/test.h"
#include "asterisk/json.h"
+#include "asterisk/bridging.h"
/*** DOCUMENTATION
<manager name="Ping" language="en_US">
@@ -966,6 +967,25 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
manager.conf will be present upon starting a new session.</para>
</description>
</manager>
+ <manager name="BlindTransfer" language="en_US">
+ <synopsis>
+ Blind transfer channel(s) to the given destination
+ </synopsis>
+ <syntax>
+ <parameter name="Channel" required="true">
+ </parameter>
+ <parameter name="Context">
+ </parameter>
+ <parameter name="Exten">
+ </parameter>
+ </syntax>
+ <description>
+ <para>Redirect all channels currently bridged to the specified channel to the specified destination.</para>
+ </description>
+ <see-also>
+ <ref type="manager">Redirect</ref>
+ </see-also>
+ </manager>
***/
/*! \addtogroup Group_AMI AMI functions
@@ -3538,7 +3558,8 @@ static int action_status(struct mansession *s, const struct message *m)
const char *cvariables = astman_get_header(m, "Variables");
char *variables = ast_strdupa(S_OR(cvariables, ""));
struct ast_channel *c;
- char bridge[256];
+ struct ast_bridge *bridge;
+ char bridge_text[256];
struct timeval now = ast_tvnow();
long elapsed_seconds = 0;
int channels = 0;
@@ -3607,44 +3628,52 @@ static int action_status(struct mansession *s, const struct message *m)
}
channels++;
- if (ast_channel_internal_bridged_channel(c)) {
- snprintf(bridge, sizeof(bridge), "BridgedChannel: %s\r\nBridgedUniqueid: %s\r\n", ast_channel_name(ast_channel_internal_bridged_channel(c)), ast_channel_uniqueid(ast_channel_internal_bridged_channel(c)));
+ bridge = ast_channel_get_bridge(c);
+ if (bridge) {
+ snprintf(bridge_text, sizeof(bridge_text), "BridgeID: %s\r\n",
+ bridge->uniqueid);
+ ao2_ref(bridge, -1);
} else {
- bridge[0] = '\0';
+ bridge_text[0] = '\0';
}
if (ast_channel_pbx(c)) {
if (ast_channel_cdr(c)) {
elapsed_seconds = now.tv_sec - ast_channel_cdr(c)->start.tv_sec;
}
astman_append(s,
- "Event: Status\r\n"
- "Privilege: Call\r\n"
- "Channel: %s\r\n"
- "CallerIDNum: %s\r\n"
- "CallerIDName: %s\r\n"
- "ConnectedLineNum: %s\r\n"
- "ConnectedLineName: %s\r\n"
- "Accountcode: %s\r\n"
- "ChannelState: %d\r\n"
- "ChannelStateDesc: %s\r\n"
- "Context: %s\r\n"
- "Extension: %s\r\n"
- "Priority: %d\r\n"
- "Seconds: %ld\r\n"
- "%s"
- "Uniqueid: %s\r\n"
- "%s"
- "%s"
- "\r\n",
- ast_channel_name(c),
- S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, "<unknown>"),
- S_COR(ast_channel_caller(c)->id.name.valid, ast_channel_caller(c)->id.name.str, "<unknown>"),
- S_COR(ast_channel_connected(c)->id.number.valid, ast_channel_connected(c)->id.number.str, "<unknown>"),
- S_COR(ast_channel_connected(c)->id.name.valid, ast_channel_connected(c)->id.name.str, "<unknown>"),
- ast_channel_accountcode(c),
- ast_channel_state(c),
- ast_state2str(ast_channel_state(c)), ast_channel_context(c),
- ast_channel_exten(c), ast_channel_priority(c), (long)elapsed_seconds, bridge, ast_channel_uniqueid(c), ast_str_buffer(str), idText);
+ "Event: Status\r\n"
+ "Privilege: Call\r\n"
+ "Channel: %s\r\n"
+ "CallerIDNum: %s\r\n"
+ "CallerIDName: %s\r\n"
+ "ConnectedLineNum: %s\r\n"
+ "ConnectedLineName: %s\r\n"
+ "Accountcode: %s\r\n"
+ "ChannelState: %d\r\n"
+ "ChannelStateDesc: %s\r\n"
+ "Context: %s\r\n"
+ "Extension: %s\r\n"
+ "Priority: %d\r\n"
+ "Seconds: %ld\r\n"
+ "%s"
+ "Uniqueid: %s\r\n"
+ "%s"
+ "%s"
+ "\r\n",
+ ast_channel_name(c),
+ S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, "<unknown>"),
+ S_COR(ast_channel_caller(c)->id.name.valid, ast_channel_caller(c)->id.name.str, "<unknown>"),
+ S_COR(ast_channel_connected(c)->id.number.valid, ast_channel_connected(c)->id.number.str, "<unknown>"),
+ S_COR(ast_channel_connected(c)->id.name.valid, ast_channel_connected(c)->id.name.str, "<unknown>"),
+ ast_channel_accountcode(c),
+ ast_channel_state(c),
+ ast_state2str(ast_channel_state(c)),
+ ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c),
+ (long) elapsed_seconds,
+ bridge_text,
+ ast_channel_uniqueid(c),
+ ast_str_buffer(str),
+ idText);
} else {
astman_append(s,
"Event: Status\r\n"
@@ -3667,8 +3696,11 @@ static int action_status(struct mansession *s, const struct message *m)
S_COR(ast_channel_connected(c)->id.number.valid, ast_channel_connected(c)->id.number.str, "<unknown>"),
S_COR(ast_channel_connected(c)->id.name.valid, ast_channel_connected(c)->id.name.str, "<unknown>"),
ast_channel_accountcode(c),
- ast_state2str(ast_channel_state(c)), bridge, ast_channel_uniqueid(c),
- ast_str_buffer(str), idText);
+ ast_state2str(ast_channel_state(c)),
+ bridge_text,
+ ast_channel_uniqueid(c),
+ ast_str_buffer(str),
+ idText);
}
ast_channel_unlock(c);
@@ -3804,12 +3836,6 @@ static int action_redirect(struct mansession *s, const struct message *m)
if (ast_strlen_zero(name2)) {
/* Single channel redirect in progress. */
- if (ast_channel_pbx(chan)) {
- ast_channel_lock(chan);
- /* don't let the after-bridge code run the h-exten */
- ast_set_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT);
- ast_channel_unlock(chan);
- }
res = ast_async_goto(chan, context, exten, pi);
if (!res) {
astman_send_ack(s, m, "Redirect successful");
@@ -3837,16 +3863,12 @@ static int action_redirect(struct mansession *s, const struct message *m)
/* Dual channel redirect in progress. */
if (ast_channel_pbx(chan)) {
ast_channel_lock(chan);
- /* don't let the after-bridge code run the h-exten */
- ast_set_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT
- | AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT);
+ ast_set_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT);
ast_channel_unlock(chan);
}
if (ast_channel_pbx(chan2)) {
ast_channel_lock(chan2);
- /* don't let the after-bridge code run the h-exten */
- ast_set_flag(ast_channel_flags(chan2), AST_FLAG_BRIDGE_HANGUP_DONT
- | AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT);
+ ast_set_flag(ast_channel_flags(chan2), AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT);
ast_channel_unlock(chan2);
}
res = ast_async_goto(chan, context, exten, pi);
@@ -3882,6 +3904,51 @@ static int action_redirect(struct mansession *s, const struct message *m)
return 0;
}
+static int action_blind_transfer(struct mansession *s, const struct message *m)
+{
+ const char *name = astman_get_header(m, "Channel");
+ const char *exten = astman_get_header(m, "Exten");
+ const char *context = astman_get_header(m, "Context");
+ RAII_VAR(struct ast_channel *, chan, NULL, ao2_cleanup);
+
+ if (ast_strlen_zero(name)) {
+ astman_send_error(s, m, "No channel specified");
+ return 0;
+ }
+
+ if (ast_strlen_zero(exten)) {
+ astman_send_error(s, m, "No extension specified");
+ return 0;
+ }
+
+ chan = ast_channel_get_by_name(name);
+ if (!chan) {
+ astman_send_error(s, m, "Channel specified does not exist");
+ return 0;
+ }
+
+ if (ast_strlen_zero(context)) {
+ context = ast_channel_context(chan);
+ }
+
+ switch (ast_bridge_transfer_blind(chan, exten, context, NULL, NULL)) {
+ case AST_BRIDGE_TRANSFER_NOT_PERMITTED:
+ astman_send_error(s, m, "Transfer not permitted");
+ break;
+ case AST_BRIDGE_TRANSFER_INVALID:
+ astman_send_error(s, m, "Transfer invalid");
+ break;
+ case AST_BRIDGE_TRANSFER_FAIL:
+ astman_send_error(s, m, "Transfer failed");
+ break;
+ case AST_BRIDGE_TRANSFER_SUCCESS:
+ astman_send_ack(s, m, "Transfer succeeded");
+ break;
+ }
+
+ return 0;
+}
+
static int action_atxfer(struct mansession *s, const struct message *m)
{
const char *name = astman_get_header(m, "Channel");
@@ -5683,7 +5750,7 @@ int __ast_manager_event_multichan(int category, const char *event, int chancount
return -1;
}
- cat_str = authority_to_str (category, &auth);
+ cat_str = authority_to_str(category, &auth);
ast_str_set(&buf, 0,
"Event: %s\r\nPrivilege: %s\r\n",
event, cat_str);
@@ -7510,6 +7577,10 @@ static int __init_manager(int reload, int by_external_config)
return -1;
}
+ if (manager_bridging_init()) {
+ return -1;
+ }
+
if (!registered) {
/* Register default actions */
ast_manager_register_xml_core("Ping", 0, action_ping);
@@ -7547,6 +7618,7 @@ static int __init_manager(int reload, int by_external_config)
ast_manager_register_xml_core("ModuleCheck", EVENT_FLAG_SYSTEM, manager_modulecheck);
ast_manager_register_xml_core("AOCMessage", EVENT_FLAG_AOC, action_aocmessage);
ast_manager_register_xml_core("Filter", EVENT_FLAG_SYSTEM, action_filter);
+ ast_manager_register_xml_core("BlindTransfer", EVENT_FLAG_CALL, action_blind_transfer);
#ifdef TEST_FRAMEWORK
stasis_subscribe(ast_test_suite_topic(), test_suite_event_cb, NULL);
@@ -8025,3 +8097,44 @@ struct ast_datastore *astman_datastore_find(struct mansession *s, const struct a
return datastore;
}
+
+static void manager_event_blob_dtor(void *obj)
+{
+ struct ast_manager_event_blob *ev = obj;
+ ast_string_field_free_memory(ev);
+}
+
+struct ast_manager_event_blob *
+__attribute__((format(printf, 3, 4)))
+ast_manager_event_blob_create(
+ int event_flags,
+ const char *manager_event,
+ const char *extra_fields_fmt,
+ ...)
+{
+ RAII_VAR(struct ast_manager_event_blob *, ev, NULL, ao2_cleanup);
+ va_list argp;
+
+ ast_assert(extra_fields_fmt != NULL);
+ ast_assert(manager_event != NULL);
+
+ ev = ao2_alloc(sizeof(*ev), manager_event_blob_dtor);
+ if (!ev) {
+ return NULL;
+ }
+
+ if (ast_string_field_init(ev, 20)) {
+ return NULL;
+ }
+
+ ev->manager_event = manager_event;
+ ev->event_flags = event_flags;
+
+ va_start(argp, extra_fields_fmt);
+ ast_string_field_ptr_build_va(ev, &ev->extra_fields, extra_fields_fmt,
+ argp);
+ va_end(argp);
+
+ ao2_ref(ev, +1);
+ return ev;
+}
diff --git a/main/manager_bridging.c b/main/manager_bridging.c
new file mode 100644
index 000000000..01ce68aaf
--- /dev/null
+++ b/main/manager_bridging.c
@@ -0,0 +1,470 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, 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 The Asterisk Management Interface - AMI (bridge event handling)
+ *
+ * \author Kinsey Moore <kmoore@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/stasis_bridging.h"
+#include "asterisk/stasis_channels.h"
+#include "asterisk/manager.h"
+#include "asterisk/stasis_message_router.h"
+
+/*! \brief Message router for cached bridge state snapshot updates */
+static struct stasis_message_router *bridge_state_router;
+
+/*** DOCUMENTATION
+ <managerEvent language="en_US" name="BridgeCreate">
+ <managerEventInstance class="EVENT_FLAG_CALL">
+ <synopsis>Raised when a bridge is created.</synopsis>
+ <syntax>
+ <parameter name="BridgeUniqueid">
+ </parameter>
+ <parameter name="BridgeType">
+ <para>The type of bridge</para>
+ </parameter>
+ </syntax>
+ </managerEventInstance>
+ </managerEvent>
+ <managerEvent language="en_US" name="BridgeDestroy">
+ <managerEventInstance class="EVENT_FLAG_CALL">
+ <synopsis>Raised when a bridge is destroyed.</synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" />
+ </syntax>
+ </managerEventInstance>
+ </managerEvent>
+ <managerEvent language="en_US" name="BridgeEnter">
+ <managerEventInstance class="EVENT_FLAG_CALL">
+ <synopsis>Raised when a channel enters a bridge.</synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" />
+ <parameter name="Uniqueid">
+ <para>The uniqueid of the channel entering the bridge</para>
+ </parameter>
+ </syntax>
+ </managerEventInstance>
+ </managerEvent>
+ <managerEvent language="en_US" name="BridgeLeave">
+ <managerEventInstance class="EVENT_FLAG_CALL">
+ <synopsis>Raised when a channel leaves a bridge.</synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" />
+ <parameter name="Uniqueid">
+ <para>The uniqueid of the channel leaving the bridge</para>
+ </parameter>
+ </syntax>
+ </managerEventInstance>
+ </managerEvent>
+ <manager name="BridgeList" language="en_US">
+ <synopsis>
+ Get a list of bridges in the system.
+ </synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+ <parameter name="BridgeType">
+ <para>Optional type for filtering the resulting list of bridges.</para>
+ </parameter>
+ </syntax>
+ <description>
+ <para>Returns a list of bridges, optionally filtering on a bridge type.</para>
+ </description>
+ </manager>
+ <manager name="BridgeInfo" language="en_US">
+ <synopsis>
+ Get information about a bridge.
+ </synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+ <parameter name="BridgeUniqueid" required="true">
+ <para>The unique ID of the bridge about which to retreive information.</para>
+ </parameter>
+ </syntax>
+ <description>
+ <para>Returns detailed information about a bridge and the channels in it.</para>
+ </description>
+ </manager>
+ ***/
+
+struct ast_str *ast_manager_build_bridge_state_string(
+ const struct ast_bridge_snapshot *snapshot,
+ const char *suffix)
+{
+ struct ast_str *out = ast_str_create(128);
+ int res = 0;
+ if (!out) {
+ return NULL;
+ }
+ res = ast_str_set(&out, 0,
+ "BridgeUniqueid%s: %s\r\n"
+ "BridgeType%s: %s\r\n",
+ suffix, snapshot->uniqueid,
+ suffix, snapshot->technology);
+
+ if (!res) {
+ return NULL;
+ }
+
+ return out;
+}
+
+/*! \brief Typedef for callbacks that get called on channel snapshot updates */
+typedef struct ast_manager_event_blob *(*bridge_snapshot_monitor)(
+ struct ast_bridge_snapshot *old_snapshot,
+ struct ast_bridge_snapshot *new_snapshot);
+
+/*! \brief Handle bridge creation */
+static struct ast_manager_event_blob *bridge_create(
+ struct ast_bridge_snapshot *old_snapshot,
+ struct ast_bridge_snapshot *new_snapshot)
+{
+ if (!new_snapshot || old_snapshot) {
+ return NULL;
+ }
+
+ return ast_manager_event_blob_create(
+ EVENT_FLAG_CALL, "BridgeCreate", NO_EXTRA_FIELDS);
+}
+
+/*! \brief Handle bridge destruction */
+static struct ast_manager_event_blob *bridge_destroy(
+ struct ast_bridge_snapshot *old_snapshot,
+ struct ast_bridge_snapshot *new_snapshot)
+{
+ if (new_snapshot || !old_snapshot) {
+ return NULL;
+ }
+
+ return ast_manager_event_blob_create(
+ EVENT_FLAG_CALL, "BridgeDestroy", NO_EXTRA_FIELDS);
+}
+
+
+bridge_snapshot_monitor bridge_monitors[] = {
+ bridge_create,
+ bridge_destroy,
+};
+
+static void bridge_snapshot_update(void *data, struct stasis_subscription *sub,
+ struct stasis_topic *topic,
+ struct stasis_message *message)
+{
+ RAII_VAR(struct ast_str *, bridge_event_string, NULL, ast_free);
+ struct stasis_cache_update *update;
+ struct ast_bridge_snapshot *old_snapshot;
+ struct ast_bridge_snapshot *new_snapshot;
+ size_t i;
+
+ update = stasis_message_data(message);
+
+ if (ast_bridge_snapshot_type() != update->type) {
+ return;
+ }
+
+ old_snapshot = stasis_message_data(update->old_snapshot);
+ new_snapshot = stasis_message_data(update->new_snapshot);
+
+ for (i = 0; i < ARRAY_LEN(bridge_monitors); ++i) {
+ RAII_VAR(struct ast_manager_event_blob *, event, NULL, ao2_cleanup);
+
+ event = bridge_monitors[i](old_snapshot, new_snapshot);
+ if (!event) {
+ continue;
+ }
+
+ /* If we haven't already, build the channel event string */
+ if (!bridge_event_string) {
+ bridge_event_string =
+ ast_manager_build_bridge_state_string(
+ new_snapshot ? new_snapshot : old_snapshot, "");
+ if (!bridge_event_string) {
+ return;
+ }
+ }
+
+ manager_event(event->event_flags, event->manager_event, "%s%s",
+ ast_str_buffer(bridge_event_string),
+ event->extra_fields);
+ }
+}
+
+static void bridge_merge_cb(void *data, struct stasis_subscription *sub,
+ struct stasis_topic *topic,
+ struct stasis_message *message)
+{
+ struct ast_bridge_merge_message *merge_msg = stasis_message_data(message);
+ RAII_VAR(struct ast_str *, to_text, NULL, ast_free);
+ RAII_VAR(struct ast_str *, from_text, NULL, ast_free);
+
+ ast_assert(merge_msg->to != NULL);
+ ast_assert(merge_msg->from != NULL);
+
+ to_text = ast_manager_build_bridge_state_string(merge_msg->to, "");
+ from_text = ast_manager_build_bridge_state_string(merge_msg->from, "From");
+
+ /*** DOCUMENTATION
+ <managerEventInstance>
+ <synopsis>Raised when two bridges are merged.</synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" />
+ <parameter name="BridgeUniqueidFrom">
+ <para>The uniqueid of the bridge being dissolved in the merge</para>
+ </parameter>
+ <parameter name="BridgeTypeFrom">
+ <para>The type of bridge that is being dissolved in the merge</para>
+ </parameter>
+ </syntax>
+ </managerEventInstance>
+ ***/
+ manager_event(EVENT_FLAG_CALL, "BridgeMerge",
+ "%s"
+ "%s",
+ ast_str_buffer(to_text),
+ ast_str_buffer(from_text));
+}
+
+static void channel_enter_cb(void *data, struct stasis_subscription *sub,
+ struct stasis_topic *topic,
+ struct stasis_message *message)
+{
+ struct ast_bridge_blob *blob = stasis_message_data(message);
+ RAII_VAR(struct ast_str *, bridge_text, NULL, ast_free);
+ RAII_VAR(struct ast_str *, channel_text, NULL, ast_free);
+
+ bridge_text = ast_manager_build_bridge_state_string(blob->bridge, "");
+ channel_text = ast_manager_build_channel_state_string(blob->channel);
+
+ manager_event(EVENT_FLAG_CALL, "BridgeEnter",
+ "%s"
+ "%s",
+ ast_str_buffer(bridge_text),
+ ast_str_buffer(channel_text));
+}
+
+static void channel_leave_cb(void *data, struct stasis_subscription *sub,
+ struct stasis_topic *topic,
+ struct stasis_message *message)
+{
+ struct ast_bridge_blob *blob = stasis_message_data(message);
+ RAII_VAR(struct ast_str *, bridge_text, NULL, ast_free);
+ RAII_VAR(struct ast_str *, channel_text, NULL, ast_free);
+
+ bridge_text = ast_manager_build_bridge_state_string(blob->bridge, "");
+ channel_text = ast_manager_build_channel_state_string(blob->channel);
+
+ manager_event(EVENT_FLAG_CALL, "BridgeLeave",
+ "%s"
+ "%s",
+ ast_str_buffer(bridge_text),
+ ast_str_buffer(channel_text));
+}
+
+static int filter_bridge_type_cb(void *obj, void *arg, int flags)
+{
+ char *bridge_type = arg;
+ struct ast_bridge_snapshot *snapshot = stasis_message_data(obj);
+ /* unlink all the snapshots that do not match the bridge type */
+ return strcmp(bridge_type, snapshot->technology) ? CMP_MATCH : 0;
+}
+
+static int send_bridge_list_item_cb(void *obj, void *arg, void *data, int flags)
+{
+ struct ast_bridge_snapshot *snapshot = stasis_message_data(obj);
+ struct mansession *s = arg;
+ char *id_text = data;
+ RAII_VAR(struct ast_str *, bridge_info, ast_manager_build_bridge_state_string(snapshot, ""), ast_free);
+
+ astman_append(s,
+ "Event: BridgeListItem\r\n"
+ "%s"
+ "%s"
+ "\r\n",
+ ast_str_buffer(bridge_info),
+ id_text);
+ return 0;
+}
+
+static int manager_bridges_list(struct mansession *s, const struct message *m)
+{
+ const char *id = astman_get_header(m, "ActionID");
+ const char *type_filter = astman_get_header(m, "BridgeType");
+ RAII_VAR(struct ast_str *, id_text, ast_str_create(128), ast_free);
+ RAII_VAR(struct ao2_container *, bridges, NULL, ao2_cleanup);
+
+ if (!id_text) {
+ astman_send_error(s, m, "Internal error");
+ return -1;
+ }
+
+ if (!ast_strlen_zero(id)) {
+ ast_str_set(&id_text, 0, "ActionID: %s\r\n", id);
+ }
+
+ bridges = stasis_cache_dump(ast_bridge_topic_all_cached(), ast_bridge_snapshot_type());
+ if (!bridges) {
+ astman_send_error(s, m, "Internal error");
+ return -1;
+ }
+
+ astman_send_ack(s, m, "Bridge listing will follow");
+
+ if (!ast_strlen_zero(type_filter)) {
+ char *type_filter_dup = ast_strdupa(type_filter);
+ ao2_callback(bridges, OBJ_MULTIPLE | OBJ_NODATA | OBJ_UNLINK, filter_bridge_type_cb, type_filter_dup);
+ }
+
+ ao2_callback_data(bridges, OBJ_NODATA, send_bridge_list_item_cb, s, ast_str_buffer(id_text));
+
+ astman_append(s,
+ "Event: BridgeListComplete\r\n"
+ "%s"
+ "\r\n",
+ ast_str_buffer(id_text));
+
+ return 0;
+}
+
+static int send_bridge_info_item_cb(void *obj, void *arg, void *data, int flags)
+{
+ char *uniqueid = obj;
+ struct mansession *s = arg;
+ char *id_text = data;
+
+ astman_append(s,
+ "Event: BridgeInfoChannel\r\n"
+ "Uniqueid: %s\r\n"
+ "%s"
+ "\r\n",
+ uniqueid,
+ id_text);
+ return 0;
+}
+
+static int manager_bridge_info(struct mansession *s, const struct message *m)
+{
+ const char *id = astman_get_header(m, "ActionID");
+ const char *bridge_uniqueid = astman_get_header(m, "BridgeUniqueid");
+ RAII_VAR(struct ast_str *, id_text, ast_str_create(128), ast_free);
+ RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_str *, bridge_info, NULL, ast_free);
+ struct ast_bridge_snapshot *snapshot;
+
+ if (!id_text) {
+ astman_send_error(s, m, "Internal error");
+ return -1;
+ }
+
+ if (ast_strlen_zero(bridge_uniqueid)) {
+ astman_send_error(s, m, "BridgeUniqueid must be provided");
+ return -1;
+ }
+
+ if (!ast_strlen_zero(id)) {
+ ast_str_set(&id_text, 0, "ActionID: %s\r\n", id);
+ }
+
+ msg = stasis_cache_get(ast_bridge_topic_all_cached(), ast_bridge_snapshot_type(), bridge_uniqueid);
+ if (!msg) {
+ astman_send_error(s, m, "Specified BridgeUniqueid not found");
+ return -1;
+ }
+
+ astman_send_ack(s, m, "Bridge channel listing will follow");
+
+ snapshot = stasis_message_data(msg);
+ bridge_info = ast_manager_build_bridge_state_string(snapshot, "");
+
+ ao2_callback_data(snapshot->channels, OBJ_NODATA, send_bridge_info_item_cb, s, ast_str_buffer(id_text));
+
+ astman_append(s,
+ "Event: BridgeInfoComplete\r\n"
+ "%s"
+ "%s"
+ "\r\n",
+ ast_str_buffer(bridge_info),
+ ast_str_buffer(id_text));
+
+ return 0;
+}
+
+static void manager_bridging_shutdown(void)
+{
+ stasis_message_router_unsubscribe(bridge_state_router);
+ bridge_state_router = NULL;
+ ast_manager_unregister("BridgeList");
+ ast_manager_unregister("BridgeInfo");
+}
+
+int manager_bridging_init(void)
+{
+ int ret = 0;
+
+ if (bridge_state_router) {
+ /* Already initialized */
+ return 0;
+ }
+
+ ast_register_atexit(manager_bridging_shutdown);
+
+ bridge_state_router = stasis_message_router_create(
+ stasis_caching_get_topic(ast_bridge_topic_all_cached()));
+
+ if (!bridge_state_router) {
+ return -1;
+ }
+
+ ret |= stasis_message_router_add(bridge_state_router,
+ stasis_cache_update_type(),
+ bridge_snapshot_update,
+ NULL);
+
+ ret |= stasis_message_router_add(bridge_state_router,
+ ast_bridge_merge_message_type(),
+ bridge_merge_cb,
+ NULL);
+
+ ret |= stasis_message_router_add(bridge_state_router,
+ ast_channel_entered_bridge_type(),
+ channel_enter_cb,
+ NULL);
+
+ ret |= stasis_message_router_add(bridge_state_router,
+ ast_channel_left_bridge_type(),
+ channel_leave_cb,
+ NULL);
+
+ ret |= ast_manager_register_xml_core("BridgeList", 0, manager_bridges_list);
+ ret |= ast_manager_register_xml_core("BridgeInfo", 0, manager_bridge_info);
+
+ /* If somehow we failed to add any routes, just shut down the whole
+ * thing and fail it.
+ */
+ if (ret) {
+ manager_bridging_shutdown();
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/main/manager_channels.c b/main/manager_channels.c
index 0cab36562..fb579dd95 100644
--- a/main/manager_channels.c
+++ b/main/manager_channels.c
@@ -284,83 +284,18 @@ struct ast_str *ast_manager_build_channel_state_string_suffix(
}
struct ast_str *ast_manager_build_channel_state_string(
- const struct ast_channel_snapshot *snapshot)
+ const struct ast_channel_snapshot *snapshot)
{
return ast_manager_build_channel_state_string_suffix(snapshot, "");
}
-/*! \brief Struct containing info for an AMI channel event to send out. */
-struct snapshot_manager_event {
- /*! event_flags manager_event() flags parameter. */
- int event_flags;
- /*! manager_event manager_event() category. */
- const char *manager_event;
- AST_DECLARE_STRING_FIELDS(
- /* extra fields to include in the event. */
- AST_STRING_FIELD(extra_fields);
- );
-};
-
-static void snapshot_manager_event_dtor(void *obj)
-{
- struct snapshot_manager_event *ev = obj;
- ast_string_field_free_memory(ev);
-}
-
-/*!
- * \brief Construct a \ref snapshot_manager_event.
- * \param event_flags manager_event() flags parameter.
- * \param manager_event manager_event() category.
- * \param extra_fields_fmt Format string for extra fields to include.
- * Or NO_EXTRA_FIELDS for no extra fields.
- * \return New \ref snapshot_manager_event object.
- * \return \c NULL on error.
- */
-static struct snapshot_manager_event *
-__attribute__((format(printf, 3, 4)))
-snapshot_manager_event_create(
- int event_flags,
- const char *manager_event,
- const char *extra_fields_fmt,
- ...)
-{
- RAII_VAR(struct snapshot_manager_event *, ev, NULL, ao2_cleanup);
- va_list argp;
-
- ast_assert(extra_fields_fmt != NULL);
- ast_assert(manager_event != NULL);
-
- ev = ao2_alloc(sizeof(*ev), snapshot_manager_event_dtor);
- if (!ev) {
- return NULL;
- }
-
- if (ast_string_field_init(ev, 20)) {
- return NULL;
- }
-
- ev->manager_event = manager_event;
- ev->event_flags = event_flags;
-
- va_start(argp, extra_fields_fmt);
- ast_string_field_ptr_build_va(ev, &ev->extra_fields, extra_fields_fmt,
- argp);
- va_end(argp);
-
- ao2_ref(ev, +1);
- return ev;
-}
-
-/*! GCC warns about blank or NULL format strings. So, shenanigans! */
-#define NO_EXTRA_FIELDS "%s", ""
-
/*! \brief Typedef for callbacks that get called on channel snapshot updates */
-typedef struct snapshot_manager_event *(*snapshot_monitor)(
+typedef struct ast_manager_event_blob *(*channel_snapshot_monitor)(
struct ast_channel_snapshot *old_snapshot,
struct ast_channel_snapshot *new_snapshot);
/*! \brief Handle channel state changes */
-static struct snapshot_manager_event *channel_state_change(
+static struct ast_manager_event_blob *channel_state_change(
struct ast_channel_snapshot *old_snapshot,
struct ast_channel_snapshot *new_snapshot)
{
@@ -377,7 +312,7 @@ static struct snapshot_manager_event *channel_state_change(
*/
if (!old_snapshot) {
- return snapshot_manager_event_create(
+ return ast_manager_event_blob_create(
EVENT_FLAG_CALL, "Newchannel", NO_EXTRA_FIELDS);
}
@@ -385,7 +320,7 @@ static struct snapshot_manager_event *channel_state_change(
is_hungup = ast_test_flag(&new_snapshot->flags, AST_FLAG_ZOMBIE) ? 1 : 0;
if (!was_hungup && is_hungup) {
- return snapshot_manager_event_create(
+ return ast_manager_event_blob_create(
EVENT_FLAG_CALL, "Hangup",
"Cause: %d\r\n"
"Cause-txt: %s\r\n",
@@ -394,7 +329,7 @@ static struct snapshot_manager_event *channel_state_change(
}
if (old_snapshot->state != new_snapshot->state) {
- return snapshot_manager_event_create(
+ return ast_manager_event_blob_create(
EVENT_FLAG_CALL, "Newstate", NO_EXTRA_FIELDS);
}
@@ -402,7 +337,7 @@ static struct snapshot_manager_event *channel_state_change(
return NULL;
}
-static struct snapshot_manager_event *channel_newexten(
+static struct ast_manager_event_blob *channel_newexten(
struct ast_channel_snapshot *old_snapshot,
struct ast_channel_snapshot *new_snapshot)
{
@@ -421,7 +356,7 @@ static struct snapshot_manager_event *channel_newexten(
}
/* DEPRECATED: Extension field deprecated in 12; remove in 14 */
- return snapshot_manager_event_create(
+ return ast_manager_event_blob_create(
EVENT_FLAG_CALL, "Newexten",
"Extension: %s\r\n"
"Application: %s\r\n"
@@ -431,7 +366,7 @@ static struct snapshot_manager_event *channel_newexten(
new_snapshot->data);
}
-static struct snapshot_manager_event *channel_new_callerid(
+static struct ast_manager_event_blob *channel_new_callerid(
struct ast_channel_snapshot *old_snapshot,
struct ast_channel_snapshot *new_snapshot)
{
@@ -444,14 +379,14 @@ static struct snapshot_manager_event *channel_new_callerid(
return NULL;
}
- return snapshot_manager_event_create(
+ return ast_manager_event_blob_create(
EVENT_FLAG_CALL, "NewCallerid",
"CID-CallingPres: %d (%s)\r\n",
new_snapshot->caller_pres,
ast_describe_caller_presentation(new_snapshot->caller_pres));
}
-snapshot_monitor monitors[] = {
+channel_snapshot_monitor channel_monitors[] = {
channel_state_change,
channel_newexten,
channel_new_callerid
@@ -476,9 +411,9 @@ static void channel_snapshot_update(void *data, struct stasis_subscription *sub,
old_snapshot = stasis_message_data(update->old_snapshot);
new_snapshot = stasis_message_data(update->new_snapshot);
- for (i = 0; i < ARRAY_LEN(monitors); ++i) {
- RAII_VAR(struct snapshot_manager_event *, ev, NULL, ao2_cleanup);
- ev = monitors[i](old_snapshot, new_snapshot);
+ for (i = 0; i < ARRAY_LEN(channel_monitors); ++i) {
+ RAII_VAR(struct ast_manager_event_blob *, ev, NULL, ao2_cleanup);
+ ev = channel_monitors[i](old_snapshot, new_snapshot);
if (!ev) {
continue;
diff --git a/main/parking.c b/main/parking.c
new file mode 100644
index 000000000..2a5b72e61
--- /dev/null
+++ b/main/parking.c
@@ -0,0 +1,191 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Jonathan Rose <jrose@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 Parking Core
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/_private.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/pbx.h"
+#include "asterisk/bridging.h"
+#include "asterisk/parking.h"
+#include "asterisk/channel.h"
+
+/*! \brief Message type for parked calls */
+static struct stasis_message_type *parked_call_type;
+
+/*! \brief Topic for parking lots */
+static struct stasis_topic *parking_topic;
+
+/*! \brief Function Callback for handling blind transfers to park applications */
+static ast_park_blind_xfer_fn ast_park_blind_xfer_func = NULL;
+
+/*! \brief Function Callback for handling a bridge channel trying to park itself */
+static ast_bridge_channel_park_fn ast_bridge_channel_park_func = NULL;
+
+void ast_parking_stasis_init(void)
+{
+ parked_call_type = stasis_message_type_create("ast_parked_call");
+ parking_topic = stasis_topic_create("ast_parking");
+}
+
+void ast_parking_stasis_disable(void)
+{
+ ao2_cleanup(parked_call_type);
+ ao2_cleanup(parking_topic);
+ parked_call_type = NULL;
+ parking_topic = NULL;
+}
+
+struct stasis_topic *ast_parking_topic(void)
+{
+ return parking_topic;
+}
+
+struct stasis_message_type *ast_parked_call_type(void)
+{
+ return parked_call_type;
+}
+
+/*! \brief Destructor for parked_call_payload objects */
+static void parked_call_payload_destructor(void *obj)
+{
+ struct ast_parked_call_payload *park_obj = obj;
+
+ ao2_cleanup(park_obj->parkee);
+ ao2_cleanup(park_obj->parker);
+ ast_string_field_free_memory(park_obj);
+}
+
+struct ast_parked_call_payload *ast_parked_call_payload_create(enum ast_parked_call_event_type event_type,
+ struct ast_channel_snapshot *parkee_snapshot, struct ast_channel_snapshot *parker_snapshot,
+ struct ast_channel_snapshot *retriever_snapshot, const char *parkinglot,
+ unsigned int parkingspace, unsigned long int timeout,
+ unsigned long int duration)
+{
+ RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
+
+ payload = ao2_alloc(sizeof(*payload), parked_call_payload_destructor);
+ if (!payload) {
+ return NULL;
+ }
+
+ if (ast_string_field_init(payload, 32)) {
+ return NULL;
+ }
+
+ payload->event_type = event_type;
+
+ ao2_ref(parkee_snapshot, +1);
+ payload->parkee = parkee_snapshot;
+
+ if (parker_snapshot) {
+ ao2_ref(parker_snapshot, +1);
+ payload->parker = parker_snapshot;
+ }
+
+ if (retriever_snapshot) {
+ ao2_ref(retriever_snapshot, +1);
+ payload->retriever = retriever_snapshot;
+ }
+
+ if (parkinglot) {
+ ast_string_field_set(payload, parkinglot, parkinglot);
+ }
+
+ payload->parkingspace = parkingspace;
+ payload->timeout = timeout;
+ payload->duration = duration;
+
+ /* Bump the ref count by one since RAII_VAR is going to eat one when we leave. */
+ ao2_ref(payload, +1);
+ return payload;
+}
+
+void ast_install_park_blind_xfer_func(ast_park_blind_xfer_fn park_blind_xfer_func)
+{
+ ast_park_blind_xfer_func = park_blind_xfer_func;
+}
+
+void ast_install_bridge_channel_park_func(ast_bridge_channel_park_fn bridge_channel_park_func)
+{
+ ast_bridge_channel_park_func = bridge_channel_park_func;
+}
+
+void ast_uninstall_park_blind_xfer_func(void)
+{
+ ast_park_blind_xfer_func = NULL;
+}
+
+void ast_uninstall_bridge_channel_park_func(void)
+{
+ ast_bridge_channel_park_func = NULL;
+}
+
+int ast_park_blind_xfer(struct ast_bridge *bridge, struct ast_bridge_channel *parker,
+ struct ast_exten *park_exten)
+{
+ static int warned = 0;
+ if (ast_park_blind_xfer_func) {
+ return ast_park_blind_xfer_func(bridge, parker, park_exten);
+ }
+
+ if (warned++ % 10 == 0) {
+ ast_verb(3, "%s attempted to blind transfer to a parking extension, but no parking blind transfer function is loaded.\n",
+ ast_channel_name(parker->chan));
+ }
+
+ return -1;
+}
+
+struct ast_exten *ast_get_parking_exten(const char *exten_str, struct ast_channel *chan, const char *context)
+{
+ struct ast_exten *exten;
+ struct pbx_find_info q = { .stacklen = 0 }; /* the rest is reset in pbx_find_extension */
+ const char *app_at_exten;
+
+ ast_debug(4, "Checking if %s@%s is a parking exten\n", exten_str, context);
+ exten = pbx_find_extension(chan, NULL, &q, context, exten_str, 1, NULL, NULL,
+ E_MATCH);
+ if (!exten) {
+ return NULL;
+ }
+
+ app_at_exten = ast_get_extension_app(exten);
+ if (!app_at_exten || strcasecmp(PARK_APPLICATION, app_at_exten)) {
+ return NULL;
+ }
+
+ return exten;
+}
+
+void ast_bridge_channel_park(struct ast_bridge_channel *bridge_channel, const char *parkee_uuid, const char *parker_uuid, const char *app_data)
+{
+ /* Run installable function */
+ if (ast_bridge_channel_park_func) {
+ return ast_bridge_channel_park_func(bridge_channel, parkee_uuid, parker_uuid, app_data);
+ }
+}
diff --git a/main/pbx.c b/main/pbx.c
index 6359c056c..8408048f2 100644
--- a/main/pbx.c
+++ b/main/pbx.c
@@ -9395,8 +9395,12 @@ int ast_async_goto(struct ast_channel *chan, const char *context, const char *ex
} tmpvars = { 0, };
ast_channel_lock(chan);
- if (ast_channel_pbx(chan)) { /* This channel is currently in the PBX */
- ast_explicit_goto(chan, context, exten, priority + 1);
+ /* Channels in a bridge or running a PBX can be sent directly to the specified destination */
+ if (ast_channel_is_bridged(chan) || ast_channel_pbx(chan)) {
+ if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP)) {
+ priority += 1;
+ }
+ ast_explicit_goto(chan, context, exten, priority);
ast_softhangup_nolock(chan, AST_SOFTHANGUP_ASYNCGOTO);
ast_channel_unlock(chan);
return res;
@@ -9441,8 +9445,7 @@ int ast_async_goto(struct ast_channel *chan, const char *context, const char *ex
/* Masquerade into tmp channel */
if (ast_channel_masquerade(tmpchan, chan)) {
- /* Failed to set up the masquerade. It's probably chan_local
- * in the middle of optimizing itself out. Sad. :( */
+ /* Failed to set up the masquerade. */
ast_hangup(tmpchan);
tmpchan = NULL;
res = -1;
diff --git a/main/rtp_engine.c b/main/rtp_engine.c
index b25cb95b3..8e58f658d 100644
--- a/main/rtp_engine.c
+++ b/main/rtp_engine.c
@@ -928,497 +928,6 @@ struct ast_rtp_glue *ast_rtp_instance_get_glue(const char *type)
return glue;
}
-static enum ast_bridge_result local_bridge_loop(struct ast_channel *c0, struct ast_channel *c1, struct ast_rtp_instance *instance0, struct ast_rtp_instance *instance1, int timeoutms, int flags, struct ast_frame **fo, struct ast_channel **rc, void *pvt0, void *pvt1)
-{
- enum ast_bridge_result res = AST_BRIDGE_FAILED;
- struct ast_channel *who = NULL, *other = NULL, *cs[3] = { NULL, };
- struct ast_frame *fr = NULL;
- struct timeval start;
-
- /* Start locally bridging both instances */
- if (instance0->engine->local_bridge && instance0->engine->local_bridge(instance0, instance1)) {
- ast_debug(1, "Failed to locally bridge %s to %s, backing out.\n", ast_channel_name(c0), ast_channel_name(c1));
- ast_channel_unlock(c0);
- ast_channel_unlock(c1);
- return AST_BRIDGE_FAILED_NOWARN;
- }
- if (instance1->engine->local_bridge && instance1->engine->local_bridge(instance1, instance0)) {
- ast_debug(1, "Failed to locally bridge %s to %s, backing out.\n", ast_channel_name(c1), ast_channel_name(c0));
- if (instance0->engine->local_bridge) {
- instance0->engine->local_bridge(instance0, NULL);
- }
- ast_channel_unlock(c0);
- ast_channel_unlock(c1);
- return AST_BRIDGE_FAILED_NOWARN;
- }
-
- ast_channel_unlock(c0);
- ast_channel_unlock(c1);
-
- instance0->bridged = instance1;
- instance1->bridged = instance0;
-
- ast_poll_channel_add(c0, c1);
-
- /* Hop into a loop waiting for a frame from either channel */
- cs[0] = c0;
- cs[1] = c1;
- cs[2] = NULL;
- start = ast_tvnow();
- for (;;) {
- int ms;
- /* If the underlying formats have changed force this bridge to break */
- if ((ast_format_cmp(ast_channel_rawreadformat(c0), ast_channel_rawwriteformat(c1)) == AST_FORMAT_CMP_NOT_EQUAL) ||
- (ast_format_cmp(ast_channel_rawreadformat(c1), ast_channel_rawwriteformat(c0)) == AST_FORMAT_CMP_NOT_EQUAL)) {
- ast_debug(1, "rtp-engine-local-bridge: Oooh, formats changed, backing out\n");
- res = AST_BRIDGE_FAILED_NOWARN;
- break;
- }
- /* Check if anything changed */
- if ((ast_channel_tech_pvt(c0) != pvt0) ||
- (ast_channel_tech_pvt(c1) != pvt1) ||
- (ast_channel_masq(c0) || ast_channel_masqr(c0) || ast_channel_masq(c1) || ast_channel_masqr(c1)) ||
- (ast_channel_monitor(c0) || ast_channel_audiohooks(c0) || ast_channel_monitor(c1) || ast_channel_audiohooks(c1)) ||
- (!ast_framehook_list_is_empty(ast_channel_framehooks(c0)) || !ast_framehook_list_is_empty(ast_channel_framehooks(c1)))) {
- ast_debug(1, "rtp-engine-local-bridge: Oooh, something is weird, backing out\n");
- /* If a masquerade needs to happen we have to try to read in a frame so that it actually happens. Without this we risk being called again and going into a loop */
- if ((ast_channel_masq(c0) || ast_channel_masqr(c0)) && (fr = ast_read(c0))) {
- ast_frfree(fr);
- }
- if ((ast_channel_masq(c1) || ast_channel_masqr(c1)) && (fr = ast_read(c1))) {
- ast_frfree(fr);
- }
- res = AST_BRIDGE_RETRY;
- break;
- }
- /* Wait on a channel to feed us a frame */
- ms = ast_remaining_ms(start, timeoutms);
- if (!(who = ast_waitfor_n(cs, 2, &ms))) {
- if (!ms) {
- res = AST_BRIDGE_RETRY;
- break;
- }
- ast_debug(2, "rtp-engine-local-bridge: Ooh, empty read...\n");
- if (ast_check_hangup(c0) || ast_check_hangup(c1)) {
- break;
- }
- continue;
- }
- /* Read in frame from channel */
- fr = ast_read(who);
- other = (who == c0) ? c1 : c0;
- /* Depending on the frame we may need to break out of our bridge */
- if (!fr || ((fr->frametype == AST_FRAME_DTMF_BEGIN || fr->frametype == AST_FRAME_DTMF_END) &&
- ((who == c0) && (flags & AST_BRIDGE_DTMF_CHANNEL_0)) |
- ((who == c1) && (flags & AST_BRIDGE_DTMF_CHANNEL_1)))) {
- /* Record received frame and who */
- *fo = fr;
- *rc = who;
- ast_debug(1, "rtp-engine-local-bridge: Ooh, got a %s\n", fr ? "digit" : "hangup");
- res = AST_BRIDGE_COMPLETE;
- break;
- } else if ((fr->frametype == AST_FRAME_CONTROL) && !(flags & AST_BRIDGE_IGNORE_SIGS)) {
- if ((fr->subclass.integer == AST_CONTROL_HOLD) ||
- (fr->subclass.integer == AST_CONTROL_UNHOLD) ||
- (fr->subclass.integer == AST_CONTROL_VIDUPDATE) ||
- (fr->subclass.integer == AST_CONTROL_SRCUPDATE) ||
- (fr->subclass.integer == AST_CONTROL_T38_PARAMETERS) ||
- (fr->subclass.integer == AST_CONTROL_UPDATE_RTP_PEER)) {
- /* If we are going on hold, then break callback mode and P2P bridging */
- if (fr->subclass.integer == AST_CONTROL_HOLD) {
- if (instance0->engine->local_bridge) {
- instance0->engine->local_bridge(instance0, NULL);
- }
- if (instance1->engine->local_bridge) {
- instance1->engine->local_bridge(instance1, NULL);
- }
- instance0->bridged = NULL;
- instance1->bridged = NULL;
- } else if (fr->subclass.integer == AST_CONTROL_UNHOLD) {
- if (instance0->engine->local_bridge) {
- instance0->engine->local_bridge(instance0, instance1);
- }
- if (instance1->engine->local_bridge) {
- instance1->engine->local_bridge(instance1, instance0);
- }
- instance0->bridged = instance1;
- instance1->bridged = instance0;
- }
- /* Since UPDATE_BRIDGE_PEER is only used by the bridging code, don't forward it */
- if (fr->subclass.integer != AST_CONTROL_UPDATE_RTP_PEER) {
- ast_indicate_data(other, fr->subclass.integer, fr->data.ptr, fr->datalen);
- }
- ast_frfree(fr);
- } else if (fr->subclass.integer == AST_CONTROL_CONNECTED_LINE) {
- if (ast_channel_connected_line_sub(who, other, fr, 1) &&
- ast_channel_connected_line_macro(who, other, fr, other == c0, 1)) {
- ast_indicate_data(other, fr->subclass.integer, fr->data.ptr, fr->datalen);
- }
- ast_frfree(fr);
- } else if (fr->subclass.integer == AST_CONTROL_REDIRECTING) {
- if (ast_channel_redirecting_sub(who, other, fr, 1) &&
- ast_channel_redirecting_macro(who, other, fr, other == c0, 1)) {
- ast_indicate_data(other, fr->subclass.integer, fr->data.ptr, fr->datalen);
- }
- ast_frfree(fr);
- } else if (fr->subclass.integer == AST_CONTROL_PVT_CAUSE_CODE) {
- ast_channel_hangupcause_hash_set(other, fr->data.ptr, fr->datalen);
- ast_frfree(fr);
- } else {
- *fo = fr;
- *rc = who;
- ast_debug(1, "rtp-engine-local-bridge: Got a FRAME_CONTROL (%d) frame on channel %s\n", fr->subclass.integer, ast_channel_name(who));
- res = AST_BRIDGE_COMPLETE;
- break;
- }
- } else {
- if ((fr->frametype == AST_FRAME_DTMF_BEGIN) ||
- (fr->frametype == AST_FRAME_DTMF_END) ||
- (fr->frametype == AST_FRAME_VOICE) ||
- (fr->frametype == AST_FRAME_VIDEO) ||
- (fr->frametype == AST_FRAME_IMAGE) ||
- (fr->frametype == AST_FRAME_HTML) ||
- (fr->frametype == AST_FRAME_MODEM) ||
- (fr->frametype == AST_FRAME_TEXT)) {
- ast_write(other, fr);
- }
-
- ast_frfree(fr);
- }
- /* Swap priority */
- cs[2] = cs[0];
- cs[0] = cs[1];
- cs[1] = cs[2];
- }
-
- /* Stop locally bridging both instances */
- if (instance0->engine->local_bridge) {
- instance0->engine->local_bridge(instance0, NULL);
- }
- if (instance1->engine->local_bridge) {
- instance1->engine->local_bridge(instance1, NULL);
- }
-
- instance0->bridged = NULL;
- instance1->bridged = NULL;
-
- ast_poll_channel_del(c0, c1);
-
- return res;
-}
-
-static enum ast_bridge_result remote_bridge_loop(struct ast_channel *c0,
- struct ast_channel *c1,
- struct ast_rtp_instance *instance0,
- struct ast_rtp_instance *instance1,
- struct ast_rtp_instance *vinstance0,
- struct ast_rtp_instance *vinstance1,
- struct ast_rtp_instance *tinstance0,
- struct ast_rtp_instance *tinstance1,
- struct ast_rtp_glue *glue0,
- struct ast_rtp_glue *glue1,
- struct ast_format_cap *cap0,
- struct ast_format_cap *cap1,
- int timeoutms,
- int flags,
- struct ast_frame **fo,
- struct ast_channel **rc,
- void *pvt0,
- void *pvt1)
-{
- enum ast_bridge_result res = AST_BRIDGE_FAILED;
- struct ast_channel *who = NULL, *other = NULL, *cs[3] = { NULL, };
- struct ast_format_cap *oldcap0 = ast_format_cap_dup(cap0);
- struct ast_format_cap *oldcap1 = ast_format_cap_dup(cap1);
- struct ast_sockaddr ac1 = {{0,}}, vac1 = {{0,}}, tac1 = {{0,}}, ac0 = {{0,}}, vac0 = {{0,}}, tac0 = {{0,}};
- struct ast_sockaddr t1 = {{0,}}, vt1 = {{0,}}, tt1 = {{0,}}, t0 = {{0,}}, vt0 = {{0,}}, tt0 = {{0,}};
- struct ast_frame *fr = NULL;
- struct timeval start;
-
- if (!oldcap0 || !oldcap1) {
- ast_channel_unlock(c0);
- ast_channel_unlock(c1);
- goto remote_bridge_cleanup;
- }
- /* Test the first channel */
- if (!(glue0->update_peer(c0, instance1, vinstance1, tinstance1, cap1, 0))) {
- ast_rtp_instance_get_remote_address(instance1, &ac1);
- if (vinstance1) {
- ast_rtp_instance_get_remote_address(vinstance1, &vac1);
- }
- if (tinstance1) {
- ast_rtp_instance_get_remote_address(tinstance1, &tac1);
- }
- } else {
- ast_log(LOG_WARNING, "Channel '%s' failed to talk to '%s'\n", ast_channel_name(c0), ast_channel_name(c1));
- }
-
- /* Test the second channel */
- if (!(glue1->update_peer(c1, instance0, vinstance0, tinstance0, cap0, 0))) {
- ast_rtp_instance_get_remote_address(instance0, &ac0);
- if (vinstance0) {
- ast_rtp_instance_get_remote_address(instance0, &vac0);
- }
- if (tinstance0) {
- ast_rtp_instance_get_remote_address(instance0, &tac0);
- }
- } else {
- ast_log(LOG_WARNING, "Channel '%s' failed to talk to '%s'\n", ast_channel_name(c1), ast_channel_name(c0));
- }
-
- ast_channel_unlock(c0);
- ast_channel_unlock(c1);
-
- instance0->bridged = instance1;
- instance1->bridged = instance0;
-
- ast_poll_channel_add(c0, c1);
-
- /* Go into a loop handling any stray frames that may come in */
- cs[0] = c0;
- cs[1] = c1;
- cs[2] = NULL;
- start = ast_tvnow();
- for (;;) {
- int ms;
- /* Check if anything changed */
- if ((ast_channel_tech_pvt(c0) != pvt0) ||
- (ast_channel_tech_pvt(c1) != pvt1) ||
- (ast_channel_masq(c0) || ast_channel_masqr(c0) || ast_channel_masq(c1) || ast_channel_masqr(c1)) ||
- (ast_channel_monitor(c0) || ast_channel_audiohooks(c0) || ast_channel_monitor(c1) || ast_channel_audiohooks(c1)) ||
- (!ast_framehook_list_is_empty(ast_channel_framehooks(c0)) || !ast_framehook_list_is_empty(ast_channel_framehooks(c1)))) {
- ast_debug(1, "Oooh, something is weird, backing out\n");
- res = AST_BRIDGE_RETRY;
- break;
- }
-
- /* Check if they have changed their address */
- ast_rtp_instance_get_remote_address(instance1, &t1);
- if (vinstance1) {
- ast_rtp_instance_get_remote_address(vinstance1, &vt1);
- }
- if (tinstance1) {
- ast_rtp_instance_get_remote_address(tinstance1, &tt1);
- }
- if (glue1->get_codec) {
- ast_format_cap_remove_all(cap1);
- glue1->get_codec(c1, cap1);
- }
-
- ast_rtp_instance_get_remote_address(instance0, &t0);
- if (vinstance0) {
- ast_rtp_instance_get_remote_address(vinstance0, &vt0);
- }
- if (tinstance0) {
- ast_rtp_instance_get_remote_address(tinstance0, &tt0);
- }
- if (glue0->get_codec) {
- ast_format_cap_remove_all(cap0);
- glue0->get_codec(c0, cap0);
- }
-
- if ((ast_sockaddr_cmp(&t1, &ac1)) ||
- (vinstance1 && ast_sockaddr_cmp(&vt1, &vac1)) ||
- (tinstance1 && ast_sockaddr_cmp(&tt1, &tac1)) ||
- (!ast_format_cap_identical(cap1, oldcap1))) {
- char tmp_buf[512] = { 0, };
- ast_debug(1, "Oooh, '%s' changed end address to %s (format %s)\n",
- ast_channel_name(c1), ast_sockaddr_stringify(&t1),
- ast_getformatname_multiple(tmp_buf, sizeof(tmp_buf), cap1));
- ast_debug(1, "Oooh, '%s' changed end vaddress to %s (format %s)\n",
- ast_channel_name(c1), ast_sockaddr_stringify(&vt1),
- ast_getformatname_multiple(tmp_buf, sizeof(tmp_buf), cap1));
- ast_debug(1, "Oooh, '%s' changed end taddress to %s (format %s)\n",
- ast_channel_name(c1), ast_sockaddr_stringify(&tt1),
- ast_getformatname_multiple(tmp_buf, sizeof(tmp_buf), cap1));
- ast_debug(1, "Oooh, '%s' was %s/(format %s)\n",
- ast_channel_name(c1), ast_sockaddr_stringify(&ac1),
- ast_getformatname_multiple(tmp_buf, sizeof(tmp_buf), oldcap1));
- ast_debug(1, "Oooh, '%s' was %s/(format %s)\n",
- ast_channel_name(c1), ast_sockaddr_stringify(&vac1),
- ast_getformatname_multiple(tmp_buf, sizeof(tmp_buf), oldcap1));
- ast_debug(1, "Oooh, '%s' was %s/(format %s)\n",
- ast_channel_name(c1), ast_sockaddr_stringify(&tac1),
- ast_getformatname_multiple(tmp_buf, sizeof(tmp_buf), oldcap1));
- if (glue0->update_peer(c0,
- ast_sockaddr_isnull(&t1) ? NULL : instance1,
- ast_sockaddr_isnull(&vt1) ? NULL : vinstance1,
- ast_sockaddr_isnull(&tt1) ? NULL : tinstance1,
- cap1, 0)) {
- ast_log(LOG_WARNING, "Channel '%s' failed to update to '%s'\n", ast_channel_name(c0), ast_channel_name(c1));
- }
- ast_sockaddr_copy(&ac1, &t1);
- ast_sockaddr_copy(&vac1, &vt1);
- ast_sockaddr_copy(&tac1, &tt1);
- ast_format_cap_copy(oldcap1, cap1);
- }
- if ((ast_sockaddr_cmp(&t0, &ac0)) ||
- (vinstance0 && ast_sockaddr_cmp(&vt0, &vac0)) ||
- (tinstance0 && ast_sockaddr_cmp(&tt0, &tac0)) ||
- (!ast_format_cap_identical(cap0, oldcap0))) {
- char tmp_buf[512] = { 0, };
- ast_debug(1, "Oooh, '%s' changed end address to %s (format %s)\n",
- ast_channel_name(c0), ast_sockaddr_stringify(&t0),
- ast_getformatname_multiple(tmp_buf, sizeof(tmp_buf), cap0));
- ast_debug(1, "Oooh, '%s' was %s/(format %s)\n",
- ast_channel_name(c0), ast_sockaddr_stringify(&ac0),
- ast_getformatname_multiple(tmp_buf, sizeof(tmp_buf), oldcap0));
- if (glue1->update_peer(c1, t0.len ? instance0 : NULL,
- vt0.len ? vinstance0 : NULL,
- tt0.len ? tinstance0 : NULL,
- cap0, 0)) {
- ast_log(LOG_WARNING, "Channel '%s' failed to update to '%s'\n", ast_channel_name(c1), ast_channel_name(c0));
- }
- ast_sockaddr_copy(&ac0, &t0);
- ast_sockaddr_copy(&vac0, &vt0);
- ast_sockaddr_copy(&tac0, &tt0);
- ast_format_cap_copy(oldcap0, cap0);
- }
-
- ms = ast_remaining_ms(start, timeoutms);
- /* Wait for frame to come in on the channels */
- if (!(who = ast_waitfor_n(cs, 2, &ms))) {
- if (!ms) {
- res = AST_BRIDGE_RETRY;
- break;
- }
- ast_debug(1, "Ooh, empty read...\n");
- if (ast_check_hangup(c0) || ast_check_hangup(c1)) {
- break;
- }
- continue;
- }
- fr = ast_read(who);
- other = (who == c0) ? c1 : c0;
- if (!fr || ((fr->frametype == AST_FRAME_DTMF_BEGIN || fr->frametype == AST_FRAME_DTMF_END) &&
- (((who == c0) && (flags & AST_BRIDGE_DTMF_CHANNEL_0)) ||
- ((who == c1) && (flags & AST_BRIDGE_DTMF_CHANNEL_1))))) {
- /* Break out of bridge */
- *fo = fr;
- *rc = who;
- ast_debug(1, "Oooh, got a %s\n", fr ? "digit" : "hangup");
- res = AST_BRIDGE_COMPLETE;
- break;
- } else if ((fr->frametype == AST_FRAME_CONTROL) && !(flags & AST_BRIDGE_IGNORE_SIGS)) {
- if ((fr->subclass.integer == AST_CONTROL_HOLD) ||
- (fr->subclass.integer == AST_CONTROL_UNHOLD) ||
- (fr->subclass.integer == AST_CONTROL_VIDUPDATE) ||
- (fr->subclass.integer == AST_CONTROL_SRCUPDATE) ||
- (fr->subclass.integer == AST_CONTROL_T38_PARAMETERS) ||
- (fr->subclass.integer == AST_CONTROL_UPDATE_RTP_PEER)) {
- if (fr->subclass.integer == AST_CONTROL_HOLD) {
- /* If we someone went on hold we want the other side to reinvite back to us */
- if (who == c0) {
- glue1->update_peer(c1, NULL, NULL, NULL, 0, 0);
- } else {
- glue0->update_peer(c0, NULL, NULL, NULL, 0, 0);
- }
- } else if (fr->subclass.integer == AST_CONTROL_UNHOLD ||
- fr->subclass.integer == AST_CONTROL_UPDATE_RTP_PEER) {
- /* If they went off hold they should go back to being direct, or if we have
- * been told to force a peer update, go ahead and do it. */
- if (who == c0) {
- glue1->update_peer(c1, instance0, vinstance0, tinstance0, cap0, 0);
- } else {
- glue0->update_peer(c0, instance1, vinstance1, tinstance1, cap1, 0);
- }
- }
- /* Update local address information */
- ast_rtp_instance_get_remote_address(instance0, &t0);
- ast_sockaddr_copy(&ac0, &t0);
- ast_rtp_instance_get_remote_address(instance1, &t1);
- ast_sockaddr_copy(&ac1, &t1);
- /* Update codec information */
- if (glue0->get_codec && ast_channel_tech_pvt(c0)) {
- ast_format_cap_remove_all(cap0);
- ast_format_cap_remove_all(oldcap0);
- glue0->get_codec(c0, cap0);
- ast_format_cap_append(oldcap0, cap0);
-
- }
- if (glue1->get_codec && ast_channel_tech_pvt(c1)) {
- ast_format_cap_remove_all(cap1);
- ast_format_cap_remove_all(oldcap1);
- glue1->get_codec(c1, cap1);
- ast_format_cap_append(oldcap1, cap1);
- }
- /* Since UPDATE_BRIDGE_PEER is only used by the bridging code, don't forward it */
- if (fr->subclass.integer != AST_CONTROL_UPDATE_RTP_PEER) {
- ast_indicate_data(other, fr->subclass.integer, fr->data.ptr, fr->datalen);
- }
- ast_frfree(fr);
- } else if (fr->subclass.integer == AST_CONTROL_CONNECTED_LINE) {
- if (ast_channel_connected_line_sub(who, other, fr, 1) &&
- ast_channel_connected_line_macro(who, other, fr, other == c0, 1)) {
- ast_indicate_data(other, fr->subclass.integer, fr->data.ptr, fr->datalen);
- }
- ast_frfree(fr);
- } else if (fr->subclass.integer == AST_CONTROL_REDIRECTING) {
- if (ast_channel_redirecting_sub(who, other, fr, 1) &&
- ast_channel_redirecting_macro(who, other, fr, other == c0, 1)) {
- ast_indicate_data(other, fr->subclass.integer, fr->data.ptr, fr->datalen);
- }
- ast_frfree(fr);
- } else if (fr->subclass.integer == AST_CONTROL_PVT_CAUSE_CODE) {
- ast_channel_hangupcause_hash_set(other, fr->data.ptr, fr->datalen);
- ast_frfree(fr);
- } else {
- *fo = fr;
- *rc = who;
- ast_debug(1, "Got a FRAME_CONTROL (%d) frame on channel %s\n", fr->subclass.integer, ast_channel_name(who));
- res = AST_BRIDGE_COMPLETE;
- goto remote_bridge_cleanup;
- }
- } else {
- if ((fr->frametype == AST_FRAME_DTMF_BEGIN) ||
- (fr->frametype == AST_FRAME_DTMF_END) ||
- (fr->frametype == AST_FRAME_VOICE) ||
- (fr->frametype == AST_FRAME_VIDEO) ||
- (fr->frametype == AST_FRAME_IMAGE) ||
- (fr->frametype == AST_FRAME_HTML) ||
- (fr->frametype == AST_FRAME_MODEM) ||
- (fr->frametype == AST_FRAME_TEXT)) {
- ast_write(other, fr);
- }
- ast_frfree(fr);
- }
- /* Swap priority */
- cs[2] = cs[0];
- cs[0] = cs[1];
- cs[1] = cs[2];
- }
-
- if (ast_test_flag(ast_channel_flags(c0), AST_FLAG_ZOMBIE)) {
- ast_debug(1, "Channel '%s' Zombie cleardown from bridge\n", ast_channel_name(c0));
- } else if (ast_channel_tech_pvt(c0) != pvt0) {
- ast_debug(1, "Channel c0->'%s' pvt changed, in bridge with c1->'%s'\n", ast_channel_name(c0), ast_channel_name(c1));
- } else if (glue0 != ast_rtp_instance_get_glue(ast_channel_tech(c0)->type)) {
- ast_debug(1, "Channel c0->'%s' technology changed, in bridge with c1->'%s'\n", ast_channel_name(c0), ast_channel_name(c1));
- } else if (glue0->update_peer(c0, NULL, NULL, NULL, 0, 0)) {
- ast_log(LOG_WARNING, "Channel '%s' failed to break RTP bridge\n", ast_channel_name(c0));
- }
- if (ast_test_flag(ast_channel_flags(c1), AST_FLAG_ZOMBIE)) {
- ast_debug(1, "Channel '%s' Zombie cleardown from bridge\n", ast_channel_name(c1));
- } else if (ast_channel_tech_pvt(c1) != pvt1) {
- ast_debug(1, "Channel c1->'%s' pvt changed, in bridge with c0->'%s'\n", ast_channel_name(c1), ast_channel_name(c0));
- } else if (glue1 != ast_rtp_instance_get_glue(ast_channel_tech(c1)->type)) {
- ast_debug(1, "Channel c1->'%s' technology changed, in bridge with c0->'%s'\n", ast_channel_name(c1), ast_channel_name(c0));
- } else if (glue1->update_peer(c1, NULL, NULL, NULL, 0, 0)) {
- ast_log(LOG_WARNING, "Channel '%s' failed to break RTP bridge\n", ast_channel_name(c1));
- }
-
- instance0->bridged = NULL;
- instance1->bridged = NULL;
-
- ast_poll_channel_del(c0, c1);
-
-remote_bridge_cleanup:
- ast_format_cap_destroy(oldcap0);
- ast_format_cap_destroy(oldcap1);
-
- return res;
-}
-
/*!
* \brief Conditionally unref an rtp instance
*/
@@ -1430,184 +939,14 @@ static void unref_instance_cond(struct ast_rtp_instance **instance)
}
}
-enum ast_bridge_result ast_rtp_instance_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags, struct ast_frame **fo, struct ast_channel **rc, int timeoutms)
+struct ast_rtp_instance *ast_rtp_instance_get_bridged(struct ast_rtp_instance *instance)
{
- struct ast_rtp_instance *instance0 = NULL, *instance1 = NULL,
- *vinstance0 = NULL, *vinstance1 = NULL,
- *tinstance0 = NULL, *tinstance1 = NULL;
- struct ast_rtp_glue *glue0, *glue1;
- struct ast_sockaddr addr1 = { {0, }, }, addr2 = { {0, }, };
- enum ast_rtp_glue_result audio_glue0_res = AST_RTP_GLUE_RESULT_FORBID, video_glue0_res = AST_RTP_GLUE_RESULT_FORBID;
- enum ast_rtp_glue_result audio_glue1_res = AST_RTP_GLUE_RESULT_FORBID, video_glue1_res = AST_RTP_GLUE_RESULT_FORBID;
- enum ast_bridge_result res = AST_BRIDGE_FAILED;
- enum ast_rtp_dtmf_mode dmode;
- struct ast_format_cap *cap0 = ast_format_cap_alloc_nolock();
- struct ast_format_cap *cap1 = ast_format_cap_alloc_nolock();
- int unlock_chans = 1;
- int read_ptime0, read_ptime1, write_ptime0, write_ptime1;
-
- if (!cap0 || !cap1) {
- unlock_chans = 0;
- goto done;
- }
-
- /* Lock both channels so we can look for the glue that binds them together */
- ast_channel_lock_both(c0, c1);
-
- /* Ensure neither channel got hungup during lock avoidance */
- if (ast_check_hangup(c0) || ast_check_hangup(c1)) {
- ast_log(LOG_WARNING, "Got hangup while attempting to bridge '%s' and '%s'\n", ast_channel_name(c0), ast_channel_name(c1));
- goto done;
- }
-
- /* Grab glue that binds each channel to something using the RTP engine */
- if (!(glue0 = ast_rtp_instance_get_glue(ast_channel_tech(c0)->type)) || !(glue1 = ast_rtp_instance_get_glue(ast_channel_tech(c1)->type))) {
- ast_debug(1, "Can't find native functions for channel '%s'\n", glue0 ? ast_channel_name(c1) : ast_channel_name(c0));
- goto done;
- }
-
- audio_glue0_res = glue0->get_rtp_info(c0, &instance0);
- video_glue0_res = glue0->get_vrtp_info ? glue0->get_vrtp_info(c0, &vinstance0) : AST_RTP_GLUE_RESULT_FORBID;
-
- audio_glue1_res = glue1->get_rtp_info(c1, &instance1);
- video_glue1_res = glue1->get_vrtp_info ? glue1->get_vrtp_info(c1, &vinstance1) : AST_RTP_GLUE_RESULT_FORBID;
-
- /* Apply any limitations on direct media bridging that may be present */
- if (audio_glue0_res == audio_glue1_res && audio_glue1_res == AST_RTP_GLUE_RESULT_REMOTE) {
- if (glue0->allow_rtp_remote && !(glue0->allow_rtp_remote(c0, instance1))) {
- /* If the allow_rtp_remote indicates that remote isn't allowed, revert to local bridge */
- audio_glue0_res = audio_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
- } else if (glue1->allow_rtp_remote && !(glue1->allow_rtp_remote(c1, instance0))) {
- audio_glue0_res = audio_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
- }
- }
- if (video_glue0_res == video_glue1_res && video_glue1_res == AST_RTP_GLUE_RESULT_REMOTE) {
- if (glue0->allow_vrtp_remote && !(glue0->allow_vrtp_remote(c0, instance1))) {
- /* if the allow_vrtp_remote indicates that remote isn't allowed, revert to local bridge */
- video_glue0_res = video_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
- } else if (glue1->allow_vrtp_remote && !(glue1->allow_vrtp_remote(c1, instance0))) {
- video_glue0_res = video_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
- }
- }
-
- /* If we are carrying video, and both sides are not going to remotely bridge... fail the native bridge */
- if (video_glue0_res != AST_RTP_GLUE_RESULT_FORBID && (audio_glue0_res != AST_RTP_GLUE_RESULT_REMOTE || video_glue0_res != AST_RTP_GLUE_RESULT_REMOTE)) {
- audio_glue0_res = AST_RTP_GLUE_RESULT_FORBID;
- }
- if (video_glue1_res != AST_RTP_GLUE_RESULT_FORBID && (audio_glue1_res != AST_RTP_GLUE_RESULT_REMOTE || video_glue1_res != AST_RTP_GLUE_RESULT_REMOTE)) {
- audio_glue1_res = AST_RTP_GLUE_RESULT_FORBID;
- }
-
- /* If any sort of bridge is forbidden just completely bail out and go back to generic bridging */
- if (audio_glue0_res == AST_RTP_GLUE_RESULT_FORBID || audio_glue1_res == AST_RTP_GLUE_RESULT_FORBID) {
- res = AST_BRIDGE_FAILED_NOWARN;
- goto done;
- }
-
-
- /* If address families differ, force a local bridge */
- ast_rtp_instance_get_remote_address(instance0, &addr1);
- ast_rtp_instance_get_remote_address(instance1, &addr2);
-
- if (addr1.ss.ss_family != addr2.ss.ss_family ||
- (ast_sockaddr_is_ipv4_mapped(&addr1) != ast_sockaddr_is_ipv4_mapped(&addr2))) {
- audio_glue0_res = AST_RTP_GLUE_RESULT_LOCAL;
- audio_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
- }
-
- /* If we need to get DTMF see if we can do it outside of the RTP stream itself */
- dmode = ast_rtp_instance_dtmf_mode_get(instance0);
- if ((flags & AST_BRIDGE_DTMF_CHANNEL_0) && dmode) {
- res = AST_BRIDGE_FAILED_NOWARN;
- goto done;
- }
- dmode = ast_rtp_instance_dtmf_mode_get(instance1);
- if ((flags & AST_BRIDGE_DTMF_CHANNEL_1) && dmode) {
- res = AST_BRIDGE_FAILED_NOWARN;
- goto done;
- }
-
- /* If we have gotten to a local bridge make sure that both sides have the same local bridge callback and that they are DTMF compatible */
- if ((audio_glue0_res == AST_RTP_GLUE_RESULT_LOCAL || audio_glue1_res == AST_RTP_GLUE_RESULT_LOCAL)
- && (instance0->engine->local_bridge != instance1->engine->local_bridge
- || (instance0->engine->dtmf_compatible && !instance0->engine->dtmf_compatible(c0, instance0, c1, instance1)))) {
- res = AST_BRIDGE_FAILED_NOWARN;
- goto done;
- }
-
- /* Make sure that codecs match */
- if (glue0->get_codec){
- glue0->get_codec(c0, cap0);
- }
- if (glue1->get_codec) {
- glue1->get_codec(c1, cap1);
- }
- if (!ast_format_cap_is_empty(cap0) && !ast_format_cap_is_empty(cap1) && !ast_format_cap_has_joint(cap0, cap1)) {
- char tmp0[256] = { 0, };
- char tmp1[256] = { 0, };
- ast_debug(1, "Channel codec0 = %s is not codec1 = %s, cannot native bridge in RTP.\n",
- ast_getformatname_multiple(tmp0, sizeof(tmp0), cap0),
- ast_getformatname_multiple(tmp1, sizeof(tmp1), cap1));
- res = AST_BRIDGE_FAILED_NOWARN;
- goto done;
- }
-
- read_ptime0 = (ast_codec_pref_getsize(&instance0->codecs.pref, ast_channel_rawreadformat(c0))).cur_ms;
- read_ptime1 = (ast_codec_pref_getsize(&instance1->codecs.pref, ast_channel_rawreadformat(c1))).cur_ms;
- write_ptime0 = (ast_codec_pref_getsize(&instance0->codecs.pref, ast_channel_rawwriteformat(c0))).cur_ms;
- write_ptime1 = (ast_codec_pref_getsize(&instance1->codecs.pref, ast_channel_rawwriteformat(c1))).cur_ms;
-
- if (read_ptime0 != write_ptime1 || read_ptime1 != write_ptime0) {
- ast_debug(1, "Packetization differs between RTP streams (%d != %d or %d != %d). Cannot native bridge in RTP\n",
- read_ptime0, write_ptime1, read_ptime1, write_ptime0);
- res = AST_BRIDGE_FAILED_NOWARN;
- goto done;
- }
-
- instance0->glue = glue0;
- instance1->glue = glue1;
- instance0->chan = c0;
- instance1->chan = c1;
-
- /* Depending on the end result for bridging either do a local bridge or remote bridge */
- if (audio_glue0_res == AST_RTP_GLUE_RESULT_LOCAL || audio_glue1_res == AST_RTP_GLUE_RESULT_LOCAL) {
- ast_verb(3, "Locally bridging %s and %s\n", ast_channel_name(c0), ast_channel_name(c1));
- res = local_bridge_loop(c0, c1, instance0, instance1, timeoutms, flags, fo, rc, ast_channel_tech_pvt(c0), ast_channel_tech_pvt(c1));
- } else {
- ast_verb(3, "Remotely bridging %s and %s\n", ast_channel_name(c0), ast_channel_name(c1));
- res = remote_bridge_loop(c0, c1, instance0, instance1, vinstance0, vinstance1,
- tinstance0, tinstance1, glue0, glue1, cap0, cap1, timeoutms, flags,
- fo, rc, ast_channel_tech_pvt(c0), ast_channel_tech_pvt(c1));
- }
-
- instance0->glue = NULL;
- instance1->glue = NULL;
- instance0->chan = NULL;
- instance1->chan = NULL;
-
- unlock_chans = 0;
-
-done:
- if (unlock_chans) {
- ast_channel_unlock(c0);
- ast_channel_unlock(c1);
- }
- ast_format_cap_destroy(cap1);
- ast_format_cap_destroy(cap0);
-
- unref_instance_cond(&instance0);
- unref_instance_cond(&instance1);
- unref_instance_cond(&vinstance0);
- unref_instance_cond(&vinstance1);
- unref_instance_cond(&tinstance0);
- unref_instance_cond(&tinstance1);
-
- return res;
+ return instance->bridged;
}
-struct ast_rtp_instance *ast_rtp_instance_get_bridged(struct ast_rtp_instance *instance)
+void ast_rtp_instance_set_bridged(struct ast_rtp_instance *instance, struct ast_rtp_instance *bridged)
{
- return instance->bridged;
+ instance->bridged = bridged;
}
void ast_rtp_instance_early_bridge_make_compatible(struct ast_channel *c0, struct ast_channel *c1)
diff --git a/main/stasis_bridging.c b/main/stasis_bridging.c
new file mode 100644
index 000000000..2ee4fcfc1
--- /dev/null
+++ b/main/stasis_bridging.c
@@ -0,0 +1,358 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, 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 Stasis Messages and Data Types for Bridge Objects
+ *
+ * \author Kinsey Moore <kmoore@digium.com>
+ */
+
+/*** MODULEINFO
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/astobj2.h"
+#include "asterisk/stasis.h"
+#include "asterisk/channel.h"
+#include "asterisk/stasis_bridging.h"
+#include "asterisk/stasis_channels.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_technology.h"
+
+#define SNAPSHOT_CHANNELS_BUCKETS 13
+
+/*!
+ * @{ \brief Define bridge message types.
+ */
+STASIS_MESSAGE_TYPE_DEFN(ast_bridge_snapshot_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_bridge_merge_message_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_entered_bridge_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_left_bridge_type);
+/*! @} */
+
+/*! \brief Aggregate topic for bridge messages */
+static struct stasis_topic *bridge_topic_all;
+
+/*! \brief Caching aggregate topic for bridge snapshots */
+static struct stasis_caching_topic *bridge_topic_all_cached;
+
+/*! \brief Topic pool for individual bridge topics */
+static struct stasis_topic_pool *bridge_topic_pool;
+
+/*! \brief Destructor for bridge snapshots */
+static void bridge_snapshot_dtor(void *obj)
+{
+ struct ast_bridge_snapshot *snapshot = obj;
+ ast_string_field_free_memory(snapshot);
+ ao2_cleanup(snapshot->channels);
+ snapshot->channels = NULL;
+}
+
+struct ast_bridge_snapshot *ast_bridge_snapshot_create(struct ast_bridge *bridge)
+{
+ RAII_VAR(struct ast_bridge_snapshot *, snapshot, NULL, ao2_cleanup);
+ struct ast_bridge_channel *bridge_channel;
+
+ snapshot = ao2_alloc(sizeof(*snapshot), bridge_snapshot_dtor);
+ if (!snapshot || ast_string_field_init(snapshot, 128)) {
+ return NULL;
+ }
+
+ snapshot->channels = ast_str_container_alloc(SNAPSHOT_CHANNELS_BUCKETS);
+ if (!snapshot->channels) {
+ return NULL;
+ }
+
+ AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+ if (ast_str_container_add(snapshot->channels,
+ ast_channel_uniqueid(bridge_channel->chan))) {
+ return NULL;
+ }
+ }
+
+ ast_string_field_set(snapshot, uniqueid, bridge->uniqueid);
+ ast_string_field_set(snapshot, technology, bridge->technology->name);
+
+ snapshot->feature_flags = bridge->feature_flags;
+ snapshot->num_channels = bridge->num_channels;
+ snapshot->num_active = bridge->num_active;
+
+ ao2_ref(snapshot, +1);
+ return snapshot;
+}
+
+struct stasis_topic *ast_bridge_topic(struct ast_bridge *bridge)
+{
+ struct stasis_topic *bridge_topic = stasis_topic_pool_get_topic(bridge_topic_pool, bridge->uniqueid);
+ if (!bridge_topic) {
+ return ast_bridge_topic_all();
+ }
+ return bridge_topic;
+}
+
+struct stasis_topic *ast_bridge_topic_all(void)
+{
+ return bridge_topic_all;
+}
+
+struct stasis_caching_topic *ast_bridge_topic_all_cached(void)
+{
+ return bridge_topic_all_cached;
+}
+
+void ast_bridge_publish_state(struct ast_bridge *bridge)
+{
+ RAII_VAR(struct ast_bridge_snapshot *, snapshot, NULL, ao2_cleanup);
+ RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+ ast_assert(bridge != NULL);
+
+ snapshot = ast_bridge_snapshot_create(bridge);
+ if (!snapshot) {
+ return;
+ }
+
+ msg = stasis_message_create(ast_bridge_snapshot_type(), snapshot);
+ if (!msg) {
+ return;
+ }
+
+ stasis_publish(ast_bridge_topic(bridge), msg);
+}
+
+static void bridge_publish_state_from_blob(struct ast_bridge_blob *obj)
+{
+ RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+ ast_assert(obj != NULL);
+
+ msg = stasis_message_create(ast_bridge_snapshot_type(), obj->bridge);
+ if (!msg) {
+ return;
+ }
+
+ stasis_publish(stasis_topic_pool_get_topic(bridge_topic_pool, obj->bridge->uniqueid), msg);
+}
+
+/*! \brief Destructor for bridge merge messages */
+static void bridge_merge_message_dtor(void *obj)
+{
+ struct ast_bridge_merge_message *msg = obj;
+
+ ao2_cleanup(msg->to);
+ msg->to = NULL;
+ ao2_cleanup(msg->from);
+ msg->from = NULL;
+}
+
+/*! \brief Bridge merge message creation helper */
+static struct ast_bridge_merge_message *bridge_merge_message_create(struct ast_bridge *to, struct ast_bridge *from)
+{
+ RAII_VAR(struct ast_bridge_merge_message *, msg, NULL, ao2_cleanup);
+
+ msg = ao2_alloc(sizeof(*msg), bridge_merge_message_dtor);
+ if (!msg) {
+ return NULL;
+ }
+
+ msg->to = ast_bridge_snapshot_create(to);
+ if (!msg->to) {
+ return NULL;
+ }
+
+ msg->from = ast_bridge_snapshot_create(from);
+ if (!msg->from) {
+ return NULL;
+ }
+
+ ao2_ref(msg, +1);
+ return msg;
+}
+
+void ast_bridge_publish_merge(struct ast_bridge *to, struct ast_bridge *from)
+{
+ RAII_VAR(struct ast_bridge_merge_message *, merge_msg, NULL, ao2_cleanup);
+ RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+ ast_assert(to != NULL);
+ ast_assert(from != NULL);
+
+ merge_msg = bridge_merge_message_create(to, from);
+ if (!merge_msg) {
+ return;
+ }
+
+ msg = stasis_message_create(ast_bridge_merge_message_type(), merge_msg);
+ if (!msg) {
+ return;
+ }
+
+ stasis_publish(ast_bridge_topic_all(), msg);
+}
+
+static void bridge_blob_dtor(void *obj)
+{
+ struct ast_bridge_blob *event = obj;
+ ao2_cleanup(event->bridge);
+ event->bridge = NULL;
+ ao2_cleanup(event->channel);
+ event->channel = NULL;
+ ast_json_unref(event->blob);
+ event->blob = NULL;
+}
+
+struct stasis_message *ast_bridge_blob_create(
+ struct stasis_message_type *message_type,
+ struct ast_bridge *bridge,
+ struct ast_channel *chan,
+ struct ast_json *blob)
+{
+ RAII_VAR(struct ast_bridge_blob *, obj, NULL, ao2_cleanup);
+ RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+ obj = ao2_alloc(sizeof(*obj), bridge_blob_dtor);
+ if (!obj) {
+ return NULL;
+ }
+
+ if (bridge) {
+ obj->bridge = ast_bridge_snapshot_create(bridge);
+ if (obj->bridge == NULL) {
+ return NULL;
+ }
+ }
+
+ if (chan) {
+ obj->channel = ast_channel_snapshot_create(chan);
+ if (obj->channel == NULL) {
+ return NULL;
+ }
+ }
+
+ if (blob) {
+ obj->blob = ast_json_ref(blob);
+ }
+
+ msg = stasis_message_create(message_type, obj);
+ if (!msg) {
+ return NULL;
+ }
+
+ ao2_ref(msg, +1);
+ return msg;
+}
+
+const char *ast_bridge_blob_json_type(struct ast_bridge_blob *obj)
+{
+ if (obj == NULL) {
+ return NULL;
+ }
+
+ return ast_json_string_get(ast_json_object_get(obj->blob, "type"));
+}
+
+void ast_bridge_publish_enter(struct ast_bridge *bridge, struct ast_channel *chan)
+{
+ RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+ msg = ast_bridge_blob_create(ast_channel_entered_bridge_type(), bridge, chan, NULL);
+ if (!msg) {
+ return;
+ }
+
+ /* enter blob first, then state */
+ stasis_publish(ast_bridge_topic(bridge), msg);
+ bridge_publish_state_from_blob(stasis_message_data(msg));
+}
+
+void ast_bridge_publish_leave(struct ast_bridge *bridge, struct ast_channel *chan)
+{
+ RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+ msg = ast_bridge_blob_create(ast_channel_left_bridge_type(), bridge, chan, NULL);
+ if (!msg) {
+ return;
+ }
+
+ /* state first, then leave blob (opposite of enter, preserves nesting of events) */
+ bridge_publish_state_from_blob(stasis_message_data(msg));
+ stasis_publish(ast_bridge_topic(bridge), msg);
+}
+
+struct ast_json *ast_bridge_snapshot_to_json(const struct ast_bridge_snapshot *snapshot)
+{
+ RAII_VAR(struct ast_json *, json_chan, NULL, ast_json_unref);
+ int r = 0;
+
+ if (snapshot == NULL) {
+ return NULL;
+ }
+
+ json_chan = ast_json_object_create();
+ if (!json_chan) { ast_log(LOG_ERROR, "Error creating channel json object\n"); return NULL; }
+
+ r = ast_json_object_set(json_chan, "bridge-uniqueid", ast_json_string_create(snapshot->uniqueid));
+ if (r) { ast_log(LOG_ERROR, "Error adding attrib to channel json object\n"); return NULL; }
+ r = ast_json_object_set(json_chan, "bridge-technology", ast_json_string_create(snapshot->technology));
+ if (r) { ast_log(LOG_ERROR, "Error adding attrib to channel json object\n"); return NULL; }
+
+ return ast_json_ref(json_chan);
+}
+
+void ast_stasis_bridging_shutdown(void)
+{
+ STASIS_MESSAGE_TYPE_CLEANUP(ast_bridge_snapshot_type);
+ STASIS_MESSAGE_TYPE_CLEANUP(ast_bridge_merge_message_type);
+ STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_entered_bridge_type);
+ STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_left_bridge_type);
+ ao2_cleanup(bridge_topic_all);
+ bridge_topic_all = NULL;
+ bridge_topic_all_cached = stasis_caching_unsubscribe(bridge_topic_all_cached);
+ ao2_cleanup(bridge_topic_pool);
+ bridge_topic_pool = NULL;
+}
+
+/*! \brief snapshot ID getter for caching topic */
+static const char *bridge_snapshot_get_id(struct stasis_message *msg)
+{
+ struct ast_bridge_snapshot *snapshot;
+ if (stasis_message_type(msg) != ast_bridge_snapshot_type()) {
+ return NULL;
+ }
+ snapshot = stasis_message_data(msg);
+ return snapshot->uniqueid;
+}
+
+int ast_stasis_bridging_init(void)
+{
+ STASIS_MESSAGE_TYPE_INIT(ast_bridge_snapshot_type);
+ STASIS_MESSAGE_TYPE_INIT(ast_bridge_merge_message_type);
+ STASIS_MESSAGE_TYPE_INIT(ast_channel_entered_bridge_type);
+ STASIS_MESSAGE_TYPE_INIT(ast_channel_left_bridge_type);
+ bridge_topic_all = stasis_topic_create("ast_bridge_topic_all");
+ bridge_topic_all_cached = stasis_caching_topic_create(bridge_topic_all, bridge_snapshot_get_id);
+ bridge_topic_pool = stasis_topic_pool_create(bridge_topic_all);
+ return !bridge_topic_all
+ || !bridge_topic_all_cached
+ || !bridge_topic_pool ? -1 : 0;
+}
diff --git a/main/strings.c b/main/strings.c
index 47715e63e..d004da227 100644
--- a/main/strings.c
+++ b/main/strings.c
@@ -160,3 +160,4 @@ char *__ast_str_helper2(struct ast_str **buf, ssize_t maxlen, const char *src, s
return (*buf)->__AST_STR_STR;
}
+