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 71eb8a262f)
This commit is contained in:
Holger Hans Peter Freyther
2024-06-15 16:01:58 +08:00
committed by Asterisk Development Team
parent 007e281313
commit 0e8bde6bde
18 changed files with 1462 additions and 6 deletions

View File

@@ -2428,6 +2428,60 @@ ari_validator ast_ari_validate_mailbox_fn(void)
return ast_ari_validate_mailbox;
}
int ast_ari_validate_additional_param(struct ast_json *json)
{
int res = 1;
struct ast_json_iter *iter;
int has_parameter_name = 0;
int has_parameter_value = 0;
for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
if (strcmp("parameter_name", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
has_parameter_name = 1;
prop_is_valid = ast_ari_validate_string(
ast_json_object_iter_value(iter));
if (!prop_is_valid) {
ast_log(LOG_ERROR, "ARI AdditionalParam field parameter_name failed validation\n");
res = 0;
}
} else
if (strcmp("parameter_value", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
has_parameter_value = 1;
prop_is_valid = ast_ari_validate_string(
ast_json_object_iter_value(iter));
if (!prop_is_valid) {
ast_log(LOG_ERROR, "ARI AdditionalParam field parameter_value failed validation\n");
res = 0;
}
} else
{
ast_log(LOG_ERROR,
"ARI AdditionalParam has undocumented field %s\n",
ast_json_object_iter_key(iter));
res = 0;
}
}
if (!has_parameter_name) {
ast_log(LOG_ERROR, "ARI AdditionalParam missing required field parameter_name\n");
res = 0;
}
if (!has_parameter_value) {
ast_log(LOG_ERROR, "ARI AdditionalParam missing required field parameter_value\n");
res = 0;
}
return res;
}
ari_validator ast_ari_validate_additional_param_fn(void)
{
return ast_ari_validate_additional_param;
}
int ast_ari_validate_application_move_failed(struct ast_json *json)
{
int res = 1;
@@ -4915,6 +4969,126 @@ ari_validator ast_ari_validate_channel_talking_started_fn(void)
return ast_ari_validate_channel_talking_started;
}
int ast_ari_validate_channel_transfer(struct ast_json *json)
{
int res = 1;
struct ast_json_iter *iter;
int has_type = 0;
int has_application = 0;
int has_timestamp = 0;
int has_refer_to = 0;
int has_referred_by = 0;
for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
if (strcmp("asterisk_id", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
prop_is_valid = ast_ari_validate_string(
ast_json_object_iter_value(iter));
if (!prop_is_valid) {
ast_log(LOG_ERROR, "ARI ChannelTransfer field asterisk_id failed validation\n");
res = 0;
}
} else
if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
has_type = 1;
prop_is_valid = ast_ari_validate_string(
ast_json_object_iter_value(iter));
if (!prop_is_valid) {
ast_log(LOG_ERROR, "ARI ChannelTransfer field type failed validation\n");
res = 0;
}
} else
if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
has_application = 1;
prop_is_valid = ast_ari_validate_string(
ast_json_object_iter_value(iter));
if (!prop_is_valid) {
ast_log(LOG_ERROR, "ARI ChannelTransfer field application failed validation\n");
res = 0;
}
} else
if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
has_timestamp = 1;
prop_is_valid = ast_ari_validate_date(
ast_json_object_iter_value(iter));
if (!prop_is_valid) {
ast_log(LOG_ERROR, "ARI ChannelTransfer field timestamp failed validation\n");
res = 0;
}
} else
if (strcmp("refer_to", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
has_refer_to = 1;
prop_is_valid = ast_ari_validate_refer_to(
ast_json_object_iter_value(iter));
if (!prop_is_valid) {
ast_log(LOG_ERROR, "ARI ChannelTransfer field refer_to failed validation\n");
res = 0;
}
} else
if (strcmp("referred_by", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
has_referred_by = 1;
prop_is_valid = ast_ari_validate_referred_by(
ast_json_object_iter_value(iter));
if (!prop_is_valid) {
ast_log(LOG_ERROR, "ARI ChannelTransfer field referred_by failed validation\n");
res = 0;
}
} else
if (strcmp("state", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
prop_is_valid = ast_ari_validate_string(
ast_json_object_iter_value(iter));
if (!prop_is_valid) {
ast_log(LOG_ERROR, "ARI ChannelTransfer field state failed validation\n");
res = 0;
}
} else
{
ast_log(LOG_ERROR,
"ARI ChannelTransfer has undocumented field %s\n",
ast_json_object_iter_key(iter));
res = 0;
}
}
if (!has_type) {
ast_log(LOG_ERROR, "ARI ChannelTransfer missing required field type\n");
res = 0;
}
if (!has_application) {
ast_log(LOG_ERROR, "ARI ChannelTransfer missing required field application\n");
res = 0;
}
if (!has_timestamp) {
ast_log(LOG_ERROR, "ARI ChannelTransfer missing required field timestamp\n");
res = 0;
}
if (!has_refer_to) {
ast_log(LOG_ERROR, "ARI ChannelTransfer missing required field refer_to\n");
res = 0;
}
if (!has_referred_by) {
ast_log(LOG_ERROR, "ARI ChannelTransfer missing required field referred_by\n");
res = 0;
}
return res;
}
ari_validator ast_ari_validate_channel_transfer_fn(void)
{
return ast_ari_validate_channel_transfer;
}
int ast_ari_validate_channel_unhold(struct ast_json *json)
{
int res = 1;
@@ -5876,6 +6050,9 @@ int ast_ari_validate_event(struct ast_json *json)
if (strcmp("ChannelTalkingStarted", discriminator) == 0) {
return ast_ari_validate_channel_talking_started(json);
} else
if (strcmp("ChannelTransfer", discriminator) == 0) {
return ast_ari_validate_channel_transfer(json);
} else
if (strcmp("ChannelUnhold", discriminator) == 0) {
return ast_ari_validate_channel_unhold(json);
} else
@@ -6083,6 +6260,9 @@ int ast_ari_validate_message(struct ast_json *json)
if (strcmp("ChannelTalkingStarted", discriminator) == 0) {
return ast_ari_validate_channel_talking_started(json);
} else
if (strcmp("ChannelTransfer", discriminator) == 0) {
return ast_ari_validate_channel_transfer(json);
} else
if (strcmp("ChannelUnhold", discriminator) == 0) {
return ast_ari_validate_channel_unhold(json);
} else
@@ -7006,6 +7186,177 @@ ari_validator ast_ari_validate_recording_started_fn(void)
return ast_ari_validate_recording_started;
}
int ast_ari_validate_refer_to(struct ast_json *json)
{
int res = 1;
struct ast_json_iter *iter;
int has_requested_destination = 0;
for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
if (strcmp("bridge", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
prop_is_valid = ast_ari_validate_bridge(
ast_json_object_iter_value(iter));
if (!prop_is_valid) {
ast_log(LOG_ERROR, "ARI ReferTo field bridge failed validation\n");
res = 0;
}
} else
if (strcmp("connected_channel", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
prop_is_valid = ast_ari_validate_channel(
ast_json_object_iter_value(iter));
if (!prop_is_valid) {
ast_log(LOG_ERROR, "ARI ReferTo field connected_channel failed validation\n");
res = 0;
}
} else
if (strcmp("destination_channel", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
prop_is_valid = ast_ari_validate_channel(
ast_json_object_iter_value(iter));
if (!prop_is_valid) {
ast_log(LOG_ERROR, "ARI ReferTo field destination_channel failed validation\n");
res = 0;
}
} else
if (strcmp("requested_destination", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
has_requested_destination = 1;
prop_is_valid = ast_ari_validate_required_destination(
ast_json_object_iter_value(iter));
if (!prop_is_valid) {
ast_log(LOG_ERROR, "ARI ReferTo field requested_destination failed validation\n");
res = 0;
}
} else
{
ast_log(LOG_ERROR,
"ARI ReferTo has undocumented field %s\n",
ast_json_object_iter_key(iter));
res = 0;
}
}
if (!has_requested_destination) {
ast_log(LOG_ERROR, "ARI ReferTo missing required field requested_destination\n");
res = 0;
}
return res;
}
ari_validator ast_ari_validate_refer_to_fn(void)
{
return ast_ari_validate_refer_to;
}
int ast_ari_validate_referred_by(struct ast_json *json)
{
int res = 1;
struct ast_json_iter *iter;
int has_source_channel = 0;
for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
if (strcmp("bridge", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
prop_is_valid = ast_ari_validate_bridge(
ast_json_object_iter_value(iter));
if (!prop_is_valid) {
ast_log(LOG_ERROR, "ARI ReferredBy field bridge failed validation\n");
res = 0;
}
} else
if (strcmp("connected_channel", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
prop_is_valid = ast_ari_validate_channel(
ast_json_object_iter_value(iter));
if (!prop_is_valid) {
ast_log(LOG_ERROR, "ARI ReferredBy field connected_channel failed validation\n");
res = 0;
}
} else
if (strcmp("source_channel", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
has_source_channel = 1;
prop_is_valid = ast_ari_validate_channel(
ast_json_object_iter_value(iter));
if (!prop_is_valid) {
ast_log(LOG_ERROR, "ARI ReferredBy field source_channel failed validation\n");
res = 0;
}
} else
{
ast_log(LOG_ERROR,
"ARI ReferredBy has undocumented field %s\n",
ast_json_object_iter_key(iter));
res = 0;
}
}
if (!has_source_channel) {
ast_log(LOG_ERROR, "ARI ReferredBy missing required field source_channel\n");
res = 0;
}
return res;
}
ari_validator ast_ari_validate_referred_by_fn(void)
{
return ast_ari_validate_referred_by;
}
int ast_ari_validate_required_destination(struct ast_json *json)
{
int res = 1;
struct ast_json_iter *iter;
for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
if (strcmp("additional_protocol_params", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
prop_is_valid = ast_ari_validate_list(
ast_json_object_iter_value(iter),
ast_ari_validate_additional_param);
if (!prop_is_valid) {
ast_log(LOG_ERROR, "ARI RequiredDestination field additional_protocol_params failed validation\n");
res = 0;
}
} else
if (strcmp("destination", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
prop_is_valid = ast_ari_validate_string(
ast_json_object_iter_value(iter));
if (!prop_is_valid) {
ast_log(LOG_ERROR, "ARI RequiredDestination field destination failed validation\n");
res = 0;
}
} else
if (strcmp("protocol_id", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
prop_is_valid = ast_ari_validate_string(
ast_json_object_iter_value(iter));
if (!prop_is_valid) {
ast_log(LOG_ERROR, "ARI RequiredDestination field protocol_id failed validation\n");
res = 0;
}
} else
{
ast_log(LOG_ERROR,
"ARI RequiredDestination has undocumented field %s\n",
ast_json_object_iter_key(iter));
res = 0;
}
}
return res;
}
ari_validator ast_ari_validate_required_destination_fn(void)
{
return ast_ari_validate_required_destination;
}
int ast_ari_validate_stasis_end(struct ast_json *json)
{
int res = 1;

View File

@@ -571,6 +571,22 @@ int ast_ari_validate_mailbox(struct ast_json *json);
*/
ari_validator ast_ari_validate_mailbox_fn(void);
/*!
* \brief Validator for AdditionalParam.
*
* Protocol specific additional parameter
*
* \param json JSON object to validate.
* \retval True (non-zero) if valid.
* \retval False (zero) if invalid.
*/
int ast_ari_validate_additional_param(struct ast_json *json);
/*!
* \brief Function pointer to ast_ari_validate_additional_param().
*/
ari_validator ast_ari_validate_additional_param_fn(void);
/*!
* \brief Validator for ApplicationMoveFailed.
*
@@ -911,6 +927,22 @@ int ast_ari_validate_channel_talking_started(struct ast_json *json);
*/
ari_validator ast_ari_validate_channel_talking_started_fn(void);
/*!
* \brief Validator for ChannelTransfer.
*
* transfer on a channel.
*
* \param json JSON object to validate.
* \retval True (non-zero) if valid.
* \retval False (zero) if invalid.
*/
int ast_ari_validate_channel_transfer(struct ast_json *json);
/*!
* \brief Function pointer to ast_ari_validate_channel_transfer().
*/
ari_validator ast_ari_validate_channel_transfer_fn(void);
/*!
* \brief Validator for ChannelUnhold.
*
@@ -1215,6 +1247,54 @@ int ast_ari_validate_recording_started(struct ast_json *json);
*/
ari_validator ast_ari_validate_recording_started_fn(void);
/*!
* \brief Validator for ReferTo.
*
* transfer destination requested by transferee
*
* \param json JSON object to validate.
* \retval True (non-zero) if valid.
* \retval False (zero) if invalid.
*/
int ast_ari_validate_refer_to(struct ast_json *json);
/*!
* \brief Function pointer to ast_ari_validate_refer_to().
*/
ari_validator ast_ari_validate_refer_to_fn(void);
/*!
* \brief Validator for ReferredBy.
*
* transfer destination requested by transferee
*
* \param json JSON object to validate.
* \retval True (non-zero) if valid.
* \retval False (zero) if invalid.
*/
int ast_ari_validate_referred_by(struct ast_json *json);
/*!
* \brief Function pointer to ast_ari_validate_referred_by().
*/
ari_validator ast_ari_validate_referred_by_fn(void);
/*!
* \brief Validator for RequiredDestination.
*
* Information about the requested destination
*
* \param json JSON object to validate.
* \retval True (non-zero) if valid.
* \retval False (zero) if invalid.
*/
int ast_ari_validate_required_destination(struct ast_json *json);
/*!
* \brief Function pointer to ast_ari_validate_required_destination().
*/
ari_validator ast_ari_validate_required_destination_fn(void);
/*!
* \brief Validator for StasisEnd.
*
@@ -1441,6 +1521,9 @@ ari_validator ast_ari_validate_application_fn(void);
* - name: string (required)
* - new_messages: int (required)
* - old_messages: int (required)
* AdditionalParam
* - parameter_name: string (required)
* - parameter_value: string (required)
* ApplicationMoveFailed
* - asterisk_id: string
* - type: string (required)
@@ -1606,6 +1689,14 @@ ari_validator ast_ari_validate_application_fn(void);
* - application: string (required)
* - timestamp: Date (required)
* - channel: Channel (required)
* ChannelTransfer
* - asterisk_id: string
* - type: string (required)
* - application: string (required)
* - timestamp: Date (required)
* - refer_to: ReferTo (required)
* - referred_by: ReferredBy (required)
* - state: string
* ChannelUnhold
* - asterisk_id: string
* - type: string (required)
@@ -1726,6 +1817,19 @@ ari_validator ast_ari_validate_application_fn(void);
* - application: string (required)
* - timestamp: Date (required)
* - recording: LiveRecording (required)
* ReferTo
* - bridge: Bridge
* - connected_channel: Channel
* - destination_channel: Channel
* - requested_destination: RequiredDestination (required)
* ReferredBy
* - bridge: Bridge
* - connected_channel: Channel
* - source_channel: Channel (required)
* RequiredDestination
* - additional_protocol_params: List[AdditionalParam]
* - destination: string
* - protocol_id: string
* StasisEnd
* - asterisk_id: string
* - type: string (required)

View File

@@ -2252,3 +2252,44 @@ void ast_ari_channels_external_media(struct ast_variable *headers,
"The encapsulation and/or transport is not supported");
}
}
void ast_ari_channels_transfer_progress(struct ast_variable *headers, struct ast_ari_channels_transfer_progress_args *args, struct ast_ari_response *response)
{
enum ast_control_transfer message;
RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
RAII_VAR(struct ast_channel *, chan, NULL, ast_channel_cleanup);
control = find_control(response, args->channel_id);
if (control == NULL) {
/* Response filled in by find_control */
return;
}
chan = ast_channel_get_by_name(args->channel_id);
if (!chan) {
ast_ari_response_error(response, 404, "Not Found",
"Callee not found");
return;
}
if (ast_strlen_zero(args->states)) {
ast_ari_response_error(response, 400, "Bad Request", "states must not be empty");
return;
}
if (strcasecmp(args->states, "channel_progress") == 0) {
message = AST_TRANSFER_PROGRESS;
} else if (strcasecmp(args->states, "channel_answered") == 0) {
message = AST_TRANSFER_SUCCESS;
} else if (strcasecmp(args->states, "channel_unavailable") == 0) {
message = AST_TRANSFER_UNAVAILABLE;
} else if (strcasecmp(args->states, "channel_declined") == 0) {
message = AST_TRANSFER_FAILED;
} else {
ast_ari_response_error(response, 400, "Bad Request", "Invalid states value");
return;
}
ast_indicate_data(chan, AST_CONTROL_TRANSFER, &message, sizeof(message));
ast_ari_response_no_content(response);
}

View File

@@ -870,5 +870,31 @@ int ast_ari_channels_external_media_parse_body(
* \param[out] response HTTP response
*/
void ast_ari_channels_external_media(struct ast_variable *headers, struct ast_ari_channels_external_media_args *args, struct ast_ari_response *response);
/*! Argument struct for ast_ari_channels_transfer_progress() */
struct ast_ari_channels_transfer_progress_args {
/*! Channel's id */
const char *channel_id;
/*! The state of the progress */
const char *states;
};
/*!
* \brief Body parsing function for /channels/{channelId}/transfer_progress.
* \param body The JSON body from which to parse parameters.
* \param[out] args The args structure to parse into.
* \retval zero on success
* \retval non-zero on failure
*/
int ast_ari_channels_transfer_progress_parse_body(
struct ast_json *body,
struct ast_ari_channels_transfer_progress_args *args);
/*!
* \brief Inform the channel about the progress of the attended/blind transfer.
*
* \param headers HTTP headers
* \param args Swagger parameters
* \param[out] response HTTP response
*/
void ast_ari_channels_transfer_progress(struct ast_variable *headers, struct ast_ari_channels_transfer_progress_args *args, struct ast_ari_response *response);
#endif /* _ASTERISK_RESOURCE_CHANNELS_H */