Merge "asterisk.c: When astcanary dies on linux, reset priority on all threads."
[asterisk/asterisk.git] / main / presencestate.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2011-2012, Digium, Inc.
5  *
6  * David Vossel <dvossel@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 Presence state management
22  */
23
24 /*** MODULEINFO
25         <support_level>core</support_level>
26  ***/
27
28 /*** DOCUMENTATION
29         <managerEvent language="en_US" name="PresenceStateChange">
30                 <managerEventInstance class="EVENT_FLAG_CALL">
31                         <synopsis>Raised when a presence state changes</synopsis>
32                         <syntax>
33                                 <parameter name="Presentity">
34                                         <para>The entity whose presence state has changed</para>
35                                 </parameter>
36                                 <parameter name="Status">
37                                         <para>The new status of the presentity</para>
38                                 </parameter>
39                                 <parameter name="Subtype">
40                                         <para>The new subtype of the presentity</para>
41                                 </parameter>
42                                 <parameter name="Message">
43                                         <para>The new message of the presentity</para>
44                                 </parameter>
45                         </syntax>
46                         <description>
47                                 <para>This differs from the <literal>PresenceStatus</literal>
48                                 event because this event is raised for all presence state changes,
49                                 not only for changes that affect dialplan hints.</para>
50                         </description>
51                         <see-also>
52                                 <ref type="managerEvent">PresenceStatus</ref>
53                         </see-also>
54                 </managerEventInstance>
55         </managerEvent>
56 ***/
57
58 #include "asterisk.h"
59
60 ASTERISK_REGISTER_FILE()
61
62 #include "asterisk/_private.h"
63 #include "asterisk/utils.h"
64 #include "asterisk/lock.h"
65 #include "asterisk/linkedlists.h"
66 #include "asterisk/presencestate.h"
67 #include "asterisk/pbx.h"
68 #include "asterisk/app.h"
69 #include "asterisk/test.h"
70
71 /*! \brief Device state strings for printing */
72 static const struct {
73         const char *string;
74         enum ast_presence_state state;
75
76 } state2string[] = {
77         { "not_set", AST_PRESENCE_NOT_SET},
78         { "unavailable", AST_PRESENCE_UNAVAILABLE },
79         { "available", AST_PRESENCE_AVAILABLE},
80         { "away", AST_PRESENCE_AWAY},
81         { "xa", AST_PRESENCE_XA},
82         { "chat", AST_PRESENCE_CHAT},
83         { "dnd", AST_PRESENCE_DND},
84 };
85
86 static struct ast_manager_event_blob *presence_state_to_ami(struct stasis_message *msg);
87
88 STASIS_MESSAGE_TYPE_DEFN(ast_presence_state_message_type,
89         .to_ami = presence_state_to_ami,
90 );
91 struct stasis_topic *presence_state_topic_all;
92 struct stasis_cache *presence_state_cache;
93 struct stasis_caching_topic *presence_state_topic_cached;
94
95 /*! \brief  A presence state provider */
96 struct presence_state_provider {
97         char label[40];
98         ast_presence_state_prov_cb_type callback;
99         AST_RWLIST_ENTRY(presence_state_provider) list;
100 };
101
102 /*! \brief A list of providers */
103 static AST_RWLIST_HEAD_STATIC(presence_state_providers, presence_state_provider);
104
105 const char *ast_presence_state2str(enum ast_presence_state state)
106 {
107         int i;
108         for (i = 0; i < ARRAY_LEN(state2string); i++) {
109                 if (state == state2string[i].state) {
110                         return state2string[i].string;
111                 }
112         }
113         return "";
114 }
115
116 enum ast_presence_state ast_presence_state_val(const char *val)
117 {
118         int i;
119         for (i = 0; i < ARRAY_LEN(state2string); i++) {
120                 if (!strcasecmp(val, state2string[i].string)) {
121                         return state2string[i].state;
122                 }
123         }
124         return AST_PRESENCE_INVALID;
125 }
126
127 static enum ast_presence_state presence_state_cached(const char *presence_provider, char **subtype, char **message)
128 {
129         enum ast_presence_state res = AST_PRESENCE_INVALID;
130         RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
131         struct ast_presence_state_message *presence_state;
132
133         msg = stasis_cache_get(ast_presence_state_cache(), ast_presence_state_message_type(), presence_provider);
134
135         if (!msg) {
136                 return res;
137         }
138
139         presence_state = stasis_message_data(msg);
140         res = presence_state->state;
141
142         *subtype = !ast_strlen_zero(presence_state->subtype) ? ast_strdup(presence_state->subtype) : NULL;
143         *message = !ast_strlen_zero(presence_state->message) ? ast_strdup(presence_state->message) : NULL;
144
145         return res;
146 }
147
148 static enum ast_presence_state ast_presence_state_helper(const char *presence_provider, char **subtype, char **message, int check_cache)
149 {
150         char *labels = ast_strdupa(presence_provider);
151         char *label;
152         enum ast_presence_state state = AST_PRESENCE_INVALID;
153         enum ast_presence_state state_order[] = {
154                 [AST_PRESENCE_INVALID]     = 0,
155                 [AST_PRESENCE_NOT_SET]     = 1,
156                 [AST_PRESENCE_AVAILABLE]   = 2,
157                 [AST_PRESENCE_UNAVAILABLE] = 3,
158                 [AST_PRESENCE_CHAT]        = 4,
159                 [AST_PRESENCE_AWAY]        = 5,
160                 [AST_PRESENCE_XA]          = 6,
161                 [AST_PRESENCE_DND]         = 7
162         };
163
164         while ((label = strsep(&labels, "&"))) {
165                 enum ast_presence_state next_state = AST_PRESENCE_INVALID;
166                 char *next_subtype = NULL;
167                 char *next_message = NULL;
168
169                 if (check_cache) {
170                         next_state = presence_state_cached(label, &next_subtype, &next_message);
171                 }
172
173                 if (next_state == AST_PRESENCE_INVALID) {
174                         struct presence_state_provider *provider;
175                         const struct ast_channel_tech *chan_tech;
176                         char *address;
177
178                         if ((address = strchr(label, '/'))) {
179                                 *address++ = '\0';
180
181                                 if ((chan_tech = ast_get_channel_tech(label)) && chan_tech->presencestate) {
182                                         next_state = chan_tech->presencestate(address, &next_subtype, &next_message);
183                                 }
184                         } else if ((address = strchr(label, ':'))) {
185                                 *address++ = '\0';
186
187                                 AST_RWLIST_RDLOCK(&presence_state_providers);
188                                 AST_RWLIST_TRAVERSE(&presence_state_providers, provider, list) {
189                                         ast_debug(5, "Checking provider %s with %s\n", provider->label, label);
190
191                                         if (!strcasecmp(provider->label, label)) {
192                                                 next_state = provider->callback(address, &next_subtype, &next_message);
193                                                 break;
194                                         }
195                                 }
196                                 AST_RWLIST_UNLOCK(&presence_state_providers);
197
198                                 if (!provider) {
199                                         ast_log(LOG_WARNING, "No provider found for label: %s\n", label);
200                                 }
201                         } else {
202                                 ast_log(LOG_WARNING, "No label found for presence state provider: %s\n", label);
203                         }
204                 }
205
206                 if (state_order[next_state] > state_order[state]) {
207                         state = next_state;
208
209                         ast_free(*subtype);
210                         ast_free(*message);
211
212                         *subtype = next_subtype;
213                         *message = next_message;
214                 }
215         }
216
217         return state;
218 }
219
220 enum ast_presence_state ast_presence_state(const char *presence_provider, char **subtype, char **message)
221 {
222         return ast_presence_state_helper(presence_provider, subtype, message, 1);
223 }
224
225 enum ast_presence_state ast_presence_state_nocache(const char *presence_provider, char **subtype, char **message)
226 {
227         return ast_presence_state_helper(presence_provider, subtype, message, 0);
228 }
229
230 int ast_presence_state_prov_add(const char *label, ast_presence_state_prov_cb_type callback)
231 {
232         struct presence_state_provider *provider;
233
234         if (!callback || !(provider = ast_calloc(1, sizeof(*provider)))) {
235                 return -1;
236         }
237
238         provider->callback = callback;
239         ast_copy_string(provider->label, label, sizeof(provider->label));
240
241         AST_RWLIST_WRLOCK(&presence_state_providers);
242         AST_RWLIST_INSERT_HEAD(&presence_state_providers, provider, list);
243         AST_RWLIST_UNLOCK(&presence_state_providers);
244
245         return 0;
246 }
247 int ast_presence_state_prov_del(const char *label)
248 {
249         struct presence_state_provider *provider;
250         int res = -1;
251
252         AST_RWLIST_WRLOCK(&presence_state_providers);
253         AST_RWLIST_TRAVERSE_SAFE_BEGIN(&presence_state_providers, provider, list) {
254                 if (!strcasecmp(provider->label, label)) {
255                         AST_RWLIST_REMOVE_CURRENT(list);
256                         ast_free(provider);
257                         res = 0;
258                         break;
259                 }
260         }
261         AST_RWLIST_TRAVERSE_SAFE_END;
262         AST_RWLIST_UNLOCK(&presence_state_providers);
263
264         return res;
265 }
266
267 static void presence_state_dtor(void *obj)
268 {
269         struct ast_presence_state_message *presence_state = obj;
270         ast_string_field_free_memory(presence_state);
271 }
272
273 static struct ast_presence_state_message *presence_state_alloc(const char *provider,
274                 enum ast_presence_state state,
275                 const char *subtype,
276                 const char *message)
277 {
278         RAII_VAR(struct ast_presence_state_message *, presence_state, ao2_alloc(sizeof(*presence_state), presence_state_dtor), ao2_cleanup);
279
280         if (!presence_state || ast_string_field_init(presence_state, 256)) {
281                 return NULL;
282         }
283
284         presence_state->state = state;
285         ast_string_field_set(presence_state, provider, provider);
286         ast_string_field_set(presence_state, subtype, S_OR(subtype, ""));
287         ast_string_field_set(presence_state, message, S_OR(message, ""));
288
289         ao2_ref(presence_state, +1);
290         return presence_state;
291 }
292
293 static void presence_state_event(const char *provider,
294                 enum ast_presence_state state,
295                 const char *subtype,
296                 const char *message)
297 {
298         RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
299         RAII_VAR(struct ast_presence_state_message *, presence_state, NULL, ao2_cleanup);
300
301         if (!ast_presence_state_message_type()) {
302                 return;
303         }
304
305         presence_state = presence_state_alloc(provider, state, subtype, message);
306         if (!presence_state) {
307                 return;
308         }
309
310         msg = stasis_message_create(ast_presence_state_message_type(), presence_state);
311         if (!msg) {
312                 return;
313         }
314
315         stasis_publish(ast_presence_state_topic_all(), msg);
316 }
317
318 static void do_presence_state_change(const char *provider)
319 {
320         char *subtype = NULL;
321         char *message = NULL;
322         enum ast_presence_state state;
323
324         state = ast_presence_state_helper(provider, &subtype, &message, 0);
325
326         if (state == AST_PRESENCE_INVALID) {
327                 return;
328         }
329
330         presence_state_event(provider, state, subtype, message);
331         ast_free(subtype);
332         ast_free(message);
333 }
334
335 int ast_presence_state_changed_literal(enum ast_presence_state state,
336                 const char *subtype,
337                 const char *message,
338                 const char *presence_provider)
339 {
340         if (state == AST_PRESENCE_NOT_SET) {
341                 do_presence_state_change(presence_provider);
342         } else {
343                 presence_state_event(presence_provider, state, subtype, message);
344         }
345
346         return 0;
347 }
348
349 int ast_presence_state_changed(enum ast_presence_state state,
350                 const char *subtype,
351                 const char *message,
352                 const char *fmt, ...)
353 {
354         char buf[AST_MAX_EXTENSION];
355         va_list ap;
356
357         va_start(ap, fmt);
358         vsnprintf(buf, sizeof(buf), fmt, ap);
359         va_end(ap);
360
361         return ast_presence_state_changed_literal(state, subtype, message, buf);
362 }
363
364 struct stasis_topic *ast_presence_state_topic_all(void)
365 {
366         return presence_state_topic_all;
367 }
368
369 struct stasis_cache *ast_presence_state_cache(void)
370 {
371         return presence_state_cache;
372 }
373
374 struct stasis_topic *ast_presence_state_topic_cached(void)
375 {
376         return stasis_caching_get_topic(presence_state_topic_cached);
377 }
378
379 static const char *presence_state_get_id(struct stasis_message *msg)
380 {
381         struct ast_presence_state_message *presence_state = stasis_message_data(msg);
382
383         if (stasis_message_type(msg) != ast_presence_state_message_type()) {
384                 return NULL;
385         }
386
387         return presence_state->provider;
388 }
389
390 #if defined(TEST_FRAMEWORK)
391
392 #define TEST_CATEGORY "/main/presence/"
393
394 static int presence_test_alice_state = AST_PRESENCE_UNAVAILABLE;
395 static int presence_test_bob_state = AST_PRESENCE_UNAVAILABLE;
396
397 static int presence_test_presencestate(const char *label, char **subtype, char **message)
398 {
399         if (!strcmp(label, "Alice")) {
400                 return presence_test_alice_state;
401         } else if (!strcmp(label, "Bob")) {
402                 return presence_test_bob_state;
403         } else {
404                 return AST_PRESENCE_UNAVAILABLE;
405         }
406 }
407
408 static struct ast_channel_tech presence_test_tech = {
409         .type = "PresenceTestChannel",
410         .description = "Presence test technology",
411         .presencestate = presence_test_presencestate,
412 };
413
414 AST_TEST_DEFINE(test_presence_chan)
415 {
416         int res = AST_TEST_FAIL;
417         char provider[80];
418         enum ast_presence_state state;
419         char *subtype = NULL, *message = NULL;
420
421         switch (cmd) {
422         case TEST_INIT:
423                 info->name = "channel_presence";
424                 info->category = TEST_CATEGORY;
425                 info->summary = "Channel presence state tests";
426                 info->description = "Creates test channel technology and then test the presence state callback";
427                 return AST_TEST_NOT_RUN;
428         case TEST_EXECUTE:
429                 break;
430         }
431
432         if (ast_channel_register(&presence_test_tech)) {
433                 ast_log(LOG_WARNING, "Unable to register channel type '%s'\n", presence_test_tech.type);
434                 goto presence_test_cleanup;
435         }
436
437         /* Check Alice's state */
438         snprintf(provider, sizeof(provider), "%s/Alice", presence_test_tech.type);
439
440         presence_test_alice_state = AST_PRESENCE_AVAILABLE;
441         state = ast_presence_state_nocache(provider, &subtype, &message);
442
443         if (state != presence_test_alice_state) {
444                 ast_log(LOG_WARNING, "Presence state of '%s' returned '%s' instead of the expected value '%s'\n",
445                         provider, ast_presence_state2str(state), ast_presence_state2str(presence_test_alice_state));
446                 goto presence_test_cleanup;
447         }
448
449         /* Check Alice's and Bob's state, Alice's should win as DND > AVAILABLE */
450         snprintf(provider, sizeof(provider), "%s/Alice&%s/Bob", presence_test_tech.type, presence_test_tech.type);
451
452         presence_test_alice_state = AST_PRESENCE_DND;
453         presence_test_bob_state = AST_PRESENCE_UNAVAILABLE;
454         state = ast_presence_state_nocache(provider, &subtype, &message);
455
456         if (state != presence_test_alice_state) {
457                 ast_log(LOG_WARNING, "Presence state of '%s' returned '%s' instead of the expected value '%s'\n",
458                         provider, ast_presence_state2str(state), ast_presence_state2str(presence_test_alice_state));
459                 goto presence_test_cleanup;
460         }
461
462         /* Check Alice's and Bob's state, Bob's should now win as AVAILABLE < UNAVAILABLE */
463         presence_test_alice_state = AST_PRESENCE_AVAILABLE;
464         state = ast_presence_state_nocache(provider, &subtype, &message);
465
466         if (state != presence_test_bob_state) {
467                 ast_log(LOG_WARNING, "Presence state of '%s' returned '%s' instead of the expected value '%s'\n",
468                         provider, ast_presence_state2str(state), ast_presence_state2str(presence_test_bob_state));
469                 goto presence_test_cleanup;
470         }
471
472         res = AST_TEST_PASS;
473
474 presence_test_cleanup:
475         ast_channel_unregister(&presence_test_tech);
476         ast_free(subtype);
477         ast_free(message);
478
479         return res;
480 }
481 #endif
482
483 static void presence_state_engine_cleanup(void)
484 {
485         ao2_cleanup(presence_state_topic_all);
486         presence_state_topic_all = NULL;
487         ao2_cleanup(presence_state_cache);
488         presence_state_cache = NULL;
489         presence_state_topic_cached = stasis_caching_unsubscribe_and_join(presence_state_topic_cached);
490         STASIS_MESSAGE_TYPE_CLEANUP(ast_presence_state_message_type);
491         AST_TEST_UNREGISTER(test_presence_chan);
492 }
493
494 int ast_presence_state_engine_init(void)
495 {
496         ast_register_cleanup(presence_state_engine_cleanup);
497
498         if (STASIS_MESSAGE_TYPE_INIT(ast_presence_state_message_type) != 0) {
499                 return -1;
500         }
501
502         presence_state_topic_all = stasis_topic_create("ast_presence_state_topic_all");
503         if (!presence_state_topic_all) {
504                 return -1;
505         }
506
507         presence_state_cache = stasis_cache_create(presence_state_get_id);
508         if (!presence_state_cache) {
509                 return -1;
510         }
511
512         presence_state_topic_cached = stasis_caching_topic_create(presence_state_topic_all, presence_state_cache);
513         if (!presence_state_topic_cached) {
514                 return -1;
515         }
516
517         AST_TEST_REGISTER(test_presence_chan);
518
519         return 0;
520 }
521
522 static struct ast_manager_event_blob *presence_state_to_ami(struct stasis_message *msg)
523 {
524         struct ast_presence_state_message *presence_state = stasis_message_data(msg);
525         struct ast_manager_event_blob *res;
526
527         char *subtype = ast_escape_c_alloc(presence_state->subtype);
528         char *message = ast_escape_c_alloc(presence_state->message);
529
530         res = ast_manager_event_blob_create(EVENT_FLAG_CALL, "PresenceStateChange",
531                 "Presentity: %s\r\n"
532                 "Status: %s\r\n"
533                 "Subtype: %s\r\n"
534                 "Message: %s\r\n",
535                 presence_state->provider,
536                 ast_presence_state2str(presence_state->state),
537                 subtype ?: "",
538                 message ?: "");
539
540         ast_free(subtype);
541         ast_free(message);
542
543         return res;
544 }