core: Ensure presencestate subtype and message are NULL.
[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         *subtype = NULL;
165         *message = NULL;
166
167         while ((label = strsep(&labels, "&"))) {
168                 enum ast_presence_state next_state = AST_PRESENCE_INVALID;
169                 char *next_subtype = NULL;
170                 char *next_message = NULL;
171
172                 if (check_cache) {
173                         next_state = presence_state_cached(label, &next_subtype, &next_message);
174                 }
175
176                 if (next_state == AST_PRESENCE_INVALID) {
177                         struct presence_state_provider *provider;
178                         const struct ast_channel_tech *chan_tech;
179                         char *address;
180
181                         if ((address = strchr(label, '/'))) {
182                                 *address++ = '\0';
183
184                                 if ((chan_tech = ast_get_channel_tech(label)) && chan_tech->presencestate) {
185                                         next_state = chan_tech->presencestate(address, &next_subtype, &next_message);
186                                 }
187                         } else if ((address = strchr(label, ':'))) {
188                                 *address++ = '\0';
189
190                                 AST_RWLIST_RDLOCK(&presence_state_providers);
191                                 AST_RWLIST_TRAVERSE(&presence_state_providers, provider, list) {
192                                         ast_debug(5, "Checking provider %s with %s\n", provider->label, label);
193
194                                         if (!strcasecmp(provider->label, label)) {
195                                                 next_state = provider->callback(address, &next_subtype, &next_message);
196                                                 break;
197                                         }
198                                 }
199                                 AST_RWLIST_UNLOCK(&presence_state_providers);
200
201                                 if (!provider) {
202                                         ast_log(LOG_WARNING, "No provider found for label: %s\n", label);
203                                 }
204                         } else {
205                                 ast_log(LOG_WARNING, "No label found for presence state provider: %s\n", label);
206                         }
207                 }
208
209                 if (state_order[next_state] > state_order[state]) {
210                         state = next_state;
211
212                         ast_free(*subtype);
213                         ast_free(*message);
214
215                         *subtype = next_subtype;
216                         *message = next_message;
217                 }
218         }
219
220         return state;
221 }
222
223 enum ast_presence_state ast_presence_state(const char *presence_provider, char **subtype, char **message)
224 {
225         return ast_presence_state_helper(presence_provider, subtype, message, 1);
226 }
227
228 enum ast_presence_state ast_presence_state_nocache(const char *presence_provider, char **subtype, char **message)
229 {
230         return ast_presence_state_helper(presence_provider, subtype, message, 0);
231 }
232
233 int ast_presence_state_prov_add(const char *label, ast_presence_state_prov_cb_type callback)
234 {
235         struct presence_state_provider *provider;
236
237         if (!callback || !(provider = ast_calloc(1, sizeof(*provider)))) {
238                 return -1;
239         }
240
241         provider->callback = callback;
242         ast_copy_string(provider->label, label, sizeof(provider->label));
243
244         AST_RWLIST_WRLOCK(&presence_state_providers);
245         AST_RWLIST_INSERT_HEAD(&presence_state_providers, provider, list);
246         AST_RWLIST_UNLOCK(&presence_state_providers);
247
248         return 0;
249 }
250 int ast_presence_state_prov_del(const char *label)
251 {
252         struct presence_state_provider *provider;
253         int res = -1;
254
255         AST_RWLIST_WRLOCK(&presence_state_providers);
256         AST_RWLIST_TRAVERSE_SAFE_BEGIN(&presence_state_providers, provider, list) {
257                 if (!strcasecmp(provider->label, label)) {
258                         AST_RWLIST_REMOVE_CURRENT(list);
259                         ast_free(provider);
260                         res = 0;
261                         break;
262                 }
263         }
264         AST_RWLIST_TRAVERSE_SAFE_END;
265         AST_RWLIST_UNLOCK(&presence_state_providers);
266
267         return res;
268 }
269
270 static void presence_state_dtor(void *obj)
271 {
272         struct ast_presence_state_message *presence_state = obj;
273         ast_string_field_free_memory(presence_state);
274 }
275
276 static struct ast_presence_state_message *presence_state_alloc(const char *provider,
277                 enum ast_presence_state state,
278                 const char *subtype,
279                 const char *message)
280 {
281         RAII_VAR(struct ast_presence_state_message *, presence_state, ao2_alloc(sizeof(*presence_state), presence_state_dtor), ao2_cleanup);
282
283         if (!presence_state || ast_string_field_init(presence_state, 256)) {
284                 return NULL;
285         }
286
287         presence_state->state = state;
288         ast_string_field_set(presence_state, provider, provider);
289         ast_string_field_set(presence_state, subtype, S_OR(subtype, ""));
290         ast_string_field_set(presence_state, message, S_OR(message, ""));
291
292         ao2_ref(presence_state, +1);
293         return presence_state;
294 }
295
296 static void presence_state_event(const char *provider,
297                 enum ast_presence_state state,
298                 const char *subtype,
299                 const char *message)
300 {
301         RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
302         RAII_VAR(struct ast_presence_state_message *, presence_state, NULL, ao2_cleanup);
303
304         if (!ast_presence_state_message_type()) {
305                 return;
306         }
307
308         presence_state = presence_state_alloc(provider, state, subtype, message);
309         if (!presence_state) {
310                 return;
311         }
312
313         msg = stasis_message_create(ast_presence_state_message_type(), presence_state);
314         if (!msg) {
315                 return;
316         }
317
318         stasis_publish(ast_presence_state_topic_all(), msg);
319 }
320
321 static void do_presence_state_change(const char *provider)
322 {
323         char *subtype = NULL;
324         char *message = NULL;
325         enum ast_presence_state state;
326
327         state = ast_presence_state_helper(provider, &subtype, &message, 0);
328
329         if (state == AST_PRESENCE_INVALID) {
330                 return;
331         }
332
333         presence_state_event(provider, state, subtype, message);
334         ast_free(subtype);
335         ast_free(message);
336 }
337
338 int ast_presence_state_changed_literal(enum ast_presence_state state,
339                 const char *subtype,
340                 const char *message,
341                 const char *presence_provider)
342 {
343         if (state == AST_PRESENCE_NOT_SET) {
344                 do_presence_state_change(presence_provider);
345         } else {
346                 presence_state_event(presence_provider, state, subtype, message);
347         }
348
349         return 0;
350 }
351
352 int ast_presence_state_changed(enum ast_presence_state state,
353                 const char *subtype,
354                 const char *message,
355                 const char *fmt, ...)
356 {
357         char buf[AST_MAX_EXTENSION];
358         va_list ap;
359
360         va_start(ap, fmt);
361         vsnprintf(buf, sizeof(buf), fmt, ap);
362         va_end(ap);
363
364         return ast_presence_state_changed_literal(state, subtype, message, buf);
365 }
366
367 struct stasis_topic *ast_presence_state_topic_all(void)
368 {
369         return presence_state_topic_all;
370 }
371
372 struct stasis_cache *ast_presence_state_cache(void)
373 {
374         return presence_state_cache;
375 }
376
377 struct stasis_topic *ast_presence_state_topic_cached(void)
378 {
379         return stasis_caching_get_topic(presence_state_topic_cached);
380 }
381
382 static const char *presence_state_get_id(struct stasis_message *msg)
383 {
384         struct ast_presence_state_message *presence_state = stasis_message_data(msg);
385
386         if (stasis_message_type(msg) != ast_presence_state_message_type()) {
387                 return NULL;
388         }
389
390         return presence_state->provider;
391 }
392
393 #if defined(TEST_FRAMEWORK)
394
395 #define TEST_CATEGORY "/main/presence/"
396
397 static int presence_test_alice_state = AST_PRESENCE_UNAVAILABLE;
398 static int presence_test_bob_state = AST_PRESENCE_UNAVAILABLE;
399
400 static int presence_test_presencestate(const char *label, char **subtype, char **message)
401 {
402         if (!strcmp(label, "Alice")) {
403                 return presence_test_alice_state;
404         } else if (!strcmp(label, "Bob")) {
405                 return presence_test_bob_state;
406         } else {
407                 return AST_PRESENCE_UNAVAILABLE;
408         }
409 }
410
411 static struct ast_channel_tech presence_test_tech = {
412         .type = "PresenceTestChannel",
413         .description = "Presence test technology",
414         .presencestate = presence_test_presencestate,
415 };
416
417 AST_TEST_DEFINE(test_presence_chan)
418 {
419         int res = AST_TEST_FAIL;
420         char provider[80];
421         enum ast_presence_state state;
422         char *subtype = NULL, *message = NULL;
423
424         switch (cmd) {
425         case TEST_INIT:
426                 info->name = "channel_presence";
427                 info->category = TEST_CATEGORY;
428                 info->summary = "Channel presence state tests";
429                 info->description = "Creates test channel technology and then test the presence state callback";
430                 return AST_TEST_NOT_RUN;
431         case TEST_EXECUTE:
432                 break;
433         }
434
435         if (ast_channel_register(&presence_test_tech)) {
436                 ast_log(LOG_WARNING, "Unable to register channel type '%s'\n", presence_test_tech.type);
437                 goto presence_test_cleanup;
438         }
439
440         /* Check Alice's state */
441         snprintf(provider, sizeof(provider), "%s/Alice", presence_test_tech.type);
442
443         presence_test_alice_state = AST_PRESENCE_AVAILABLE;
444         state = ast_presence_state_nocache(provider, &subtype, &message);
445
446         if (state != presence_test_alice_state) {
447                 ast_log(LOG_WARNING, "Presence state of '%s' returned '%s' instead of the expected value '%s'\n",
448                         provider, ast_presence_state2str(state), ast_presence_state2str(presence_test_alice_state));
449                 goto presence_test_cleanup;
450         }
451
452         /* Check Alice's and Bob's state, Alice's should win as DND > AVAILABLE */
453         snprintf(provider, sizeof(provider), "%s/Alice&%s/Bob", presence_test_tech.type, presence_test_tech.type);
454
455         presence_test_alice_state = AST_PRESENCE_DND;
456         presence_test_bob_state = AST_PRESENCE_UNAVAILABLE;
457         state = ast_presence_state_nocache(provider, &subtype, &message);
458
459         if (state != presence_test_alice_state) {
460                 ast_log(LOG_WARNING, "Presence state of '%s' returned '%s' instead of the expected value '%s'\n",
461                         provider, ast_presence_state2str(state), ast_presence_state2str(presence_test_alice_state));
462                 goto presence_test_cleanup;
463         }
464
465         /* Check Alice's and Bob's state, Bob's should now win as AVAILABLE < UNAVAILABLE */
466         presence_test_alice_state = AST_PRESENCE_AVAILABLE;
467         state = ast_presence_state_nocache(provider, &subtype, &message);
468
469         if (state != presence_test_bob_state) {
470                 ast_log(LOG_WARNING, "Presence state of '%s' returned '%s' instead of the expected value '%s'\n",
471                         provider, ast_presence_state2str(state), ast_presence_state2str(presence_test_bob_state));
472                 goto presence_test_cleanup;
473         }
474
475         res = AST_TEST_PASS;
476
477 presence_test_cleanup:
478         ast_channel_unregister(&presence_test_tech);
479         ast_free(subtype);
480         ast_free(message);
481
482         return res;
483 }
484 #endif
485
486 static void presence_state_engine_cleanup(void)
487 {
488         ao2_cleanup(presence_state_topic_all);
489         presence_state_topic_all = NULL;
490         ao2_cleanup(presence_state_cache);
491         presence_state_cache = NULL;
492         presence_state_topic_cached = stasis_caching_unsubscribe_and_join(presence_state_topic_cached);
493         STASIS_MESSAGE_TYPE_CLEANUP(ast_presence_state_message_type);
494         AST_TEST_UNREGISTER(test_presence_chan);
495 }
496
497 int ast_presence_state_engine_init(void)
498 {
499         ast_register_cleanup(presence_state_engine_cleanup);
500
501         if (STASIS_MESSAGE_TYPE_INIT(ast_presence_state_message_type) != 0) {
502                 return -1;
503         }
504
505         presence_state_topic_all = stasis_topic_create("ast_presence_state_topic_all");
506         if (!presence_state_topic_all) {
507                 return -1;
508         }
509
510         presence_state_cache = stasis_cache_create(presence_state_get_id);
511         if (!presence_state_cache) {
512                 return -1;
513         }
514
515         presence_state_topic_cached = stasis_caching_topic_create(presence_state_topic_all, presence_state_cache);
516         if (!presence_state_topic_cached) {
517                 return -1;
518         }
519
520         AST_TEST_REGISTER(test_presence_chan);
521
522         return 0;
523 }
524
525 static struct ast_manager_event_blob *presence_state_to_ami(struct stasis_message *msg)
526 {
527         struct ast_presence_state_message *presence_state = stasis_message_data(msg);
528         struct ast_manager_event_blob *res;
529
530         char *subtype = ast_escape_c_alloc(presence_state->subtype);
531         char *message = ast_escape_c_alloc(presence_state->message);
532
533         res = ast_manager_event_blob_create(EVENT_FLAG_CALL, "PresenceStateChange",
534                 "Presentity: %s\r\n"
535                 "Status: %s\r\n"
536                 "Subtype: %s\r\n"
537                 "Message: %s\r\n",
538                 presence_state->provider,
539                 ast_presence_state2str(presence_state->state),
540                 subtype ?: "",
541                 message ?: "");
542
543         ast_free(subtype);
544         ast_free(message);
545
546         return res;
547 }