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