ari/pjsip: Make it possible to control transfers through ARI

Introduce a ChannelTransfer event and the ability to notify progress to
ARI. Implement emitting this event from the PJSIP channel instead of
handling the transfer in Asterisk when configured.

Introduce a dialplan function to the PJSIP channel to switch between the
"core" and "ari-only" behavior.

UserNote: Call transfers on the PJSIP channel can now be controlled by
ARI. This can be enabled by using the PJSIP_TRANSFER_HANDLING(ari-only)
dialplan function.

(cherry picked from commit 5e4fca062c)
This commit is contained in:
Holger Hans Peter Freyther
2024-06-15 16:01:58 +08:00
committed by Asterisk Development Team
parent af966a98a1
commit 6f17f61394
18 changed files with 1462 additions and 6 deletions

View File

@@ -35,6 +35,8 @@
#include "asterisk/datastore.h"
#include "asterisk/pbx.h"
#include "asterisk/manager.h"
#include "asterisk/stasis_bridges.h"
#include "asterisk/stasis_channels.h"
#include "asterisk/strings.h"
#include "asterisk/astobj2.h"
#include "asterisk/vector.h"
@@ -535,3 +537,59 @@ int ast_refer_init(void)
ast_register_cleanup(refer_shutdown);
return 0;
}
int ast_refer_notify_transfer_request(struct ast_channel *source, const char *referred_by,
const char *exten, const char *protocol_id,
struct ast_channel *dest, struct ast_refer_params *params,
enum ast_control_transfer state)
{
RAII_VAR(struct ast_ari_transfer_message *, transfer_message, NULL, ao2_cleanup);
RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
RAII_VAR(struct ast_bridge *, source_bridge, NULL, ao2_cleanup);
RAII_VAR(struct ast_bridge *, dest_bridge, NULL, ao2_cleanup);
transfer_message = ast_ari_transfer_message_create(source, referred_by, exten, protocol_id, dest, params, state);
if (!transfer_message) {
return -1;
}
source_bridge = ast_bridge_transfer_acquire_bridge(source);
if (source_bridge) {
RAII_VAR(struct ast_channel *, peer, NULL, ast_channel_cleanup);
ast_bridge_lock(source_bridge);
transfer_message->source_bridge = ast_bridge_get_snapshot(source_bridge);
peer = ast_bridge_peer_nolock(source_bridge, source);
if (peer) {
ast_channel_lock(peer);
transfer_message->source_peer = ao2_bump(ast_channel_snapshot(peer));
ast_channel_unlock(peer);
}
ast_bridge_unlock(source_bridge);
}
if (dest) {
dest_bridge = ast_bridge_transfer_acquire_bridge(dest);
if (dest_bridge) {
RAII_VAR(struct ast_channel *, peer, NULL, ast_channel_cleanup);
ast_bridge_lock(dest_bridge);
transfer_message->dest_bridge = ast_bridge_get_snapshot(dest_bridge);
peer = ast_bridge_peer_nolock(dest_bridge, dest);
if (peer) {
ast_channel_lock(peer);
transfer_message->dest_peer = ao2_bump(ast_channel_snapshot(peer));
ast_channel_unlock(peer);
}
ast_bridge_unlock(dest_bridge);
}
}
msg = stasis_message_create(ast_channel_transfer_request_type(), transfer_message);
if (msg) {
ast_channel_lock(source);
stasis_publish(ast_channel_topic(source), msg);
ast_channel_unlock(source);
}
return 0;
}

View File

@@ -34,12 +34,14 @@
#include "asterisk/json.h"
#include "asterisk/pbx.h"
#include "asterisk/bridge.h"
#include "asterisk/stasis_bridges.h"
#include "asterisk/translate.h"
#include "asterisk/stasis.h"
#include "asterisk/stasis_channels.h"
#include "asterisk/dial.h"
#include "asterisk/linkedlists.h"
#include "asterisk/utf8.h"
#include "asterisk/vector.h"
/*** DOCUMENTATION
<managerEvent language="en_US" name="VarSet">
@@ -1631,6 +1633,175 @@ static struct ast_json *unhold_to_json(struct stasis_message *message,
"channel", json_channel);
}
static const char *state2str(enum ast_control_transfer state) {
switch (state) {
case AST_TRANSFER_FAILED:
return "channel_declined";
case AST_TRANSFER_SUCCESS:
return "channel_answered";
case AST_TRANSFER_PROGRESS:
return "channel_progress";
case AST_TRANSFER_UNAVAILABLE:
return "channel_declined";
default:
return "invalid";
}
}
static struct ast_json *ari_transfer_to_json(struct stasis_message *msg,
const struct stasis_message_sanitizer *sanitize)
{
struct ast_json *json_channel, *res;
struct ast_json *refer_json, *referred_json, *dest_json;
const struct timeval *tv = stasis_message_timestamp(msg);
struct ast_ari_transfer_message *transfer_msg = stasis_message_data(msg);
dest_json = ast_json_pack("{s: s, s: s}",
"protocol_id", transfer_msg->protocol_id,
"destination", transfer_msg->destination);
if (!dest_json) {
return NULL;
}
if (AST_VECTOR_SIZE(transfer_msg->refer_params) > 0) {
struct ast_json *params = ast_json_array_create();
if (!params) {
return NULL;
}
for (int i = 0; i < AST_VECTOR_SIZE(transfer_msg->refer_params); ++i) {
struct ast_refer_param param = AST_VECTOR_GET(transfer_msg->refer_params, i);
ast_json_array_append(params, ast_json_pack("{s: s, s: s}",
"parameter_name", param.param_name,
"parameter_value", param.param_value));
}
ast_json_object_set(dest_json, "additional_protocol_params", params);
}
refer_json = ast_json_pack("{s: o}",
"requested_destination", dest_json);
if (!refer_json) {
return NULL;
}
if (transfer_msg->dest) {
struct ast_json *dest_chan_json;
dest_chan_json = ast_channel_snapshot_to_json(transfer_msg->dest, sanitize);
ast_json_object_set(refer_json, "destination_channel", dest_chan_json);
}
if (transfer_msg->dest_peer) {
struct ast_json *peer_chan_json;
peer_chan_json = ast_channel_snapshot_to_json(transfer_msg->dest_peer, sanitize);
ast_json_object_set(refer_json, "connected_channel", peer_chan_json);
}
if (transfer_msg->dest_bridge) {
struct ast_json *dest_bridge_json;
dest_bridge_json = ast_bridge_snapshot_to_json(transfer_msg->dest_bridge, sanitize);
ast_json_object_set(refer_json, "bridge", dest_bridge_json);
}
json_channel = ast_channel_snapshot_to_json(transfer_msg->source, sanitize);
if (!json_channel) {
return NULL;
}
referred_json = ast_json_pack("{s: o}",
"source_channel", json_channel);
if (!referred_json) {
return NULL;
}
if (transfer_msg->source_peer) {
struct ast_json *peer_chan_json;
peer_chan_json = ast_channel_snapshot_to_json(transfer_msg->source_peer, sanitize);
ast_json_object_set(referred_json, "connected_channel", peer_chan_json);
}
if (transfer_msg->source_bridge) {
struct ast_json *source_bridge_json;
source_bridge_json = ast_bridge_snapshot_to_json(transfer_msg->source_bridge, sanitize);
ast_json_object_set(referred_json, "bridge", source_bridge_json);
}
res = ast_json_pack("{s: s, s: o, s: o, s: o}",
"type", "ChannelTransfer",
"timestamp", ast_json_timeval(*tv, NULL),
"refer_to", refer_json,
"referred_by", referred_json);
if (!res) {
return NULL;
}
if (transfer_msg->state != AST_TRANSFER_INVALID) {
ast_json_object_set(res, "state", ast_json_string_create(state2str(transfer_msg->state)));
}
return res;
}
static void ari_transfer_dtor(void *obj)
{
struct ast_ari_transfer_message *msg = obj;
ao2_cleanup(msg->source);
ao2_cleanup(msg->source_bridge);
ao2_cleanup(msg->source_peer);
ao2_cleanup(msg->dest);
ao2_cleanup(msg->dest_bridge);
ao2_cleanup(msg->dest_peer);
ao2_cleanup(msg->refer_params);
ast_free(msg->referred_by);
ast_free(msg->protocol_id);
}
struct ast_ari_transfer_message *ast_ari_transfer_message_create(struct ast_channel *originating_chan, const char *referred_by,
const char *exten, const char *protocol_id, struct ast_channel *dest,
struct ast_refer_params *params, enum ast_control_transfer state)
{
struct ast_ari_transfer_message *msg;
msg = ao2_alloc(sizeof(*msg), ari_transfer_dtor);
if (!msg) {
return NULL;
}
msg->refer_params = params;
ao2_ref(msg->refer_params, +1);
msg->state = state;
ast_channel_lock(originating_chan);
msg->source = ao2_bump(ast_channel_snapshot(originating_chan));
ast_channel_unlock(originating_chan);
if (!msg->source) {
ao2_cleanup(msg);
return NULL;
}
if (dest) {
ast_channel_lock(dest);
msg->dest = ao2_bump(ast_channel_snapshot(dest));
ast_channel_unlock(dest);
if (!msg->dest) {
ao2_cleanup(msg);
return NULL;
}
}
msg->referred_by = ast_strdup(referred_by);
if (!msg->referred_by) {
ao2_cleanup(msg);
return NULL;
}
ast_copy_string(msg->destination, exten, sizeof(msg->destination));
msg->protocol_id = ast_strdup(protocol_id);
if (!msg->protocol_id) {
ao2_cleanup(msg);
return NULL;
}
return msg;
}
/*!
* @{ \brief Define channel message types.
*/
@@ -1683,6 +1854,9 @@ STASIS_MESSAGE_TYPE_DEFN(ast_channel_talking_stop,
.to_ami = talking_stop_to_ami,
.to_json = talking_stop_to_json,
);
STASIS_MESSAGE_TYPE_DEFN(ast_channel_transfer_request_type,
.to_json = ari_transfer_to_json,
);
/*! @} */
@@ -1721,6 +1895,7 @@ static void stasis_channels_cleanup(void)
STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_agent_logoff_type);
STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_talking_start);
STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_talking_stop);
STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_transfer_request_type);
}
int ast_stasis_channels_init(void)
@@ -1774,6 +1949,7 @@ int ast_stasis_channels_init(void)
res |= STASIS_MESSAGE_TYPE_INIT(ast_channel_mixmonitor_mute_type);
res |= STASIS_MESSAGE_TYPE_INIT(ast_channel_talking_start);
res |= STASIS_MESSAGE_TYPE_INIT(ast_channel_talking_stop);
res |= STASIS_MESSAGE_TYPE_INIT(ast_channel_transfer_request_type);
return res;
}