diff --git a/include/asterisk/bridge.h b/include/asterisk/bridge.h index 58f627a253..a45bed31e9 100644 --- a/include/asterisk/bridge.h +++ b/include/asterisk/bridge.h @@ -345,6 +345,8 @@ struct ast_bridge_snapshot { enum ast_bridge_video_mode_type video_mode; /*! The time of bridge creation */ struct timeval creationtime; + /*! Variables to be included in ARI events */ + struct varshead *bridgevars; }; /*! @@ -410,6 +412,10 @@ struct ast_bridge { struct ast_bridge_snapshot *current_snapshot; /*! The time of bridge creation */ struct timeval creationtime; + /*! A linked list for bridge variables */ + struct varshead bridgevars; + /*! A vector of variable names to be included in ARI events on this bridge */ + AST_VECTOR(, char *) ari_reportable_variable_names; }; /*! \brief Bridge base class virtual method table. */ @@ -854,6 +860,57 @@ int ast_bridge_unsuspend(struct ast_bridge *bridge, struct ast_channel *chan); */ void ast_bridge_vars_set(struct ast_channel *chan, const char *name, const char *pvtid); +/*! + * \brief Set a variable on the bridge. + * \since 20.20.0 + * \since 22.10.0 + * \since 23.4.0 + * + * \pre Bridge is locked + * + * \param bridge Bridge to operate on. + * \param name Name of variable to set. + * \param value Value of variable to set. (NULL to delete variable.) + * \param report_events If non-zero, the variable change will be reported in ARI events. + * + * \retval 0 on success + * \retval -1 on failure + */ +int ast_bridge_set_variable(struct ast_bridge *bridge, const char *name, const char *value, int report_events); + +/*! + * \brief Get value a variable from the bridge by name. + * \since 20.20.0 + * \since 22.10.0 + * \since 23.4.0 + * + * \pre Bridge is locked + * + * \param bridge Bridge to operate on. + * \param name Name of variable to get. + * + * \retval Value of the variable on success. + * \retval NULL on failure. + */ +const char *ast_bridge_get_variable(const struct ast_bridge *bridge, const char *name); + +/*! + * \brief Get a list of variables that should be included in ARI events for this bridge. + * \since 20.20.0 + * \since 22.10.0 + * \since 23.4.0 + * + * \pre Bridge is locked + * + * \param bridge Bridge to operate on. + * + * \note The returned list must be freed by the caller. + * + * \retval A pointer to the head of a linked list of variables to include in ARI events on success. + * \retval NULL on failure or if no variables are set to be reported. + */ +struct varshead *ast_bridge_get_ari_reportable_variables(struct ast_bridge *bridge); + struct ast_unreal_pvt; /*! diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h index 4a43b5ee44..67689113e2 100644 --- a/include/asterisk/channel.h +++ b/include/asterisk/channel.h @@ -4500,6 +4500,12 @@ int ast_channel_fd_count(const struct ast_channel *chan); */ int ast_channel_fd_add(struct ast_channel *chan, int value); +/* ARI reportable variables accessors */ +size_t ast_channel_internal_ari_reportable_vars_count(const struct ast_channel *chan); +char *ast_channel_internal_ari_reportable_vars_get(const struct ast_channel *chan, size_t index); +int ast_channel_internal_ari_reportable_vars_append(struct ast_channel *chan, char *key); +char *ast_channel_internal_ari_reportable_vars_remove(struct ast_channel *chan, size_t index); + pthread_t ast_channel_blocker(const struct ast_channel *chan); void ast_channel_blocker_set(struct ast_channel *chan, pthread_t value); @@ -4689,6 +4695,20 @@ void ast_channel_set_ari_vars(size_t varc, char **vars); */ struct varshead *ast_channel_get_ari_vars(struct ast_channel *chan); +/*! + * \since 20.20.0 + * \since 22.10.0 + * \since 23.4.0 + * \brief Set whether a channel variable should be included in REST events on the channel. + * + * \param chan Channel to update. + * \param variable Variable name or dialplan function expression. + * \param report_events Non-zero to include in REST events, zero to omit. + * \retval 0 on success + * \retval -1 on failure + */ +int ast_channel_set_ari_var_reportable(struct ast_channel *chan, const char *variable, int report_events); + /*! * \since 12 * \brief Gets the variables for a given channel, as set using pbx_builtin_setvar_helper(). diff --git a/include/asterisk/stasis_app.h b/include/asterisk/stasis_app.h index 44f811cf7f..d522a1ff68 100644 --- a/include/asterisk/stasis_app.h +++ b/include/asterisk/stasis_app.h @@ -636,6 +636,19 @@ int stasis_app_control_answer(struct stasis_app_control *control); */ int stasis_app_control_set_channel_var(struct stasis_app_control *control, const char *variable, const char *value); +/*! + * \brief Set a variable on the channel associated with this control to value with option of including in events. + * \param control Control for \c res_stasis. + * \param variable The name of the variable + * \param value The value to set the variable to + * \param report_events Whether to include this variable in channel events. + * + * \return 0 for success. + * \return -1 for error. + */ +int stasis_app_control_set_channel_var_reportable(struct stasis_app_control *control, const char *variable, const char *value, int report_events); + + /*! * \brief Place the channel associated with the control on hold. * \param control Control for \c res_stasis. @@ -911,6 +924,20 @@ struct ast_bridge *stasis_app_get_bridge(struct stasis_app_control *control); */ void stasis_app_bridge_destroy(const char *bridge_id); +/*! + * \brief Set or clear a variable on a bridge and control ARI event reporting for it. + * + * \param bridge_id Uniqueid of bridge + * \param variable Variable name + * \param value Variable value (NULL/empty clears) + * \param report_events Non-zero to include in ARI bridge events + * + * \retval 0 on success + * \retval -1 on failure + */ +int stasis_app_bridge_set_var_reportable(const char *bridge_id, const char *variable, + const char *value, int report_events); + /*! * \brief Get the Stasis message sanitizer for app_stasis applications * diff --git a/main/bridge.c b/main/bridge.c index cc2183b526..7bcc32cb54 100644 --- a/main/bridge.c +++ b/main/bridge.c @@ -149,6 +149,17 @@ static unsigned int optimization_id; /* Variable name - stores peer information about the most recent attended transfer */ #define ATTENDEDTRANSFER "ATTENDEDTRANSFER" +/*! + * \brief Compare a bridge variable name with a given name. + * + * \param var_str The variable name to compare. + * \param name The name to compare against. + * + * \retval 0 if the names do not match. + * \retval 1 if the names match. + */ +#define BV_NAME_CMP(var_str, name) !strcmp(var_str, name) + static void cleanup_video_mode(struct ast_bridge *bridge); /*! Default DTMF keys for built in features */ @@ -659,6 +670,7 @@ static void bridge_handle_actions(struct ast_bridge *bridge) static void destroy_bridge(void *obj) { struct ast_bridge *bridge = obj; + struct ast_var_t *var; ast_debug(1, "Bridge " BRIDGE_PRINTF_SPEC ": actually destroying %s bridge, nobody wants it anymore\n", BRIDGE_PRINTF_VARS(bridge), bridge->v_table->name); @@ -708,6 +720,12 @@ static void destroy_bridge(void *obj) ast_string_field_free_memory(bridge); ao2_cleanup(bridge->current_snapshot); bridge->current_snapshot = NULL; + while ((var = AST_LIST_REMOVE_HEAD(&bridge->bridgevars, entries))) { + ast_var_delete(var); + } + AST_LIST_HEAD_INIT_NOLOCK(&bridge->bridgevars); + AST_VECTOR_RESET(&bridge->ari_reportable_variable_names, ast_free); + AST_VECTOR_FREE(&bridge->ari_reportable_variable_names); } struct ast_bridge *bridge_register(struct ast_bridge *bridge) @@ -776,6 +794,8 @@ struct ast_bridge *bridge_alloc(size_t size, const struct ast_bridge_methods *v_ bridge->v_table = v_table; AST_VECTOR_INIT(&bridge->media_types, AST_MEDIA_TYPE_END); + AST_LIST_HEAD_INIT_NOLOCK(&bridge->bridgevars); + AST_VECTOR_INIT(&bridge->ari_reportable_variable_names, 8); return bridge; } @@ -1286,6 +1306,116 @@ void ast_bridge_vars_set(struct ast_channel *chan, const char *name, const char ast_channel_stage_snapshot_done(chan); } +static int bridge_set_ari_var_reportable(struct ast_bridge *bridge, const char *variable, + int report_events) +{ + char *var_str; + + if (ast_strlen_zero(variable)) { + return -1; + } + + if (!report_events) { + AST_VECTOR_REMOVE_CMP_UNORDERED(&bridge->ari_reportable_variable_names, variable, + BV_NAME_CMP, ast_free); + return 0; + } + + if (AST_VECTOR_GET_CMP(&bridge->ari_reportable_variable_names, variable, BV_NAME_CMP)) { + return 0; /* already present */ + } + + var_str = ast_strdup(variable); + if (!var_str) { + return -1; + } + + if (AST_VECTOR_APPEND(&bridge->ari_reportable_variable_names, var_str)) { + ast_free(var_str); + return -1; + } + + return 0; +} + +int ast_bridge_set_variable(struct ast_bridge *bridge, const char *name, const char *value, + int report_events) +{ + struct ast_var_t *var; + + if (ast_strlen_zero(name)) { + return -1; + } + + AST_LIST_TRAVERSE_SAFE_BEGIN(&bridge->bridgevars, var, entries) { + if (!strcmp(ast_var_name(var), name)) { + AST_LIST_REMOVE_CURRENT(entries); + ast_var_delete(var); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + + if (ast_strlen_zero(value)) { + /* If the value is empty, remove the variable from the reportable list by + forcing report_events to 0 */ + bridge_set_ari_var_reportable(bridge, name, 0); + return 0; + } + + var = ast_var_assign(name, value); + if (!var) { + return -1; + } + + if (bridge_set_ari_var_reportable(bridge, name, report_events)) { + ast_var_delete(var); + return -1; + } + + AST_LIST_INSERT_TAIL(&bridge->bridgevars, var, entries); + return 0; +} + +const char *ast_bridge_get_variable(const struct ast_bridge *bridge, const char *name) +{ + return ast_var_find(&bridge->bridgevars, name); +} + +struct varshead *ast_bridge_get_ari_reportable_variables(struct ast_bridge *bridge) +{ + struct varshead *ret; + char *var_str; + size_t i; + + if (AST_VECTOR_SIZE(&bridge->ari_reportable_variable_names) == 0) { + return NULL; + } + + ret = ast_var_list_create(); + if (!ret) { + return NULL; + } + + for (i = 0; i < AST_VECTOR_SIZE(&bridge->ari_reportable_variable_names); ++i) { + const char *val = NULL; + struct ast_var_t *var; + + var_str = AST_VECTOR_GET(&bridge->ari_reportable_variable_names, i); + val = ast_bridge_get_variable(bridge, var_str); + + var = ast_var_assign(var_str, val ? val : ""); + if (!var) { + ast_var_list_destroy(ret); + return NULL; + } + + AST_LIST_INSERT_TAIL(ret, var, entries); + } + + return ret; +} + /*! * \internal * \brief Set BRIDGEPEER and BRIDGEPVTCALLID channel variables in a 2 party bridge. diff --git a/main/channel.c b/main/channel.c index afe3cacc10..ec0912dda1 100644 --- a/main/channel.c +++ b/main/channel.c @@ -7986,6 +7986,46 @@ void ast_channel_set_ari_vars(size_t varc, char **vars) channel_set_external_vars(&ari_vars, varc, vars); } +int ast_channel_set_ari_var_reportable(struct ast_channel *chan, const char *variable, int report_events) +{ + char *var_str; + size_t i, count; + + SCOPED_CHANNELLOCK(lock, chan); + + if (ast_strlen_zero(variable)) { + return -1; + } + + count = ast_channel_internal_ari_reportable_vars_count(chan); + for (i = 0; i < count; ++i) { + var_str = ast_channel_internal_ari_reportable_vars_get(chan, i); + if (!strcmp(var_str, variable)) { + if (!report_events) { + var_str = ast_channel_internal_ari_reportable_vars_remove(chan, i); + ast_free(var_str); + } + return 0; + } + } + + if (!report_events) { + return 0; + } + + var_str = ast_strdup(variable); + if (!var_str) { + return -1; + } + + if (ast_channel_internal_ari_reportable_vars_append(chan, var_str)) { + ast_free(var_str); + return -1; + } + + return 0; +} + /*! * \brief Destructor for lists of variables. * \param obj AO2 object. @@ -8079,7 +8119,75 @@ struct varshead *ast_channel_get_manager_vars(struct ast_channel *chan) struct varshead *ast_channel_get_ari_vars(struct ast_channel *chan) { - return channel_get_external_vars(&ari_vars, chan); + RAII_VAR(struct varshead *, ret, NULL, ao2_cleanup); + RAII_VAR(struct ast_str *, tmp, NULL, ast_free); + char *var_str; + size_t i; + + SCOPED_CHANNELLOCK(lock, chan); + + ret = channel_get_external_vars(&ari_vars, chan); + + if (ast_channel_internal_ari_reportable_vars_count(chan) == 0) { + if (!ret) { + return NULL; + } + + ao2_ref(ret, +1); + return ret; + } + + if (!ret) { + ret = ao2_alloc(sizeof(*ret), varshead_dtor); + if (!ret) { + return NULL; + } + } + + tmp = ast_str_create(16); + if (!tmp) { + return NULL; + } + + for (i = 0; i < ast_channel_internal_ari_reportable_vars_count(chan); ++i) { + const char *val = NULL; + struct ast_var_t *var; + int already_present = 0; + struct ast_var_t *existing; + + var_str = ast_channel_internal_ari_reportable_vars_get(chan, i); + + AST_LIST_TRAVERSE(ret, existing, entries) { + if (!strcmp(ast_var_name(existing), var_str)) { + already_present = 1; + break; + } + } + if (already_present) { + continue; + } + + if (strchr(var_str, '(')) { + if (ast_func_read2(chan, var_str, &tmp, 0) == 0) { + val = ast_str_buffer(tmp); + } else { + ast_log(LOG_ERROR, + "Error invoking function %s\n", var_str); + } + } else { + val = pbx_builtin_getvar_helper(chan, var_str); + } + + var = ast_var_assign(var_str, val ? val : ""); + if (!var) { + return NULL; + } + + AST_RWLIST_INSERT_TAIL(ret, var, entries); + } + + ao2_ref(ret, +1); + return ret; } void ast_channel_close_storage(void) diff --git a/main/channel_internal_api.c b/main/channel_internal_api.c index 73a9828c94..9c99350e74 100644 --- a/main/channel_internal_api.c +++ b/main/channel_internal_api.c @@ -1096,6 +1096,35 @@ int ast_channel_fd_count(const struct ast_channel *chan) return AST_VECTOR_SIZE(&chan->fds); } +size_t ast_channel_internal_ari_reportable_vars_count(const struct ast_channel *chan) +{ + return AST_VECTOR_SIZE(&chan->ari_report_vars); +} + +char *ast_channel_internal_ari_reportable_vars_get( + const struct ast_channel *chan, size_t index) +{ + return index < AST_VECTOR_SIZE(&chan->ari_report_vars) + ? AST_VECTOR_GET(&chan->ari_report_vars, index) + : NULL; +} + +int ast_channel_internal_ari_reportable_vars_append(struct ast_channel *chan, + char *key) +{ + return AST_VECTOR_APPEND(&chan->ari_report_vars, key); +} + +char *ast_channel_internal_ari_reportable_vars_remove( + struct ast_channel *chan, size_t index) +{ + if (index >= AST_VECTOR_SIZE(&chan->ari_report_vars)) { + return NULL; + } + + return AST_VECTOR_REMOVE(&chan->ari_report_vars, index, 0); +} + int ast_channel_fd_add(struct ast_channel *chan, int value) { int pos = AST_EXTENDED_FDS; @@ -1301,6 +1330,7 @@ struct ast_channel *__ast_channel_internal_alloc_with_initializers(void (*destru } AST_VECTOR_INIT(&tmp->fds, AST_MAX_FDS); + AST_VECTOR_INIT(&tmp->ari_report_vars, 8); /* Force all channel snapshot segments to be created on first use, so we don't have to check if * an old snapshot exists. @@ -1434,6 +1464,8 @@ void ast_channel_internal_cleanup(struct ast_channel *chan) ast_channel_internal_set_stream_topology(chan, NULL); + AST_VECTOR_RESET(&chan->ari_report_vars, ast_free); + AST_VECTOR_FREE(&chan->ari_report_vars); AST_VECTOR_FREE(&chan->fds); } diff --git a/main/channel_private.h b/main/channel_private.h index f7a0402996..d1fd64a7df 100644 --- a/main/channel_private.h +++ b/main/channel_private.h @@ -204,6 +204,7 @@ struct ast_channel { struct ast_flags snapshot_segment_flags; /*!< Flags regarding the segments of the snapshot */ int linked_in_container; /*!< Whether this channel is linked in a storage container */ struct ast_endpoint *endpoint; /*!< The endpoint associated with this channel */ + AST_VECTOR(, char *) ari_report_vars; /*!< Channel variable names to be included in ARI events */ }; #if defined(__cplusplus) || defined(c_plusplus) diff --git a/main/stasis_bridges.c b/main/stasis_bridges.c index b03724b832..ef2b87a5cc 100644 --- a/main/stasis_bridges.c +++ b/main/stasis_bridges.c @@ -30,6 +30,7 @@ #include "asterisk.h" #include "asterisk/astobj2.h" +#include "asterisk/json.h" #include "asterisk/stasis.h" #include "asterisk/stasis_cache_pattern.h" #include "asterisk/channel.h" @@ -224,6 +225,8 @@ static void bridge_snapshot_dtor(void *obj) ast_string_field_free_memory(snapshot); ao2_cleanup(snapshot->channels); snapshot->channels = NULL; + ast_var_list_destroy(snapshot->bridgevars); + snapshot->bridgevars = NULL; } struct ast_bridge_snapshot *ast_bridge_snapshot_create(struct ast_bridge *bridge) @@ -285,6 +288,8 @@ struct ast_bridge_snapshot *ast_bridge_snapshot_create(struct ast_bridge *bridge ast_channel_uniqueid(bridge->softmix.video_mode.mode_data.talker_src_data.chan_vsrc)); } + snapshot->bridgevars = ast_bridge_get_ari_reportable_variables(bridge); + return snapshot; } @@ -783,6 +788,11 @@ struct ast_json *ast_bridge_snapshot_to_json( ast_json_string_create(snapshot->video_source_id)); } + if (snapshot->bridgevars && !AST_LIST_EMPTY(snapshot->bridgevars)) { + ast_json_object_set(json_bridge, "bridgevars", + ast_json_channel_vars(snapshot->bridgevars)); + } + return json_bridge; } diff --git a/main/stasis_channels.c b/main/stasis_channels.c index 5ab142f369..236d0c3825 100644 --- a/main/stasis_channels.c +++ b/main/stasis_channels.c @@ -1218,9 +1218,13 @@ void ast_channel_publish_varset(struct ast_channel *chan, const char *name, cons return; } - /*! If there are manager variables, force a cache update */ - if (chan && ast_channel_has_manager_vars()) { - ast_channel_publish_snapshot(chan); + /*! If there are manager or ARI variables, force a cache update */ + if (chan) { + struct varshead *ari_vars = ast_channel_get_ari_vars(chan); + if (ast_channel_has_manager_vars() || (ari_vars && !AST_LIST_EMPTY(ari_vars))) { + ast_channel_publish_snapshot(chan); + } + ao2_cleanup(ari_vars); } /* This function is NULL safe for global variables */ diff --git a/res/ari/ari_model_validators.c b/res/ari/ari_model_validators.c index ae82cee8fc..8e5679ab19 100644 --- a/res/ari/ari_model_validators.c +++ b/res/ari/ari_model_validators.c @@ -1809,6 +1809,15 @@ int ast_ari_validate_bridge(struct ast_json *json) res = 0; } } else + if (strcmp("bridgevars", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + prop_is_valid = ast_ari_validate_object( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI Bridge field bridgevars failed validation\n"); + res = 0; + } + } else if (strcmp("channels", ast_json_object_iter_key(iter)) == 0) { int prop_is_valid; has_channels = 1; diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h index e05dd1e5bd..102c45f29d 100644 --- a/res/ari/ari_model_validators.h +++ b/res/ari/ari_model_validators.h @@ -1626,6 +1626,7 @@ ari_validator ast_ari_validate_application_fn(void); * Bridge * - bridge_class: string (required) * - bridge_type: string (required) + * - bridgevars: object * - channels: List[string] (required) * - creationtime: Date (required) * - creator: string (required) diff --git a/res/ari/resource_bridges.c b/res/ari/resource_bridges.c index 712f6a5ac1..5ae152bd1f 100644 --- a/res/ari/resource_bridges.c +++ b/res/ari/resource_bridges.c @@ -1023,12 +1023,114 @@ void ast_ari_bridges_list(struct ast_variable *headers, ast_ari_response_ok(response, ast_json_ref(json)); } +static int json_to_ast_variables(struct ast_ari_response *response, struct ast_json *json_variables, + struct ast_variable **variables, struct ast_variable **report_event_variables) +{ + struct ast_json_iter *it_json_var; + struct ast_variable *var_tail = NULL; + struct ast_variable *report_var_tail = NULL; + + *variables = NULL; + *report_event_variables = NULL; + + for (it_json_var = ast_json_object_iter(json_variables); it_json_var; + it_json_var = ast_json_object_iter_next(json_variables, it_json_var)) { + struct ast_variable *new_var; + const char *key = ast_json_object_iter_key(it_json_var); + const char *value = NULL; + struct ast_json *json_value = ast_json_object_iter_value(it_json_var); + int report_events = 0; + + if (ast_strlen_zero(key)) { + continue; + } + + if (ast_json_typeof(json_value) == AST_JSON_STRING) { + value = ast_json_string_get(json_value); + } else if (ast_json_typeof(json_value) == AST_JSON_OBJECT) { + struct ast_json *value_field = ast_json_object_get(json_value, "value"); + struct ast_json *report_field = ast_json_object_get(json_value, "report_events"); + ast_log(LOG_DEBUG, "Processing variable '%s' with report_events: %s\n", key, + report_field ? (ast_json_is_true(report_field) ? "true" : "false") : "not set"); + + if (!value_field || ast_json_typeof(value_field) != AST_JSON_STRING) { + ast_ari_response_error(response, 400, "Bad Request", + "Each object value in 'variables' must include string field 'value'"); + ast_log(LOG_WARNING, "Missing or invalid 'value' field for variable '%s'\n", key); + if (!value_field) { + ast_log(LOG_WARNING, "Missing 'value' field for variable '%s'\n", key); + } else if (ast_json_typeof(value_field) != AST_JSON_STRING) { + ast_log(LOG_WARNING, "Invalid 'value' field for variable '%s' (bad type)\n", key); + } + goto error; + } + + value = ast_json_string_get(value_field); + + if (report_field) { + enum ast_json_type report_type = ast_json_typeof(report_field); + + if (report_type != AST_JSON_TRUE && report_type != AST_JSON_FALSE) { + ast_ari_response_error(response, 400, "Bad Request", + "Field 'report_events' in 'variables' entries must be boolean"); + ast_log(LOG_WARNING, "Invalid 'report_events' field for variable '%s' (bad type)\n", key); + goto error; + } + + report_events = ast_json_is_true(report_field); + } + } else { + ast_ari_response_error(response, 400, "Bad Request", + "Each value in 'variables' must be a string or an object with 'value' and optional 'report_events'"); + ast_log(LOG_WARNING, "Invalid value for variable '%s'\n", key); + goto error; + } + + if (!value) { + continue; + } + + new_var = ast_variable_new(key, value, ""); + if (!new_var) { + ast_ari_response_alloc_failed(response); + goto error; + } + + var_tail = ast_variable_list_append_hint(variables, var_tail, new_var); + + if (report_events && report_event_variables) { + struct ast_variable *report_var = ast_variable_new(key, "1", ""); + + if (!report_var) { + ast_ari_response_alloc_failed(response); + goto error; + } + + report_var_tail = ast_variable_list_append_hint(report_event_variables, + report_var_tail, report_var); + } + } + + return 0; + +error: + ast_variables_destroy(*variables); + *variables = NULL; + if (report_event_variables) { + ast_variables_destroy(*report_event_variables); + *report_event_variables = NULL; + } + return -1; +} + void ast_ari_bridges_create(struct ast_variable *headers, struct ast_ari_bridges_create_args *args, struct ast_ari_response *response) { RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); RAII_VAR(struct ast_bridge_snapshot *, snapshot, NULL, ao2_cleanup); + struct ast_variable *variables = NULL; + struct ast_variable *report_event_variables = NULL; if (ast_bridge_topic_exists(args->bridge_id)) { ast_ari_response_error( @@ -1037,6 +1139,8 @@ void ast_ari_bridges_create(struct ast_variable *headers, return; } + ast_ari_bridges_create_parse_body(args->variables, args); + bridge = stasis_app_bridge_create(args->type, args->name, args->bridge_id); if (!bridge) { ast_ari_response_error( @@ -1045,9 +1149,45 @@ void ast_ari_bridges_create(struct ast_variable *headers, return; } + if (args->variables && json_to_ast_variables(response, args->variables, + &variables, &report_event_variables)) { + return; + } + ast_bridge_lock(bridge); + if (variables) { + struct ast_variable *var; + + for (var = variables; var; var = var->next) { + int report_events = 0; + struct ast_variable *report_var; + char buf[strlen(var->name) + 1]; + char *variable; + strcpy(buf, var->name); + /* Strip whitespace from the variable name */ + variable = ast_strip(buf); + + for (report_var = report_event_variables; report_var; + report_var = report_var->next) { + if (!strcmp(report_var->name, var->name)) { + report_events = 1; + break; + } + } + + if (ast_bridge_set_variable(bridge, variable, var->value, report_events)) { + ast_bridge_unlock(bridge); + ast_variables_destroy(variables); + ast_variables_destroy(report_event_variables); + ast_ari_response_alloc_failed(response); + return; + } + } + } snapshot = ast_bridge_snapshot_create(bridge); ast_bridge_unlock(bridge); + ast_variables_destroy(variables); + ast_variables_destroy(report_event_variables); if (!snapshot) { ast_ari_response_error( @@ -1066,6 +1206,8 @@ void ast_ari_bridges_create_with_id(struct ast_variable *headers, { RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); RAII_VAR(struct ast_bridge_snapshot *, snapshot, NULL, ao2_cleanup); + struct ast_variable *variables = NULL; + struct ast_variable *report_event_variables = NULL; if (ast_bridge_topic_exists(args->bridge_id)) { ast_ari_response_error( @@ -1074,6 +1216,8 @@ void ast_ari_bridges_create_with_id(struct ast_variable *headers, return; } + ast_ari_bridges_create_with_id_parse_body(args->variables, args); + bridge = stasis_app_bridge_create(args->type, args->name, args->bridge_id); if (!bridge) { ast_ari_response_error( @@ -1082,9 +1226,56 @@ void ast_ari_bridges_create_with_id(struct ast_variable *headers, return; } + if (args->variables) { + struct ast_json *json_variables; + + json_variables = ast_json_object_get(args->variables, "variables"); + if (json_variables && json_to_ast_variables(response, json_variables, + &variables, &report_event_variables)) { + if (args->variables) { + ast_log(LOG_WARNING, "Failed to parse variables for new bridge '%s'\n", args->bridge_id); + } else { + ast_log(LOG_WARNING, "Failed to find variables for new bridge '%s'\n", args->bridge_id); + } + return; + } + } + ast_bridge_lock(bridge); + if (variables) { + struct ast_variable *var; + + for (var = variables; var; var = var->next) { + int report_events = 0; + struct ast_variable *report_var; + char buf[strlen(var->name) + 1]; + char *variable; + strcpy(buf, var->name); + /* Strip whitespace from the variable name */ + variable = ast_strip(buf); + + report_events = 0; + for (report_var = report_event_variables; report_var; + report_var = report_var->next) { + if (!strcmp(report_var->name, var->name)) { + report_events = 1; + break; + } + } + + if (ast_bridge_set_variable(bridge, variable, var->value, report_events)) { + ast_bridge_unlock(bridge); + ast_variables_destroy(variables); + ast_variables_destroy(report_event_variables); + ast_ari_response_alloc_failed(response); + return; + } + } + } snapshot = ast_bridge_snapshot_create(bridge); ast_bridge_unlock(bridge); + ast_variables_destroy(variables); + ast_variables_destroy(report_event_variables); if (!snapshot) { ast_ari_response_error( @@ -1161,3 +1352,245 @@ void ast_ari_bridges_clear_video_source(struct ast_variable *headers, ao2_ref(bridge, -1); ast_ari_response_no_content(response); } + +void ast_ari_bridges_get_bridge_var(struct ast_variable *headers, + struct ast_ari_bridges_get_bridge_var_args *args, + struct ast_ari_response *response) +{ + RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); + struct ast_bridge *bridge; + const char *value; + + if (ast_strlen_zero(args->variable)) { + ast_ari_response_error(response, 400, "Bad Request", + "Variable name is required"); + return; + } + + bridge = find_bridge(response, args->bridge_id); + if (!bridge) { + return; + } + + ast_bridge_lock(bridge); + value = ast_bridge_get_variable(bridge, args->variable); + ast_bridge_unlock(bridge); + + if (!value) { + ao2_ref(bridge, -1); + ast_ari_response_error(response, 404, "Not Found", + "Provided variable was not found"); + return; + } + + json = ast_json_pack("{s: s}", "value", value); + ao2_ref(bridge, -1); + + if (!json) { + ast_ari_response_alloc_failed(response); + return; + } + + ast_ari_response_ok(response, ast_json_ref(json)); +} + +void ast_ari_bridges_set_bridge_var(struct ast_variable *headers, + struct ast_ari_bridges_set_bridge_var_args *args, + struct ast_ari_response *response) +{ + struct ast_bridge *bridge; + char buf[strlen(args->variable) + 1]; + char *variable; + + if (ast_strlen_zero(args->variable)) { + ast_ari_response_error(response, 400, "Bad Request", + "Variable name is required"); + return; + } + + bridge = find_bridge(response, args->bridge_id); + if (!bridge) { + return; + } + ao2_ref(bridge, -1); + + strcpy(buf, args->variable); + /* Strip whitespace from the variable name */ + variable = ast_strip(buf); + + if (stasis_app_bridge_set_var_reportable(args->bridge_id, variable, args->value, + args->report_events)) { + ast_ari_response_error(response, 400, "Bad Request", + "Failed to execute function"); + return; + } + + ast_ari_response_no_content(response); +} + +void ast_ari_bridges_get_bridge_vars(struct ast_variable *headers, + struct ast_ari_bridges_get_bridge_vars_args *args, + struct ast_ari_response *response) +{ + int res; + RAII_VAR(struct ast_json *, json, ast_json_object_create(), ast_json_unref); + RAII_VAR(struct ast_json *, inner_json, ast_json_object_create(), ast_json_unref); + RAII_VAR(struct ast_str *, value, ast_str_create(32), ast_free); + RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); + + ast_assert(response != NULL); + + if (!json || !inner_json || !value) { + ast_ari_response_alloc_failed(response); + return; + } + + if (args->variables_count == 0) { + ast_ari_response_error( + response, 400, "Bad Request", + "At least one variable name is required"); + return; + } + + if (ast_strlen_zero(args->bridge_id)) { + ast_ari_response_error( + response, 400, "Bad Request", + "Bridge ID is required"); + return; + } + + bridge = stasis_app_bridge_find_by_id(args->bridge_id); + if (!bridge) { + ast_ari_response_error( + response, 404, "Bridge Not Found", + "Provided bridge was not found"); + return; + } + + for (int i = 0; i < args->variables_count; i++) { + struct ast_json *json_str; + char buf[strlen(args->variables[i]) + 1]; + char *variable; + const char *var_value; + + strcpy(buf, args->variables[i]); + variable = ast_strip(buf); + if (ast_strlen_zero(variable)) { + ast_ari_response_error( + response, 400, "Bad Request", + "Variable names are required"); + return; + } + + if (variable[strlen(variable) - 1] == ')') { + if (ast_func_read2(NULL, variable, &value, 0)) { + ast_ari_response_error( + response, 500, "Error With Function", + "Unable to read provided function"); + return; + } + } else { + ast_bridge_lock(bridge); + var_value = ast_bridge_get_variable(bridge, variable); + ast_bridge_unlock(bridge); + if (!var_value) { + ast_ari_response_error( + response, 404, "Variable Not Found", + "Provided variable was not found"); + return; + } + ast_str_set(&value, 0, "%s", var_value); + } + + json_str = ast_json_string_create(ast_str_buffer(value)); + if (!json_str) { + ast_ari_response_alloc_failed(response); + return; + } + + res = ast_json_object_set(inner_json, variable, json_str); + if (res) { + ast_ari_response_alloc_failed(response); + ast_json_unref(json_str); + return; + } + } + + res = ast_json_object_set(json, "variables", ast_json_ref(inner_json)); + if (res) { + ast_ari_response_alloc_failed(response); + return; + } + + ast_ari_response_ok(response, ast_json_ref(json)); +} + +void ast_ari_bridges_set_bridge_vars(struct ast_variable *headers, + struct ast_ari_bridges_set_bridge_vars_args *args, + struct ast_ari_response *response) +{ + struct ast_json *json_variables; + struct ast_variable *var; + RAII_VAR(struct ast_variable *, variables, NULL, ast_variables_destroy); + RAII_VAR(struct ast_variable *, report_event_variables, NULL, ast_variables_destroy); + RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); + + ast_assert(response != NULL); + + if (!args->variables) { + ast_ari_response_error( + response, 400, "Bad Request", + "The 'variables' field is required"); + return; + } + + bridge = stasis_app_bridge_find_by_id(args->bridge_id); + if (!bridge) { + ast_ari_response_error( + response, 404, "Bridge Not Found", + "Provided bridge was not found"); + return; + } + + json_variables = ast_json_object_get(args->variables, "variables"); + if (!json_variables || ast_json_typeof(json_variables) != AST_JSON_OBJECT) { + ast_ari_response_error( + response, 400, "Bad Request", + "The 'variables' field must be a JSON object"); + return; + } + + if (json_to_ast_variables(response, json_variables, &variables, + &report_event_variables)) { + return; + } + + for (var = variables; var; var = var->next) { + int report_events = 0; + struct ast_variable *report_var; + char buf[strlen(var->name) + 1]; + char *variable; + strcpy(buf, var->name); + /* Strip whitespace from the variable name */ + variable = ast_strip(buf); + + /* See if the variable is in the report event list */ + for (report_var = report_event_variables; report_var; + report_var = report_var->next) { + if (!strcmp(report_var->name, var->name)) { + report_events = 1; + break; + } + } + + if (stasis_app_bridge_set_var_reportable(args->bridge_id, variable, var->value, + report_events)) { + ast_ari_response_error( + response, 400, "Bad Request", + "Failed to execute function"); + return; + } + } + + ast_ari_response_no_content(response); +} diff --git a/res/ari/resource_bridges.h b/res/ari/resource_bridges.h index 068ac28d60..72ec5ce4e5 100644 --- a/res/ari/resource_bridges.h +++ b/res/ari/resource_bridges.h @@ -58,6 +58,8 @@ struct ast_ari_bridges_create_args { const char *bridge_id; /*! Name to give to the bridge being created. */ const char *name; + /*! The "variables" key in the body object holds variable key/value pairs to set on the bridge on creation. Each variable is an object containing "value" (string) and optional "report_events" (boolean) to include updates for that variable in bridge events (defaults to false). Ex. { "name": "SupportBridge", "variables": { "Bridge_State": { "value": "WaitingForAgent", "report_events": true } } } */ + struct ast_json *variables; }; /*! * \brief Body parsing function for /bridges. @@ -88,6 +90,8 @@ struct ast_ari_bridges_create_with_id_args { const char *bridge_id; /*! Set the name of the bridge. */ const char *name; + /*! The "variables" key in the body object holds variable key/value pairs to set on the bridge on creation. Each variable is an object containing "value" (string) and optional "report_events" (boolean) to include updates for that variable in bridge events (defaults to false). Ex. { "name": "SupportBridge", "variables": { "Bridge_State": { "value": "WaitingForAgent", "report_events": true } } } */ + struct ast_json *variables; }; /*! * \brief Body parsing function for /bridges/{bridgeId}. @@ -138,6 +142,118 @@ struct ast_ari_bridges_destroy_args { * \param[out] response HTTP response */ void ast_ari_bridges_destroy(struct ast_variable *headers, struct ast_ari_bridges_destroy_args *args, struct ast_ari_response *response); +/*! Argument struct for ast_ari_bridges_get_bridge_var() */ +struct ast_ari_bridges_get_bridge_var_args { + /*! Bridge's id */ + const char *bridge_id; + /*! The bridge variable or function to get */ + const char *variable; +}; +/*! + * \brief Body parsing function for /bridges/{bridgeId}/variable. + * \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_bridges_get_bridge_var_parse_body( + struct ast_json *body, + struct ast_ari_bridges_get_bridge_var_args *args); + +/*! + * \brief Get the value of a bridge variable or function. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void ast_ari_bridges_get_bridge_var(struct ast_variable *headers, struct ast_ari_bridges_get_bridge_var_args *args, struct ast_ari_response *response); +/*! Argument struct for ast_ari_bridges_set_bridge_var() */ +struct ast_ari_bridges_set_bridge_var_args { + /*! Bridge's id */ + const char *bridge_id; + /*! The bridge variable or function to set */ + const char *variable; + /*! The value to set the variable to */ + const char *value; + /*! Whether this variable should be included in bridge events. Defaults to false. */ + int report_events; +}; +/*! + * \brief Body parsing function for /bridges/{bridgeId}/variable. + * \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_bridges_set_bridge_var_parse_body( + struct ast_json *body, + struct ast_ari_bridges_set_bridge_var_args *args); + +/*! + * \brief Set the value of a bridge variable or function. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void ast_ari_bridges_set_bridge_var(struct ast_variable *headers, struct ast_ari_bridges_set_bridge_var_args *args, struct ast_ari_response *response); +/*! Argument struct for ast_ari_bridges_get_bridge_vars() */ +struct ast_ari_bridges_get_bridge_vars_args { + /*! Bridge's id */ + const char *bridge_id; + /*! Array of The bridge variables or functions to get */ + const char **variables; + /*! Length of variables array. */ + size_t variables_count; + /*! Parsing context for variables. */ + char *variables_parse; +}; +/*! + * \brief Body parsing function for /bridges/{bridgeId}/variables. + * \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_bridges_get_bridge_vars_parse_body( + struct ast_json *body, + struct ast_ari_bridges_get_bridge_vars_args *args); + +/*! + * \brief Get the value of multiple bridge variables or functions. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void ast_ari_bridges_get_bridge_vars(struct ast_variable *headers, struct ast_ari_bridges_get_bridge_vars_args *args, struct ast_ari_response *response); +/*! Argument struct for ast_ari_bridges_set_bridge_vars() */ +struct ast_ari_bridges_set_bridge_vars_args { + /*! Bridge's id */ + const char *bridge_id; + /*! The "variables" key in the body object holds variable key/value pairs to set on the bridge. Each variable value may be either a string or an object containing "value" (string) and optional "report_events" (boolean) to include updates for that variable in bridge events (defaults to false). Ex. { "variables": { "Bridge_State": "WaitingForAgent", "Support_Level": { "value": "Premium", "report_events": true } } } */ + struct ast_json *variables; +}; +/*! + * \brief Body parsing function for /bridges/{bridgeId}/variables. + * \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_bridges_set_bridge_vars_parse_body( + struct ast_json *body, + struct ast_ari_bridges_set_bridge_vars_args *args); + +/*! + * \brief Set the values of multiple bridge variables or functions. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void ast_ari_bridges_set_bridge_vars(struct ast_variable *headers, struct ast_ari_bridges_set_bridge_vars_args *args, struct ast_ari_response *response); /*! Argument struct for ast_ari_bridges_add_channel() */ struct ast_ari_bridges_add_channel_args { /*! Bridge's id */ diff --git a/res/ari/resource_channels.c b/res/ari/resource_channels.c index ac4a33f092..c9d4a417fc 100644 --- a/res/ari/resource_channels.c +++ b/res/ari/resource_channels.c @@ -1122,6 +1122,7 @@ static struct ast_channel *ari_channels_handle_originate_with_id(const char *arg const char *args_caller_id, int args_timeout, struct ast_variable *variables, + struct ast_variable *report_event_variables, const char *args_channel_id, const char *args_other_channel_id, const char *args_originator, @@ -1390,6 +1391,18 @@ static struct ast_channel *ari_channels_handle_originate_with_id(const char *arg } } + if (report_event_variables) { + struct ast_variable *var; + + for (var = report_event_variables; var; var = var->next) { + if (ast_channel_set_ari_var_reportable(chan, var->name, 1)) { + ast_ari_response_alloc_failed(response); + ast_dial_destroy(dial); + ast_free(origination); + return NULL; + } + } + } snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(chan)); /* Before starting the async dial bump the ref in case the dial quickly goes away and takes @@ -1420,24 +1433,94 @@ static struct ast_channel *ari_channels_handle_originate_with_id(const char *arg * \retval 0 on success. * \retval -1 on error. */ -static int json_to_ast_variables(struct ast_ari_response *response, struct ast_json *json_variables, struct ast_variable **variables) +static int json_to_ast_variables(struct ast_ari_response *response, struct ast_json *json_variables, + struct ast_variable **variables, struct ast_variable **report_event_variables) { - enum ast_json_to_ast_vars_code res; + struct ast_json_iter *it_json_var; + struct ast_variable *var_tail = NULL; + struct ast_variable *report_var_tail = NULL; - res = ast_json_to_ast_variables(json_variables, variables); - switch (res) { - case AST_JSON_TO_AST_VARS_CODE_SUCCESS: - return 0; - case AST_JSON_TO_AST_VARS_CODE_INVALID_TYPE: - ast_ari_response_error(response, 400, "Bad Request", - "Only string values in the 'variables' object allowed"); - break; - case AST_JSON_TO_AST_VARS_CODE_OOM: - ast_ari_response_alloc_failed(response); - break; + *variables = NULL; + *report_event_variables = NULL; + + for (it_json_var = ast_json_object_iter(json_variables); it_json_var; + it_json_var = ast_json_object_iter_next(json_variables, it_json_var)) { + struct ast_variable *new_var; + const char *key = ast_json_object_iter_key(it_json_var); + const char *value = NULL; + struct ast_json *json_value = ast_json_object_iter_value(it_json_var); + int report_events = 0; + + if (ast_strlen_zero(key)) { + continue; + } + + if (ast_json_typeof(json_value) == AST_JSON_STRING) { + value = ast_json_string_get(json_value); + } else if (ast_json_typeof(json_value) == AST_JSON_OBJECT) { + struct ast_json *value_field = ast_json_object_get(json_value, "value"); + struct ast_json *report_field = ast_json_object_get(json_value, "report_events"); + ast_log(LOG_DEBUG, "Processing variable '%s' with report_events: %s\n", key, + report_field ? (ast_json_is_true(report_field) ? "true" : "false") : "not set"); + + if (!value_field || ast_json_typeof(value_field) != AST_JSON_STRING) { + ast_ari_response_error(response, 400, "Bad Request", + "Each object value in 'variables' must include string field 'value'"); + goto error; + } + + value = ast_json_string_get(value_field); + ast_log(LOG_DEBUG, "Variable '%s' has value '%s'\n", key, value); + + if (report_field) { + enum ast_json_type report_type = ast_json_typeof(report_field); + + if (report_type != AST_JSON_TRUE && report_type != AST_JSON_FALSE) { + ast_ari_response_error(response, 400, "Bad Request", + "Field 'report_events' in 'variables' entries must be boolean"); + goto error; + } + + report_events = ast_json_is_true(report_field); + } + } else { + ast_ari_response_error(response, 400, "Bad Request", + "Each value in 'variables' must be a string or an object with 'value' and optional 'report_events'"); + goto error; + } + + if (!value) { + continue; + } + + new_var = ast_variable_new(key, value, ""); + if (!new_var) { + ast_ari_response_alloc_failed(response); + goto error; + } + + var_tail = ast_variable_list_append_hint(variables, var_tail, new_var); + + if (report_events) { + struct ast_variable *report_var = ast_variable_new(key, "1", ""); + + if (!report_var) { + ast_ari_response_alloc_failed(response); + goto error; + } + + report_var_tail = ast_variable_list_append_hint(report_event_variables, + report_var_tail, report_var); + } } - ast_log(AST_LOG_ERROR, "Unable to convert 'variables' in JSON body to channel variables\n"); + return 0; + +error: + ast_variables_destroy(*variables); + *variables = NULL; + ast_variables_destroy(*report_event_variables); + *report_event_variables = NULL; return -1; } @@ -1446,6 +1529,7 @@ void ast_ari_channels_originate_with_id(struct ast_variable *headers, struct ast_ari_response *response) { struct ast_variable *variables = NULL; + struct ast_variable *report_event_variables = NULL; struct ast_channel *chan; /* Parse any query parameters out of the body parameter */ @@ -1455,7 +1539,8 @@ void ast_ari_channels_originate_with_id(struct ast_variable *headers, ast_ari_channels_originate_with_id_parse_body(args->variables, args); json_variables = ast_json_object_get(args->variables, "variables"); if (json_variables - && json_to_ast_variables(response, json_variables, &variables)) { + && json_to_ast_variables(response, json_variables, &variables, + &report_event_variables)) { return; } } @@ -1471,12 +1556,14 @@ void ast_ari_channels_originate_with_id(struct ast_variable *headers, args->caller_id, args->timeout, variables, + report_event_variables, args->channel_id, args->other_channel_id, args->originator, args->formats, response); ast_channel_cleanup(chan); + ast_variables_destroy(report_event_variables); ast_variables_destroy(variables); } @@ -1485,6 +1572,7 @@ void ast_ari_channels_originate(struct ast_variable *headers, struct ast_ari_response *response) { struct ast_variable *variables = NULL; + struct ast_variable *report_event_variables = NULL; struct ast_channel *chan; /* Parse any query parameters out of the body parameter */ @@ -1494,7 +1582,8 @@ void ast_ari_channels_originate(struct ast_variable *headers, ast_ari_channels_originate_parse_body(args->variables, args); json_variables = ast_json_object_get(args->variables, "variables"); if (json_variables - && json_to_ast_variables(response, json_variables, &variables)) { + && json_to_ast_variables(response, json_variables, &variables, + &report_event_variables)) { return; } } @@ -1510,12 +1599,14 @@ void ast_ari_channels_originate(struct ast_variable *headers, args->caller_id, args->timeout, variables, + report_event_variables, args->channel_id, args->other_channel_id, args->originator, args->formats, response); ast_channel_cleanup(chan); + ast_variables_destroy(report_event_variables); ast_variables_destroy(variables); } @@ -1701,7 +1792,8 @@ void ast_ari_channels_set_channel_var(struct ast_variable *headers, return; } - if (stasis_app_control_set_channel_var(control, args->variable, args->value)) { + if (stasis_app_control_set_channel_var_reportable(control, args->variable, args->value, + args->report_events)) { ast_ari_response_error( response, 400, "Bad Request", "Failed to execute function"); @@ -1716,9 +1808,9 @@ void ast_ari_channels_set_channel_vars(struct ast_variable *headers, struct ast_ari_response *response) { struct ast_json *json_variables; - struct ast_json_iter *it_json_var; - struct ast_variable *var = NULL; + struct ast_variable *var; RAII_VAR(struct ast_variable *, variables, NULL, ast_variables_destroy); + RAII_VAR(struct ast_variable *, report_event_variables, NULL, ast_variables_destroy); RAII_VAR(struct ast_channel *, channel, NULL, ast_channel_cleanup); RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup); @@ -1746,57 +1838,38 @@ void ast_ari_channels_set_channel_vars(struct ast_variable *headers, } json_variables = ast_json_object_get(args->variables, "variables"); - for (it_json_var = ast_json_object_iter(json_variables); it_json_var; - it_json_var = ast_json_object_iter_next(json_variables, it_json_var)) { - const char *key = ast_json_object_iter_key(it_json_var); - char buf[strlen(key) + 1]; - char *stripped_key; - struct ast_json *json_value; - const char *value; - struct ast_variable *new_var; - - strcpy(buf, key); - stripped_key = ast_strip(buf); - if (ast_strlen_zero(stripped_key)) { - ast_ari_response_error( - response, 400, "Bad Request", - "Variable names are required"); - return; - } - - json_value = ast_json_object_iter_value(it_json_var); - if (ast_json_typeof(json_value) != AST_JSON_STRING) { - ast_ari_response_error( - response, 400, "Bad Request", - "Variable values must be strings"); - return; - } - - value = ast_json_string_get(json_value); - if (!value) { - ast_ari_response_error( - response, 500, "Internal Server Error", - "Could not get string value from JSON string"); - return; - } - - new_var = ast_variable_new(stripped_key, value, ""); - if (!new_var) { - ast_ari_response_error( - response, 500, "Internal Server Error", - "Could not create internal variable"); - return; - } - - /* Append to the tail */ - var = ast_variable_list_append_hint(&variables, var, new_var); + if (!json_variables || ast_json_typeof(json_variables) != AST_JSON_OBJECT) { + ast_ari_response_error( + response, 400, "Bad Request", + "The 'variables' field must be a JSON object"); + return; + } + + if (json_to_ast_variables(response, json_variables, &variables, + &report_event_variables)) { + return; } - /* We loop twice to preserve variable state if something goes wrong. If something - * goes wrong in this loop, something went VERY wrong. - */ for (var = variables; var; var = var->next) { - if (stasis_app_control_set_channel_var(control, var->name, var->value)) { + int report_events = 0; + struct ast_variable *report_var; + char buf[strlen(var->name) + 1]; + char *variable; + strcpy(buf, var->name); + /* Strip whitespace from the variable name */ + variable = ast_strip(buf); + + /* See if the variable is in the report event list */ + for (report_var = report_event_variables; report_var; + report_var = report_var->next) { + if (!strcmp(report_var->name, var->name)) { + report_events = 1; + break; + } + } + + if (stasis_app_control_set_channel_var_reportable(control, variable, + var->value, report_events)) { ast_ari_response_error( response, 400, "Bad Request", "Failed to execute function"); @@ -2018,6 +2091,7 @@ void ast_ari_channels_create(struct ast_variable *headers, struct ast_ari_response *response) { RAII_VAR(struct ast_variable *, variables, NULL, ast_variables_destroy); + RAII_VAR(struct ast_variable *, report_event_variables, NULL, ast_variables_destroy); struct ast_assigned_ids assignedids; struct ari_channel_thread_data *chan_data; struct ast_channel_snapshot *snapshot; @@ -2035,8 +2109,9 @@ void ast_ari_channels_create(struct ast_variable *headers, ast_ari_channels_create_parse_body(args->variables, args); json_variables = ast_json_object_get(args->variables, "variables"); - if (json_variables - && json_to_ast_variables(response, json_variables, &variables)) { + if (json_variables && + json_to_ast_variables(response, json_variables, &variables, &report_event_variables)) { + ast_log(LOG_ERROR, "Failed to parse variables from request body for channel creation\n"); return; } } @@ -2156,6 +2231,18 @@ void ast_ari_channels_create(struct ast_variable *headers, if (variables) { ast_set_variables(chan_data->chan, variables); } + if (report_event_variables) { + struct ast_variable *var; + + for (var = report_event_variables; var; var = var->next) { + if (ast_channel_set_ari_var_reportable(chan_data->chan, var->name, 1)) { + ast_ari_response_alloc_failed(response); + ast_channel_cleanup(originator); + chan_data_destroy(chan_data); + return; + } + } + } ast_channel_cleanup(originator); @@ -2326,6 +2413,7 @@ void ast_ari_channels_rtpstatistics(struct ast_variable *headers, static int external_media_rtp_udp(struct ast_ari_channels_external_media_args *args, struct ast_variable *variables, + struct ast_variable *report_event_variables, struct ast_ari_response *response) { char *endpoint; @@ -2349,6 +2437,7 @@ static int external_media_rtp_udp(struct ast_ari_channels_external_media_args *a NULL, 0, variables, + report_event_variables, args->channel_id, NULL, NULL, @@ -2373,6 +2462,7 @@ static int external_media_rtp_udp(struct ast_ari_channels_external_media_args *a static int external_media_audiosocket_tcp(struct ast_ari_channels_external_media_args *args, struct ast_variable *variables, + struct ast_variable *report_event_variables, struct ast_ari_response *response) { char *endpoint; @@ -2395,6 +2485,7 @@ static int external_media_audiosocket_tcp(struct ast_ari_channels_external_media NULL, 0, variables, + report_event_variables, args->channel_id, NULL, NULL, @@ -2419,6 +2510,7 @@ static int external_media_audiosocket_tcp(struct ast_ari_channels_external_media static int external_media_websocket(struct ast_ari_channels_external_media_args *args, struct ast_variable *variables, + struct ast_variable *report_event_variables, struct ast_ari_response *response) { char *endpoint; @@ -2453,6 +2545,7 @@ static int external_media_websocket(struct ast_ari_channels_external_media_args NULL, 0, variables, + report_event_variables, args->channel_id, NULL, NULL, @@ -2482,6 +2575,7 @@ void ast_ari_channels_external_media(struct ast_variable *headers, struct ast_ari_channels_external_media_args *args, struct ast_ari_response *response) { RAII_VAR(struct ast_variable *, variables, NULL, ast_variables_destroy); + RAII_VAR(struct ast_variable *, report_event_variables, NULL, ast_variables_destroy); char *external_host; char *host = NULL; char *port = NULL; @@ -2495,7 +2589,8 @@ void ast_ari_channels_external_media(struct ast_variable *headers, ast_ari_channels_external_media_parse_body(args->variables, args); json_variables = ast_json_object_get(args->variables, "variables"); if (json_variables - && json_to_ast_variables(response, json_variables, &variables)) { + && json_to_ast_variables(response, json_variables, &variables, + &report_event_variables)) { return; } } @@ -2587,7 +2682,7 @@ void ast_ari_channels_external_media(struct ast_variable *headers, } if (strcasecmp(args->encapsulation, "rtp") == 0 && strcasecmp(args->transport, "udp") == 0) { - if (external_media_rtp_udp(args, variables, response)) { + if (external_media_rtp_udp(args, variables, report_event_variables, response)) { ast_ari_response_error( response, 500, "Internal Server Error", "An internal error prevented this request from being handled"); @@ -2595,13 +2690,13 @@ void ast_ari_channels_external_media(struct ast_variable *headers, } else if (strcasecmp(args->encapsulation, "audiosocket") == 0 && strcasecmp(args->transport, "tcp") == 0) { if (ast_strlen_zero(args->data)) { ast_ari_response_error(response, 400, "Bad Request", "data can not be empty"); - } else if (external_media_audiosocket_tcp(args, variables, response)) { + } else if (external_media_audiosocket_tcp(args, variables, report_event_variables, response)) { ast_ari_response_error( response, 500, "Internal Server Error", "An internal error prevented this request from being handled"); } } else if (strcasecmp(args->encapsulation, "none") == 0 && strcasecmp(args->transport, "websocket") == 0) { - if (external_media_websocket(args, variables, response)) { + if (external_media_websocket(args, variables, report_event_variables, response)) { ast_ari_response_error( response, 500, "Internal Server Error", "An internal error prevented this request from being handled"); diff --git a/res/ari/resource_channels.h b/res/ari/resource_channels.h index 5d4bf5833a..2be83a0e7e 100644 --- a/res/ari/resource_channels.h +++ b/res/ari/resource_channels.h @@ -70,7 +70,7 @@ struct ast_ari_channels_originate_args { const char *caller_id; /*! Timeout (in seconds) before giving up dialing, or -1 for no timeout. */ int timeout; - /*! The "variables" key in the body object holds variable key/value pairs to set on the channel on creation. Other keys in the body object are interpreted as query parameters. Ex. { "endpoint": "SIP/Alice", "variables": { "CALLERID(name)": "Alice" } } */ + /*! The "variables" key in the body object holds variable key/value pairs to set on the channel on creation. Each variable value may be either a string or an object containing "value" (string) and optional "report_events" (boolean) to include updates for that variable in channel events (defaults to false). Other keys in the body object are interpreted as query parameters. Ex. { "endpoint": "SIP/Alice", "variables": { "CALLERID(name)": "Alice", "Call_State": { "value": "WaitingForAgent", "report_events": true } } } */ struct ast_json *variables; /*! The unique id to assign the channel on creation. */ const char *channel_id; @@ -118,7 +118,7 @@ struct ast_ari_channels_create_args { const char *originator; /*! The format name capability list to use if originator is not specified. Ex. "ulaw,slin16". Format names can be found with "core show codecs". */ const char *formats; - /*! The "variables" key in the body object holds variable key/value pairs to set on the channel on creation. Other keys in the body object are interpreted as query parameters. Ex. { "endpoint": "SIP/Alice", "variables": { "CALLERID(name)": "Alice" } } */ + /*! The "variables" key in the body object holds variable key/value pairs to set on the channel on creation. Each variable value may be either a string or an object containing "value" (string) and optional "report_events" (boolean) to include updates for that variable in channel events (defaults to false). Other keys in the body object are interpreted as query parameters. Ex. { "endpoint": "SIP/Alice", "variables": { "CALLERID(name)": "Alice", "Call_State": { "value": "WaitingForAgent", "report_events": true } } } */ struct ast_json *variables; }; /*! @@ -175,7 +175,7 @@ struct ast_ari_channels_originate_with_id_args { const char *caller_id; /*! Timeout (in seconds) before giving up dialing, or -1 for no timeout. */ int timeout; - /*! The "variables" key in the body object holds variable key/value pairs to set on the channel on creation. Other keys in the body object are interpreted as query parameters. Ex. { "endpoint": "SIP/Alice", "variables": { "CALLERID(name)": "Alice" } } */ + /*! The "variables" key in the body object holds variable key/value pairs to set on the channel on creation. Each variable value may be either a string or an object containing "value" (string) and optional "report_events" (boolean) to include updates for that variable in channel events (defaults to false). Other keys in the body object are interpreted as query parameters. Ex. { "endpoint": "SIP/Alice", "variables": { "CALLERID(name)": "Alice", "Call_State": { "value": "WaitingForAgent", "report_events": true } } } */ struct ast_json *variables; /*! The unique id to assign the second channel when using local channels. */ const char *other_channel_id; @@ -706,6 +706,8 @@ struct ast_ari_channels_set_channel_var_args { const char *variable; /*! The value to set the variable to */ const char *value; + /*! Whether this variable should be included in channel events. Defaults to false. */ + int report_events; }; /*! * \brief Body parsing function for /channels/{channelId}/variable. @@ -760,7 +762,7 @@ void ast_ari_channels_get_channel_vars(struct ast_variable *headers, struct ast_ struct ast_ari_channels_set_channel_vars_args { /*! Channel's id */ const char *channel_id; - /*! The "variables" key in the body object holds variable key/value pairs to set on the channel. Ex. { "variables": { "CALLERID(name)": "Alice" } } */ + /*! The "variables" key in the body object holds variable key/value pairs to set on the channel. Each variable value may be either a string or an object containing "value" (string) and optional "report_events" (boolean) to include updates for that variable in channel events (defaults to false). Ex. { "variables": { "CALLERID(name)": "Alice", "Call_State": { "value": "WaitingForAgent", "report_events": true } } } */ struct ast_json *variables; }; /*! @@ -901,7 +903,7 @@ struct ast_ari_channels_external_media_args { const char *channel_id; /*! Stasis Application to place channel into */ const char *app; - /*! The "variables" key in the body object holds variable key/value pairs to set on the channel on creation. Other keys in the body object are interpreted as query parameters. Ex. { "endpoint": "SIP/Alice", "variables": { "CALLERID(name)": "Alice" } } */ + /*! The "variables" key in the body object holds variable key/value pairs to set on the channel on creation. Each variable value may be either a string or an object containing "value" (string) and optional "report_events" (boolean) to include updates for that variable in channel events (defaults to false). Other keys in the body object are interpreted as query parameters. Ex. { "endpoint": "SIP/Alice", "variables": { "CALLERID(name)": "Alice", "Call_State": { "value": "WaitingForAgent", "report_events": true } } } */ struct ast_json *variables; /*! Hostname/ip:port or websocket_client connection ID of external host. May be empty for a websocket server connection. */ const char *external_host; diff --git a/res/res_ari_bridges.c b/res/res_ari_bridges.c index 465f6e947a..ac236957eb 100644 --- a/res/res_ari_bridges.c +++ b/res/res_ari_bridges.c @@ -158,10 +158,7 @@ static void ast_ari_bridges_create_cb( } else {} } - if (ast_ari_bridges_create_parse_body(body, &args)) { - ast_ari_response_alloc_failed(response); - goto fin; - } + args.variables = body; ast_ari_bridges_create(headers, &args, response); #if defined(AST_DEVMODE) code = response->response_code; @@ -248,10 +245,7 @@ static void ast_ari_bridges_create_with_id_cb( } else {} } - if (ast_ari_bridges_create_with_id_parse_body(body, &args)) { - ast_ari_response_alloc_failed(response); - goto fin; - } + args.variables = body; ast_ari_bridges_create_with_id(headers, &args, response); #if defined(AST_DEVMODE) code = response->response_code; @@ -402,6 +396,413 @@ static void ast_ari_bridges_destroy_cb( } #endif /* AST_DEVMODE */ +fin: __attribute__((unused)) + return; +} +int ast_ari_bridges_get_bridge_var_parse_body( + struct ast_json *body, + struct ast_ari_bridges_get_bridge_var_args *args) +{ + struct ast_json *field; + /* Parse query parameters out of it */ + field = ast_json_object_get(body, "variable"); + if (field) { + args->variable = ast_json_string_get(field); + } + return 0; +} + +/*! + * \brief Parameter parsing callback for /bridges/{bridgeId}/variable. + * \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_bridges_get_bridge_var_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_bridges_get_bridge_var_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, "variable") == 0) { + args.variable = (i->value); + } else + {} + } + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "bridgeId") == 0) { + args.bridge_id = (i->value); + } else + {} + } + if (ast_ari_bridges_get_bridge_var_parse_body(body, &args)) { + ast_ari_response_alloc_failed(response); + goto fin; + } + ast_ari_bridges_get_bridge_var(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: /* Missing variable parameter. */ + case 404: /* Bridge or variable not found */ + case 409: /* Bridge not in a Stasis application */ + is_valid = 1; + break; + default: + if (200 <= code && code <= 299) { + is_valid = ast_ari_validate_variable( + response->message); + } else { + ast_log(LOG_ERROR, "Invalid error response %d for /bridges/{bridgeId}/variable\n", code); + is_valid = 0; + } + } + + if (!is_valid) { + ast_log(LOG_ERROR, "Response validation failed for /bridges/{bridgeId}/variable\n"); + ast_ari_response_error(response, 500, + "Internal Server Error", "Response validation failed"); + } +#endif /* AST_DEVMODE */ + +fin: __attribute__((unused)) + return; +} +int ast_ari_bridges_set_bridge_var_parse_body( + struct ast_json *body, + struct ast_ari_bridges_set_bridge_var_args *args) +{ + struct ast_json *field; + /* Parse query parameters out of it */ + field = ast_json_object_get(body, "variable"); + if (field) { + args->variable = ast_json_string_get(field); + } + field = ast_json_object_get(body, "value"); + if (field) { + args->value = ast_json_string_get(field); + } + field = ast_json_object_get(body, "report_events"); + if (field) { + args->report_events = ast_json_is_true(field); + } + return 0; +} + +/*! + * \brief Parameter parsing callback for /bridges/{bridgeId}/variable. + * \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_bridges_set_bridge_var_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_bridges_set_bridge_var_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, "variable") == 0) { + args.variable = (i->value); + } else + if (strcmp(i->name, "value") == 0) { + args.value = (i->value); + } else + if (strcmp(i->name, "report_events") == 0) { + args.report_events = ast_true(i->value); + } else + {} + } + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "bridgeId") == 0) { + args.bridge_id = (i->value); + } else + {} + } + if (ast_ari_bridges_set_bridge_var_parse_body(body, &args)) { + ast_ari_response_alloc_failed(response); + goto fin; + } + ast_ari_bridges_set_bridge_var(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: /* Missing variable parameter. */ + case 404: /* Bridge not found */ + case 409: /* Bridge not in a Stasis application */ + 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 /bridges/{bridgeId}/variable\n", code); + is_valid = 0; + } + } + + if (!is_valid) { + ast_log(LOG_ERROR, "Response validation failed for /bridges/{bridgeId}/variable\n"); + ast_ari_response_error(response, 500, + "Internal Server Error", "Response validation failed"); + } +#endif /* AST_DEVMODE */ + +fin: __attribute__((unused)) + return; +} +int ast_ari_bridges_get_bridge_vars_parse_body( + struct ast_json *body, + struct ast_ari_bridges_get_bridge_vars_args *args) +{ + struct ast_json *field; + /* Parse query parameters out of it */ + field = ast_json_object_get(body, "variables"); + if (field) { + /* If they were silly enough to both pass in a query param and a + * JSON body, free up the query value. + */ + ast_free(args->variables); + if (ast_json_typeof(field) == AST_JSON_ARRAY) { + /* Multiple param passed as array */ + size_t i; + args->variables_count = ast_json_array_size(field); + args->variables = ast_malloc(sizeof(*args->variables) * args->variables_count); + + if (!args->variables) { + return -1; + } + + for (i = 0; i < args->variables_count; ++i) { + args->variables[i] = ast_json_string_get(ast_json_array_get(field, i)); + } + } else { + /* Multiple param passed as single value */ + args->variables_count = 1; + args->variables = ast_malloc(sizeof(*args->variables) * args->variables_count); + if (!args->variables) { + return -1; + } + args->variables[0] = ast_json_string_get(field); + } + } + return 0; +} + +/*! + * \brief Parameter parsing callback for /bridges/{bridgeId}/variables. + * \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_bridges_get_bridge_vars_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_bridges_get_bridge_vars_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, "variables") == 0) { + /* Parse comma separated list */ + char *vals[MAX_VALS]; + size_t j; + + args.variables_parse = ast_strdup(i->value); + if (!args.variables_parse) { + ast_ari_response_alloc_failed(response); + goto fin; + } + + if (strlen(args.variables_parse) == 0) { + /* ast_app_separate_args can't handle "" */ + args.variables_count = 1; + vals[0] = args.variables_parse; + } else { + args.variables_count = ast_app_separate_args( + args.variables_parse, ',', vals, + ARRAY_LEN(vals)); + } + + if (args.variables_count == 0) { + ast_ari_response_alloc_failed(response); + goto fin; + } + + if (args.variables_count >= MAX_VALS) { + ast_ari_response_error(response, 400, + "Bad Request", + "Too many values for variables"); + goto fin; + } + + args.variables = ast_malloc(sizeof(*args.variables) * args.variables_count); + if (!args.variables) { + ast_ari_response_alloc_failed(response); + goto fin; + } + + for (j = 0; j < args.variables_count; ++j) { + args.variables[j] = (vals[j]); + } + } else + {} + } + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "bridgeId") == 0) { + args.bridge_id = (i->value); + } else + {} + } + if (ast_ari_bridges_get_bridge_vars_parse_body(body, &args)) { + ast_ari_response_alloc_failed(response); + goto fin; + } + ast_ari_bridges_get_bridge_vars(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: /* Missing variables parameter. */ + case 404: /* Bridge or variable not found */ + case 409: /* Bridge not in a Stasis application */ + is_valid = 1; + break; + default: + if (200 <= code && code <= 299) { + is_valid = ast_ari_validate_variables( + response->message); + } else { + ast_log(LOG_ERROR, "Invalid error response %d for /bridges/{bridgeId}/variables\n", code); + is_valid = 0; + } + } + + if (!is_valid) { + ast_log(LOG_ERROR, "Response validation failed for /bridges/{bridgeId}/variables\n"); + ast_ari_response_error(response, 500, + "Internal Server Error", "Response validation failed"); + } +#endif /* AST_DEVMODE */ + +fin: __attribute__((unused)) + ast_free(args.variables_parse); + ast_free(args.variables); + return; +} +int ast_ari_bridges_set_bridge_vars_parse_body( + struct ast_json *body, + struct ast_ari_bridges_set_bridge_vars_args *args) +{ + /* Parse query parameters out of it */ + return 0; +} + +/*! + * \brief Parameter parsing callback for /bridges/{bridgeId}/variables. + * \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_bridges_set_bridge_vars_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_bridges_set_bridge_vars_args args = {}; + struct ast_variable *i; +#if defined(AST_DEVMODE) + int is_valid; + int code; +#endif /* AST_DEVMODE */ + + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "bridgeId") == 0) { + args.bridge_id = (i->value); + } else + {} + } + args.variables = body; + ast_ari_bridges_set_bridge_vars(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: /* Missing variables parameter. */ + case 404: /* Bridge not found */ + case 409: /* Bridge not in a Stasis application */ + 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 /bridges/{bridgeId}/variables\n", code); + is_valid = 0; + } + } + + if (!is_valid) { + ast_log(LOG_ERROR, "Response validation failed for /bridges/{bridgeId}/variables\n"); + ast_ari_response_error(response, 500, + "Internal Server Error", "Response validation failed"); + } +#endif /* AST_DEVMODE */ + fin: __attribute__((unused)) return; } @@ -1515,6 +1916,26 @@ fin: __attribute__((unused)) return; } +/*! \brief REST handler for /api-docs/bridges.json */ +static struct stasis_rest_handlers bridges_bridgeId_variable = { + .path_segment = "variable", + .callbacks = { + [AST_HTTP_GET] = ast_ari_bridges_get_bridge_var_cb, + [AST_HTTP_POST] = ast_ari_bridges_set_bridge_var_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/bridges.json */ +static struct stasis_rest_handlers bridges_bridgeId_variables = { + .path_segment = "variables", + .callbacks = { + [AST_HTTP_GET] = ast_ari_bridges_get_bridge_vars_cb, + [AST_HTTP_POST] = ast_ari_bridges_set_bridge_vars_cb, + }, + .num_children = 0, + .children = { } +}; /*! \brief REST handler for /api-docs/bridges.json */ static struct stasis_rest_handlers bridges_bridgeId_addChannel = { .path_segment = "addChannel", @@ -1599,8 +2020,8 @@ static struct stasis_rest_handlers bridges_bridgeId = { [AST_HTTP_GET] = ast_ari_bridges_get_cb, [AST_HTTP_DELETE] = ast_ari_bridges_destroy_cb, }, - .num_children = 6, - .children = { &bridges_bridgeId_addChannel,&bridges_bridgeId_removeChannel,&bridges_bridgeId_videoSource,&bridges_bridgeId_moh,&bridges_bridgeId_play,&bridges_bridgeId_record, } + .num_children = 8, + .children = { &bridges_bridgeId_variable,&bridges_bridgeId_variables,&bridges_bridgeId_addChannel,&bridges_bridgeId_removeChannel,&bridges_bridgeId_videoSource,&bridges_bridgeId_moh,&bridges_bridgeId_play,&bridges_bridgeId_record, } }; /*! \brief REST handler for /api-docs/bridges.json */ static struct stasis_rest_handlers bridges = { diff --git a/res/res_ari_channels.c b/res/res_ari_channels.c index 3d109e101c..d07631df46 100644 --- a/res/res_ari_channels.c +++ b/res/res_ari_channels.c @@ -2484,6 +2484,10 @@ int ast_ari_channels_set_channel_var_parse_body( if (field) { args->value = ast_json_string_get(field); } + field = ast_json_object_get(body, "report_events"); + if (field) { + args->report_events = ast_json_is_true(field); + } return 0; } @@ -2515,6 +2519,9 @@ static void ast_ari_channels_set_channel_var_cb( if (strcmp(i->name, "value") == 0) { args.value = (i->value); } else + if (strcmp(i->name, "report_events") == 0) { + args.report_events = ast_true(i->value); + } else {} } for (i = path_vars; i; i = i->next) { diff --git a/res/res_stasis.c b/res/res_stasis.c index 6912f3a9c5..634a2e4649 100644 --- a/res/res_stasis.c +++ b/res/res_stasis.c @@ -920,6 +920,26 @@ void stasis_app_bridge_destroy(const char *bridge_id) ast_bridge_destroy(bridge, 0); } +int stasis_app_bridge_set_var_reportable(const char *bridge_id, const char *variable, + const char *value, int report_events) +{ + RAII_VAR(struct ast_bridge *, bridge, stasis_app_bridge_find_by_id(bridge_id), ao2_cleanup); + + if (!bridge) { + return -1; + } + + ast_bridge_lock(bridge); + if (ast_bridge_set_variable(bridge, variable, value, report_events)) { + ast_bridge_unlock(bridge); + return -1; + } + ast_bridge_publish_state(bridge); + ast_bridge_unlock(bridge); + + return 0; +} + struct replace_channel_store { struct ast_channel_snapshot *snapshot; char *app; diff --git a/res/stasis/control.c b/res/stasis/control.c index 52058ec0c7..ae99f9496e 100644 --- a/res/stasis/control.c +++ b/res/stasis/control.c @@ -728,6 +728,8 @@ struct chanvar { char *name; /*! Value of variable to set. If unsetting, this will be NULL */ char *value; + /*! Whether this variable should be included in channel events */ + unsigned int report_events; }; static void free_chanvar(void *data) @@ -744,12 +746,21 @@ static int app_control_set_channel_var(struct stasis_app_control *control, { struct chanvar *var = data; + if (ast_channel_set_ari_var_reportable(control->channel, var->name, var->report_events)) { + return -1; + } + pbx_builtin_setvar_helper(control->channel, var->name, var->value); return 0; } int stasis_app_control_set_channel_var(struct stasis_app_control *control, const char *variable, const char *value) +{ + return stasis_app_control_set_channel_var_reportable(control, variable, value, 0); +} + +int stasis_app_control_set_channel_var_reportable(struct stasis_app_control *control, const char *variable, const char *value, int report_events) { struct chanvar *var; @@ -773,9 +784,9 @@ int stasis_app_control_set_channel_var(struct stasis_app_control *control, const } } - stasis_app_send_command_async(control, app_control_set_channel_var, var, free_chanvar); + var->report_events = report_events ? 1 : 0; - return 0; + return stasis_app_send_command(control, app_control_set_channel_var, var, free_chanvar); } static int app_control_hold(struct stasis_app_control *control, diff --git a/rest-api/api-docs/bridges.json b/rest-api/api-docs/bridges.json index ae5ee56fec..c0947cb088 100644 --- a/rest-api/api-docs/bridges.json +++ b/rest-api/api-docs/bridges.json @@ -60,6 +60,14 @@ "required": false, "allowMultiple": false, "dataType": "string" + }, + { + "name": "variables", + "description": "The \"variables\" key in the body object holds variable key/value pairs to set on the bridge on creation. Each variable is an object containing \"value\" (string) and optional \"report_events\" (boolean) to include updates for that variable in bridge events (defaults to false). Ex. { \"name\": \"SupportBridge\", \"variables\": { \"Bridge_State\": { \"value\": \"WaitingForAgent\", \"report_events\": true } } }", + "paramType": "body", + "required": false, + "dataType": "containers", + "allowMultiple": false } ], "errorResponses": [ @@ -108,6 +116,14 @@ "required": false, "allowMultiple": false, "dataType": "string" + }, + { + "name": "variables", + "description": "The \"variables\" key in the body object holds variable key/value pairs to set on the bridge on creation. Each variable is an object containing \"value\" (string) and optional \"report_events\" (boolean) to include updates for that variable in bridge events (defaults to false). Ex. { \"name\": \"SupportBridge\", \"variables\": { \"Bridge_State\": { \"value\": \"WaitingForAgent\", \"report_events\": true } } }", + "paramType": "body", + "required": false, + "dataType": "containers", + "allowMultiple": false } ], "errorResponses": [ @@ -170,6 +186,207 @@ } ] }, + { + "path": "/bridges/{bridgeId}/variable", + "description": "Variables on a bridge", + "operations": [ + { + "httpMethod": "GET", + "since": [ + "20.20.0", + "22.10.0", + "23.4.0" + ], + "summary": "Get the value of a bridge variable or function.", + "nickname": "getBridgeVar", + "responseClass": "Variable", + "parameters": [ + { + "name": "bridgeId", + "description": "Bridge's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "variable", + "description": "The bridge variable or function to get", + "paramType": "query", + "required": true, + "allowMultiple": false, + "dataType": "string" + } + ], + "errorResponses": [ + { + "code": 400, + "reason": "Missing variable parameter." + }, + { + "code": 404, + "reason": "Bridge or variable not found" + }, + { + "code": 409, + "reason": "Bridge not in a Stasis application" + } + ] + }, + { + "httpMethod": "POST", + "since": [ + "20.20.0", + "22.10.0", + "23.4.0" + ], + "summary": "Set the value of a bridge variable or function.", + "nickname": "setBridgeVar", + "responseClass": "void", + "parameters": [ + { + "name": "bridgeId", + "description": "Bridge's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "variable", + "description": "The bridge variable or function to set", + "paramType": "query", + "required": true, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "value", + "description": "The value to set the variable to", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "report_events", + "description": "Whether this variable should be included in bridge events. Defaults to false.", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "boolean", + "defaultValue": false + } + ], + "errorResponses": [ + { + "code": 400, + "reason": "Missing variable parameter." + }, + { + "code": 404, + "reason": "Bridge not found" + }, + { + "code": 409, + "reason": "Bridge not in a Stasis application" + } + ] + } + ] + }, + { + "path": "/bridges/{bridgeId}/variables", + "description": "Multiple variables on a bridge", + "operations": [ + { + "httpMethod": "GET", + "since": [ + "20.20.0", + "22.10.0", + "23.4.0" + ], + "summary": "Get the value of multiple bridge variables or functions.", + "nickname": "getBridgeVars", + "responseClass": "Variables", + "parameters": [ + { + "name": "bridgeId", + "description": "Bridge's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "variables", + "description": "The bridge variables or functions to get", + "paramType": "query", + "required": true, + "allowMultiple": true, + "dataType": "string" + } + ], + "errorResponses": [ + { + "code": 400, + "reason": "Missing variables parameter." + }, + { + "code": 404, + "reason": "Bridge or variable not found" + }, + { + "code": 409, + "reason": "Bridge not in a Stasis application" + } + ] + }, + { + "httpMethod": "POST", + "since": [ + "20.20.0", + "22.10.0", + "23.4.0" + ], + "summary": "Set the values of multiple bridge variables or functions.", + "nickname": "setBridgeVars", + "responseClass": "void", + "parameters": [ + { + "name": "bridgeId", + "description": "Bridge's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "variables", + "description": "The \"variables\" key in the body object holds variable key/value pairs to set on the bridge. Each variable value may be either a string or an object containing \"value\" (string) and optional \"report_events\" (boolean) to include updates for that variable in bridge events (defaults to false). Ex. { \"variables\": { \"Bridge_State\": \"WaitingForAgent\", \"Support_Level\": { \"value\": \"Premium\", \"report_events\": true } } }", + "paramType": "body", + "required": true, + "dataType": "containers", + "allowMultiple": false + } + ], + "errorResponses": [ + { + "code": 400, + "reason": "Missing variables parameter." + }, + { + "code": 404, + "reason": "Bridge not found" + }, + { + "code": 409, + "reason": "Bridge not in a Stasis application" + } + ] + } + ] + }, { "path": "/bridges/{bridgeId}/addChannel", "description": "Add a channel to a bridge", @@ -857,6 +1074,11 @@ "required": true, "type": "Date", "description": "Timestamp when bridge was created" + }, + "bridgevars": { + "required": false, + "type": "object", + "description": "Bridge variables" } } } diff --git a/rest-api/api-docs/channels.json b/rest-api/api-docs/channels.json index 517ceb7602..22ced77013 100644 --- a/rest-api/api-docs/channels.json +++ b/rest-api/api-docs/channels.json @@ -114,7 +114,7 @@ }, { "name": "variables", - "description": "The \"variables\" key in the body object holds variable key/value pairs to set on the channel on creation. Other keys in the body object are interpreted as query parameters. Ex. { \"endpoint\": \"SIP/Alice\", \"variables\": { \"CALLERID(name)\": \"Alice\" } }", + "description": "The \"variables\" key in the body object holds variable key/value pairs to set on the channel on creation. Each variable value may be either a string or an object containing \"value\" (string) and optional \"report_events\" (boolean) to include updates for that variable in channel events (defaults to false). Other keys in the body object are interpreted as query parameters. Ex. { \"endpoint\": \"SIP/Alice\", \"variables\": { \"CALLERID(name)\": \"Alice\", \"Call_State\": { \"value\": \"WaitingForAgent\", \"report_events\": true } } }", "paramType": "body", "required": false, "dataType": "containers", @@ -237,7 +237,7 @@ }, { "name": "variables", - "description": "The \"variables\" key in the body object holds variable key/value pairs to set on the channel on creation. Other keys in the body object are interpreted as query parameters. Ex. { \"endpoint\": \"SIP/Alice\", \"variables\": { \"CALLERID(name)\": \"Alice\" } }", + "description": "The \"variables\" key in the body object holds variable key/value pairs to set on the channel on creation. Each variable value may be either a string or an object containing \"value\" (string) and optional \"report_events\" (boolean) to include updates for that variable in channel events (defaults to false). Other keys in the body object are interpreted as query parameters. Ex. { \"endpoint\": \"SIP/Alice\", \"variables\": { \"CALLERID(name)\": \"Alice\", \"Call_State\": { \"value\": \"WaitingForAgent\", \"report_events\": true } } }", "paramType": "body", "required": false, "dataType": "containers", @@ -375,7 +375,7 @@ }, { "name": "variables", - "description": "The \"variables\" key in the body object holds variable key/value pairs to set on the channel on creation. Other keys in the body object are interpreted as query parameters. Ex. { \"endpoint\": \"SIP/Alice\", \"variables\": { \"CALLERID(name)\": \"Alice\" } }", + "description": "The \"variables\" key in the body object holds variable key/value pairs to set on the channel on creation. Each variable value may be either a string or an object containing \"value\" (string) and optional \"report_events\" (boolean) to include updates for that variable in channel events (defaults to false). Other keys in the body object are interpreted as query parameters. Ex. { \"endpoint\": \"SIP/Alice\", \"variables\": { \"CALLERID(name)\": \"Alice\", \"Call_State\": { \"value\": \"WaitingForAgent\", \"report_events\": true } } }", "paramType": "body", "required": false, "dataType": "containers", @@ -1601,6 +1601,15 @@ "required": false, "allowMultiple": false, "dataType": "string" + }, + { + "name": "report_events", + "description": "Whether this variable should be included in channel events. Defaults to false.", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "boolean", + "defaultValue": false } ], "errorResponses": [ @@ -1688,9 +1697,9 @@ }, { "name": "variables", - "description": "The \"variables\" key in the body object holds variable key/value pairs to set on the channel. Ex. { \"variables\": { \"CALLERID(name)\": \"Alice\" } }", + "description": "The \"variables\" key in the body object holds variable key/value pairs to set on the channel. Each variable value may be either a string or an object containing \"value\" (string) and optional \"report_events\" (boolean) to include updates for that variable in channel events (defaults to false). Ex. { \"variables\": { \"CALLERID(name)\": \"Alice\", \"Call_State\": { \"value\": \"WaitingForAgent\", \"report_events\": true } } }", "paramType": "body", - "required": false, + "required": true, "dataType": "containers", "allowMultiple": false } @@ -2026,7 +2035,7 @@ }, { "name": "variables", - "description": "The \"variables\" key in the body object holds variable key/value pairs to set on the channel on creation. Other keys in the body object are interpreted as query parameters. Ex. { \"endpoint\": \"SIP/Alice\", \"variables\": { \"CALLERID(name)\": \"Alice\" } }", + "description": "The \"variables\" key in the body object holds variable key/value pairs to set on the channel on creation. Each variable value may be either a string or an object containing \"value\" (string) and optional \"report_events\" (boolean) to include updates for that variable in channel events (defaults to false). Other keys in the body object are interpreted as query parameters. Ex. { \"endpoint\": \"SIP/Alice\", \"variables\": { \"CALLERID(name)\": \"Alice\", \"Call_State\": { \"value\": \"WaitingForAgent\", \"report_events\": true } } }", "paramType": "body", "required": false, "dataType": "containers",