<p>Benjamin Keith Ford has uploaded this change for <strong>review</strong>.</p><p><a href="https://gerrit.asterisk.org/10882">View Change</a></p><pre style="font-family: monospace,monospace; white-space: pre-wrap;">res_stasis: Auto-create context and add ability to switch applications.<br><br>At AstriCon, there was a strong desire for the ability to completely<br>bypass dialplan when using ARI. This is possible through the automatic<br>creation of a context and a couple of extensions whenever an application<br>is started.<br><br>For example, if you have an application named 'ari-example', a context<br>named 'stasis:ari-example' will be automatically created whenever this<br>application is started. Two extensions (a match-all extension for Stasis<br>and a 'h' extension) are created within this context. Any endpoint that<br>registers to Asterisk within this context will send all calls to the<br>corresponding Stasis application. When the application is destroyed, the<br>context is removed.<br><br>Another new feature is the ability to move between Stasis applications<br>within Stasis itself. This can be done by calling 'move' in an<br>application, providing (at minimum) the channel's id and the application<br>to switch to. If the application is not registered or active, nothing<br>will happen and the channel will remain in the current application.<br>Optionally, a list of arguments can be passed to the function call for<br>the receiving application. A full example of a 'move' call would look<br>like this:<br><br>client.channels.move(channelId, app, appArgs)<br><br>The control object used to control the channel in Stasis can now switch<br>which application it belongs to, rather than belonging to one Stasis<br>application for its lifetime. This allows us to use the same control<br>object instead of having to tear down the current one and create<br>another.<br><br>ASTERISK-28104 #close<br><br>Change-Id: Ib6c569468472dbb08905b356887373c81e03015d<br>---<br>M CHANGES<br>M include/asterisk/stasis_app.h<br>M res/ari/resource_channels.c<br>M res/ari/resource_channels.h<br>M res/res_ari_channels.c<br>M res/res_stasis.c<br>M res/stasis/app.c<br>M res/stasis/control.c<br>M res/stasis/control.h<br>M rest-api/api-docs/channels.json<br>10 files changed, 504 insertions(+), 9 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/82/10882/1</pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/CHANGES b/CHANGES</span><br><span>index da58f29..c4fa372 100644</span><br><span>--- a/CHANGES</span><br><span>+++ b/CHANGES</span><br><span>@@ -12,6 +12,26 @@</span><br><span> --- Functionality changes from Asterisk 13.24.0 to Asterisk 13.25.0 ----------</span><br><span> ------------------------------------------------------------------------------</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+ARI</span><br><span style="color: hsl(120, 100%, 40%);">+------------------</span><br><span style="color: hsl(120, 100%, 40%);">+ * Whenever an ARI application is started, a context will be created for it</span><br><span style="color: hsl(120, 100%, 40%);">+   automatically, following the format 'stasis:<app_name>'. Two extensions</span><br><span style="color: hsl(120, 100%, 40%);">+   are also added to this context: a match-all extension, and the 'h'</span><br><span style="color: hsl(120, 100%, 40%);">+   extension. Any phone that registers under this context will place all calls</span><br><span style="color: hsl(120, 100%, 40%);">+   to the corresponding Stasis application.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   To go along with this, a new REST API call has been added: 'move'. It</span><br><span style="color: hsl(120, 100%, 40%);">+   follows the format 'channels/{channelId}/move' and can be used to move</span><br><span style="color: hsl(120, 100%, 40%);">+   channels from one application to another without needing to exit back into</span><br><span style="color: hsl(120, 100%, 40%);">+   the dialplan. An application must be specified, but the passing a list of</span><br><span style="color: hsl(120, 100%, 40%);">+   arguments to the new application is optional. An example call would look</span><br><span style="color: hsl(120, 100%, 40%);">+   like this:</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   client.channels.move(channelId=chan.id, app='ari-example', appArgs='a,b,c')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   If the channel was inside of a bridge when switching applications, it will</span><br><span style="color: hsl(120, 100%, 40%);">+   remain there.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> res_pjsip</span><br><span> ------------------</span><br><span>  * Added "send_contact_status_on_update_registration" global configuration option</span><br><span>diff --git a/include/asterisk/stasis_app.h b/include/asterisk/stasis_app.h</span><br><span>index 90a7ea3..e887d14 100644</span><br><span>--- a/include/asterisk/stasis_app.h</span><br><span>+++ b/include/asterisk/stasis_app.h</span><br><span>@@ -520,6 +520,20 @@</span><br><span> int stasis_app_control_continue(struct stasis_app_control *control, const char *context, const char *extension, int priority);</span><br><span> </span><br><span> /*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Exit \c res_stasis and move to another Stasis application.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * If the channel is no longer in \c res_stasis, this function does nothing.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param control Control for \c res_stasis</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param app_name The name of the application to switch to</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param app_args The list of arguments to pass to the application</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \return 0 for success</span><br><span style="color: hsl(120, 100%, 40%);">+ * \return -1 for error</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+int stasis_app_control_move(struct stasis_app_control *control, const char *app_name, const char *app_args);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span>  * \brief Redirect a channel in \c res_stasis to a particular endpoint</span><br><span>  *</span><br><span>  * \param control Control for \c res_stasis</span><br><span>diff --git a/res/ari/resource_channels.c b/res/ari/resource_channels.c</span><br><span>index 04db704..f5065f5 100644</span><br><span>--- a/res/ari/resource_channels.c</span><br><span>+++ b/res/ari/resource_channels.c</span><br><span>@@ -159,6 +159,26 @@</span><br><span>         ast_ari_response_no_content(response);</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+void ast_ari_channels_move(struct ast_variable *headers,</span><br><span style="color: hsl(120, 100%, 40%);">+     struct ast_ari_channels_move_args *args,</span><br><span style="color: hsl(120, 100%, 40%);">+      struct ast_ari_response *response)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+  control = find_control(response, args->channel_id);</span><br><span style="color: hsl(120, 100%, 40%);">+        if (!control) {</span><br><span style="color: hsl(120, 100%, 40%);">+               return;</span><br><span style="color: hsl(120, 100%, 40%);">+       }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   if (stasis_app_control_move(control, args->app, args->app_args)) {</span><br><span style="color: hsl(120, 100%, 40%);">+              ast_ari_response_error(response, 500, "Internal Server Error",</span><br><span style="color: hsl(120, 100%, 40%);">+                      "Failed to switch Stasis applications");</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_ari_response_no_content(response);</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> void ast_ari_channels_redirect(struct ast_variable *headers,</span><br><span>  struct ast_ari_channels_redirect_args *args,</span><br><span>         struct ast_ari_response *response)</span><br><span>diff --git a/res/ari/resource_channels.h b/res/ari/resource_channels.h</span><br><span>index dd20415..b9e5d02 100644</span><br><span>--- a/res/ari/resource_channels.h</span><br><span>+++ b/res/ari/resource_channels.h</span><br><span>@@ -225,6 +225,34 @@</span><br><span>  * \param[out] response HTTP response</span><br><span>  */</span><br><span> void ast_ari_channels_continue_in_dialplan(struct ast_variable *headers, struct ast_ari_channels_continue_in_dialplan_args *args, struct ast_ari_response *response);</span><br><span style="color: hsl(120, 100%, 40%);">+/*! Argument struct for ast_ari_channels_move() */</span><br><span style="color: hsl(120, 100%, 40%);">+struct ast_ari_channels_move_args {</span><br><span style="color: hsl(120, 100%, 40%);">+  /*! Channel's id */</span><br><span style="color: hsl(120, 100%, 40%);">+       const char *channel_id;</span><br><span style="color: hsl(120, 100%, 40%);">+       /*! The channel will be passed to this Stasis application. */</span><br><span style="color: hsl(120, 100%, 40%);">+ const char *app;</span><br><span style="color: hsl(120, 100%, 40%);">+      /*! The application arguments to pass to the Stasis application provided by 'app'. */</span><br><span style="color: hsl(120, 100%, 40%);">+ const char *app_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 Body parsing function for /channels/{channelId}/move.</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_move_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_move_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 Move the channel from one Stasis application to another.</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_move(struct ast_variable *headers, struct ast_ari_channels_move_args *args, struct ast_ari_response *response);</span><br><span> /*! Argument struct for ast_ari_channels_redirect() */</span><br><span> struct ast_ari_channels_redirect_args {</span><br><span>  /*! Channel's id */</span><br><span>diff --git a/res/res_ari_channels.c b/res/res_ari_channels.c</span><br><span>index dafca15..4eecd45 100644</span><br><span>--- a/res/res_ari_channels.c</span><br><span>+++ b/res/res_ari_channels.c</span><br><span>@@ -658,6 +658,96 @@</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_move_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_move_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, "appArgs");</span><br><span style="color: hsl(120, 100%, 40%);">+       if (field) {</span><br><span style="color: hsl(120, 100%, 40%);">+          args->app_args = 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/{channelId}/move.</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_move_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_move_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, "appArgs") == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+                   args.app_args = (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%);">+     if (ast_ari_channels_move_parse_body(body, &args)) {</span><br><span style="color: hsl(120, 100%, 40%);">+              ast_ari_response_alloc_failed(response);</span><br><span style="color: hsl(120, 100%, 40%);">+              goto fin;</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+     ast_ari_channels_move(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 404: /* Channel not found */</span><br><span style="color: hsl(120, 100%, 40%);">+     case 409: /* Channel not in a Stasis application */</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_void(</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/{channelId}/move\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/{channelId}/move\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> int ast_ari_channels_redirect_parse_body(</span><br><span>      struct ast_json *body,</span><br><span>       struct ast_ari_channels_redirect_args *args)</span><br><span>@@ -2313,6 +2403,15 @@</span><br><span>        .children = {  }</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_channelId_move = {</span><br><span style="color: hsl(120, 100%, 40%);">+      .path_segment = "move",</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_move_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_channelId_redirect = {</span><br><span>   .path_segment = "redirect",</span><br><span>        .callbacks = {</span><br><span>@@ -2455,8 +2554,8 @@</span><br><span>               [AST_HTTP_POST] = ast_ari_channels_originate_with_id_cb,</span><br><span>             [AST_HTTP_DELETE] = ast_ari_channels_hangup_cb,</span><br><span>      },</span><br><span style="color: hsl(0, 100%, 40%);">-      .num_children = 13,</span><br><span style="color: hsl(0, 100%, 40%);">-     .children = { &channels_channelId_continue,&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, }</span><br><span style="color: hsl(120, 100%, 40%);">+  .num_children = 14,</span><br><span style="color: hsl(120, 100%, 40%);">+   .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, }</span><br><span> };</span><br><span> /*! \brief REST handler for /api-docs/channels.json */</span><br><span> static struct stasis_rest_handlers channels = {</span><br><span>diff --git a/res/res_stasis.c b/res/res_stasis.c</span><br><span>index 127d0e8..8e085d8 100644</span><br><span>--- a/res/res_stasis.c</span><br><span>+++ b/res/res_stasis.c</span><br><span>@@ -1330,15 +1330,76 @@</span><br><span>                  break;</span><br><span>               }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+         if (control_next_app(control)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                      RAII_VAR(struct stasis_app *, last_app, NULL, ao2_cleanup);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                 last_app = ao2_find(apps_registry, stasis_app_name(control_app(control)), OBJ_SEARCH_KEY);</span><br><span style="color: hsl(120, 100%, 40%);">+                    app = ao2_find(apps_registry, control_next_app(control), OBJ_SEARCH_KEY);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                   if (app && app_is_active(app)) {</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                            if (!stasis_app_channel_is_stasis_end_published(chan)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                                      res = has_masquerade_store(chan) && app_send_end_msg(last_app, chan);</span><br><span style="color: hsl(120, 100%, 40%);">+                                 if (res != 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+                                               ast_log(LOG_ERROR,</span><br><span style="color: hsl(120, 100%, 40%);">+                                                    "Error sending end message to %s\n", stasis_app_name(last_app));</span><br><span style="color: hsl(120, 100%, 40%);">+                                            ao2_ref(last_app, -1);</span><br><span style="color: hsl(120, 100%, 40%);">+                                                control_mark_done(control);</span><br><span style="color: hsl(120, 100%, 40%);">+                                           break;</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%);">+                                      remove_stasis_end_published(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%);">+                           /* We bumped this when trying to find it, so we need to decrement */</span><br><span style="color: hsl(120, 100%, 40%);">+                          ao2_ref(last_app, -1);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                              cleanup();</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                          ao2_cleanup(last_app);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                              /* This calls ao2_bump! */</span><br><span style="color: hsl(120, 100%, 40%);">+                            control_set_app(control, app);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                              /* We need to add another masquerade store, otherwise the leave message will</span><br><span style="color: hsl(120, 100%, 40%);">+                           * not show up for the correct application.</span><br><span style="color: hsl(120, 100%, 40%);">+                            */</span><br><span style="color: hsl(120, 100%, 40%);">+                           if (add_masquerade_store(chan)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                                     ast_log(LOG_ERROR, "Failed to attach masquerade detector\n");</span><br><span style="color: hsl(120, 100%, 40%);">+                                       res = -1;</span><br><span style="color: hsl(120, 100%, 40%);">+                                     control_mark_done(control);</span><br><span style="color: hsl(120, 100%, 40%);">+                                   break;</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%);">+                           res = send_start_msg(control_app(control), chan, control_argc(control), control_argv(control));</span><br><span style="color: hsl(120, 100%, 40%);">+                               if (res != 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+                                       ast_log(LOG_ERROR,</span><br><span style="color: hsl(120, 100%, 40%);">+                                            "Error sending start message to '%s'\n", stasis_app_name(control_app(control)));</span><br><span style="color: hsl(120, 100%, 40%);">+                                    remove_masquerade_store(chan);</span><br><span style="color: hsl(120, 100%, 40%);">+                                        control_mark_done(control);</span><br><span style="color: hsl(120, 100%, 40%);">+                                   break;</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%);">+                           /* Done switching applications, free memory and clean up */</span><br><span style="color: hsl(120, 100%, 40%);">+                           control_move_cleanup(control);</span><br><span style="color: hsl(120, 100%, 40%);">+                        } else {</span><br><span style="color: hsl(120, 100%, 40%);">+                              /* If we can't switch applications, do nothing */</span><br><span style="color: hsl(120, 100%, 40%);">+                         ast_log(LOG_ERROR, "Could not move to Stasis app '%s' - not registered or not active\n",</span><br><span style="color: hsl(120, 100%, 40%);">+                                    control_next_app(control));</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%);">+                   control_move_cleanup(control);</span><br><span style="color: hsl(120, 100%, 40%);">+                }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>          last_bridge = bridge;</span><br><span>                bridge = ao2_bump(stasis_app_get_bridge(control));</span><br><span> </span><br><span>               if (bridge != last_bridge) {</span><br><span>                         if (last_bridge) {</span><br><span style="color: hsl(0, 100%, 40%);">-                              app_unsubscribe_bridge(app, last_bridge);</span><br><span style="color: hsl(120, 100%, 40%);">+                             app_unsubscribe_bridge(control_app(control), last_bridge);</span><br><span>                   }</span><br><span>                    if (bridge) {</span><br><span style="color: hsl(0, 100%, 40%);">-                           app_subscribe_bridge(app, bridge);</span><br><span style="color: hsl(120, 100%, 40%);">+                            app_subscribe_bridge(control_app(control), bridge);</span><br><span>                  }</span><br><span>            }</span><br><span> </span><br><span>@@ -1398,18 +1459,23 @@</span><br><span>      }</span><br><span> </span><br><span>        if (stasis_app_get_bridge(control)) {</span><br><span style="color: hsl(0, 100%, 40%);">-           app_unsubscribe_bridge(app, stasis_app_get_bridge(control));</span><br><span style="color: hsl(120, 100%, 40%);">+          app_unsubscribe_bridge(control_app(control), stasis_app_get_bridge(control));</span><br><span>        }</span><br><span>    ao2_cleanup(bridge);</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+      /* If something went wrong while moving applications, res will not be zero - return here */</span><br><span style="color: hsl(120, 100%, 40%);">+   if (res != 0) {</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>  /* Only publish a stasis_end event if it hasn't already been published */</span><br><span>        if (!stasis_app_channel_is_stasis_end_published(chan)) {</span><br><span>             /* A masquerade has occurred and this message will be wrong so it</span><br><span>             * has already been sent elsewhere. */</span><br><span style="color: hsl(0, 100%, 40%);">-          res = has_masquerade_store(chan) && app_send_end_msg(app, chan);</span><br><span style="color: hsl(120, 100%, 40%);">+              res = has_masquerade_store(chan) && app_send_end_msg(control_app(control), chan);</span><br><span>            if (res != 0) {</span><br><span>                      ast_log(LOG_ERROR,</span><br><span style="color: hsl(0, 100%, 40%);">-                              "Error sending end message to %s\n", app_name);</span><br><span style="color: hsl(120, 100%, 40%);">+                             "Error sending end message to %s\n", stasis_app_name(control_app(control)));</span><br><span>                       return res;</span><br><span>          }</span><br><span>    } else {</span><br><span>@@ -1429,8 +1495,9 @@</span><br><span>     /* The control needs to be removed from the controls container in</span><br><span>     * case a new PBX is started and ends up coming back into Stasis.</span><br><span>     */</span><br><span style="color: hsl(0, 100%, 40%);">-     ao2_cleanup(app);</span><br><span style="color: hsl(120, 100%, 40%);">+     ao2_cleanup(control_app(control));</span><br><span>   app = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>        control_unlink(control);</span><br><span>     control = NULL;</span><br><span> </span><br><span>diff --git a/res/stasis/app.c b/res/stasis/app.c</span><br><span>index ccb93bc..c765a7a 100644</span><br><span>--- a/res/stasis/app.c</span><br><span>+++ b/res/stasis/app.c</span><br><span>@@ -299,6 +299,8 @@</span><br><span> static void app_dtor(void *obj)</span><br><span> {</span><br><span>       struct stasis_app *app = obj;</span><br><span style="color: hsl(120, 100%, 40%);">+ size_t size = strlen("stasis:") + strlen(app->name) + 1;</span><br><span style="color: hsl(120, 100%, 40%);">+ char context_name[size];</span><br><span> </span><br><span>         ast_verb(1, "Destroying Stasis app %s\n", app->name);</span><br><span> </span><br><span>@@ -306,6 +308,10 @@</span><br><span>        ast_assert(app->bridge_router == NULL);</span><br><span>   ast_assert(app->endpoint_router == NULL);</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+      /* Remove the context we created for this application */</span><br><span style="color: hsl(120, 100%, 40%);">+      strcat(strcpy(context_name, "stasis:"), app->name);</span><br><span style="color: hsl(120, 100%, 40%);">+      ast_context_destroy_by_name(context_name, "res_stasis");</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>         ao2_cleanup(app->topic);</span><br><span>  app->topic = NULL;</span><br><span>        ao2_cleanup(app->forwards);</span><br><span>@@ -934,6 +940,8 @@</span><br><span>         RAII_VAR(struct stasis_app *, app, NULL, ao2_cleanup);</span><br><span>       size_t size;</span><br><span>         int res = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+  size_t context_size = strlen("stasis:") + strlen(name) + 1;</span><br><span style="color: hsl(120, 100%, 40%);">+ char context_name[context_size];</span><br><span> </span><br><span>         ast_assert(name != NULL);</span><br><span>    ast_assert(handler != NULL);</span><br><span>@@ -1009,6 +1017,15 @@</span><br><span>        app->handler = handler;</span><br><span>   app->data = ao2_bump(data);</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+    /* Create a context, a match-all extension, and a 'h' extension for this application */</span><br><span style="color: hsl(120, 100%, 40%);">+       strcat(strcpy(context_name, "stasis:"), name);</span><br><span style="color: hsl(120, 100%, 40%);">+      if (!ast_context_find_or_create(NULL, NULL, context_name, "res_stasis")) {</span><br><span style="color: hsl(120, 100%, 40%);">+          ast_log(LOG_WARNING, "Could not find or create context '%s'\n", context_name);</span><br><span style="color: hsl(120, 100%, 40%);">+      } else {</span><br><span style="color: hsl(120, 100%, 40%);">+              ast_add_extension(context_name, 0, "_.", 1, NULL, NULL, "Stasis", ast_strdup(name), ast_free_ptr, "res_stasis");</span><br><span style="color: hsl(120, 100%, 40%);">+                ast_add_extension(context_name, 0, "h", 1, NULL, NULL, "NoOp", NULL, NULL, "res_stasis");</span><br><span style="color: hsl(120, 100%, 40%);">+       }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>  ao2_ref(app, +1);</span><br><span>    return app;</span><br><span> }</span><br><span>diff --git a/res/stasis/control.c b/res/stasis/control.c</span><br><span>index 1821f20..c71603a 100644</span><br><span>--- a/res/stasis/control.c</span><br><span>+++ b/res/stasis/control.c</span><br><span>@@ -74,10 +74,24 @@</span><br><span>   */</span><br><span>  struct ast_silence_generator *silgen;</span><br><span>        /*!</span><br><span style="color: hsl(0, 100%, 40%);">-      * The app for which this control was created</span><br><span style="color: hsl(120, 100%, 40%);">+  * The app for which this control is currently controlling.</span><br><span style="color: hsl(120, 100%, 40%);">+    * This can change through the use of the /channels/{channelId}/move</span><br><span style="color: hsl(120, 100%, 40%);">+   * command.</span><br><span>   */</span><br><span>  struct stasis_app *app;</span><br><span>      /*!</span><br><span style="color: hsl(120, 100%, 40%);">+    * The name of the next Stasis application to move to.</span><br><span style="color: hsl(120, 100%, 40%);">+         */</span><br><span style="color: hsl(120, 100%, 40%);">+   char *next_app;</span><br><span style="color: hsl(120, 100%, 40%);">+       /*!</span><br><span style="color: hsl(120, 100%, 40%);">+    * The list of arguments to pass to StasisStart when moving to another app.</span><br><span style="color: hsl(120, 100%, 40%);">+    */</span><br><span style="color: hsl(120, 100%, 40%);">+   char **argv;</span><br><span style="color: hsl(120, 100%, 40%);">+  /*!</span><br><span style="color: hsl(120, 100%, 40%);">+    * The number of arguments argv contains.</span><br><span style="color: hsl(120, 100%, 40%);">+      */</span><br><span style="color: hsl(120, 100%, 40%);">+   int argc;</span><br><span style="color: hsl(120, 100%, 40%);">+     /*!</span><br><span>   * When set, /c app_stasis should exit and continue in the dialplan.</span><br><span>          */</span><br><span>  int is_done:1;</span><br><span>@@ -92,6 +106,8 @@</span><br><span>  ast_channel_cleanup(control->channel);</span><br><span>    ao2_cleanup(control->app);</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+     control_move_cleanup(control);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>     ast_cond_destroy(&control->wait_cond);</span><br><span>        AST_LIST_HEAD_DESTROY(&control->add_rules);</span><br><span>   AST_LIST_HEAD_DESTROY(&control->remove_rules);</span><br><span>@@ -130,6 +146,10 @@</span><br><span>                 return NULL;</span><br><span>         }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+ control->next_app = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+  control->argv = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+      control->argc = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>      return control;</span><br><span> }</span><br><span> </span><br><span>@@ -463,6 +483,103 @@</span><br><span>     return 0;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+struct stasis_app_control_move_data {</span><br><span style="color: hsl(120, 100%, 40%);">+     char *app_name;</span><br><span style="color: hsl(120, 100%, 40%);">+       char **argv;</span><br><span style="color: hsl(120, 100%, 40%);">+  int argc;</span><br><span style="color: hsl(120, 100%, 40%);">+};</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static int app_control_move(struct stasis_app_control *control,</span><br><span style="color: hsl(120, 100%, 40%);">+      struct ast_channel *chan, void *data)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+      struct stasis_app_control_move_data *move_data = data;</span><br><span style="color: hsl(120, 100%, 40%);">+        size_t size;</span><br><span style="color: hsl(120, 100%, 40%);">+  int idx;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    size = strlen(move_data->app_name) * sizeof(*move_data->app_name) + 1;</span><br><span style="color: hsl(120, 100%, 40%);">+  if (!(control->next_app = ast_calloc(1, size))) {</span><br><span style="color: hsl(120, 100%, 40%);">+          ast_log(LOG_ERROR, "Allocation failed for next app\n");</span><br><span style="color: hsl(120, 100%, 40%);">+             return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+    }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   ast_copy_string(control->next_app, S_OR(move_data->app_name, ""), size);</span><br><span style="color: hsl(120, 100%, 40%);">+      control->argc = move_data->argc;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      if (move_data->argv) {</span><br><span style="color: hsl(120, 100%, 40%);">+             size = sizeof(*move_data->argv) * control->argc;</span><br><span style="color: hsl(120, 100%, 40%);">+                if (!(control->argv = ast_calloc(1, size))) {</span><br><span style="color: hsl(120, 100%, 40%);">+                      ast_log(LOG_ERROR, "Allocation failed for argument list\n");</span><br><span style="color: hsl(120, 100%, 40%);">+                        control_move_cleanup(control);</span><br><span style="color: hsl(120, 100%, 40%);">+                        return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+            }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           for (idx = 0; idx < control->argc; idx++) {</span><br><span style="color: hsl(120, 100%, 40%);">+                     size = strlen(move_data->argv[idx]) * sizeof(*move_data->argv[idx]) + 1;</span><br><span style="color: hsl(120, 100%, 40%);">+                        if (!(control->argv[idx] = ast_calloc(1, size))) {</span><br><span style="color: hsl(120, 100%, 40%);">+                         ast_log(LOG_ERROR, "Allocation failed for argument\n");</span><br><span style="color: hsl(120, 100%, 40%);">+                             control_move_cleanup(control);</span><br><span style="color: hsl(120, 100%, 40%);">+                                return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+                    }</span><br><span style="color: hsl(120, 100%, 40%);">+                     ast_copy_string(control->argv[idx], S_OR(move_data->argv[idx], ""), size);</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%);">+              control->argv = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+      }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+int stasis_app_control_move(struct stasis_app_control *control, const char *app_name, const char *app_args)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+        struct stasis_app_control_move_data *move_data;</span><br><span style="color: hsl(120, 100%, 40%);">+       size_t size;</span><br><span style="color: hsl(120, 100%, 40%);">+  int argc = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ char *iter;</span><br><span style="color: hsl(120, 100%, 40%);">+   char *token;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        if (!(move_data = ast_calloc(1, sizeof(*move_data)))) {</span><br><span style="color: hsl(120, 100%, 40%);">+               return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+    }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   size = strlen(app_name) * sizeof(*app_name) + 1;</span><br><span style="color: hsl(120, 100%, 40%);">+      if (!(move_data->app_name = ast_calloc(1, size))) {</span><br><span style="color: hsl(120, 100%, 40%);">+                return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+    }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   ast_copy_string(move_data->app_name, S_OR(app_name, ""), size);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        if (app_args) {</span><br><span style="color: hsl(120, 100%, 40%);">+               size = strlen(app_args) * sizeof(*app_args) + 1;</span><br><span style="color: hsl(120, 100%, 40%);">+              if (!(iter = ast_calloc(1, size))) {</span><br><span style="color: hsl(120, 100%, 40%);">+                  return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+            }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           ast_copy_string(iter, S_OR(app_args, ""), size);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+          if (!(move_data->argv = ast_calloc(1, sizeof(*move_data->argv)))) {</span><br><span style="color: hsl(120, 100%, 40%);">+                     return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+            }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           while ((token = strtok_r(iter, ",", &iter))) {</span><br><span style="color: hsl(120, 100%, 40%);">+                  if (!(move_data->argv = ast_realloc(move_data->argv, sizeof(*move_data->argv) * (argc + 1)))) {</span><br><span style="color: hsl(120, 100%, 40%);">+                              return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+                    }</span><br><span style="color: hsl(120, 100%, 40%);">+                     size = strlen(token) * sizeof(*token) + 1;</span><br><span style="color: hsl(120, 100%, 40%);">+                    move_data->argv[argc] = ast_calloc(1, size);</span><br><span style="color: hsl(120, 100%, 40%);">+                       ast_copy_string(move_data->argv[argc], S_OR(token, ""), size);</span><br><span style="color: hsl(120, 100%, 40%);">+                   argc++;</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%);">+              move_data->argv = NULL;</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%);">+   move_data->argc = argc;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+  stasis_app_send_command_async(control, app_control_move, move_data, ast_free_ptr);</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> static int app_control_redirect(struct stasis_app_control *control,</span><br><span>        struct ast_channel *chan, void *data)</span><br><span> {</span><br><span>@@ -1278,3 +1395,46 @@</span><br><span> {</span><br><span>     return control->app;</span><br><span> }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+void control_set_app(struct stasis_app_control *control, struct stasis_app *app)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+   control->app = ao2_bump(app);</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+char *control_next_app(struct stasis_app_control *control)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+  return control->next_app;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+void control_move_cleanup(struct stasis_app_control *control)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+   if (control->next_app) {</span><br><span style="color: hsl(120, 100%, 40%);">+           ast_free(control->next_app);</span><br><span style="color: hsl(120, 100%, 40%);">+       }</span><br><span style="color: hsl(120, 100%, 40%);">+     control->next_app = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        if (control->argv) {</span><br><span style="color: hsl(120, 100%, 40%);">+               int idx;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+            for (idx = 0; idx < control->argc; idx++) {</span><br><span style="color: hsl(120, 100%, 40%);">+                     if (control->argv[idx]) {</span><br><span style="color: hsl(120, 100%, 40%);">+                          ast_free(control->argv[idx]);</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_free(control->argv);</span><br><span style="color: hsl(120, 100%, 40%);">+   }</span><br><span style="color: hsl(120, 100%, 40%);">+     control->argv = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    control->argc = 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%);">+char **control_argv(struct stasis_app_control *control)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+        return control->argv;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+int control_argc(struct stasis_app_control *control)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+        return control->argc;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span>diff --git a/res/stasis/control.h b/res/stasis/control.h</span><br><span>index 868a809..d933d52 100644</span><br><span>--- a/res/stasis/control.h</span><br><span>+++ b/res/stasis/control.h</span><br><span>@@ -108,6 +108,28 @@</span><br><span> struct stasis_app *control_app(struct stasis_app_control *control);</span><br><span> </span><br><span> /*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Set the application the control object belongs to</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param control The control for the channel</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param app The application this control will now belong to</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+void control_set_app(struct stasis_app_control *control, struct stasis_app *app);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+char *control_next_app(struct stasis_app_control *control);</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 Free any memory that was allocated for switching applications via</span><br><span style="color: hsl(120, 100%, 40%);">+ * /channels/{channelId}/move</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param control The control for the channel</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+void control_move_cleanup(struct stasis_app_control *control);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+char **control_argv(struct stasis_app_control *control);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+int control_argc(struct stasis_app_control *control);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span>  * \brief Command callback for adding a channel to a bridge</span><br><span>  *</span><br><span>  * \param control The control for chan</span><br><span>diff --git a/rest-api/api-docs/channels.json b/rest-api/api-docs/channels.json</span><br><span>index 487db44..07a20a9 100644</span><br><span>--- a/rest-api/api-docs/channels.json</span><br><span>+++ b/rest-api/api-docs/channels.json</span><br><span>@@ -420,6 +420,54 @@</span><br><span>                        ]</span><br><span>            },</span><br><span>           {</span><br><span style="color: hsl(120, 100%, 40%);">+                     "path": "/channels/{channelId}/move",</span><br><span style="color: hsl(120, 100%, 40%);">+                     "description": "Move the channel from one Stasis application to another.",</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": "Move the channel from one Stasis application to another.",</span><br><span style="color: hsl(120, 100%, 40%);">+                                    "nickname": "move",</span><br><span style="color: hsl(120, 100%, 40%);">+                                       "responseClass": "void",</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": "Channel's id",</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": "app",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                    "description": "The channel will be passed to this Stasis application.",</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": "appArgs",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                        "description": "The application arguments to pass to the Stasis application provided by 'app'.",</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%);">+                                    "errorResponses": [</span><br><span style="color: hsl(120, 100%, 40%);">+                                         {</span><br><span style="color: hsl(120, 100%, 40%);">+                                                     "code": "404",</span><br><span style="color: hsl(120, 100%, 40%);">+                                                    "reason": "Channel not found"</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 not in a Stasis application"</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%);">+             },</span><br><span style="color: hsl(120, 100%, 40%);">+            {</span><br><span>                    "path": "/channels/{channelId}/redirect",</span><br><span>                        "description": "Inform the channel that it should redirect itself to a different location. Note that this will almost certainly cause the channel to exit the application.",</span><br><span>                     "operations": [</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/10882">change 10882</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/10882"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: 13 </div>
<div style="display:none"> Gerrit-MessageType: newchange </div>
<div style="display:none"> Gerrit-Change-Id: Ib6c569468472dbb08905b356887373c81e03015d </div>
<div style="display:none"> Gerrit-Change-Number: 10882 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: Benjamin Keith Ford <bford@digium.com> </div>