Skip to content
Snippets Groups Projects
app_agent_pool.c 76.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • 
    /*! Agent holding bridge deferred creation lock. */
    AST_MUTEX_DEFINE_STATIC(agent_holding_lock);
    
    
    /*!
     * \internal
     * \brief Callback to clear AGENT_STATUS on the caller channel.
     *
     * \param bridge_channel Which channel to operate on.
     * \param payload Data to pass to the callback. (NULL if none).
     * \param payload_size Size of the payload if payload is non-NULL.  A number otherwise.
     *
     * \note The payload MUST NOT have any resources that need to be freed.
     *
     * \return Nothing
     */
    static void clear_agent_status(struct ast_bridge_channel *bridge_channel, const void *payload, size_t payload_size)
    {
    	pbx_builtin_setvar_helper(bridge_channel->chan, "AGENT_STATUS", NULL);
    }
    
    
    /*!
     * \internal
     * \brief Connect the agent with the waiting caller.
     * \since 12.0.0
     *
     * \param bridge_channel Agent channel connecting to the caller.
     * \param agent Which agent is connecting to the caller.
     *
     * \note The agent is locked on entry and not locked on exit.
     *
     * \return Nothing
     */
    static void agent_connect_caller(struct ast_bridge_channel *bridge_channel, struct agent_pvt *agent)
    {
    	struct ast_bridge *caller_bridge;
    	int record_agent_calls;
    	int res;
    
    	record_agent_calls = agent->cfg->record_agent_calls;
    	caller_bridge = agent->caller_bridge;
    	agent->caller_bridge = NULL;
    	agent->state = AGENT_STATE_ON_CALL;
    	time(&agent->call_start);
    	agent_unlock(agent);
    
    	if (!caller_bridge) {
    		/* Reset agent. */
    
    		ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END,
    			AST_CAUSE_NORMAL_CLEARING);
    
    		return;
    	}
    	res = ast_bridge_move(caller_bridge, bridge_channel->bridge, bridge_channel->chan,
    		NULL, 0);
    	if (res) {
    		/* Reset agent. */
    
    		ast_bridge_destroy(caller_bridge, 0);
    
    		ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END,
    			AST_CAUSE_NORMAL_CLEARING);
    
    	res = ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_ANSWER, NULL, 0)
    		|| ast_bridge_channel_write_callback(bridge_channel, 0, clear_agent_status, NULL, 0);
    	if (res) {
    		/* Reset agent. */
    		ast_bridge_destroy(caller_bridge, 0);
    		return;
    	}
    
    
    	if (record_agent_calls) {
    		struct ast_bridge_features_automixmonitor options = {
    			.start_stop = AUTO_MONITOR_START,
    			};
    
    		/*
    		 * The agent is in the new bridge so we can invoke the
    		 * mixmonitor hook to only start recording.
    		 */
    
    		ast_bridge_features_do(AST_BRIDGE_BUILTIN_AUTOMIXMON, bridge_channel, &options);
    
    
    	ao2_t_ref(caller_bridge, -1, "Agent successfully in caller_bridge");
    
    static int bridge_agent_hold_ack(struct ast_bridge_channel *bridge_channel, void *hook_pvt)
    
    {
    	struct agent_pvt *agent = hook_pvt;
    
    	agent_lock(agent);
    	switch (agent->state) {
    	case AGENT_STATE_CALL_WAIT_ACK:
    		/* Connect to caller now. */
    		ast_debug(1, "Agent %s: Acked call.\n", agent->username);
    		agent_connect_caller(bridge_channel, agent);/* Will unlock agent. */
    		return 0;
    	default:
    		break;
    	}
    	agent_unlock(agent);
    	return 0;
    }
    
    
    static int bridge_agent_hold_heartbeat(struct ast_bridge_channel *bridge_channel, void *hook_pvt)
    
    {
    	struct agent_pvt *agent = hook_pvt;
    	int probation_timedout = 0;
    	int ack_timedout = 0;
    	int wrapup_timedout = 0;
    	int deferred_logoff;
    	unsigned int wrapup_time;
    	unsigned int auto_logoff;
    
    	agent_lock(agent);
    	deferred_logoff = agent->deferred_logoff;
    	if (deferred_logoff) {
    		agent->state = AGENT_STATE_LOGGING_OUT;
    	}
    
    	switch (agent->state) {
    	case AGENT_STATE_PROBATION_WAIT:
    		probation_timedout =
    			LOGIN_WAIT_TIMEOUT_TIME <= (time(NULL) - agent->probation_start);
    		if (probation_timedout) {
    			/* Now ready for a caller. */
    			agent->state = AGENT_STATE_READY_FOR_CALL;
    			agent->devstate = AST_DEVICE_NOT_INUSE;
    		}
    		break;
    	case AGENT_STATE_CALL_WAIT_ACK:
    		/* Check ack call time. */
    		auto_logoff = agent->cfg->auto_logoff;
    		if (ast_test_flag(agent, AGENT_FLAG_AUTO_LOGOFF)) {
    			auto_logoff = agent->override_auto_logoff;
    		}
    		if (auto_logoff) {
    			auto_logoff *= 1000;
    			ack_timedout = ast_tvdiff_ms(ast_tvnow(), agent->ack_time) > auto_logoff;
    			if (ack_timedout) {
    				agent->state = AGENT_STATE_LOGGING_OUT;
    			}
    		}
    		break;
    	case AGENT_STATE_CALL_WRAPUP:
    		/* Check wrapup time. */
    		wrapup_time = agent->cfg->wrapup_time;
    		if (ast_test_flag(agent, AGENT_FLAG_WRAPUP_TIME)) {
    			wrapup_time = agent->override_wrapup_time;
    		}
    		wrapup_timedout = ast_tvdiff_ms(ast_tvnow(), agent->last_disconnect) > wrapup_time;
    		if (wrapup_timedout) {
    			agent->state = AGENT_STATE_READY_FOR_CALL;
    			agent->devstate = AST_DEVICE_NOT_INUSE;
    		}
    		break;
    	default:
    		break;
    	}
    	agent_unlock(agent);
    
    	if (deferred_logoff) {
    		ast_debug(1, "Agent %s: Deferred logoff.\n", agent->username);
    
    		ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END,
    			AST_CAUSE_NORMAL_CLEARING);
    
    	} else if (probation_timedout) {
    		ast_debug(1, "Agent %s: Login complete.\n", agent->username);
    		agent_devstate_changed(agent->username);
    	} else if (ack_timedout) {
    		ast_debug(1, "Agent %s: Ack call timeout.\n", agent->username);
    
    		ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END,
    			AST_CAUSE_NORMAL_CLEARING);
    
    	} else if (wrapup_timedout) {
    		ast_debug(1, "Agent %s: Wrapup timeout. Ready for new call.\n", agent->username);
    		agent_devstate_changed(agent->username);
    	}
    
    	return 0;
    }
    
    static void agent_after_bridge_cb(struct ast_channel *chan, void *data);
    
    static void agent_after_bridge_cb_failed(enum ast_bridge_after_cb_reason reason, void *data);
    
    
    /*!
     * \internal
     * \brief ast_bridge agent_hold 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_agent_hold_push(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap)
    {
    	int res = 0;
    	unsigned int wrapup_time;
    	char dtmf[AST_FEATURE_MAX_LEN];
    	struct ast_channel *chan;
    	const char *moh_class;
    	RAII_VAR(struct agent_pvt *, agent, NULL, ao2_cleanup);
    
    	chan = bridge_channel->chan;
    
    	agent = ao2_find(agents, swap ? swap->chan : chan, 0);
    	if (!agent) {
    		/* Could not find the agent. */
    		return -1;
    	}
    
    	/* Setup agent entertainment */
    	agent_lock(agent);
    	moh_class = ast_strdupa(agent->cfg->moh);
    	agent_unlock(agent);
    	res |= ast_channel_add_bridge_role(chan, "holding_participant");
    	res |= ast_channel_set_bridge_role_option(chan, "holding_participant", "idle_mode", "musiconhold");
    	res |= ast_channel_set_bridge_role_option(chan, "holding_participant", "moh_class", moh_class);
    
    	/* Add DTMF acknowledge hook. */
    	dtmf[0] = '\0';
    	agent_lock(agent);
    	if (ast_test_flag(agent, AGENT_FLAG_ACK_CALL)
    		? agent->override_ack_call : agent->cfg->ack_call) {
    		const char *dtmf_accept;
    
    		dtmf_accept = ast_test_flag(agent, AGENT_FLAG_DTMF_ACCEPT)
    			? agent->override_dtmf_accept : agent->cfg->dtmf_accept;
    		ast_copy_string(dtmf, dtmf_accept, sizeof(dtmf));
    	}
    	agent_unlock(agent);
    	if (!ast_strlen_zero(dtmf)) {
    		ao2_ref(agent, +1);
    		if (ast_bridge_dtmf_hook(bridge_channel->features, dtmf, bridge_agent_hold_ack,
    			agent, __ao2_cleanup, AST_BRIDGE_HOOK_REMOVE_ON_PULL)) {
    			ao2_ref(agent, -1);
    			res = -1;
    		}
    	}
    
    	/* Add heartbeat interval hook. */
    	ao2_ref(agent, +1);
    
    	if (ast_bridge_interval_hook(bridge_channel->features, 0, 1000,
    
    		bridge_agent_hold_heartbeat, agent, __ao2_cleanup, AST_BRIDGE_HOOK_REMOVE_ON_PULL)) {
    		ao2_ref(agent, -1);
    		res = -1;
    	}
    
    	res |= ast_bridge_base_v_table.push(self, bridge_channel, swap);
    	if (res) {
    		ast_channel_remove_bridge_role(chan, "holding_participant");
    		return -1;
    	}
    
    	if (swap) {
    
    		res = ast_bridge_set_after_callback(chan, agent_after_bridge_cb,
    
    			agent_after_bridge_cb_failed, chan);
    		if (res) {
    			ast_channel_remove_bridge_role(chan, "holding_participant");
    			return -1;
    		}
    
    		agent_lock(agent);
    		ast_channel_unref(agent->logged);
    		agent->logged = ast_channel_ref(chan);
    		agent_unlock(agent);
    
    		/*
    		 * Kick the channel out so it can come back in fully controlled.
    		 * Otherwise, the after bridge callback will linger and the
    		 * agent will have some slightly different behavior in corner
    		 * cases.
    		 */
    
    		ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END,
    			AST_CAUSE_NORMAL_CLEARING);
    
    		return 0;
    	}
    
    	agent_lock(agent);
    	switch (agent->state) {
    	case AGENT_STATE_LOGGED_OUT:
    		/*!
    		 * \todo XXX the login probation time should be only if it is needed.
    		 *
    		 * Need to determine if there are any local channels that can
    		 * optimize and wait until they actually do before leaving the
    		 * AGENT_STATE_PROBATION_WAIT state.  For now, the blind
    		 * timer of LOGIN_WAIT_TIMEOUT_TIME will do.
    		 */
    		/*
    		 * Start the login probation timer.
    		 *
    		 * We cannot handle an agent local channel optimization when the
    		 * agent is on a call.  The optimization may kick the agent
    		 * channel we know about out of the call without our being able
    		 * to switch to the replacement channel.  Get any agent local
    		 * channel optimization out of the way while the agent is in the
    		 * holding bridge.
    		 */
    		time(&agent->probation_start);
    		agent->state = AGENT_STATE_PROBATION_WAIT;
    		agent_unlock(agent);
    		break;
    	case AGENT_STATE_PROBATION_WAIT:
    		/* Restart the probation timer. */
    		time(&agent->probation_start);
    		agent_unlock(agent);
    		break;
    	case AGENT_STATE_READY_FOR_CALL:
    		/*
    		 * Likely someone manually kicked us out of the holding bridge
    		 * and we came right back in.
    		 */
    		agent_unlock(agent);
    		break;
    	default:
    		/* Unexpected agent state. */
    		ast_assert(0);
    		/* Fall through */
    	case AGENT_STATE_CALL_PRESENT:
    	case AGENT_STATE_CALL_WAIT_ACK:
    		agent->state = AGENT_STATE_READY_FOR_CALL;
    		agent->devstate = AST_DEVICE_NOT_INUSE;
    		agent_unlock(agent);
    		ast_debug(1, "Agent %s: Call abort recovery complete.\n", agent->username);
    		agent_devstate_changed(agent->username);
    		break;
    	case AGENT_STATE_ON_CALL:
    	case AGENT_STATE_CALL_WRAPUP:
    		wrapup_time = agent->cfg->wrapup_time;
    		if (ast_test_flag(agent, AGENT_FLAG_WRAPUP_TIME)) {
    			wrapup_time = agent->override_wrapup_time;
    		}
    		if (wrapup_time) {
    			agent->state = AGENT_STATE_CALL_WRAPUP;
    		} else {
    			agent->state = AGENT_STATE_READY_FOR_CALL;
    			agent->devstate = AST_DEVICE_NOT_INUSE;
    		}
    		agent_unlock(agent);
    		if (!wrapup_time) {
    			/* No wrapup time. */
    			ast_debug(1, "Agent %s: Ready for new call.\n", agent->username);
    			agent_devstate_changed(agent->username);
    		}
    		break;
    	}
    
    	return 0;
    }
    
    /*!
     * \internal
     * \brief ast_bridge agent_hold pull method.
     *
     * \param self Bridge to operate upon.
     * \param bridge_channel Bridge channel to pull.
     *
     * \details
     * Remove any channel hooks controlled by the bridge.  Release
     * any resources held by bridge_channel->bridge_pvt and release
     * bridge_channel->bridge_pvt.
     *
     * \note On entry, self is already locked.
     *
     * \return Nothing
     */
    static void bridge_agent_hold_pull(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel)
    {
    	ast_channel_remove_bridge_role(bridge_channel->chan, "holding_participant");
    	ast_bridge_base_v_table.pull(self, bridge_channel);
    }
    
    /*!
     * \brief The bridge is being dissolved.
     *
     * \param self Bridge to operate upon.
     *
     * \details
     * The bridge is being dissolved.  Remove any external
     * references to the bridge so it can be destroyed.
     *
     * \note On entry, self must NOT be locked.
     *
     * \return Nothing
     */
    static void bridge_agent_hold_dissolving(struct ast_bridge *self)
    {
    
    	ao2_global_obj_release(agent_holding);
    
    	ast_bridge_base_v_table.dissolving(self);
    }
    
    static struct ast_bridge_methods bridge_agent_hold_v_table;
    
    static struct ast_bridge *bridge_agent_hold_new(void)
    {
    	struct ast_bridge *bridge;
    
    
    	bridge = bridge_alloc(sizeof(struct ast_bridge), &bridge_agent_hold_v_table);
    	bridge = 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 | AST_BRIDGE_FLAG_TRANSFER_PROHIBITED,
    
    	bridge = bridge_register(bridge);
    
    static void bridge_init_agent_hold(void)
    
    {
    	/* Setup bridge agent_hold subclass v_table. */
    	bridge_agent_hold_v_table = ast_bridge_base_v_table;
    	bridge_agent_hold_v_table.name = "agent_hold";
    	bridge_agent_hold_v_table.dissolving = bridge_agent_hold_dissolving;
    	bridge_agent_hold_v_table.push = bridge_agent_hold_push;
    	bridge_agent_hold_v_table.pull = bridge_agent_hold_pull;
    }
    
    static int bridge_agent_hold_deferred_create(void)
    {
    	RAII_VAR(struct ast_bridge *, holding, ao2_global_obj_ref(agent_holding), ao2_cleanup);
    
    	if (!holding) {
    		ast_mutex_lock(&agent_holding_lock);
    		holding = ao2_global_obj_ref(agent_holding);
    		if (!holding) {
    			holding = bridge_agent_hold_new();
    			ao2_global_obj_replace_unref(agent_holding, holding);
    		}
    		ast_mutex_unlock(&agent_holding_lock);
    		if (!holding) {
    			ast_log(LOG_ERROR, "Could not create agent holding bridge.\n");
    			return -1;
    		}
    	}
    	return 0;
    }
    
    static void send_agent_login(struct ast_channel *chan, const char *agent)
    {
    	RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
    
    	ast_assert(agent != NULL);
    
    	blob = ast_json_pack("{s: s}",
    		"agent", agent);
    	if (!blob) {
    		return;
    	}
    
    
    	ast_channel_publish_blob(chan, ast_channel_agent_login_type(), blob);
    
    }
    
    static void send_agent_logoff(struct ast_channel *chan, const char *agent, long logintime)
    {
    	RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
    
    	ast_assert(agent != NULL);
    
    
    	blob = ast_json_pack("{s: s, s: I}",
    
    		"agent", agent,
    
    		"logintime", (ast_json_int_t)logintime);
    
    	ast_channel_publish_blob(chan, ast_channel_agent_logoff_type(), blob);
    
    }
    
    /*!
     * \internal
     * \brief Logout the agent.
     * \since 12.0.0
     *
     * \param agent Which agent logging out.
     *
     * \note On entry agent is already locked.  On exit it is no longer locked.
     *
     * \return Nothing
     */
    static void agent_logout(struct agent_pvt *agent)
    {
    	struct ast_channel *logged;
    	struct ast_bridge *caller_bridge;
    	long time_logged_in;
    
    	time_logged_in = time(NULL) - agent->login_start;
    	logged = agent->logged;
    	agent->logged = NULL;
    	caller_bridge = agent->caller_bridge;
    	agent->caller_bridge = NULL;
    	agent->state = AGENT_STATE_LOGGED_OUT;
    	agent->devstate = AST_DEVICE_UNAVAILABLE;
    	ast_clear_flag(agent, AST_FLAGS_ALL);
    	agent_unlock(agent);
    	agent_devstate_changed(agent->username);
    
    	if (caller_bridge) {
    
    		ast_bridge_destroy(caller_bridge, 0);
    
    	send_agent_logoff(logged, agent->username, time_logged_in);
    
    	ast_channel_unlock(logged);
    
    	ast_verb(2, "Agent '%s' logged out.  Logged in for %ld seconds.\n",
    		agent->username, time_logged_in);
    	ast_channel_unref(logged);
    }
    
    /*!
     * \internal
     * \brief Agent driver loop.
     * \since 12.0.0
     *
     * \param agent Which agent.
     * \param logged The logged in channel.
     *
     * \return Nothing
     */
    static void agent_run(struct agent_pvt *agent, struct ast_channel *logged)
    {
    	struct ast_bridge_features features;
    
    	if (ast_bridge_features_init(&features)) {
    
    		ast_channel_hangupcause_set(logged, AST_CAUSE_NORMAL_CLEARING);
    
    		goto agent_run_cleanup;
    	}
    	for (;;) {
    		struct agents_cfg *cfgs;
    		struct agent_cfg *cfg_new;
    		struct agent_cfg *cfg_old;
    		struct ast_bridge *holding;
    		struct ast_bridge *caller_bridge;
    
    
    		ast_channel_hangupcause_set(logged, AST_CAUSE_NORMAL_CLEARING);
    
    
    		holding = ao2_global_obj_ref(agent_holding);
    		if (!holding) {
    			ast_debug(1, "Agent %s: Someone destroyed the agent holding bridge.\n",
    				agent->username);
    			break;
    		}
    
    		/*
    		 * When the agent channel leaves the bridging system we usually
    		 * want to put the agent back into the holding bridge for the
    		 * next caller.
    		 */
    
    		ast_bridge_join(holding, logged, NULL, &features, NULL,
    			AST_BRIDGE_JOIN_PASS_REFERENCE);
    
    		if (logged != agent->logged) {
    			/* This channel is no longer the logged in agent. */
    			break;
    		}
    
    		if (agent->dead) {
    			/* The agent is no longer configured. */
    			break;
    		}
    
    		/* Update the agent's config before rejoining the holding bridge. */
    		cfgs = ao2_global_obj_ref(cfg_handle);
    		if (!cfgs) {
    			/* There is no agent configuration.  All agents were destroyed. */
    			break;
    		}
    		cfg_new = ao2_find(cfgs->agents, agent->username, OBJ_KEY);
    		ao2_ref(cfgs, -1);
    		if (!cfg_new) {
    			/* The agent is no longer configured. */
    			break;
    		}
    		agent_lock(agent);
    		cfg_old = agent->cfg;
    		agent->cfg = cfg_new;
    
    		agent->last_disconnect = ast_tvnow();
    
    		/* Clear out any caller bridge before rejoining the holding bridge. */
    		caller_bridge = agent->caller_bridge;
    		agent->caller_bridge = NULL;
    		agent_unlock(agent);
    		ao2_ref(cfg_old, -1);
    		if (caller_bridge) {
    
    			ast_bridge_destroy(caller_bridge, 0);
    
    		}
    
    		if (agent->state == AGENT_STATE_LOGGING_OUT
    			|| agent->deferred_logoff
    			|| ast_check_hangup_locked(logged)) {
    			/* The agent was requested to logout or hungup. */
    			break;
    		}
    
    		/*
    		 * It is safe to access agent->waiting_colp without a lock.  It
    		 * is only setup on agent login and not changed.
    		 */
    		ast_channel_update_connected_line(logged, &agent->waiting_colp, NULL);
    	}
    	ast_bridge_features_cleanup(&features);
    
    agent_run_cleanup:
    	agent_lock(agent);
    	if (logged != agent->logged) {
    		/*
    		 * We are no longer the agent channel because of local channel
    		 * optimization.
    		 */
    		agent_unlock(agent);
    		ast_debug(1, "Agent %s: Channel %s is no longer the agent.\n",
    			agent->username, ast_channel_name(logged));
    		return;
    	}
    	agent_logout(agent);
    }
    
    static void agent_after_bridge_cb(struct ast_channel *chan, void *data)
    {
    	struct agent_pvt *agent;
    
    	agent = ao2_find(agents, chan, 0);
    	if (!agent) {
    		return;
    	}
    
    	ast_debug(1, "Agent %s: New agent channel %s.\n",
    		agent->username, ast_channel_name(chan));
    	agent_run(agent, chan);
    	ao2_ref(agent, -1);
    }
    
    
    static void agent_after_bridge_cb_failed(enum ast_bridge_after_cb_reason reason, void *data)
    
    {
    	struct ast_channel *chan = data;
    	struct agent_pvt *agent;
    
    	agent = ao2_find(agents, chan, 0);
    	if (!agent) {
    		return;
    	}
    	ast_log(LOG_WARNING, "Agent %s: Forced logout.  Lost control of %s because: %s\n",
    		agent->username, ast_channel_name(chan),
    
    		ast_bridge_after_cb_reason_string(reason));
    
    	agent_lock(agent);
    	agent_logout(agent);
    	ao2_ref(agent, -1);
    }
    
    /*!
     * \internal
     * \brief Get the lock on the agent bridge_channel and return it.
     * \since 12.0.0
     *
     * \param agent Whose bridge_channel to get.
     *
     * \retval bridge_channel on success (Reffed and locked).
     * \retval NULL on error.
     */
    static struct ast_bridge_channel *agent_bridge_channel_get_lock(struct agent_pvt *agent)
    {
    	struct ast_channel *logged;
    	struct ast_bridge_channel *bc;
    
    	for (;;) {
    		agent_lock(agent);
    		logged = agent->logged;
    		if (!logged) {
    			agent_unlock(agent);
    			return NULL;
    		}
    		ast_channel_ref(logged);
    		agent_unlock(agent);
    
    		ast_channel_lock(logged);
    		bc = ast_channel_get_bridge_channel(logged);
    		ast_channel_unlock(logged);
    		ast_channel_unref(logged);
    		if (!bc) {
    			if (agent->logged != logged) {
    				continue;
    			}
    			return NULL;
    		}
    
    		ast_bridge_channel_lock(bc);
    		if (bc->chan != logged || agent->logged != logged) {
    			ast_bridge_channel_unlock(bc);
    			ao2_ref(bc, -1);
    			continue;
    		}
    		return bc;
    	}
    }
    
    static void caller_abort_agent(struct agent_pvt *agent)
    {
    	struct ast_bridge_channel *logged;
    
    	logged = agent_bridge_channel_get_lock(agent);
    	if (!logged) {
    		struct ast_bridge *caller_bridge;
    
    		ast_debug(1, "Agent '%s' no longer logged in.\n", agent->username);
    
    		agent_lock(agent);
    		caller_bridge = agent->caller_bridge;
    		agent->caller_bridge = NULL;
    		agent_unlock(agent);
    		if (caller_bridge) {
    
    			ast_bridge_destroy(caller_bridge, 0);
    
    		}
    		return;
    	}
    
    	/* Kick the agent out of the holding bridge to reset it. */
    
    	ast_bridge_channel_leave_bridge_nolock(logged, BRIDGE_CHANNEL_STATE_END,
    		AST_CAUSE_NORMAL_CLEARING);
    
    	ast_bridge_channel_unlock(logged);
    }
    
    
    static int caller_safety_timeout(struct ast_bridge_channel *bridge_channel, void *hook_pvt)
    
    {
    	struct agent_pvt *agent = hook_pvt;
    
    	if (agent->state == AGENT_STATE_CALL_PRESENT) {
    
    		ast_log(LOG_WARNING, "Agent '%s' process did not respond.  Safety timeout.\n",
    			agent->username);
    		pbx_builtin_setvar_helper(bridge_channel->chan, "AGENT_STATUS", "ERROR");
    
    		ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END, 0);
    
    		caller_abort_agent(agent);
    	}
    
    	return -1;
    }
    
    static void agent_alert(struct ast_bridge_channel *bridge_channel, const void *payload, size_t payload_size)
    {
    	const char *agent_id = payload;
    	const char *playfile;
    
    	const char *dtmf_accept;
    	struct agent_pvt *agent;
    	int digit;
    	char dtmf[2];
    
    
    	agent = ao2_find(agents, agent_id, OBJ_KEY);
    	if (!agent) {
    		ast_debug(1, "Agent '%s' does not exist.  Where did it go?\n", agent_id);
    		return;
    	}
    
    
    	/* Change holding bridge participant role's idle mode to silence */
    	ast_bridge_channel_lock_bridge(bridge_channel);
    	ast_bridge_channel_clear_roles(bridge_channel);
    	ast_channel_set_bridge_role_option(bridge_channel->chan, "holding_participant", "idle_mode", "silence");
    	ast_bridge_channel_establish_roles(bridge_channel);
    	ast_bridge_unlock(bridge_channel->bridge);
    
    
    	agent_lock(agent);
    	playfile = ast_strdupa(agent->cfg->beep_sound);
    
    
    	/* Determine which DTMF digits interrupt the alerting signal. */
    	if (ast_test_flag(agent, AGENT_FLAG_ACK_CALL)
    		? agent->override_ack_call : agent->cfg->ack_call) {
    		dtmf_accept = ast_test_flag(agent, AGENT_FLAG_DTMF_ACCEPT)
    			? agent->override_dtmf_accept : agent->cfg->dtmf_accept;
    
    		/* Only the first digit of the ack will stop playback. */
    		dtmf[0] = *dtmf_accept;
    		dtmf[1] = '\0';
    		dtmf_accept = dtmf;
    	} else {
    		dtmf_accept = NULL;
    	}
    
    	agent_unlock(agent);
    
    
    	/* Alert the agent. */
    	digit = ast_stream_and_wait(bridge_channel->chan, playfile,
    		ast_strlen_zero(dtmf_accept) ? AST_DIGIT_ANY : dtmf_accept);
    	ast_stopstream(bridge_channel->chan);
    
    
    	agent_lock(agent);
    	switch (agent->state) {
    	case AGENT_STATE_CALL_PRESENT:
    
    		if (!ast_strlen_zero(dtmf_accept)) {
    
    			agent->state = AGENT_STATE_CALL_WAIT_ACK;
    			agent->ack_time = ast_tvnow();
    
    
    			if (0 < digit) {
    				/* Playback was interrupted by a digit. */
    				agent_unlock(agent);
    				ao2_ref(agent, -1);
    				ast_bridge_channel_feature_digit(bridge_channel, digit);
    				return;
    			}
    
    			break;
    		}
    
    		/* Connect to caller now. */
    		ast_debug(1, "Agent %s: Immediately connecting to call.\n", agent->username);
    		agent_connect_caller(bridge_channel, agent);/* Will unlock agent. */
    
    		return;
    	default:
    		break;
    	}
    	agent_unlock(agent);
    
    }
    
    static int send_alert_to_agent(struct ast_bridge_channel *bridge_channel, const char *agent_id)
    {
    
    	return ast_bridge_channel_queue_callback(bridge_channel,
    		AST_BRIDGE_CHANNEL_CB_OPTION_MEDIA, agent_alert, agent_id, strlen(agent_id) + 1);
    
    }
    
    static int send_colp_to_agent(struct ast_bridge_channel *bridge_channel, struct ast_party_connected_line *connected)
    {
    	struct ast_set_party_connected_line update = {
    		.id.name = 1,
    		.id.number = 1,
    		.id.subaddress = 1,
    	};
    	unsigned char data[1024];	/* This should be large enough */
    	size_t datalen;
    
    	datalen = ast_connected_line_build_data(data, sizeof(data), connected, &update);
    	if (datalen == (size_t) -1) {
    		return 0;
    	}
    
    	return ast_bridge_channel_queue_control_data(bridge_channel,
    		AST_CONTROL_CONNECTED_LINE, data, datalen);
    }
    
    
    /*!
     * \internal
     * \brief Caller joined the bridge event callback.
     *
     * \param bridge_channel Channel executing the feature
     * \param hook_pvt Private data passed in when the hook was created
     *
     * \retval 0 Keep the callback hook.
     * \retval -1 Remove the callback hook.
     */
    static int caller_joined_bridge(struct ast_bridge_channel *bridge_channel, void *hook_pvt)
    {
    	struct agent_pvt *agent = hook_pvt;
    	struct ast_bridge_channel *logged;
    	int res;
    
    	logged = agent_bridge_channel_get_lock(agent);
    	if (!logged) {
    		ast_verb(3, "Agent '%s' not logged in.\n", agent->username);
    		pbx_builtin_setvar_helper(bridge_channel->chan, "AGENT_STATUS", "NOT_LOGGED_IN");
    
    		ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END, 0);
    		caller_abort_agent(agent);
    		return -1;
    	}
    
    	res = send_alert_to_agent(logged, agent->username);
    	ast_bridge_channel_unlock(logged);
    	ao2_ref(logged, -1);
    	if (res) {
    		ast_verb(3, "Agent '%s': Failed to alert the agent.\n", agent->username);
    		pbx_builtin_setvar_helper(bridge_channel->chan, "AGENT_STATUS", "ERROR");
    
    		ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END, 0);
    		caller_abort_agent(agent);
    		return -1;
    	}
    
    	pbx_builtin_setvar_helper(bridge_channel->chan, "AGENT_STATUS", "NOT_CONNECTED");
    	ast_indicate(bridge_channel->chan, AST_CONTROL_RINGING);
    	return -1;
    }
    
    
    /*!
     * \brief Dialplan AgentRequest application to locate an agent to talk with.
     *
     * \param chan Channel wanting to talk with an agent.
     * \param data Application parameters
     *
     * \retval 0 To continue in dialplan.
     * \retval -1 To hangup.
     */
    static int agent_request_exec(struct ast_channel *chan, const char *data)
    {
    	struct ast_bridge *caller_bridge;
    	struct ast_bridge_channel *logged;
    	char *parse;
    	int res;
    	struct ast_bridge_features caller_features;
    	struct ast_party_connected_line connected;
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(agent_id);
    		AST_APP_ARG(other);		/* Any remaining unused arguments */
    	);
    
    	RAII_VAR(struct agent_pvt *, agent, NULL, ao2_cleanup);
    
    	if (bridge_agent_hold_deferred_create()) {
    		return -1;
    	}
    
    	parse = ast_strdupa(data);
    	AST_STANDARD_APP_ARGS(args, parse);
    
    	if (ast_strlen_zero(args.agent_id)) {
    		ast_log(LOG_WARNING, "AgentRequest requires an AgentId\n");
    		return -1;
    	}
    
    	/* Find the agent. */
    	agent = ao2_find(agents, args.agent_id, OBJ_KEY);
    	if (!agent) {
    		ast_verb(3, "Agent '%s' does not exist.\n", args.agent_id);
    		pbx_builtin_setvar_helper(chan, "AGENT_STATUS", "INVALID");
    		return 0;
    	}
    
    	if (ast_bridge_features_init(&caller_features)) {
    		return -1;
    	}
    
    	/* Add safety timeout hook. */
    	ao2_ref(agent, +1);
    
    	if (ast_bridge_interval_hook(&caller_features, 0, CALLER_SAFETY_TIMEOUT_TIME,
    
    		caller_safety_timeout, agent, __ao2_cleanup, AST_BRIDGE_HOOK_REMOVE_ON_PULL)) {
    		ao2_ref(agent, -1);
    		ast_bridge_features_cleanup(&caller_features);
    		return -1;
    	}
    
    
    	/* Setup the alert agent on caller joining the bridge hook. */
    	ao2_ref(agent, +1);
    	if (ast_bridge_join_hook(&caller_features, caller_joined_bridge, agent,
    		__ao2_cleanup, 0)) {
    		ao2_ref(agent, -1);
    		ast_bridge_features_cleanup(&caller_features);
    		return -1;
    	}
    
    
    	caller_bridge = ast_bridge_basic_new();
    	if (!caller_bridge) {
    		ast_bridge_features_cleanup(&caller_features);
    		return -1;
    	}
    
    	agent_lock(agent);
    	switch (agent->state) {
    	case AGENT_STATE_LOGGED_OUT:
    	case AGENT_STATE_LOGGING_OUT:
    		agent_unlock(agent);
    
    		ast_bridge_features_cleanup(&caller_features);
    		ast_verb(3, "Agent '%s' not logged in.\n", agent->username);
    		pbx_builtin_setvar_helper(chan, "AGENT_STATUS", "NOT_LOGGED_IN");
    		return 0;
    	case AGENT_STATE_READY_FOR_CALL:
    		ao2_ref(caller_bridge, +1);
    		agent->caller_bridge = caller_bridge;
    		agent->state = AGENT_STATE_CALL_PRESENT;
    		agent->devstate = AST_DEVICE_INUSE;
    		break;
    	default:
    		agent_unlock(agent);
    
    		ast_bridge_features_cleanup(&caller_features);
    		ast_verb(3, "Agent '%s' is busy.\n", agent->username);
    		pbx_builtin_setvar_helper(chan, "AGENT_STATUS", "BUSY");
    		return 0;
    	}
    	agent_unlock(agent);
    	agent_devstate_changed(agent->username);
    
    
    	/* Get COLP for agent. */
    	ast_party_connected_line_init(&connected);
    	ast_channel_lock(chan);
    	ast_connected_line_copy_from_caller(&connected, ast_channel_caller(chan));
    	ast_channel_unlock(chan);
    
    
    	logged = agent_bridge_channel_get_lock(agent);
    	if (!logged) {
    		ast_party_connected_line_free(&connected);
    
    		ast_bridge_features_cleanup(&caller_features);
    		ast_verb(3, "Agent '%s' not logged in.\n", agent->username);
    		pbx_builtin_setvar_helper(chan, "AGENT_STATUS", "NOT_LOGGED_IN");
    		return 0;
    	}
    
    	send_colp_to_agent(logged, &connected);
    	ast_bridge_channel_unlock(logged);
    	ao2_ref(logged, -1);
    
    	ast_party_connected_line_free(&connected);