diff options
author | Richard Mudgett <rmudgett@digium.com> | 2013-05-21 18:00:22 +0000 |
---|---|---|
committer | Richard Mudgett <rmudgett@digium.com> | 2013-05-21 18:00:22 +0000 |
commit | 3d63833bd6c869b7efa383e8dea14be1a6eff998 (patch) | |
tree | 34957dd051b8f67c7cc58a510e24ee3873a61ad4 /main | |
parent | e1e1cc2deefb92f8b43825f1f34e619354737842 (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.c | 309 | ||||
-rw-r--r-- | main/asterisk.c | 20 | ||||
-rw-r--r-- | main/bridging.c | 5675 | ||||
-rw-r--r-- | main/bridging_basic.c | 159 | ||||
-rw-r--r-- | main/bridging_roles.c | 462 | ||||
-rw-r--r-- | main/channel.c | 182 | ||||
-rw-r--r-- | main/channel_internal_api.c | 13 | ||||
-rw-r--r-- | main/cli.c | 45 | ||||
-rw-r--r-- | main/config_options.c | 5 | ||||
-rw-r--r-- | main/core_local.c | 775 | ||||
-rw-r--r-- | main/core_unreal.c | 855 | ||||
-rw-r--r-- | main/features.c | 1920 | ||||
-rw-r--r-- | main/frame.c | 8 | ||||
-rw-r--r-- | main/manager.c | 207 | ||||
-rw-r--r-- | main/manager_bridging.c | 470 | ||||
-rw-r--r-- | main/manager_channels.c | 93 | ||||
-rw-r--r-- | main/parking.c | 191 | ||||
-rw-r--r-- | main/pbx.c | 11 | ||||
-rw-r--r-- | main/rtp_engine.c | 669 | ||||
-rw-r--r-- | main/stasis_bridging.c | 358 | ||||
-rw-r--r-- | main/strings.c | 1 |
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; } + |