5ad0538c5932d8539a8c81e4897a371aa2a57a3b
[asterisk/asterisk.git] / res / parking / parking_bridge_features.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * Jonathan Rose <jrose@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 Parking Bridge DTMF and Interval features
22  *
23  * \author Jonathan Rose <jrose@digium.com>
24  */
25
26 #include "asterisk.h"
27
28 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
29
30 #include "res_parking.h"
31 #include "asterisk/utils.h"
32 #include "asterisk/astobj2.h"
33 #include "asterisk/logger.h"
34 #include "asterisk/pbx.h"
35 #include "asterisk/bridge.h"
36 #include "asterisk/bridge_internal.h"
37 #include "asterisk/bridge_channel.h"
38 #include "asterisk/bridge_features.h"
39 #include "asterisk/features.h"
40 #include "asterisk/say.h"
41 #include "asterisk/datastore.h"
42 #include "asterisk/stasis.h"
43 #include "asterisk/module.h"
44 #include "asterisk/core_local.h"
45
46 struct parked_subscription_datastore {
47         struct stasis_subscription *parked_subscription;
48 };
49
50 struct parked_subscription_data {
51         char *parkee_uuid;
52         char parker_uuid[0];
53 };
54
55 static void parked_subscription_datastore_destroy(void *data)
56 {
57         struct parked_subscription_datastore *subscription_datastore = data;
58
59         stasis_unsubscribe(subscription_datastore->parked_subscription);
60         subscription_datastore->parked_subscription = NULL;
61
62         ast_free(subscription_datastore);
63 }
64
65 static const struct ast_datastore_info parked_subscription_info = {
66         .type = "park subscription",
67         .destroy = parked_subscription_datastore_destroy,
68 };
69
70 static void wipe_subscription_datastore(struct ast_channel *chan)
71 {
72         struct ast_datastore *datastore;
73
74         ast_channel_lock(chan);
75
76         datastore = ast_channel_datastore_find(chan, &parked_subscription_info, NULL);
77         if (datastore) {
78                 ast_channel_datastore_remove(chan, datastore);
79                 ast_datastore_free(datastore);
80         }
81         ast_channel_unlock(chan);
82 }
83
84 static void parker_parked_call_message_response(struct ast_parked_call_payload *message, struct parked_subscription_data *data,
85         struct stasis_subscription *sub)
86 {
87         const char *parkee_to_act_on = data->parkee_uuid;
88         char saynum_buf[16];
89         struct ast_channel_snapshot *parkee_snapshot = message->parkee;
90         RAII_VAR(struct ast_channel *, parker, NULL, ao2_cleanup);
91         RAII_VAR(struct ast_bridge_channel *, bridge_channel, NULL, ao2_cleanup);
92
93         if (strcmp(parkee_to_act_on, parkee_snapshot->uniqueid)) {
94                 return;
95         }
96
97         if (message->event_type != PARKED_CALL && message->event_type != PARKED_CALL_FAILED) {
98                 /* We only care about these two event types */
99                 return;
100         }
101
102         parker = ast_channel_get_by_name(data->parker_uuid);
103         if (!parker) {
104                 return;
105         }
106
107         ast_channel_lock(parker);
108         bridge_channel = ast_channel_get_bridge_channel(parker);
109         ast_channel_unlock(parker);
110         if (!bridge_channel) {
111                 return;
112         }
113
114         if (message->event_type == PARKED_CALL) {
115                 /* queue the saynum on the bridge channel and hangup */
116                 snprintf(saynum_buf, sizeof(saynum_buf), "%u %u", 1, message->parkingspace);
117                 ast_bridge_channel_queue_playfile(bridge_channel, say_parking_space, saynum_buf, NULL);
118                 wipe_subscription_datastore(bridge_channel->chan);
119         }
120
121         if (message->event_type == PARKED_CALL_FAILED) {
122                 ast_bridge_channel_queue_playfile(bridge_channel, NULL, "pbx-parkingfailed", NULL);
123                 wipe_subscription_datastore(bridge_channel->chan);
124         }
125 }
126
127 static void parker_update_cb(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message)
128 {
129         if (stasis_subscription_final_message(sub, message)) {
130                 ast_free(data);
131                 return;
132         }
133
134         if (stasis_message_type(message) == ast_parked_call_type()) {
135                 struct ast_parked_call_payload *parked_call_message = stasis_message_data(message);
136                 parker_parked_call_message_response(parked_call_message, data, sub);
137         }
138 }
139
140 static int create_parked_subscription(struct ast_channel *chan, const char *parkee_uuid)
141 {
142         struct ast_datastore *datastore;
143         struct parked_subscription_datastore *parked_datastore;
144         struct parked_subscription_data *subscription_data;
145
146         char *parker_uuid = ast_strdupa(ast_channel_uniqueid(chan));
147         size_t parker_uuid_size = strlen(parker_uuid) + 1;
148
149         /* If there is already a subscription, get rid of it. */
150         wipe_subscription_datastore(chan);
151
152         if (!(datastore = ast_datastore_alloc(&parked_subscription_info, NULL))) {
153                 return -1;
154         }
155
156         if (!(parked_datastore = ast_calloc(1, sizeof(*parked_datastore)))) {
157                 ast_datastore_free(datastore);
158                 return -1;
159         }
160
161         if (!(subscription_data = ast_calloc(1, sizeof(*subscription_data) + parker_uuid_size +
162                         strlen(parkee_uuid) + 1))) {
163                 ast_datastore_free(datastore);
164                 ast_free(parked_datastore);
165                 return -1;
166         }
167
168         subscription_data->parkee_uuid = subscription_data->parker_uuid + parker_uuid_size;
169         strcpy(subscription_data->parkee_uuid, parkee_uuid);
170         strcpy(subscription_data->parker_uuid, parker_uuid);
171
172         if (!(parked_datastore->parked_subscription = stasis_subscribe(ast_parking_topic(), parker_update_cb, subscription_data))) {
173                 return -1;
174         }
175
176         datastore->data = parked_datastore;
177
178         ast_channel_lock(chan);
179         ast_channel_datastore_add(chan, datastore);
180         ast_channel_unlock(chan);
181
182         return 0;
183 }
184
185 /*!
186  * \internal
187  * \brief Helper function that creates an outgoing channel and returns it immediately. This function is nearly
188  *        identical to the dial_transfer function in bridge_basic.c, however it doesn't swap the
189  *        local channel and the channel that instigated the park.
190  */
191 static struct ast_channel *park_local_transfer(struct ast_channel *parker, const char *context, const char *exten)
192 {
193         char destination[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 1];
194         struct ast_channel *parkee;
195         struct ast_channel *parkee_side_2;
196         int cause;
197
198         /* Fill the variable with the extension and context we want to call */
199         snprintf(destination, sizeof(destination), "%s@%s", exten, context);
200
201         /* Now we request that chan_local prepare to call the destination */
202         parkee = ast_request("Local", ast_channel_nativeformats(parker), parker, destination,
203                 &cause);
204         if (!parkee) {
205                 return NULL;
206         }
207
208         /* Before we actually dial out let's inherit appropriate information. */
209         ast_channel_lock_both(parker, parkee);
210         ast_connected_line_copy_from_caller(ast_channel_connected(parkee), ast_channel_caller(parker));
211         ast_channel_inherit_variables(parker, parkee);
212         ast_channel_datastore_inherit(parker, parkee);
213         ast_channel_unlock(parker);
214
215         parkee_side_2 = ast_local_get_peer(parkee);
216         ast_assert(parkee_side_2 != NULL);
217         ast_channel_unlock(parkee);
218
219         /* We need to have the parker subscribe to the new local channel before hand. */
220         create_parked_subscription(parker, ast_channel_uniqueid(parkee_side_2));
221
222         pbx_builtin_setvar_helper(parkee_side_2, "BLINDTRANSFER", ast_channel_name(parker));
223
224         ast_channel_unref(parkee_side_2);
225
226         /* Since the above worked fine now we actually call it and return the channel */
227         if (ast_call(parkee, destination, 0)) {
228                 ast_hangup(parkee);
229                 return NULL;
230         }
231
232         return parkee;
233 }
234
235 /*!
236  * \internal
237  * \brief Determine if an extension is a parking extension
238  */
239 static int parking_is_exten_park(const char *context, const char *exten)
240 {
241         struct ast_exten *exten_obj;
242         struct pbx_find_info info = { .stacklen = 0 }; /* the rest is reset in pbx_find_extension */
243         const char *app_at_exten;
244
245         ast_debug(4, "Checking if %s@%s is a parking exten\n", exten, context);
246         exten_obj = pbx_find_extension(NULL, NULL, &info, context, exten, 1, NULL, NULL, E_MATCH);
247         if (!exten_obj) {
248                 return 0;
249         }
250
251         app_at_exten = ast_get_extension_app(exten_obj);
252         if (!app_at_exten || strcasecmp(PARK_APPLICATION, app_at_exten)) {
253                 return 0;
254         }
255
256         return 1;
257 }
258
259 /*!
260  * \internal
261  * \since 12.0.0
262  * \brief Perform a blind transfer to a parking lot
263  *
264  * In general, most parking features should work to call this function. This will safely
265  * park either a channel in the bridge with \ref bridge_channel or will park the entire
266  * bridge if more than one channel is in the bridge. It will create the correct data to
267  * pass to the \ref AstBridging Bridging API to safely park the channel.
268  *
269  * \param bridge_channel The bridge_channel representing the channel performing the park
270  * \param context The context to blind transfer to
271  * \param exten The extension to blind transfer to
272  *
273  * \retval 0 on success
274  * \retval non-zero on error
275  */
276 static int parking_blind_transfer_park(struct ast_bridge_channel *bridge_channel,
277                 const char *context, const char *exten)
278 {
279         RAII_VAR(struct ast_bridge_channel *, other, NULL, ao2_cleanup);
280         int peer_count;
281
282         if (ast_strlen_zero(context) || ast_strlen_zero(exten)) {
283                 return -1;
284         }
285
286         if (!bridge_channel->in_bridge) {
287                 return -1;
288         }
289
290         if (!parking_is_exten_park(context, exten)) {
291                 return -1;
292         }
293
294         ast_bridge_channel_lock_bridge(bridge_channel);
295         peer_count = bridge_channel->bridge->num_channels;
296         if (peer_count == 2) {
297                 other = ast_bridge_channel_peer(bridge_channel);
298                 ao2_ref(other, +1);
299         }
300         ast_bridge_unlock(bridge_channel->bridge);
301
302         if (peer_count < 2) {
303                 /* There is nothing to do if there is no one to park. */
304                 return -1;
305         }
306
307         /* With a multiparty bridge, we need to do a regular blind transfer. We link the
308          * existing bridge to the parking lot with a Local channel rather than
309          * transferring others. */
310         if (peer_count > 2) {
311                 struct ast_channel *transfer_chan = NULL;
312
313                 transfer_chan = park_local_transfer(bridge_channel->chan, context, exten);
314                 if (!transfer_chan) {
315                         return -1;
316                 }
317
318                 if (ast_bridge_impart(bridge_channel->bridge, transfer_chan, NULL, NULL, 1)) {
319                         ast_hangup(transfer_chan);
320                         return -1;
321                 }
322                 return 0;
323         }
324
325         /* Subscribe to park messages with the other channel entering */
326         if (create_parked_subscription(bridge_channel->chan, ast_channel_uniqueid(other->chan))) {
327                 return -1;
328         }
329
330         /* Write the park frame with the intended recipient and other data out to the bridge. */
331         ast_bridge_channel_write_park(bridge_channel,
332                 ast_channel_uniqueid(other->chan),
333                 ast_channel_uniqueid(bridge_channel->chan),
334                 NULL);
335
336         return 0;
337 }
338
339
340 /*!
341  * \internal
342  * \since 12.0.0
343  * \brief Perform a direct park on a channel in a bridge
344  *
345  * \note This will be called from within the \ref AstBridging Bridging API
346  *
347  * \param bridge_channel The bridge_channel representing the channel to be parked
348  * \param uuid_parkee The UUID of the channel being parked
349  * \param uuid_parker The UUID of the channel performing the park
350  * \param app_data Application parseable data to pass to the parking application
351  */
352 static int parking_park_bridge_channel(struct ast_bridge_channel *bridge_channel, const char *uuid_parkee, const char *uuid_parker, const char *app_data)
353 {
354         RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
355         RAII_VAR(struct ast_bridge *, original_bridge, NULL, ao2_cleanup);
356         RAII_VAR(struct ast_channel *, parker, NULL, ao2_cleanup);
357
358         if (strcmp(ast_channel_uniqueid(bridge_channel->chan), uuid_parkee)) {
359                 /* We aren't the parkee, so ignore this action. */
360                 return -1;
361         }
362
363         parker = ast_channel_get_by_name(uuid_parker);
364
365         if (!parker) {
366                 ast_log(LOG_NOTICE, "Channel with uuid %s left before we could start parking the call. Parking canceled.\n", uuid_parker);
367                 publish_parked_call_failure(bridge_channel->chan);
368                 return -1;
369         }
370
371         if (!(parking_bridge = park_application_setup(bridge_channel->chan, parker, app_data, NULL))) {
372                 publish_parked_call_failure(bridge_channel->chan);
373                 return -1;
374         }
375
376         pbx_builtin_setvar_helper(bridge_channel->chan, "BLINDTRANSFER", ast_channel_name(parker));
377
378         /* bridge_channel must be locked so we can get a reference to the bridge it is currently on */
379         ao2_lock(bridge_channel);
380
381         original_bridge = bridge_channel->bridge;
382         if (!original_bridge) {
383                 ao2_unlock(bridge_channel);
384                 publish_parked_call_failure(bridge_channel->chan);
385                 return -1;
386         }
387
388         ao2_ref(original_bridge, +1); /* Cleaned by RAII_VAR */
389
390         ao2_unlock(bridge_channel);
391
392         if (ast_bridge_move(parking_bridge, original_bridge, bridge_channel->chan, NULL, 1)) {
393                 ast_log(LOG_ERROR, "Failed to move %s into the parking bridge.\n",
394                         ast_channel_name(bridge_channel->chan));
395                 return -1;
396         }
397
398         return 0;
399 }
400
401 /*!
402  * \internal
403  * \since 12.0.0
404  * \brief Park a call
405  *
406  * \param parker The bridge_channel parking the call
407  * \param exten Optional. The extension where the call was parked.
408  * \param length Optional. If \c exten is specified, the length of the buffer.
409  *
410  * \note This will determine the context and extension to park the channel based on
411  * the configuration of the \ref ast_channel associated with \ref parker. It will then
412  * park either the channel or the entire bridge.
413  *
414  * \retval 0 on success
415  * \retval -1 on error
416  */
417 static int parking_park_call(struct ast_bridge_channel *parker, char *exten, size_t length)
418 {
419         RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
420         const char *lot_name = NULL;
421
422         ast_channel_lock(parker->chan);
423         lot_name = find_channel_parking_lot_name(parker->chan);
424         if (!ast_strlen_zero(lot_name)) {
425                 lot_name = ast_strdupa(lot_name);
426         }
427         ast_channel_unlock(parker->chan);
428
429         if (ast_strlen_zero(lot_name)) {
430                 return -1;
431         }
432
433         lot = parking_lot_find_by_name(lot_name);
434         if (!lot) {
435                 ast_log(AST_LOG_WARNING, "Cannot Park %s: lot %s unknown\n",
436                         ast_channel_name(parker->chan), lot_name);
437                 return -1;
438         }
439
440         if (exten) {
441                 ast_copy_string(exten, lot->cfg->parkext, length);
442         }
443         return parking_blind_transfer_park(parker, lot->cfg->parking_con, lot->cfg->parkext);
444 }
445
446 static int feature_park_call(struct ast_bridge_channel *bridge_channel, void *hook_pvt)
447 {
448         SCOPED_MODULE_USE(parking_get_module_info()->self);
449
450         return parking_park_call(bridge_channel, NULL, 0);
451 }
452
453 /*! \internal
454  * \brief Interval hook. Pulls a parked call from the parking bridge after the timeout is passed and sets the resolution to timeout.
455  *
456  * \param bridge_channel bridge channel this interval hook is being executed on
457  * \param hook_pvt A pointer to the parked_user struct associated with the channel is stuffed in here
458  */
459 static int parking_duration_callback(struct ast_bridge_channel *bridge_channel, void *hook_pvt)
460 {
461         struct parked_user *user = hook_pvt;
462         struct ast_channel *chan = user->chan;
463         struct ast_context *park_dial_context;
464         const char *dial_string;
465         char *dial_string_flat;
466         char parking_space[AST_MAX_EXTENSION];
467
468         char returnexten[AST_MAX_EXTENSION];
469         char *duplicate_returnexten;
470         struct ast_exten *existing_exten;
471         struct pbx_find_info pbx_finder = { .stacklen = 0 }; /* The rest is reset in pbx_find_extension */
472
473
474         /* We are still in the bridge, so it's possible for other stuff to mess with the parked call before we leave the bridge
475            to deal with this, lock the parked user, check and set resolution. */
476         ao2_lock(user);
477         if (user->resolution != PARK_UNSET) {
478                 /* Abandon timeout since something else has resolved the parked user before we got to it. */
479                 ao2_unlock(user);
480                 return -1;
481         }
482
483         user->resolution = PARK_TIMEOUT;
484         ao2_unlock(user);
485
486         ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END_NO_DISSOLVE);
487
488         /* Set parking timeout channel variables */
489         snprintf(parking_space, sizeof(parking_space), "%d", user->parking_space);
490         pbx_builtin_setvar_helper(chan, "PARKING_SPACE", parking_space);
491         pbx_builtin_setvar_helper(chan, "PARKINGSLOT", parking_space); /* Deprecated version of PARKING_SPACE */
492         pbx_builtin_setvar_helper(chan, "PARKEDLOT", user->lot->name);
493
494         dial_string = user->parker_dial_string;
495         dial_string_flat = ast_strdupa(dial_string);
496         flatten_dial_string(dial_string_flat);
497
498         pbx_builtin_setvar_helper(chan, "PARKER", dial_string);
499         pbx_builtin_setvar_helper(chan, "PARKER_FLAT", dial_string_flat);
500
501         /* Dialplan generation for park-dial extensions */
502
503         if (ast_wrlock_contexts()) {
504                 ast_log(LOG_ERROR, "Failed to lock the contexts list. Can't add the park-dial extension.\n");
505                 return -1;
506         }
507
508         if (!(park_dial_context = ast_context_find_or_create(NULL, NULL, PARK_DIAL_CONTEXT, BASE_REGISTRAR))) {
509                 ast_log(LOG_ERROR, "Parking dial context '%s' does not exist and unable to create\n", PARK_DIAL_CONTEXT);
510                 if (ast_unlock_contexts()) {
511                         ast_assert(0);
512                 }
513                 goto abandon_extension_creation;
514         }
515
516         if (ast_wrlock_context(park_dial_context)) {
517                 ast_log(LOG_ERROR, "failed to obtain write lock on context '%s'\n", PARK_DIAL_CONTEXT);
518                 if (ast_unlock_contexts()) {
519                         ast_assert(0);
520                 }
521                 goto abandon_extension_creation;
522         }
523
524         if (ast_unlock_contexts()) {
525                 ast_assert(0);
526         }
527
528         snprintf(returnexten, sizeof(returnexten), "%s,%u", dial_string,
529                 user->lot->cfg->comebackdialtime);
530
531         duplicate_returnexten = ast_strdup(returnexten);
532
533         if (!duplicate_returnexten) {
534                 ast_log(LOG_ERROR, "Failed to create parking redial parker extension %s@%s - Dial(%s)\n",
535                         dial_string_flat, PARK_DIAL_CONTEXT, returnexten);
536         }
537
538         /* If an extension already exists here because we registered it for another parked call timing out, then we may overwrite it. */
539         if ((existing_exten = pbx_find_extension(NULL, NULL, &pbx_finder, PARK_DIAL_CONTEXT, dial_string_flat, 1, NULL, NULL, E_MATCH)) &&
540             (strcmp(ast_get_extension_registrar(existing_exten), BASE_REGISTRAR))) {
541                 ast_debug(3, "An extension for '%s@%s' was already registered by another registrar '%s'\n",
542                         dial_string_flat, PARK_DIAL_CONTEXT, ast_get_extension_registrar(existing_exten));
543         } else if (ast_add_extension2_nolock(park_dial_context, 1, dial_string_flat, 1, NULL, NULL,
544                         "Dial", duplicate_returnexten, ast_free_ptr, BASE_REGISTRAR)) {
545                         ast_free(duplicate_returnexten);
546                 ast_log(LOG_ERROR, "Failed to create parking redial parker extension %s@%s - Dial(%s)\n",
547                         dial_string_flat, PARK_DIAL_CONTEXT, returnexten);
548         }
549
550         if (ast_unlock_context(park_dial_context)) {
551                 ast_assert(0);
552         }
553
554 abandon_extension_creation:
555
556         /* async_goto the proper PBX destination - this should happen when we come out of the bridge */
557         if (!ast_strlen_zero(user->comeback)) {
558                 ast_async_parseable_goto(chan, user->comeback);
559         } else {
560                 comeback_goto(user, user->lot);
561         }
562
563         return -1;
564 }
565
566 void say_parking_space(struct ast_bridge_channel *bridge_channel, const char *payload)
567 {
568         int numeric_value;
569         int hangup_after;
570
571         if (sscanf(payload, "%u %u", &hangup_after, &numeric_value) != 2) {
572                 /* If say_parking_space is called with a non-numeric string, we have a problem. */
573                 ast_assert(0);
574                 ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END_NO_DISSOLVE);
575                 return;
576         }
577
578         ast_say_digits(bridge_channel->chan, numeric_value, "", ast_channel_language(bridge_channel->chan));
579
580         if (hangup_after) {
581                 ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END_NO_DISSOLVE);
582         }
583 }
584
585 void parking_set_duration(struct ast_bridge_features *features, struct parked_user *user)
586 {
587         unsigned int time_limit;
588
589         time_limit = user->time_limit * 1000;
590
591         if (!time_limit) {
592                 /* There is no duration limit that we need to apply. */
593                 return;
594         }
595
596         /* If the time limit has already been passed, set a really low time limit so we can kick them out immediately. */
597         time_limit = ast_remaining_ms(user->start, time_limit);
598         if (time_limit <= 0) {
599                 time_limit = 1;
600         }
601
602         /* The interval hook is going to need a reference to the parked_user */
603         ao2_ref(user, +1);
604
605         if (ast_bridge_interval_hook(features, 0, time_limit,
606                 parking_duration_callback, user, __ao2_cleanup, AST_BRIDGE_HOOK_REMOVE_ON_PULL)) {
607                 ast_log(LOG_ERROR, "Failed to apply duration limit to the parked call.\n");
608                 ao2_ref(user, -1);
609         }
610 }
611
612 struct ast_parking_bridge_feature_fn_table parking_provider = {
613         .module_version = PARKING_MODULE_VERSION,
614         .module_name = __FILE__,
615         .parking_is_exten_park = parking_is_exten_park,
616         .parking_blind_transfer_park = parking_blind_transfer_park,
617         .parking_park_bridge_channel = parking_park_bridge_channel,
618         .parking_park_call = parking_park_call,
619 };
620
621 void unload_parking_bridge_features(void)
622 {
623         ast_bridge_features_unregister(AST_BRIDGE_BUILTIN_PARKCALL);
624         ast_parking_unregister_bridge_features(parking_provider.module_name);
625 }
626
627 int load_parking_bridge_features(void)
628 {
629         parking_provider.module_info = parking_get_module_info();
630
631         if (ast_parking_register_bridge_features(&parking_provider)) {
632                 return -1;
633         }
634
635         if (ast_bridge_features_register(AST_BRIDGE_BUILTIN_PARKCALL, feature_park_call, NULL)) {
636                 return -1;
637         }
638
639         return 0;
640 }