Newer
Older
AST_LIST_INSERT_TAIL(&bridge_channel->wr_queue, dup, frame_list);
if (ast_alertpipe_write(bridge_channel->alert_pipe)) {
ast_log(LOG_ERROR, "We couldn't write alert pipe for %p(%s)... something is VERY wrong\n",
bridge_channel, ast_channel_name(bridge_channel->chan));
}
ast_bridge_channel_unlock(bridge_channel);
return 0;
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
}
int ast_bridge_queue_everyone_else(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
{
struct ast_bridge_channel *cur;
int not_written = -1;
if (frame->frametype == AST_FRAME_NULL) {
/* "Accept" the frame and discard it. */
return 0;
}
AST_LIST_TRAVERSE(&bridge->channels, cur, entry) {
if (cur == bridge_channel) {
continue;
}
if (!ast_bridge_channel_queue_frame(cur, frame)) {
not_written = 0;
}
}
return not_written;
}
int ast_bridge_channel_queue_control_data(struct ast_bridge_channel *bridge_channel, enum ast_control_frame_type control, const void *data, size_t datalen)
{
struct ast_frame frame = {
.frametype = AST_FRAME_CONTROL,
.subclass.integer = control,
.datalen = datalen,
.data.ptr = (void *) data,
};
return ast_bridge_channel_queue_frame(bridge_channel, &frame);
}
int ast_bridge_channel_write_control_data(struct ast_bridge_channel *bridge_channel, enum ast_control_frame_type control, const void *data, size_t datalen)
{
struct ast_frame frame = {
.frametype = AST_FRAME_CONTROL,
.subclass.integer = control,
.datalen = datalen,
.data.ptr = (void *) data,
};
return bridge_channel_write_frame(bridge_channel, &frame);
}
int ast_bridge_channel_write_hold(struct ast_bridge_channel *bridge_channel, const char *moh_class)
{
struct ast_json *blob;
int res;
size_t datalen;
if (!ast_strlen_zero(moh_class)) {
datalen = strlen(moh_class) + 1;
blob = ast_json_pack("{s: s}",
"musicclass", moh_class);
} else {
moh_class = NULL;
datalen = 0;
blob = NULL;
ast_channel_publish_cached_blob(bridge_channel->chan, ast_channel_hold_type(), blob);
res = ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_HOLD,
ast_json_unref(blob);
return res;
}
int ast_bridge_channel_write_unhold(struct ast_bridge_channel *bridge_channel)
{
ast_channel_publish_cached_blob(bridge_channel->chan, ast_channel_unhold_type(), NULL);
return ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_UNHOLD, NULL, 0);
}
/*!
* \internal
* \brief Helper function to kick off a PBX app on a bridge_channel
*/
static int run_app_helper(struct ast_channel *chan, const char *app_name, const char *app_args)
{
int res = 0;
if (!strcasecmp("Gosub", app_name)) {
ast_app_exec_sub(NULL, chan, app_args, 0);
} else if (!strcasecmp("Macro", app_name)) {
ast_app_exec_macro(NULL, chan, app_args);
} else {
struct ast_app *app;
app = pbx_findapp(app_name);
if (!app) {
ast_log(LOG_WARNING, "Could not find application (%s)\n", app_name);
} else {
struct ast_str *substituted_args = ast_str_create(16);
if (substituted_args) {
ast_str_substitute_variables(&substituted_args, 0, chan, app_args);
res = pbx_exec(chan, app, ast_str_buffer(substituted_args));
ast_free(substituted_args);
} else {
ast_log(LOG_WARNING, "Could not substitute application argument variables for %s\n", app_name);
res = pbx_exec(chan, app, app_args);
}
}
}
return res;
}
void ast_bridge_channel_run_app(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class)
{
if (moh_class) {
ast_bridge_channel_write_hold(bridge_channel, moh_class);
}
if (run_app_helper(bridge_channel->chan, app_name, S_OR(app_args, ""))) {
/* Break the bridge if the app returns non-zero. */
Richard Mudgett
committed
ast_bridge_channel_kick(bridge_channel, AST_CAUSE_NORMAL_CLEARING);
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
}
if (moh_class) {
ast_bridge_channel_write_unhold(bridge_channel);
}
}
struct bridge_run_app {
/*! Offset into app_name[] where the MOH class name starts. (zero if no MOH) */
int moh_offset;
/*! Offset into app_name[] where the application argument string starts. (zero if no arguments) */
int app_args_offset;
/*! Application name to run. */
char app_name[0];
};
/*!
* \internal
* \brief Handle the run application bridge action.
* \since 12.0.0
*
* \param bridge_channel Which channel to run the application on.
* \param data Action frame data to run the application.
*
* \return Nothing
*/
static void bridge_channel_run_app(struct ast_bridge_channel *bridge_channel, struct bridge_run_app *data)
{
ast_bridge_channel_run_app(bridge_channel, data->app_name,
data->app_args_offset ? &data->app_name[data->app_args_offset] : NULL,
data->moh_offset ? &data->app_name[data->moh_offset] : NULL);
}
/*!
* \internal
* \brief Marshal an application to be executed on a bridge_channel
*/
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
static int payload_helper_app(ast_bridge_channel_post_action_data post_it,
struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class)
{
struct bridge_run_app *app_data;
size_t len_name = strlen(app_name) + 1;
size_t len_args = ast_strlen_zero(app_args) ? 0 : strlen(app_args) + 1;
size_t len_moh = !moh_class ? 0 : strlen(moh_class) + 1;
size_t len_data = sizeof(*app_data) + len_name + len_args + len_moh;
/* Fill in application run frame data. */
app_data = alloca(len_data);
app_data->app_args_offset = len_args ? len_name : 0;
app_data->moh_offset = len_moh ? len_name + len_args : 0;
strcpy(app_data->app_name, app_name);/* Safe */
if (len_args) {
strcpy(&app_data->app_name[app_data->app_args_offset], app_args);/* Safe */
}
if (moh_class) {
strcpy(&app_data->app_name[app_data->moh_offset], moh_class);/* Safe */
}
return post_it(bridge_channel, BRIDGE_CHANNEL_ACTION_RUN_APP, app_data, len_data);
}
int ast_bridge_channel_write_app(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class)
{
return payload_helper_app(bridge_channel_write_action_data,
bridge_channel, app_name, app_args, moh_class);
}
int ast_bridge_channel_queue_app(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class)
{
return payload_helper_app(bridge_channel_queue_action_data,
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
bridge_channel, app_name, app_args, moh_class);
}
void ast_bridge_channel_playfile(struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class)
{
if (moh_class) {
ast_bridge_channel_write_hold(bridge_channel, moh_class);
}
if (custom_play) {
custom_play(bridge_channel, playfile);
} else {
ast_stream_and_wait(bridge_channel->chan, playfile, AST_DIGIT_NONE);
}
if (moh_class) {
ast_bridge_channel_write_unhold(bridge_channel);
}
/*
* It may be necessary to resume music on hold after we finish
* playing the announcment.
*/
if (ast_test_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_MOH)) {
const char *latest_musicclass;
ast_channel_lock(bridge_channel->chan);
latest_musicclass = ast_strdupa(ast_channel_latest_musicclass(bridge_channel->chan));
ast_channel_unlock(bridge_channel->chan);
ast_moh_start(bridge_channel->chan, latest_musicclass, NULL);
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
}
}
struct bridge_playfile {
/*! Call this function to play the playfile. (NULL if normal sound file to play) */
ast_bridge_custom_play_fn custom_play;
/*! Offset into playfile[] where the MOH class name starts. (zero if no MOH)*/
int moh_offset;
/*! Filename to play. */
char playfile[0];
};
/*!
* \internal
* \brief Handle the playfile bridge action.
* \since 12.0.0
*
* \param bridge_channel Which channel to play a file on.
* \param payload Action frame payload to play a file.
*
* \return Nothing
*/
static void bridge_channel_playfile(struct ast_bridge_channel *bridge_channel, struct bridge_playfile *payload)
{
ast_bridge_channel_playfile(bridge_channel, payload->custom_play, payload->playfile,
payload->moh_offset ? &payload->playfile[payload->moh_offset] : NULL);
}
/*!
* \internal
* \brief Marshal a file to be played on a bridge_channel
*/
static int payload_helper_playfile(ast_bridge_channel_post_action_data post_it,
struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class)
{
struct bridge_playfile *payload;
size_t len_name = strlen(playfile) + 1;
size_t len_moh = !moh_class ? 0 : strlen(moh_class) + 1;
size_t len_payload = sizeof(*payload) + len_name + len_moh;
/* Fill in play file frame data. */
Mark Michelson
committed
payload = ast_alloca(len_payload);
payload->custom_play = custom_play;
payload->moh_offset = len_moh ? len_name : 0;
strcpy(payload->playfile, playfile);/* Safe */
if (moh_class) {
strcpy(&payload->playfile[payload->moh_offset], moh_class);/* Safe */
}
return post_it(bridge_channel, BRIDGE_CHANNEL_ACTION_PLAY_FILE, payload, len_payload);
}
int ast_bridge_channel_write_playfile(struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class)
{
return payload_helper_playfile(bridge_channel_write_action_data,
bridge_channel, custom_play, playfile, moh_class);
}
int ast_bridge_channel_queue_playfile(struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class)
{
return payload_helper_playfile(bridge_channel_queue_action_data,
bridge_channel, custom_play, playfile, moh_class);
}
Mark Michelson
committed
int ast_bridge_channel_queue_playfile_sync(struct ast_bridge_channel *bridge_channel,
ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class)
{
return payload_helper_playfile(bridge_channel_queue_action_data_sync,
bridge_channel, custom_play, playfile, moh_class);
}
struct bridge_custom_callback {
/*! Call this function on the bridge channel thread. */
ast_bridge_custom_callback_fn callback;
/*! Size of the payload if it exists. A number otherwise. */
size_t payload_size;
Richard Mudgett
committed
/*! Option flags determining how callback is called. */
unsigned int flags;
/*! Nonzero if the payload exists. */
char payload_exists;
/*! Payload to give to callback. */
char payload[0];
};
/*!
* \internal
* \brief Handle the do custom callback bridge action.
* \since 12.0.0
*
Richard Mudgett
committed
* \param bridge_channel Which channel to call the callback on.
* \param data Action frame data to call the callback.
*
* \return Nothing
*/
static void bridge_channel_do_callback(struct ast_bridge_channel *bridge_channel, struct bridge_custom_callback *data)
{
Richard Mudgett
committed
if (ast_test_flag(data, AST_BRIDGE_CHANNEL_CB_OPTION_MEDIA)) {
bridge_channel_suspend(bridge_channel);
ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
}
data->callback(bridge_channel, data->payload_exists ? data->payload : NULL, data->payload_size);
Richard Mudgett
committed
if (ast_test_flag(data, AST_BRIDGE_CHANNEL_CB_OPTION_MEDIA)) {
ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
bridge_channel_unsuspend(bridge_channel);
}
/*!
* \internal
* \brief Marshal a custom callback function to be called on a bridge_channel
*/
static int payload_helper_cb(ast_bridge_channel_post_action_data post_it,
Richard Mudgett
committed
struct ast_bridge_channel *bridge_channel,
enum ast_bridge_channel_custom_callback_option flags,
ast_bridge_custom_callback_fn callback, const void *payload, size_t payload_size)
{
struct bridge_custom_callback *cb_data;
size_t len_data = sizeof(*cb_data) + (payload ? payload_size : 0);
/* Sanity check. */
if (!callback) {
ast_assert(0);
return -1;
}
/* Fill in custom callback frame data. */
cb_data = alloca(len_data);
cb_data->callback = callback;
cb_data->payload_size = payload_size;
Richard Mudgett
committed
cb_data->flags = flags;
cb_data->payload_exists = payload && payload_size;
if (cb_data->payload_exists) {
memcpy(cb_data->payload, payload, payload_size);/* Safe */
}
return post_it(bridge_channel, BRIDGE_CHANNEL_ACTION_CALLBACK, cb_data, len_data);
Richard Mudgett
committed
int ast_bridge_channel_write_callback(struct ast_bridge_channel *bridge_channel,
enum ast_bridge_channel_custom_callback_option flags,
ast_bridge_custom_callback_fn callback, const void *payload, size_t payload_size)
return payload_helper_cb(bridge_channel_write_action_data,
Richard Mudgett
committed
bridge_channel, flags, callback, payload, payload_size);
Richard Mudgett
committed
int ast_bridge_channel_queue_callback(struct ast_bridge_channel *bridge_channel,
enum ast_bridge_channel_custom_callback_option flags,
ast_bridge_custom_callback_fn callback, const void *payload, size_t payload_size)
return payload_helper_cb(bridge_channel_queue_action_data,
Richard Mudgett
committed
bridge_channel, flags, callback, payload, payload_size);
}
struct bridge_park {
int parker_uuid_offset;
int app_data_offset;
/* buffer used for holding those strings */
char parkee_uuid[0];
};
/*!
* \internal
* \brief Park a bridge_cahnnel
*/
static void bridge_channel_park(struct ast_bridge_channel *bridge_channel, struct bridge_park *payload)
{
if (!ast_parking_provider_registered()) {
ast_log(AST_LOG_WARNING, "Unable to park %s: No parking provider loaded!\n",
ast_channel_name(bridge_channel->chan));
return;
}
if (ast_parking_park_bridge_channel(bridge_channel, payload->parkee_uuid,
&payload->parkee_uuid[payload->parker_uuid_offset],
payload->app_data_offset ? &payload->parkee_uuid[payload->app_data_offset] : NULL)) {
ast_log(AST_LOG_WARNING, "Error occurred while parking %s\n",
ast_channel_name(bridge_channel->chan));
}
/*!
* \internal
* \brief Marshal a park action onto a bridge_channel
*/
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
static int payload_helper_park(ast_bridge_channel_post_action_data post_it,
struct ast_bridge_channel *bridge_channel,
const char *parkee_uuid,
const char *parker_uuid,
const char *app_data)
{
struct bridge_park *payload;
size_t len_parkee_uuid = strlen(parkee_uuid) + 1;
size_t len_parker_uuid = strlen(parker_uuid) + 1;
size_t len_app_data = !app_data ? 0 : strlen(app_data) + 1;
size_t len_payload = sizeof(*payload) + len_parker_uuid + len_parkee_uuid + len_app_data;
payload = alloca(len_payload);
payload->app_data_offset = len_app_data ? len_parkee_uuid + len_parker_uuid : 0;
payload->parker_uuid_offset = len_parkee_uuid;
strcpy(payload->parkee_uuid, parkee_uuid);
strcpy(&payload->parkee_uuid[payload->parker_uuid_offset], parker_uuid);
if (app_data) {
strcpy(&payload->parkee_uuid[payload->app_data_offset], app_data);
}
return post_it(bridge_channel, BRIDGE_CHANNEL_ACTION_PARK, payload, len_payload);
}
int ast_bridge_channel_write_park(struct ast_bridge_channel *bridge_channel, const char *parkee_uuid, const char *parker_uuid, const char *app_data)
{
return payload_helper_park(bridge_channel_write_action_data,
bridge_channel, parkee_uuid, parker_uuid, app_data);
}
Richard Mudgett
committed
/*!
* \internal
* \brief Handle bridge channel interval expiration.
* \since 12.0.0
*
* \param bridge_channel Channel to run expired intervals on.
*
* \return Nothing
*/
static void bridge_channel_handle_interval(struct ast_bridge_channel *bridge_channel)
Richard Mudgett
committed
struct ast_heap *interval_hooks;
struct ast_bridge_hook_timer *hook;
struct timeval start;
Richard Mudgett
committed
int chan_suspended = 0;
Richard Mudgett
committed
interval_hooks = bridge_channel->features->interval_hooks;
ast_heap_wrlock(interval_hooks);
Richard Mudgett
committed
while ((hook = ast_heap_peek(interval_hooks, 1))) {
int interval;
unsigned int execution_time;
if (ast_tvdiff_ms(hook->timer.trip_time, start) > 0) {
ast_debug(1, "Hook %p on %p(%s) wants to happen in the future, stopping our traversal\n",
hook, bridge_channel, ast_channel_name(bridge_channel->chan));
break;
}
ao2_ref(hook, +1);
Richard Mudgett
committed
ast_heap_unlock(interval_hooks);
Richard Mudgett
committed
if (!chan_suspended
&& ast_test_flag(&hook->timer, AST_BRIDGE_HOOK_TIMER_OPTION_MEDIA)) {
chan_suspended = 1;
Richard Mudgett
committed
bridge_channel_suspend(bridge_channel);
ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
}
ast_debug(1, "Executing hook %p on %p(%s)\n",
hook, bridge_channel, ast_channel_name(bridge_channel->chan));
interval = hook->generic.callback(bridge_channel, hook->generic.hook_pvt);
Richard Mudgett
committed
ast_heap_wrlock(interval_hooks);
if (ast_heap_peek(interval_hooks, hook->timer.heap_index) != hook
|| !ast_heap_remove(interval_hooks, hook)) {
/* Interval hook is already removed from the bridge_channel. */
ao2_ref(hook, -1);
continue;
}
ao2_ref(hook, -1);
if (interval < 0) {
ast_debug(1, "Removed interval hook %p from %p(%s)\n",
hook, bridge_channel, ast_channel_name(bridge_channel->chan));
ao2_ref(hook, -1);
continue;
}
if (interval) {
/* Set new interval for the hook. */
hook->timer.interval = interval;
}
ast_debug(1, "Updating interval hook %p with interval %u on %p(%s)\n",
hook, hook->timer.interval, bridge_channel,
ast_channel_name(bridge_channel->chan));
/* resetting start */
start = ast_tvnow();
/*
* Resetup the interval hook for the next interval. We may need
* to skip over any missed intervals because the hook was
* delayed or took too long.
*/
execution_time = ast_tvdiff_ms(start, hook->timer.trip_time);
while (hook->timer.interval < execution_time) {
execution_time -= hook->timer.interval;
hook->timer.trip_time = ast_tvadd(start, ast_samp2tv(hook->timer.interval - execution_time, 1000));
hook->timer.seqno = ast_atomic_fetchadd_int((int *) &bridge_channel->features->interval_sequence, +1);
Richard Mudgett
committed
if (ast_heap_push(interval_hooks, hook)) {
/* Could not push the hook back onto the heap. */
ao2_ref(hook, -1);
}
}
Richard Mudgett
committed
ast_heap_unlock(interval_hooks);
Richard Mudgett
committed
if (chan_suspended) {
Richard Mudgett
committed
ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
bridge_channel_unsuspend(bridge_channel);
}
/*!
* \internal
* \brief Write a DTMF stream out to a channel
*/
static int bridge_channel_write_dtmf_stream(struct ast_bridge_channel *bridge_channel, const char *dtmf)
{
return bridge_channel_write_action_data(bridge_channel,
BRIDGE_CHANNEL_ACTION_DTMF_STREAM, dtmf, strlen(dtmf) + 1);
/*!
* \internal
* \brief Indicate to the testsuite a feature was successfully detected.
*
* Currently, this function only will relay built-in features to the testsuite,
* but it could be modified to detect applicationmap items should the need arise.
*
* \param chan The channel that activated the feature
* \param dtmf The DTMF sequence entered to activate the feature
*/
static void testsuite_notify_feature_success(struct ast_channel *chan, const char *dtmf)
{
#ifdef TEST_FRAMEWORK
char *feature = "unknown";
struct ast_featuremap_config *featuremap;
struct ast_features_xfer_config *xfer;
ast_channel_lock(chan);
featuremap = ast_get_chan_featuremap_config(chan);
xfer = ast_get_chan_features_xfer_config(chan);
ast_channel_unlock(chan);
if (featuremap) {
if (!strcmp(dtmf, featuremap->blindxfer)) {
feature = "blindxfer";
} else if (!strcmp(dtmf, featuremap->atxfer)) {
feature = "atxfer";
} else if (!strcmp(dtmf, featuremap->disconnect)) {
feature = "disconnect";
} else if (!strcmp(dtmf, featuremap->automon)) {
feature = "automon";
} else if (!strcmp(dtmf, featuremap->automixmon)) {
feature = "automixmon";
} else if (!strcmp(dtmf, featuremap->parkcall)) {
feature = "parkcall";
if (!strcmp(dtmf, xfer->atxferthreeway)) {
feature = "atxferthreeway";
}
}
ao2_cleanup(featuremap);
ao2_cleanup(xfer);
ast_test_suite_event_notify("FEATURE_DETECTION",
"Result: success\r\n"
"Feature: %s", feature);
#endif /* TEST_FRAMEWORK */
}
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
static int bridge_channel_feature_digit_add(
struct ast_bridge_channel *bridge_channel, int digit, size_t dtmf_len)
{
if (dtmf_len < ARRAY_LEN(bridge_channel->dtmf_hook_state.collected) - 1) {
/* Add the new digit to the DTMF string so we can do our matching */
bridge_channel->dtmf_hook_state.collected[dtmf_len++] = digit;
bridge_channel->dtmf_hook_state.collected[dtmf_len] = '\0';
ast_debug(1, "DTMF feature string on %p(%s) is now '%s'\n",
bridge_channel, ast_channel_name(bridge_channel->chan),
bridge_channel->dtmf_hook_state.collected);
}
return dtmf_len;
}
static unsigned int bridge_channel_feature_digit_timeout(struct ast_bridge_channel *bridge_channel)
{
unsigned int digit_timeout;
struct ast_features_general_config *gen_cfg;
/* Determine interdigit timeout */
ast_channel_lock(bridge_channel->chan);
gen_cfg = ast_get_chan_features_general_config(bridge_channel->chan);
ast_channel_unlock(bridge_channel->chan);
if (!gen_cfg) {
ast_log(LOG_ERROR, "Unable to retrieve features configuration.\n");
return 3000; /* Pick a reasonable failsafe timeout in ms */
}
digit_timeout = gen_cfg->featuredigittimeout;
ao2_ref(gen_cfg, -1);
return digit_timeout;
}
void ast_bridge_channel_feature_digit_add(struct ast_bridge_channel *bridge_channel, int digit)
{
if (digit) {
bridge_channel_feature_digit_add(
bridge_channel, digit, strlen(bridge_channel->dtmf_hook_state.collected));
}
}
Richard Mudgett
committed
void ast_bridge_channel_feature_digit(struct ast_bridge_channel *bridge_channel, int digit)
{
struct ast_bridge_features *features = bridge_channel->features;
struct ast_bridge_hook_dtmf *hook = NULL;
size_t dtmf_len;
Richard Mudgett
committed
struct sanity_check_of_dtmf_size {
char check[1 / (ARRAY_LEN(bridge_channel->dtmf_hook_state.collected) == ARRAY_LEN(hook->dtmf.code))];
};
dtmf_len = strlen(bridge_channel->dtmf_hook_state.collected);
if (!dtmf_len && !digit) {
/* Nothing to do */
Richard Mudgett
committed
if (digit) {
dtmf_len = bridge_channel_feature_digit_add(bridge_channel, digit, dtmf_len);
}
Richard Mudgett
committed
/* See if a DTMF feature hook matches or can match */
hook = ao2_find(features->dtmf_hooks, bridge_channel->dtmf_hook_state.collected,
OBJ_SEARCH_PARTIAL_KEY);
if (!hook) {
ast_debug(1, "No DTMF feature hooks on %p(%s) match '%s'\n",
bridge_channel, ast_channel_name(bridge_channel->chan),
bridge_channel->dtmf_hook_state.collected);
Richard Mudgett
committed
} else if (dtmf_len != strlen(hook->dtmf.code)) {
unsigned int digit_timeout;
/* Need more digits to match */
ao2_ref(hook, -1);
digit_timeout = bridge_channel_feature_digit_timeout(bridge_channel);
Richard Mudgett
committed
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
bridge_channel->dtmf_hook_state.interdigit_timeout =
ast_tvadd(ast_tvnow(), ast_samp2tv(digit_timeout, 1000));
return;
} else {
int remove_me;
int already_suspended;
ast_debug(1, "DTMF feature hook %p matched DTMF string '%s' on %p(%s)\n",
hook, bridge_channel->dtmf_hook_state.collected, bridge_channel,
ast_channel_name(bridge_channel->chan));
/*
* Clear the collected digits before executing the hook
* in case the hook starts another sequence.
*/
bridge_channel->dtmf_hook_state.collected[0] = '\0';
ast_bridge_channel_lock_bridge(bridge_channel);
already_suspended = bridge_channel->suspended;
if (!already_suspended) {
bridge_channel_internal_suspend_nolock(bridge_channel);
Richard Mudgett
committed
ast_bridge_unlock(bridge_channel->bridge);
ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
/* Execute the matched hook on this channel. */
remove_me = hook->generic.callback(bridge_channel, hook->generic.hook_pvt);
if (remove_me) {
ast_debug(1, "DTMF hook %p is being removed from %p(%s)\n",
hook, bridge_channel, ast_channel_name(bridge_channel->chan));
ao2_unlink(features->dtmf_hooks, hook);
}
testsuite_notify_feature_success(bridge_channel->chan, hook->dtmf.code);
ao2_ref(hook, -1);
Richard Mudgett
committed
ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
if (!already_suspended) {
bridge_channel_unsuspend(bridge_channel);
}
Richard Mudgett
committed
/*
* If we are handing the channel off to an external hook for
* ownership, we are not guaranteed what kind of state it will
* come back in. If the channel hungup, we need to detect that
* here if the hook did not already change the state.
*/
if (bridge_channel->chan && ast_check_hangup_locked(bridge_channel->chan)) {
ast_bridge_channel_kick(bridge_channel, 0);
bridge_channel->dtmf_hook_state.collected[0] = '\0';
return;
}
/* if there is dtmf that has been collected then loop back through,
but set digit to -1 so it doesn't try to do an add since the dtmf
is already in the buffer */
dtmf_len = strlen(bridge_channel->dtmf_hook_state.collected);
if (!dtmf_len) {
return;
Richard Mudgett
committed
}
Richard Mudgett
committed
ast_debug(1, "DTMF feature string collection on %p(%s) timed out\n",
bridge_channel, ast_channel_name(bridge_channel->chan));
Richard Mudgett
committed
/* Timeout or DTMF digit didn't allow a match with any hooks. */
if (features->dtmf_passthrough) {
/* Stream the collected DTMF to the other channels. */
bridge_channel_write_dtmf_stream(bridge_channel,
bridge_channel->dtmf_hook_state.collected);
}
bridge_channel->dtmf_hook_state.collected[0] = '\0';
Richard Mudgett
committed
ast_test_suite_event_notify("FEATURE_DETECTION", "Result: fail");
}
Richard Mudgett
committed
/*!
* \internal
* \brief Handle bridge channel DTMF feature timeout expiration.
* \since 12.8.0
*
* \param bridge_channel Channel to check expired interdigit timer on.
*
* \return Nothing
*/
static void bridge_channel_handle_feature_timeout(struct ast_bridge_channel *bridge_channel)
{
if (!bridge_channel->dtmf_hook_state.collected[0]
|| 0 < ast_tvdiff_ms(bridge_channel->dtmf_hook_state.interdigit_timeout,
ast_tvnow())) {
/* Not within a sequence or not timed out. */
return;
Richard Mudgett
committed
ast_bridge_channel_feature_digit(bridge_channel, 0);
/*!
* \internal
* \brief Indicate that a bridge_channel is talking
*/
static void bridge_channel_talking(struct ast_bridge_channel *bridge_channel, int talking)
{
struct ast_bridge_features *features = bridge_channel->features;
struct ast_bridge_hook *hook;
struct ao2_iterator iter;
/* Run any talk detection hooks. */
iter = ao2_iterator_init(features->other_hooks, 0);
for (; (hook = ao2_iterator_next(&iter)); ao2_ref(hook, -1)) {
int remove_me;
ast_bridge_talking_indicate_callback talk_cb;
if (hook->type != AST_BRIDGE_HOOK_TYPE_TALK) {
continue;
}
talk_cb = (ast_bridge_talking_indicate_callback) hook->callback;
remove_me = talk_cb(bridge_channel, hook->hook_pvt, talking);
if (remove_me) {
ast_debug(1, "Talk detection hook %p is being removed from %p(%s)\n",
hook, bridge_channel, ast_channel_name(bridge_channel->chan));
ao2_unlink(features->other_hooks, hook);
}
}
/*! \brief Internal function that plays back DTMF on a bridge channel */
static void bridge_channel_dtmf_stream(struct ast_bridge_channel *bridge_channel, const char *dtmf)
{
ast_debug(1, "Playing DTMF stream '%s' out to %p(%s)\n",
dtmf, bridge_channel, ast_channel_name(bridge_channel->chan));
ast_dtmf_stream(bridge_channel->chan, NULL, dtmf, 0, 0);
}
/*! \brief Data specifying where a blind transfer is going to */
struct blind_transfer_data {
char exten[AST_MAX_EXTENSION];
char context[AST_MAX_CONTEXT];
};
/*!
* \internal
* \brief Execute after bridge actions on a channel when it leaves a bridge
*/
static void after_bridge_move_channel(struct ast_channel *chan_bridged, void *data)
{
RAII_VAR(struct ast_channel *, chan_target, data, ao2_cleanup);
struct ast_party_connected_line connected_target;
unsigned char connected_line_data[1024];
int payload_size;
ast_party_connected_line_init(&connected_target);
ast_channel_lock(chan_target);
ast_party_connected_line_copy(&connected_target, ast_channel_connected(chan_target));
ast_channel_unlock(chan_target);
ast_party_id_reset(&connected_target.priv);
if (ast_channel_move(chan_target, chan_bridged)) {
ast_softhangup(chan_target, AST_SOFTHANGUP_DEV);
ast_party_connected_line_free(&connected_target);
return;
}
/* The ast_channel_move function will end up updating the connected line information
* on chan_target to the value we have here, but will not inform it. To ensure that
* AST_FRAME_READ_ACTION_CONNECTED_LINE_MACRO is executed we wipe it away here. If
* we don't do this then the change will be considered redundant, since the connected
* line information is already there (despite the channel not being told).
*/
ast_channel_lock(chan_target);
ast_party_connected_line_free(ast_channel_connected_indicated(chan_target));
ast_party_connected_line_init(ast_channel_connected_indicated(chan_target));
ast_channel_unlock(chan_target);
if ((payload_size = ast_connected_line_build_data(connected_line_data,
sizeof(connected_line_data), &connected_target, NULL)) != -1) {
struct ast_control_read_action_payload *frame_payload;
int frame_size;
frame_size = payload_size + sizeof(*frame_payload);
frame_payload = ast_alloca(frame_size);
frame_payload->action = AST_FRAME_READ_ACTION_CONNECTED_LINE_MACRO;
frame_payload->payload_size = payload_size;
memcpy(frame_payload->payload, connected_line_data, payload_size);
ast_queue_control_data(chan_target, AST_CONTROL_READ_ACTION, frame_payload, frame_size);
}
/* A connected line update is queued so that if chan_target is remotely involved with
* anything (such as dialing a channel) the other channel(s) will be informed of the
* new channel they are involved with.
*/
ast_channel_lock(chan_target);
ast_connected_line_copy_from_caller(&connected_target, ast_channel_caller(chan_target));
ast_channel_queue_connected_line_update(chan_target, &connected_target, NULL);
ast_channel_unlock(chan_target);
ast_party_connected_line_free(&connected_target);
}
/*!
* \internal
* \brief Execute logic to cleanup when after bridge fails
*/
static void after_bridge_move_channel_fail(enum ast_bridge_after_cb_reason reason, void *data)
{
RAII_VAR(struct ast_channel *, chan_target, data, ao2_cleanup);
ast_log(LOG_WARNING, "Unable to complete transfer: %s\n",
ast_bridge_after_cb_reason_string(reason));
ast_softhangup(chan_target, AST_SOFTHANGUP_DEV);
}
/*!
* \internal
* \brief Perform a blind transfer on a channel in a bridge
*/
static void bridge_channel_blind_transfer(struct ast_bridge_channel *bridge_channel,
struct blind_transfer_data *blind_data)
{
ast_async_goto(bridge_channel->chan, blind_data->context, blind_data->exten, 1);
Richard Mudgett
committed
ast_bridge_channel_kick(bridge_channel, AST_CAUSE_NORMAL_CLEARING);
/*!
* \internal
* \brief Perform an attended transfer on a channel in a bridge
*/
static void bridge_channel_attended_transfer(struct ast_bridge_channel *bridge_channel,
const char *target_chan_name)
{
RAII_VAR(struct ast_channel *, chan_target, NULL, ao2_cleanup);
RAII_VAR(struct ast_channel *, chan_bridged, NULL, ao2_cleanup);
chan_target = ast_channel_get_by_name(target_chan_name);
if (!chan_target) {
/* Dang, it disappeared somehow */
Richard Mudgett
committed
ast_bridge_channel_kick(bridge_channel, AST_CAUSE_NORMAL_CLEARING);
return;
}
ast_bridge_channel_lock(bridge_channel);
chan_bridged = bridge_channel->chan;
ast_assert(chan_bridged != NULL);
ao2_ref(chan_bridged, +1);
ast_bridge_channel_unlock(bridge_channel);
if (ast_bridge_set_after_callback(chan_bridged, after_bridge_move_channel,
after_bridge_move_channel_fail, ast_channel_ref(chan_target))) {
ast_softhangup(chan_target, AST_SOFTHANGUP_DEV);
/* Release the ref we tried to pass to ast_bridge_set_after_callback(). */
ast_channel_unref(chan_target);
}
Richard Mudgett
committed
ast_bridge_channel_kick(bridge_channel, AST_CAUSE_NORMAL_CLEARING);
}
/*!
* \internal
* \brief Handle bridge channel bridge action frame.
* \since 12.0.0
*
* \param bridge_channel Channel to execute the action on.
* \param action What to do.
Mark Michelson
committed
* \param data data from the action.
*
* \return Nothing
*/
Mark Michelson
committed
static void bridge_channel_handle_action(struct ast_bridge_channel *bridge_channel,
enum bridge_channel_action_type action, void *data)
Mark Michelson
committed
switch (action) {
case BRIDGE_CHANNEL_ACTION_DTMF_STREAM:
bridge_channel_suspend(bridge_channel);
ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
Mark Michelson
committed
bridge_channel_dtmf_stream(bridge_channel, data);
ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
bridge_channel_unsuspend(bridge_channel);
break;
case BRIDGE_CHANNEL_ACTION_TALKING_START:
case BRIDGE_CHANNEL_ACTION_TALKING_STOP:
bridge_channel_talking(bridge_channel,
Mark Michelson
committed
action == BRIDGE_CHANNEL_ACTION_TALKING_START);
case BRIDGE_CHANNEL_ACTION_PLAY_FILE:
bridge_channel_suspend(bridge_channel);
ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
Mark Michelson
committed
bridge_channel_playfile(bridge_channel, data);
ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
bridge_channel_unsuspend(bridge_channel);
break;
case BRIDGE_CHANNEL_ACTION_RUN_APP:
bridge_channel_suspend(bridge_channel);
ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
Mark Michelson
committed
bridge_channel_run_app(bridge_channel, data);
ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
bridge_channel_unsuspend(bridge_channel);
break;
case BRIDGE_CHANNEL_ACTION_CALLBACK:
Mark Michelson
committed
bridge_channel_do_callback(bridge_channel, data);
case BRIDGE_CHANNEL_ACTION_PARK:
bridge_channel_suspend(bridge_channel);
ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
Mark Michelson
committed
bridge_channel_park(bridge_channel, data);
ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
bridge_channel_unsuspend(bridge_channel);
break;
case BRIDGE_CHANNEL_ACTION_BLIND_TRANSFER:
Mark Michelson
committed
bridge_channel_blind_transfer(bridge_channel, data);
case BRIDGE_CHANNEL_ACTION_ATTENDED_TRANSFER:
Mark Michelson
committed
bridge_channel_attended_transfer(bridge_channel, data);
break;
default:
break;
}
/* While invoking an action it is possible for the channel to be hung up. So
* that the bridge respects this we check here and if hung up kick it out.