<p>Friendly Automation <strong>merged</strong> this change.</p><p><a href="https://gerrit.asterisk.org/c/asterisk/+/12775">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  George Joseph: Looks good to me, approved
  Friendly Automation: Approved for Submit

</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">ARI: External Media<br><br>The Channel resource has a new sub-resource "externalMedia".<br>This allows an application to create a channel for the sole purpose<br>of exchanging media with an external server.  Once created, this<br>channel could be placed into a bridge with existing channels to<br>allow the external server to inject audio into the bridge or<br>receive audio from the bridge.<br>See https://wiki.asterisk.org/wiki/display/AST/External+Media+and+ARI<br>for more information.<br><br>Change-Id: I9618899198880b4c650354581b50c0401b58bc46<br>---<br>A doc/CHANGES-staging/ARI.txt<br>M res/ari/ari_model_validators.c<br>M res/ari/ari_model_validators.h<br>M res/ari/resource_channels.c<br>M res/ari/resource_channels.h<br>M res/res_ari_channels.c<br>M rest-api/api-docs/channels.json<br>7 files changed, 579 insertions(+), 24 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/doc/CHANGES-staging/ARI.txt b/doc/CHANGES-staging/ARI.txt</span><br><span>new file mode 100644</span><br><span>index 0000000..06ac4ab</span><br><span>--- /dev/null</span><br><span>+++ b/doc/CHANGES-staging/ARI.txt</span><br><span>@@ -0,0 +1,10 @@</span><br><span style="color: hsl(120, 100%, 40%);">+Subject: ARI Channels</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+The Channel resource has a new sub-resource "externalMedia".</span><br><span style="color: hsl(120, 100%, 40%);">+This allows an application to create a channel for the sole purpose</span><br><span style="color: hsl(120, 100%, 40%);">+of exchanging media with an external server.  Once created, this</span><br><span style="color: hsl(120, 100%, 40%);">+channel could be placed into a bridge with existing channels to</span><br><span style="color: hsl(120, 100%, 40%);">+allow the external server to inject audio into the bridge or</span><br><span style="color: hsl(120, 100%, 40%);">+receive audio from the bridge.</span><br><span style="color: hsl(120, 100%, 40%);">+See https://wiki.asterisk.org/wiki/display/AST/External+Media+and+ARI</span><br><span style="color: hsl(120, 100%, 40%);">+for more information.</span><br><span>\ No newline at end of file</span><br><span>diff --git a/res/ari/ari_model_validators.c b/res/ari/ari_model_validators.c</span><br><span>index 8910bbb..3d63f53 100644</span><br><span>--- a/res/ari/ari_model_validators.c</span><br><span>+++ b/res/ari/ari_model_validators.c</span><br><span>@@ -1385,6 +1385,62 @@</span><br><span>  return ast_ari_validate_dialplan_cep;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+int ast_ari_validate_external_media(struct ast_json *json)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ int res = 1;</span><br><span style="color: hsl(120, 100%, 40%);">+  struct ast_json_iter *iter;</span><br><span style="color: hsl(120, 100%, 40%);">+   int has_channel = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        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("channel", 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_channel = 1;</span><br><span style="color: hsl(120, 100%, 40%);">+                      prop_is_valid = ast_ari_validate_channel(</span><br><span style="color: hsl(120, 100%, 40%);">+                             ast_json_object_iter_value(iter));</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 ExternalMedia field channel 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 style="color: hsl(120, 100%, 40%);">+                if (strcmp("local_address", 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%);">+                    prop_is_valid = ast_ari_validate_string(</span><br><span style="color: hsl(120, 100%, 40%);">+                              ast_json_object_iter_value(iter));</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 ExternalMedia field local_address 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 style="color: hsl(120, 100%, 40%);">+                if (strcmp("local_port", 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%);">+                    prop_is_valid = ast_ari_validate_int(</span><br><span style="color: hsl(120, 100%, 40%);">+                         ast_json_object_iter_value(iter));</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 ExternalMedia field local_port 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 style="color: hsl(120, 100%, 40%);">+                {</span><br><span style="color: hsl(120, 100%, 40%);">+                     ast_log(LOG_ERROR,</span><br><span style="color: hsl(120, 100%, 40%);">+                            "ARI ExternalMedia has undocumented field %s\n",</span><br><span style="color: hsl(120, 100%, 40%);">+                            ast_json_object_iter_key(iter));</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 style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   if (!has_channel) {</span><br><span style="color: hsl(120, 100%, 40%);">+           ast_log(LOG_ERROR, "ARI ExternalMedia missing required field channel\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 style="color: hsl(120, 100%, 40%);">+   return res;</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_validator ast_ari_validate_external_media_fn(void)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+   return ast_ari_validate_external_media;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> int ast_ari_validate_rtpstat(struct ast_json *json)</span><br><span> {</span><br><span>     int res = 1;</span><br><span>diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h</span><br><span>index f9285b4..53a8573 100644</span><br><span>--- a/res/ari/ari_model_validators.h</span><br><span>+++ b/res/ari/ari_model_validators.h</span><br><span>@@ -478,6 +478,24 @@</span><br><span> ari_validator ast_ari_validate_dialplan_cep_fn(void);</span><br><span> </span><br><span> /*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Validator for ExternalMedia.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * ExternalMedia session.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param json JSON object to validate.</span><br><span style="color: hsl(120, 100%, 40%);">+ * \returns True (non-zero) if valid.</span><br><span style="color: hsl(120, 100%, 40%);">+ * \returns False (zero) if invalid.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+int ast_ari_validate_external_media(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 Function pointer to ast_ari_validate_external_media().</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * See \ref ast_ari_model_validators.h for more details.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+ari_validator ast_ari_validate_external_media_fn(void);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span>  * \brief Validator for RTPstat.</span><br><span>  *</span><br><span>  * A statistics of a RTP.</span><br><span>@@ -1522,6 +1540,10 @@</span><br><span>  * - context: string (required)</span><br><span>  * - exten: string (required)</span><br><span>  * - priority: long (required)</span><br><span style="color: hsl(120, 100%, 40%);">+ * ExternalMedia</span><br><span style="color: hsl(120, 100%, 40%);">+ * - channel: Channel (required)</span><br><span style="color: hsl(120, 100%, 40%);">+ * - local_address: string</span><br><span style="color: hsl(120, 100%, 40%);">+ * - local_port: int</span><br><span>  * RTPstat</span><br><span>  * - channel_uniqueid: string (required)</span><br><span>  * - local_maxjitter: double</span><br><span>diff --git a/res/ari/resource_channels.c b/res/ari/resource_channels.c</span><br><span>index d415224..ec0960d 100644</span><br><span>--- a/res/ari/resource_channels.c</span><br><span>+++ b/res/ari/resource_channels.c</span><br><span>@@ -1056,7 +1056,7 @@</span><br><span>   return NULL;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-static void ari_channels_handle_originate_with_id(const char *args_endpoint,</span><br><span style="color: hsl(120, 100%, 40%);">+static struct ast_channel *ari_channels_handle_originate_with_id(const char *args_endpoint,</span><br><span>       const char *args_extension,</span><br><span>  const char *args_context,</span><br><span>    long args_priority,</span><br><span>@@ -1094,19 +1094,19 @@</span><br><span>                || (assignedids.uniqueid2 && AST_MAX_PUBLIC_UNIQUEID < strlen(assignedids.uniqueid2))) {</span><br><span>          ast_ari_response_error(response, 400, "Bad Request",</span><br><span>                       "Uniqueid length exceeds maximum of %d", AST_MAX_PUBLIC_UNIQUEID);</span><br><span style="color: hsl(0, 100%, 40%);">-            return;</span><br><span style="color: hsl(120, 100%, 40%);">+               return NULL;</span><br><span>         }</span><br><span> </span><br><span>        if (ast_strlen_zero(args_endpoint)) {</span><br><span>                ast_ari_response_error(response, 400, "Bad Request",</span><br><span>                       "Endpoint must be specified");</span><br><span style="color: hsl(0, 100%, 40%);">-                return;</span><br><span style="color: hsl(120, 100%, 40%);">+               return NULL;</span><br><span>         }</span><br><span> </span><br><span>        if (!ast_strlen_zero(args_originator) && !ast_strlen_zero(args_formats)) {</span><br><span>           ast_ari_response_error(response, 400, "Bad Request",</span><br><span>                       "Originator and formats can't both be specified");</span><br><span style="color: hsl(0, 100%, 40%);">-                return;</span><br><span style="color: hsl(120, 100%, 40%);">+               return NULL;</span><br><span>         }</span><br><span> </span><br><span>        dialtech = ast_strdupa(args_endpoint);</span><br><span>@@ -1118,7 +1118,7 @@</span><br><span>       if (ast_strlen_zero(dialtech) || ast_strlen_zero(dialdevice)) {</span><br><span>              ast_ari_response_error(response, 400, "Bad Request",</span><br><span>                       "Invalid endpoint specified");</span><br><span style="color: hsl(0, 100%, 40%);">-                return;</span><br><span style="color: hsl(120, 100%, 40%);">+               return NULL;</span><br><span>         }</span><br><span> </span><br><span>        if (!ast_strlen_zero(args_app)) {</span><br><span>@@ -1126,7 +1126,7 @@</span><br><span> </span><br><span>                if (!appdata) {</span><br><span>                      ast_ari_response_alloc_failed(response);</span><br><span style="color: hsl(0, 100%, 40%);">-                        return;</span><br><span style="color: hsl(120, 100%, 40%);">+                       return NULL;</span><br><span>                 }</span><br><span> </span><br><span>                ast_str_set(&appdata, 0, "%s", args_app);</span><br><span>@@ -1137,7 +1137,7 @@</span><br><span>              origination = ast_calloc(1, sizeof(*origination) + ast_str_size(appdata) + 1);</span><br><span>               if (!origination) {</span><br><span>                  ast_ari_response_alloc_failed(response);</span><br><span style="color: hsl(0, 100%, 40%);">-                        return;</span><br><span style="color: hsl(120, 100%, 40%);">+                       return NULL;</span><br><span>                 }</span><br><span> </span><br><span>                strcpy(origination->appdata, ast_str_buffer(appdata));</span><br><span>@@ -1145,7 +1145,7 @@</span><br><span>            origination = ast_calloc(1, sizeof(*origination) + 1);</span><br><span>               if (!origination) {</span><br><span>                  ast_ari_response_alloc_failed(response);</span><br><span style="color: hsl(0, 100%, 40%);">-                        return;</span><br><span style="color: hsl(120, 100%, 40%);">+                       return NULL;</span><br><span>                 }</span><br><span> </span><br><span>                ast_copy_string(origination->context, S_OR(args_context, "default"), sizeof(origination->context));</span><br><span>@@ -1160,7 +1160,7 @@</span><br><span>                          if (ipri == -1) {</span><br><span>                                    ast_log(AST_LOG_ERROR, "Requested label: %s can not be found in context: %s\n", args_label, args_context);</span><br><span>                                         ast_ari_response_error(response, 404, "Not Found", "Requested label can not be found");</span><br><span style="color: hsl(0, 100%, 40%);">-                                     return;</span><br><span style="color: hsl(120, 100%, 40%);">+                                       return NULL;</span><br><span>                                 }</span><br><span>                    } else {</span><br><span>                             ast_debug(3, "Numeric value provided for label, jumping to that priority\n");</span><br><span>@@ -1170,7 +1170,7 @@</span><br><span>                              ast_log(AST_LOG_ERROR, "Invalid priority label '%s' specified for extension %s in context: %s\n",</span><br><span>                                          args_label, args_extension, args_context);</span><br><span>                           ast_ari_response_error(response, 400, "Bad Request", "Requested priority is illegal");</span><br><span style="color: hsl(0, 100%, 40%);">-                              return;</span><br><span style="color: hsl(120, 100%, 40%);">+                               return NULL;</span><br><span>                         }</span><br><span> </span><br><span>                        /* Our priority was provided by a label */</span><br><span>@@ -1184,14 +1184,14 @@</span><br><span>         } else {</span><br><span>             ast_ari_response_error(response, 400, "Bad Request",</span><br><span>                       "Application or extension must be specified");</span><br><span style="color: hsl(0, 100%, 40%);">-                return;</span><br><span style="color: hsl(120, 100%, 40%);">+               return NULL;</span><br><span>         }</span><br><span> </span><br><span>        dial = ast_dial_create();</span><br><span>    if (!dial) {</span><br><span>                 ast_ari_response_alloc_failed(response);</span><br><span>             ast_free(origination);</span><br><span style="color: hsl(0, 100%, 40%);">-          return;</span><br><span style="color: hsl(120, 100%, 40%);">+               return NULL;</span><br><span>         }</span><br><span>    ast_dial_set_user_data(dial, origination);</span><br><span> </span><br><span>@@ -1199,7 +1199,7 @@</span><br><span>               ast_ari_response_alloc_failed(response);</span><br><span>             ast_dial_destroy(dial);</span><br><span>              ast_free(origination);</span><br><span style="color: hsl(0, 100%, 40%);">-          return;</span><br><span style="color: hsl(120, 100%, 40%);">+               return NULL;</span><br><span>         }</span><br><span> </span><br><span>        if (args_timeout > 0) {</span><br><span>@@ -1227,7 +1227,7 @@</span><br><span>                           "Provided originator channel was not found");</span><br><span>                      ast_dial_destroy(dial);</span><br><span>                      ast_free(origination);</span><br><span style="color: hsl(0, 100%, 40%);">-                  return;</span><br><span style="color: hsl(120, 100%, 40%);">+                       return NULL;</span><br><span>                 }</span><br><span>    }</span><br><span> </span><br><span>@@ -1240,7 +1240,7 @@</span><br><span>                        ast_dial_destroy(dial);</span><br><span>                      ast_free(origination);</span><br><span>                       ast_channel_cleanup(other);</span><br><span style="color: hsl(0, 100%, 40%);">-                     return;</span><br><span style="color: hsl(120, 100%, 40%);">+                       return NULL;</span><br><span>                 }</span><br><span> </span><br><span>                while ((format_name = ast_strip(strsep(&formats_copy, ",")))) {</span><br><span>@@ -1259,7 +1259,7 @@</span><br><span>                                ast_channel_cleanup(other);</span><br><span>                          ao2_ref(format_cap, -1);</span><br><span>                             ao2_cleanup(fmt);</span><br><span style="color: hsl(0, 100%, 40%);">-                               return;</span><br><span style="color: hsl(120, 100%, 40%);">+                               return NULL;</span><br><span>                         }</span><br><span>                    ao2_ref(fmt, -1);</span><br><span>            }</span><br><span>@@ -1275,7 +1275,7 @@</span><br><span>            ast_dial_destroy(dial);</span><br><span>              ast_free(origination);</span><br><span>               ast_channel_cleanup(other);</span><br><span style="color: hsl(0, 100%, 40%);">-             return;</span><br><span style="color: hsl(120, 100%, 40%);">+               return NULL;</span><br><span>         }</span><br><span> </span><br><span>        ast_channel_cleanup(other);</span><br><span>@@ -1286,7 +1286,7 @@</span><br><span>          ast_ari_response_alloc_failed(response);</span><br><span>             ast_dial_destroy(dial);</span><br><span>              ast_free(origination);</span><br><span style="color: hsl(0, 100%, 40%);">-          return;</span><br><span style="color: hsl(120, 100%, 40%);">+               return NULL;</span><br><span>         }</span><br><span> </span><br><span>        if (!ast_strlen_zero(cid_num) || !ast_strlen_zero(cid_name)) {</span><br><span>@@ -1351,8 +1351,7 @@</span><br><span>               ast_ari_response_ok(response, ast_channel_snapshot_to_json(snapshot, NULL));</span><br><span>         }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-   ast_channel_unref(chan);</span><br><span style="color: hsl(0, 100%, 40%);">-        return;</span><br><span style="color: hsl(120, 100%, 40%);">+       return chan;</span><br><span> }</span><br><span> </span><br><span> /*!</span><br><span>@@ -1393,6 +1392,7 @@</span><br><span>         struct ast_ari_response *response)</span><br><span> {</span><br><span>      struct ast_variable *variables = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+        struct ast_channel *chan;</span><br><span> </span><br><span>        /* Parse any query parameters out of the body parameter */</span><br><span>   if (args->variables) {</span><br><span>@@ -1406,7 +1406,7 @@</span><br><span>            }</span><br><span>    }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-   ari_channels_handle_originate_with_id(</span><br><span style="color: hsl(120, 100%, 40%);">+        chan = ari_channels_handle_originate_with_id(</span><br><span>                args->endpoint,</span><br><span>           args->extension,</span><br><span>          args->context,</span><br><span>@@ -1422,6 +1422,7 @@</span><br><span>            args->originator,</span><br><span>                 args->formats,</span><br><span>            response);</span><br><span style="color: hsl(120, 100%, 40%);">+    ast_channel_cleanup(chan);</span><br><span>   ast_variables_destroy(variables);</span><br><span> }</span><br><span> </span><br><span>@@ -1430,6 +1431,7 @@</span><br><span>   struct ast_ari_response *response)</span><br><span> {</span><br><span>      struct ast_variable *variables = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+        struct ast_channel *chan;</span><br><span> </span><br><span>        /* Parse any query parameters out of the body parameter */</span><br><span>   if (args->variables) {</span><br><span>@@ -1443,7 +1445,7 @@</span><br><span>            }</span><br><span>    }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-   ari_channels_handle_originate_with_id(</span><br><span style="color: hsl(120, 100%, 40%);">+        chan = ari_channels_handle_originate_with_id(</span><br><span>                args->endpoint,</span><br><span>           args->extension,</span><br><span>          args->context,</span><br><span>@@ -1459,6 +1461,7 @@</span><br><span>            args->originator,</span><br><span>                 args->formats,</span><br><span>            response);</span><br><span style="color: hsl(120, 100%, 40%);">+    ast_channel_cleanup(chan);</span><br><span>   ast_variables_destroy(variables);</span><br><span> }</span><br><span> </span><br><span>@@ -2049,3 +2052,148 @@</span><br><span> </span><br><span>     return;</span><br><span> }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static void external_media_rtp_udp(struct ast_ari_channels_external_media_args *args,</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_variable *variables,</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%);">+ size_t endpoint_len;</span><br><span style="color: hsl(120, 100%, 40%);">+  char *endpoint;</span><br><span style="color: hsl(120, 100%, 40%);">+       struct ast_channel *chan;</span><br><span style="color: hsl(120, 100%, 40%);">+     struct ast_json *json_chan;</span><br><span style="color: hsl(120, 100%, 40%);">+   struct varshead *vars;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      endpoint_len = strlen("UnicastRTP/") + strlen(args->external_host) + 1;</span><br><span style="color: hsl(120, 100%, 40%);">+  endpoint = ast_alloca(endpoint_len);</span><br><span style="color: hsl(120, 100%, 40%);">+  snprintf(endpoint, endpoint_len, "UnicastRTP/%s", args->external_host);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        chan = ari_channels_handle_originate_with_id(</span><br><span style="color: hsl(120, 100%, 40%);">+         endpoint,</span><br><span style="color: hsl(120, 100%, 40%);">+             NULL,</span><br><span style="color: hsl(120, 100%, 40%);">+         NULL,</span><br><span style="color: hsl(120, 100%, 40%);">+         0,</span><br><span style="color: hsl(120, 100%, 40%);">+            NULL,</span><br><span style="color: hsl(120, 100%, 40%);">+         args->app,</span><br><span style="color: hsl(120, 100%, 40%);">+         NULL,</span><br><span style="color: hsl(120, 100%, 40%);">+         NULL,</span><br><span style="color: hsl(120, 100%, 40%);">+         0,</span><br><span style="color: hsl(120, 100%, 40%);">+            variables,</span><br><span style="color: hsl(120, 100%, 40%);">+            args->channel_id,</span><br><span style="color: hsl(120, 100%, 40%);">+          NULL,</span><br><span style="color: hsl(120, 100%, 40%);">+         NULL,</span><br><span style="color: hsl(120, 100%, 40%);">+         args->format,</span><br><span style="color: hsl(120, 100%, 40%);">+              response);</span><br><span style="color: hsl(120, 100%, 40%);">+    ast_variables_destroy(variables);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   if (!chan) {</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%);">+   /*</span><br><span style="color: hsl(120, 100%, 40%);">+     * At this point, response->message contains a channel object so we</span><br><span style="color: hsl(120, 100%, 40%);">+         * need to save it then create a new ExternalMedia object and put the</span><br><span style="color: hsl(120, 100%, 40%);">+  * channel in it.</span><br><span style="color: hsl(120, 100%, 40%);">+      */</span><br><span style="color: hsl(120, 100%, 40%);">+   json_chan = response->message;</span><br><span style="color: hsl(120, 100%, 40%);">+     response->message = ast_json_object_create();</span><br><span style="color: hsl(120, 100%, 40%);">+      if (!response->message) {</span><br><span style="color: hsl(120, 100%, 40%);">+          ast_channel_unref(chan);</span><br><span style="color: hsl(120, 100%, 40%);">+              ast_json_unref(json_chan);</span><br><span style="color: hsl(120, 100%, 40%);">+            ast_ari_response_alloc_failed(response);</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%);">+   ast_json_object_set(response->message, "channel", json_chan);</span><br><span style="color: hsl(120, 100%, 40%);">+    /*</span><br><span style="color: hsl(120, 100%, 40%);">+     * At the time the channel snapshot was taken the channel variables might</span><br><span style="color: hsl(120, 100%, 40%);">+      * not have been set so we try to grab them directly from the channel.</span><br><span style="color: hsl(120, 100%, 40%);">+         */</span><br><span style="color: hsl(120, 100%, 40%);">+   ast_channel_lock(chan);</span><br><span style="color: hsl(120, 100%, 40%);">+       vars = ast_channel_varshead(chan);</span><br><span style="color: hsl(120, 100%, 40%);">+    if (vars && !AST_LIST_EMPTY(vars)) {</span><br><span style="color: hsl(120, 100%, 40%);">+          struct ast_var_t *variables;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                /* Put them all on the channel object */</span><br><span style="color: hsl(120, 100%, 40%);">+              ast_json_object_set(json_chan, "channelvars", ast_json_channel_vars(vars));</span><br><span style="color: hsl(120, 100%, 40%);">+         /* Grab out the local address and port */</span><br><span style="color: hsl(120, 100%, 40%);">+             AST_LIST_TRAVERSE(vars, variables, entries) {</span><br><span style="color: hsl(120, 100%, 40%);">+                 if (!strcmp("UNICASTRTP_LOCAL_ADDRESS", ast_var_name(variables))) {</span><br><span style="color: hsl(120, 100%, 40%);">+                         ast_json_object_set(response->message, "local_address",</span><br><span style="color: hsl(120, 100%, 40%);">+                                  ast_json_string_create(ast_var_value(variables)));</span><br><span style="color: hsl(120, 100%, 40%);">+                    }</span><br><span style="color: hsl(120, 100%, 40%);">+                     else if (!strcmp("UNICASTRTP_LOCAL_PORT", ast_var_name(variables))) {</span><br><span style="color: hsl(120, 100%, 40%);">+                               ast_json_object_set(response->message, "local_port",</span><br><span style="color: hsl(120, 100%, 40%);">+                                     ast_json_integer_create(strtol(ast_var_value(variables), NULL, 10)));</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%);">+     ast_channel_unlock(chan);</span><br><span style="color: hsl(120, 100%, 40%);">+     ast_channel_unref(chan);</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%);">+#include "asterisk/config.h"</span><br><span style="color: hsl(120, 100%, 40%);">+#include "asterisk/netsock2.h"</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+void ast_ari_channels_external_media(struct ast_variable *headers,</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_ari_channels_external_media_args *args, 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_variable *variables = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+        char *external_host;</span><br><span style="color: hsl(120, 100%, 40%);">+  char *host = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+    char *port = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+  ast_assert(response != NULL);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+       if (ast_strlen_zero(args->app)) {</span><br><span style="color: hsl(120, 100%, 40%);">+          ast_ari_response_error(response, 400, "Bad Request", "app cannot be empty");</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 (ast_strlen_zero(args->external_host)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                ast_ari_response_error(response, 400, "Bad Request", "external_host cannot be empty");</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%);">+   external_host = ast_strdupa(args->external_host);</span><br><span style="color: hsl(120, 100%, 40%);">+  if (!ast_sockaddr_split_hostport(external_host, &host, &port, PARSE_PORT_REQUIRE)) {</span><br><span style="color: hsl(120, 100%, 40%);">+          ast_ari_response_error(response, 400, "Bad Request", "external_host must be <host>:<port>");</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 (ast_strlen_zero(args->format)) {</span><br><span style="color: hsl(120, 100%, 40%);">+               ast_ari_response_error(response, 400, "Bad Request", "format cannot be empty");</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 (ast_strlen_zero(args->encapsulation)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                args->encapsulation = "rtp";</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+     if (ast_strlen_zero(args->transport)) {</span><br><span style="color: hsl(120, 100%, 40%);">+            args->transport = "udp";</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+     if (ast_strlen_zero(args->connection_type)) {</span><br><span style="color: hsl(120, 100%, 40%);">+              args->connection_type = "client";</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+     if (ast_strlen_zero(args->direction)) {</span><br><span style="color: hsl(120, 100%, 40%);">+            args->direction = "both";</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 (args->variables) {</span><br><span style="color: hsl(120, 100%, 40%);">+             struct ast_json *json_variables;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+            ast_ari_channels_external_media_parse_body(args->variables, args);</span><br><span style="color: hsl(120, 100%, 40%);">+         json_variables = ast_json_object_get(args->variables, "variables");</span><br><span style="color: hsl(120, 100%, 40%);">+              if (json_variables</span><br><span style="color: hsl(120, 100%, 40%);">+                    && json_to_ast_variables(response, json_variables, &variables)) {</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%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   if (strcasecmp(args->encapsulation, "rtp") == 0 && strcasecmp(args->transport, "udp") == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+               external_media_rtp_udp(args, variables, response);</span><br><span style="color: hsl(120, 100%, 40%);">+    } else {</span><br><span style="color: hsl(120, 100%, 40%);">+              ast_ari_response_error(</span><br><span style="color: hsl(120, 100%, 40%);">+                       response, 501, "Not Implemented",</span><br><span style="color: hsl(120, 100%, 40%);">+                   "The encapsulation and/or transport is not supported");</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span>diff --git a/res/ari/resource_channels.h b/res/ari/resource_channels.h</span><br><span>index 401c8a5..7c70cec 100644</span><br><span>--- a/res/ari/resource_channels.h</span><br><span>+++ b/res/ari/resource_channels.h</span><br><span>@@ -822,5 +822,47 @@</span><br><span>  * \param[out] response HTTP response</span><br><span>  */</span><br><span> void ast_ari_channels_rtpstatistics(struct ast_variable *headers, struct ast_ari_channels_rtpstatistics_args *args, struct ast_ari_response *response);</span><br><span style="color: hsl(120, 100%, 40%);">+/*! Argument struct for ast_ari_channels_external_media() */</span><br><span style="color: hsl(120, 100%, 40%);">+struct ast_ari_channels_external_media_args {</span><br><span style="color: hsl(120, 100%, 40%);">+  /*! The unique id to assign the channel on creation. */</span><br><span style="color: hsl(120, 100%, 40%);">+       const char *channel_id;</span><br><span style="color: hsl(120, 100%, 40%);">+       /*! Stasis Application to place channel into */</span><br><span style="color: hsl(120, 100%, 40%);">+       const char *app;</span><br><span style="color: hsl(120, 100%, 40%);">+      /*! 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" } } */</span><br><span style="color: hsl(120, 100%, 40%);">+    struct ast_json *variables;</span><br><span style="color: hsl(120, 100%, 40%);">+   /*! Hostname/ip:port of external host */</span><br><span style="color: hsl(120, 100%, 40%);">+      const char *external_host;</span><br><span style="color: hsl(120, 100%, 40%);">+    /*! Payload encapsulation protocol */</span><br><span style="color: hsl(120, 100%, 40%);">+ const char *encapsulation;</span><br><span style="color: hsl(120, 100%, 40%);">+    /*! Transport protocol */</span><br><span style="color: hsl(120, 100%, 40%);">+     const char *transport;</span><br><span style="color: hsl(120, 100%, 40%);">+        /*! Connection type (client/server) */</span><br><span style="color: hsl(120, 100%, 40%);">+        const char *connection_type;</span><br><span style="color: hsl(120, 100%, 40%);">+  /*! Format to encode audio in */</span><br><span style="color: hsl(120, 100%, 40%);">+      const char *format;</span><br><span style="color: hsl(120, 100%, 40%);">+   /*! External media direction */</span><br><span style="color: hsl(120, 100%, 40%);">+       const char *direction;</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 /channels/externalMedia.</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_channels_external_media_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_channels_external_media_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 Start an External Media session.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * Create a channel to an External Media source/sink.</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_channels_external_media(struct ast_variable *headers, struct ast_ari_channels_external_media_args *args, struct ast_ari_response *response);</span><br><span> </span><br><span> #endif /* _ASTERISK_RESOURCE_CHANNELS_H */</span><br><span>diff --git a/res/res_ari_channels.c b/res/res_ari_channels.c</span><br><span>index 73d1e4b..fe3a96c 100644</span><br><span>--- a/res/res_ari_channels.c</span><br><span>+++ b/res/res_ari_channels.c</span><br><span>@@ -2807,6 +2807,128 @@</span><br><span> fin: __attribute__((unused))</span><br><span>     return;</span><br><span> }</span><br><span style="color: hsl(120, 100%, 40%);">+int ast_ari_channels_external_media_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_channels_external_media_args *args)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_json *field;</span><br><span style="color: hsl(120, 100%, 40%);">+       /* Parse query parameters out of it */</span><br><span style="color: hsl(120, 100%, 40%);">+        field = ast_json_object_get(body, "channelId");</span><br><span style="color: hsl(120, 100%, 40%);">+     if (field) {</span><br><span style="color: hsl(120, 100%, 40%);">+          args->channel_id = ast_json_string_get(field);</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+     field = ast_json_object_get(body, "app");</span><br><span style="color: hsl(120, 100%, 40%);">+   if (field) {</span><br><span style="color: hsl(120, 100%, 40%);">+          args->app = ast_json_string_get(field);</span><br><span style="color: hsl(120, 100%, 40%);">+    }</span><br><span style="color: hsl(120, 100%, 40%);">+     field = ast_json_object_get(body, "external_host");</span><br><span style="color: hsl(120, 100%, 40%);">+ if (field) {</span><br><span style="color: hsl(120, 100%, 40%);">+          args->external_host = ast_json_string_get(field);</span><br><span style="color: hsl(120, 100%, 40%);">+  }</span><br><span style="color: hsl(120, 100%, 40%);">+     field = ast_json_object_get(body, "encapsulation");</span><br><span style="color: hsl(120, 100%, 40%);">+ if (field) {</span><br><span style="color: hsl(120, 100%, 40%);">+          args->encapsulation = ast_json_string_get(field);</span><br><span style="color: hsl(120, 100%, 40%);">+  }</span><br><span style="color: hsl(120, 100%, 40%);">+     field = ast_json_object_get(body, "transport");</span><br><span style="color: hsl(120, 100%, 40%);">+     if (field) {</span><br><span style="color: hsl(120, 100%, 40%);">+          args->transport = ast_json_string_get(field);</span><br><span style="color: hsl(120, 100%, 40%);">+      }</span><br><span style="color: hsl(120, 100%, 40%);">+     field = ast_json_object_get(body, "connection_type");</span><br><span style="color: hsl(120, 100%, 40%);">+       if (field) {</span><br><span style="color: hsl(120, 100%, 40%);">+          args->connection_type = ast_json_string_get(field);</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+     field = ast_json_object_get(body, "format");</span><br><span style="color: hsl(120, 100%, 40%);">+        if (field) {</span><br><span style="color: hsl(120, 100%, 40%);">+          args->format = ast_json_string_get(field);</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+     field = ast_json_object_get(body, "direction");</span><br><span style="color: hsl(120, 100%, 40%);">+     if (field) {</span><br><span style="color: hsl(120, 100%, 40%);">+          args->direction = ast_json_string_get(field);</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%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Parameter parsing callback for /channels/externalMedia.</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_channels_external_media_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_channels_external_media_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 = get_params; i; i = i->next) {</span><br><span style="color: hsl(120, 100%, 40%);">+             if (strcmp(i->name, "channelId") == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+                 args.channel_id = (i->value);</span><br><span style="color: hsl(120, 100%, 40%);">+              } else</span><br><span style="color: hsl(120, 100%, 40%);">+                if (strcmp(i->name, "app") == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+                       args.app = (i->value);</span><br><span style="color: hsl(120, 100%, 40%);">+             } else</span><br><span style="color: hsl(120, 100%, 40%);">+                if (strcmp(i->name, "external_host") == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+                     args.external_host = (i->value);</span><br><span style="color: hsl(120, 100%, 40%);">+           } else</span><br><span style="color: hsl(120, 100%, 40%);">+                if (strcmp(i->name, "encapsulation") == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+                     args.encapsulation = (i->value);</span><br><span style="color: hsl(120, 100%, 40%);">+           } else</span><br><span style="color: hsl(120, 100%, 40%);">+                if (strcmp(i->name, "transport") == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+                 args.transport = (i->value);</span><br><span style="color: hsl(120, 100%, 40%);">+               } else</span><br><span style="color: hsl(120, 100%, 40%);">+                if (strcmp(i->name, "connection_type") == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+                   args.connection_type = (i->value);</span><br><span style="color: hsl(120, 100%, 40%);">+         } else</span><br><span style="color: hsl(120, 100%, 40%);">+                if (strcmp(i->name, "format") == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+                    args.format = (i->value);</span><br><span style="color: hsl(120, 100%, 40%);">+          } else</span><br><span style="color: hsl(120, 100%, 40%);">+                if (strcmp(i->name, "direction") == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+                 args.direction = (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.variables = body;</span><br><span style="color: hsl(120, 100%, 40%);">+        ast_ari_channels_external_media(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: /* Invalid parameters */</span><br><span style="color: hsl(120, 100%, 40%);">+    case 409: /* Channel is not in a Stasis application; Channel is already bridged */</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_external_media(</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 /channels/externalMedia\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 /channels/externalMedia\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/channels.json */</span><br><span> static struct stasis_rest_handlers channels_create = {</span><br><span>@@ -3000,14 +3122,23 @@</span><br><span>         .children = { &channels_channelId_continue,&channels_channelId_move,&channels_channelId_redirect,&channels_channelId_answer,&channels_channelId_ring,&channels_channelId_dtmf,&channels_channelId_mute,&channels_channelId_hold,&channels_channelId_moh,&channels_channelId_silence,&channels_channelId_play,&channels_channelId_record,&channels_channelId_variable,&channels_channelId_snoop,&channels_channelId_dial,&channels_channelId_rtp_statistics, }</span><br><span> };</span><br><span> /*! \brief REST handler for /api-docs/channels.json */</span><br><span style="color: hsl(120, 100%, 40%);">+static struct stasis_rest_handlers channels_externalMedia = {</span><br><span style="color: hsl(120, 100%, 40%);">+  .path_segment = "externalMedia",</span><br><span style="color: hsl(120, 100%, 40%);">+    .callbacks = {</span><br><span style="color: hsl(120, 100%, 40%);">+                [AST_HTTP_POST] = ast_ari_channels_external_media_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/channels.json */</span><br><span> static struct stasis_rest_handlers channels = {</span><br><span>      .path_segment = "channels",</span><br><span>        .callbacks = {</span><br><span>               [AST_HTTP_GET] = ast_ari_channels_list_cb,</span><br><span>           [AST_HTTP_POST] = ast_ari_channels_originate_cb,</span><br><span>     },</span><br><span style="color: hsl(0, 100%, 40%);">-      .num_children = 2,</span><br><span style="color: hsl(0, 100%, 40%);">-      .children = { &channels_create,&channels_channelId, }</span><br><span style="color: hsl(120, 100%, 40%);">+ .num_children = 3,</span><br><span style="color: hsl(120, 100%, 40%);">+    .children = { &channels_create,&channels_channelId,&channels_externalMedia, }</span><br><span> };</span><br><span> </span><br><span> static int unload_module(void)</span><br><span>diff --git a/rest-api/api-docs/channels.json b/rest-api/api-docs/channels.json</span><br><span>index 4ea5f56..6d964d2 100644</span><br><span>--- a/rest-api/api-docs/channels.json</span><br><span>+++ b/rest-api/api-docs/channels.json</span><br><span>@@ -1748,6 +1748,131 @@</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": "/channels/externalMedia",</span><br><span style="color: hsl(120, 100%, 40%);">+                        "description": "Create a channel to an External Media source/sink.",</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": "POST",</span><br><span style="color: hsl(120, 100%, 40%);">+                                     "summary": "Start an External Media session.",</span><br><span style="color: hsl(120, 100%, 40%);">+                                    "notes": "Create a channel to an External Media source/sink.",</span><br><span style="color: hsl(120, 100%, 40%);">+                                    "nickname": "externalMedia",</span><br><span style="color: hsl(120, 100%, 40%);">+                                      "responseClass": "ExternalMedia",</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": "channelId",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                      "description": "The unique id to assign the channel on creation.",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                        "paramType": "query",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                     "required": false,</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": "app",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                    "description": "Stasis Application to place channel into",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                        "paramType": "query",</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": "variables",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                      "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\" } }",</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": "containers",</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%);">+                                                     "name": "external_host",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                  "description": "Hostname/ip:port of external host",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                       "paramType": "query",</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": "encapsulation",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                  "description": "Payload encapsulation protocol",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                  "paramType": "query",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                     "required": false,</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%);">+                                                     "defaultValue": "rtp",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                    "allowableValues": {</span><br><span style="color: hsl(120, 100%, 40%);">+                                                                "valueType": "LIST",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                              "values": [</span><br><span style="color: hsl(120, 100%, 40%);">+                                                                 "rtp"</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%);">+                                                     "name": "transport",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                      "description": "Transport protocol",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                      "paramType": "query",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                     "required": false,</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%);">+                                                     "defaultValue": "udp",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                    "allowableValues": {</span><br><span style="color: hsl(120, 100%, 40%);">+                                                                "valueType": "LIST",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                              "values": [</span><br><span style="color: hsl(120, 100%, 40%);">+                                                                 "udp"</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%);">+                                                     "name": "connection_type",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                        "description": "Connection type (client/server)",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                 "paramType": "query",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                     "required": false,</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%);">+                                                     "defaultValue": "client",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                 "allowableValues": {</span><br><span style="color: hsl(120, 100%, 40%);">+                                                                "valueType": "LIST",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                              "values": [</span><br><span style="color: hsl(120, 100%, 40%);">+                                                                 "client"</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%);">+                                                     "name": "format",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                 "description": "Format to encode audio in",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                       "paramType": "query",</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": "direction",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                      "description": "External media direction",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                        "paramType": "query",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                     "required": false,</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%);">+                                                     "defaultValue": "both",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                   "allowableValues": {</span><br><span style="color: hsl(120, 100%, 40%);">+                                                                "valueType": "LIST",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                              "values": [</span><br><span style="color: hsl(120, 100%, 40%);">+                                                                 "both"</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%);">+                                    "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": "Invalid parameters"</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": 409,</span><br><span style="color: hsl(120, 100%, 40%);">+                                                        "reason": "Channel is not in a Stasis application; Channel is already bridged"</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>@@ -2034,6 +2159,27 @@</span><br><span>                                       "description": "Channel variables"</span><br><span>                               }</span><br><span>                    }</span><br><span style="color: hsl(120, 100%, 40%);">+             },</span><br><span style="color: hsl(120, 100%, 40%);">+            "ExternalMedia": {</span><br><span style="color: hsl(120, 100%, 40%);">+                  "id": "ExternalMedia",</span><br><span style="color: hsl(120, 100%, 40%);">+                    "description": "ExternalMedia session.",</span><br><span style="color: hsl(120, 100%, 40%);">+                  "properties": {</span><br><span style="color: hsl(120, 100%, 40%);">+                             "channel": {</span><br><span style="color: hsl(120, 100%, 40%);">+                                        "required": true,</span><br><span style="color: hsl(120, 100%, 40%);">+                                   "type": "Channel",</span><br><span style="color: hsl(120, 100%, 40%);">+                                        "description": "The Asterisk channel representing the external media"</span><br><span style="color: hsl(120, 100%, 40%);">+                             },</span><br><span style="color: hsl(120, 100%, 40%);">+                            "local_address": {</span><br><span style="color: hsl(120, 100%, 40%);">+                                  "required": false,</span><br><span style="color: hsl(120, 100%, 40%);">+                                  "type": "string",</span><br><span style="color: hsl(120, 100%, 40%);">+                                 "description": "The local ip address used" </span><br><span style="color: hsl(120, 100%, 40%);">+                               },</span><br><span style="color: hsl(120, 100%, 40%);">+                            "local_port": {</span><br><span style="color: hsl(120, 100%, 40%);">+                                     "required": false,</span><br><span style="color: hsl(120, 100%, 40%);">+                                  "type": "int",</span><br><span style="color: hsl(120, 100%, 40%);">+                                    "description": "The local ip port used" </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> }</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/c/asterisk/+/12775">change 12775</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/c/asterisk/+/12775"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: 16 </div>
<div style="display:none"> Gerrit-Change-Id: I9618899198880b4c650354581b50c0401b58bc46 </div>
<div style="display:none"> Gerrit-Change-Number: 12775 </div>
<div style="display:none"> Gerrit-PatchSet: 5 </div>
<div style="display:none"> Gerrit-Owner: George Joseph <gjoseph@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Friendly Automation </div>
<div style="display:none"> Gerrit-Reviewer: George Joseph <gjoseph@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Joshua Colp <jcolp@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Kevin Harwell <kharwell@digium.com> </div>
<div style="display:none"> Gerrit-MessageType: merged </div>