summaryrefslogtreecommitdiff
path: root/res/parking
diff options
context:
space:
mode:
authorRichard Mudgett <rmudgett@digium.com>2013-05-21 18:00:22 +0000
committerRichard Mudgett <rmudgett@digium.com>2013-05-21 18:00:22 +0000
commit3d63833bd6c869b7efa383e8dea14be1a6eff998 (patch)
tree34957dd051b8f67c7cc58a510e24ee3873a61ad4 /res/parking
parente1e1cc2deefb92f8b43825f1f34e619354737842 (diff)
Merge in the bridge_construction branch to make the system use the Bridging API.
Breaks many things until they can be reworked. A partial list: chan_agent chan_dahdi, chan_misdn, chan_iax2 native bridging app_queue COLP updates DTMF attended transfers Protocol attended transfers git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@389378 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'res/parking')
-rw-r--r--res/parking/parking_applications.c801
-rw-r--r--res/parking/parking_bridge.c421
-rw-r--r--res/parking/parking_bridge_features.c479
-rw-r--r--res/parking/parking_controller.c292
-rw-r--r--res/parking/parking_manager.c610
-rw-r--r--res/parking/parking_ui.c199
-rw-r--r--res/parking/res_parking.h436
7 files changed, 3238 insertions, 0 deletions
diff --git a/res/parking/parking_applications.c b/res/parking/parking_applications.c
new file mode 100644
index 000000000..097329b93
--- /dev/null
+++ b/res/parking/parking_applications.c
@@ -0,0 +1,801 @@
+/*
+ * 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 Call Parking Applications
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "res_parking.h"
+#include "asterisk/config.h"
+#include "asterisk/config_options.h"
+#include "asterisk/event.h"
+#include "asterisk/utils.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/features.h"
+#include "asterisk/module.h"
+#include "asterisk/app.h"
+#include "asterisk/say.h"
+#include "asterisk/features.h"
+#include "asterisk/bridging_basic.h"
+
+/*** DOCUMENTATION
+ <application name="Park" language="en_US">
+ <synopsis>
+ Park yourself.
+ </synopsis>
+ <syntax>
+ <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 to this application</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>
+ <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>
+ <option name="c" argsep=",">
+ <argument name="context" required="false" />
+ <argument name="extension" required="false" />
+ <argument name="priority" required="true" />
+ <para>If the parking times out, go to this place in the dialplan
+ instead of where the parking lot defines the call should go.
+ </para>
+ </option>
+ <option name="t">
+ <argument name="duration" required="true" />
+ <para>Use a timeout of <literal>duration</literal> seconds instead
+ of the timeout specified by the parking lot.</para>
+ </option>
+ </optionlist>
+ </parameter>
+ </syntax>
+ <description>
+ <para>Used to park yourself (typically in combination with an attended
+ 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 in use then execution
+ will continue at the next priority.
+ </para>
+ </description>
+ <see-also>
+ <ref type="application">ParkedCall</ref>
+ </see-also>
+ </application>
+
+ <application name="ParkedCall" language="en_US">
+ <synopsis>
+ Retrieve a parked call.
+ </synopsis>
+ <syntax>
+ <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>
+ <parameter name="parking_space">
+ <para>Parking space to retrieve a parked call from.
+ If not provided then the first available parked call in the
+ parking lot will be retrieved.</para>
+ </parameter>
+ </syntax>
+ <description>
+ <para>Used to retrieve a parked call from a parking lot.</para>
+ <note>
+ <para>If a parking lot's parkext option is set, then Parking lots
+ will automatically create and manage dialplan extensions in
+ the parking lot context. If that is the case then you will not
+ need to manage parking extensions yourself, just include the
+ parking context of the parking lot.</para>
+ </note>
+ </description>
+ <see-also>
+ <ref type="application">Park</ref>
+ </see-also>
+ </application>
+
+ <application name="ParkAndAnnounce" language="en_US">
+ <synopsis>
+ Park and Announce.
+ </synopsis>
+ <syntax>
+ <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 to this application</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>
+ <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="c" argsep=",">
+ <argument name="context" required="false" />
+ <argument name="extension" required="false" />
+ <argument name="priority" required="true" />
+ <para>If the parking times out, go to this place in the dialplan
+ instead of where the parking lot defines the call should go.
+ </para>
+ </option>
+ <option name="t">
+ <argument name="duration" required="true" />
+ <para>Use a timeout of <literal>duration</literal> seconds instead
+ of the timeout specified by the parking lot.</para>
+ </option>
+ </optionlist>
+ </parameter>
+ <parameter name="announce_template" required="true" argsep=":">
+ <argument name="announce" required="true">
+ <para>Colon-separated list of files to announce. The word
+ <literal>PARKED</literal> will be replaced by a say_digits of the extension in which
+ the call is parked.</para>
+ </argument>
+ <argument name="announce1" multiple="true" />
+ </parameter>
+ <parameter name="dial" required="true">
+ <para>The app_dial style resource to call to make the
+ announcement. Console/dsp calls the console.</para>
+ </parameter>
+ </syntax>
+ <description>
+ <para>Park a call into the parkinglot and announce the call to another channel.</para>
+ <para>The variable <variable>PARKEDAT</variable> will contain the parking extension
+ into which the call was placed. Use with the Local channel to allow the dialplan to make
+ use of this information.</para>
+ </description>
+ <see-also>
+ <ref type="application">Park</ref>
+ <ref type="application">ParkedCall</ref>
+ </see-also>
+ </application>
+ ***/
+
+/* Park a call */
+
+enum park_args {
+ OPT_ARG_COMEBACK,
+ OPT_ARG_TIMEOUT,
+ OPT_ARG_ARRAY_SIZE /* Always the last element of the enum */
+};
+
+enum park_flags {
+ MUXFLAG_RINGING = (1 << 0),
+ MUXFLAG_RANDOMIZE = (1 << 1),
+ MUXFLAG_NOANNOUNCE = (1 << 2),
+ MUXFLAG_COMEBACK_OVERRIDE = (1 << 3),
+ MUXFLAG_TIMEOUT_OVERRIDE = (1 << 4),
+};
+
+AST_APP_OPTIONS(park_opts, {
+ AST_APP_OPTION('r', MUXFLAG_RINGING),
+ AST_APP_OPTION('R', MUXFLAG_RANDOMIZE),
+ AST_APP_OPTION('s', MUXFLAG_NOANNOUNCE),
+ AST_APP_OPTION_ARG('c', MUXFLAG_COMEBACK_OVERRIDE, OPT_ARG_COMEBACK),
+ AST_APP_OPTION_ARG('t', MUXFLAG_TIMEOUT_OVERRIDE, OPT_ARG_TIMEOUT),
+});
+
+static int apply_option_timeout (int *var, char *timeout_arg)
+{
+ if (ast_strlen_zero(timeout_arg)) {
+ ast_log(LOG_ERROR, "No duration value provided for the timeout ('t') option.\n");
+ return -1;
+ }
+
+ if (sscanf(timeout_arg, "%d", var) != 1 || *var < 0) {
+ ast_log(LOG_ERROR, "Duration value provided for timeout ('t') option must be 0 or greater.\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int park_app_parse_data(const char *data, int *disable_announce, int *use_ringing, int *randomize, int *time_limit, char **comeback_override, char **lot_name)
+{
+ char *parse;
+ struct ast_flags flags = { 0 };
+
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(lot_name);
+ AST_APP_ARG(options);
+ AST_APP_ARG(other); /* Any remaining unused arguments */
+ );
+
+ parse = ast_strdupa(data);
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ if (args.options) {
+ char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
+ ast_app_parse_options(park_opts, &flags, opts, args.options);
+ if (ast_test_flag(&flags, MUXFLAG_TIMEOUT_OVERRIDE)) {
+ if (apply_option_timeout(time_limit, opts[OPT_ARG_TIMEOUT])) {
+ return -1;
+ }
+ }
+
+ if (ast_test_flag(&flags, MUXFLAG_COMEBACK_OVERRIDE)) {
+ *comeback_override = ast_strdup(opts[OPT_ARG_COMEBACK]);
+ }
+
+ if (ast_test_flag(&flags, MUXFLAG_NOANNOUNCE)) {
+ if (disable_announce) {
+ *disable_announce = 1;
+ }
+ }
+
+ if (ast_test_flag(&flags, MUXFLAG_RINGING)) {
+ *use_ringing = 1;
+ }
+
+ if (ast_test_flag(&flags, MUXFLAG_RANDOMIZE)) {
+ *randomize = 1;
+ }
+ }
+
+ if (!ast_strlen_zero(args.lot_name)) {
+ *lot_name = ast_strdup(args.lot_name);
+ }
+
+ return 0;
+}
+
+static void park_common_datastore_destroy(void *data)
+{
+ struct park_common_datastore *datastore = data;
+ ast_free(datastore->parker_uuid);
+ ast_free(datastore->comeback_override);
+ ast_free(datastore);
+}
+
+static const struct ast_datastore_info park_common_info = {
+ .type = "park entry data",
+ .destroy = park_common_datastore_destroy,
+};
+
+static void wipe_park_common_datastore(struct ast_channel *chan)
+{
+ struct ast_datastore *datastore;
+
+ ast_channel_lock(chan);
+ datastore = ast_channel_datastore_find(chan, &park_common_info, NULL);
+ if (datastore) {
+ ast_channel_datastore_remove(chan, datastore);
+ ast_datastore_free(datastore);
+ }
+ ast_channel_unlock(chan);
+}
+
+static int setup_park_common_datastore(struct ast_channel *parkee, const char *parker_uuid, const char *comeback_override, int randomize, int time_limit, int silence_announce)
+{
+ struct ast_datastore *datastore = NULL;
+ struct park_common_datastore *park_datastore;
+
+ wipe_park_common_datastore(parkee);
+
+ if (!(datastore = ast_datastore_alloc(&park_common_info, NULL))) {
+ return -1;
+ }
+
+ if (!(park_datastore = ast_calloc(1, sizeof(*park_datastore)))) {
+ ast_datastore_free(datastore);
+ return -1;
+ }
+
+ park_datastore->parker_uuid = ast_strdup(parker_uuid);
+ park_datastore->randomize = randomize;
+ park_datastore->time_limit = time_limit;
+ park_datastore->silence_announce = silence_announce;
+
+ if (comeback_override) {
+ park_datastore->comeback_override = ast_strdup(comeback_override);
+ }
+
+
+ datastore->data = park_datastore;
+ ast_channel_lock(parkee);
+ ast_channel_datastore_add(parkee, datastore);
+ ast_channel_unlock(parkee);
+
+ return 0;
+}
+
+void get_park_common_datastore_data(struct ast_channel *parkee, char **parker_uuid, char **comeback_override,
+ int *randomize, int *time_limit, int *silence_announce)
+{
+ struct ast_datastore *datastore;
+ struct park_common_datastore *data;
+
+ ast_channel_lock(parkee);
+ if (!(datastore = ast_channel_datastore_find(parkee, &park_common_info, NULL))) {
+ ast_channel_unlock(parkee);
+ return;
+ }
+
+ data = datastore->data;
+
+ if (!data) {
+ /* This data should always be populated if this datastore was appended to the channel */
+ ast_assert(0);
+ }
+
+ *parker_uuid = ast_strdup(data->parker_uuid);
+ *randomize = data->randomize;
+ *time_limit = data->time_limit;
+ *silence_announce = data->silence_announce;
+
+ if (data->comeback_override) {
+ *comeback_override = ast_strdup(data->comeback_override);
+ }
+
+ ast_channel_unlock(parkee);
+}
+
+struct ast_bridge *park_common_setup(struct ast_channel *parkee, struct ast_channel *parker, const char *app_data,
+ int *silence_announcements)
+{
+ int use_ringing = 0;
+ int randomize = 0;
+ int time_limit = -1;
+ char *lot_name;
+
+ struct ast_bridge *parking_bridge;
+ RAII_VAR(char *, comeback_override, NULL, ast_free);
+ RAII_VAR(char *, lot_name_app_arg, NULL, ast_free);
+ RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
+
+ if (app_data) {
+ park_app_parse_data(app_data, silence_announcements, &use_ringing, &randomize, &time_limit, &comeback_override, &lot_name_app_arg);
+ }
+
+ lot_name = lot_name_app_arg;
+
+ /* If the name of the parking lot isn't specified in the arguments, find it based on the channel. */
+ if (ast_strlen_zero(lot_name)) {
+ ast_channel_lock(parker);
+ lot_name = ast_strdupa(find_channel_parking_lot_name(parker));
+ ast_channel_unlock(parker);
+ }
+
+ lot = parking_lot_find_by_name(lot_name);
+
+ if (!lot) {
+ ast_log(LOG_ERROR, "Could not find parking lot: '%s'\n", lot_name);
+ return NULL;
+ }
+
+ ao2_lock(lot);
+ parking_bridge = parking_lot_get_bridge(lot);
+ ao2_unlock(lot);
+
+ if (parking_bridge) {
+ /* Apply relevant bridge roles and such to the parking channel */
+ parking_channel_set_roles(parkee, lot, use_ringing);
+ setup_park_common_datastore(parkee, ast_channel_uniqueid(parker), comeback_override, randomize, time_limit,
+ silence_announcements ? *silence_announcements : 0);
+ return parking_bridge;
+ }
+
+ /* Couldn't get the parking bridge. Epic failure. */
+ return NULL;
+}
+
+/* XXX BUGBUG - determining the parker when transferred to deep park priority
+ * Currently all parking by the park application is treated as calls parking themselves.
+ * However, it's possible for calls to be transferred here when the Park application is
+ * set after the first priority of an extension. In that case, there used to be a variable
+ * (BLINDTRANSFER) set indicating which channel placed that call here.
+ *
+ * If BLINDTRANSFER is set, this channel name will need to be referenced in Park events
+ * generated by stasis. Ideally we would get a whole channel snapshot and use that for the
+ * parker, but that would likely require applying the channel snapshot to a channel datastore
+ * on all transfers. Alternatively just the name of the parking channel could be applied along
+ * with an indication that it's dead.
+ */
+int park_app_exec(struct ast_channel *chan, const char *data)
+{
+ RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
+
+ struct ast_bridge_features chan_features;
+ int res;
+ int silence_announcements = 0;
+ const char *blind_transfer;
+
+ ast_channel_lock(chan);
+ if ((blind_transfer = pbx_builtin_getvar_helper(chan, "BLINDTRANSFER"))) {
+ blind_transfer = ast_strdupa(blind_transfer);
+ }
+ ast_channel_unlock(chan);
+
+ /* Handle the common parking setup stuff */
+ if (!(parking_bridge = park_common_setup(chan, chan, data, &silence_announcements))) {
+ if (!silence_announcements && !blind_transfer) {
+ ast_stream_and_wait(chan, "pbx-parkingfailed", "");
+ }
+ return 0;
+ }
+
+ /* Initialize bridge features for the channel. */
+ res = ast_bridge_features_init(&chan_features);
+ if (res) {
+ ast_bridge_features_cleanup(&chan_features);
+ return -1;
+ }
+
+ /* Now for the fun part... park it! */
+ ast_bridge_join(parking_bridge, chan, NULL, &chan_features, NULL, 0);
+
+ /*
+ * 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 only happen with AST_SOFTHANGUP_ASYNCGOTO.
+ */
+ res = -1;
+
+ ast_channel_lock(chan);
+ if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) {
+ res = 0;
+ }
+ ast_channel_unlock(chan);
+
+ ast_bridge_features_cleanup(&chan_features);
+
+ return res;
+}
+
+/* Retrieve a parked call */
+
+int parked_call_app_exec(struct ast_channel *chan, const char *data)
+{
+ RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
+ RAII_VAR(struct parked_user *, pu, NULL, ao2_cleanup); /* Parked user being retrieved */
+ RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
+ struct ast_bridge *retrieval_bridge;
+ int res;
+ int target_space = -1;
+ struct ast_bridge_features chan_features;
+ char *parse;
+ char *lot_name;
+
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(lot_name);
+ AST_APP_ARG(parking_space);
+ AST_APP_ARG(other); /* Any remaining unused arguments */
+ );
+
+ parse = ast_strdupa(data);
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ /* Answer the channel if needed */
+ if (ast_channel_state(chan) != AST_STATE_UP) {
+ ast_answer(chan);
+ }
+
+ lot_name = args.lot_name;
+
+ /* If the name of the parking lot isn't in the arguments, find it based on the channel. */
+ if (ast_strlen_zero(lot_name)) {
+ ast_channel_lock(chan);
+ lot_name = ast_strdupa(find_channel_parking_lot_name(chan));
+ ast_channel_unlock(chan);
+ }
+
+ lot = parking_lot_find_by_name(lot_name);
+
+ if (!lot) {
+ ast_log(LOG_ERROR, "Could not find the requested parking lot\n");
+ ast_stream_and_wait(chan, "pbx-invalidpark", "");
+ return -1;
+ }
+
+ if (!ast_strlen_zero(args.parking_space)) {
+ if (sscanf(args.parking_space, "%d", &target_space) != 1 || target_space < 0) {
+ ast_stream_and_wait(chan, "pbx-invalidpark", "");
+ ast_log(LOG_ERROR, "value '%s' for parking_space argument is invalid. Must be an integer greater than 0.\n", args.parking_space);
+ return -1;
+ }
+ }
+
+ /* Attempt to get the parked user from the parking lot */
+ pu = parking_lot_retrieve_parked_user(lot, target_space);
+ if (!pu) {
+ ast_stream_and_wait(chan, "pbx-invalidpark", "");
+ return -1;
+ }
+
+ /* The parked call needs to know who is retrieving it before we move it out of the parking bridge */
+ pu->retriever = ast_channel_snapshot_create(chan);
+
+ /* Create bridge */
+ retrieval_bridge = ast_bridge_basic_new();
+ if (!retrieval_bridge) {
+ return -1;
+ }
+
+ /* Move the parkee into the new bridge */
+ if (ast_bridge_move(retrieval_bridge, lot->parking_bridge, pu->chan, NULL, 0)) {
+ ast_bridge_destroy(retrieval_bridge);
+ return -1;
+ }
+
+ /* Initialize our bridge features */
+ res = ast_bridge_features_init(&chan_features);
+ if (res) {
+ ast_bridge_destroy(retrieval_bridge);
+ ast_bridge_features_cleanup(&chan_features);
+ return -1;
+ }
+
+ /* Set the features */
+ parked_call_retrieve_enable_features(chan, lot, AST_FEATURE_FLAG_BYCALLER);
+
+ /* If the parkedplay option is set for the caller to hear, play that tone now. */
+ if (lot->cfg->parkedplay & AST_FEATURE_FLAG_BYCALLER) {
+ ast_stream_and_wait(chan, lot->cfg->courtesytone, NULL);
+ }
+
+ /* Now we should try to join the new bridge ourselves... */
+ ast_bridge_join(retrieval_bridge, chan, NULL, &chan_features, NULL, 1);
+
+ ast_bridge_features_cleanup(&chan_features);
+
+ return 0;
+}
+
+struct park_announce_subscription_data {
+ char *parkee_uuid;
+ char *dial_string;
+ char *announce_string;
+};
+
+static void park_announce_subscription_data_destroy(void *data)
+{
+ struct park_announce_subscription_data *pa_data = data;
+ ast_free(pa_data->parkee_uuid);
+ ast_free(pa_data->dial_string);
+ ast_free(pa_data->announce_string);
+ ast_free(pa_data);
+}
+
+static struct park_announce_subscription_data *park_announce_subscription_data_create(const char *parkee_uuid,
+ const char *dial_string,
+ const char *announce_string)
+{
+ struct park_announce_subscription_data *pa_data;
+
+ if (!(pa_data = ast_calloc(1, sizeof(*pa_data)))) {
+ return NULL;
+ }
+
+ if (!(pa_data->parkee_uuid = ast_strdup(parkee_uuid))
+ || !(pa_data->dial_string = ast_strdup(dial_string))
+ || !(pa_data->announce_string = ast_strdup(announce_string))) {
+ park_announce_subscription_data_destroy(pa_data);
+ return NULL;
+ }
+
+ return pa_data;
+}
+
+static void announce_to_dial(char *dial_string, char *announce_string, int parkingspace, struct ast_channel_snapshot *parkee_snapshot)
+{
+ struct ast_channel *dchan;
+ struct outgoing_helper oh = { 0, };
+ int outstate;
+ struct ast_format_cap *cap_slin = ast_format_cap_alloc_nolock();
+ char buf[13];
+ char *dial_tech;
+ char *cur_announce;
+ struct ast_format tmpfmt;
+
+ dial_tech = strsep(&dial_string, "/");
+ ast_verb(3, "Dial Tech,String: (%s,%s)\n", dial_tech, dial_string);
+
+ if (!cap_slin) {
+ ast_log(LOG_WARNING, "PARK: Failed to announce park.\n");
+ goto announce_cleanup;
+ }
+ ast_format_cap_add(cap_slin, ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR, 0));
+
+ snprintf(buf, sizeof(buf), "%d", parkingspace);
+ oh.vars = ast_variable_new("_PARKEDAT", buf, "");
+ dchan = __ast_request_and_dial(dial_tech, cap_slin, NULL, dial_string, 30000,
+ &outstate,
+ parkee_snapshot->caller_number,
+ parkee_snapshot->caller_name,
+ &oh);
+
+ ast_variables_destroy(oh.vars);
+ if (!dchan) {
+ ast_log(LOG_WARNING, "PARK: Unable to allocate announce channel.\n");
+ goto announce_cleanup;
+ }
+
+ ast_verb(4, "Announce Template: %s\n", announce_string);
+
+ for (cur_announce = strsep(&announce_string, ":"); cur_announce; cur_announce = strsep(&announce_string, ":")) {
+ ast_verb(4, "Announce:%s\n", cur_announce);
+ if (!strcmp(cur_announce, "PARKED")) {
+ ast_say_digits(dchan, parkingspace, "", ast_channel_language(dchan));
+ } else {
+ int dres = ast_streamfile(dchan, cur_announce, ast_channel_language(dchan));
+ if (!dres) {
+ dres = ast_waitstream(dchan, "");
+ } else {
+ ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", cur_announce, ast_channel_name(dchan));
+ }
+ }
+ }
+
+ ast_stopstream(dchan);
+ ast_hangup(dchan);
+
+announce_cleanup:
+ cap_slin = ast_format_cap_destroy(cap_slin);
+}
+
+static void park_announce_update_cb(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message)
+{
+ struct park_announce_subscription_data *pa_data = data;
+ char *dial_string = pa_data->dial_string;
+
+ struct ast_parked_call_payload *payload = stasis_message_data(message);
+
+ if (stasis_subscription_final_message(sub, message)) {
+ park_announce_subscription_data_destroy(data);
+ return;
+ }
+
+ if (payload->event_type != PARKED_CALL) {
+ /* We are only concerned with calls parked */
+ return;
+ }
+
+ if (strcmp(payload->parkee->uniqueid, pa_data->parkee_uuid)) {
+ /* We are only concerned with the parkee we are subscribed for. */
+ return;
+ }
+
+ if (!ast_strlen_zero(dial_string)) {
+ announce_to_dial(dial_string, pa_data->announce_string, payload->parkingspace, payload->parkee);
+ }
+
+ *dial_string = '\0'; /* If we observe this dial string on a second pass, we don't want to do anything with it. */
+}
+
+int park_and_announce_app_exec(struct ast_channel *chan, const char *data)
+{
+ struct ast_bridge_features chan_features;
+ char *parse;
+ int res;
+ int silence_announcements = 1;
+
+ struct stasis_subscription *parking_subscription;
+ struct park_announce_subscription_data *pa_data;
+
+ RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
+
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(lot_name);
+ AST_APP_ARG(options);
+ AST_APP_ARG(announce_template);
+ AST_APP_ARG(dial);
+ AST_APP_ARG(others);/* Any remaining unused arguments */
+ );
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_ERROR, "ParkAndAnnounce has required arguments. No arguments were provided.\n");
+ return -1;
+ }
+
+ parse = ast_strdupa(data);
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ if (ast_strlen_zero(args.announce_template)) {
+ /* improperly configured arguments for the application */
+ ast_log(LOG_ERROR, "ParkAndAnnounce requires the announce_template argument.\n");
+ return -1;
+ }
+
+ if (ast_strlen_zero(args.dial)) {
+ /* improperly configured arguments */
+ ast_log(LOG_ERROR, "ParkAndAnnounce requires the dial argument.\n");
+ return -1;
+ }
+
+ if (!strchr(args.dial, '/')) {
+ ast_log(LOG_ERROR, "ParkAndAnnounce dial string '%s' is improperly formed.\n", args.dial);
+ return -1;
+ }
+
+ /* Handle the common parking setup stuff */
+ if (!(parking_bridge = park_common_setup(chan, chan, data, &silence_announcements))) {
+ return 0;
+ }
+
+ /* Initialize bridge features for the channel. */
+ res = ast_bridge_features_init(&chan_features);
+ if (res) {
+ ast_bridge_features_cleanup(&chan_features);
+ return -1;
+ }
+
+ /* subscribe to the parking message so that we can announce once it is parked */
+ pa_data = park_announce_subscription_data_create(ast_channel_uniqueid(chan), args.dial, args.announce_template);
+ if (!pa_data) {
+ return -1;
+ }
+
+ if (!(parking_subscription = stasis_subscribe(ast_parking_topic(), park_announce_update_cb, pa_data))) {
+ /* Failed to create subscription */
+ park_announce_subscription_data_destroy(pa_data);
+ return -1;
+ }
+
+ /* Now for the fun part... park it! */
+ ast_bridge_join(parking_bridge, chan, NULL, &chan_features, NULL, 0);
+
+ /* Toss the subscription since we aren't bridged at this point. */
+ stasis_unsubscribe(parking_subscription);
+
+ /*
+ * 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 only happen with AST_SOFTHANGUP_ASYNCGOTO.
+ */
+ res = -1;
+
+ ast_channel_lock(chan);
+ if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) {
+ res = 0;
+ }
+ ast_channel_unlock(chan);
+
+ ast_bridge_features_cleanup(&chan_features);
+
+ return res;
+}
diff --git a/res/parking/parking_bridge.c b/res/parking/parking_bridge.c
new file mode 100644
index 000000000..b9ceb5203
--- /dev/null
+++ b/res/parking/parking_bridge.c
@@ -0,0 +1,421 @@
+/*
+ * 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"
+
+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)
+{
+ struct parking_lot *lot = self->lot;
+
+ /* Unlink the parking bridge from the parking lot that owns it */
+ lot->parking_bridge = NULL;
+ ao2_ref(lot, -1);
+
+ /* Disassociate the bridge from the parking lot as well. */
+ 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);
+ pu->lot = NULL;
+
+ ao2_cleanup(pu->parker);
+ pu->parker = NULL;
+}
+
+/*!
+ * \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;
+ }
+
+
+ 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);
+
+ /* 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);
+
+ 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;
+
+ 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);
+
+ 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;
+}
diff --git a/res/parking/parking_bridge_features.c b/res/parking/parking_bridge_features.c
new file mode 100644
index 000000000..ddb5e7ff2
--- /dev/null
+++ b/res/parking/parking_bridge_features.c
@@ -0,0 +1,479 @@
+/*
+ * 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 DTMF and Interval features
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "res_parking.h"
+#include "asterisk/utils.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/logger.h"
+#include "asterisk/pbx.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_features.h"
+#include "asterisk/features.h"
+#include "asterisk/say.h"
+#include "asterisk/datastore.h"
+#include "asterisk/stasis.h"
+
+struct parked_subscription_datastore {
+ struct stasis_subscription *parked_subscription;
+};
+
+struct parked_subscription_data {
+ char *parkee_uuid;
+ char parker_uuid[0];
+};
+
+static void parked_subscription_datastore_destroy(void *data)
+{
+ struct parked_subscription_datastore *subscription_datastore = data;
+
+ stasis_unsubscribe(subscription_datastore->parked_subscription);
+ subscription_datastore->parked_subscription = NULL;
+
+ ast_free(subscription_datastore);
+}
+
+static const struct ast_datastore_info parked_subscription_info = {
+ .type = "park subscription",
+ .destroy = parked_subscription_datastore_destroy,
+};
+
+static void wipe_subscription_datastore(struct ast_channel *chan)
+{
+ struct ast_datastore *datastore;
+
+ ast_channel_lock(chan);
+
+ datastore = ast_channel_datastore_find(chan, &parked_subscription_info, NULL);
+ if (datastore) {
+ ast_channel_datastore_remove(chan, datastore);
+ ast_datastore_free(datastore);
+ }
+ ast_channel_unlock(chan);
+}
+
+static void parker_parked_call_message_response(struct ast_parked_call_payload *message, struct parked_subscription_data *data,
+ struct stasis_subscription *sub)
+{
+ const char *parkee_to_act_on = data->parkee_uuid;
+ char saynum_buf[16];
+ struct ast_channel_snapshot *parkee_snapshot = message->parkee;
+ RAII_VAR(struct ast_channel *, parker, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_bridge_channel *, bridge_channel, NULL, ao2_cleanup);
+
+ if (strcmp(parkee_to_act_on, parkee_snapshot->uniqueid)) {
+ return;
+ }
+
+ if (message->event_type != PARKED_CALL && message->event_type != PARKED_CALL_FAILED) {
+ /* We only care about these two event types */
+ return;
+ }
+
+ parker = ast_channel_get_by_name(data->parker_uuid);
+ if (!parker) {
+ return;
+ }
+
+ ast_channel_lock(parker);
+ bridge_channel = ast_channel_get_bridge_channel(parker);
+ ast_channel_unlock(parker);
+ if (!bridge_channel) {
+ return;
+ }
+
+ if (message->event_type == PARKED_CALL) {
+ /* queue the saynum on the bridge channel and hangup */
+ snprintf(saynum_buf, sizeof(saynum_buf), "%u %u", 1, message->parkingspace);
+ ast_bridge_channel_queue_playfile(bridge_channel, say_parking_space, saynum_buf, NULL);
+ wipe_subscription_datastore(bridge_channel->chan);
+ }
+
+ if (message->event_type == PARKED_CALL_FAILED) {
+ ast_bridge_channel_queue_playfile(bridge_channel, NULL, "pbx-parkingfailed", NULL);
+ wipe_subscription_datastore(bridge_channel->chan);
+ }
+}
+
+static void parker_update_cb(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message)
+{
+ if (stasis_subscription_final_message(sub, message)) {
+ ast_free(data);
+ return;
+ }
+
+ if (stasis_message_type(message) == ast_parked_call_type()) {
+ struct ast_parked_call_payload *parked_call_message = stasis_message_data(message);
+ parker_parked_call_message_response(parked_call_message, data, sub);
+ }
+}
+
+static int create_parked_subscription(struct ast_channel *chan, const char *parkee_uuid)
+{
+ struct ast_datastore *datastore;
+ struct parked_subscription_datastore *parked_datastore;
+ struct parked_subscription_data *subscription_data;
+
+ char *parker_uuid = ast_strdupa(ast_channel_uniqueid(chan));
+ size_t parker_uuid_size = strlen(parker_uuid) + 1;
+
+ /* If there is already a subscription, get rid of it. */
+ wipe_subscription_datastore(chan);
+
+ if (!(datastore = ast_datastore_alloc(&parked_subscription_info, NULL))) {
+ return -1;
+ }
+
+ if (!(parked_datastore = ast_calloc(1, sizeof(*parked_datastore)))) {
+ ast_datastore_free(datastore);
+ return -1;
+ }
+
+ if (!(subscription_data = ast_calloc(1, sizeof(*subscription_data) + parker_uuid_size +
+ strlen(parkee_uuid) + 1))) {
+ ast_datastore_free(datastore);
+ ast_free(parked_datastore);
+ return -1;
+ }
+
+ subscription_data->parkee_uuid = subscription_data->parker_uuid + parker_uuid_size;
+ strcpy(subscription_data->parkee_uuid, parkee_uuid);
+ strcpy(subscription_data->parker_uuid, parker_uuid);
+
+ if (!(parked_datastore->parked_subscription = stasis_subscribe(ast_parking_topic(), parker_update_cb, subscription_data))) {
+ return -1;
+ }
+
+ datastore->data = parked_datastore;
+
+ ast_channel_lock(chan);
+ ast_channel_datastore_add(chan, datastore);
+ ast_channel_unlock(chan);
+
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Helper function that creates an outgoing channel and returns it immediately. This function is nearly
+ * identical to the dial_transfer function in bridge_builtin_features.c, however it doesn't swap the
+ * local channel and the channel that instigated the park.
+ */
+static struct ast_channel *park_local_transfer(struct ast_channel *parker, const char *exten, const char *context)
+{
+ RAII_VAR(struct ast_channel *, parkee_side_2, NULL, ao2_cleanup);
+ char destination[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 1];
+ struct ast_channel *parkee;
+ int cause;
+
+ /* Used for side_2 hack */
+ char *parkee_name;
+ char *semi_pos;
+
+ /* Fill the variable with the extension and context we want to call */
+ snprintf(destination, sizeof(destination), "%s@%s", exten, context);
+
+ /* Now we request that chan_local prepare to call the destination */
+ parkee = ast_request("Local", ast_channel_nativeformats(parker), parker, destination,
+ &cause);
+ if (!parkee) {
+ return NULL;
+ }
+
+ /* Before we actually dial out let's inherit appropriate information. */
+ ast_channel_lock_both(parker, parkee);
+ ast_connected_line_copy_from_caller(ast_channel_connected(parkee), ast_channel_caller(parker));
+ ast_channel_inherit_variables(parker, parkee);
+ ast_channel_datastore_inherit(parker, parkee);
+ ast_channel_unlock(parkee);
+ ast_channel_unlock(parker);
+
+ /* BUGBUG Use Richard's unreal channel stuff here instead of this hack */
+ parkee_name = ast_strdupa(ast_channel_name(parkee));
+
+ semi_pos = strrchr(parkee_name, ';');
+ if (!semi_pos) {
+ /* There should always be a semicolon present in the string if is used since it's a local channel. */
+ ast_assert(0);
+ return NULL;
+ }
+
+ parkee_name[(semi_pos - parkee_name) + 1] = '2';
+ parkee_side_2 = ast_channel_get_by_name(parkee_name);
+
+ /* We need to have the parker subscribe to the new local channel before hand. */
+ create_parked_subscription(parker, ast_channel_uniqueid(parkee_side_2));
+
+ pbx_builtin_setvar_helper(parkee_side_2, "BLINDTRANSFER", ast_channel_name(parker));
+
+ /* Since the above worked fine now we actually call it and return the channel */
+ if (ast_call(parkee, destination, 0)) {
+ ast_hangup(parkee);
+ return NULL;
+ }
+
+ return parkee;
+}
+
+static int park_feature_helper(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_exten *park_exten)
+{
+ RAII_VAR(struct ast_channel *, other, NULL, ao2_cleanup);
+ RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
+ RAII_VAR(struct parked_user *, pu, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
+ RAII_VAR(struct ao2_container *, bridge_peers, NULL, ao2_cleanup);
+ struct ao2_iterator iter;
+
+ bridge_peers = ast_bridge_peers(bridge);
+
+ if (ao2_container_count(bridge_peers) < 2) {
+ /* There is nothing to do if there is no one to park. */
+ return 0;
+ }
+
+ if (ao2_container_count(bridge_peers) > 2) {
+ /* With a multiparty bridge, we need to do a regular blind transfer. We link the existing bridge to the parking lot with a
+ * local channel rather than transferring others. */
+ struct ast_channel *transfer_chan = NULL;
+
+ if (!park_exten) {
+ /* This simply doesn't work. The user attempted to one-touch park the parking lot and we can't originate a local channel
+ * without knowing an extension to transfer it to.
+ * XXX However, when parking lots are changed to be able to register extensions then this will be doable. */
+ ast_log(LOG_ERROR, "Can not one-touch park a multiparty bridge.\n");
+ return 0;
+ }
+
+ transfer_chan = park_local_transfer(bridge_channel->chan,
+ ast_get_extension_name(park_exten), ast_get_context_name(ast_get_extension_context(park_exten)));
+
+ if (!transfer_chan) {
+ return 0;
+ }
+
+ if (ast_bridge_impart(bridge_channel->bridge, transfer_chan, NULL, NULL, 1)) {
+ ast_hangup(transfer_chan);
+ }
+
+ return 0;
+ }
+
+ /* Since neither of the above cases were used, we are doing a simple park with a two party bridge. */
+
+ for (iter = ao2_iterator_init(bridge_peers, 0); (other = ao2_iterator_next(&iter)); ao2_ref(other, -1)) {
+ /* We need the channel that isn't the bridge_channel's channel. */
+ if (strcmp(ast_channel_uniqueid(other), ast_channel_uniqueid(bridge_channel->chan))) {
+ break;
+ }
+ }
+ ao2_iterator_destroy(&iter);
+
+ if (!other) {
+ ast_assert(0);
+ return -1;
+ }
+
+ /* Subscribe to park messages with the other channel entering */
+ if (create_parked_subscription(bridge_channel->chan, ast_channel_uniqueid(other))) {
+ return -1;
+ }
+
+ /* Write the park frame with the intended recipient and other data out to the bridge. */
+ ast_bridge_channel_write_park(bridge_channel, ast_channel_uniqueid(other), ast_channel_uniqueid(bridge_channel->chan), ast_get_extension_app_data(park_exten));
+
+ return 0;
+}
+
+static void park_bridge_channel(struct ast_bridge_channel *bridge_channel, const char *uuid_parkee, const char *uuid_parker, const char *app_data)
+{
+ RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_bridge *, original_bridge, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_channel *, parker, NULL, ao2_cleanup);
+
+ if (strcmp(ast_channel_uniqueid(bridge_channel->chan), uuid_parkee)) {
+ /* We aren't the parkee, so ignore this action. */
+ return;
+ }
+
+ parker = ast_channel_get_by_name(uuid_parker);
+
+ if (!parker) {
+ ast_log(LOG_NOTICE, "Channel with uuid %s left before we could start parking the call. Parking canceled.\n", uuid_parker);
+ publish_parked_call_failure(bridge_channel->chan);
+ return;
+ }
+
+ if (!(parking_bridge = park_common_setup(bridge_channel->chan, parker, app_data, NULL))) {
+ publish_parked_call_failure(bridge_channel->chan);
+ return;
+ }
+
+ pbx_builtin_setvar_helper(bridge_channel->chan, "BLINDTRANSFER", ast_channel_name(parker));
+
+ /* bridge_channel must be locked so we can get a reference to the bridge it is currently on */
+ ao2_lock(bridge_channel);
+
+ original_bridge = bridge_channel->bridge;
+ if (!original_bridge) {
+ ao2_unlock(bridge_channel);
+ publish_parked_call_failure(bridge_channel->chan);
+ return;
+ }
+
+ ao2_ref(original_bridge, +1); /* Cleaned by RAII_VAR */
+
+ ao2_unlock(bridge_channel);
+
+ if (ast_bridge_move(parking_bridge, original_bridge, bridge_channel->chan, NULL, 1)) {
+ ast_log(LOG_ERROR, "Failed to move %s into the parking bridge.\n",
+ ast_channel_name(bridge_channel->chan));
+ }
+}
+
+static int feature_park(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+ park_feature_helper(bridge, bridge_channel, NULL);
+ return 0;
+}
+
+static void parking_duration_cb_destroyer(void *hook_pvt)
+{
+ struct parked_user *user = hook_pvt;
+ ao2_ref(user, -1);
+}
+
+/*! \internal
+ * \brief Interval hook. Pulls a parked call from the parking bridge after the timeout is passed and sets the resolution to timeout.
+ *
+ * \param bridge Which bridge the channel was parked in
+ * \param bridge_channel bridge channel this interval hook is being executed on
+ * \param hook_pvt A pointer to the parked_user struct associated with the channel is stuffed in here
+ */
+static int parking_duration_callback(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+ struct parked_user *user = hook_pvt;
+ struct ast_channel *chan = user->chan;
+ char *peername;
+ char parking_space[AST_MAX_EXTENSION];
+
+ /* We are still in the bridge, so it's possible for other stuff to mess with the parked call before we leave the bridge
+ to deal with this, lock the parked user, check and set resolution. */
+ ao2_lock(user);
+ if (user->resolution != PARK_UNSET) {
+ /* Abandon timeout since something else has resolved the parked user before we got to it. */
+ ao2_unlock(user);
+ return -1;
+ }
+
+ user->resolution = PARK_TIMEOUT;
+ ao2_unlock(user);
+
+ ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+
+ /* Set parking timeout channel variables */
+ snprintf(parking_space, sizeof(parking_space), "%d", user->parking_space);
+ pbx_builtin_setvar_helper(chan, "PARKING_SPACE", parking_space);
+ pbx_builtin_setvar_helper(chan, "PARKINGSLOT", parking_space); /* Deprecated version of PARKING_SPACE */
+ pbx_builtin_setvar_helper(chan, "PARKEDLOT", user->lot->name);
+
+ peername = ast_strdupa(user->parker->name);
+ flatten_peername(peername);
+
+ pbx_builtin_setvar_helper(chan, "PARKER", peername);
+
+ /* TODO Dialplan generation for park-dial extensions */
+
+ /* async_goto the proper PBX destination - this should happen when we come out of the bridge */
+ if (!ast_strlen_zero(user->comeback)) {
+ ast_async_parseable_goto(chan, user->comeback);
+ } else {
+ comeback_goto(user, user->lot);
+ }
+
+ return -1;
+}
+
+void say_parking_space(struct ast_bridge_channel *bridge_channel, const char *payload)
+{
+ int numeric_value;
+ int hangup_after;
+
+ if (sscanf(payload, "%u %u", &hangup_after, &numeric_value) != 2) {
+ /* If say_parking_space is called with a non-numeric string, we have a problem. */
+ ast_assert(0);
+ ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+ return;
+ }
+
+ ast_say_digits(bridge_channel->chan, numeric_value, "", ast_channel_language(bridge_channel->chan));
+
+ if (hangup_after) {
+ ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+ }
+}
+
+void parking_set_duration(struct ast_bridge_features *features, struct parked_user *user)
+{
+ unsigned int time_limit;
+
+ time_limit = user->time_limit * 1000;
+
+ if (!time_limit) {
+ /* There is no duration limit that we need to apply. */
+ return;
+ }
+
+ /* If the time limit has already been passed, set a really low time limit so we can kick them out immediately. */
+ time_limit = ast_remaining_ms(user->start, time_limit);
+ if (time_limit <= 0) {
+ time_limit = 1;
+ }
+
+ /* The interval hook is going to need a reference to the parked_user */
+ ao2_ref(user, +1);
+
+ if (ast_bridge_interval_hook(features, time_limit,
+ parking_duration_callback, user, parking_duration_cb_destroyer, 1)) {
+ ast_log(LOG_ERROR, "Failed to apply duration limits to the parking call.\n");
+ }
+}
+
+void unload_parking_bridge_features(void)
+{
+ ast_bridge_features_unregister(AST_BRIDGE_BUILTIN_PARKCALL);
+ ast_uninstall_park_blind_xfer_func();
+ ast_uninstall_bridge_channel_park_func();
+}
+
+int load_parking_bridge_features(void)
+{
+ ast_bridge_features_register(AST_BRIDGE_BUILTIN_PARKCALL, feature_park, NULL);
+ ast_install_park_blind_xfer_func(park_feature_helper);
+ ast_install_bridge_channel_park_func(park_bridge_channel);
+ return 0;
+}
diff --git a/res/parking/parking_controller.c b/res/parking/parking_controller.c
new file mode 100644
index 000000000..03d7b8861
--- /dev/null
+++ b/res/parking/parking_controller.c
@@ -0,0 +1,292 @@
+/*
+ * 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 Entry, Exit, and other assorted controls.
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+#include "asterisk.h"
+
+#include "asterisk/logger.h"
+#include "res_parking.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/utils.h"
+#include "asterisk/manager.h"
+#include "asterisk/test.h"
+#include "asterisk/features.h"
+#include "asterisk/bridging_basic.h"
+
+struct ast_bridge *parking_lot_get_bridge(struct parking_lot *lot)
+{
+ struct ast_bridge *lot_bridge;
+
+ if (lot->parking_bridge) {
+ ao2_ref(lot->parking_bridge, +1);
+ return lot->parking_bridge;
+ }
+
+ lot_bridge = bridge_parking_new(lot);
+ if (!lot_bridge) {
+ return NULL;
+ }
+
+ /* The parking lot needs a reference to the bridge as well. */
+ lot->parking_bridge = lot_bridge;
+ ao2_ref(lot->parking_bridge, +1);
+
+ return lot_bridge;
+}
+
+void parking_channel_set_roles(struct ast_channel *chan, struct parking_lot *lot, int force_ringing)
+{
+ ast_channel_add_bridge_role(chan, "holding_participant");
+ if (force_ringing) {
+ ast_channel_set_bridge_role_option(chan, "holding_participant", "idle_mode", "ringing");
+ } else {
+ ast_channel_set_bridge_role_option(chan, "holding_participant", "idle_mode", "musiconhold");
+ if (!ast_strlen_zero(lot->cfg->mohclass)) {
+ ast_channel_set_bridge_role_option(chan, "holding_participant", "moh_class", lot->cfg->mohclass);
+ }
+ }
+}
+
+struct parking_limits_pvt {
+ struct parked_user *user;
+};
+
+int unpark_parked_user(struct parked_user *pu)
+{
+ if (pu->lot) {
+ ao2_unlink(pu->lot->parked_users, pu);
+ parking_lot_remove_if_unused(pu->lot);
+ return 0;
+ }
+
+ return -1;
+}
+
+int parking_lot_get_space(struct parking_lot *lot, int target_override)
+{
+ int original_target;
+ int current_target;
+ struct ao2_iterator i;
+ struct parked_user *user;
+ int wrap;
+
+ if (lot->cfg->parkfindnext) {
+ /* Use next_space if the lot already has next_space set; otherwise use lot start. */
+ original_target = lot->next_space ? lot->next_space : lot->cfg->parking_start;
+ } else {
+ original_target = lot->cfg->parking_start;
+ }
+
+ if (target_override >= lot->cfg->parking_start && target_override <= lot->cfg->parking_stop) {
+ original_target = target_override;
+ }
+
+ current_target = original_target;
+
+ wrap = lot->cfg->parking_start;
+
+ i = ao2_iterator_init(lot->parked_users, 0);
+ while ((user = ao2_iterator_next(&i))) {
+ /* Increment the wrap on each pass until we find an empty space */
+ if (wrap == user->parking_space) {
+ wrap += 1;
+ }
+
+ if (user->parking_space < current_target) {
+ /* It's lower than the anticipated target, so we haven't reached the target yet. */
+ ao2_ref(user, -1);
+ continue;
+ }
+
+ if (user->parking_space > current_target) {
+ /* The current target is usable because all items below have been read and the next target is higher than the one we want. */
+ ao2_ref(user, -1);
+ break;
+ }
+
+ /* We found one already parked here. */
+ current_target += 1;
+ ao2_ref(user, -1);
+ }
+ ao2_iterator_destroy(&i);
+
+ if (current_target <= lot->cfg->parking_stop) {
+ return current_target;
+ }
+
+ if (wrap <= lot->cfg->parking_stop) {
+ return wrap;
+ }
+
+ return -1;
+}
+
+static int retrieve_parked_user_targeted(void *obj, void *arg, int flags)
+{
+ int *target = arg;
+ struct parked_user *user = obj;
+ if (user->parking_space == *target) {
+ return CMP_MATCH;
+ }
+
+ return 0;
+}
+
+struct parked_user *parking_lot_retrieve_parked_user(struct parking_lot *lot, int target)
+{
+ RAII_VAR(struct parked_user *, user, NULL, ao2_cleanup);
+
+ if (target < 0) {
+ user = ao2_callback(lot->parked_users, 0, NULL, NULL);
+ } else {
+ user = ao2_callback(lot->parked_users, 0, retrieve_parked_user_targeted, &target);
+ }
+
+ if (!user) {
+ return NULL;
+ }
+
+ ao2_lock(user);
+ if (user->resolution != PARK_UNSET) {
+ /* Abandon. Something else has resolved the parked user before we got to it. */
+ ao2_unlock(user);
+ return NULL;
+ }
+
+ ao2_unlink(lot->parked_users, user);
+ user->resolution = PARK_ANSWERED;
+ ao2_unlock(user);
+
+ parking_lot_remove_if_unused(user->lot);
+
+ /* Bump the ref count by 1 since the RAII_VAR will eat the reference otherwise */
+ ao2_ref(user, +1);
+ return user;
+}
+
+void parked_call_retrieve_enable_features(struct ast_channel *chan, struct parking_lot *lot, int recipient_mode)
+{
+ /* Enabling features here should be additive to features that are already on the channel. */
+ struct ast_flags feature_flags = { 0 };
+ struct ast_flags *existing_features;
+
+ ast_channel_lock(chan);
+ existing_features = ast_bridge_features_ds_get(chan);
+
+ if (existing_features) {
+ feature_flags = *existing_features;
+ }
+
+ if (lot->cfg->parkedcalltransfers & recipient_mode) {
+ ast_set_flag(&feature_flags, AST_FEATURE_REDIRECT);
+ ast_set_flag(&feature_flags, AST_FEATURE_ATXFER);
+ }
+
+ if (lot->cfg->parkedcallreparking & recipient_mode) {
+ ast_set_flag(&feature_flags, AST_FEATURE_PARKCALL);
+ }
+
+ if (lot->cfg->parkedcallhangup & recipient_mode) {
+ ast_set_flag(&feature_flags, AST_FEATURE_DISCONNECT);
+ }
+
+ if (lot->cfg->parkedcallrecording & recipient_mode) {
+ ast_set_flag(&feature_flags, AST_FEATURE_AUTOMIXMON);
+ }
+
+ ast_bridge_features_ds_set(chan, &feature_flags);
+ ast_channel_unlock(chan);
+
+ return;
+}
+
+void flatten_peername(char *peername)
+{
+ int i;
+ char *dash;
+
+ /* Truncate after the dash */
+ dash = strrchr(peername, '-');
+ if (dash) {
+ *dash = '\0';
+ }
+
+ /* Replace slashes with underscores since slashes are reserved characters for extension matching */
+ for (i = 0; peername[i]; i++) {
+ if (peername[i] == '/') {
+ /* The underscore is the flattest character of all. */
+ peername[i] = '_';
+ }
+ }
+}
+
+int comeback_goto(struct parked_user *pu, struct parking_lot *lot)
+{
+ struct ast_channel *chan = pu->chan;
+ char *peername;
+ const char *blindtransfer;
+
+ ast_channel_lock(chan);
+ if ((blindtransfer = pbx_builtin_getvar_helper(chan, "BLINDTRANSFER"))) {
+ blindtransfer = ast_strdupa(blindtransfer);
+ }
+ ast_channel_unlock(chan);
+
+ peername = blindtransfer ? ast_strdupa(blindtransfer) : ast_strdupa(pu->parker->name);
+
+ /* XXX Comeback to origin mode: Generate an extension in park-dial to Dial the peer */
+
+
+ /* Flatten the peername so that it can be used for performing the timeout PBX operations */
+ flatten_peername(peername);
+
+ if (lot->cfg->comebacktoorigin) {
+ if (ast_exists_extension(chan, PARK_DIAL_CONTEXT, peername, 1, NULL)) {
+ ast_async_goto(chan, PARK_DIAL_CONTEXT, peername, 1);
+ return 0;
+ } else {
+ ast_log(LOG_ERROR, "Can not start %s at %s,%s,1 because extension does not exist. Terminating call.\n",
+ ast_channel_name(chan), PARK_DIAL_CONTEXT, peername);
+ return -1;
+ }
+ }
+
+ if (ast_exists_extension(chan, lot->cfg->comebackcontext, peername, 1, NULL)) {
+ ast_async_goto(chan, lot->cfg->comebackcontext, peername, 1);
+ return 0;
+ }
+
+ if (ast_exists_extension(chan, lot->cfg->comebackcontext, "s", 1, NULL)) {
+ ast_verb(2, "Could not start %s at %s,%s,1. Using 's@%s' instead.\n", ast_channel_name(chan),
+ lot->cfg->comebackcontext, peername, lot->cfg->comebackcontext);
+ ast_async_goto(chan, lot->cfg->comebackcontext, "s", 1);
+ return 0;
+ }
+
+ ast_verb(2, "Can not start %s at %s,%s,1 and exten 's@%s' does not exist. Using 's@default'\n",
+ ast_channel_name(chan),
+ lot->cfg->comebackcontext, peername, lot->cfg->comebackcontext);
+ ast_async_goto(chan, "default", "s", 1);
+
+ return 0;
+}
diff --git a/res/parking/parking_manager.c b/res/parking/parking_manager.c
new file mode 100644
index 000000000..d6a05573f
--- /dev/null
+++ b/res/parking/parking_manager.c
@@ -0,0 +1,610 @@
+/*
+ * 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 Call Parking Manager Actions and Events
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "res_parking.h"
+#include "asterisk/config.h"
+#include "asterisk/config_options.h"
+#include "asterisk/event.h"
+#include "asterisk/utils.h"
+#include "asterisk/module.h"
+#include "asterisk/cli.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/features.h"
+#include "asterisk/manager.h"
+
+/*** DOCUMENTATION
+ <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>
+ <manager name="ParkedCalls" language="en_US">
+ <synopsis>
+ List parked calls.
+ </synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+ <parameter name="ParkingLot">
+ <para>If specified, only show parked calls from the parking lot with this name.</para>
+ </parameter>
+ </syntax>
+ <description>
+ <para>List parked calls.</para>
+ </description>
+ </manager>
+ <managerEvent language="en_US" name="ParkedCall">
+ <managerEventInstance class="EVENT_FLAG_CALL">
+ <synopsis>Raised when a channel is parked.</synopsis>
+ <syntax>
+ <parameter name="ChannelParkee">
+ </parameter>
+ <parameter name="ChannelStateParkee">
+ <para>A numeric code for the channel's current state, related to ChannelStateDesc</para>
+ </parameter>
+ <parameter name="ChannelStateDescParkee">
+ <enumlist>
+ <enum name="Down"/>
+ <enum name="Rsrvd"/>
+ <enum name="OffHook"/>
+ <enum name="Dialing"/>
+ <enum name="Ring"/>
+ <enum name="Ringing"/>
+ <enum name="Up"/>
+ <enum name="Busy"/>
+ <enum name="Dialing Offhook"/>
+ <enum name="Pre-ring"/>
+ <enum name="Unknown"/>
+ </enumlist>
+ </parameter>
+ <parameter name="CallerIDNumParkee">
+ </parameter>
+ <parameter name="CallerIDNameParkee">
+ </parameter>
+ <parameter name="ConnectedLineNumParkee">
+ </parameter>
+ <parameter name="ConnectedLineNameParkee">
+ </parameter>
+ <parameter name="AccountCodeParkee">
+ </parameter>
+ <parameter name="ContextParkee">
+ </parameter>
+ <parameter name="ExtenParkee">
+ </parameter>
+ <parameter name="PriorityParkee">
+ </parameter>
+ <parameter name="UniqueidParkee">
+ </parameter>
+ <parameter name="ChannelParker">
+ </parameter>
+ <parameter name="ChannelStateParker">
+ <para>A numeric code for the channel's current state, related to ChannelStateDesc</para>
+ </parameter>
+ <parameter name="ChannelStateDescParker">
+ <enumlist>
+ <enum name="Down"/>
+ <enum name="Rsrvd"/>
+ <enum name="OffHook"/>
+ <enum name="Dialing"/>
+ <enum name="Ring"/>
+ <enum name="Ringing"/>
+ <enum name="Up"/>
+ <enum name="Busy"/>
+ <enum name="Dialing Offhook"/>
+ <enum name="Pre-ring"/>
+ <enum name="Unknown"/>
+ </enumlist>
+ </parameter>
+ <parameter name="CallerIDNumParker">
+ </parameter>
+ <parameter name="CallerIDNameParker">
+ </parameter>
+ <parameter name="ConnectedLineNumParker">
+ </parameter>
+ <parameter name="ConnectedLineNameParker">
+ </parameter>
+ <parameter name="AccountCodeParker">
+ </parameter>
+ <parameter name="ContextParker">
+ </parameter>
+ <parameter name="ExtenParker">
+ </parameter>
+ <parameter name="PriorityParker">
+ </parameter>
+ <parameter name="UniqueidParker">
+ </parameter>
+ <parameter name="Parkinglot">
+ <para>Name of the parking lot that the parkee is parked in</para>
+ </parameter>
+ <parameter name="ParkingSpace">
+ <para>Parking Space that the parkee is parked in</para>
+ </parameter>
+ <parameter name="ParkingTimeout">
+ <para>Time remaining until the parkee is forcefully removed from parking in seconds</para>
+ </parameter>
+ <parameter name="ParkingDuration">
+ <para>Time the parkee has been in the parking bridge (in seconds)</para>
+ </parameter>
+ </syntax>
+ </managerEventInstance>
+ </managerEvent>
+ <managerEvent language="en_US" name="ParkedCallTimeOut">
+ <managerEventInstance class="EVENT_FLAG_CALL">
+ <synopsis>Raised when a channel leaves a parking lot due to reaching the time limit of being parked.</synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter)" />
+ </syntax>
+ </managerEventInstance>
+ </managerEvent>
+ <managerEvent language="en_US" name="ParkedCallGiveUp">
+ <managerEventInstance class="EVENT_FLAG_CALL">
+ <synopsis>Raised when a channel leaves a parking lot because it hung up without being answered.</synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter)" />
+ </syntax>
+ </managerEventInstance>
+ </managerEvent>
+ <managerEvent language="en_US" name="UnParkedCall">
+ <managerEventInstance class="EVENT_FLAG_CALL">
+ <synopsis>Raised when a channel leaves a parking lot because it was retrieved from the parking lot and reconnected.</synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter)" />
+ <parameter name="ChannelRetriever">
+ </parameter>
+ <parameter name="ChannelStateRetriever">
+ <para>A numeric code for the channel's current state, related to ChannelStateDesc</para>
+ </parameter>
+ <parameter name="ChannelStateDescRetriever">
+ <enumlist>
+ <enum name="Down"/>
+ <enum name="Rsrvd"/>
+ <enum name="OffHook"/>
+ <enum name="Dialing"/>
+ <enum name="Ring"/>
+ <enum name="Ringing"/>
+ <enum name="Up"/>
+ <enum name="Busy"/>
+ <enum name="Dialing Offhook"/>
+ <enum name="Pre-ring"/>
+ <enum name="Unknown"/>
+ </enumlist>
+ </parameter>
+ <parameter name="CallerIDNumRetriever">
+ </parameter>
+ <parameter name="CallerIDNameRetriever">
+ </parameter>
+ <parameter name="ConnectedLineNumRetriever">
+ </parameter>
+ <parameter name="ConnectedLineNameRetriever">
+ </parameter>
+ <parameter name="AccountCodeRetriever">
+ </parameter>
+ <parameter name="ContextRetriever">
+ </parameter>
+ <parameter name="ExtenRetriever">
+ </parameter>
+ <parameter name="PriorityRetriever">
+ </parameter>
+ <parameter name="UniqueidRetriever">
+ </parameter>
+ </syntax>
+ </managerEventInstance>
+ </managerEvent>
+ ***/
+
+/*! \brief subscription to the parking lot topic */
+static struct stasis_subscription *parking_sub;
+
+static struct ast_parked_call_payload *parked_call_payload_from_failure(struct ast_channel *chan)
+{
+ RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_channel_snapshot *, parkee_snapshot, NULL, ao2_cleanup);
+
+ parkee_snapshot = ast_channel_snapshot_create(chan);
+ if (!parkee_snapshot) {
+ return NULL;
+ }
+
+ return ast_parked_call_payload_create(PARKED_CALL_FAILED, parkee_snapshot, NULL, NULL, NULL, 0, 0, 0);
+}
+
+static struct ast_parked_call_payload *parked_call_payload_from_parked_user(struct parked_user *pu, enum ast_parked_call_event_type event_type)
+{
+ RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_channel_snapshot *, parkee_snapshot, NULL, ao2_cleanup);
+ long int timeout;
+ long int duration;
+ struct timeval now = ast_tvnow();
+ const char *lot_name = pu->lot->name;
+
+ if (!pu->parker) {
+ return NULL;
+ }
+
+ parkee_snapshot = ast_channel_snapshot_create(pu->chan);
+
+ if (!parkee_snapshot) {
+ return NULL;
+ }
+
+ timeout = pu->start.tv_sec + (long) pu->time_limit - now.tv_sec;
+ duration = now.tv_sec - pu->start.tv_sec;
+
+ return ast_parked_call_payload_create(event_type, parkee_snapshot, pu->parker, pu->retriever, lot_name, pu->parking_space, timeout, duration);
+
+}
+
+/*! \brief Builds a manager string based on the contents of a parked call payload */
+static struct ast_str *manager_build_parked_call_string(const struct ast_parked_call_payload *payload)
+{
+ struct ast_str *out = ast_str_create(1024);
+ RAII_VAR(struct ast_str *, parkee_string, NULL, ast_free);
+ RAII_VAR(struct ast_str *, parker_string, NULL, ast_free);
+ RAII_VAR(struct ast_str *, retriever_string, NULL, ast_free);
+
+ if (!out) {
+ return NULL;
+ }
+
+ parkee_string = ast_manager_build_channel_state_string_suffix(payload->parkee, "Parkee");
+
+ if (payload->parker) {
+ parker_string = ast_manager_build_channel_state_string_suffix(payload->parker, "Parker");
+ }
+
+ if (payload->retriever) {
+ retriever_string = ast_manager_build_channel_state_string_suffix(payload->retriever, "Retriever");
+ }
+
+ ast_str_set(&out, 0,
+ "%s" /* parkee channel state */
+ "%s" /* parker channel state */
+ "%s" /* retriever channel state (when available) */
+ "Parkinglot: %s\r\n"
+ "ParkingSpace: %u\r\n"
+ "ParkingTimeout: %lu\r\n"
+ "ParkingDuration: %lu\r\n",
+
+ ast_str_buffer(parkee_string),
+ parker_string ? ast_str_buffer(parker_string) : "",
+ retriever_string ? ast_str_buffer(retriever_string) : "",
+ payload->parkinglot,
+ payload->parkingspace,
+ payload->timeout,
+ payload->duration);
+
+ return out;
+}
+
+static int manager_parking_status_single_lot(struct mansession *s, const struct message *m, const char *id_text, const char *lot_name)
+{
+ RAII_VAR(struct parking_lot *, curlot, NULL, ao2_cleanup);
+ struct parked_user *curuser;
+ struct ao2_iterator iter_users;
+ int total = 0;
+
+ curlot = parking_lot_find_by_name(lot_name);
+
+ if (!curlot) {
+ astman_send_error(s, m, "Requested parking lot could not be found.");
+ return RESULT_SUCCESS;
+ }
+
+ astman_send_ack(s, m, "Parked calls will follow");
+
+ iter_users = ao2_iterator_init(curlot->parked_users, 0);
+ while ((curuser = ao2_iterator_next(&iter_users))) {
+ RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_str *, parked_call_string, NULL, ast_free);
+
+ payload = parked_call_payload_from_parked_user(curuser, PARKED_CALL);
+ if (!payload) {
+ astman_send_error(s, m, "Failed to retrieve parking data about a parked user.");
+ return RESULT_FAILURE;
+ }
+
+ parked_call_string = manager_build_parked_call_string(payload);
+ if (!parked_call_string) {
+ astman_send_error(s, m, "Failed to retrieve parkingd ata about a parked user.");
+ return RESULT_FAILURE;
+ }
+
+ total++;
+
+ astman_append(s, "Event: ParkedCall\r\n"
+ "%s" /* The parked call string */
+ "%s" /* The action ID */
+ "\r\n",
+ ast_str_buffer(parked_call_string),
+ id_text);
+
+ ao2_ref(curuser, -1);
+ }
+
+ ao2_iterator_destroy(&iter_users);
+
+ astman_append(s,
+ "Event: ParkedCallsComplete\r\n"
+ "Total: %d\r\n"
+ "%s"
+ "\r\n",
+ total, id_text);
+
+ return RESULT_SUCCESS;
+}
+
+static int manager_parking_status_all_lots(struct mansession *s, const struct message *m, const char *id_text)
+{
+ struct parked_user *curuser;
+ struct ao2_container *lot_container;
+ struct ao2_iterator iter_lots;
+ struct ao2_iterator iter_users;
+ struct parking_lot *curlot;
+ int total = 0;
+
+ lot_container = get_parking_lot_container();
+
+ if (!lot_container) {
+ ast_log(LOG_ERROR, "Failed to obtain parking lot list. Action canceled.\n");
+ astman_send_error(s, m, "Could not create parking lot list");
+ return RESULT_SUCCESS;
+ }
+
+ iter_lots = ao2_iterator_init(lot_container, 0);
+
+ astman_send_ack(s, m, "Parked calls will follow");
+
+ while ((curlot = ao2_iterator_next(&iter_lots))) {
+ iter_users = ao2_iterator_init(curlot->parked_users, 0);
+ while ((curuser = ao2_iterator_next(&iter_users))) {
+ RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_str *, parked_call_string, NULL, ast_free);
+
+ payload = parked_call_payload_from_parked_user(curuser, PARKED_CALL);
+ if (!payload) {
+ return RESULT_FAILURE;
+ }
+
+ parked_call_string = manager_build_parked_call_string(payload);
+ if (!payload) {
+ return RESULT_FAILURE;
+ }
+
+ total++;
+
+ astman_append(s, "Event: ParkedCall\r\n"
+ "%s" /* The parked call string */
+ "%s" /* The action ID */
+ "\r\n",
+ ast_str_buffer(parked_call_string),
+ id_text);
+
+ ao2_ref(curuser, -1);
+ }
+ ao2_iterator_destroy(&iter_users);
+ ao2_ref(curlot, -1);
+ }
+
+ ao2_iterator_destroy(&iter_lots);
+
+ astman_append(s,
+ "Event: ParkedCallsComplete\r\n"
+ "Total: %d\r\n"
+ "%s"
+ "\r\n",
+ total, id_text);
+
+ return RESULT_SUCCESS;
+}
+
+static int manager_parking_status(struct mansession *s, const struct message *m)
+{
+ const char *id = astman_get_header(m, "ActionID");
+ const char *lot_name = astman_get_header(m, "ParkingLot");
+ char id_text[256] = "";
+
+ if (!ast_strlen_zero(id)) {
+ snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", id);
+ }
+
+ if (!ast_strlen_zero(lot_name)) {
+ return manager_parking_status_single_lot(s, m, id_text, lot_name);
+ }
+
+ return manager_parking_status_all_lots(s, m, id_text);
+
+}
+
+static int manager_append_event_parking_lot_data_cb(void *obj, void *arg, void *data, int flags)
+{
+ struct parking_lot *curlot = obj;
+ struct mansession *s = arg;
+ char *id_text = data;
+
+ astman_append(s, "Event: Parkinglot\r\n"
+ "Name: %s\r\n"
+ "StartSpace: %d\r\n"
+ "StopSpace: %d\r\n"
+ "Timeout: %d\r\n"
+ "%s" /* The Action ID */
+ "\r\n",
+ curlot->name,
+ curlot->cfg->parking_start,
+ curlot->cfg->parking_stop,
+ curlot->cfg->parkingtime,
+ id_text);
+
+ return 0;
+}
+
+static int manager_parking_lot_list(struct mansession *s, const struct message *m)
+{
+ const char *id = astman_get_header(m, "ActionID");
+ char id_text[256] = "";
+ struct ao2_container *lot_container;
+
+ if (!ast_strlen_zero(id)) {
+ snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", id);
+ }
+
+ lot_container = get_parking_lot_container();
+
+ if (!lot_container) {
+ ast_log(LOG_ERROR, "Failed to obtain parking lot list. Action canceled.\n");
+ astman_send_error(s, m, "Could not create parking lot list");
+ return -1;
+ }
+
+ astman_send_ack(s, m, "Parking lots will follow");
+
+ ao2_callback_data(lot_container, OBJ_MULTIPLE | OBJ_NODATA, manager_append_event_parking_lot_data_cb, s, id_text);
+
+ astman_append(s,
+ "Event: ParkinglotsComplete\r\n"
+ "%s"
+ "\r\n",id_text);
+
+ return RESULT_SUCCESS;
+}
+
+void publish_parked_call_failure(struct ast_channel *parkee)
+{
+ RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
+ RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+ payload = parked_call_payload_from_failure(parkee);
+ if (!payload) {
+ return;
+ }
+
+ msg = stasis_message_create(ast_parked_call_type(), payload);
+ if (!msg) {
+ return;
+ }
+
+ stasis_publish(ast_parking_topic(), msg);
+}
+
+void publish_parked_call(struct parked_user *pu, enum ast_parked_call_event_type event_type)
+{
+ RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
+ RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+ payload = parked_call_payload_from_parked_user(pu, event_type);
+ if (!payload) {
+ return;
+ }
+
+ msg = stasis_message_create(ast_parked_call_type(), payload);
+ if (!msg) {
+ return;
+ }
+
+ stasis_publish(ast_parking_topic(), msg);
+}
+
+static void parked_call_message_response(struct ast_parked_call_payload *parked_call)
+{
+ char *event_type = "";
+ RAII_VAR(struct ast_str *, parked_call_string, NULL, ast_free);
+
+ switch (parked_call->event_type) {
+ case PARKED_CALL:
+ event_type = "ParkedCall";
+ break;
+ case PARKED_CALL_TIMEOUT:
+ event_type = "ParkedCallTimeOut";
+ break;
+ case PARKED_CALL_GIVEUP:
+ event_type = "ParkedCallGiveUp";
+ break;
+ case PARKED_CALL_UNPARKED:
+ event_type = "UnParkedCall";
+ break;
+ case PARKED_CALL_FAILED:
+ /* PARKED_CALL_FAILED doesn't currently get a message and is used exclusively for bridging */
+ return;
+ }
+
+ parked_call_string = manager_build_parked_call_string(parked_call);
+ if (!parked_call_string) {
+ ast_log(LOG_ERROR, "Failed to issue an AMI event of '%s' in response to a stasis message.\n", event_type);
+ return;
+ }
+
+ manager_event(EVENT_FLAG_CALL, event_type,
+ "%s",
+ ast_str_buffer(parked_call_string)
+ );
+}
+
+static void parking_event_cb(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message)
+{
+ if (stasis_message_type(message) == ast_parked_call_type()) {
+ struct ast_parked_call_payload *parked_call_message = stasis_message_data(message);
+ parked_call_message_response(parked_call_message);
+ }
+}
+
+static void parking_manager_enable_stasis(void)
+{
+ ast_parking_stasis_init();
+ if (!parking_sub) {
+ parking_sub = stasis_subscribe(ast_parking_topic(), parking_event_cb, NULL);
+ }
+}
+
+int load_parking_manager(void)
+{
+ int res;
+
+ res = ast_manager_register_xml_core("Parkinglots", 0, manager_parking_lot_list);
+ res |= ast_manager_register_xml_core("ParkedCalls", 0, manager_parking_status);
+ /* TODO Add a 'Park' manager action */
+ parking_manager_enable_stasis();
+ return res ? -1 : 0;
+}
+
+static void parking_manager_disable_stasis(void)
+{
+ parking_sub = stasis_unsubscribe(parking_sub);
+ ast_parking_stasis_disable();
+}
+
+void unload_parking_manager(void)
+{
+ ast_manager_unregister("Parkinglots");
+ ast_manager_unregister("ParkedCalls");
+ parking_manager_disable_stasis();
+}
diff --git a/res/parking/parking_ui.c b/res/parking/parking_ui.c
new file mode 100644
index 000000000..5b432b689
--- /dev/null
+++ b/res/parking/parking_ui.c
@@ -0,0 +1,199 @@
+/*
+ * 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 Call Parking CLI commands
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "res_parking.h"
+#include "asterisk/config.h"
+#include "asterisk/config_options.h"
+#include "asterisk/event.h"
+#include "asterisk/utils.h"
+#include "asterisk/module.h"
+#include "asterisk/cli.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/features.h"
+#include "asterisk/manager.h"
+
+static void display_parked_call(struct parked_user *user, int fd)
+{
+ ast_cli(fd, " Space: %d\n", user->parking_space);
+ ast_cli(fd, " Channel: %s\n", ast_channel_name(user->chan));
+ ast_cli(fd, " Parker: %s\n", user->parker ? user->parker->name : "<unknown>");
+ ast_cli(fd, "\n");
+}
+
+static int display_parked_users_cb(void *obj, void *arg, int flags)
+{
+ int *fd = arg;
+ struct parked_user *user = obj;
+ display_parked_call(user, *fd);
+ return 0;
+}
+
+static void display_parking_lot(struct parking_lot *lot, int fd)
+{
+ ast_cli(fd, "Parking Lot: %s\n--------------------------------------------------------------------------\n", lot->name);
+ ast_cli(fd, "Parking Extension : %s\n", lot->cfg->parkext);
+ ast_cli(fd, "Parking Context : %s\n", lot->cfg->parking_con);
+ ast_cli(fd, "Parking Spaces : %d-%d\n", lot->cfg->parking_start, lot->cfg->parking_stop);
+ ast_cli(fd, "Parking Time : %u sec\n", lot->cfg->parkingtime);
+ ast_cli(fd, "Comeback to Origin : %s\n", lot->cfg->comebacktoorigin ? "yes" : "no");
+ ast_cli(fd, "Comeback Context : %s%s\n", lot->cfg->comebackcontext, lot->cfg->comebacktoorigin ? " (comebacktoorigin=yes, not used)" : "");
+ ast_cli(fd, "Comeback Dial Time : %u sec\n", lot->cfg->comebackdialtime);
+ ast_cli(fd, "MusicOnHold Class : %s\n", lot->cfg->mohclass);
+ ast_cli(fd, "Enabled : %s\n", (lot->mode == PARKINGLOT_DISABLED) ? "no" : "yes");
+ ast_cli(fd, "\n");
+}
+
+static int display_parking_lot_cb(void *obj, void *arg, int flags)
+{
+ int *fd = arg;
+ struct parking_lot *lot = obj;
+ display_parking_lot(lot, *fd);
+ return 0;
+}
+
+static void cli_display_parking_lot(int fd, const char *name)
+{
+ RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
+ lot = parking_lot_find_by_name(name);
+
+ /* If the parking lot couldn't be found with the search, also abort. */
+ if (!lot) {
+ ast_cli(fd, "Could not find parking lot '%s'\n\n", name);
+ return;
+ }
+
+ display_parking_lot(lot, fd);
+
+ ast_cli(fd, "Parked Calls\n------------\n");
+
+ if (!ao2_container_count(lot->parked_users)) {
+ ast_cli(fd, " (none)\n");
+ ast_cli(fd, "\n\n");
+ return;
+ }
+
+ ao2_callback(lot->parked_users, OBJ_MULTIPLE | OBJ_NODATA, display_parked_users_cb, &fd);
+ ast_cli(fd, "\n");
+}
+
+static void cli_display_parking_lot_list(int fd)
+{
+ struct ao2_container *lot_container;
+
+ lot_container = get_parking_lot_container();
+
+ if (!lot_container) {
+ ast_cli(fd, "Failed to obtain parking lot list.\n\n");
+ return;
+ }
+
+ ao2_callback(lot_container, OBJ_MULTIPLE | OBJ_NODATA, display_parking_lot_cb, &fd);
+ ast_cli(fd, "\n");
+}
+
+struct parking_lot_complete {
+ int seeking; /*! Nth match to return. */
+ int which; /*! Which match currently on. */
+};
+
+static int complete_parking_lot_search(void *obj, void *arg, void *data, int flags)
+{
+ struct parking_lot_complete *search = data;
+ if (++search->which > search->seeking) {
+ return CMP_MATCH;
+ }
+ return 0;
+}
+
+static char *complete_parking_lot(const char *word, int seeking)
+{
+ char *ret = NULL;
+ struct parking_lot *lot;
+ struct ao2_container *global_lots = get_parking_lot_container();
+ struct parking_lot_complete search = {
+ .seeking = seeking,
+ };
+
+ lot = ao2_callback_data(global_lots, ast_strlen_zero(word) ? 0 : OBJ_PARTIAL_KEY,
+ complete_parking_lot_search, (char *) word, &search);
+
+ if (!lot) {
+ return NULL;
+ }
+
+ ret = ast_strdup(lot->name);
+ ao2_ref(lot, -1);
+ return ret;
+}
+
+/* \brief command parking show <name> */
+static char *handle_show_parking_lot_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "parking show";
+ e->usage =
+ "Usage: parking show [name]\n"
+ " Shows a list of parking lots or details of a specific parking lot.";
+ return NULL;
+ case CLI_GENERATE:
+ if (a->pos == 2) {
+ return complete_parking_lot(a->word, a->n);
+ }
+ return NULL;
+ }
+
+ ast_cli(a->fd, "\n");
+
+ if (a->argc == 2) {
+ cli_display_parking_lot_list(a->fd);
+ return CLI_SUCCESS;
+ }
+
+ if (a->argc == 3) {
+ cli_display_parking_lot(a->fd, a->argv[2]);
+ return CLI_SUCCESS;
+ }
+
+ return CLI_SHOWUSAGE;
+}
+
+static struct ast_cli_entry cli_parking_lot[] = {
+ AST_CLI_DEFINE(handle_show_parking_lot_cmd, "Show a parking lot or a list of all parking lots."),
+};
+
+int load_parking_ui(void)
+{
+ return ast_cli_register_multiple(cli_parking_lot, ARRAY_LEN(cli_parking_lot));
+}
+
+void unload_parking_ui(void)
+{
+ ast_cli_unregister_multiple(cli_parking_lot, ARRAY_LEN(cli_parking_lot));
+}
diff --git a/res/parking/res_parking.h b/res/parking/res_parking.h
new file mode 100644
index 000000000..7f66d6e8f
--- /dev/null
+++ b/res/parking/res_parking.h
@@ -0,0 +1,436 @@
+/*
+ * 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 Call Parking Resource Internal API
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+#include "asterisk/pbx.h"
+#include "asterisk/bridging.h"
+#include "asterisk/parking.h"
+#include "asterisk/stasis_channels.h"
+
+#define DEFAULT_PARKING_LOT "default"
+#define DEFAULT_PARKING_EXTEN "700"
+#define PARK_DIAL_CONTEXT "park-dial"
+
+enum park_call_resolution {
+ PARK_UNSET = 0, /*! Nothing set a resolution. This should never be observed in practice. */
+ PARK_ABANDON, /*! The channel for the parked call hung up */
+ PARK_TIMEOUT, /*! The parked call stayed parked until the parking lot timeout was reached and was removed */
+ PARK_FORCED, /*! The parked call was forcibly terminated by an unusual means in Asterisk */
+ PARK_ANSWERED, /*! The parked call was retrieved successfully */
+};
+
+enum parked_call_feature_options {
+ OPT_PARKEDPLAY = 0,
+ OPT_PARKEDTRANSFERS,
+ OPT_PARKEDREPARKING,
+ OPT_PARKEDHANGUP,
+ OPT_PARKEDRECORDING,
+};
+
+enum parking_lot_modes {
+ PARKINGLOT_NORMAL = 0, /*! The parking lot is configured normally and can accept new calls. Disable on reload if the config isn't replaced.
+ * valid transitions: PARKINGLOT_DISABLED */
+ PARKINGLOT_DYNAMIC, /*! The parking lot is a dynamically created parking lot. It can be parked to at any time. Disabled on last parked call leaving.
+ * valid transitions: PARKINGLOT_DISABLED */
+ PARKINGLOT_DISABLED, /*! The parking lot is no longer linked to a parking lot in configuration. It can no longer be parked to.
+ * and it can not be parked to. This mode has no transitions. */
+};
+
+struct parking_lot_cfg {
+ int parking_start; /*!< First space in the parking lot */
+ int parking_stop; /*!< Last space in the parking lot */
+
+ unsigned int parkingtime; /*!< Analogous to parkingtime config option */
+ unsigned int comebackdialtime; /*!< Analogous to comebackdialtime config option */
+ unsigned int parkfindnext; /*!< Analogous to parkfindnext config option */
+ unsigned int parkext_exclusive; /*!< Analogous to parkext_exclusive config option */
+ unsigned int parkaddhints; /*!< Analogous to parkaddhints config option */
+ unsigned int comebacktoorigin; /*!< Analogous to comebacktoorigin config option */
+ int parkedplay; /*!< Analogous to parkedplay config option */
+ int parkedcalltransfers; /*!< Analogous to parkedcalltransfers config option */
+ int parkedcallreparking; /*!< Analogous to parkedcallreparking config option */
+ int parkedcallhangup; /*!< Analogous to parkedcallhangup config option */
+ int parkedcallrecording; /*!< Analogous to parkedcallrecording config option */
+
+ AST_DECLARE_STRING_FIELDS(
+ AST_STRING_FIELD(name); /*!< Name of the parking lot configuration object */
+ AST_STRING_FIELD(mohclass); /*!< Analogous to mohclass config option */
+ AST_STRING_FIELD(parkext); /*!< Analogous to parkext config option */
+ AST_STRING_FIELD(parking_con); /*!< Analogous to context config option */
+ AST_STRING_FIELD(comebackcontext); /*!< Analogous to comebackcontext config option */
+ AST_STRING_FIELD(courtesytone); /*!< Analogous to courtesytone config option */
+ );
+};
+
+struct parking_lot {
+ int next_space; /*!< When using parkfindnext, which space we should start searching from next time we park */
+ struct ast_bridge *parking_bridge; /*!< Bridged where parked calls will rest until they are answered or otherwise leave */
+ struct ao2_container *parked_users; /*!< List of parked users rigidly ordered by their parking space */
+ struct parking_lot_cfg *cfg; /*!< Reference to configuration object for the parking lot */
+ enum parking_lot_modes mode; /*!< Whether a parking lot is operational, being reconfigured, primed for deletion, or dynamically created. */
+ int disable_mark; /*!< On reload, disable this parking lot if it doesn't receive a new configuration. */
+
+ AST_DECLARE_STRING_FIELDS(
+ AST_STRING_FIELD(name); /*!< Name of the parking lot object */
+ );
+};
+
+struct parked_user {
+ struct ast_channel *chan; /*!< Parked channel */
+ struct ast_channel_snapshot *parker; /*!< Snapshot of the channel that parked the call at the time of parking */
+ struct ast_channel_snapshot *retriever; /*!< Snapshot of the channel that retrieves a parked call */
+ struct timeval start; /*!< When the call was parked */
+ int parking_space; /*!< Which parking space is used */
+ char comeback[AST_MAX_CONTEXT]; /*!< Where to go on parking timeout */
+ unsigned int time_limit; /*!< How long this specific channel may remain in the parking lot before timing out */
+ struct parking_lot *lot; /*!< Which parking lot the user is parked to */
+ enum park_call_resolution resolution; /*!< How did the parking session end? If the call is in a bridge, lock parked_user before checking/setting */
+};
+
+/*!
+ * \since 12
+ * \brief If a parking lot exists in the parking lot list already, update its status to match the provided
+ * configuration and return a reference return a reference to it. Otherwise, create a parking lot
+ * struct based on a parking lot configuration and return a reference to the new one.
+ *
+ * \param cfg The configuration being used as a reference to build the parking lot from.
+ *
+ * \retval A reference to the new parking lot
+ * \retval NULL if it was not found and could not be be allocated
+ *
+ * \note The parking lot will need to be unreffed if it ever falls out of scope
+ * \note The parking lot will automatically be added to the parking lot container if needed as part of this process
+ */
+struct parking_lot *parking_lot_build_or_update(struct parking_lot_cfg *cfg);
+
+/*!
+ * \since 12
+ * \brief Remove a parking lot from the usable lists if it is no longer involved in any calls and no configuration currently claims it
+ *
+ * \param lot Which parking lot is being checked for elimination
+ *
+ * \note This should generally be called when something is happening that could cause a parking lot to die such as a call being unparked or
+ * a parking lot no longer existing in configurations.
+ */
+void parking_lot_remove_if_unused(struct parking_lot *lot);
+
+/*!
+ * \since 12
+ * \brief Create a new parking bridge
+ *
+ * \param bridge_lot Parking lot which the new bridge should be based on
+ *
+ * \retval NULL if the bridge can not be created
+ * \retval Newly created parking bridge
+ */
+struct ast_bridge *bridge_parking_new(struct parking_lot *bridge_lot);
+
+/*!
+ * \since 12
+ * \brief Get a reference to a parking lot's bridge. If it doesn't exist, create it and get a reference.
+ *
+ * \param lot Which parking lot we need the bridge from. This parking lot must be locked before calling this function.
+ *
+ * \retval A reference to the ast_bridge associated with the parking lot
+ * \retval NULL if it didn't already have a bridge and one couldn't be created
+ *
+ * \note This bridge will need to be unreffed if it ever falls out of scope.
+ */
+struct ast_bridge *parking_lot_get_bridge(struct parking_lot *lot);
+
+/*!
+ * \since 12
+ * \brief Get an available parking space within a parking lot.
+ *
+ * \param lot Which parking lot we are getting a space from
+ * \param target_override If there is a specific slot we want, provide it here and we'll start from that position
+ *
+ * \retval -1 if No slot can be found
+ * \retval integer value of parking space selected
+ *
+ * \note lot should be locked before this is called and unlocked only after a parked_user with the space
+ * returned has been added to the parking lot.
+ */
+int parking_lot_get_space(struct parking_lot *lot, int target_override);
+
+/*!
+ * \since 12
+ * \brief Determine if there is a parked user in a parking space and pull it from the parking lot if there is.
+ *
+ * \param lot Parking lot being pulled from
+ * \param target If < 0 search for the first occupied space in the parking lot
+ * If >= 0 Only pull from the indicated target
+ *
+ * \retval NULL if no parked user could be pulled from the requested parking lot at the requested parking space
+ * \retval reference to the requested parked user
+ *
+ * \note The parked user will be removed from parking lot as part of this process
+ * \note Remove this reference with ao2_cleanup once it falls out of scope.
+ */
+struct parked_user *parking_lot_retrieve_parked_user(struct parking_lot *lot, int target);
+
+/*!
+ * \since 12
+ * \brief Apply features based on the parking lot feature options
+ *
+ * \param chan Which channel's feature set is being modified
+ * \param lot parking lot which establishes the features used
+ * \param recipient_mode AST_FEATURE_FLAG_BYCALLER if the user is the retriever
+ * AST_FEATURE_FLAG_BYCALLEE if the user is the parkee
+ */
+void parked_call_retrieve_enable_features(struct ast_channel *chan, struct parking_lot *lot, int recipient_mode);
+
+/*!
+ * \since 12
+ * \brief Set necessary bridge roles on a channel that is about to enter a parking lot
+ *
+ * \param chan Entering channel
+ * \param lot The parking lot the channel will be entering
+ * \param force_ringing Use ringing instead of music on hold
+ */
+void parking_channel_set_roles(struct ast_channel *chan, struct parking_lot *lot, int force_ringing);
+
+/*!
+ * \since 12
+ * \brief custom callback function for ast_bridge_channel_queue_playfile which plays a parking space
+ * and optionally hangs up the call afterwards based on the payload in playfile.
+ */
+void say_parking_space(struct ast_bridge_channel *bridge_channel, const char *payload);
+
+/*!
+ * \since 12
+ * \brief Setup timeout interval feature on an ast_bridge_features for parking
+ *
+ * \param features The ast_bridge_features we are establishing the interval hook on
+ * \param user The parked_user receiving the timeout duration limits
+ */
+void parking_set_duration(struct ast_bridge_features *features, struct parked_user *user);
+
+/*!
+ * \since 12
+ * \brief Get a pointer to the parking lot container for purposes such as iteration
+ *
+ * \retval pointer to the parking lot container.
+ */
+struct ao2_container *get_parking_lot_container(void);
+
+/*!
+ * \since 12
+ * \brief Find a parking lot based on its name
+ *
+ * \param lot_name Name of the parking lot sought
+ *
+ * \retval The parking lot if found
+ * \retval NULL if no parking lot with the name specified exists
+ *
+ * \note ao2_cleanup this reference when you are done using it or you'll cause leaks.
+ */
+struct parking_lot *parking_lot_find_by_name(const char *lot_name);
+
+/*!
+ * \since 12
+ * \brief Find parking lot name from channel
+ *
+ * \param chan The channel we want the parking lot name for
+ *
+ * \retval name of the channel's assigned parking lot if it is defined by the channel in some way
+ * \retval name of the default parking lot if it is not
+ *
+ * \note Channel needs to be locked while the returned string is in use.
+ */
+const char *find_channel_parking_lot_name(struct ast_channel *chan);
+
+/*!
+ * \since 12
+ * \brief Flattens a peer name so that it can be written to/found from PBX extensions
+ *
+ * \param peername unflattened peer name. This will be flattened in place, so expect it to change.
+ */
+void flatten_peername(char *peername);
+
+/*!
+ * \since 12
+ * \brief Set a channel's position in the PBX after timeout using the parking lot settings
+ *
+ * \param pu Parked user who is entering/reentering the PBX
+ * \param lot Parking lot the user was removed from.
+ *
+ * \retval 0 Position set successfully
+ * \retval -1 Failed to set the position
+ */
+int comeback_goto(struct parked_user *pu, struct parking_lot *lot);
+
+/*!
+ * \since 12
+ * \brief Pull a parked user out of its parking lot. Use this when you don't want to use the parked user afterwards.
+ * \param user The parked user being pulled.
+ *
+ * \retval 0 on success
+ * \retval -1 if the user didn't have its parking lot set
+ */
+int unpark_parked_user(struct parked_user *user);
+
+/*!
+ * \since 12
+ * \brief Publish a stasis parked call message for the channel indicating failure to park.
+ *
+ * \param parkee channel belonging to the failed parkee
+ */
+void publish_parked_call_failure(struct ast_channel *parkee);
+
+/*!
+ * \since 12
+ * \brief Publish a stasis parked call message for a given parked user
+ *
+ * \param pu pointer to a parked_user that we are generating the message for
+ * \param event_type What parked call event type is provoking this message
+ */
+void publish_parked_call(struct parked_user *pu, enum ast_parked_call_event_type event_type);
+
+/*!
+ * \since 12
+ * \brief Function to prepare a channel for parking by determining which parking bridge should
+ * be used, setting up a park common datastore so that the parking bridge will have access
+ * to necessary parking information when joining, and applying various bridge roles to the
+ * channel.
+ *
+ * \param parkee The channel being preparred for parking
+ * \param parker The channel initiating the park; may be the parkee as well
+ * \param app_data arguments supplied to the Park application. May be NULL.
+ * \param silence_announcements optional pointer to an integer where we want to store the silence option flag
+ * this value should be initialized to 0 prior to calling park_common_setup.
+ *
+ * \retval reference to a parking bridge if successful
+ * \retval NULL on failure
+ *
+ * \note ao2_cleanup this reference when you are done using it or you'll cause leaks.
+ */
+struct ast_bridge *park_common_setup(struct ast_channel *parkee, struct ast_channel *parker,
+ const char *app_data, int *silence_announcements);
+
+struct park_common_datastore {
+ char *parker_uuid; /*!< Unique ID of the channel parking the call. */
+ char *comeback_override; /*!< Optional goto string for where to send the call after we are done */
+ int randomize; /*!< Pick a parking space to enter on at random */
+ int time_limit; /*!< time limit override. -1 values don't override, 0 for unlimited time, >0 for custom time limit in seconds */
+ int silence_announce; /*!< Used when a call parks itself to keep it from hearing the parked call announcement */
+};
+
+/*!
+ * \since 12
+ * \brief Function that pulls data from the park common datastore on a channel in order to apply it to
+ * the parked user struct upon bridging.
+ *
+ * \param parkee The channel entering parking with the datastore we are checking
+ * \param parker_uuid pointer to a string pointer for placing the name of the channel that parked parkee
+ * \param comeback_override pointer to a string pointer for placing the comeback_override option
+ * \param randomize integer pointer to an integer for placing the randomize option
+ * \param time_limit integer pointer to an integer for placing the time limit option
+ * \param silence_announce pointer to an integer for placing the silence_announcements option
+ */
+void get_park_common_datastore_data(struct ast_channel *parkee,
+ char **parker_uuid, char **comeback_override,
+ int *randomize, int *time_limit, int *silence_announce);
+
+/*!
+ * \since 12
+ * \brief Execution function for the parking application
+ *
+ * \param chan ast_channel entering the application
+ * \param data arguments to the application
+ *
+ * \retval 0 the application executed in such a way that the channel should proceed in the dial plan
+ * \retval -1 the channel should no longer proceed through the dial plan
+ *
+ * \note this function should only be used to register the parking application and not generally to park calls.
+ */
+int park_app_exec(struct ast_channel *chan, const char *data);
+
+/*!
+ * \since 12
+ * \brief Execution function for the parked call application
+ *
+ * \param chan ast_channel entering the application
+ * \param data arguments to the application
+ *
+ * \retval 0 the application executed in such a way that the channel should proceed in the dial plan
+ * \retval -1 the channel should no longer proceed through the dial plan
+ */
+int parked_call_app_exec(struct ast_channel *chan, const char *data);
+
+/*!
+ * \since 12
+ * \brief Execution function for the park and retrieve application
+ *
+ * \param chan ast_channel entering the application
+ * \param data arguments to the application
+ *
+ * \retval 0 the application executed in such a way that the channel should proceed in the dial plan
+ * \retval -1 the channel should no longer proceed through the dial plan
+ *
+ * \note this function should only be used to register the park and announce application and not generally to park and announce.
+ */
+int park_and_announce_app_exec(struct ast_channel *chan, const char *data);
+
+/*!
+ * \since 12
+ * \brief Register CLI commands
+ *
+ * \retval 0 if successful
+ * \retval -1 on failure
+ */
+int load_parking_ui(void);
+
+/*!
+ * \since 12
+ * \brief Unregister CLI commands
+ */
+void unload_parking_ui(void);
+
+/*!
+ * \since 12
+ * \brief Register manager actions and setup subscriptions for stasis events
+ */
+int load_parking_manager(void);
+
+/*!
+ * \since 12
+ * \brief Unregister manager actions and remove subscriptions for stasis events
+ */
+void unload_parking_manager(void);
+
+/*!
+ * \since 12
+ * \brief Register bridge features for parking
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+int load_parking_bridge_features(void);
+
+/*!
+ * \since 12
+ * \brief Unregister features registered by load_parking_bridge_features
+ */
+void unload_parking_bridge_features(void);