res_stasis_recording: Add a "target_uri" field to recording events.
[asterisk/asterisk.git] / res / ari / resource_bridges.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2012 - 2013, Digium, Inc.
5  *
6  * David M. Lee, II <dlee@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief Implementation for ARI stubs.
22  *
23  * \author David M. Lee, II <dlee@digium.com>
24  */
25
26 /*** MODULEINFO
27         <support_level>core</support_level>
28  ***/
29
30 #include "asterisk.h"
31
32 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
33
34 #include "resource_bridges.h"
35 #include "asterisk/stasis.h"
36 #include "asterisk/stasis_bridges.h"
37 #include "asterisk/stasis_app.h"
38 #include "asterisk/stasis_app_playback.h"
39 #include "asterisk/stasis_app_recording.h"
40 #include "asterisk/stasis_channels.h"
41 #include "asterisk/core_unreal.h"
42 #include "asterisk/channel.h"
43 #include "asterisk/bridge.h"
44 #include "asterisk/format_cap.h"
45 #include "asterisk/file.h"
46 #include "asterisk/musiconhold.h"
47
48 /*!
49  * \brief Finds a bridge, filling the response with an error, if appropriate.
50  *
51  * \param[out] response Response to fill with an error if control is not found.
52  * \param bridge_id ID of the bridge to lookup.
53  *
54  * \return Bridget.
55  * \return \c NULL if bridge does not exist.
56  */
57 static struct ast_bridge *find_bridge(
58         struct ast_ari_response *response,
59         const char *bridge_id)
60 {
61         RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
62
63         ast_assert(response != NULL);
64
65         bridge = stasis_app_bridge_find_by_id(bridge_id);
66         if (bridge == NULL) {
67                 RAII_VAR(struct ast_bridge_snapshot *, snapshot,
68                         ast_bridge_snapshot_get_latest(bridge_id), ao2_cleanup);
69                 if (!snapshot) {
70                         ast_ari_response_error(response, 404, "Not found",
71                                 "Bridge not found");
72                         return NULL;
73                 }
74
75                 ast_ari_response_error(response, 409, "Conflict",
76                         "Bridge not in Stasis application");
77                 return NULL;
78         }
79
80         ao2_ref(bridge, +1);
81         return bridge;
82 }
83
84 /*!
85  * \brief Finds the control object for a channel, filling the response with an
86  * error, if appropriate.
87  * \param[out] response Response to fill with an error if control is not found.
88  * \param channel_id ID of the channel to lookup.
89  * \return Channel control object.
90  * \return \c NULL if control object does not exist.
91  */
92 static struct stasis_app_control *find_channel_control(
93         struct ast_ari_response *response,
94         const char *channel_id)
95 {
96         RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
97
98         ast_assert(response != NULL);
99
100         control = stasis_app_control_find_by_channel_id(channel_id);
101         if (control == NULL) {
102                 /* Distinguish between 400 and 422 errors */
103                 RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL,
104                         ao2_cleanup);
105                 snapshot = ast_channel_snapshot_get_latest(channel_id);
106                 if (snapshot == NULL) {
107                         ast_log(LOG_DEBUG, "Couldn't find '%s'\n", channel_id);
108                         ast_ari_response_error(response, 400, "Bad Request",
109                                 "Channel not found");
110                         return NULL;
111                 }
112
113                 ast_log(LOG_DEBUG, "Found non-stasis '%s'\n", channel_id);
114                 ast_ari_response_error(response, 422, "Unprocessable Entity",
115                         "Channel not in Stasis application");
116                 return NULL;
117         }
118
119         ao2_ref(control, +1);
120         return control;
121 }
122
123 struct control_list {
124         size_t count;
125         struct stasis_app_control *controls[];
126 };
127
128 static void control_list_dtor(void *obj) {
129         struct control_list *list = obj;
130         size_t i;
131
132         for (i = 0; i < list->count; ++i) {
133                 ao2_cleanup(list->controls[i]);
134                 list->controls[i] = NULL;
135         }
136 }
137
138 static struct control_list *control_list_create(struct ast_ari_response *response, size_t count, const char **channels) {
139         RAII_VAR(struct control_list *, list, NULL, ao2_cleanup);
140         size_t i;
141
142         if (count == 0 || !channels) {
143                 ast_ari_response_error(response, 400, "Bad Request", "Missing parameter channel");
144                 return NULL;
145         }
146
147         list = ao2_alloc(sizeof(*list) + count * sizeof(list->controls[0]), control_list_dtor);
148         if (!list) {
149                 ast_ari_response_alloc_failed(response);
150                 return NULL;
151         }
152
153         for (i = 0; i < count; ++i) {
154                 if (ast_strlen_zero(channels[i])) {
155                         continue;
156                 }
157                 list->controls[list->count] =
158                         find_channel_control(response, channels[i]);
159                 if (!list->controls[list->count]) {
160                         /* response filled in by find_channel_control() */
161                         return NULL;
162                 }
163                 ++list->count;
164         }
165
166         if (list->count == 0) {
167                 ast_ari_response_error(response, 400, "Bad Request", "Missing parameter channel");
168                 return NULL;
169         }
170
171         ao2_ref(list, +1);
172         return list;
173 }
174
175 static int check_add_remove_channel(struct ast_ari_response *response,
176                                     struct stasis_app_control *control,
177                                     enum stasis_app_control_channel_result result)
178 {
179         switch (result) {
180         case STASIS_APP_CHANNEL_RECORDING :
181                 ast_ari_response_error(
182                         response, 409, "Conflict", "Channel %s currently recording",
183                         stasis_app_control_get_channel_id(control));
184                 return -1;
185         case STASIS_APP_CHANNEL_OKAY:
186                 return 0;
187         }
188         return 0;
189 }
190
191 void ast_ari_bridges_add_channel(struct ast_variable *headers,
192         struct ast_ari_bridges_add_channel_args *args,
193         struct ast_ari_response *response)
194 {
195         RAII_VAR(struct ast_bridge *, bridge, find_bridge(response, args->bridge_id), ao2_cleanup);
196         RAII_VAR(struct control_list *, list, NULL, ao2_cleanup);
197         size_t i;
198         int has_error = 0;
199
200         if (!bridge) {
201                 /* Response filled in by find_bridge() */
202                 return;
203         }
204
205         list = control_list_create(response, args->channel_count, args->channel);
206         if (!list) {
207                 /* Response filled in by control_list_create() */
208                 return;
209         }
210
211         for (i = 0; i < list->count; ++i) {
212                 stasis_app_control_clear_roles(list->controls[i]);
213                 if (!ast_strlen_zero(args->role)) {
214                         if (stasis_app_control_add_role(list->controls[i], args->role)) {
215                                 ast_ari_response_alloc_failed(response);
216                                 return;
217                         }
218                 }
219         }
220
221         for (i = 0; i < list->count; ++i) {
222                 if ((has_error = check_add_remove_channel(response, list->controls[i],
223                              stasis_app_control_add_channel_to_bridge(
224                                     list->controls[i], bridge)))) {
225                         break;
226                 }
227         }
228
229         if (!has_error) {
230                 ast_ari_response_no_content(response);
231         }
232 }
233
234 void ast_ari_bridges_remove_channel(struct ast_variable *headers,
235         struct ast_ari_bridges_remove_channel_args *args,
236         struct ast_ari_response *response)
237 {
238         RAII_VAR(struct ast_bridge *, bridge, find_bridge(response, args->bridge_id), ao2_cleanup);
239         RAII_VAR(struct control_list *, list, NULL, ao2_cleanup);
240         size_t i;
241
242         if (!bridge) {
243                 /* Response filled in by find_bridge() */
244                 return;
245         }
246
247         list = control_list_create(response, args->channel_count, args->channel);
248         if (!list) {
249                 /* Response filled in by control_list_create() */
250                 return;
251         }
252
253         /* Make sure all of the channels are in this bridge */
254         for (i = 0; i < list->count; ++i) {
255                 if (stasis_app_get_bridge(list->controls[i]) != bridge) {
256                         ast_log(LOG_WARNING, "Channel %s not in bridge %s\n",
257                                 args->channel[i], args->bridge_id);
258                         ast_ari_response_error(response, 422,
259                                 "Unprocessable Entity",
260                                 "Channel not in this bridge");
261                         return;
262                 }
263         }
264
265         /* Now actually remove it */
266         for (i = 0; i < list->count; ++i) {
267                 stasis_app_control_remove_channel_from_bridge(list->controls[i],
268                         bridge);
269         }
270
271         ast_ari_response_no_content(response);
272 }
273
274 struct bridge_channel_control_thread_data {
275         struct ast_channel *bridge_channel;
276         struct stasis_app_control *control;
277 };
278
279 static void *bridge_channel_control_thread(void *data)
280 {
281         struct bridge_channel_control_thread_data *thread_data = data;
282         struct ast_channel *bridge_channel = thread_data->bridge_channel;
283         struct stasis_app_control *control = thread_data->control;
284
285         RAII_VAR(struct ast_callid *, callid, ast_channel_callid(bridge_channel), ast_callid_cleanup);
286
287         if (callid) {
288                 ast_callid_threadassoc_add(callid);
289         }
290
291         ast_free(thread_data);
292         thread_data = NULL;
293
294         stasis_app_control_execute_until_exhausted(bridge_channel, control);
295
296         ast_hangup(bridge_channel);
297         ao2_cleanup(control);
298         return NULL;
299 }
300
301 static struct ast_channel *prepare_bridge_media_channel(const char *type)
302 {
303         RAII_VAR(struct ast_format_cap *, cap, NULL, ast_format_cap_destroy);
304         struct ast_format format;
305
306         cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_NOLOCK);
307         if (!cap) {
308                 return NULL;
309         }
310
311         ast_format_cap_add(cap, ast_format_set(&format, AST_FORMAT_SLINEAR, 0));
312
313         if (!cap) {
314                 return NULL;
315         }
316
317         return ast_request(type, cap, NULL, "ARI", NULL);
318 }
319
320 void ast_ari_bridges_play(struct ast_variable *headers,
321         struct ast_ari_bridges_play_args *args,
322         struct ast_ari_response *response)
323 {
324         RAII_VAR(struct ast_bridge *, bridge, find_bridge(response, args->bridge_id), ao2_cleanup);
325         RAII_VAR(struct ast_channel *, play_channel, NULL, ast_hangup);
326         RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
327         RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
328         RAII_VAR(struct stasis_app_playback *, playback, NULL, ao2_cleanup);
329         RAII_VAR(char *, playback_url, NULL, ast_free);
330         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
331
332         struct bridge_channel_control_thread_data *thread_data;
333         const char *language;
334         pthread_t threadid;
335
336         ast_assert(response != NULL);
337
338         if (!bridge) {
339                 return;
340         }
341
342         if (!(play_channel = prepare_bridge_media_channel("Announcer"))) {
343                 ast_ari_response_error(
344                         response, 500, "Internal Error", "Could not create playback channel");
345                 return;
346         }
347         ast_debug(1, "Created announcer channel '%s'\n", ast_channel_name(play_channel));
348
349         if (ast_unreal_channel_push_to_bridge(play_channel, bridge,
350                 AST_BRIDGE_CHANNEL_FLAG_IMMOVABLE | AST_BRIDGE_CHANNEL_FLAG_LONELY)) {
351                 ast_ari_response_error(
352                         response, 500, "Internal Error", "Failed to put playback channel into the bridge");
353                 return;
354         }
355
356         control = stasis_app_control_create(play_channel);
357         if (control == NULL) {
358                 ast_ari_response_alloc_failed(response);
359                 return;
360         }
361
362         snapshot = stasis_app_control_get_snapshot(control);
363         if (!snapshot) {
364                 ast_ari_response_error(
365                         response, 500, "Internal Error", "Failed to get control snapshot");
366                 return;
367         }
368
369         language = S_OR(args->lang, snapshot->language);
370
371         playback = stasis_app_control_play_uri(control, args->media, language,
372                 args->bridge_id, STASIS_PLAYBACK_TARGET_BRIDGE, args->skipms,
373                 args->offsetms);
374
375         if (!playback) {
376                 ast_ari_response_alloc_failed(response);
377                 return;
378         }
379
380         ast_asprintf(&playback_url, "/playback/%s",
381                 stasis_app_playback_get_id(playback));
382
383         if (!playback_url) {
384                 ast_ari_response_alloc_failed(response);
385                 return;
386         }
387
388         json = stasis_app_playback_to_json(playback);
389         if (!json) {
390                 ast_ari_response_alloc_failed(response);
391                 return;
392         }
393
394         /* Give play_channel and control reference to the thread data */
395         thread_data = ast_calloc(1, sizeof(*thread_data));
396         if (!thread_data) {
397                 ast_ari_response_alloc_failed(response);
398                 return;
399         }
400
401         thread_data->bridge_channel = play_channel;
402         thread_data->control = control;
403
404         if (ast_pthread_create_detached(&threadid, NULL, bridge_channel_control_thread, thread_data)) {
405                 ast_ari_response_alloc_failed(response);
406                 ast_free(thread_data);
407                 return;
408         }
409
410         /* These are owned by the other thread now, so we don't want RAII_VAR disposing of them. */
411         play_channel = NULL;
412         control = NULL;
413
414         ast_ari_response_created(response, playback_url, ast_json_ref(json));
415 }
416
417 void ast_ari_bridges_record(struct ast_variable *headers,
418         struct ast_ari_bridges_record_args *args,
419         struct ast_ari_response *response)
420 {
421         RAII_VAR(struct ast_bridge *, bridge, find_bridge(response, args->bridge_id), ao2_cleanup);
422         RAII_VAR(struct ast_channel *, record_channel, NULL, ast_hangup);
423         RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
424         RAII_VAR(struct stasis_app_recording *, recording, NULL, ao2_cleanup);
425         RAII_VAR(char *, recording_url, NULL, ast_free);
426         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
427         RAII_VAR(struct stasis_app_recording_options *, options, NULL, ao2_cleanup);
428         RAII_VAR(char *, uri_encoded_name, NULL, ast_free);
429
430         size_t uri_name_maxlen;
431         struct bridge_channel_control_thread_data *thread_data;
432         pthread_t threadid;
433
434         ast_assert(response != NULL);
435
436         if (bridge == NULL) {
437                 return;
438         }
439
440         if (!(record_channel = prepare_bridge_media_channel("Recorder"))) {
441                 ast_ari_response_error(
442                         response, 500, "Internal Server Error", "Failed to create recording channel");
443                 return;
444         }
445
446         if (ast_unreal_channel_push_to_bridge(record_channel, bridge,
447                 AST_BRIDGE_CHANNEL_FLAG_IMMOVABLE | AST_BRIDGE_CHANNEL_FLAG_LONELY)) {
448                 ast_ari_response_error(
449                         response, 500, "Internal Error", "Failed to put recording channel into the bridge");
450                 return;
451         }
452
453         control = stasis_app_control_create(record_channel);
454         if (control == NULL) {
455                 ast_ari_response_alloc_failed(response);
456                 return;
457         }
458
459         options = stasis_app_recording_options_create(args->name, args->format);
460         if (options == NULL) {
461                 ast_ari_response_alloc_failed(response);
462                 return;
463         }
464
465         ast_string_field_build(options, target, "bridge:%s", args->bridge_id);
466         options->max_silence_seconds = args->max_silence_seconds;
467         options->max_duration_seconds = args->max_duration_seconds;
468         options->terminate_on =
469                 stasis_app_recording_termination_parse(args->terminate_on);
470         options->if_exists =
471                 stasis_app_recording_if_exists_parse(args->if_exists);
472         options->beep = args->beep;
473
474         if (options->terminate_on == STASIS_APP_RECORDING_TERMINATE_INVALID) {
475                 ast_ari_response_error(
476                         response, 400, "Bad Request",
477                         "terminateOn invalid");
478                 return;
479         }
480
481         if (options->if_exists == -1) {
482                 ast_ari_response_error(
483                         response, 400, "Bad Request",
484                         "ifExists invalid");
485                 return;
486         }
487
488         if (!ast_get_format_for_file_ext(options->format)) {
489                 ast_ari_response_error(
490                         response, 422, "Unprocessable Entity",
491                         "specified format is unknown on this system");
492                 return;
493         }
494
495         recording = stasis_app_control_record(control, options);
496         if (recording == NULL) {
497                 switch(errno) {
498                 case EINVAL:
499                         /* While the arguments are invalid, we should have
500                          * caught them prior to calling record.
501                          */
502                         ast_ari_response_error(
503                                 response, 500, "Internal Server Error",
504                                 "Error parsing request");
505                         break;
506                 case EEXIST:
507                         ast_ari_response_error(response, 409, "Conflict",
508                                 "Recording '%s' already exists and can not be overwritten",
509                                 args->name);
510                         break;
511                 case ENOMEM:
512                         ast_ari_response_alloc_failed(response);
513                         break;
514                 case EPERM:
515                         ast_ari_response_error(
516                                 response, 400, "Bad Request",
517                                 "Recording name invalid");
518                         break;
519                 default:
520                         ast_log(LOG_WARNING,
521                                 "Unrecognized recording error: %s\n",
522                                 strerror(errno));
523                         ast_ari_response_error(
524                                 response, 500, "Internal Server Error",
525                                 "Internal Server Error");
526                         break;
527                 }
528                 return;
529         }
530
531         uri_name_maxlen = strlen(args->name) * 3;
532         uri_encoded_name = ast_malloc(uri_name_maxlen);
533         if (!uri_encoded_name) {
534                 ast_ari_response_alloc_failed(response);
535                 return;
536         }
537         ast_uri_encode(args->name, uri_encoded_name, uri_name_maxlen, ast_uri_http);
538
539         ast_asprintf(&recording_url, "/recordings/live/%s", uri_encoded_name);
540         if (!recording_url) {
541                 ast_ari_response_alloc_failed(response);
542                 return;
543         }
544
545         json = stasis_app_recording_to_json(recording);
546         if (!json) {
547                 ast_ari_response_alloc_failed(response);
548                 return;
549         }
550
551         thread_data = ast_calloc(1, sizeof(*thread_data));
552         if (!thread_data) {
553                 ast_ari_response_alloc_failed(response);
554                 return;
555         }
556
557         thread_data->bridge_channel = record_channel;
558         thread_data->control = control;
559
560         if (ast_pthread_create_detached(&threadid, NULL, bridge_channel_control_thread, thread_data)) {
561                 ast_ari_response_alloc_failed(response);
562                 ast_free(thread_data);
563                 return;
564         }
565
566         /* These are owned by the other thread now, so we don't want RAII_VAR disposing of them. */
567         record_channel = NULL;
568         control = NULL;
569
570         ast_ari_response_created(response, recording_url, ast_json_ref(json));
571 }
572
573 void ast_ari_bridges_start_moh(struct ast_variable *headers,
574         struct ast_ari_bridges_start_moh_args *args,
575         struct ast_ari_response *response)
576 {
577         RAII_VAR(struct ast_bridge *, bridge, find_bridge(response, args->bridge_id), ao2_cleanup);
578         struct ast_channel *moh_channel;
579         const char *moh_class = args->moh_class;
580
581         if (!bridge) {
582                 /* The response is provided by find_bridge() */
583                 return;
584         }
585
586         moh_channel = stasis_app_bridge_moh_channel(bridge);
587         if (!moh_channel) {
588                 ast_ari_response_alloc_failed(response);
589                 return;
590         }
591
592         ast_moh_start(moh_channel, moh_class, NULL);
593
594         ast_ari_response_no_content(response);
595
596 }
597
598 void ast_ari_bridges_stop_moh(struct ast_variable *headers,
599         struct ast_ari_bridges_stop_moh_args *args,
600         struct ast_ari_response *response)
601 {
602         RAII_VAR(struct ast_bridge *, bridge, find_bridge(response, args->bridge_id), ao2_cleanup);
603
604         if (!bridge) {
605                 /* the response is provided by find_bridge() */
606                 return;
607         }
608
609         if (stasis_app_bridge_moh_stop(bridge)) {
610                 ast_ari_response_error(
611                         response, 409, "Conflict",
612                         "Bridge isn't playing music");
613                 return;
614         }
615
616         ast_ari_response_no_content(response);
617 }
618
619 void ast_ari_bridges_get(struct ast_variable *headers,
620         struct ast_ari_bridges_get_args *args,
621         struct ast_ari_response *response)
622 {
623         RAII_VAR(struct ast_bridge_snapshot *, snapshot, ast_bridge_snapshot_get_latest(args->bridge_id), ao2_cleanup);
624         if (!snapshot) {
625                 ast_ari_response_error(
626                         response, 404, "Not Found",
627                         "Bridge not found");
628                 return;
629         }
630
631         ast_ari_response_ok(response,
632                 ast_bridge_snapshot_to_json(snapshot, stasis_app_get_sanitizer()));
633 }
634
635 void ast_ari_bridges_destroy(struct ast_variable *headers,
636         struct ast_ari_bridges_destroy_args *args,
637         struct ast_ari_response *response)
638 {
639         RAII_VAR(struct ast_bridge *, bridge, find_bridge(response, args->bridge_id), ao2_cleanup);
640         if (!bridge) {
641                 return;
642         }
643
644         stasis_app_bridge_destroy(args->bridge_id);
645         ast_ari_response_no_content(response);
646 }
647
648 void ast_ari_bridges_list(struct ast_variable *headers,
649         struct ast_ari_bridges_list_args *args,
650         struct ast_ari_response *response)
651 {
652         RAII_VAR(struct stasis_cache *, cache, NULL, ao2_cleanup);
653         RAII_VAR(struct ao2_container *, snapshots, NULL, ao2_cleanup);
654         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
655         struct ao2_iterator i;
656         void *obj;
657
658         cache = ast_bridge_cache();
659         if (!cache) {
660                 ast_ari_response_error(
661                         response, 500, "Internal Server Error",
662                         "Message bus not initialized");
663                 return;
664         }
665         ao2_ref(cache, +1);
666
667         snapshots = stasis_cache_dump(cache, ast_bridge_snapshot_type());
668         if (!snapshots) {
669                 ast_ari_response_alloc_failed(response);
670                 return;
671         }
672
673         json = ast_json_array_create();
674         if (!json) {
675                 ast_ari_response_alloc_failed(response);
676                 return;
677         }
678
679         i = ao2_iterator_init(snapshots, 0);
680         while ((obj = ao2_iterator_next(&i))) {
681                 RAII_VAR(struct stasis_message *, msg, obj, ao2_cleanup);
682                 struct ast_bridge_snapshot *snapshot = stasis_message_data(msg);
683                 struct ast_json *json_bridge = ast_bridge_snapshot_to_json(snapshot, stasis_app_get_sanitizer());
684
685                 if (!json_bridge || ast_json_array_append(json, json_bridge)) {
686                         ao2_iterator_destroy(&i);
687                         ast_ari_response_alloc_failed(response);
688                         return;
689                 }
690         }
691         ao2_iterator_destroy(&i);
692
693         ast_ari_response_ok(response, ast_json_ref(json));
694 }
695
696 void ast_ari_bridges_create(struct ast_variable *headers,
697         struct ast_ari_bridges_create_args *args,
698         struct ast_ari_response *response)
699 {
700         RAII_VAR(struct ast_bridge *, bridge, stasis_app_bridge_create(args->type, args->name), ao2_cleanup);
701         RAII_VAR(struct ast_bridge_snapshot *, snapshot, NULL, ao2_cleanup);
702
703         if (!bridge) {
704                 ast_ari_response_error(
705                         response, 500, "Internal Error",
706                         "Unable to create bridge");
707                 return;
708         }
709
710         snapshot = ast_bridge_snapshot_create(bridge);
711         if (!snapshot) {
712                 ast_ari_response_error(
713                         response, 500, "Internal Error",
714                         "Unable to create snapshot for new bridge");
715                 return;
716         }
717
718         ast_ari_response_ok(response,
719                 ast_bridge_snapshot_to_json(snapshot, stasis_app_get_sanitizer()));
720 }