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