8f2433b296dad8980c4e715f532eb67bd6b02eca
[asterisk/asterisk.git] / res / parking / parking_controller.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 Entry, Exit, and other assorted controls.
22  *
23  * \author Jonathan Rose <jrose@digium.com>
24  */
25 #include "asterisk.h"
26
27 #include "asterisk/logger.h"
28 #include "res_parking.h"
29 #include "asterisk/astobj2.h"
30 #include "asterisk/utils.h"
31 #include "asterisk/manager.h"
32 #include "asterisk/test.h"
33 #include "asterisk/features.h"
34 #include "asterisk/bridging_basic.h"
35
36 struct ast_bridge *parking_lot_get_bridge(struct parking_lot *lot)
37 {
38         struct ast_bridge *lot_bridge;
39
40         if (lot->parking_bridge) {
41                 ao2_ref(lot->parking_bridge, +1);
42                 return lot->parking_bridge;
43         }
44
45         lot_bridge = bridge_parking_new(lot);
46         if (!lot_bridge) {
47                 return NULL;
48         }
49
50         /* The parking lot needs a reference to the bridge as well. */
51         lot->parking_bridge = lot_bridge;
52         ao2_ref(lot->parking_bridge, +1);
53
54         return lot_bridge;
55 }
56
57 void parking_channel_set_roles(struct ast_channel *chan, struct parking_lot *lot, int force_ringing)
58 {
59         ast_channel_add_bridge_role(chan, "holding_participant");
60         if (force_ringing) {
61                 ast_channel_set_bridge_role_option(chan, "holding_participant", "idle_mode", "ringing");
62         } else {
63                 ast_channel_set_bridge_role_option(chan, "holding_participant", "idle_mode", "musiconhold");
64                 if (!ast_strlen_zero(lot->cfg->mohclass)) {
65                         ast_channel_set_bridge_role_option(chan, "holding_participant", "moh_class", lot->cfg->mohclass);
66                 }
67         }
68 }
69
70 struct parking_limits_pvt {
71         struct parked_user *user;
72 };
73
74 int unpark_parked_user(struct parked_user *pu)
75 {
76         if (pu->lot) {
77                 ao2_unlink(pu->lot->parked_users, pu);
78                 parking_lot_remove_if_unused(pu->lot);
79                 return 0;
80         }
81
82         return -1;
83 }
84
85 int parking_lot_get_space(struct parking_lot *lot, int target_override)
86 {
87         int original_target;
88         int current_target;
89         struct ao2_iterator i;
90         struct parked_user *user;
91         int wrap;
92
93         if (lot->cfg->parkfindnext) {
94                 /* Use next_space if the lot already has next_space set; otherwise use lot start. */
95                 original_target = lot->next_space ? lot->next_space : lot->cfg->parking_start;
96         } else {
97                 original_target = lot->cfg->parking_start;
98         }
99
100         if (target_override >= lot->cfg->parking_start && target_override <= lot->cfg->parking_stop) {
101                 original_target = target_override;
102         }
103
104         current_target = original_target;
105
106         wrap = lot->cfg->parking_start;
107
108         i = ao2_iterator_init(lot->parked_users, 0);
109         while ((user = ao2_iterator_next(&i))) {
110                 /* Increment the wrap on each pass until we find an empty space */
111                 if (wrap == user->parking_space) {
112                         wrap += 1;
113                 }
114
115                 if (user->parking_space < current_target) {
116                         /* It's lower than the anticipated target, so we haven't reached the target yet. */
117                         ao2_ref(user, -1);
118                         continue;
119                 }
120
121                 if (user->parking_space > current_target) {
122                         /* The current target is usable because all items below have been read and the next target is higher than the one we want. */
123                         ao2_ref(user, -1);
124                         break;
125                 }
126
127                 /* We found one already parked here. */
128                 current_target += 1;
129                 ao2_ref(user, -1);
130         }
131         ao2_iterator_destroy(&i);
132
133         if (current_target <= lot->cfg->parking_stop) {
134                 return current_target;
135         }
136
137         if (wrap <= lot->cfg->parking_stop) {
138                 return wrap;
139         }
140
141         return -1;
142 }
143
144 static int retrieve_parked_user_targeted(void *obj, void *arg, int flags)
145 {
146         int *target = arg;
147         struct parked_user *user = obj;
148         if (user->parking_space == *target) {
149                 return CMP_MATCH;
150         }
151
152         return 0;
153 }
154
155 struct parked_user *parking_lot_retrieve_parked_user(struct parking_lot *lot, int target)
156 {
157         RAII_VAR(struct parked_user *, user, NULL, ao2_cleanup);
158
159         if (target < 0) {
160                 user = ao2_callback(lot->parked_users, 0, NULL, NULL);
161         } else {
162                 user = ao2_callback(lot->parked_users, 0, retrieve_parked_user_targeted, &target);
163         }
164
165         if (!user) {
166                 return NULL;
167         }
168
169         ao2_lock(user);
170         if (user->resolution != PARK_UNSET) {
171                 /* Abandon. Something else has resolved the parked user before we got to it. */
172                 ao2_unlock(user);
173                 return NULL;
174         }
175
176         ao2_unlink(lot->parked_users, user);
177         user->resolution = PARK_ANSWERED;
178         ao2_unlock(user);
179
180         parking_lot_remove_if_unused(user->lot);
181
182         /* Bump the ref count by 1 since the RAII_VAR will eat the reference otherwise */
183         ao2_ref(user, +1);
184         return user;
185 }
186
187 void parked_call_retrieve_enable_features(struct ast_channel *chan, struct parking_lot *lot, int recipient_mode)
188 {
189         /* Enabling features here should be additive to features that are already on the channel. */
190         struct ast_flags feature_flags = { 0 };
191         struct ast_flags *existing_features;
192
193         ast_channel_lock(chan);
194         existing_features = ast_bridge_features_ds_get(chan);
195
196         if (existing_features) {
197                 feature_flags = *existing_features;
198         }
199
200         if (lot->cfg->parkedcalltransfers & recipient_mode) {
201                 ast_set_flag(&feature_flags, AST_FEATURE_REDIRECT);
202                 ast_set_flag(&feature_flags, AST_FEATURE_ATXFER);
203         }
204
205         if (lot->cfg->parkedcallreparking & recipient_mode) {
206                 ast_set_flag(&feature_flags, AST_FEATURE_PARKCALL);
207         }
208
209         if (lot->cfg->parkedcallhangup & recipient_mode) {
210                 ast_set_flag(&feature_flags, AST_FEATURE_DISCONNECT);
211         }
212
213         if (lot->cfg->parkedcallrecording & recipient_mode) {
214                 ast_set_flag(&feature_flags, AST_FEATURE_AUTOMIXMON);
215         }
216
217         ast_bridge_features_ds_set(chan, &feature_flags);
218         ast_channel_unlock(chan);
219
220         return;
221 }
222
223 void flatten_peername(char *peername)
224 {
225         int i;
226         char *dash;
227
228         /* Truncate after the dash */
229         dash = strrchr(peername, '-');
230         if (dash) {
231                 *dash = '\0';
232         }
233
234         /* Replace slashes with underscores since slashes are reserved characters for extension matching */
235         for (i = 0; peername[i]; i++) {
236                 if (peername[i] == '/') {
237                         /* The underscore is the flattest character of all. */
238                         peername[i] = '_';
239                 }
240         }
241 }
242
243 int comeback_goto(struct parked_user *pu, struct parking_lot *lot)
244 {
245         struct ast_channel *chan = pu->chan;
246         char *peername;
247         const char *blindtransfer;
248
249         ast_channel_lock(chan);
250         if ((blindtransfer = pbx_builtin_getvar_helper(chan, "BLINDTRANSFER"))) {
251                 blindtransfer = ast_strdupa(blindtransfer);
252         }
253         ast_channel_unlock(chan);
254
255         peername = blindtransfer ? ast_strdupa(blindtransfer) : ast_strdupa(pu->parker->name);
256
257         /* Flatten the peername so that it can be used for performing the timeout PBX operations */
258         flatten_peername(peername);
259
260         if (lot->cfg->comebacktoorigin) {
261                 if (ast_exists_extension(chan, PARK_DIAL_CONTEXT, peername, 1, NULL)) {
262                         ast_async_goto(chan, PARK_DIAL_CONTEXT, peername, 1);
263                         return 0;
264                 } else {
265                         ast_log(LOG_ERROR, "Can not start %s at %s,%s,1 because extension does not exist. Terminating call.\n",
266                                 ast_channel_name(chan), PARK_DIAL_CONTEXT, peername);
267                         return -1;
268                 }
269         }
270
271         if (ast_exists_extension(chan, lot->cfg->comebackcontext, peername, 1, NULL)) {
272                 ast_async_goto(chan, lot->cfg->comebackcontext, peername, 1);
273                 return 0;
274         }
275
276         if (ast_exists_extension(chan, lot->cfg->comebackcontext, "s", 1, NULL)) {
277                 ast_verb(2, "Could not start %s at %s,%s,1. Using 's@%s' instead.\n", ast_channel_name(chan),
278                         lot->cfg->comebackcontext, peername, lot->cfg->comebackcontext);
279                 ast_async_goto(chan, lot->cfg->comebackcontext, "s", 1);
280                 return 0;
281         }
282
283         ast_verb(2, "Can not start %s at %s,%s,1 and exten 's@%s' does not exist. Using 's@default'\n",
284                 ast_channel_name(chan),
285                 lot->cfg->comebackcontext, peername, lot->cfg->comebackcontext);
286         ast_async_goto(chan, "default", "s", 1);
287
288         return 0;
289 }