<p>Kevin Harwell has uploaded this change for <strong>review</strong>.</p><p><a href="https://gerrit.asterisk.org/10978">View Change</a></p><pre style="font-family: monospace,monospace; white-space: pre-wrap;">ARI event type filtering<br><br>Event type filtering is now enabled, and configurable per application. An app is<br>now able to specify which events are sent to the application by configuring an<br>allowed and/or disallowed list(s). This can be done by issuing the following:<br><br>PUT /applications/{applicationName}/eventFilter<br><br>And then enumerating the allowed/disallowed event types as a body parameter.<br><br>ASTERISK-28106<br><br>Change-Id: I9671ba1fcdb3b6c830b553d4c5365aed5d588d5b<br>---<br>M CHANGES<br>M include/asterisk/stasis_app.h<br>M res/ari/ari_model_validators.c<br>M res/ari/ari_model_validators.h<br>M res/ari/resource_applications.c<br>M res/ari/resource_applications.h<br>M res/ari/resource_events.c<br>M res/res_ari_applications.c<br>M res/res_stasis.c<br>M res/stasis/app.c<br>M rest-api/api-docs/applications.json<br>11 files changed, 401 insertions(+), 5 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/78/10978/1</pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/CHANGES b/CHANGES</span><br><span>index 26a3d49..6083b1a 100644</span><br><span>--- a/CHANGES</span><br><span>+++ b/CHANGES</span><br><span>@@ -9,6 +9,18 @@</span><br><span> ==============================================================================</span><br><span> </span><br><span> ------------------------------------------------------------------------------</span><br><span style="color: hsl(120, 100%, 40%);">+--- Functionality changes from Asterisk 13.25.0 to Asterisk 13.26.0 ----------</span><br><span style="color: hsl(120, 100%, 40%);">+------------------------------------------------------------------------------</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ARI</span><br><span style="color: hsl(120, 100%, 40%);">+------------------</span><br><span style="color: hsl(120, 100%, 40%);">+ * Application event filtering is now supported. An application can now specify</span><br><span style="color: hsl(120, 100%, 40%);">+ an "allowed" and/or "disallowed" list(s) of event types. Only those types</span><br><span style="color: hsl(120, 100%, 40%);">+ indicated in the "allowed" list are sent to the application. Conversely, any</span><br><span style="color: hsl(120, 100%, 40%);">+ types defined in the "disallowed" list are not sent to the application. Note</span><br><span style="color: hsl(120, 100%, 40%);">+ that if a type is specified in both lists "disallowed" takes precedence.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+------------------------------------------------------------------------------</span><br><span> --- Functionality changes from Asterisk 13.24.0 to Asterisk 13.25.0 ----------</span><br><span> ------------------------------------------------------------------------------</span><br><span> </span><br><span>diff --git a/include/asterisk/stasis_app.h b/include/asterisk/stasis_app.h</span><br><span>index 90a7ea3..37a4bf0 100644</span><br><span>--- a/include/asterisk/stasis_app.h</span><br><span>+++ b/include/asterisk/stasis_app.h</span><br><span>@@ -150,6 +150,16 @@</span><br><span> /*!</span><br><span> * \brief Return the JSON representation of a Stasis application.</span><br><span> *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param app The application.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \return JSON representation of app with given name.</span><br><span style="color: hsl(120, 100%, 40%);">+ * \return \c NULL on error.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+struct ast_json *stasis_app_object_to_json(struct stasis_app *app);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Return the JSON representation of a Stasis application.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span> * \param app_name Name of the application.</span><br><span> *</span><br><span> * \return JSON representation of app with given name.</span><br><span>@@ -939,6 +949,41 @@</span><br><span> */</span><br><span> void stasis_app_to_cli(const struct stasis_app *app, struct ast_cli_args *a);</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Convert and add the app's event type filter(s) to the given json object.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param app The application</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param json The json object to add the filter data to</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \return The given json object</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+struct ast_json *stasis_app_event_filter_to_json(struct stasis_app *app, struct ast_json *json);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Set the application's event type filter</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param app The application</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param filter The allowed and/or disallowed event filter</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \return 0 if successfully set</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+int stasis_app_event_filter_set(struct stasis_app *app, struct ast_json *filter);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Check if the given event should be filtered.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * Attempts first to find the event in the application's disallowed events list.</span><br><span style="color: hsl(120, 100%, 40%);">+ * If found then the event won't be sent to the remote. If not found in the</span><br><span style="color: hsl(120, 100%, 40%);">+ * disallowed list then a search is done to see if it can be found in the allowed</span><br><span style="color: hsl(120, 100%, 40%);">+ * list. If found the event message is sent, otherwise it is not sent.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param app_name The application name</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param event The event to check</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \return True if allowed, false otherwise</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+int stasis_app_event_allowed(const char *app_name, struct ast_json *event);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> /*! @} */</span><br><span> </span><br><span> #endif /* _ASTERISK_STASIS_APP_H */</span><br><span>diff --git a/res/ari/ari_model_validators.c b/res/ari/ari_model_validators.c</span><br><span>index 673864a..d22d705 100644</span><br><span>--- a/res/ari/ari_model_validators.c</span><br><span>+++ b/res/ari/ari_model_validators.c</span><br><span>@@ -6420,13 +6420,26 @@</span><br><span> {</span><br><span> int res = 1;</span><br><span> struct ast_json_iter *iter;</span><br><span style="color: hsl(120, 100%, 40%);">+ int has_allowed_events = 0;</span><br><span> int has_bridge_ids = 0;</span><br><span> int has_channel_ids = 0;</span><br><span> int has_device_names = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ int has_disallowed_events = 0;</span><br><span> int has_endpoint_ids = 0;</span><br><span> int has_name = 0;</span><br><span> </span><br><span> for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ if (strcmp("allowed_events", ast_json_object_iter_key(iter)) == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+ int prop_is_valid;</span><br><span style="color: hsl(120, 100%, 40%);">+ has_allowed_events = 1;</span><br><span style="color: hsl(120, 100%, 40%);">+ prop_is_valid = ast_ari_validate_list(</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_json_object_iter_value(iter),</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_ari_validate_object);</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!prop_is_valid) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "ARI Application field allowed_events failed validation\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ res = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ } else</span><br><span> if (strcmp("bridge_ids", ast_json_object_iter_key(iter)) == 0) {</span><br><span> int prop_is_valid;</span><br><span> has_bridge_ids = 1;</span><br><span>@@ -6460,6 +6473,17 @@</span><br><span> res = 0;</span><br><span> }</span><br><span> } else</span><br><span style="color: hsl(120, 100%, 40%);">+ if (strcmp("disallowed_events", ast_json_object_iter_key(iter)) == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+ int prop_is_valid;</span><br><span style="color: hsl(120, 100%, 40%);">+ has_disallowed_events = 1;</span><br><span style="color: hsl(120, 100%, 40%);">+ prop_is_valid = ast_ari_validate_list(</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_json_object_iter_value(iter),</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_ari_validate_object);</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!prop_is_valid) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "ARI Application field disallowed_events failed validation\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ res = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ } else</span><br><span> if (strcmp("endpoint_ids", ast_json_object_iter_key(iter)) == 0) {</span><br><span> int prop_is_valid;</span><br><span> has_endpoint_ids = 1;</span><br><span>@@ -6489,6 +6513,11 @@</span><br><span> }</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+ if (!has_allowed_events) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "ARI Application missing required field allowed_events\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ res = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> if (!has_bridge_ids) {</span><br><span> ast_log(LOG_ERROR, "ARI Application missing required field bridge_ids\n");</span><br><span> res = 0;</span><br><span>@@ -6504,6 +6533,11 @@</span><br><span> res = 0;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+ if (!has_disallowed_events) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "ARI Application missing required field disallowed_events\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ res = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> if (!has_endpoint_ids) {</span><br><span> ast_log(LOG_ERROR, "ARI Application missing required field endpoint_ids\n");</span><br><span> res = 0;</span><br><span>diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h</span><br><span>index 64dd88a..53a6dbb 100644</span><br><span>--- a/res/ari/ari_model_validators.h</span><br><span>+++ b/res/ari/ari_model_validators.h</span><br><span>@@ -1801,9 +1801,11 @@</span><br><span> * - endpoint: Endpoint</span><br><span> * - message: TextMessage (required)</span><br><span> * Application</span><br><span style="color: hsl(120, 100%, 40%);">+ * - allowed_events: List[object] (required)</span><br><span> * - bridge_ids: List[string] (required)</span><br><span> * - channel_ids: List[string] (required)</span><br><span> * - device_names: List[string] (required)</span><br><span style="color: hsl(120, 100%, 40%);">+ * - disallowed_events: List[object] (required)</span><br><span> * - endpoint_ids: List[string] (required)</span><br><span> * - name: string (required)</span><br><span> */</span><br><span>diff --git a/res/ari/resource_applications.c b/res/ari/resource_applications.c</span><br><span>index 85a631a..061d6b4 100644</span><br><span>--- a/res/ari/resource_applications.c</span><br><span>+++ b/res/ari/resource_applications.c</span><br><span>@@ -170,3 +170,23 @@</span><br><span> "Error processing request");</span><br><span> }</span><br><span> }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+void ast_ari_applications_filter(struct ast_variable *headers,</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_ari_applications_filter_args *args,</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_ari_response *response)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ struct stasis_app *app = stasis_app_get_by_name(args->application_name);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!app) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_ari_response_error(response, 404, "Not Found", "Application not found");</span><br><span style="color: hsl(120, 100%, 40%);">+ return;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (stasis_app_event_filter_set(app, args->filter)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_ari_response_error(response, 400, "Bad Request", "Invalid format definition");</span><br><span style="color: hsl(120, 100%, 40%);">+ } else {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_ari_response_ok(response, stasis_app_object_to_json(app));</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ ao2_ref(app, -1);</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span>diff --git a/res/ari/resource_applications.h b/res/ari/resource_applications.h</span><br><span>index be62e9d..23a9b9b 100644</span><br><span>--- a/res/ari/resource_applications.h</span><br><span>+++ b/res/ari/resource_applications.h</span><br><span>@@ -127,5 +127,33 @@</span><br><span> * \param[out] response HTTP response</span><br><span> */</span><br><span> void ast_ari_applications_unsubscribe(struct ast_variable *headers, struct ast_ari_applications_unsubscribe_args *args, struct ast_ari_response *response);</span><br><span style="color: hsl(120, 100%, 40%);">+/*! Argument struct for ast_ari_applications_filter() */</span><br><span style="color: hsl(120, 100%, 40%);">+struct ast_ari_applications_filter_args {</span><br><span style="color: hsl(120, 100%, 40%);">+ /*! Application's name */</span><br><span style="color: hsl(120, 100%, 40%);">+ const char *application_name;</span><br><span style="color: hsl(120, 100%, 40%);">+ /*! Specify which event types to allow/disallow */</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_json *filter;</span><br><span style="color: hsl(120, 100%, 40%);">+};</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Body parsing function for /applications/{applicationName}/eventFilter.</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param body The JSON body from which to parse parameters.</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param[out] args The args structure to parse into.</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval zero on success</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval non-zero on failure</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+int ast_ari_applications_filter_parse_body(</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_json *body,</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_ari_applications_filter_args *args);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Filter application events types.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * Allowed and/or disallowed event type filtering can be done. The body (parameter) should specify a JSON key/value object that describes the type of event filtering needed. One, or both of the following keys can be designated:<br /><br />"allowed" - Specifies an allowed list of event types<br />"disallowed" - Specifies a disallowed list of event types<br /><br />Further, each of those key's value should be a JSON array that holds zero, or more JSON key/value objects. Each of these objects must contain the following key with an associated value:<br /><br />"type" - The type name of the event to filter<br /><br />The value must be the string name (case sensitive) of the event type that needs filtering. For example:<br /><br />{ "allowed": [ { "type": "StasisStart" }, { "type": "StasisEnd" } ] }<br /><br />As this specifies only an allowed list, then only those two event type messages are sent to the application. No other event messages are sent.<br /><br />The following rules apply:<br /><br />* If the body is empty, both the allowed and disallowed filters are set empty.<br />* If both list types are given then both are set to their respective values (note, specifying an empty array for a given type sets that type to empty).<br />* If only one list type is given then only that type is set. The other type is not updated.<br />* An empty "allowed" list means all events are allowed.<br />* An empty "disallowed" list means no events are disallowed.<br />* Disallowed events take precedence over allowed events if the event type is specified in both lists.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param headers HTTP headers</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param args Swagger parameters</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param[out] response HTTP response</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+void ast_ari_applications_filter(struct ast_variable *headers, struct ast_ari_applications_filter_args *args, struct ast_ari_response *response);</span><br><span> </span><br><span> #endif /* _ASTERISK_RESOURCE_APPLICATIONS_H */</span><br><span>diff --git a/res/ari/resource_events.c b/res/ari/resource_events.c</span><br><span>index fb996f7..20d9eaa 100644</span><br><span>--- a/res/ari/resource_events.c</span><br><span>+++ b/res/ari/resource_events.c</span><br><span>@@ -143,7 +143,7 @@</span><br><span> }</span><br><span> </span><br><span> ao2_lock(session);</span><br><span style="color: hsl(0, 100%, 40%);">- if (session->ws_session) {</span><br><span style="color: hsl(120, 100%, 40%);">+ if (session->ws_session && stasis_app_event_allowed(app_name, message)) {</span><br><span> if (stasis_app_get_debug_by_name(app_name)) {</span><br><span> char *str = ast_json_dump_string_format(message, ast_ari_json_format());</span><br><span> </span><br><span>diff --git a/res/res_ari_applications.c b/res/res_ari_applications.c</span><br><span>index c70b926..4d5be3f 100644</span><br><span>--- a/res/res_ari_applications.c</span><br><span>+++ b/res/res_ari_applications.c</span><br><span>@@ -461,6 +461,74 @@</span><br><span> ast_free(args.event_source);</span><br><span> return;</span><br><span> }</span><br><span style="color: hsl(120, 100%, 40%);">+int ast_ari_applications_filter_parse_body(</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_json *body,</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_ari_applications_filter_args *args)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ /* Parse query parameters out of it */</span><br><span style="color: hsl(120, 100%, 40%);">+ return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Parameter parsing callback for /applications/{applicationName}/eventFilter.</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param get_params GET parameters in the HTTP request.</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param path_vars Path variables extracted from the request.</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param headers HTTP headers.</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param[out] response Response to the HTTP request.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+static void ast_ari_applications_filter_cb(</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_tcptls_session_instance *ser,</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_variable *get_params, struct ast_variable *path_vars,</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_ari_applications_filter_args args = {};</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_variable *i;</span><br><span style="color: hsl(120, 100%, 40%);">+#if defined(AST_DEVMODE)</span><br><span style="color: hsl(120, 100%, 40%);">+ int is_valid;</span><br><span style="color: hsl(120, 100%, 40%);">+ int code;</span><br><span style="color: hsl(120, 100%, 40%);">+#endif /* AST_DEVMODE */</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ for (i = path_vars; i; i = i->next) {</span><br><span style="color: hsl(120, 100%, 40%);">+ if (strcmp(i->name, "applicationName") == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+ args.application_name = (i->value);</span><br><span style="color: hsl(120, 100%, 40%);">+ } else</span><br><span style="color: hsl(120, 100%, 40%);">+ {}</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ args.filter = body;</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_ari_applications_filter(headers, &args, response);</span><br><span style="color: hsl(120, 100%, 40%);">+#if defined(AST_DEVMODE)</span><br><span style="color: hsl(120, 100%, 40%);">+ code = response->response_code;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ switch (code) {</span><br><span style="color: hsl(120, 100%, 40%);">+ case 0: /* Implementation is still a stub, or the code wasn't set */</span><br><span style="color: hsl(120, 100%, 40%);">+ is_valid = response->message == NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span style="color: hsl(120, 100%, 40%);">+ case 500: /* Internal Server Error */</span><br><span style="color: hsl(120, 100%, 40%);">+ case 501: /* Not Implemented */</span><br><span style="color: hsl(120, 100%, 40%);">+ case 400: /* Bad request. */</span><br><span style="color: hsl(120, 100%, 40%);">+ case 404: /* Application does not exist. */</span><br><span style="color: hsl(120, 100%, 40%);">+ is_valid = 1;</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span style="color: hsl(120, 100%, 40%);">+ default:</span><br><span style="color: hsl(120, 100%, 40%);">+ if (200 <= code && code <= 299) {</span><br><span style="color: hsl(120, 100%, 40%);">+ is_valid = ast_ari_validate_application(</span><br><span style="color: hsl(120, 100%, 40%);">+ response->message);</span><br><span style="color: hsl(120, 100%, 40%);">+ } else {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "Invalid error response %d for /applications/{applicationName}/eventFilter\n", code);</span><br><span style="color: hsl(120, 100%, 40%);">+ is_valid = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!is_valid) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "Response validation failed for /applications/{applicationName}/eventFilter\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_ari_response_error(response, 500,</span><br><span style="color: hsl(120, 100%, 40%);">+ "Internal Server Error", "Response validation failed");</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+#endif /* AST_DEVMODE */</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+fin: __attribute__((unused))</span><br><span style="color: hsl(120, 100%, 40%);">+ return;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span> </span><br><span> /*! \brief REST handler for /api-docs/applications.json */</span><br><span> static struct stasis_rest_handlers applications_applicationName_subscription = {</span><br><span>@@ -473,14 +541,23 @@</span><br><span> .children = { }</span><br><span> };</span><br><span> /*! \brief REST handler for /api-docs/applications.json */</span><br><span style="color: hsl(120, 100%, 40%);">+static struct stasis_rest_handlers applications_applicationName_eventFilter = {</span><br><span style="color: hsl(120, 100%, 40%);">+ .path_segment = "eventFilter",</span><br><span style="color: hsl(120, 100%, 40%);">+ .callbacks = {</span><br><span style="color: hsl(120, 100%, 40%);">+ [AST_HTTP_PUT] = ast_ari_applications_filter_cb,</span><br><span style="color: hsl(120, 100%, 40%);">+ },</span><br><span style="color: hsl(120, 100%, 40%);">+ .num_children = 0,</span><br><span style="color: hsl(120, 100%, 40%);">+ .children = { }</span><br><span style="color: hsl(120, 100%, 40%);">+};</span><br><span style="color: hsl(120, 100%, 40%);">+/*! \brief REST handler for /api-docs/applications.json */</span><br><span> static struct stasis_rest_handlers applications_applicationName = {</span><br><span> .path_segment = "applicationName",</span><br><span> .is_wildcard = 1,</span><br><span> .callbacks = {</span><br><span> [AST_HTTP_GET] = ast_ari_applications_get_cb,</span><br><span> },</span><br><span style="color: hsl(0, 100%, 40%);">- .num_children = 1,</span><br><span style="color: hsl(0, 100%, 40%);">- .children = { &applications_applicationName_subscription, }</span><br><span style="color: hsl(120, 100%, 40%);">+ .num_children = 2,</span><br><span style="color: hsl(120, 100%, 40%);">+ .children = { &applications_applicationName_subscription,&applications_applicationName_eventFilter, }</span><br><span> };</span><br><span> /*! \brief REST handler for /api-docs/applications.json */</span><br><span> static struct stasis_rest_handlers applications = {</span><br><span>diff --git a/res/res_stasis.c b/res/res_stasis.c</span><br><span>index 127d0e8..9cb56c6 100644</span><br><span>--- a/res/res_stasis.c</span><br><span>+++ b/res/res_stasis.c</span><br><span>@@ -1670,13 +1670,14 @@</span><br><span> return json;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-static struct ast_json *stasis_app_object_to_json(struct stasis_app *app)</span><br><span style="color: hsl(120, 100%, 40%);">+struct ast_json *stasis_app_object_to_json(struct stasis_app *app)</span><br><span> {</span><br><span> if (!app) {</span><br><span> return NULL;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">- return app_event_sources_to_json(app, app_to_json(app));</span><br><span style="color: hsl(120, 100%, 40%);">+ return stasis_app_event_filter_to_json(</span><br><span style="color: hsl(120, 100%, 40%);">+ app, app_event_sources_to_json(app, app_to_json(app)));</span><br><span> }</span><br><span> </span><br><span> struct ast_json *stasis_app_to_json(const char *app_name)</span><br><span>diff --git a/res/stasis/app.c b/res/stasis/app.c</span><br><span>index e48d8cc..6642bc4 100644</span><br><span>--- a/res/stasis/app.c</span><br><span>+++ b/res/stasis/app.c</span><br><span>@@ -67,6 +67,10 @@</span><br><span> enum stasis_app_subscription_model subscription_model;</span><br><span> /*! Whether or not someone wants to see debug messages about this app */</span><br><span> int debug;</span><br><span style="color: hsl(120, 100%, 40%);">+ /*! An array of allowed events types for this application */</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_json *allowed;</span><br><span style="color: hsl(120, 100%, 40%);">+ /*! An array of disallowed events types for this application */</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_json *disallowed;</span><br><span> /*! Name of the Stasis application */</span><br><span> char name[];</span><br><span> };</span><br><span>@@ -319,6 +323,12 @@</span><br><span> app->forwards = NULL;</span><br><span> ao2_cleanup(app->data);</span><br><span> app->data = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_json_unref(app->allowed);</span><br><span style="color: hsl(120, 100%, 40%);">+ app->allowed = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_json_unref(app->disallowed);</span><br><span style="color: hsl(120, 100%, 40%);">+ app->disallowed = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> }</span><br><span> </span><br><span> static void call_forwarded_handler(struct stasis_app *app, struct stasis_message *message)</span><br><span>@@ -1653,3 +1663,119 @@</span><br><span> stasis_app_unregister_event_source(&bridge_event_source);</span><br><span> stasis_app_unregister_event_source(&channel_event_source);</span><br><span> }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+struct ast_json *stasis_app_event_filter_to_json(struct stasis_app *app, struct ast_json *json)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!app || !json) {</span><br><span style="color: hsl(120, 100%, 40%);">+ return json;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_json_object_set(json, "allowed_events", app->allowed ?</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_json_ref(app->allowed) : ast_json_array_create());</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_json_object_set(json, "disallowed_events", app->disallowed ?</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_json_ref(app->disallowed) : ast_json_array_create());</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ return json;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static int app_event_filter_set(struct stasis_app *app, struct ast_json **member,</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_json *filter, const char *filter_type)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ if (filter && ast_json_typeof(filter) == AST_JSON_OBJECT) {</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!ast_json_object_size(filter)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ /* If no filters are specified then reset this filter type */</span><br><span style="color: hsl(120, 100%, 40%);">+ filter = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+ } else {</span><br><span style="color: hsl(120, 100%, 40%);">+ /* Otherwise try to get the filter array for this type */</span><br><span style="color: hsl(120, 100%, 40%);">+ filter = ast_json_object_get(filter, filter_type);</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!filter) {</span><br><span style="color: hsl(120, 100%, 40%);">+ /* A filter type exists, but not this one, so don't update */</span><br><span style="color: hsl(120, 100%, 40%);">+ return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ /* At this point the filter object should be an array */</span><br><span style="color: hsl(120, 100%, 40%);">+ if (filter && ast_json_typeof(filter) != AST_JSON_ARRAY) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "Invalid json type event filter - app: %s, filter: %s\n",</span><br><span style="color: hsl(120, 100%, 40%);">+ app->name, filter_type);</span><br><span style="color: hsl(120, 100%, 40%);">+ return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (filter) {</span><br><span style="color: hsl(120, 100%, 40%);">+ /* Confirm that at least the type names are specified */</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_json *obj;</span><br><span style="color: hsl(120, 100%, 40%);">+ int i;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ for (i = 0; i < ast_json_array_size(filter) &&</span><br><span style="color: hsl(120, 100%, 40%);">+ (obj = ast_json_array_get(filter, i)); ++i) {</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (ast_strlen_zero(ast_json_object_string_get(obj, "type"))) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "Filter event must have a type - app: %s, "</span><br><span style="color: hsl(120, 100%, 40%);">+ "filter: %s\n", app->name, filter_type);</span><br><span style="color: hsl(120, 100%, 40%);">+ return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ ao2_lock(app);</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_json_unref(*member);</span><br><span style="color: hsl(120, 100%, 40%);">+ *member = filter ? ast_json_ref(filter) : NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+ ao2_unlock(app);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static int app_allowed_set(struct stasis_app *app, struct ast_json *filter)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ return app_event_filter_set(app, &app->allowed, filter, "allowed");</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static int app_disallowed_set(struct stasis_app *app, struct ast_json *filter)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ return app_event_filter_set(app, &app->disallowed, filter, "disallowed");</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+int stasis_app_event_filter_set(struct stasis_app *app, struct ast_json *filter)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ return app_disallowed_set(app, filter) || app_allowed_set(app, filter);</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static int app_event_filter_matched(struct ast_json *array, struct ast_json *event, int empty)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_json *obj;</span><br><span style="color: hsl(120, 100%, 40%);">+ int i;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!array || !ast_json_array_size(array)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ return empty;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ for (i = 0; i < ast_json_array_size(array) &&</span><br><span style="color: hsl(120, 100%, 40%);">+ (obj = ast_json_array_get(array, i)); ++i) {</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!ast_strcmp_s(ast_json_object_string_get(obj, "type"),</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_json_object_string_get(event, "type"))) {</span><br><span style="color: hsl(120, 100%, 40%);">+ return 1;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+int stasis_app_event_allowed(const char *app_name, struct ast_json *event)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ struct stasis_app *app = stasis_app_get_by_name(app_name);</span><br><span style="color: hsl(120, 100%, 40%);">+ int res;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!app) {</span><br><span style="color: hsl(120, 100%, 40%);">+ return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ ao2_lock(app);</span><br><span style="color: hsl(120, 100%, 40%);">+ res = !app_event_filter_matched(app->disallowed, event, 0) &&</span><br><span style="color: hsl(120, 100%, 40%);">+ app_event_filter_matched(app->allowed, event, 1);</span><br><span style="color: hsl(120, 100%, 40%);">+ ao2_unlock(app);</span><br><span style="color: hsl(120, 100%, 40%);">+ ao2_ref(app, -1);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ return res;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span>diff --git a/rest-api/api-docs/applications.json b/rest-api/api-docs/applications.json</span><br><span>index 5ed720d..d0eec1a 100644</span><br><span>--- a/rest-api/api-docs/applications.json</span><br><span>+++ b/rest-api/api-docs/applications.json</span><br><span>@@ -134,6 +134,47 @@</span><br><span> ]</span><br><span> }</span><br><span> ]</span><br><span style="color: hsl(120, 100%, 40%);">+ },</span><br><span style="color: hsl(120, 100%, 40%);">+ {</span><br><span style="color: hsl(120, 100%, 40%);">+ "path": "/applications/{applicationName}/eventFilter",</span><br><span style="color: hsl(120, 100%, 40%);">+ "description": "Stasis application",</span><br><span style="color: hsl(120, 100%, 40%);">+ "operations": [</span><br><span style="color: hsl(120, 100%, 40%);">+ {</span><br><span style="color: hsl(120, 100%, 40%);">+ "httpMethod": "PUT",</span><br><span style="color: hsl(120, 100%, 40%);">+ "summary": "Filter application events types.",</span><br><span style="color: hsl(120, 100%, 40%);">+ "notes": "Allowed and/or disallowed event type filtering can be done. The body (parameter) should specify a JSON key/value object that describes the type of event filtering needed. One, or both of the following keys can be designated:<br /><br />\"allowed\" - Specifies an allowed list of event types<br />\"disallowed\" - Specifies a disallowed list of event types<br /><br />Further, each of those key's value should be a JSON array that holds zero, or more JSON key/value objects. Each of these objects must contain the following key with an associated value:<br /><br />\"type\" - The type name of the event to filter<br /><br />The value must be the string name (case sensitive) of the event type that needs filtering. For example:<br /><br />{ \"allowed\": [ { \"type\": \"StasisStart\" }, { \"type\": \"StasisEnd\" } ] }<br /><br />As this specifies only an allowed list, then only those two event type messages are sent to the application. No other event messages are sent.<br /><br />The following rules apply:<br /><br />* If the body is empty, both the allowed and disallowed filters are set empty.<br />* If both list types are given then both are set to their respective values (note, specifying an empty array for a given type sets that type to empty).<br />* If only one list type is given then only that type is set. The other type is not updated.<br />* An empty \"allowed\" list means all events are allowed.<br />* An empty \"disallowed\" list means no events are disallowed.<br />* Disallowed events take precedence over allowed events if the event type is specified in both lists.",</span><br><span style="color: hsl(120, 100%, 40%);">+ "nickname": "filter",</span><br><span style="color: hsl(120, 100%, 40%);">+ "responseClass": "Application",</span><br><span style="color: hsl(120, 100%, 40%);">+ "parameters": [</span><br><span style="color: hsl(120, 100%, 40%);">+ {</span><br><span style="color: hsl(120, 100%, 40%);">+ "name": "applicationName",</span><br><span style="color: hsl(120, 100%, 40%);">+ "description": "Application's name",</span><br><span style="color: hsl(120, 100%, 40%);">+ "paramType": "path",</span><br><span style="color: hsl(120, 100%, 40%);">+ "required": true,</span><br><span style="color: hsl(120, 100%, 40%);">+ "allowMultiple": false,</span><br><span style="color: hsl(120, 100%, 40%);">+ "dataType": "string"</span><br><span style="color: hsl(120, 100%, 40%);">+ },</span><br><span style="color: hsl(120, 100%, 40%);">+ {</span><br><span style="color: hsl(120, 100%, 40%);">+ "name": "filter",</span><br><span style="color: hsl(120, 100%, 40%);">+ "description": "Specify which event types to allow/disallow",</span><br><span style="color: hsl(120, 100%, 40%);">+ "paramType": "body",</span><br><span style="color: hsl(120, 100%, 40%);">+ "required": false,</span><br><span style="color: hsl(120, 100%, 40%);">+ "dataType": "object",</span><br><span style="color: hsl(120, 100%, 40%);">+ "allowMultiple": false</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ ],</span><br><span style="color: hsl(120, 100%, 40%);">+ "errorResponses": [</span><br><span style="color: hsl(120, 100%, 40%);">+ {</span><br><span style="color: hsl(120, 100%, 40%);">+ "code": 400,</span><br><span style="color: hsl(120, 100%, 40%);">+ "reason": "Bad request."</span><br><span style="color: hsl(120, 100%, 40%);">+ },</span><br><span style="color: hsl(120, 100%, 40%);">+ {</span><br><span style="color: hsl(120, 100%, 40%);">+ "code": 404,</span><br><span style="color: hsl(120, 100%, 40%);">+ "reason": "Application does not exist."</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ ]</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ ]</span><br><span> }</span><br><span> ],</span><br><span> "models": {</span><br><span>@@ -165,6 +206,16 @@</span><br><span> "type": "List[string]",</span><br><span> "description": "Names of the devices subscribed to.",</span><br><span> "required": true</span><br><span style="color: hsl(120, 100%, 40%);">+ },</span><br><span style="color: hsl(120, 100%, 40%);">+ "allowed_events": {</span><br><span style="color: hsl(120, 100%, 40%);">+ "type": "List[object]",</span><br><span style="color: hsl(120, 100%, 40%);">+ "description": "Event types sent to the application.",</span><br><span style="color: hsl(120, 100%, 40%);">+ "required": true</span><br><span style="color: hsl(120, 100%, 40%);">+ },</span><br><span style="color: hsl(120, 100%, 40%);">+ "disallowed_events": {</span><br><span style="color: hsl(120, 100%, 40%);">+ "type": "List[object]",</span><br><span style="color: hsl(120, 100%, 40%);">+ "description": "Event types not sent to the application.",</span><br><span style="color: hsl(120, 100%, 40%);">+ "required": true</span><br><span> }</span><br><span> }</span><br><span> }</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/10978">change 10978</a>. To unsubscribe, or for help writing mail filters, visit <a href="https://gerrit.asterisk.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.asterisk.org/10978"/><meta itemprop="name" content="View Change"/></div></div>
<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: 13 </div>
<div style="display:none"> Gerrit-MessageType: newchange </div>
<div style="display:none"> Gerrit-Change-Id: I9671ba1fcdb3b6c830b553d4c5365aed5d588d5b </div>
<div style="display:none"> Gerrit-Change-Number: 10978 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: Kevin Harwell <kharwell@digium.com> </div>