diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c index cb60378e83..49078c6afb 100644 --- a/channels/chan_pjsip.c +++ b/channels/chan_pjsip.c @@ -1851,6 +1851,8 @@ static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const voi break; case AST_CONTROL_STREAM_TOPOLOGY_SOURCE_CHANGED: break; + case AST_CONTROL_TRANSFER: + break; case -1: res = -1; break; @@ -3274,6 +3276,11 @@ static struct ast_custom_function session_refresh_function = { .write = pjsip_acf_session_refresh_write, }; +static struct ast_custom_function transfer_handling_function = { + .name = "PJSIP_TRANSFER_HANDLING", + .write = pjsip_transfer_handling_write, +}; + static char *app_pjsip_hangup = "PJSIPHangup"; /*! @@ -3338,6 +3345,11 @@ static int load_module(void) goto end; } + if (ast_custom_function_register(&transfer_handling_function)) { + ast_log(LOG_WARNING, "Unable to register PJSIP_TRANSFER_HANDLING dialplan function\n"); + goto end; + } + if (ast_register_application_xml(app_pjsip_hangup, pjsip_app_hangup)) { ast_log(LOG_WARNING, "Unable to register PJSIPHangup dialplan application\n"); goto end; @@ -3393,6 +3405,7 @@ end: ast_custom_function_unregister(&chan_pjsip_parse_uri_function); ast_custom_function_unregister(&chan_pjsip_parse_uri_from_function); ast_custom_function_unregister(&session_refresh_function); + ast_custom_function_unregister(&transfer_handling_function); ast_unregister_application(app_pjsip_hangup); ast_manager_unregister(app_pjsip_hangup); @@ -3426,6 +3439,7 @@ static int unload_module(void) ast_custom_function_unregister(&chan_pjsip_parse_uri_function); ast_custom_function_unregister(&chan_pjsip_parse_uri_from_function); ast_custom_function_unregister(&session_refresh_function); + ast_custom_function_unregister(&transfer_handling_function); ast_unregister_application(app_pjsip_hangup); ast_manager_unregister(app_pjsip_hangup); diff --git a/channels/pjsip/dialplan_functions.c b/channels/pjsip/dialplan_functions.c index 7d0484c9ae..594a56601b 100644 --- a/channels/pjsip/dialplan_functions.c +++ b/channels/pjsip/dialplan_functions.c @@ -1368,3 +1368,36 @@ int pjsip_action_hangup(struct mansession *s, const struct message *m) return ast_manager_hangup_helper(s, m, pjsip_app_hangup_handler, response_code_validator); } + +int pjsip_transfer_handling_write(struct ast_channel *chan, const char *cmd, char *data, const char *value) +{ + struct ast_sip_channel_pvt *channel; + int ret = 0; + + if (!chan) { + ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd); + return -1; + } + + ast_channel_lock(chan); + if (strcmp(ast_channel_tech(chan)->type, "PJSIP")) { + ast_log(LOG_WARNING, "Cannot call %s on a non-PJSIP channel %s\n", cmd, ast_channel_name(chan)); + ast_channel_unlock(chan); + return -1; + } + + channel = ast_channel_tech_pvt(chan); + + if (ast_strlen_zero(value) || !strcmp(value, "core")) { + channel->session->transferhandling_ari = 0; + } else if (!strcmp(value, "ari-only")) { + channel->session->transferhandling_ari = 1; + } else { + ast_log(AST_LOG_WARNING, "Cannot set unknown transfer handling '%s' on channel '%s', transfer handling will remain unchanged.", + value, ast_channel_name(chan)); + ret = -1; + } + + ast_channel_unlock(chan); + return ret; +} diff --git a/channels/pjsip/dialplan_functions_doc.xml b/channels/pjsip/dialplan_functions_doc.xml index ef4280a1dc..bff5206b73 100644 --- a/channels/pjsip/dialplan_functions_doc.xml +++ b/channels/pjsip/dialplan_functions_doc.xml @@ -405,6 +405,32 @@ Parse the contents of the provided variable as a URI and return a specified part of the URI. + + + 22.3.0 + 21.8.0 + 20.13.0 + + + Set how transfers are handled for a PJSIP channel. + + + + How transfers are handled for a PJSIP channel. Default is core. + + + Asterisk will handle attended and blind transfers. + + + Asterisk will generate ARI events on incoming SIP REFER. + + + + + + When written, sets the transferhandling behavior + + diff --git a/channels/pjsip/include/dialplan_functions.h b/channels/pjsip/include/dialplan_functions.h index 03005d5d8f..20edfca087 100644 --- a/channels/pjsip/include/dialplan_functions.h +++ b/channels/pjsip/include/dialplan_functions.h @@ -168,4 +168,16 @@ int pjsip_app_hangup(struct ast_channel *chan, const char *data); */ int pjsip_action_hangup(struct mansession *s, const struct message *m); +/*! + * \brief PJSIP_TRANSFER_HANDLING function write callback + * \param chan The channel the function is called on + * \param cmd the Name of the function + * \param data Arguments passed to the function + * \param value Value to be set by the function + * + * \retval 0 on success + * \retval -1 on failure + */ +int pjsip_transfer_handling_write(struct ast_channel *chan, const char *cmd, char *data, const char *value); + #endif /* _PJSIP_DIALPLAN_FUNCTIONS */ diff --git a/include/asterisk/frame.h b/include/asterisk/frame.h index 78e369c642..fc2c9d513d 100644 --- a/include/asterisk/frame.h +++ b/include/asterisk/frame.h @@ -408,6 +408,9 @@ struct ast_control_t38_parameters { enum ast_control_transfer { AST_TRANSFER_SUCCESS = 0, /*!< Transfer request on the channel worked */ AST_TRANSFER_FAILED, /*!< Transfer request on the channel failed */ + AST_TRANSFER_PROGRESS, /*!< Transfer request on the channel is in progress */ + AST_TRANSFER_UNAVAILABLE, /*!< Transfer request on the channel is unavailable */ + AST_TRANSFER_INVALID, /*!< Invalid state for none of the above. */ }; struct ast_control_pvt_cause_code { diff --git a/include/asterisk/refer.h b/include/asterisk/refer.h index 4d0744c876..10fb6ee716 100644 --- a/include/asterisk/refer.h +++ b/include/asterisk/refer.h @@ -37,6 +37,11 @@ extern "C" { #endif +#include "asterisk/vector.h" +#include "asterisk/frame.h" + +struct ast_channel; + /*! * \brief A refer structure. * @@ -73,6 +78,13 @@ struct ast_refer_tech { int (* const refer_send)(const struct ast_refer *refer); }; +struct ast_refer_param { + const char *param_name; + const char *param_value; +}; + +AST_VECTOR(ast_refer_params, struct ast_refer_param); + /*! * \brief Register a refer technology * @@ -314,6 +326,20 @@ void ast_refer_var_iterator_destroy(struct ast_refer_var_iterator *iter); */ void ast_refer_var_unref_current(struct ast_refer_var_iterator *iter); +/*! + * \brief Notify a transfer request. + * \param originating_chan The channel that received the transfer request + * \param referred_by Information about the requesting identity + * \param exten The extension for blind transfers + * \param protocol_id Technology specific replace indication + * \param dest The identified replace target for attended requests. + * \param params List of protocol specific params. + * \param state The state of the transfer + */ +int ast_refer_notify_transfer_request(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); + /*! * @} */ diff --git a/include/asterisk/res_pjsip_session.h b/include/asterisk/res_pjsip_session.h index d287e96865..75ba60e523 100644 --- a/include/asterisk/res_pjsip_session.h +++ b/include/asterisk/res_pjsip_session.h @@ -217,6 +217,8 @@ struct ast_sip_session { unsigned int defer_terminate:1; /*! Termination requested while termination deferred */ unsigned int terminate_while_deferred:1; + /*! Transferhandling ari */ + unsigned int transferhandling_ari:1; /*! Deferred incoming re-invite */ pjsip_rx_data *deferred_reinvite; /*! Current T.38 state */ diff --git a/include/asterisk/stasis_channels.h b/include/asterisk/stasis_channels.h index 71eb6ff82b..86422540ae 100644 --- a/include/asterisk/stasis_channels.h +++ b/include/asterisk/stasis_channels.h @@ -20,6 +20,7 @@ #ifndef STASIS_CHANNELS_H_ #define STASIS_CHANNELS_H_ +#include "asterisk/refer.h" #include "asterisk/stringfields.h" #include "asterisk/stasis.h" #include "asterisk/channel.h" @@ -665,6 +666,14 @@ struct stasis_message_type *ast_channel_talking_stop(void); */ struct stasis_message_type *ast_channel_tone_detect(void); +/* + * \since 23 + * \brief Message type for a attended or blind transfer request + * + * \return A stasis message type + */ +struct stasis_message_type *ast_channel_transfer_request_type(void); + /*! * \since 12 * \brief Publish in the \ref ast_channel_topic or \ref ast_channel_topic_all @@ -758,6 +767,44 @@ int ast_channel_snapshot_connected_line_equal( const struct ast_channel_snapshot *old_snapshot, const struct ast_channel_snapshot *new_snapshot); + +/*! + * \since 23 + * \brief Message published during an "ARI" transfer + */ +struct ast_ari_transfer_message { + /*! The channel receiving the transfer request */ + struct ast_channel_snapshot *source; + /*! The bridge associated with the source channel */ + struct ast_bridge_snapshot *source_bridge; + /*! The peer channel */ + struct ast_channel_snapshot *source_peer; + /*! Referer identity */ + char *referred_by; + + + /*! Destination extension */ + char destination[AST_MAX_EXTENSION]; + /*! Information for attended transfers */ + char *protocol_id; + /*! The identified destination channel. */ + struct ast_channel_snapshot *dest; + /*! The bridge associated with the channel. */ + struct ast_bridge_snapshot *dest_bridge; + /*! The peer of the destination channel. */ + struct ast_channel_snapshot *dest_peer; + /*! An array of protocol specific params, e.g. from/to information */ + struct ast_refer_params *refer_params; + /*! The current state of the transfer. */ + enum ast_control_transfer state; +}; + + +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); + /*! * \brief Initialize the stasis channel topic and message types * \retval 0 on success diff --git a/main/refer.c b/main/refer.c index bb948944bd..3ee07e6c5b 100644 --- a/main/refer.c +++ b/main/refer.c @@ -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; +} diff --git a/main/stasis_channels.c b/main/stasis_channels.c index d08a5f26ca..67df940255 100644 --- a/main/stasis_channels.c +++ b/main/stasis_channels.c @@ -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 @@ -1638,6 +1640,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. */ @@ -1691,6 +1862,9 @@ STASIS_MESSAGE_TYPE_DEFN(ast_channel_talking_stop, STASIS_MESSAGE_TYPE_DEFN(ast_channel_tone_detect, .to_json = tone_detect_to_json, ); +STASIS_MESSAGE_TYPE_DEFN(ast_channel_transfer_request_type, + .to_json = ari_transfer_to_json, + ); /*! @} */ @@ -1728,6 +1902,7 @@ static void stasis_channels_cleanup(void) STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_talking_start); STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_talking_stop); STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_tone_detect); + STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_transfer_request_type); } int ast_stasis_channels_init(void) @@ -1780,6 +1955,7 @@ int ast_stasis_channels_init(void) 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_tone_detect); + res |= STASIS_MESSAGE_TYPE_INIT(ast_channel_transfer_request_type); return res; } diff --git a/res/ari/ari_model_validators.c b/res/ari/ari_model_validators.c index f327add42c..ad1f8561c3 100644 --- a/res/ari/ari_model_validators.c +++ b/res/ari/ari_model_validators.c @@ -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; @@ -5010,6 +5064,126 @@ ari_validator ast_ari_validate_channel_tone_detected_fn(void) return ast_ari_validate_channel_tone_detected; } +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; @@ -5974,6 +6148,9 @@ int ast_ari_validate_event(struct ast_json *json) if (strcmp("ChannelToneDetected", discriminator) == 0) { return ast_ari_validate_channel_tone_detected(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 @@ -6184,6 +6361,9 @@ int ast_ari_validate_message(struct ast_json *json) if (strcmp("ChannelToneDetected", discriminator) == 0) { return ast_ari_validate_channel_tone_detected(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 @@ -7107,6 +7287,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; diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h index 64d9c9e565..2e52079ee7 100644 --- a/res/ari/ari_model_validators.h +++ b/res/ari/ari_model_validators.h @@ -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. * @@ -927,6 +943,22 @@ int ast_ari_validate_channel_tone_detected(struct ast_json *json); */ ari_validator ast_ari_validate_channel_tone_detected_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. * @@ -1231,6 +1263,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. * @@ -1457,6 +1537,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) @@ -1628,6 +1711,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) @@ -1748,6 +1839,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) diff --git a/res/ari/resource_channels.c b/res/ari/resource_channels.c index 8a6844ce1a..194abc398b 100644 --- a/res/ari/resource_channels.c +++ b/res/ari/resource_channels.c @@ -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); +} diff --git a/res/ari/resource_channels.h b/res/ari/resource_channels.h index 4110301a6e..8dc7fc9e19 100644 --- a/res/ari/resource_channels.h +++ b/res/ari/resource_channels.h @@ -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 */ diff --git a/res/res_ari_channels.c b/res/res_ari_channels.c index f43f726d09..33f50624c9 100644 --- a/res/res_ari_channels.c +++ b/res/res_ari_channels.c @@ -2999,6 +2999,92 @@ static void ast_ari_channels_external_media_cb( } #endif /* AST_DEVMODE */ +fin: __attribute__((unused)) + return; +} +int ast_ari_channels_transfer_progress_parse_body( + struct ast_json *body, + struct ast_ari_channels_transfer_progress_args *args) +{ + struct ast_json *field; + /* Parse query parameters out of it */ + field = ast_json_object_get(body, "states"); + if (field) { + args->states = ast_json_string_get(field); + } + return 0; +} + +/*! + * \brief Parameter parsing callback for /channels/{channelId}/transfer_progress. + * \param ser TCP/TLS session object + * \param get_params GET parameters in the HTTP request. + * \param path_vars Path variables extracted from the request. + * \param headers HTTP headers. + * \param body + * \param[out] response Response to the HTTP request. + */ +static void ast_ari_channels_transfer_progress_cb( + struct ast_tcptls_session_instance *ser, + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response) +{ + struct ast_ari_channels_transfer_progress_args args = {}; + struct ast_variable *i; +#if defined(AST_DEVMODE) + int is_valid; + int code; +#endif /* AST_DEVMODE */ + + for (i = get_params; i; i = i->next) { + if (strcmp(i->name, "states") == 0) { + args.states = (i->value); + } else + {} + } + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "channelId") == 0) { + args.channel_id = (i->value); + } else + {} + } + if (ast_ari_channels_transfer_progress_parse_body(body, &args)) { + ast_ari_response_alloc_failed(response); + goto fin; + } + ast_ari_channels_transfer_progress(headers, &args, response); +#if defined(AST_DEVMODE) + code = response->response_code; + + switch (code) { + case 0: /* Implementation is still a stub, or the code wasn't set */ + is_valid = response->message == NULL; + break; + case 500: /* Internal Server Error */ + case 501: /* Not Implemented */ + case 400: /* Endpoint parameter not provided */ + case 404: /* Channel or endpoint not found */ + case 409: /* Channel not in a Stasis application */ + case 412: /* Channel in invalid state */ + is_valid = 1; + break; + default: + if (200 <= code && code <= 299) { + is_valid = ast_ari_validate_void( + response->message); + } else { + ast_log(LOG_ERROR, "Invalid error response %d for /channels/{channelId}/transfer_progress\n", code); + is_valid = 0; + } + } + + if (!is_valid) { + ast_log(LOG_ERROR, "Response validation failed for /channels/{channelId}/transfer_progress\n"); + ast_ari_response_error(response, 500, + "Internal Server Error", "Response validation failed"); + } +#endif /* AST_DEVMODE */ + fin: __attribute__((unused)) return; } @@ -3183,6 +3269,15 @@ static struct stasis_rest_handlers channels_channelId_rtp_statistics = { .children = { } }; /*! \brief REST handler for /api-docs/channels.json */ +static struct stasis_rest_handlers channels_channelId_transfer_progress = { + .path_segment = "transfer_progress", + .callbacks = { + [AST_HTTP_POST] = ast_ari_channels_transfer_progress_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/channels.json */ static struct stasis_rest_handlers channels_channelId = { .path_segment = "channelId", .is_wildcard = 1, @@ -3191,8 +3286,8 @@ static struct stasis_rest_handlers channels_channelId = { [AST_HTTP_POST] = ast_ari_channels_originate_with_id_cb, [AST_HTTP_DELETE] = ast_ari_channels_hangup_cb, }, - .num_children = 16, - .children = { &channels_channelId_continue,&channels_channelId_move,&channels_channelId_redirect,&channels_channelId_answer,&channels_channelId_ring,&channels_channelId_dtmf,&channels_channelId_mute,&channels_channelId_hold,&channels_channelId_moh,&channels_channelId_silence,&channels_channelId_play,&channels_channelId_record,&channels_channelId_variable,&channels_channelId_snoop,&channels_channelId_dial,&channels_channelId_rtp_statistics, } + .num_children = 17, + .children = { &channels_channelId_continue,&channels_channelId_move,&channels_channelId_redirect,&channels_channelId_answer,&channels_channelId_ring,&channels_channelId_dtmf,&channels_channelId_mute,&channels_channelId_hold,&channels_channelId_moh,&channels_channelId_silence,&channels_channelId_play,&channels_channelId_record,&channels_channelId_variable,&channels_channelId_snoop,&channels_channelId_dial,&channels_channelId_rtp_statistics,&channels_channelId_transfer_progress, } }; /*! \brief REST handler for /api-docs/channels.json */ static struct stasis_rest_handlers channels_externalMedia = { diff --git a/res/res_pjsip_refer.c b/res/res_pjsip_refer.c index 3ffe58eef9..6616b1c0c9 100644 --- a/res/res_pjsip_refer.c +++ b/res/res_pjsip_refer.c @@ -44,6 +44,22 @@ static struct ast_taskprocessor *refer_serializer; static pj_status_t refer_on_tx_request(pjsip_tx_data *tdata); +static int defer_termination_cancel_task(void *data); + +struct transfer_ari_state { + /*! \brief A deferred session used by the ARI only mode */ + struct ast_sip_session *transferer; + struct ast_channel *transferer_chan; + + struct ast_sip_session *other_session; + + char exten[AST_MAX_EXTENSION]; + char *referred_by; + + char *protocol_id; + struct ast_refer_params *params; + int last_response; +}; /*! \brief REFER Progress structure */ struct refer_progress { @@ -69,6 +85,8 @@ struct refer_progress { int sent_100; /*! \brief Whether to notifies all the progress details on blind transfer */ unsigned int refer_blind_progress; + /*! \brief State related to the transfer in ARI only mode */ + struct transfer_ari_state *ari_state; }; /*! \brief REFER Progress notification structure */ @@ -87,6 +105,30 @@ static pjsip_module refer_progress_module = { .id = -1, }; +/*! \brief Destructor of the state used for the ARI transfer */ +static void transfer_ari_state_destroy(void *obj) +{ + struct transfer_ari_state *state = obj; + + ao2_cleanup(state->transferer); + ao2_cleanup(state->other_session); + ast_channel_cleanup(state->transferer_chan); + ast_free(state->referred_by); + ast_free(state->protocol_id); + ao2_cleanup(state->params); +} + +static void refer_params_destroy(void *obj) +{ + struct ast_refer_params *params = obj; + + for (int i = 0; i < AST_VECTOR_SIZE(params); ++i) { + struct ast_refer_param param = AST_VECTOR_GET(params, i); + ast_free((char *) param.param_name); + ast_free((char *) param.param_value); + } +} + /*! \brief Destructor for REFER Progress notification structure */ static void refer_progress_notification_destroy(void *obj) { @@ -95,6 +137,14 @@ static void refer_progress_notification_destroy(void *obj) ao2_cleanup(notification->progress); } +static int ari_notify(struct transfer_ari_state *state) +{ + return ast_refer_notify_transfer_request(state->transferer_chan, state->referred_by, + state->exten, state->protocol_id, + state->other_session ? state->other_session->channel : NULL, + state->params, state->last_response); +} + /*! \brief Allocator for REFER Progress notification structure */ static struct refer_progress_notification *refer_progress_notification_alloc(struct refer_progress *progress, int response, pjsip_evsub_state state) @@ -178,6 +228,18 @@ static int refer_progress_notify(void *data) pjsip_xfer_send_request(sub, tdata); } + + if (notification->progress->ari_state) { + struct transfer_ari_state *ari_state = notification->progress->ari_state; + if (ari_state->transferer && notification->state == PJSIP_EVSUB_STATE_TERMINATED) { + if (!ast_sip_push_task(ari_state->transferer->serializer, defer_termination_cancel_task, ari_state->transferer)) { + /* Gave the ref to the pushed task. */ + ari_state->transferer = NULL; + } + } + ari_notify(ari_state); + } + pjsip_dlg_dec_lock(notification->progress->dlg); return 0; @@ -298,6 +360,58 @@ static struct ast_frame *refer_progress_framehook(struct ast_channel *chan, stru return f; } +/*! \brief Progress monitoring frame hook - examines frames to determine state of transfer. Used for the ari-only mode */ +static struct ast_frame *refer_ari_progress_framehook(struct ast_channel *chan, struct ast_frame *f, enum ast_framehook_event event, void *data) +{ + struct refer_progress *progress = data; + struct refer_progress_notification *notification = NULL; + + /* We only care about frames *to* the channel */ + if (!f || (event != AST_FRAMEHOOK_EVENT_WRITE)) { + return f; + } + + /* Determine the state of the REFER based on the control frames (or voice frames) passing */ + if (f->frametype == AST_FRAME_CONTROL + && f->subclass.integer == AST_CONTROL_TRANSFER + && f->datalen >= sizeof(enum ast_control_transfer)) { + enum ast_control_transfer *message = f->data.ptr; + switch (*message) { + case AST_TRANSFER_FAILED: + notification = refer_progress_notification_alloc(progress, 603, PJSIP_EVSUB_STATE_TERMINATED); + break; + case AST_TRANSFER_SUCCESS: + notification = refer_progress_notification_alloc(progress, 200, PJSIP_EVSUB_STATE_TERMINATED); + break; + case AST_TRANSFER_PROGRESS: + notification = refer_progress_notification_alloc(progress, 100, PJSIP_EVSUB_STATE_ACTIVE); + break; + case AST_TRANSFER_UNAVAILABLE: + notification = refer_progress_notification_alloc(progress, 503, PJSIP_EVSUB_STATE_TERMINATED); + break; + case AST_TRANSFER_INVALID: + break; + } + progress->ari_state->last_response = *message; + } + + /* If a notification is due to be sent push it to the thread pool */ + if (notification) { + /* If the subscription is being terminated we don't need the frame hook any longer */ + if (notification->state == PJSIP_EVSUB_STATE_TERMINATED) { + ast_debug(3, "Detaching REFER progress monitoring hook from '%s' as subscription is being terminated\n", + ast_channel_name(chan)); + ast_framehook_detach(chan, progress->framehook); + } + + if (ast_sip_push_task(progress->serializer, refer_progress_notify, notification)) { + ao2_cleanup(notification); + } + } + + return f; +} + /*! \brief Destroy callback for monitoring framehook */ static void refer_progress_framehook_destroy(void *data) { @@ -378,6 +492,7 @@ static void refer_progress_destroy(void *obj) } ao2_cleanup(progress->transfer_data); + ao2_cleanup(progress->ari_state); ast_free(progress->transferee); ast_taskprocessor_unreference(progress->serializer); @@ -1339,6 +1454,169 @@ static const struct ast_refer_tech refer_tech = { .refer_send = sip_refer_send, }; +static char *copy_string(struct pj_str_t *str) +{ + int len = pj_strlen(str) + 1; + char *dst = ast_malloc(len); + if (!dst) { + return NULL; + } + ast_copy_pj_str(dst, str, len); + return dst; +} + +static int add_refer_param(struct ast_refer_params *params, const char *key, struct pj_str_t *str) +{ + struct ast_refer_param param; + + param.param_name = ast_strdup(key); + if (!param.param_name) { + return 0; + } + + param.param_value = copy_string(str); + if (!param.param_value) { + ast_free((char *) param.param_name); + return 0; + } + + if (AST_VECTOR_APPEND(params, param) != 0) { + ast_free((char *) param.param_name); + ast_free((char *) param.param_value); + return 0; + } + return 1; +} + + +static int refer_incoming_ari_request(struct ast_sip_session *session, pjsip_rx_data *rdata, pjsip_sip_uri *target, + pjsip_param *replaces_param, struct refer_progress *progress) +{ + int parsed_len; + pjsip_replaces_hdr *replaces; + pjsip_generic_string_hdr *referred_hdr; + + + RAII_VAR(struct transfer_ari_state *, state, NULL, ao2_cleanup); + + struct ast_framehook_interface hook = { + .version = AST_FRAMEHOOK_INTERFACE_VERSION, + .event_cb = refer_ari_progress_framehook, + .destroy_cb = refer_progress_framehook_destroy, + .data = progress, + .disable_inheritance = 1, + }; + + static const pj_str_t str_referred_by = { "Referred-By", 11 }; + static const pj_str_t str_referred_by_s = { "b", 1 }; + static const pj_str_t str_replaces = { "Replaces", 8 }; + + + state = ao2_alloc(sizeof(struct transfer_ari_state), transfer_ari_state_destroy); + if (!state) { + return 500; + } + + state->last_response = AST_TRANSFER_INVALID; + + state->params = ao2_alloc(sizeof(struct ast_refer_params), refer_params_destroy); + if (!state->params) { + return 500; + } + AST_VECTOR_INIT(state->params, 0); + + + ast_channel_ref(session->channel); + state->transferer_chan = session->channel; + + /* Using the user portion of the target URI see if it exists as a valid extension in their context */ + ast_copy_pj_str(state->exten, &target->user, sizeof(state->exten)); + + /* + * We may want to match in the dialplan without any user + * options getting in the way. + */ + AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(state->exten); + + referred_hdr = pjsip_msg_find_hdr_by_names(rdata->msg_info.msg, + &str_referred_by, &str_referred_by_s, NULL); + if (referred_hdr) { + state->referred_by = copy_string(&referred_hdr->hvalue); + if (!state->referred_by) { + return 500; + } + } + + if (replaces_param) { + pjsip_dialog *dlg; + pj_str_t replaces_content = { 0, }; + pj_strdup_with_null(rdata->tp_info.pool, &replaces_content, &replaces_param->value); + + /* Parsing the parameter as a Replaces header easily grabs the needed information */ + if (!(replaces = pjsip_parse_hdr(rdata->tp_info.pool, &str_replaces, replaces_content.ptr, + pj_strlen(&replaces_content), &parsed_len))) { + ast_log(LOG_ERROR, "Received REFER request on channel '%s' from endpoint '%s' with invalid Replaces header, rejecting\n", + ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint)); + return 400; + } + + dlg = pjsip_ua_find_dialog(&replaces->call_id, &replaces->to_tag, &replaces->from_tag, PJ_TRUE); + if (dlg) { + state->other_session = ast_sip_dialog_get_session(dlg); + pjsip_dlg_dec_lock(dlg); + } + + state->protocol_id = copy_string(&replaces->call_id); + if (!state->protocol_id) { + return 500; + } + + if (!add_refer_param(state->params, "from", &replaces->from_tag)) { + return 500; + } + + if (!add_refer_param(state->params, "to", &replaces->to_tag)) { + return 500; + } + } + + ao2_ref(session, +1); + if (ast_sip_session_defer_termination(session)) { + ast_log(LOG_ERROR, "Channel '%s' from endpoint '%s' attempted ari-only transfer but could not defer termination, rejecting\n", + ast_channel_name(session->channel), + ast_sorcery_object_get_id(session->endpoint)); + ao2_cleanup(session); + return 500; + } + state->transferer = session; + + + /* We need to bump the reference count up on the progress structure since it is in the frame hook now */ + ao2_ref(progress, +1); + ast_channel_lock(session->channel); + progress->framehook = ast_framehook_attach(session->channel, &hook); + ast_channel_unlock(session->channel); + + if (progress->framehook < 0) { + ao2_cleanup(progress); + return 500; + } + + if (ari_notify(state)) { + ast_channel_lock(session->channel); + ast_framehook_detach(session->channel, progress->framehook); + progress->framehook = -1; + ao2_cleanup(progress); + ast_channel_unlock(session->channel); + return 500; + } + + /* Transfer ownership to the progress */ + progress->ari_state = state; + state = NULL; + return 200; +} + static int refer_incoming_attended_request(struct ast_sip_session *session, pjsip_rx_data *rdata, pjsip_sip_uri *target_uri, pjsip_param *replaces_param, struct refer_progress *progress) { @@ -1710,9 +1988,15 @@ static int refer_incoming_refer_request(struct ast_sip_session *session, struct return 0; } - /* Determine if this is an attended or blind transfer */ - if ((replaces = pjsip_param_find(&target_uri->header_param, &str_replaces)) || - (replaces = pjsip_param_find(&target_uri->other_param, &str_replaces))) { + replaces = pjsip_param_find(&target_uri->header_param, &str_replaces); + if (!replaces) { + replaces = pjsip_param_find(&target_uri->other_param, &str_replaces); + } + + /* Determine if this is handled externally or an attended or blind transfer */ + if (session->transferhandling_ari) { + response = refer_incoming_ari_request(session, rdata, target_uri, replaces, progress); + } else if (replaces) { response = refer_incoming_attended_request(session, rdata, target_uri, replaces, progress); } else { response = refer_incoming_blind_request(session, rdata, target_uri, progress); diff --git a/rest-api/api-docs/channels.json b/rest-api/api-docs/channels.json index 820aabc69a..e4d436632e 100644 --- a/rest-api/api-docs/channels.json +++ b/rest-api/api-docs/channels.json @@ -1997,6 +1997,59 @@ ] } ] + }, + { + "path": "/channels/{channelId}/transfer_progress", + "description": "Inform the channel that the transfer is in progress.", + "operations": [ + { + "httpMethod": "POST", + "since": [ + "22.3.0", + "21.8.0", + "20.13.0" + ], + "summary": "Inform the channel about the progress of the attended/blind transfer.", + "nickname": "transfer_progress", + "responseClass": "void", + "parameters": [ + { + "name": "channelId", + "description": "Channel's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "states", + "description": "The state of the progress", + "paramType": "query", + "required": true, + "allowMultiple": false, + "dataType": "string" + } + ], + "errorResponses": [ + { + "code": 400, + "reason": "Endpoint parameter not provided" + }, + { + "code": 404, + "reason": "Channel or endpoint not found" + }, + { + "code": 409, + "reason": "Channel not in a Stasis application" + }, + { + "code": 412, + "reason": "Channel in invalid state" + } + ] + } + ] } ], "models": { diff --git a/rest-api/api-docs/events.json b/rest-api/api-docs/events.json index 4b4159e83f..23c7aca56f 100644 --- a/rest-api/api-docs/events.json +++ b/rest-api/api-docs/events.json @@ -199,7 +199,8 @@ "StasisStart", "TextMessageReceived", "ChannelConnectedLine", - "PeerStatusChange" + "PeerStatusChange", + "ChannelTransfer" ] }, "ContactInfo": { @@ -934,6 +935,110 @@ "description": "The channel whose connected line has changed." } } + }, + "ChannelTransfer": { + "id": "ChannelTransfer", + "description": "transfer on a channel.", + "properties": { + "state": { + "required": false, + "type": "string", + "description": "Transfer State" + }, + "refer_to": { + "required": true, + "type": "ReferTo", + "description": "Refer-To information with optionally both affected channels" + }, + "referred_by": { + "required": true, + "type": "ReferredBy", + "description": "Referred-By SIP Header according rfc3892" + } + } + }, + "ReferTo": { + "id": "ReferTo", + "description": "transfer destination requested by transferee", + "properties": { + "requested_destination": { + "required": true, + "type": "RequiredDestination" + }, + "destination_channel": { + "required": false, + "type": "Channel", + "description": "The Channel Object, that is to be replaced" + }, + "connected_channel": { + "required": false, + "type": "Channel", + "description": "Channel, connected to the to be replaced channel" + }, + "bridge": { + "required": false, + "type": "Bridge", + "description": "Bridge connecting both destination channels" + } + } + }, + "ReferredBy": { + "id": "ReferredBy", + "description": "transfer destination requested by transferee", + "properties": { + "source_channel": { + "required": true, + "type": "Channel", + "description": "The channel on which the refer was received" + }, + "connected_channel": { + "required": false, + "type": "Channel", + "description": "Channel, Connected to the channel, receiving the transfer request on." + }, + "bridge": { + "required": false, + "type": "Bridge", + "description": "Bridge connecting both Channels" + } + } + }, + "RequiredDestination": { + "id": "RequiredDestination", + "description": "Information about the requested destination", + "properties": { + "protocol_id": { + "required": false, + "type": "string", + "description": "the requested protocol-id by the referee in case of SIP channel, this is a SIP Call ID, Mutually exclusive to destination" + }, + "destination": { + "required": false, + "type": "string", + "description": "Destination User Part. Only for Blind transfer. Mutually exclusive to protocol_id" + }, + "additional_protocol_params": { + "required": false, + "type": "List[AdditionalParam]", + "description": "List of additional protocol specific information" + } + } + }, + "AdditionalParam": { + "id": "AdditionalParam", + "description": "Protocol specific additional parameter", + "properties": { + "parameter_name": { + "required": true, + "type": "string", + "description": "Name of the parameter" + }, + "parameter_value": { + "required": true, + "type": "string", + "description": "Value of the parameter" + } + } } } }