(closes issue ASTERISK-21641) Reported by: Matt Jordan Review: https://reviewboard.asterisk.org/r/2573/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@392915 65c4cc65-6c06-0410-ace0-fbb531ad65f3
432 lines
14 KiB
C
432 lines
14 KiB
C
/*
|
|
* 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 Bridge Class
|
|
*
|
|
* \author Jonathan Rose <jrose@digium.com>
|
|
*/
|
|
|
|
#include "asterisk.h"
|
|
#include "asterisk/logger.h"
|
|
#include "res_parking.h"
|
|
#include "asterisk/astobj2.h"
|
|
#include "asterisk/features.h"
|
|
#include "asterisk/say.h"
|
|
#include "asterisk/term.h"
|
|
|
|
struct ast_bridge_parking
|
|
{
|
|
struct ast_bridge base;
|
|
|
|
/* private stuff for parking */
|
|
struct parking_lot *lot;
|
|
};
|
|
|
|
/*!
|
|
* \internal
|
|
* \brief ast_bridge parking class destructor
|
|
* \since 12.0.0
|
|
*
|
|
* \param self Bridge to operate upon.
|
|
*
|
|
* \note XXX Stub... and it might go unused.
|
|
*
|
|
* \return Nothing
|
|
*/
|
|
static void bridge_parking_destroy(struct ast_bridge_parking *self)
|
|
{
|
|
ast_bridge_base_v_table.destroy(&self->base);
|
|
}
|
|
|
|
static void bridge_parking_dissolving(struct ast_bridge_parking *self)
|
|
{
|
|
self->lot = NULL;
|
|
ast_bridge_base_v_table.dissolving(&self->base);
|
|
}
|
|
|
|
static void destroy_parked_user(void *obj)
|
|
{
|
|
struct parked_user *pu = obj;
|
|
|
|
ao2_cleanup(pu->lot);
|
|
ao2_cleanup(pu->parker);
|
|
ao2_cleanup(pu->retriever);
|
|
}
|
|
|
|
/*!
|
|
* \internal
|
|
* \since 12
|
|
* \brief Construct a parked_user struct assigned to the specified parking lot
|
|
*
|
|
* \param lot The parking lot we are assigning the user to
|
|
* \param parkee The channel being parked
|
|
* \param parker The channel performing the park operation (may be the same channel)
|
|
* \param use_random_space if true, prioritize using a random parking space instead
|
|
* of ${PARKINGEXTEN} and/or automatic assignment from the parking lot
|
|
* \param time_limit If using a custom timeout, this should be supplied so that the
|
|
* parked_user struct can provide this information for manager events. If <0,
|
|
* use the parking lot limit instead.
|
|
*
|
|
* \retval NULL on failure
|
|
* \retval reference to the parked user
|
|
*
|
|
* \note ao2_cleanup this reference when you are done using it or you'll cause leaks.
|
|
*/
|
|
static struct parked_user *generate_parked_user(struct parking_lot *lot, struct ast_channel *chan, struct ast_channel *parker, int use_random_space, int time_limit)
|
|
{
|
|
struct parked_user *new_parked_user;
|
|
int preferred_space = -1; /* Initialize to use parking lot defaults */
|
|
int parking_space;
|
|
const char *parkingexten;
|
|
|
|
if (lot->mode == PARKINGLOT_DISABLED) {
|
|
ast_log(LOG_NOTICE, "Tried to park in a parking lot that is no longer able to be parked to.\n");
|
|
return NULL;
|
|
}
|
|
|
|
new_parked_user = ao2_alloc(sizeof(*new_parked_user), destroy_parked_user);
|
|
if (!new_parked_user) {
|
|
return NULL;
|
|
}
|
|
|
|
ast_channel_lock(chan);
|
|
ast_copy_string(new_parked_user->blindtransfer, S_OR(pbx_builtin_getvar_helper(chan, "BLINDTRANSFER"), ""), AST_CHANNEL_NAME);
|
|
ast_channel_unlock(chan);
|
|
|
|
if (use_random_space) {
|
|
preferred_space = ast_random() % (lot->cfg->parking_stop - lot->cfg->parking_start + 1);
|
|
preferred_space += lot->cfg->parking_start;
|
|
} else {
|
|
ast_channel_lock(chan);
|
|
if ((parkingexten = pbx_builtin_getvar_helper(chan, "PARKINGEXTEN"))) {
|
|
parkingexten = ast_strdupa(parkingexten);
|
|
}
|
|
ast_channel_unlock(chan);
|
|
|
|
if (!ast_strlen_zero(parkingexten)) {
|
|
if (sscanf(parkingexten, "%30d", &preferred_space) != 1 || preferred_space <= 0) {
|
|
ast_log(LOG_WARNING, "PARKINGEXTEN='%s' is not a valid parking space.\n", parkingexten);
|
|
ao2_ref(new_parked_user, -1);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We need to keep the lot locked between parking_lot_get_space and actually placing it in the lot. Or until we decide not to. */
|
|
ao2_lock(lot);
|
|
|
|
parking_space = parking_lot_get_space(lot, preferred_space);
|
|
if (parking_space == -1) {
|
|
ast_log(LOG_NOTICE, "Failed to get parking space in lot '%s'. All full.\n", lot->name);
|
|
ao2_ref(new_parked_user, -1);
|
|
ao2_unlock(lot);
|
|
return NULL;
|
|
}
|
|
|
|
lot->next_space = ((parking_space + 1) - lot->cfg->parking_start) % (lot->cfg->parking_stop - lot->cfg->parking_start + 1) + lot->cfg->parking_start;
|
|
new_parked_user->chan = chan;
|
|
new_parked_user->parking_space = parking_space;
|
|
|
|
/* Have the parked user take a reference to the parking lot. This reference should be immutable and released at destruction */
|
|
new_parked_user->lot = lot;
|
|
ao2_ref(lot, +1);
|
|
|
|
new_parked_user->start = ast_tvnow();
|
|
new_parked_user->time_limit = (time_limit >= 0) ? time_limit : lot->cfg->parkingtime;
|
|
new_parked_user->parker = ast_channel_snapshot_create(parker);
|
|
if (!new_parked_user->parker) {
|
|
ao2_ref(new_parked_user, -1);
|
|
ao2_unlock(lot);
|
|
return NULL;
|
|
}
|
|
|
|
/* Insert into the parking lot's parked user list. We can unlock the lot now. */
|
|
ao2_link(lot->parked_users, new_parked_user);
|
|
ao2_unlock(lot);
|
|
|
|
return new_parked_user;
|
|
}
|
|
|
|
/* TODO CEL events for parking */
|
|
|
|
/*!
|
|
* \internal
|
|
* \brief ast_bridge parking 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
|
|
*
|
|
* \retval 0 on success
|
|
* \retval -1 on failure
|
|
*/
|
|
static int bridge_parking_push(struct ast_bridge_parking *self, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap)
|
|
{
|
|
struct parked_user *pu;
|
|
int randomize = 0;
|
|
int time_limit = -1;
|
|
int silence = 0;
|
|
const char *blind_transfer;
|
|
RAII_VAR(struct ast_channel *, parker, NULL, ao2_cleanup);
|
|
RAII_VAR(char *, parker_uuid, NULL, ast_free);
|
|
RAII_VAR(char *, comeback_override, NULL, ast_free);
|
|
|
|
ast_bridge_base_v_table.push(&self->base, bridge_channel, swap);
|
|
|
|
ast_assert(self->lot != NULL);
|
|
|
|
/* Answer the channel if needed */
|
|
if (ast_channel_state(bridge_channel->chan) != AST_STATE_UP) {
|
|
ast_answer(bridge_channel->chan);
|
|
}
|
|
|
|
if (swap) {
|
|
ao2_lock(swap);
|
|
pu = swap->bridge_pvt;
|
|
if (!pu) {
|
|
/* This should be impossible since the only way a channel can enter in the first place
|
|
* is if it has a parked user associated with it */
|
|
publish_parked_call_failure(bridge_channel->chan);
|
|
ao2_unlock(swap);
|
|
return -1;
|
|
}
|
|
|
|
/* Give the swap channel's parked user reference to the incoming channel */
|
|
pu->chan = bridge_channel->chan;
|
|
bridge_channel->bridge_pvt = pu;
|
|
swap->bridge_pvt = NULL;
|
|
|
|
/* TODO Add a parked call swap message type to relay information about parked channel swaps */
|
|
|
|
ao2_unlock(swap);
|
|
|
|
parking_set_duration(bridge_channel->features, pu);
|
|
|
|
/* BUGBUG Adding back local channel swapping made us not hear music on hold for the channel that got swapped
|
|
* into the parking lot. Setting the roels back up gets around that, but we still need to deal with the ringing option
|
|
* to the park application here somehow.
|
|
*/
|
|
parking_channel_set_roles(bridge_channel->chan, self->lot, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
get_park_common_datastore_data(bridge_channel->chan, &parker_uuid, &comeback_override, &randomize, &time_limit, &silence);
|
|
parker = ast_channel_get_by_name(parker_uuid);
|
|
|
|
/* If the parker and the parkee are the same channel pointer, then the channel entered using
|
|
* the park application. It's possible the the blindtransfer channel is still alive (particularly
|
|
* when a multichannel bridge is parked), so try to get the real parker if possible. */
|
|
ast_channel_lock(bridge_channel->chan);
|
|
blind_transfer = S_OR(pbx_builtin_getvar_helper(bridge_channel->chan, "BLINDTRANSFER"),
|
|
ast_channel_name(bridge_channel->chan));
|
|
if (blind_transfer) {
|
|
blind_transfer = ast_strdupa(blind_transfer);
|
|
}
|
|
ast_channel_unlock(bridge_channel->chan);
|
|
|
|
if (parker == bridge_channel->chan) {
|
|
struct ast_channel *real_parker = ast_channel_get_by_name(blind_transfer);
|
|
if (real_parker) {
|
|
ao2_cleanup(parker);
|
|
parker = real_parker;
|
|
}
|
|
}
|
|
|
|
if (!parker) {
|
|
return -1;
|
|
}
|
|
|
|
pu = generate_parked_user(self->lot, bridge_channel->chan, parker, randomize, time_limit);
|
|
if (!pu) {
|
|
publish_parked_call_failure(bridge_channel->chan);
|
|
return -1;
|
|
}
|
|
|
|
/* If a comeback_override was provided, set it for the parked user's comeback string. */
|
|
if (comeback_override) {
|
|
strncpy(pu->comeback, comeback_override, sizeof(pu->comeback));
|
|
pu->comeback[sizeof(pu->comeback) - 1] = '\0';
|
|
}
|
|
|
|
/* Generate ParkedCall Stasis Message */
|
|
publish_parked_call(pu, PARKED_CALL);
|
|
|
|
/* If the parkee and the parker are the same and silence_announce isn't set, play the announcement to the parkee */
|
|
if (!strcmp(blind_transfer, ast_channel_name(bridge_channel->chan)) && !silence) {
|
|
char saynum_buf[16];
|
|
snprintf(saynum_buf, sizeof(saynum_buf), "%u %u", 0, pu->parking_space);
|
|
ast_bridge_channel_queue_playfile(bridge_channel, say_parking_space, saynum_buf, NULL);
|
|
}
|
|
|
|
/* Apply parking duration limits */
|
|
parking_set_duration(bridge_channel->features, pu);
|
|
|
|
/* Set this to the bridge pvt so that we don't have to refind the parked user associated with this bridge channel again. */
|
|
bridge_channel->bridge_pvt = pu;
|
|
|
|
ast_verb(3, "Parking '" COLORIZE_FMT "' in '" COLORIZE_FMT "' at space %d\n",
|
|
COLORIZE(COLOR_BRMAGENTA, 0, ast_channel_name(bridge_channel->chan)),
|
|
COLORIZE(COLOR_BRMAGENTA, 0, self->lot->name),
|
|
pu->parking_space);
|
|
|
|
parking_notify_metermaids(pu->parking_space, self->lot->cfg->parking_con, AST_DEVICE_INUSE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*!
|
|
* \internal
|
|
* \brief ast_bridge parking 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_parking_pull(struct ast_bridge_parking *self, struct ast_bridge_channel *bridge_channel)
|
|
{
|
|
RAII_VAR(struct parked_user *, pu, NULL, ao2_cleanup);
|
|
ast_bridge_base_v_table.pull(&self->base, bridge_channel);
|
|
|
|
/* Take over the bridge channel's pu reference. It will be released when we are done. */
|
|
pu = bridge_channel->bridge_pvt;
|
|
bridge_channel->bridge_pvt = NULL;
|
|
|
|
/* This should only happen if the exiting channel was swapped out */
|
|
if (!pu) {
|
|
return;
|
|
}
|
|
|
|
/* If we got here without the resolution being set, that's because the call was hung up for some reason without
|
|
* timing out or being picked up. There may be some forcible park removals later, but the resolution should be
|
|
* handled in those cases */
|
|
ao2_lock(pu);
|
|
if (pu->resolution == PARK_UNSET) {
|
|
pu->resolution = PARK_ABANDON;
|
|
}
|
|
ao2_unlock(pu);
|
|
|
|
parking_notify_metermaids(pu->parking_space, self->lot->cfg->parking_con, AST_DEVICE_NOT_INUSE);
|
|
|
|
switch (pu->resolution) {
|
|
case PARK_UNSET:
|
|
/* This should be impossible now since the resolution is forcibly set to abandon if it was unset at this point. Resolution
|
|
isn't allowed to be changed when it isn't currently PARK_UNSET. */
|
|
return;
|
|
case PARK_ABANDON:
|
|
/* Since the call was abandoned without additional handling, we need to issue the give up event and unpark the user. */
|
|
publish_parked_call(pu, PARKED_CALL_GIVEUP);
|
|
unpark_parked_user(pu);
|
|
return;
|
|
case PARK_FORCED:
|
|
/* PARK_FORCED is currently unused, but it is expected that it would be handled similar to PARK_ANSWERED.
|
|
* There is currently no event related to forced parked calls either */
|
|
return;
|
|
case PARK_ANSWERED:
|
|
/* If answered or forced, the channel should be pulled from the bridge as part of that process and unlinked from
|
|
* the parking lot afterwards. We do need to apply bridge features though and play the courtesy tone if set. */
|
|
publish_parked_call(pu, PARKED_CALL_UNPARKED);
|
|
parked_call_retrieve_enable_features(bridge_channel->chan, pu->lot, AST_FEATURE_FLAG_BYCALLEE);
|
|
|
|
if (pu->lot->cfg->parkedplay & AST_FEATURE_FLAG_BYCALLEE) {
|
|
ast_bridge_channel_queue_playfile(bridge_channel, NULL, pu->lot->cfg->courtesytone, NULL);
|
|
}
|
|
|
|
return;
|
|
case PARK_TIMEOUT:
|
|
/* Timeout is similar to abandon because it simply sets the bridge state to end and doesn't
|
|
* actually pull the channel. Because of that, unpark should happen in here. */
|
|
publish_parked_call(pu, PARKED_CALL_TIMEOUT);
|
|
unpark_parked_user(pu);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \internal
|
|
* \brief ast_bridge parking 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.
|
|
* \note XXX Stub... and it will probably go unused.
|
|
*
|
|
* \return Nothing
|
|
*/
|
|
static void bridge_parking_notify_masquerade(struct ast_bridge_parking *self, struct ast_bridge_channel *bridge_channel)
|
|
{
|
|
ast_bridge_base_v_table.notify_masquerade(&self->base, bridge_channel);
|
|
}
|
|
|
|
static void bridge_parking_get_merge_priority(struct ast_bridge_parking *self)
|
|
{
|
|
ast_bridge_base_v_table.get_merge_priority(&self->base);
|
|
}
|
|
|
|
struct ast_bridge_methods ast_bridge_parking_v_table = {
|
|
.name = "parking",
|
|
.destroy = (ast_bridge_destructor_fn) bridge_parking_destroy,
|
|
.dissolving = (ast_bridge_dissolving_fn) bridge_parking_dissolving,
|
|
.push = (ast_bridge_push_channel_fn) bridge_parking_push,
|
|
.pull = (ast_bridge_pull_channel_fn) bridge_parking_pull,
|
|
.notify_masquerade = (ast_bridge_notify_masquerade_fn) bridge_parking_notify_masquerade,
|
|
.get_merge_priority = (ast_bridge_merge_priority_fn) bridge_parking_get_merge_priority,
|
|
};
|
|
|
|
static struct ast_bridge *ast_bridge_parking_init(struct ast_bridge_parking *self, struct parking_lot *bridge_lot)
|
|
{
|
|
if (!self) {
|
|
return NULL;
|
|
}
|
|
|
|
/* If no lot is defined for the bridge, then we aren't allowing the bridge to be initialized. */
|
|
if (!bridge_lot) {
|
|
ao2_ref(self, -1);
|
|
return NULL;
|
|
}
|
|
|
|
/* It doesn't need to be a reference since the bridge only lives as long as the parking lot lives. */
|
|
self->lot = bridge_lot;
|
|
|
|
return &self->base;
|
|
}
|
|
|
|
struct ast_bridge *bridge_parking_new(struct parking_lot *bridge_lot)
|
|
{
|
|
void *bridge;
|
|
|
|
bridge = ast_bridge_alloc(sizeof(struct ast_bridge_parking), &ast_bridge_parking_v_table);
|
|
bridge = ast_bridge_base_init(bridge, AST_BRIDGE_CAPABILITY_HOLDING,
|
|
AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM
|
|
| AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM);
|
|
bridge = ast_bridge_parking_init(bridge, bridge_lot);
|
|
bridge = ast_bridge_register(bridge);
|
|
return bridge;
|
|
}
|