From: Joshua Colp Date: Sat, 1 Feb 2014 16:26:57 +0000 (+0000) Subject: res_stasis: Enable transfers and provide events when they occur. X-Git-Tag: 13.0.0-beta1~582 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e5899852cc61a3f0a53707c2099ba35380198c69;p=thirdparty%2Fasterisk.git res_stasis: Enable transfers and provide events when they occur. This change enables transfers within ARI created bridges and adds events for when they occur. Unlike other events these will be received if *any* subscribed object is involved in the transfer. (closes issue ASTERISK-22984) Reported by: David M. Lee Review: https://reviewboard.asterisk.org/r/3120/ ........ Merged revisions 407153 from http://svn.asterisk.org/svn/asterisk/branches/12 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@407154 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- diff --git a/main/stasis_bridges.c b/main/stasis_bridges.c index b8d4ae8e1e..eb020163d7 100644 --- a/main/stasis_bridges.c +++ b/main/stasis_bridges.c @@ -133,7 +133,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") ***/ +static struct ast_json *attended_transfer_to_json(struct stasis_message *msg, + const struct stasis_message_sanitizer *sanitize); static struct ast_manager_event_blob *attended_transfer_to_ami(struct stasis_message *message); +static struct ast_json *blind_transfer_to_json(struct stasis_message *msg, + const struct stasis_message_sanitizer *sanitize); static struct ast_manager_event_blob *blind_transfer_to_ami(struct stasis_message *message); static struct ast_json *ast_channel_entered_bridge_to_json( struct stasis_message *msg, @@ -157,8 +161,12 @@ STASIS_MESSAGE_TYPE_DEFN(ast_channel_entered_bridge_type, .to_json = ast_channel_entered_bridge_to_json); STASIS_MESSAGE_TYPE_DEFN(ast_channel_left_bridge_type, .to_json = ast_channel_left_bridge_to_json); -STASIS_MESSAGE_TYPE_DEFN(ast_blind_transfer_type, .to_ami = blind_transfer_to_ami); -STASIS_MESSAGE_TYPE_DEFN(ast_attended_transfer_type, .to_ami = attended_transfer_to_ami); +STASIS_MESSAGE_TYPE_DEFN(ast_blind_transfer_type, + .to_json = blind_transfer_to_json, + .to_ami = blind_transfer_to_ami); +STASIS_MESSAGE_TYPE_DEFN(ast_attended_transfer_type, + .to_json = attended_transfer_to_json, + .to_ami = attended_transfer_to_ami); /*! @} */ struct stasis_cache *ast_bridge_cache(void) @@ -614,6 +622,43 @@ static const char *result_strs[] = { [AST_BRIDGE_TRANSFER_SUCCESS] = "Success", }; +static struct ast_json *blind_transfer_to_json(struct stasis_message *msg, + const struct stasis_message_sanitizer *sanitize) +{ + struct ast_bridge_blob *blob = stasis_message_data(msg); + struct ast_json *json_channel, *out; + const struct timeval *tv = stasis_message_timestamp(msg); + + json_channel = ast_channel_snapshot_to_json(blob->channel, sanitize); + if (!json_channel) { + return NULL; + } + + out = ast_json_pack("{s: s, s: o, s: o, s: O, s: O, s: s, s: o}", + "type", "BridgeBlindTransfer", + "timestamp", ast_json_timeval(*tv, NULL), + "channel", json_channel, + "exten", ast_json_object_get(blob->blob, "exten"), + "context", ast_json_object_get(blob->blob, "context"), + "result", result_strs[ast_json_integer_get(ast_json_object_get(blob->blob, "result"))], + "is_external", ast_json_boolean(ast_json_integer_get(ast_json_object_get(blob->blob, "is_external")))); + + if (!out) { + return NULL; + } + + if (blob->bridge) { + struct ast_json *json_bridge = ast_bridge_snapshot_to_json(blob->bridge, sanitize); + + if (!json_bridge || ast_json_object_set(out, "bridge", json_bridge)) { + ast_json_unref(out); + return NULL; + } + } + + return out; +} + static struct ast_manager_event_blob *blind_transfer_to_ami(struct stasis_message *msg) { RAII_VAR(struct ast_str *, channel_state, NULL, ast_free_ptr); @@ -685,6 +730,110 @@ void ast_bridge_publish_blind_transfer(int is_external, enum ast_transfer_result stasis_publish(ast_bridge_topic_all(), msg); } +static struct ast_json *attended_transfer_to_json(struct stasis_message *msg, + const struct stasis_message_sanitizer *sanitize) +{ + struct ast_attended_transfer_message *transfer_msg = stasis_message_data(msg); + RAII_VAR(struct ast_json *, out, NULL, ast_json_unref); + struct ast_json *json_transferer1, *json_transferer2, *json_bridge, *json_channel; + const struct timeval *tv = stasis_message_timestamp(msg); + int res = 0; + + json_transferer1 = ast_channel_snapshot_to_json(transfer_msg->to_transferee.channel_snapshot, sanitize); + if (!json_transferer1) { + return NULL; + } + + json_transferer2 = ast_channel_snapshot_to_json(transfer_msg->to_transfer_target.channel_snapshot, sanitize); + if (!json_transferer2) { + ast_json_unref(json_transferer1); + return NULL; + } + + out = ast_json_pack("{s: s, s: o, s: o, s: o, s: s, s: o}", + "type", "BridgeAttendedTransfer", + "timestamp", ast_json_timeval(*tv, NULL), + "transferer_first_leg", json_transferer1, + "transferer_second_leg", json_transferer2, + "result", result_strs[transfer_msg->result], + "is_external", ast_json_boolean(transfer_msg->is_external)); + if (!out) { + return NULL; + } + + if (transfer_msg->to_transferee.bridge_snapshot) { + json_bridge = ast_bridge_snapshot_to_json(transfer_msg->to_transferee.bridge_snapshot, sanitize); + + if (!json_bridge) { + return NULL; + } + + res |= ast_json_object_set(out, "transferer_first_leg_bridge", json_bridge); + } + + if (transfer_msg->to_transfer_target.bridge_snapshot) { + json_bridge = ast_bridge_snapshot_to_json(transfer_msg->to_transfer_target.bridge_snapshot, sanitize); + + if (!json_bridge) { + return NULL; + } + + res |= ast_json_object_set(out, "transferer_second_leg_bridge", json_bridge); + } + + switch (transfer_msg->dest_type) { + case AST_ATTENDED_TRANSFER_DEST_BRIDGE_MERGE: + res |= ast_json_object_set(out, "destination_type", ast_json_string_create("bridge")); + res |= ast_json_object_set(out, "destination_bridge", ast_json_string_create(transfer_msg->dest.bridge)); + break; + case AST_ATTENDED_TRANSFER_DEST_APP: + res |= ast_json_object_set(out, "destination_type", ast_json_string_create("application")); + res |= ast_json_object_set(out, "destination_application", ast_json_string_create(transfer_msg->dest.app)); + break; + case AST_ATTENDED_TRANSFER_DEST_LINK: + res |= ast_json_object_set(out, "destination_type", ast_json_string_create("link")); + + json_channel = ast_channel_snapshot_to_json(transfer_msg->dest.links[0], sanitize); + if (!json_channel) { + return NULL; + } + res |= ast_json_object_set(out, "destination_link_first_leg", json_channel); + + json_channel = ast_channel_snapshot_to_json(transfer_msg->dest.links[1], sanitize); + if (!json_channel) { + return NULL; + } + res |= ast_json_object_set(out, "destination_link_second_leg", json_channel); + + break; + case AST_ATTENDED_TRANSFER_DEST_THREEWAY: + res |= ast_json_object_set(out, "destination_type", ast_json_string_create("threeway")); + + json_channel = ast_channel_snapshot_to_json(transfer_msg->dest.threeway.channel_snapshot, sanitize); + if (!json_channel) { + return NULL; + } + res |= ast_json_object_set(out, "destination_threeway_channel", json_channel); + + json_bridge = ast_bridge_snapshot_to_json(transfer_msg->dest.threeway.bridge_snapshot, sanitize); + if (!json_bridge) { + return NULL; + } + res |= ast_json_object_set(out, "destination_threeway_bridge", json_bridge); + + break; + case AST_ATTENDED_TRANSFER_DEST_FAIL: + res |= ast_json_object_set(out, "destination_type", ast_json_string_create("fail")); + break; + } + + if (res) { + return NULL; + } + + return ast_json_ref(out); +} + static struct ast_manager_event_blob *attended_transfer_to_ami(struct stasis_message *msg) { RAII_VAR(struct ast_str *, variable_data, ast_str_create(64), ast_free_ptr); diff --git a/res/ari/ari_model_validators.c b/res/ari/ari_model_validators.c index b1482fa878..88cfc26970 100644 --- a/res/ari/ari_model_validators.c +++ b/res/ari/ari_model_validators.c @@ -1552,6 +1552,373 @@ ari_validator ast_ari_validate_application_replaced_fn(void) return ast_ari_validate_application_replaced; } +int ast_ari_validate_bridge_attended_transfer(struct ast_json *json) +{ + int res = 1; + struct ast_json_iter *iter; + int has_type = 0; + int has_application = 0; + int has_destination_type = 0; + int has_is_external = 0; + int has_result = 0; + int has_transferer_first_leg = 0; + int has_transferer_second_leg = 0; + + for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) { + 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 BridgeAttendedTransfer 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 BridgeAttendedTransfer field application failed validation\n"); + res = 0; + } + } else + if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + prop_is_valid = ast_ari_validate_date( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI BridgeAttendedTransfer field timestamp failed validation\n"); + res = 0; + } + } else + if (strcmp("destination_application", 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 BridgeAttendedTransfer field destination_application failed validation\n"); + res = 0; + } + } else + if (strcmp("destination_bridge", 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 BridgeAttendedTransfer field destination_bridge failed validation\n"); + res = 0; + } + } else + if (strcmp("destination_link_first_leg", 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 BridgeAttendedTransfer field destination_link_first_leg failed validation\n"); + res = 0; + } + } else + if (strcmp("destination_link_second_leg", 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 BridgeAttendedTransfer field destination_link_second_leg failed validation\n"); + res = 0; + } + } else + if (strcmp("destination_threeway_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 BridgeAttendedTransfer field destination_threeway_bridge failed validation\n"); + res = 0; + } + } else + if (strcmp("destination_threeway_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 BridgeAttendedTransfer field destination_threeway_channel failed validation\n"); + res = 0; + } + } else + if (strcmp("destination_type", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + has_destination_type = 1; + prop_is_valid = ast_ari_validate_string( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI BridgeAttendedTransfer field destination_type failed validation\n"); + res = 0; + } + } else + if (strcmp("is_external", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + has_is_external = 1; + prop_is_valid = ast_ari_validate_boolean( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI BridgeAttendedTransfer field is_external failed validation\n"); + res = 0; + } + } else + if (strcmp("result", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + has_result = 1; + prop_is_valid = ast_ari_validate_string( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI BridgeAttendedTransfer field result failed validation\n"); + res = 0; + } + } else + if (strcmp("transferer_first_leg", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + has_transferer_first_leg = 1; + prop_is_valid = ast_ari_validate_channel( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI BridgeAttendedTransfer field transferer_first_leg failed validation\n"); + res = 0; + } + } else + if (strcmp("transferer_first_leg_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 BridgeAttendedTransfer field transferer_first_leg_bridge failed validation\n"); + res = 0; + } + } else + if (strcmp("transferer_second_leg", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + has_transferer_second_leg = 1; + prop_is_valid = ast_ari_validate_channel( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI BridgeAttendedTransfer field transferer_second_leg failed validation\n"); + res = 0; + } + } else + if (strcmp("transferer_second_leg_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 BridgeAttendedTransfer field transferer_second_leg_bridge failed validation\n"); + res = 0; + } + } else + { + ast_log(LOG_ERROR, + "ARI BridgeAttendedTransfer has undocumented field %s\n", + ast_json_object_iter_key(iter)); + res = 0; + } + } + + if (!has_type) { + ast_log(LOG_ERROR, "ARI BridgeAttendedTransfer missing required field type\n"); + res = 0; + } + + if (!has_application) { + ast_log(LOG_ERROR, "ARI BridgeAttendedTransfer missing required field application\n"); + res = 0; + } + + if (!has_destination_type) { + ast_log(LOG_ERROR, "ARI BridgeAttendedTransfer missing required field destination_type\n"); + res = 0; + } + + if (!has_is_external) { + ast_log(LOG_ERROR, "ARI BridgeAttendedTransfer missing required field is_external\n"); + res = 0; + } + + if (!has_result) { + ast_log(LOG_ERROR, "ARI BridgeAttendedTransfer missing required field result\n"); + res = 0; + } + + if (!has_transferer_first_leg) { + ast_log(LOG_ERROR, "ARI BridgeAttendedTransfer missing required field transferer_first_leg\n"); + res = 0; + } + + if (!has_transferer_second_leg) { + ast_log(LOG_ERROR, "ARI BridgeAttendedTransfer missing required field transferer_second_leg\n"); + res = 0; + } + + return res; +} + +ari_validator ast_ari_validate_bridge_attended_transfer_fn(void) +{ + return ast_ari_validate_bridge_attended_transfer; +} + +int ast_ari_validate_bridge_blind_transfer(struct ast_json *json) +{ + int res = 1; + struct ast_json_iter *iter; + int has_type = 0; + int has_application = 0; + int has_channel = 0; + int has_context = 0; + int has_exten = 0; + int has_is_external = 0; + int has_result = 0; + + for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) { + 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 BridgeBlindTransfer 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 BridgeBlindTransfer field application failed validation\n"); + res = 0; + } + } else + if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + prop_is_valid = ast_ari_validate_date( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI BridgeBlindTransfer field timestamp failed validation\n"); + res = 0; + } + } else + 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 BridgeBlindTransfer field bridge failed validation\n"); + res = 0; + } + } else + if (strcmp("channel", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + has_channel = 1; + prop_is_valid = ast_ari_validate_channel( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI BridgeBlindTransfer field channel failed validation\n"); + res = 0; + } + } else + if (strcmp("context", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + has_context = 1; + prop_is_valid = ast_ari_validate_string( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI BridgeBlindTransfer field context failed validation\n"); + res = 0; + } + } else + if (strcmp("exten", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + has_exten = 1; + prop_is_valid = ast_ari_validate_string( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI BridgeBlindTransfer field exten failed validation\n"); + res = 0; + } + } else + if (strcmp("is_external", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + has_is_external = 1; + prop_is_valid = ast_ari_validate_boolean( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI BridgeBlindTransfer field is_external failed validation\n"); + res = 0; + } + } else + if (strcmp("result", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + has_result = 1; + prop_is_valid = ast_ari_validate_string( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI BridgeBlindTransfer field result failed validation\n"); + res = 0; + } + } else + { + ast_log(LOG_ERROR, + "ARI BridgeBlindTransfer has undocumented field %s\n", + ast_json_object_iter_key(iter)); + res = 0; + } + } + + if (!has_type) { + ast_log(LOG_ERROR, "ARI BridgeBlindTransfer missing required field type\n"); + res = 0; + } + + if (!has_application) { + ast_log(LOG_ERROR, "ARI BridgeBlindTransfer missing required field application\n"); + res = 0; + } + + if (!has_channel) { + ast_log(LOG_ERROR, "ARI BridgeBlindTransfer missing required field channel\n"); + res = 0; + } + + if (!has_context) { + ast_log(LOG_ERROR, "ARI BridgeBlindTransfer missing required field context\n"); + res = 0; + } + + if (!has_exten) { + ast_log(LOG_ERROR, "ARI BridgeBlindTransfer missing required field exten\n"); + res = 0; + } + + if (!has_is_external) { + ast_log(LOG_ERROR, "ARI BridgeBlindTransfer missing required field is_external\n"); + res = 0; + } + + if (!has_result) { + ast_log(LOG_ERROR, "ARI BridgeBlindTransfer missing required field result\n"); + res = 0; + } + + return res; +} + +ari_validator ast_ari_validate_bridge_blind_transfer_fn(void) +{ + return ast_ari_validate_bridge_blind_transfer; +} + int ast_ari_validate_bridge_created(struct ast_json *json) { int res = 1; @@ -3211,6 +3578,12 @@ int ast_ari_validate_event(struct ast_json *json) if (strcmp("ApplicationReplaced", discriminator) == 0) { return ast_ari_validate_application_replaced(json); } else + if (strcmp("BridgeAttendedTransfer", discriminator) == 0) { + return ast_ari_validate_bridge_attended_transfer(json); + } else + if (strcmp("BridgeBlindTransfer", discriminator) == 0) { + return ast_ari_validate_bridge_blind_transfer(json); + } else if (strcmp("BridgeCreated", discriminator) == 0) { return ast_ari_validate_bridge_created(json); } else @@ -3364,6 +3737,12 @@ int ast_ari_validate_message(struct ast_json *json) if (strcmp("ApplicationReplaced", discriminator) == 0) { return ast_ari_validate_application_replaced(json); } else + if (strcmp("BridgeAttendedTransfer", discriminator) == 0) { + return ast_ari_validate_bridge_attended_transfer(json); + } else + if (strcmp("BridgeBlindTransfer", discriminator) == 0) { + return ast_ari_validate_bridge_blind_transfer(json); + } else if (strcmp("BridgeCreated", discriminator) == 0) { return ast_ari_validate_bridge_created(json); } else diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h index ffe0039039..c299724eb6 100644 --- a/res/ari/ari_model_validators.h +++ b/res/ari/ari_model_validators.h @@ -536,6 +536,42 @@ int ast_ari_validate_application_replaced(struct ast_json *json); */ ari_validator ast_ari_validate_application_replaced_fn(void); +/*! + * \brief Validator for BridgeAttendedTransfer. + * + * Notification that an attended transfer has occurred. + * + * \param json JSON object to validate. + * \returns True (non-zero) if valid. + * \returns False (zero) if invalid. + */ +int ast_ari_validate_bridge_attended_transfer(struct ast_json *json); + +/*! + * \brief Function pointer to ast_ari_validate_bridge_attended_transfer(). + * + * See \ref ast_ari_model_validators.h for more details. + */ +ari_validator ast_ari_validate_bridge_attended_transfer_fn(void); + +/*! + * \brief Validator for BridgeBlindTransfer. + * + * Notification that a blind transfer has occurred. + * + * \param json JSON object to validate. + * \returns True (non-zero) if valid. + * \returns False (zero) if invalid. + */ +int ast_ari_validate_bridge_blind_transfer(struct ast_json *json); + +/*! + * \brief Function pointer to ast_ari_validate_bridge_blind_transfer(). + * + * See \ref ast_ari_model_validators.h for more details. + */ +ari_validator ast_ari_validate_bridge_blind_transfer_fn(void); + /*! * \brief Validator for BridgeCreated. * @@ -1137,6 +1173,33 @@ ari_validator ast_ari_validate_application_fn(void); * - type: string (required) * - application: string (required) * - timestamp: Date + * BridgeAttendedTransfer + * - type: string (required) + * - application: string (required) + * - timestamp: Date + * - destination_application: string + * - destination_bridge: string + * - destination_link_first_leg: Channel + * - destination_link_second_leg: Channel + * - destination_threeway_bridge: Bridge + * - destination_threeway_channel: Channel + * - destination_type: string (required) + * - is_external: boolean (required) + * - result: string (required) + * - transferer_first_leg: Channel (required) + * - transferer_first_leg_bridge: Bridge + * - transferer_second_leg: Channel (required) + * - transferer_second_leg_bridge: Bridge + * BridgeBlindTransfer + * - type: string (required) + * - application: string (required) + * - timestamp: Date + * - bridge: Bridge + * - channel: Channel (required) + * - context: string (required) + * - exten: string (required) + * - is_external: boolean (required) + * - result: string (required) * BridgeCreated * - type: string (required) * - application: string (required) diff --git a/res/res_stasis.c b/res/res_stasis.c index 32de9a0418..f6fc0ac6e3 100644 --- a/res/res_stasis.c +++ b/res/res_stasis.c @@ -591,7 +591,7 @@ struct ast_bridge *stasis_app_bridge_create(const char *type, const char *name) int capabilities; int flags = AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM | AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM | AST_BRIDGE_FLAG_SWAP_INHIBIT_TO - | AST_BRIDGE_FLAG_TRANSFER_PROHIBITED; + | AST_BRIDGE_FLAG_TRANSFER_BRIDGE_ONLY; if (ast_strlen_zero(type) || !strcmp(type, "mixing")) { capabilities = AST_BRIDGE_CAPABILITY_1TO1MIX | diff --git a/res/stasis/app.c b/res/stasis/app.c index 8e9872aec3..dc322b6bf5 100644 --- a/res/stasis/app.c +++ b/res/stasis/app.c @@ -41,8 +41,8 @@ struct stasis_app { struct stasis_topic *topic; /*! Router for handling messages forwarded to \a topic. */ struct stasis_message_router *router; - /*! Subscription to watch for bridge merge messages */ - struct stasis_subscription *bridge_merge_sub; + /*! Router for handling messages to the bridge all \a topic. */ + struct stasis_message_router *bridge_router; /*! Container of the channel forwards to this app's topic. */ struct ao2_container *forwards; /*! Callback function for this application. */ @@ -255,7 +255,7 @@ static void app_dtor(void *obj) ast_verb(1, "Destroying Stasis app %s\n", app->name); ast_assert(app->router == NULL); - ast_assert(app->bridge_merge_sub == NULL); + ast_assert(app->bridge_router == NULL); ao2_cleanup(app->topic); app->topic = NULL; @@ -589,37 +589,127 @@ static void sub_bridge_update_handler(void *data, app_send(app, json); } + +/*! \brief Helper function for determining if the application is subscribed to a given entity */ +static int bridge_app_subscribed(struct stasis_app *app, const char *uniqueid) +{ + struct app_forwards *forwards = NULL; + + forwards = ao2_find(app->forwards, uniqueid, OBJ_SEARCH_KEY); + if (!forwards) { + return 0; + } + + ao2_ref(forwards, -1); + return 1; +} + static void bridge_merge_handler(void *data, struct stasis_subscription *sub, struct stasis_message *message) { struct stasis_app *app = data; struct ast_bridge_merge_message *merge; - RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup); - if (stasis_subscription_final_message(sub, message)) { - ao2_cleanup(app); + merge = stasis_message_data(message); + + /* Find out if we're subscribed to either bridge */ + if (bridge_app_subscribed(app, merge->from->uniqueid) || + bridge_app_subscribed(app, merge->to->uniqueid)) { + /* Forward the message to the app */ + stasis_publish(app->topic, message); } +} - if (stasis_message_type(message) != ast_bridge_merge_message_type()) { - return; +/*! \brief Callback function for checking if channels in a bridge are subscribed to */ +static int bridge_app_subscribed_involved(struct stasis_app *app, struct ast_bridge_snapshot *snapshot) +{ + int subscribed = 0; + struct ao2_iterator iter; + char *uniqueid; + + if (bridge_app_subscribed(app, snapshot->uniqueid)) { + return 1; } - merge = stasis_message_data(message); + iter = ao2_iterator_init(snapshot->channels, 0); + for (; (uniqueid = ao2_iterator_next(&iter)); ao2_ref(uniqueid, -1)) { + if (bridge_app_subscribed(app, uniqueid)) { + subscribed = 1; + ao2_ref(uniqueid, -1); + break; + } + } + ao2_iterator_destroy(&iter); - /* Find out if we're subscribed to either bridge */ - forwards = ao2_find(app->forwards, merge->from->uniqueid, - OBJ_SEARCH_KEY); - if (!forwards) { - forwards = ao2_find(app->forwards, merge->to->uniqueid, - OBJ_SEARCH_KEY); + return subscribed; +} + +static void bridge_blind_transfer_handler(void *data, struct stasis_subscription *sub, + struct stasis_message *message) +{ + struct stasis_app *app = data; + struct ast_bridge_blob *blob = stasis_message_data(message); + + if (bridge_app_subscribed(app, blob->channel->uniqueid) || + bridge_app_subscribed_involved(app, blob->bridge)) { + stasis_publish(app->topic, message); } +} - if (!forwards) { - return; +static void bridge_attended_transfer_handler(void *data, struct stasis_subscription *sub, + struct stasis_message *message) +{ + struct stasis_app *app = data; + struct ast_attended_transfer_message *transfer_msg = stasis_message_data(message); + int subscribed = 0; + + subscribed = bridge_app_subscribed(app, transfer_msg->to_transferee.channel_snapshot->uniqueid); + if (!subscribed) { + subscribed = bridge_app_subscribed(app, transfer_msg->to_transfer_target.channel_snapshot->uniqueid); + } + if (!subscribed && transfer_msg->to_transferee.bridge_snapshot) { + subscribed = bridge_app_subscribed_involved(app, transfer_msg->to_transferee.bridge_snapshot); + } + if (!subscribed && transfer_msg->to_transfer_target.bridge_snapshot) { + subscribed = bridge_app_subscribed_involved(app, transfer_msg->to_transfer_target.bridge_snapshot); + } + + if (!subscribed) { + switch (transfer_msg->dest_type) { + case AST_ATTENDED_TRANSFER_DEST_BRIDGE_MERGE: + subscribed = bridge_app_subscribed(app, transfer_msg->dest.bridge); + break; + case AST_ATTENDED_TRANSFER_DEST_LINK: + subscribed = bridge_app_subscribed(app, transfer_msg->dest.links[0]->uniqueid); + if (!subscribed) { + subscribed = bridge_app_subscribed(app, transfer_msg->dest.links[1]->uniqueid); + } + break; + break; + case AST_ATTENDED_TRANSFER_DEST_THREEWAY: + subscribed = bridge_app_subscribed_involved(app, transfer_msg->dest.threeway.bridge_snapshot); + if (!subscribed) { + subscribed = bridge_app_subscribed(app, transfer_msg->dest.threeway.channel_snapshot->uniqueid); + } + break; + default: + break; + } + } + + if (subscribed) { + stasis_publish(app->topic, message); } +} - /* Forward the message to the app */ - stasis_publish(app->topic, message); +static void bridge_default_handler(void *data, struct stasis_subscription *sub, + struct stasis_message *message) +{ + struct stasis_app *app = data; + + if (stasis_subscription_final_message(sub, message)) { + ao2_cleanup(app); + } } struct stasis_app *app_create(const char *name, stasis_app_cb handler, void *data) @@ -652,12 +742,27 @@ struct stasis_app *app_create(const char *name, stasis_app_cb handler, void *dat return NULL; } - app->bridge_merge_sub = stasis_subscribe(ast_bridge_topic_all(), - bridge_merge_handler, app); - if (!app->bridge_merge_sub) { + app->bridge_router = stasis_message_router_create(ast_bridge_topic_all()); + if (!app->bridge_router) { + return NULL; + } + + res |= stasis_message_router_add(app->bridge_router, + ast_bridge_merge_message_type(), bridge_merge_handler, app); + + res |= stasis_message_router_add(app->bridge_router, + ast_blind_transfer_type(), bridge_blind_transfer_handler, app); + + res |= stasis_message_router_add(app->bridge_router, + ast_attended_transfer_type(), bridge_attended_transfer_handler, app); + + res |= stasis_message_router_set_default(app->bridge_router, + bridge_default_handler, app); + + if (res != 0) { return NULL; } - /* Subscription holds a reference */ + /* Bridge router holds a reference */ ao2_ref(app, +1); app->router = stasis_message_router_create(app->topic); @@ -739,8 +844,8 @@ void app_shutdown(struct stasis_app *app) stasis_message_router_unsubscribe(app->router); app->router = NULL; - stasis_unsubscribe(app->bridge_merge_sub); - app->bridge_merge_sub = NULL; + stasis_message_router_unsubscribe(app->bridge_router); + app->bridge_router = NULL; } int app_is_active(struct stasis_app *app) diff --git a/rest-api/api-docs/events.json b/rest-api/api-docs/events.json index b841485b38..e26eaa6c6f 100644 --- a/rest-api/api-docs/events.json +++ b/rest-api/api-docs/events.json @@ -86,6 +86,8 @@ "BridgeCreated", "BridgeDestroyed", "BridgeMerged", + "BridgeBlindTransfer", + "BridgeAttendedTransfer", "ChannelCreated", "ChannelDestroyed", "ChannelEnteredBridge", @@ -211,6 +213,104 @@ } } }, + "BridgeBlindTransfer": { + "id": "BridgeBlindTransfer", + "description": "Notification that a blind transfer has occurred.", + "properties": { + "channel": { + "description": "The channel performing the blind transfer", + "required": true, + "type": "Channel" + }, + "exten": { + "description": "The extension transferred to", + "required": true, + "type": "string" + }, + "context": { + "description": "The context transferred to", + "required": true, + "type": "string" + }, + "result": { + "description": "The result of the transfer attempt", + "required": true, + "type": "string" + }, + "is_external": { + "description": "Whether the transfer was externally initiated or not", + "required": true, + "type": "boolean" + }, + "bridge": { + "description": "The bridge being transferred", + "type": "Bridge" + } + } + }, + "BridgeAttendedTransfer": { + "id": "BridgeAttendedTransfer", + "description": "Notification that an attended transfer has occurred.", + "properties": { + "transferer_first_leg": { + "description": "First leg of the transferer", + "required": true, + "type": "Channel" + }, + "transferer_second_leg": { + "description": "Second leg of the transferer", + "required": true, + "type": "Channel" + }, + "result": { + "description": "The result of the transfer attempt", + "required": true, + "type": "string" + }, + "is_external": { + "description": "Whether the transfer was externally initiated or not", + "required": true, + "type": "boolean" + }, + "transferer_first_leg_bridge": { + "description": "Bridge the transferer first leg is in", + "type": "Bridge" + }, + "transferer_second_leg_bridge": { + "description": "Bridge the transferer second leg is in", + "type": "Bridge" + }, + "destination_type": { + "description": "How the transfer was accomplished", + "required": true, + "type": "string" + }, + "destination_bridge": { + "description": "Bridge that survived the merge result", + "type": "string" + }, + "destination_application": { + "description": "Application that has been transferred into", + "type": "string" + }, + "destination_link_first_leg": { + "description": "First leg of a link transfer result", + "type": "Channel" + }, + "destination_link_second_leg": { + "description": "Second leg of a link transfer result", + "type": "Channel" + }, + "destination_threeway_channel": { + "description": "Transferer channel that survived the threeway result", + "type": "Channel" + }, + "destination_threeway_bridge": { + "description": "Bridge that survived the threeway result", + "type": "Bridge" + } + } + }, "ChannelCreated": { "id": "ChannelCreated", "description": "Notification that a channel has been created.",