1 /*!
2  * \file	sccp_featureParkingLot.c
3  * \brief	SCCP ParkingLot Class
4  * \author	Diederik de Groot <ddegroot [at] users.sf.net>
5  * \date	2015-Sept-16
6  * \note	This program is free software and may be modified and distributed under the terms of the GNU Public License.
7  *		See the LICENSE file at the top of the source tree.
8  *
9  * $date$
10  * $revision$
11  */
12 #include "config.h"
13 #include "common.h"
14 
15 SCCP_FILE_VERSION(__FILE__, "");
16 
17 #include "sccp_featureParkingLot.h"
18 
19 #ifdef CS_SCCP_PARK
20 
21 #include "sccp_utils.h"
22 #include "sccp_vector.h"
23 #include "sccp_device.h"
24 #include "sccp_line.h"
25 #	include "sccp_linedevice.h"
26 #	include "sccp_channel.h"
27 #	include "sccp_feature.h"
28 #	include "sccp_labels.h"
29 
30 static const uint32_t appID = APPID_VISUALPARKINGLOT;
31 
32 #ifdef HAVE_PBX_APP_H
33 #  include <asterisk/app.h>
34 #endif
35 
36 /* asterisk-11 */
37 /*
38 Event: ParkedCall
39 Privilege: call,all
40 Timestamp: 1460205775.670404
41 Exten: 701
42 Channel: SCCP/10041-0000000e
43 Parkinglot: default
44 From: SCCP/10011-0000000f
45 Timeout: 45
46 CallerIDNum: 10041
47 CallerIDName: PHONE4
48 ConnectedLineNum: <unknown>
49 ConnectedLineName: <unknown>
50 Uniqueid: 1460205775.48
51 
52 Event: UnParkedCall
53 Privilege: call,all
54 Timestamp: 1460205785.934545
55 Exten: 701
56 Channel: SCCP/10041-0000000e
57 Parkinglot: default
58 From: SCCP/10031-00000010
59 CallerIDNum: 10041
60 CallerIDName: PHONE4
61 ConnectedLineNum: <unknown>
62 ConnectedLineName: <unknown>
63 Uniqueid: 1460205775.48
64 
65 Event: ParkedCallGiveUp
66 Privilege: call,all
67 Timestamp: 1460819185.922496
68 Exten: 701
69 Channel: SCCP/10041-00000001
70 Parkinglot: default
71 CallerIDNum: 10041
72 CallerIDName: PHONE4
73 ConnectedLineNum: 10011
74 ConnectedLineName: Diederik-Phone1
75 UniqueID: 1460819174.54
76 
77 Event: ParkedCallTimeOut
78 Privilege: call,all
79 Timestamp: 1460974082.683646
80 Exten: 701
81 Channel: SCCP/10011-00000003
82 Parkinglot: default
83 CallerIDNum: 10011
84 CallerIDName: Diederik-Phone1
85 ConnectedLineNum: 10031
86 ConnectedLineName: Diederik-Phone3
87 UniqueID: 1460974037.17
88 */
89 
90 /* asterisk-13 */
91 /*
92 Event: ParkedCall
93 Privilege: call,all
94 SequenceNumber: 118
95 File: parking/parking_manager.c
96 Line: 676
97 Func: parked_call_message_response
98 ParkeeChannel: SCCP/10011-00000001
99 ParkeeChannelState: 6
100 ParkeeChannelStateDesc: Up
101 ParkeeCallerIDNum: 10011
102 ParkeeCallerIDName: Diederik-Phone1
103 ParkeeConnectedLineNum: <unknown>
104 ParkeeConnectedLineName: <unknown>
105 ParkeeLanguage: en
106 ParkeeAccountCode: 10011
107 ParkeeContext: internal
108 ParkeeExten: 10031
109 ParkeePriority: 3
110 ParkeeUniqueid: 1461160476.0
111 ParkeeLinkedid: 1461160476.0
112 ParkerDialString: SCCP/10031
113 Parkinglot: default
114 ParkingSpace: 701
115 ParkingTimeout: 45
116 ParkingDuration: 0
117 
118 UnParkedCall
119 Privilege: call,all
120 SequenceNumber: 1091
121 File: parking/parking_manager.c
122 Line: 676
123 Func: parked_call_message_response
124 ParkeeChannel: SCCP/10011-00000001
125 ParkeeChannelState: 6
126 ParkeeChannelStateDesc: Up
127 ParkeeCallerIDNum: 10011
128 ParkeeCallerIDName: Diederik-Phone1
129 ParkeeConnectedLineNum: <unknown>
130 ParkeeConnectedLineName: <unknown>
131 ParkeeLanguage: en
132 ParkeeAccountCode: 10011
133 ParkeeContext: internal
134 ParkeeExten: 10031
135 ParkeePriority: 3
136 ParkeeUniqueid: 1461161791.29
137 ParkeeLinkedid: 1461161791.29
138 RetrieverChannel: SCCP/10041-00000003
139 RetrieverChannelState: 6
140 RetrieverChannelStateDesc: Up
141 RetrieverCallerIDNum: 10041
142 RetrieverCallerIDName: PHONE4
143 RetrieverConnectedLineNum: <unknown>
144 RetrieverConnectedLineName: <unknown>
145 RetrieverLanguage: en
146 RetrieverAccountCode: 79005
147 RetrieverContext: internal
148 RetrieverExten: 701
149 RetrieverPriority: 1
150 RetrieverUniqueid: 1461161803.31
151 RetrieverLinkedid: 1461161803.31
152 ParkerDialString: SCCP/10031
153 Parkinglot: default
154 ParkingSpace: 701
155 ParkingTimeout: 35
156 ParkingDuration: 10
157 
158 Event: ParkedCallGiveUp
159 Privilege: call,all
160 SequenceNumber: 142
161 File: parking/parking_manager.c
162 Line: 676
163 Func: parked_call_message_response
164 ParkeeChannel: SCCP/10011-00000001
165 ParkeeChannelState: 6
166 ParkeeChannelStateDesc: Up
167 ParkeeCallerIDNum: 10011
168 ParkeeCallerIDName: Diederik-Phone1
169 ParkeeConnectedLineNum: <unknown>
170 ParkeeConnectedLineName: <unknown>
171 ParkeeLanguage: en
172 ParkeeAccountCode: 10011
173 ParkeeContext: internal
174 ParkeeExten: 10031
175 ParkeePriority: 3
176 ParkeeUniqueid: 1461160476.0
177 ParkeeLinkedid: 1461160476.0
178 ParkerDialString: SCCP/10031
179 Parkinglot: default
180 ParkingSpace: 701
181 ParkingTimeout: 36
182 ParkingDuration: 9
183 
184 Event: ParkedCallTimeOut
185 Privilege: call,all
186 SequenceNumber: 427
187 File: parking/parking_manager.c
188 Line: 676
189 Func: parked_call_message_response
190 ParkeeChannel: SCCP/10011-00000007
191 ParkeeChannelState: 6
192 ParkeeChannelStateDesc: Up
193 ParkeeCallerIDNum: 10011
194 ParkeeCallerIDName: Diederik-Phone1
195 ParkeeConnectedLineNum: <unknown>
196 ParkeeConnectedLineName: <unknown>
197 ParkeeLanguage: en
198 ParkeeAccountCode: 10011
199 ParkeeContext: park-dial
200 ParkeeExten: SCCP_10031
201 ParkeePriority: 1
202 ParkeeUniqueid: 1461160740.12
203 ParkeeLinkedid: 1461160740.12
204 ParkerDialString: SCCP/10031
205 Parkinglot: default
206 ParkingSpace: 701
207 ParkingTimeout: 0
208 ParkingDuration: 45
209 */
210 
211 /* forward declarations */
212 struct parkinglot;
213 typedef struct parkinglot sccp_parkinglot_t;
214 static void notifyLocked(sccp_parkinglot_t *pl);
215 
216 typedef struct plslot plslot_t;
217 typedef struct plobserver plobserver_t;
218 
219 /* private variables */
220 struct plslot {
221 	int slot;
222 	const char *exten;
223 	const char *from;
224 	const char *channel;
225 	const char *callerid_num;
226 	const char *callerid_name;
227 	const char *connectedline_num;
228 	const char *connectedline_name;
229 };
230 
231 struct plobserver {
232 	sccp_device_t * device;
233 	uint8_t instance;
234 	uint8_t transactionId;
235 };
236 
237 struct parkinglot {
238 	pbx_mutex_t lock;
239 	char *context;
240 	SCCP_VECTOR(, plobserver_t) observers;
241 	SCCP_VECTOR(, plslot_t) slots;
242 	SCCP_RWLIST_ENTRY(sccp_parkinglot_t) list;
243 };
244 
245 #define ICONSTATE_NEW_ON 0x020303												// option:closed, color=yellow, flashspeed=slow
246 #define ICONSTATE_NEW_OFF 0x010000												// option:open, color=off, flashspeed=None
247 #define ICONSTATE_OLD_ON 1													// option:closed
248 #define ICONSTATE_OLD_OFF 0													// option:open
249 
250 /* private functions */
251 //#define sccp_parkinglot_lock(x)	({sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_2 "%s:%d:requestinglock:%p\n",__PRETTY_FUNCTION__,__LINE__,x);pbx_mutex_lock(&((sccp_parkinglot_t * const)(x))->lock);sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_2 "%s:%d:locked:%p\n",__PRETTY_FUNCTION__,__LINE__,x);})				// discard const
252 //#define sccp_parkinglot_unlock(x)	({sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_2 "%s:%d:unlock:%p\n",__PRETTY_FUNCTION__,__LINE__,x);pbx_mutex_unlock(&((sccp_parkinglot_t * const)(x))->lock);})			// discard const
253 #define sccp_parkinglot_lock(x)		({pbx_mutex_lock(&((sccp_parkinglot_t * const)(x))->lock);})				// discard const
254 #define sccp_parkinglot_unlock(x)	({pbx_mutex_unlock(&((sccp_parkinglot_t * const)(x))->lock);})				// discard const
255 
256 SCCP_RWLIST_HEAD(sccp_parkinglot_vector, sccp_parkinglot_t) parkinglots;
257 #define OBSERVER_CB_CMP(elem, value) ((elem).device == (value).device && (elem).instance == (value).instance)
258 #define SLOT_CB_CMP(elem, value) ((elem).slot == (value))
259 
260 #define SLOT_CLEANUP(elem) 							\
261 	if ((elem).exten) {sccp_free((elem).exten);}				\
262 	if ((elem).from) {sccp_free((elem).from);}				\
263 	if ((elem).channel) {sccp_free((elem).channel);}			\
264 	if ((elem).callerid_num) {sccp_free((elem).callerid_num);}		\
265 	if ((elem).callerid_name) {sccp_free((elem).callerid_name);}		\
266 	if ((elem).connectedline_num) {sccp_free((elem).connectedline_num);}	\
267 	if ((elem).connectedline_name) {sccp_free((elem).connectedline_name);}
268 
269 
270 /* exported functions */
addParkinglot(const char * parkinglot)271 static sccp_parkinglot_t * addParkinglot(const char *parkinglot)
272 {
273 	pbx_assert(parkinglot != NULL);
274 
275 	sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_1 "SCCP: (addParkinglot) %s\n", parkinglot);
276 	sccp_parkinglot_t *pl = (sccp_parkinglot_t *) sccp_calloc(sizeof(sccp_parkinglot_t), 1);
277 
278 	pl->context = pbx_strdup(parkinglot);
279 	pbx_mutex_init(&pl->lock);
280 	SCCP_VECTOR_INIT(&pl->observers,1);
281 	SCCP_VECTOR_INIT(&pl->slots,1);
282 
283 	SCCP_RWLIST_WRLOCK(&parkinglots);
284 	SCCP_RWLIST_INSERT_HEAD(&parkinglots, pl, list);
285 	SCCP_RWLIST_UNLOCK(&parkinglots);
286 	return pl;
287 }
288 
removeParkinglot(sccp_parkinglot_t * pl)289 static int removeParkinglot(sccp_parkinglot_t *pl)
290 {
291 	pbx_assert(pl != NULL && pl != NULL);
292 
293 	int res = FALSE;
294 	sccp_parkinglot_t *removed = NULL;
295 	sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_1 "SCCP: (removeParkinglot) %s\n", pl->context);
296 
297 	SCCP_RWLIST_WRLOCK(&parkinglots);
298 	removed = SCCP_RWLIST_REMOVE(&parkinglots, pl, list);
299 	SCCP_RWLIST_UNLOCK(&parkinglots);
300 	sccp_parkinglot_unlock(pl);
301 
302 	if (removed) {
303 		if (removed->context) {
304 			sccp_free(removed->context);
305 		}
306 		SCCP_VECTOR_RESET(&removed->observers, SCCP_VECTOR_ELEM_CLEANUP_NOOP);
307 		SCCP_VECTOR_FREE(&removed->observers);
308 		SCCP_VECTOR_RESET(&removed->slots, SLOT_CLEANUP);
309 		SCCP_VECTOR_FREE(&removed->slots);
310 		pbx_mutex_destroy(&removed->lock);
311 		sccp_free(removed);
312 		res = TRUE;
313 	}
314 	sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_1 "SCCP: (removeParkinglot) done\n");
315 	return res;
316 }
317 
318 /* returns locked pl */
findParkinglotByContext(const char * parkinglot)319 static sccp_parkinglot_t * const findParkinglotByContext(const char *parkinglot)
320 {
321 	pbx_assert(parkinglot != NULL);
322 
323 	sccp_parkinglot_t *pl = NULL;
324 	SCCP_RWLIST_RDLOCK(&parkinglots);
325 	SCCP_RWLIST_TRAVERSE(&parkinglots, pl, list) {
326 		sccp_parkinglot_lock(pl);
327 		if (sccp_strcaseequals(pl->context, parkinglot)) {
328 			//sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_1 "SCCP: (findParkinglotByContext) found match:%s\n", pl->context);
329 			// returning parkinglot locked
330 			break;
331 		}
332 		sccp_parkinglot_unlock(pl);
333 	}
334 	SCCP_RWLIST_UNLOCK(&parkinglots);
335 	return pl;
336 }
337 
338 /* returns locked pl */
findCreateParkinglot(const char * parkinglot,boolean_t create)339 static sccp_parkinglot_t * const findCreateParkinglot(const char *parkinglot, boolean_t create)
340 {
341 	pbx_assert(parkinglot != NULL);
342 
343 	//sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_1 "SCCP: (findCreateParkinglot) %s (create:%s)\n", parkinglot, create ? "TRUE" : "FALSE");
344 	sccp_parkinglot_t *pl = findParkinglotByContext(parkinglot);
345 	if (!pl && create) {
346 		if (!(pl = addParkinglot(parkinglot))) {
347 			//pbx_log(LOG_NOTICE, "SCCP: (findCreateParkinglot) Could not add ParkingLot: %s\n", parkinglot);
348 			return NULL;
349 		}
350 		//sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_1 "SCCP: (findCreateParkinglot) New %s Created\n", parkinglot);
351 		sccp_parkinglot_lock(pl);
352 	}
353 	sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_1 "SCCP: (findCreateParkinglot) Found:%s \n", pl ? "TRUE" : "FALSE");
354 	return pl;
355 }
356 
357 // observer
attachObserver(sccp_device_t * device,const sccp_buttonconfig_t * const buttonConfig)358 static int attachObserver(sccp_device_t * device, const sccp_buttonconfig_t * const buttonConfig)
359 {
360 	pbx_assert(device != NULL && buttonConfig != NULL);
361 	int res = FALSE;
362 
363 	if(!sccp_strlen_zero(buttonConfig->button.feature.options)) {
364 		sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_1 "%s: (attachObserver) device:%s at instance:%d\n", buttonConfig->button.feature.options, device->id, buttonConfig->instance);
365 		RAII(sccp_parkinglot_t *, pl, findCreateParkinglot(buttonConfig->button.feature.options, TRUE), sccp_parkinglot_unlock);
366 		if (pl) {
367 			plobserver_t observer = {
368 				.device = device,
369 				.instance = buttonConfig->instance,
370 				.transactionId = 0,
371 			};
372 
373 			/* upgrade to wrlock */
374 			if (SCCP_VECTOR_APPEND(&pl->observers, observer) == 0) {
375 				res = TRUE;
376 			}
377 		}
378 	}
379 	return res;
380 }
381 
detachObserver(sccp_device_t * device,const sccp_buttonconfig_t * const buttonConfig)382 static int detachObserver(sccp_device_t * device, const sccp_buttonconfig_t * const buttonConfig)
383 {
384 	pbx_assert(device != NULL && buttonConfig != NULL);
385 	int res = FALSE;
386 
387 	if(!sccp_strlen_zero(buttonConfig->button.feature.options)) {
388 		sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_1 "%s: (detachObserver) device:%s at instance:%d\n", buttonConfig->button.feature.options, device->id, buttonConfig->instance);
389 		sccp_parkinglot_t * pl = findCreateParkinglot(buttonConfig->button.feature.options, FALSE); /* don't use RAII, removeParkinglot unlocks and destroys the lock */
390 		if (pl) {
391 			plobserver_t cmp = {
392 				.device = device,
393 				.instance = buttonConfig->instance,
394 			};
395 			if (SCCP_VECTOR_REMOVE_CMP_UNORDERED(&pl->observers, cmp, OBSERVER_CB_CMP, SCCP_VECTOR_ELEM_CLEANUP_NOOP) == 0) {
396 				res = TRUE;
397 			}
398 			if (SCCP_VECTOR_SIZE(&pl->observers) == 0) {
399 				removeParkinglot(pl);	// will destroy pl and unlock pl in the process
400 			} else {
401 				sccp_parkinglot_unlock(pl);
402 			}
403 		}
404 	}
405 	return res;
406 }
407 
getParkingLotCXML(sccp_parkinglot_t * pl,int protocolversion,uint8_t instance,uint32_t transactionId,char ** const outbuf)408 static char * const getParkingLotCXML(sccp_parkinglot_t *pl, int protocolversion, uint8_t instance, uint32_t transactionId, char **const outbuf)
409 {
410 	pbx_assert(pl != NULL && outbuf != NULL);
411 
412 	sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_1 "%s: (getParkingLotCXML) with version:%d\n", pl->context, protocolversion);
413 	*outbuf = NULL;
414 	if (SCCP_VECTOR_SIZE(&pl->slots)) {
415 		pbx_str_t *buf = ast_str_create(DEFAULT_PBX_STR_BUFFERSIZE);
416 		pbx_str_append(&buf, 0, "<?xml version=\"1.0\"?>");
417 		if (protocolversion < 15) {
418 			pbx_str_append(&buf, 0, "<CiscoIPPhoneMenu>");
419 		} else {
420 			pbx_str_append(&buf, 0, "<CiscoIPPhoneMenu appId='%d' onAppClosed='%d'>", appID, appID);
421 		}
422 		pbx_str_append(&buf, 0, "<Title>Parked Calls</Title>");
423 		pbx_str_append(&buf, 0, "<Prompt>Choose a ParkingLot Slot</Prompt>");
424 		for(uint8_t idx = 0; idx < SCCP_VECTOR_SIZE(&pl->slots); idx++) {
425 			plslot_t *slot = SCCP_VECTOR_GET_ADDR(&pl->slots, idx);
426 			pbx_str_append(&buf, 0, "<MenuItem>");
427 			const char *connected_line = !sccp_strcaseequals(slot->connectedline_name, "<unknown>") ? slot->connectedline_name : slot->from;
428 			if (!sccp_strcaseequals(slot->callerid_name, "<unknown>")) {
429 				pbx_str_append(&buf, 0, "<Name>%s (%s) by %s</Name>", slot->callerid_name, slot->callerid_num, connected_line);
430 			} else {
431 				pbx_str_append(&buf, 0, "<Name>%s by %s</Name>", slot->callerid_num, connected_line);
432 			}
433 			pbx_str_append(&buf, 0, "<URL>UserCallData:%d:%d:%d:%d:%s/%s</URL>", appID, instance, 0, transactionId, pl->context, slot->exten);
434 			pbx_str_append(&buf, 0, "</MenuItem>");
435 		}
436 		pbx_str_append(&buf, 0, "<SoftKeyItem>");
437 		pbx_str_append(&buf, 0, "<Name>Dial</Name>");
438 		pbx_str_append(&buf, 0, "<Position>1</Position>");
439 		pbx_str_append(&buf, 0, "<URL>UserDataSoftKey:Select:%d:DIAL/%d</URL>", appID, transactionId);
440 		pbx_str_append(&buf, 0, "</SoftKeyItem>\n");
441 		pbx_str_append(&buf, 0, "<SoftKeyItem>");
442 		pbx_str_append(&buf, 0, "<Name>Exit</Name>");
443 		pbx_str_append(&buf, 0, "<Position>3</Position>");
444 		pbx_str_append(&buf, 0, "<URL>UserDataSoftKey:Select:%d:EXIT/%d</URL>", appID, transactionId);
445 		pbx_str_append(&buf, 0, "</SoftKeyItem>\n");
446 
447 		pbx_str_append(&buf, 0, "</CiscoIPPhoneMenu>");
448 		*outbuf = pbx_strdup(pbx_str_buffer(buf));
449 		sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_1 "%s: (getParkingLotCXML) with version:%d, result:\n[%s]\n", pl->context, protocolversion, *outbuf);
450 		sccp_free(buf);
451 	}
452 	return *outbuf;
453 }
454 
__showVisualParkingLot(sccp_parkinglot_t * pl,constDevicePtr d,plobserver_t * observer)455 static void __showVisualParkingLot(sccp_parkinglot_t *pl, constDevicePtr d, plobserver_t * observer)
456 {
457 	pbx_assert(pl != NULL && d != NULL && observer != NULL);
458 	uint32_t transactionId = sccp_random();
459 	char * xmlStr = NULL;
460 
461 	sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_1 "%s: (showVisualParkingLot) showing on device:%s, instance:%d\n", pl->context, observer->device->id, observer->instance);
462 	if ((xmlStr = getParkingLotCXML(pl, d->protocolversion, observer->instance, transactionId, &xmlStr))) {
463 		sccp_parkinglot_unlock(pl);
464 		d->protocol->sendUserToDeviceDataVersionMessage(d, appID, 0, 0, transactionId, xmlStr, 0);
465 		sccp_free(xmlStr);
466 		sccp_parkinglot_lock(pl);
467 	} else {
468 		sccp_parkinglot_unlock(pl);
469 		sccp_dev_displayprinotify(d, SKINNY_DISP_CANNOT_RETRIEVE_PARKED_CALL, SCCP_MESSAGE_PRIORITY_TIMEOUT, 5);
470 		sccp_parkinglot_lock(pl);
471 	}
472 	observer->transactionId = transactionId;
473 }
474 
__hideVisualParkingLot(sccp_parkinglot_t * pl,constDevicePtr d,plobserver_t * observer)475 static void __hideVisualParkingLot(sccp_parkinglot_t *pl, constDevicePtr d, plobserver_t *observer)
476 {
477 	pbx_assert(pl != NULL && observer != NULL);
478 
479 	sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_1 "%s: (hideVisualParkingLot) device:%s, instance:%d\n", pl->context, d->id, observer->instance);
480 	uint32_t transactionId = observer->transactionId;
481 	sccp_parkinglot_unlock(pl);
482 
483 	char xmlStr[DEFAULT_PBX_STR_BUFFERSIZE];
484 	if (d->protocolversion < 15) {
485 		snprintf(xmlStr, DEFAULT_PBX_STR_BUFFERSIZE, "<CiscoIPPhoneExecute><ExecuteItem Priority=\"0\" URL=\"Init:Services\"/></CiscoIPPhoneExecute>");
486 	} else {
487 		snprintf(xmlStr, DEFAULT_PBX_STR_BUFFERSIZE, "<CiscoIPPhoneExecute><ExecuteItem Priority=\"0\" URL=\"App:Close:%d\"/></CiscoIPPhoneExecute>", appID);
488 	}
489 	d->protocol->sendUserToDeviceDataVersionMessage(observer->device, appID, 0, 0, transactionId, xmlStr, 0);
490 
491 	sccp_parkinglot_lock(pl);
492 	observer->transactionId = 0;
493 }
494 
hideVisualParkingLot(const char * parkinglot,constDevicePtr d,uint8_t instance)495 static void hideVisualParkingLot(const char *parkinglot, constDevicePtr d, uint8_t instance)
496 {
497 	pbx_assert(parkinglot != NULL &&  d != NULL);
498 
499 	RAII(sccp_parkinglot_t *, pl, findCreateParkinglot(parkinglot, TRUE), sccp_parkinglot_unlock);
500 	if (pl) {
501 		sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_1 "%s: (hideVisualParkingLot) device:%s, instance:%d, size:%d\n", parkinglot, d->id, instance, (int)SCCP_VECTOR_SIZE(&pl->observers));
502 		for(uint8_t idx = 0; idx < SCCP_VECTOR_SIZE(&pl->observers); idx++) {
503 			plobserver_t *observer = SCCP_VECTOR_GET_ADDR(&pl->observers, idx);
504 			if (observer->device == d && observer->instance == instance) {
505 				__hideVisualParkingLot(pl, d, observer);
506 			}
507 		}
508 	}
509 }
510 
_notifyHelper(plobserver_t * observer,sccp_parkinglot_t * pl,constDevicePtr device)511 static void _notifyHelper(plobserver_t *observer, sccp_parkinglot_t *pl, constDevicePtr device)
512 {
513 	uint32_t iconstate = 0;
514 	sccp_buttonconfig_t *config = NULL;
515 	int numslots = SCCP_VECTOR_SIZE(&pl->slots);
516 	if (device->protocolversion < 15) {
517 		sccp_device_setLamp(device, SKINNY_STIMULUS_PARKINGLOT, 0, numslots ? SKINNY_LAMP_ON : SKINNY_LAMP_OFF);
518 		iconstate = numslots ? ICONSTATE_OLD_ON : ICONSTATE_OLD_OFF;
519 	} else {
520 		iconstate = numslots ? ICONSTATE_NEW_ON : ICONSTATE_NEW_OFF;
521 	}
522 
523 	// change button state
524 	SCCP_LIST_LOCK(&device->buttonconfig);
525 	SCCP_LIST_TRAVERSE(&device->buttonconfig, config, list) {
526 		if (config->type == FEATURE && config->instance == observer->instance) {
527 			config->button.feature.status = iconstate;
528 		}
529 	}
530 	SCCP_LIST_UNLOCK(&device->buttonconfig);
531 
532 	// update already displayed visual parkinglot window
533 	if (observer->transactionId) {
534 		if (numslots > 0 && !device->active_channel) {
535 			__showVisualParkingLot(pl, device, observer);
536 		} else {
537 			__hideVisualParkingLot(pl, device, observer);
538 		}
539 	}
540 	sccp_feat_changed(device, NULL, SCCP_FEATURE_PARKINGLOT);
541 }
542 
notifyDevice(constDevicePtr device,const sccp_buttonconfig_t * const buttonConfig)543 static void notifyDevice(constDevicePtr device, const sccp_buttonconfig_t * const buttonConfig)
544 {
545 	pbx_assert(device != NULL && buttonConfig != NULL);
546 	uint8_t idx = 0;
547 	//uint32_t iconstate = 0;
548 	plobserver_t *observer = NULL;
549 
550 	if(!sccp_strlen_zero(buttonConfig->button.feature.options)) {
551 		sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_1 "%s: (notifyDevice) notifyDevice:%s\n", buttonConfig->button.feature.options, device->id);
552 		RAII(sccp_parkinglot_t *, pl, findCreateParkinglot(buttonConfig->button.feature.options, TRUE), sccp_parkinglot_unlock);
553 		if (pl) {
554 			for (idx = 0; idx < SCCP_VECTOR_SIZE(&pl->observers); idx++) {
555 				observer = SCCP_VECTOR_GET_ADDR(&pl->observers, idx);
556 				if (observer && observer->device == device) {
557 					_notifyHelper(observer, pl, device);
558 				}
559 			}
560 		}
561 	}
562 }
563 
notifyLocked(sccp_parkinglot_t * pl)564 static void notifyLocked(sccp_parkinglot_t *pl)
565 {
566 	pbx_assert(pl != NULL);
567 
568 	sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_1 "%s: (notify)\n", pl->context);
569 	uint8_t idx = 0;
570 	plobserver_t *observer = NULL;
571 
572 	for (idx = 0; idx < SCCP_VECTOR_SIZE(&pl->observers); idx++) {
573 		observer = SCCP_VECTOR_GET_ADDR(&pl->observers, idx);
574 		if (observer) {
575 			AUTO_RELEASE(sccp_device_t, device , sccp_device_retain(observer->device));
576 			if (device) {
577 				_notifyHelper(observer, pl, device);
578 			}
579 		}
580 	}
581 }
582 
583 // slot
addSlot(const char * parkinglot,int slot,struct message * m)584 static int addSlot(const char *parkinglot, int slot, struct message *m)
585 {
586 	pbx_assert(parkinglot != NULL && m != NULL);
587 
588 	int res = FALSE;
589 
590 	sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_1 "%s: (addSlot) adding to slot:%d\n", parkinglot, slot);
591 
592 	RAII(sccp_parkinglot_t *, pl, findCreateParkinglot(parkinglot, TRUE), sccp_parkinglot_unlock);
593 	if (pl) {
594 		if (SCCP_VECTOR_GET_CMP(&pl->slots, slot, SLOT_CB_CMP) == NULL) {
595 			plslot_t new_slot = {
596 				.slot = slot,
597 				.exten = pbx_strdup(astman_get_header(m, PARKING_SLOT)),
598 				.from = pbx_strdup(astman_get_header(m, PARKING_FROM)),
599 				.channel = pbx_strdup(astman_get_header(m, PARKING_PREFIX "Channel")),
600 				.callerid_num = pbx_strdup(astman_get_header(m, PARKING_PREFIX "CallerIDNum")),
601 				.callerid_name = pbx_strdup(astman_get_header(m, PARKING_PREFIX "CallerIDName")),
602 				.connectedline_num = pbx_strdup(astman_get_header(m, PARKING_PREFIX "ConnectedLineNum")),
603 				.connectedline_name = pbx_strdup(astman_get_header(m, PARKING_PREFIX "ConnectedLineName")),
604 			};
605 			if (SCCP_VECTOR_APPEND(&pl->slots, new_slot) == 0)  {
606 				notifyLocked(pl);
607 				res = TRUE;
608 			}
609 		} else {
610 			notifyLocked(pl);
611 		}
612 	} else {
613 		sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_1 "SCCP: (addSlot) ParkingLot:%s is not being observed\n", parkinglot);
614 	}
615 	return res;
616 }
617 
removeSlot(const char * parkinglot,int slot)618 static int removeSlot(const char *parkinglot, int slot)
619 {
620 	pbx_assert(parkinglot != NULL);
621 
622 	sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_1 "%s: (removeSlot) removing slot:%d\n", parkinglot, slot);
623 	int res = FALSE;
624 
625 	RAII(sccp_parkinglot_t *, pl, findCreateParkinglot(parkinglot, TRUE), sccp_parkinglot_unlock);
626 	if (pl) {
627 		if (SCCP_VECTOR_REMOVE_CMP_UNORDERED(&pl->slots, slot, SLOT_CB_CMP, SLOT_CLEANUP) == 0) {
628 			notifyLocked(pl);
629 			res = TRUE;
630 		}
631 	} else {
632 		sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_1 "SCCP: (removeSlot) ParkingLot:%s is not being observed\n", parkinglot);
633 	}
634 	return !res;
635 }
636 
637 /*
638  * Handle Park Feature Button Press
639  * -If we have an active call -> pressing the park feature key, will park that call
640  * -If we are not on an active call:
641  * 	- If there is 0 parked calls: Display Status Message, "No parked calls'
642  * 	- If there is 1 parked call: Unpark that call immediatly
643  *	- If there is more than 1 parked call: display the visual parking lot representation.
644  */
handleButtonPress(constDevicePtr d,const sccp_buttonconfig_t * const buttonConfig)645 static void handleButtonPress(constDevicePtr d, const sccp_buttonconfig_t * const buttonConfig)
646 {
647 	pbx_assert(d != NULL && buttonConfig != NULL);
648 	sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_1 "%s: (handleButtonPress) options:%s, instance:%d\n", d->id, buttonConfig->button.feature.options, buttonConfig->instance);
649 
650 	AUTO_RELEASE(sccp_channel_t, channel , sccp_device_getActiveChannel(d));
651 	if (channel && channel->state != SCCP_CHANNELSTATE_OFFHOOK && channel->state != SCCP_CHANNELSTATE_HOLD) {
652 		sccp_channel_park(channel);
653 	} else if(!sccp_strlen_zero(buttonConfig->button.feature.options)) {
654 		RAII(sccp_parkinglot_t *, pl, findCreateParkinglot(buttonConfig->button.feature.options, TRUE), sccp_parkinglot_unlock);
655 		if (pl) {
656 			if (SCCP_VECTOR_SIZE(&pl->slots) == 0) {
657 				sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_1 "%s: (handleButtonPress) 0 slot occupied. Show statusBar message\n", buttonConfig->button.feature.options);
658 				sccp_dev_displayprinotify(d, SKINNY_DISP_CANNOT_RETRIEVE_PARKED_CALL, SCCP_MESSAGE_PRIORITY_TIMEOUT, 5);
659 			} else {
660 				if(sccp_strcaseequals(buttonConfig->button.feature.args, "RetrieveSingle") && SCCP_VECTOR_SIZE(&pl->slots) == 1) {
661 					sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_1 "%s: (handleButtonPress) 1 slot occupied -> Unpark Call Immediately\n", buttonConfig->button.feature.options);
662 					plslot_t *slot = SCCP_VECTOR_GET_ADDR(&pl->slots, 0);
663 					if (slot) {
664 						AUTO_RELEASE(sccp_line_t, line , channel ? sccp_line_retain(channel->line) : d->currentLine ? sccp_dev_getActiveLine(d) : sccp_line_find_byid(d, d->defaultLineInstance));
665 						AUTO_RELEASE(sccp_channel_t, new_channel,
666 							     sccp_channel_newcall(line, d, slot->exten, SKINNY_CALLTYPE_OUTBOUND, NULL, NULL));                                        // implicit release
667 					}
668 				} else {
669 					sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_1 "%s: (handleButtonPress) multiple slots occupied -> Show Visual ParkingLot\n", buttonConfig->button.feature.options);
670 					uint8_t idx = 0;
671 					for (idx = 0; idx < SCCP_VECTOR_SIZE(&pl->observers); idx++) {
672 						plobserver_t *observer = SCCP_VECTOR_GET_ADDR(&pl->observers, idx);
673 						if(observer->device == d && observer->instance == buttonConfig->instance) {
674 							__showVisualParkingLot(pl, d, observer);
675 						}
676 					}
677 				}
678 			}
679 		}
680 	}
681 }
682 
handleDevice2User(const char * parkinglot,constDevicePtr d,const char * slot_exten,uint8_t instance,uint32_t transactionId)683 static void handleDevice2User(const char *parkinglot, constDevicePtr d, const char *slot_exten, uint8_t instance, uint32_t transactionId)
684 {
685 	pbx_assert(d != NULL);
686 	sccp_log(DEBUGCAT_PARKINGLOT)(VERBOSE_PREFIX_1 "%s: (handleDevice2Usewr) instance:%d, transactionId:%d\n", d->id, instance, transactionId);
687 
688 	if (d->dtu_softkey.action && d->dtu_softkey.transactionID == transactionId) {
689 		if (sccp_strequals(d->dtu_softkey.action, "DIAL")) {
690 			AUTO_RELEASE(sccp_line_t, line , d->currentLine ? sccp_dev_getActiveLine(d) : sccp_line_find_byid(d, d->defaultLineInstance));
691 			AUTO_RELEASE(sccp_channel_t, new_channel, sccp_channel_newcall(line, d, slot_exten, SKINNY_CALLTYPE_OUTBOUND, NULL, NULL));                                        // implicit release
692 		} else if (sccp_strequals(d->dtu_softkey.action, "EXIT")) {
693 			hideVisualParkingLot(parkinglot, d, instance);
694 		}
695 	}
696 }
697 /* Assign to interface */
698 const ParkingLotInterface iParkingLot = {
699 	.attachObserver = attachObserver,
700 	.detachObserver = detachObserver,
701 	.addSlot = addSlot,
702 	.removeSlot = removeSlot,
703 	.handleButtonPress = handleButtonPress,
704 	.handleDevice2User = handleDevice2User,
705 	.notifyDevice = notifyDevice,
706 };
707 #else
708 const ParkingLotInterface iParkingLot = { 0 };
709 #endif
710