Skip to content
Snippets Groups Projects
bridge_channel.c 89 KiB
Newer Older
  • Learn to ignore specific revisions
  • 
    	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;
    
    }
    
    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;
    
    	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,
    
    		moh_class, datalen);
    
    }
    
    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);
    }
    
    
    Richard Mudgett's avatar
    Richard Mudgett committed
    /*!
     * \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. */
    
    		ast_bridge_channel_kick(bridge_channel, AST_CAUSE_NORMAL_CLEARING);
    
    	}
    	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
     */
    
    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,
    
    		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);
    
    	}
    }
    
    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. */
    
    	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);
    }
    
    
    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;
    
    	/*! 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
     *
    
     * \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)
    {
    
    	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);
    
    	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,
    
    	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;
    
    	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);
    
    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,
    
    		bridge_channel, flags, callback, payload, payload_size);
    
    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,
    
    		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
     */
    
    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);
    }
    
    
    /*!
     * \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)
    
    	struct ast_bridge_hook_timer *hook;
    
    	struct timeval start;
    
    	interval_hooks = bridge_channel->features->interval_hooks;
    	ast_heap_wrlock(interval_hooks);
    
    	start = ast_tvnow();
    
    	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);
    
    		if (!chan_suspended
    			&& ast_test_flag(&hook->timer, AST_BRIDGE_HOOK_TIMER_OPTION_MEDIA)) {
    			chan_suspended = 1;
    
    			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);
    
    		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);
    
    		if (ast_heap_push(interval_hooks, hook)) {
    
    			/* Could not push the hook back onto the heap. */
    			ao2_ref(hook, -1);
    		}
    	}
    
    		ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
    		bridge_channel_unsuspend(bridge_channel);
    	}
    
    Richard Mudgett's avatar
    Richard Mudgett committed
    /*!
     * \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)) {
    
    	ao2_cleanup(featuremap);
    	ao2_cleanup(xfer);
    
    
    	ast_test_suite_event_notify("FEATURE_DETECTION",
    			"Result: success\r\n"
    			"Feature: %s", feature);
    #endif /* TEST_FRAMEWORK */
    }
    
    
    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));
    	}
    }
    
    
    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;
    
    	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 */
    
    		dtmf_len = bridge_channel_feature_digit_add(bridge_channel, digit, dtmf_len);
    	}
    
    	while (digit) {
    
    		/* 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);
    
    		} 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);
    
    			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);
    
    			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);
    
    			ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
    			if (!already_suspended) {
    				bridge_channel_unsuspend(bridge_channel);
    
    			/*
    			 * 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;
    
    		ast_debug(1, "DTMF feature string collection on %p(%s) timed out\n",
    			bridge_channel, ast_channel_name(bridge_channel->chan));
    
    	/* 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';
    
    	ast_test_suite_event_notify("FEATURE_DETECTION", "Result: fail");
    }
    
    /*!
     * \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's avatar
    Richard Mudgett committed
    /*!
     * \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);
    		}
    
    	ao2_iterator_destroy(&iter);
    
    }
    
    /*! \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];
    };
    
    Richard Mudgett's avatar
    Richard Mudgett committed
    /*!
     * \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);
    }
    
    
    Richard Mudgett's avatar
    Richard Mudgett committed
    /*!
     * \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);
    }
    
    
    Richard Mudgett's avatar
    Richard Mudgett committed
    /*!
     * \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);
    
    	ast_bridge_channel_kick(bridge_channel, AST_CAUSE_NORMAL_CLEARING);
    
    Richard Mudgett's avatar
    Richard Mudgett committed
    /*!
     * \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 */
    
    		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);
    	}
    
    	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.
    
    static void bridge_channel_handle_action(struct ast_bridge_channel *bridge_channel,
    	enum bridge_channel_action_type action, void *data)
    
    	case BRIDGE_CHANNEL_ACTION_DTMF_STREAM:
    
    		bridge_channel_suspend(bridge_channel);
    		ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
    
    		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,
    
    			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);
    
    		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);
    
    		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:
    
    		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);
    
    		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:
    
    		bridge_channel_blind_transfer(bridge_channel, data);
    
    	case BRIDGE_CHANNEL_ACTION_ATTENDED_TRANSFER:
    
    		bridge_channel_attended_transfer(bridge_channel, data);
    
    
    	/* 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.