]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
ARI: External Media
authorGeorge Joseph <gjoseph@digium.com>
Mon, 5 Aug 2019 11:59:59 +0000 (05:59 -0600)
committerGeorge Joseph <gjoseph@digium.com>
Tue, 10 Sep 2019 15:44:04 +0000 (09:44 -0600)
The Channel resource has a new sub-resource "externalMedia".
This allows an application to create a channel for the sole purpose
of exchanging media with an external server.  Once created, this
channel could be placed into a bridge with existing channels to
allow the external server to inject audio into the bridge or
receive audio from the bridge.
See https://wiki.asterisk.org/wiki/display/AST/External+Media+and+ARI
for more information.

Change-Id: I9618899198880b4c650354581b50c0401b58bc46

doc/CHANGES-staging/ARI.txt [new file with mode: 0644]
res/ari/ari_model_validators.c
res/ari/ari_model_validators.h
res/ari/resource_channels.c
res/ari/resource_channels.h
res/res_ari_channels.c
rest-api/api-docs/channels.json

diff --git a/doc/CHANGES-staging/ARI.txt b/doc/CHANGES-staging/ARI.txt
new file mode 100644 (file)
index 0000000..06ac4ab
--- /dev/null
@@ -0,0 +1,10 @@
+Subject: ARI Channels
+
+The Channel resource has a new sub-resource "externalMedia".
+This allows an application to create a channel for the sole purpose
+of exchanging media with an external server.  Once created, this
+channel could be placed into a bridge with existing channels to
+allow the external server to inject audio into the bridge or
+receive audio from the bridge.
+See https://wiki.asterisk.org/wiki/display/AST/External+Media+and+ARI
+for more information.
\ No newline at end of file
index 8910bbb94a713bff9a7d3fd89a15dc9d2906c16a..3d63f5317fd2cddd400b0242e866dfc7bb121c52 100644 (file)
@@ -1385,6 +1385,62 @@ ari_validator ast_ari_validate_dialplan_cep_fn(void)
        return ast_ari_validate_dialplan_cep;
 }
 
+int ast_ari_validate_external_media(struct ast_json *json)
+{
+       int res = 1;
+       struct ast_json_iter *iter;
+       int has_channel = 0;
+
+       for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+               if (strcmp("channel", ast_json_object_iter_key(iter)) == 0) {
+                       int prop_is_valid;
+                       has_channel = 1;
+                       prop_is_valid = ast_ari_validate_channel(
+                               ast_json_object_iter_value(iter));
+                       if (!prop_is_valid) {
+                               ast_log(LOG_ERROR, "ARI ExternalMedia field channel failed validation\n");
+                               res = 0;
+                       }
+               } else
+               if (strcmp("local_address", ast_json_object_iter_key(iter)) == 0) {
+                       int prop_is_valid;
+                       prop_is_valid = ast_ari_validate_string(
+                               ast_json_object_iter_value(iter));
+                       if (!prop_is_valid) {
+                               ast_log(LOG_ERROR, "ARI ExternalMedia field local_address failed validation\n");
+                               res = 0;
+                       }
+               } else
+               if (strcmp("local_port", ast_json_object_iter_key(iter)) == 0) {
+                       int prop_is_valid;
+                       prop_is_valid = ast_ari_validate_int(
+                               ast_json_object_iter_value(iter));
+                       if (!prop_is_valid) {
+                               ast_log(LOG_ERROR, "ARI ExternalMedia field local_port failed validation\n");
+                               res = 0;
+                       }
+               } else
+               {
+                       ast_log(LOG_ERROR,
+                               "ARI ExternalMedia has undocumented field %s\n",
+                               ast_json_object_iter_key(iter));
+                       res = 0;
+               }
+       }
+
+       if (!has_channel) {
+               ast_log(LOG_ERROR, "ARI ExternalMedia missing required field channel\n");
+               res = 0;
+       }
+
+       return res;
+}
+
+ari_validator ast_ari_validate_external_media_fn(void)
+{
+       return ast_ari_validate_external_media;
+}
+
 int ast_ari_validate_rtpstat(struct ast_json *json)
 {
        int res = 1;
index f9285b43ce40a07cce3792fee984a035375c8fec..53a85739cbda1db176e04d8ec28d6f9d05636408 100644 (file)
@@ -477,6 +477,24 @@ int ast_ari_validate_dialplan_cep(struct ast_json *json);
  */
 ari_validator ast_ari_validate_dialplan_cep_fn(void);
 
+/*!
+ * \brief Validator for ExternalMedia.
+ *
+ * ExternalMedia session.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ast_ari_validate_external_media(struct ast_json *json);
+
+/*!
+ * \brief Function pointer to ast_ari_validate_external_media().
+ *
+ * See \ref ast_ari_model_validators.h for more details.
+ */
+ari_validator ast_ari_validate_external_media_fn(void);
+
 /*!
  * \brief Validator for RTPstat.
  *
@@ -1522,6 +1540,10 @@ ari_validator ast_ari_validate_application_fn(void);
  * - context: string (required)
  * - exten: string (required)
  * - priority: long (required)
+ * ExternalMedia
+ * - channel: Channel (required)
+ * - local_address: string
+ * - local_port: int
  * RTPstat
  * - channel_uniqueid: string (required)
  * - local_maxjitter: double
index d415224bc14ab24b3f329aca2baac54aea535ad2..ec0960dc897d45cb6c88688d1cbca30e641c0764 100644 (file)
@@ -1056,7 +1056,7 @@ end:
        return NULL;
 }
 
-static void ari_channels_handle_originate_with_id(const char *args_endpoint,
+static struct ast_channel *ari_channels_handle_originate_with_id(const char *args_endpoint,
        const char *args_extension,
        const char *args_context,
        long args_priority,
@@ -1094,19 +1094,19 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
                || (assignedids.uniqueid2 && AST_MAX_PUBLIC_UNIQUEID < strlen(assignedids.uniqueid2))) {
                ast_ari_response_error(response, 400, "Bad Request",
                        "Uniqueid length exceeds maximum of %d", AST_MAX_PUBLIC_UNIQUEID);
-               return;
+               return NULL;
        }
 
        if (ast_strlen_zero(args_endpoint)) {
                ast_ari_response_error(response, 400, "Bad Request",
                        "Endpoint must be specified");
-               return;
+               return NULL;
        }
 
        if (!ast_strlen_zero(args_originator) && !ast_strlen_zero(args_formats)) {
                ast_ari_response_error(response, 400, "Bad Request",
                        "Originator and formats can't both be specified");
-               return;
+               return NULL;
        }
 
        dialtech = ast_strdupa(args_endpoint);
@@ -1118,7 +1118,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
        if (ast_strlen_zero(dialtech) || ast_strlen_zero(dialdevice)) {
                ast_ari_response_error(response, 400, "Bad Request",
                        "Invalid endpoint specified");
-               return;
+               return NULL;
        }
 
        if (!ast_strlen_zero(args_app)) {
@@ -1126,7 +1126,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
 
                if (!appdata) {
                        ast_ari_response_alloc_failed(response);
-                       return;
+                       return NULL;
                }
 
                ast_str_set(&appdata, 0, "%s", args_app);
@@ -1137,7 +1137,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
                origination = ast_calloc(1, sizeof(*origination) + ast_str_size(appdata) + 1);
                if (!origination) {
                        ast_ari_response_alloc_failed(response);
-                       return;
+                       return NULL;
                }
 
                strcpy(origination->appdata, ast_str_buffer(appdata));
@@ -1145,7 +1145,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
                origination = ast_calloc(1, sizeof(*origination) + 1);
                if (!origination) {
                        ast_ari_response_alloc_failed(response);
-                       return;
+                       return NULL;
                }
 
                ast_copy_string(origination->context, S_OR(args_context, "default"), sizeof(origination->context));
@@ -1160,7 +1160,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
                                if (ipri == -1) {
                                        ast_log(AST_LOG_ERROR, "Requested label: %s can not be found in context: %s\n", args_label, args_context);
                                        ast_ari_response_error(response, 404, "Not Found", "Requested label can not be found");
-                                       return;
+                                       return NULL;
                                }
                        } else {
                                ast_debug(3, "Numeric value provided for label, jumping to that priority\n");
@@ -1170,7 +1170,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
                                ast_log(AST_LOG_ERROR, "Invalid priority label '%s' specified for extension %s in context: %s\n",
                                                args_label, args_extension, args_context);
                                ast_ari_response_error(response, 400, "Bad Request", "Requested priority is illegal");
-                               return;
+                               return NULL;
                        }
 
                        /* Our priority was provided by a label */
@@ -1184,14 +1184,14 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
        } else {
                ast_ari_response_error(response, 400, "Bad Request",
                        "Application or extension must be specified");
-               return;
+               return NULL;
        }
 
        dial = ast_dial_create();
        if (!dial) {
                ast_ari_response_alloc_failed(response);
                ast_free(origination);
-               return;
+               return NULL;
        }
        ast_dial_set_user_data(dial, origination);
 
@@ -1199,7 +1199,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
                ast_ari_response_alloc_failed(response);
                ast_dial_destroy(dial);
                ast_free(origination);
-               return;
+               return NULL;
        }
 
        if (args_timeout > 0) {
@@ -1227,7 +1227,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
                                "Provided originator channel was not found");
                        ast_dial_destroy(dial);
                        ast_free(origination);
-                       return;
+                       return NULL;
                }
        }
 
@@ -1240,7 +1240,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
                        ast_dial_destroy(dial);
                        ast_free(origination);
                        ast_channel_cleanup(other);
-                       return;
+                       return NULL;
                }
 
                while ((format_name = ast_strip(strsep(&formats_copy, ",")))) {
@@ -1259,7 +1259,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
                                ast_channel_cleanup(other);
                                ao2_ref(format_cap, -1);
                                ao2_cleanup(fmt);
-                               return;
+                               return NULL;
                        }
                        ao2_ref(fmt, -1);
                }
@@ -1275,7 +1275,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
                ast_dial_destroy(dial);
                ast_free(origination);
                ast_channel_cleanup(other);
-               return;
+               return NULL;
        }
 
        ast_channel_cleanup(other);
@@ -1286,7 +1286,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
                ast_ari_response_alloc_failed(response);
                ast_dial_destroy(dial);
                ast_free(origination);
-               return;
+               return NULL;
        }
 
        if (!ast_strlen_zero(cid_num) || !ast_strlen_zero(cid_name)) {
@@ -1351,8 +1351,7 @@ static void ari_channels_handle_originate_with_id(const char *args_endpoint,
                ast_ari_response_ok(response, ast_channel_snapshot_to_json(snapshot, NULL));
        }
 
-       ast_channel_unref(chan);
-       return;
+       return chan;
 }
 
 /*!
@@ -1393,6 +1392,7 @@ void ast_ari_channels_originate_with_id(struct ast_variable *headers,
        struct ast_ari_response *response)
 {
        struct ast_variable *variables = NULL;
+       struct ast_channel *chan;
 
        /* Parse any query parameters out of the body parameter */
        if (args->variables) {
@@ -1406,7 +1406,7 @@ void ast_ari_channels_originate_with_id(struct ast_variable *headers,
                }
        }
 
-       ari_channels_handle_originate_with_id(
+       chan = ari_channels_handle_originate_with_id(
                args->endpoint,
                args->extension,
                args->context,
@@ -1422,6 +1422,7 @@ void ast_ari_channels_originate_with_id(struct ast_variable *headers,
                args->originator,
                args->formats,
                response);
+       ast_channel_cleanup(chan);
        ast_variables_destroy(variables);
 }
 
@@ -1430,6 +1431,7 @@ void ast_ari_channels_originate(struct ast_variable *headers,
        struct ast_ari_response *response)
 {
        struct ast_variable *variables = NULL;
+       struct ast_channel *chan;
 
        /* Parse any query parameters out of the body parameter */
        if (args->variables) {
@@ -1443,7 +1445,7 @@ void ast_ari_channels_originate(struct ast_variable *headers,
                }
        }
 
-       ari_channels_handle_originate_with_id(
+       chan = ari_channels_handle_originate_with_id(
                args->endpoint,
                args->extension,
                args->context,
@@ -1459,6 +1461,7 @@ void ast_ari_channels_originate(struct ast_variable *headers,
                args->originator,
                args->formats,
                response);
+       ast_channel_cleanup(chan);
        ast_variables_destroy(variables);
 }
 
@@ -2049,3 +2052,148 @@ void ast_ari_channels_rtpstatistics(struct ast_variable *headers,
 
        return;
 }
+
+static void external_media_rtp_udp(struct ast_ari_channels_external_media_args *args,
+       struct ast_variable *variables,
+       struct ast_ari_response *response)
+{
+       size_t endpoint_len;
+       char *endpoint;
+       struct ast_channel *chan;
+       struct ast_json *json_chan;
+       struct varshead *vars;
+
+       endpoint_len = strlen("UnicastRTP/") + strlen(args->external_host) + 1;
+       endpoint = ast_alloca(endpoint_len);
+       snprintf(endpoint, endpoint_len, "UnicastRTP/%s", args->external_host);
+
+       chan = ari_channels_handle_originate_with_id(
+               endpoint,
+               NULL,
+               NULL,
+               0,
+               NULL,
+               args->app,
+               NULL,
+               NULL,
+               0,
+               variables,
+               args->channel_id,
+               NULL,
+               NULL,
+               args->format,
+               response);
+       ast_variables_destroy(variables);
+
+       if (!chan) {
+               return;
+       }
+
+       /*
+        * At this point, response->message contains a channel object so we
+        * need to save it then create a new ExternalMedia object and put the
+        * channel in it.
+        */
+       json_chan = response->message;
+       response->message = ast_json_object_create();
+       if (!response->message) {
+               ast_channel_unref(chan);
+               ast_json_unref(json_chan);
+               ast_ari_response_alloc_failed(response);
+               return;
+       }
+
+       ast_json_object_set(response->message, "channel", json_chan);
+       /*
+        * At the time the channel snapshot was taken the channel variables might
+        * not have been set so we try to grab them directly from the channel.
+        */
+       ast_channel_lock(chan);
+       vars = ast_channel_varshead(chan);
+       if (vars && !AST_LIST_EMPTY(vars)) {
+               struct ast_var_t *variables;
+
+               /* Put them all on the channel object */
+               ast_json_object_set(json_chan, "channelvars", ast_json_channel_vars(vars));
+               /* Grab out the local address and port */
+               AST_LIST_TRAVERSE(vars, variables, entries) {
+                       if (!strcmp("UNICASTRTP_LOCAL_ADDRESS", ast_var_name(variables))) {
+                               ast_json_object_set(response->message, "local_address",
+                                       ast_json_string_create(ast_var_value(variables)));
+                       }
+                       else if (!strcmp("UNICASTRTP_LOCAL_PORT", ast_var_name(variables))) {
+                               ast_json_object_set(response->message, "local_port",
+                                       ast_json_integer_create(strtol(ast_var_value(variables), NULL, 10)));
+                       }
+               }
+       }
+       ast_channel_unlock(chan);
+       ast_channel_unref(chan);
+}
+
+#include "asterisk/config.h"
+#include "asterisk/netsock2.h"
+
+void ast_ari_channels_external_media(struct ast_variable *headers,
+       struct ast_ari_channels_external_media_args *args, struct ast_ari_response *response)
+{
+       struct ast_variable *variables = NULL;
+       char *external_host;
+       char *host = NULL;
+       char *port = NULL;
+
+       ast_assert(response != NULL);
+
+       if (ast_strlen_zero(args->app)) {
+               ast_ari_response_error(response, 400, "Bad Request", "app cannot be empty");
+               return;
+       }
+
+       if (ast_strlen_zero(args->external_host)) {
+               ast_ari_response_error(response, 400, "Bad Request", "external_host cannot be empty");
+               return;
+       }
+
+       external_host = ast_strdupa(args->external_host);
+       if (!ast_sockaddr_split_hostport(external_host, &host, &port, PARSE_PORT_REQUIRE)) {
+               ast_ari_response_error(response, 400, "Bad Request", "external_host must be <host>:<port>");
+               return;
+       }
+
+       if (ast_strlen_zero(args->format)) {
+               ast_ari_response_error(response, 400, "Bad Request", "format cannot be empty");
+               return;
+       }
+
+       if (ast_strlen_zero(args->encapsulation)) {
+               args->encapsulation = "rtp";
+       }
+       if (ast_strlen_zero(args->transport)) {
+               args->transport = "udp";
+       }
+       if (ast_strlen_zero(args->connection_type)) {
+               args->connection_type = "client";
+       }
+       if (ast_strlen_zero(args->direction)) {
+               args->direction = "both";
+       }
+
+       if (args->variables) {
+               struct ast_json *json_variables;
+
+               ast_ari_channels_external_media_parse_body(args->variables, args);
+               json_variables = ast_json_object_get(args->variables, "variables");
+               if (json_variables
+                       && json_to_ast_variables(response, json_variables, &variables)) {
+                       return;
+               }
+       }
+
+       if (strcasecmp(args->encapsulation, "rtp") == 0 && strcasecmp(args->transport, "udp") == 0) {
+               external_media_rtp_udp(args, variables, response);
+       } else {
+               ast_ari_response_error(
+                       response, 501, "Not Implemented",
+                       "The encapsulation and/or transport is not supported");
+       }
+}
index 401c8a5651a1933b1e129ded841621b38dde9ba4..7c70cec8339713d65e1366d82ff47b128f27b4d8 100644 (file)
@@ -822,5 +822,47 @@ struct ast_ari_channels_rtpstatistics_args {
  * \param[out] response HTTP response
  */
 void ast_ari_channels_rtpstatistics(struct ast_variable *headers, struct ast_ari_channels_rtpstatistics_args *args, struct ast_ari_response *response);
+/*! Argument struct for ast_ari_channels_external_media() */
+struct ast_ari_channels_external_media_args {
+       /*! The unique id to assign the channel on creation. */
+       const char *channel_id;
+       /*! Stasis Application to place channel into */
+       const char *app;
+       /*! The "variables" key in the body object holds variable key/value pairs to set on the channel on creation. Other keys in the body object are interpreted as query parameters. Ex. { "endpoint": "SIP/Alice", "variables": { "CALLERID(name)": "Alice" } } */
+       struct ast_json *variables;
+       /*! Hostname/ip:port of external host */
+       const char *external_host;
+       /*! Payload encapsulation protocol */
+       const char *encapsulation;
+       /*! Transport protocol */
+       const char *transport;
+       /*! Connection type (client/server) */
+       const char *connection_type;
+       /*! Format to encode audio in */
+       const char *format;
+       /*! External media direction */
+       const char *direction;
+};
+/*!
+ * \brief Body parsing function for /channels/externalMedia.
+ * \param body The JSON body from which to parse parameters.
+ * \param[out] args The args structure to parse into.
+ * \retval zero on success
+ * \retval non-zero on failure
+ */
+int ast_ari_channels_external_media_parse_body(
+       struct ast_json *body,
+       struct ast_ari_channels_external_media_args *args);
+
+/*!
+ * \brief Start an External Media session.
+ *
+ * Create a channel to an External Media source/sink.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_channels_external_media(struct ast_variable *headers, struct ast_ari_channels_external_media_args *args, struct ast_ari_response *response);
 
 #endif /* _ASTERISK_RESOURCE_CHANNELS_H */
index 73d1e4b2df25841694ba5f77bf098d6b90afd6c6..fe3a96ce48bc9186e58cbfba58d8462668cca448 100644 (file)
@@ -2804,6 +2804,128 @@ static void ast_ari_channels_rtpstatistics_cb(
        }
 #endif /* AST_DEVMODE */
 
+fin: __attribute__((unused))
+       return;
+}
+int ast_ari_channels_external_media_parse_body(
+       struct ast_json *body,
+       struct ast_ari_channels_external_media_args *args)
+{
+       struct ast_json *field;
+       /* Parse query parameters out of it */
+       field = ast_json_object_get(body, "channelId");
+       if (field) {
+               args->channel_id = ast_json_string_get(field);
+       }
+       field = ast_json_object_get(body, "app");
+       if (field) {
+               args->app = ast_json_string_get(field);
+       }
+       field = ast_json_object_get(body, "external_host");
+       if (field) {
+               args->external_host = ast_json_string_get(field);
+       }
+       field = ast_json_object_get(body, "encapsulation");
+       if (field) {
+               args->encapsulation = ast_json_string_get(field);
+       }
+       field = ast_json_object_get(body, "transport");
+       if (field) {
+               args->transport = ast_json_string_get(field);
+       }
+       field = ast_json_object_get(body, "connection_type");
+       if (field) {
+               args->connection_type = ast_json_string_get(field);
+       }
+       field = ast_json_object_get(body, "format");
+       if (field) {
+               args->format = ast_json_string_get(field);
+       }
+       field = ast_json_object_get(body, "direction");
+       if (field) {
+               args->direction = ast_json_string_get(field);
+       }
+       return 0;
+}
+
+/*!
+ * \brief Parameter parsing callback for /channels/externalMedia.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void ast_ari_channels_external_media_cb(
+       struct ast_tcptls_session_instance *ser,
+       struct ast_variable *get_params, struct ast_variable *path_vars,
+       struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response)
+{
+       struct ast_ari_channels_external_media_args args = {};
+       struct ast_variable *i;
+#if defined(AST_DEVMODE)
+       int is_valid;
+       int code;
+#endif /* AST_DEVMODE */
+
+       for (i = get_params; i; i = i->next) {
+               if (strcmp(i->name, "channelId") == 0) {
+                       args.channel_id = (i->value);
+               } else
+               if (strcmp(i->name, "app") == 0) {
+                       args.app = (i->value);
+               } else
+               if (strcmp(i->name, "external_host") == 0) {
+                       args.external_host = (i->value);
+               } else
+               if (strcmp(i->name, "encapsulation") == 0) {
+                       args.encapsulation = (i->value);
+               } else
+               if (strcmp(i->name, "transport") == 0) {
+                       args.transport = (i->value);
+               } else
+               if (strcmp(i->name, "connection_type") == 0) {
+                       args.connection_type = (i->value);
+               } else
+               if (strcmp(i->name, "format") == 0) {
+                       args.format = (i->value);
+               } else
+               if (strcmp(i->name, "direction") == 0) {
+                       args.direction = (i->value);
+               } else
+               {}
+       }
+       args.variables = body;
+       ast_ari_channels_external_media(headers, &args, response);
+#if defined(AST_DEVMODE)
+       code = response->response_code;
+
+       switch (code) {
+       case 0: /* Implementation is still a stub, or the code wasn't set */
+               is_valid = response->message == NULL;
+               break;
+       case 500: /* Internal Server Error */
+       case 501: /* Not Implemented */
+       case 400: /* Invalid parameters */
+       case 409: /* Channel is not in a Stasis application; Channel is already bridged */
+               is_valid = 1;
+               break;
+       default:
+               if (200 <= code && code <= 299) {
+                       is_valid = ast_ari_validate_external_media(
+                               response->message);
+               } else {
+                       ast_log(LOG_ERROR, "Invalid error response %d for /channels/externalMedia\n", code);
+                       is_valid = 0;
+               }
+       }
+
+       if (!is_valid) {
+               ast_log(LOG_ERROR, "Response validation failed for /channels/externalMedia\n");
+               ast_ari_response_error(response, 500,
+                       "Internal Server Error", "Response validation failed");
+       }
+#endif /* AST_DEVMODE */
+
 fin: __attribute__((unused))
        return;
 }
@@ -3000,14 +3122,23 @@ static struct stasis_rest_handlers channels_channelId = {
        .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, }
 };
 /*! \brief REST handler for /api-docs/channels.json */
+static struct stasis_rest_handlers channels_externalMedia = {
+       .path_segment = "externalMedia",
+       .callbacks = {
+               [AST_HTTP_POST] = ast_ari_channels_external_media_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/channels.json */
 static struct stasis_rest_handlers channels = {
        .path_segment = "channels",
        .callbacks = {
                [AST_HTTP_GET] = ast_ari_channels_list_cb,
                [AST_HTTP_POST] = ast_ari_channels_originate_cb,
        },
-       .num_children = 2,
-       .children = { &channels_create,&channels_channelId, }
+       .num_children = 3,
+       .children = { &channels_create,&channels_channelId,&channels_externalMedia, }
 };
 
 static int unload_module(void)
index 4ea5f5647cce6819e46fec995b7b3c26ad32f963..6d964d2302a7e7e23012f09b22233967abe20967 100644 (file)
                                        ]
                                }
                        ]
+               },
+               {
+                       "path": "/channels/externalMedia",
+                       "description": "Create a channel to an External Media source/sink.",
+                       "operations": [
+                               {
+                                       "httpMethod": "POST",
+                                       "summary": "Start an External Media session.",
+                                       "notes": "Create a channel to an External Media source/sink.",
+                                       "nickname": "externalMedia",
+                                       "responseClass": "ExternalMedia",
+                                       "parameters": [
+                                               {
+                                                       "name": "channelId",
+                                                       "description": "The unique id to assign the channel on creation.",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               },
+                                               {
+                                                       "name": "app",
+                                                       "description": "Stasis Application to place channel into",
+                                                       "paramType": "query",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               },
+                                               {
+                                                       "name": "variables",
+                                                       "description": "The \"variables\" key in the body object holds variable key/value pairs to set on the channel on creation. Other keys in the body object are interpreted as query parameters. Ex. { \"endpoint\": \"SIP/Alice\", \"variables\": { \"CALLERID(name)\": \"Alice\" } }",
+                                                       "paramType": "body",
+                                                       "required": false,
+                                                       "dataType": "containers",
+                                                       "allowMultiple": false
+                                               },
+                                               {
+                                                       "name": "external_host",
+                                                       "description": "Hostname/ip:port of external host",
+                                                       "paramType": "query",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               },
+                                               {
+                                                       "name": "encapsulation",
+                                                       "description": "Payload encapsulation protocol",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string",
+                                                       "defaultValue": "rtp",
+                                                       "allowableValues": {
+                                                               "valueType": "LIST",
+                                                               "values": [
+                                                                       "rtp"
+                                                               ]
+                                                       }
+                                               },
+                                               {
+                                                       "name": "transport",
+                                                       "description": "Transport protocol",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string",
+                                                       "defaultValue": "udp",
+                                                       "allowableValues": {
+                                                               "valueType": "LIST",
+                                                               "values": [
+                                                                       "udp"
+                                                               ]
+                                                       }
+                                               },
+                                               {
+                                                       "name": "connection_type",
+                                                       "description": "Connection type (client/server)",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string",
+                                                       "defaultValue": "client",
+                                                       "allowableValues": {
+                                                               "valueType": "LIST",
+                                                               "values": [
+                                                                       "client"
+                                                               ]
+                                                       }
+                                               },
+                                               {
+                                                       "name": "format",
+                                                       "description": "Format to encode audio in",
+                                                       "paramType": "query",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               },
+                                               {
+                                                       "name": "direction",
+                                                       "description": "External media direction",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string",
+                                                       "defaultValue": "both",
+                                                       "allowableValues": {
+                                                               "valueType": "LIST",
+                                                               "values": [
+                                                                       "both"
+                                                               ]
+                                                       }
+                                               }
+                                       ],
+                                       "errorResponses": [
+                                               {
+                                                       "code": 400,
+                                                       "reason": "Invalid parameters"
+                                               },
+                                               {
+                                                       "code": 409,
+                                                       "reason": "Channel is not in a Stasis application; Channel is already bridged"
+                                               }
+                                       ]
+                               }
+                       ]
                }
        ],
        "models": {
                                        "description": "Channel variables"
                                }
                        }
+               },
+               "ExternalMedia": {
+                       "id": "ExternalMedia",
+                       "description": "ExternalMedia session.",
+                       "properties": {
+                               "channel": {
+                                       "required": true,
+                                       "type": "Channel",
+                                       "description": "The Asterisk channel representing the external media"
+                               },
+                               "local_address": {
+                                       "required": false,
+                                       "type": "string",
+                                       "description": "The local ip address used" 
+                               },
+                               "local_port": {
+                                       "required": false,
+                                       "type": "int",
+                                       "description": "The local ip port used" 
+                               }
+                       }
                }
        }
 }