astobj2: Add backtrace to log_bad_ao2.
[asterisk/asterisk.git] / funcs / func_presencestate.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2011, 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 Custom presence provider
22  * \ingroup functions
23  */
24
25 /*** MODULEINFO
26         <support_level>core</support_level>
27  ***/
28
29 #include "asterisk.h"
30
31 ASTERISK_REGISTER_FILE()
32
33 #include "asterisk/module.h"
34 #include "asterisk/channel.h"
35 #include "asterisk/pbx.h"
36 #include "asterisk/utils.h"
37 #include "asterisk/linkedlists.h"
38 #include "asterisk/presencestate.h"
39 #include "asterisk/cli.h"
40 #include "asterisk/astdb.h"
41 #include "asterisk/app.h"
42 #ifdef TEST_FRAMEWORK
43 #include "asterisk/test.h"
44 #include "asterisk/sem.h"
45 #endif
46
47 /*** DOCUMENTATION
48         <function name="PRESENCE_STATE" language="en_US">
49                 <synopsis>
50                         Get or Set a presence state.
51                 </synopsis>
52                 <syntax>
53                         <parameter name="provider" required="true">
54                           <para>The provider of the presence, such as <literal>CustomPresence</literal></para>
55                         </parameter>
56                         <parameter name="field" required="true">
57                           <para>Which field of the presence state information is wanted.</para>
58                           <optionlist>
59                                 <option name="value">
60                                   <para>The current presence, such as <literal>away</literal></para>
61                                 </option>
62                                 <option name="subtype">
63                                   <para>Further information about the current presence</para>
64                                 </option>
65                             <option name="message">
66                                   <para>A custom message that may indicate further details about the presence</para>
67                                 </option>
68                           </optionlist>
69                         </parameter>
70                         <parameter name="options" required="false">
71                           <optionlist>
72                             <option name="e">
73                                   <para>On Write - Use this option when the subtype and message provided are Base64
74                                         encoded. The values will be stored encoded within Asterisk, but all consumers of
75                                         the presence state (e.g. the SIP presence event package) will receive decoded values.</para>
76                                         <para>On Read - Retrieves unencoded message/subtype in Base64 encoded form.</para>
77                                 </option>
78                           </optionlist>
79                         </parameter>
80                 </syntax>
81                 <description>
82                         <para>The PRESENCE_STATE function can be used to retrieve the presence from any
83                         presence provider. For example:</para>
84                         <para>NoOp(SIP/mypeer has presence ${PRESENCE_STATE(SIP/mypeer,value)})</para>
85                         <para>NoOp(Conference number 1234 has presence message ${PRESENCE_STATE(MeetMe:1234,message)})</para>
86                         <para>The PRESENCE_STATE function can also be used to set custom presence state from
87                         the dialplan.  The <literal>CustomPresence:</literal> prefix must be used. For example:</para>
88                         <para>Set(PRESENCE_STATE(CustomPresence:lamp1)=away,temporary,Out to lunch)</para>
89                         <para>Set(PRESENCE_STATE(CustomPresence:lamp2)=dnd,,Trying to get work done)</para>
90                         <para>Set(PRESENCE_STATE(CustomPresence:lamp3)=xa,T24gdmFjYXRpb24=,,e)</para>
91                         <para>Set(BASE64_LAMP3_PRESENCE=${PRESENCE_STATE(CustomPresence:lamp3,subtype,e)})</para>
92                         <para>You can subscribe to the status of a custom presence state using a hint in
93                         the dialplan:</para>
94                         <para>exten => 1234,hint,,CustomPresence:lamp1</para>
95                         <para>The possible values for both uses of this function are:</para>
96                         <para>not_set | unavailable | available | away | xa | chat | dnd</para>
97                 </description>
98         </function>
99  ***/
100
101
102 static const char astdb_family[] = "CustomPresence";
103
104 static int presence_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
105 {
106         int state;
107         char *message = NULL;
108         char *subtype = NULL;
109         char *parse;
110         int base64encode = 0;
111         AST_DECLARE_APP_ARGS(args,
112                 AST_APP_ARG(provider);
113                 AST_APP_ARG(field);
114                 AST_APP_ARG(options);
115         );
116
117         if (ast_strlen_zero(data)) {
118                 ast_log(LOG_WARNING, "PRESENCE_STATE reading requires an argument \n");
119                 return -1;
120         }
121
122         parse = ast_strdupa(data);
123
124         AST_STANDARD_APP_ARGS(args, parse);
125
126         if (ast_strlen_zero(args.provider) || ast_strlen_zero(args.field)) {
127                 ast_log(LOG_WARNING, "PRESENCE_STATE reading requires both presence provider and presence field arguments. \n");
128                 return -1;
129         }
130
131         state = ast_presence_state_nocache(args.provider, &subtype, &message);
132         if (state == AST_PRESENCE_INVALID) {
133                 ast_log(LOG_WARNING, "PRESENCE_STATE unknown \n");
134                 return -1;
135         }
136
137         if (!(ast_strlen_zero(args.options)) && (strchr(args.options, 'e'))) {
138                 base64encode = 1;
139         }
140
141         if (!ast_strlen_zero(subtype) && !strcasecmp(args.field, "subtype")) {
142                 if (base64encode) {
143                         ast_base64encode(buf, (unsigned char *) subtype, strlen(subtype), len);
144                 } else {
145                         ast_copy_string(buf, subtype, len);
146                 }
147         } else if (!ast_strlen_zero(message) && !strcasecmp(args.field, "message")) {
148                 if (base64encode) {
149                         ast_base64encode(buf, (unsigned char *) message, strlen(message), len);
150                 } else {
151                         ast_copy_string(buf, message, len);
152                 }
153
154         } else if (!strcasecmp(args.field, "value")) {
155                 ast_copy_string(buf, ast_presence_state2str(state), len);
156         }
157
158         ast_free(message);
159         ast_free(subtype);
160
161         return 0;
162 }
163
164 static int parse_data(char *data, enum ast_presence_state *state, char **subtype, char **message, char **options)
165 {
166         char *state_str;
167
168         /* data syntax is state,subtype,message,options */
169         *subtype = "";
170         *message = "";
171         *options = "";
172
173         state_str = strsep(&data, ",");
174         if (ast_strlen_zero(state_str)) {
175                 return -1; /* state is required */
176         }
177
178         *state = ast_presence_state_val(state_str);
179
180         /* not a valid state */
181         if (*state == AST_PRESENCE_INVALID) {
182                 ast_log(LOG_WARNING, "Unknown presence state value %s\n", state_str);
183                 return -1;
184         }
185
186         if (!(*subtype = strsep(&data,","))) {
187                 *subtype = "";
188                 return 0;
189         }
190
191         if (!(*message = strsep(&data, ","))) {
192                 *message = "";
193                 return 0;
194         }
195
196         if (!(*options = strsep(&data, ","))) {
197                 *options = "";
198                 return 0;
199         }
200
201         if (!ast_strlen_zero(*options) && !(strchr(*options, 'e'))) {
202                 ast_log(LOG_NOTICE, "Invalid options  '%s'\n", *options);
203                 return -1;
204         }
205
206         return 0;
207 }
208
209 static int presence_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
210 {
211         size_t len = strlen("CustomPresence:");
212         char *tmp = data;
213         char *args = ast_strdupa(value);
214         enum ast_presence_state state;
215         char *options, *message, *subtype;
216
217         if (strncasecmp(data, "CustomPresence:", len)) {
218                 ast_log(LOG_WARNING, "The PRESENCE_STATE function can only set CustomPresence: presence providers.\n");
219                 return -1;
220         }
221         data += len;
222         if (ast_strlen_zero(data)) {
223                 ast_log(LOG_WARNING, "PRESENCE_STATE function called with no custom device name!\n");
224                 return -1;
225         }
226
227         if (parse_data(args, &state, &subtype, &message, &options)) {
228                 ast_log(LOG_WARNING, "Invalid arguments to PRESENCE_STATE\n");
229                 return -1;
230         }
231
232         ast_db_put(astdb_family, data, value);
233
234         if (strchr(options, 'e')) {
235                 /* Let's decode the values before sending them to stasis, yes? */
236                 char decoded_subtype[256] = { 0, };
237                 char decoded_message[256] = { 0, };
238
239                 ast_base64decode((unsigned char *) decoded_subtype, subtype, sizeof(decoded_subtype) -1);
240                 ast_base64decode((unsigned char *) decoded_message, message, sizeof(decoded_message) -1);
241
242                 ast_presence_state_changed_literal(state, decoded_subtype, decoded_message, tmp);
243         } else {
244                 ast_presence_state_changed_literal(state, subtype, message, tmp);
245         }
246
247         return 0;
248 }
249
250 static enum ast_presence_state custom_presence_callback(const char *data, char **subtype, char **message)
251 {
252         char buf[1301] = "";
253         enum ast_presence_state state;
254         char *_options;
255         char *_message;
256         char *_subtype;
257
258         if (ast_db_get(astdb_family, data, buf, sizeof(buf))) {
259                 return AST_PRESENCE_NOT_SET;
260         }
261
262         if (parse_data(buf, &state, &_subtype, &_message, &_options)) {
263                 return AST_PRESENCE_INVALID;
264         }
265
266         if ((strchr(_options, 'e'))) {
267                 char tmp[1301];
268
269                 if (ast_strlen_zero(_subtype)) {
270                         *subtype = NULL;
271                 } else {
272                         memset(tmp, 0, sizeof(tmp));
273                         ast_base64decode((unsigned char *) tmp, _subtype, sizeof(tmp) - 1);
274                         *subtype = ast_strdup(tmp);
275                 }
276
277                 if (ast_strlen_zero(_message)) {
278                         *message = NULL;
279                 } else {
280                         memset(tmp, 0, sizeof(tmp));
281                         ast_base64decode((unsigned char *) tmp, _message, sizeof(tmp) - 1);
282                         *message = ast_strdup(tmp);
283                 }
284         } else {
285                 *subtype = ast_strlen_zero(_subtype) ? NULL : ast_strdup(_subtype);
286                 *message = ast_strlen_zero(_message) ? NULL : ast_strdup(_message);
287         }
288         return state;
289 }
290
291 static struct ast_custom_function presence_function = {
292         .name = "PRESENCE_STATE",
293         .read = presence_read,
294         .write = presence_write,
295 };
296
297 static char *handle_cli_presencestate_list(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
298 {
299         struct ast_db_entry *db_entry, *db_tree;
300
301         switch (cmd) {
302         case CLI_INIT:
303                 e->command = "presencestate list";
304                 e->usage =
305                         "Usage: presencestate list\n"
306                         "       List all custom presence states that have been set by using\n"
307                         "       the PRESENCE_STATE dialplan function.\n";
308                 return NULL;
309         case CLI_GENERATE:
310                 return NULL;
311         }
312
313         if (a->argc != e->args) {
314                 return CLI_SHOWUSAGE;
315         }
316
317         ast_cli(a->fd, "\n"
318                 "---------------------------------------------------------------------\n"
319                 "--- Custom Presence States ------------------------------------------\n"
320                 "---------------------------------------------------------------------\n"
321                 "---\n");
322
323         db_entry = db_tree = ast_db_gettree(astdb_family, NULL);
324         if (!db_entry) {
325                 ast_cli(a->fd, "No custom presence states defined\n");
326                 return CLI_SUCCESS;
327         }
328         for (; db_entry; db_entry = db_entry->next) {
329                 const char *object_name = strrchr(db_entry->key, '/') + 1;
330                 char state_info[1301];
331                 enum ast_presence_state state;
332                 char *subtype;
333                 char *message;
334                 char *options;
335
336                 ast_copy_string(state_info, db_entry->data, sizeof(state_info));
337                 if (parse_data(state_info, &state, &subtype, &message, &options)) {
338                         ast_log(LOG_WARNING, "Invalid CustomPresence entry %s encountered\n", db_entry->data);
339                         continue;
340                 }
341
342                 if (object_name <= (const char *) 1) {
343                         continue;
344                 }
345                 ast_cli(a->fd, "--- Name: 'CustomPresence:%s'\n"
346                                        "    --- State: '%s'\n"
347                                            "    --- Subtype: '%s'\n"
348                                            "    --- Message: '%s'\n"
349                                            "    --- Base64 Encoded: '%s'\n"
350                                "---\n",
351                                            object_name,
352                                            ast_presence_state2str(state),
353                                            subtype,
354                                            message,
355                                            AST_CLI_YESNO(strchr(options, 'e')));
356         }
357         ast_db_freetree(db_tree);
358         db_tree = NULL;
359
360         ast_cli(a->fd,
361                 "---------------------------------------------------------------------\n"
362                 "---------------------------------------------------------------------\n"
363                 "\n");
364
365         return CLI_SUCCESS;
366 }
367
368 static char *handle_cli_presencestate_change(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
369 {
370     size_t len;
371         const char *dev, *state, *full_dev;
372         enum ast_presence_state state_val;
373         char *message;
374         char *subtype;
375         char *options;
376         char *args;
377
378         switch (cmd) {
379         case CLI_INIT:
380                 e->command = "presencestate change";
381                 e->usage =
382                         "Usage: presencestate change <entity> <state>[,<subtype>[,message[,options]]]\n"
383                         "       Change a custom presence to a new state.\n"
384                         "       The possible values for the state are:\n"
385                         "NOT_SET | UNAVAILABLE | AVAILABLE | AWAY | XA | CHAT | DND\n"
386                         "Optionally, a custom subtype and message may be provided, along with any options\n"
387                         "accepted by func_presencestate. If the subtype or message provided contain spaces,\n"
388                         "be sure to enclose the data in quotation marks (\"\")\n"
389                         "\n"
390                         "Examples:\n"
391                         "       presencestate change CustomPresence:mystate1 AWAY\n"
392                         "       presencestate change CustomPresence:mystate1 AVAILABLE\n"
393                         "       presencestate change CustomPresence:mystate1 \"Away,upstairs,eating lunch\"\n"
394                         "       \n";
395                 return NULL;
396         case CLI_GENERATE:
397         {
398                 static const char * const cmds[] = { "NOT_SET", "UNAVAILABLE", "AVAILABLE", "AWAY",
399                                                      "XA", "CHAT", "DND", NULL };
400
401                 if (a->pos == e->args + 1) {
402                         return ast_cli_complete(a->word, cmds, a->n);
403                 }
404
405                 return NULL;
406         }
407         }
408
409         if (a->argc != e->args + 2) {
410                 return CLI_SHOWUSAGE;
411         }
412
413         len = strlen("CustomPresence:");
414         full_dev = dev = a->argv[e->args];
415         state = a->argv[e->args + 1];
416
417         if (strncasecmp(dev, "CustomPresence:", len)) {
418                 ast_cli(a->fd, "The presencestate command can only be used to set 'CustomPresence:' presence state!\n");
419                 return CLI_FAILURE;
420         }
421
422         dev += len;
423         if (ast_strlen_zero(dev)) {
424                 return CLI_SHOWUSAGE;
425         }
426
427         args = ast_strdupa(state);
428         if (parse_data(args, &state_val, &subtype, &message, &options)) {
429                 return CLI_SHOWUSAGE;
430         }
431
432         if (state_val == AST_PRESENCE_NOT_SET) {
433                 return CLI_SHOWUSAGE;
434         }
435
436         ast_cli(a->fd, "Changing %s to %s\n", dev, args);
437
438         ast_db_put(astdb_family, dev, state);
439
440         ast_presence_state_changed_literal(state_val, subtype, message, full_dev);
441
442         return CLI_SUCCESS;
443 }
444
445 static struct ast_cli_entry cli_funcpresencestate[] = {
446         AST_CLI_DEFINE(handle_cli_presencestate_list, "List currently know custom presence states"),
447         AST_CLI_DEFINE(handle_cli_presencestate_change, "Change a custom presence state"),
448 };
449
450 #ifdef TEST_FRAMEWORK
451
452 struct test_string {
453         char *parse_string;
454         struct {
455                 int value;
456                 const char *subtype;
457                 const char *message;
458                 const char *options; 
459         } outputs;
460 };
461
462 AST_TEST_DEFINE(test_valid_parse_data)
463 {
464         int i;
465         enum ast_presence_state state;
466         char *subtype;
467         char *message;
468         char *options;
469         enum ast_test_result_state res = AST_TEST_PASS;
470         
471         struct test_string tests [] = {
472                 { "away",
473                         { AST_PRESENCE_AWAY,
474                                 "",
475                                 "",
476                                 ""
477                         }
478                 },
479                 { "not_set",
480                         { AST_PRESENCE_NOT_SET,
481                                 "",
482                                 "",
483                                 ""
484                         }
485                 },
486                 { "unavailable",
487                         { AST_PRESENCE_UNAVAILABLE,
488                                 "",
489                                 "",
490                                 ""
491                         }
492                 },
493                 { "available",
494                         { AST_PRESENCE_AVAILABLE,
495                                 "",
496                                 "",
497                                 ""
498                         }
499                 },
500                 { "xa",
501                         { AST_PRESENCE_XA,
502                                 "",
503                                 "",
504                                 ""
505                         }
506                 },
507                 { "chat",
508                         { AST_PRESENCE_CHAT,
509                                 "",
510                                 "",
511                                 ""
512                         }
513                 },
514                 { "dnd",
515                         { AST_PRESENCE_DND,
516                                 "",
517                                 "",
518                                 ""
519                         }
520                 },
521                 { "away,down the hall",
522                         { AST_PRESENCE_AWAY,
523                                 "down the hall",
524                                 "",
525                                 ""
526                         }
527                 },
528                 { "away,down the hall,Quarterly financial meeting",
529                         { AST_PRESENCE_AWAY,
530                                 "down the hall",
531                                 "Quarterly financial meeting",
532                                 ""
533                         }
534                 },
535                 { "away,,Quarterly financial meeting",
536                         { AST_PRESENCE_AWAY,
537                                 "",
538                                 "Quarterly financial meeting",
539                                 ""
540                         }
541                 },
542                 { "away,,,e",
543                         { AST_PRESENCE_AWAY,
544                                 "",
545                                 "",
546                                 "e",
547                         }
548                 },
549                 { "away,down the hall,,e",
550                         { AST_PRESENCE_AWAY,
551                                 "down the hall",
552                                 "",
553                                 "e"
554                         }
555                 },
556                 { "away,down the hall,Quarterly financial meeting,e",
557                         { AST_PRESENCE_AWAY,
558                                 "down the hall",
559                                 "Quarterly financial meeting",
560                                 "e"
561                         }
562                 },
563                 { "away,,Quarterly financial meeting,e",
564                         { AST_PRESENCE_AWAY,
565                                 "",
566                                 "Quarterly financial meeting",
567                                 "e"
568                         }
569                 }
570         };
571
572         switch (cmd) {
573         case TEST_INIT:
574                 info->name = "parse_valid_presence_data";
575                 info->category = "/funcs/func_presence/";
576                 info->summary = "PRESENCESTATE parsing test";
577                 info->description =
578                         "Ensure that parsing function accepts proper values, and gives proper outputs";
579                 return AST_TEST_NOT_RUN;
580         case TEST_EXECUTE:
581                 break;
582         }
583
584         for (i = 0; i < ARRAY_LEN(tests); ++i) {
585                 int parse_result;
586                 char *parse_string = ast_strdup(tests[i].parse_string);
587                 if (!parse_string) {
588                         res = AST_TEST_FAIL;
589                         break;
590                 }
591                 parse_result = parse_data(parse_string, &state, &subtype, &message, &options);
592                 if (parse_result == -1) {
593                         res = AST_TEST_FAIL;
594                         ast_free(parse_string);
595                         break;
596                 }
597                 if (tests[i].outputs.value != state ||
598                                 strcmp(tests[i].outputs.subtype, subtype) ||
599                                 strcmp(tests[i].outputs.message, message) ||
600                                 strcmp(tests[i].outputs.options, options)) {
601                         res = AST_TEST_FAIL;
602                         ast_free(parse_string);
603                         break;
604                 }
605                 ast_free(parse_string);
606         }
607
608         return res;
609 }
610
611 AST_TEST_DEFINE(test_invalid_parse_data)
612 {
613         int i;
614         enum ast_presence_state state;
615         char *subtype;
616         char *message;
617         char *options;
618         enum ast_test_result_state res = AST_TEST_PASS;
619
620         char *tests[] = {
621                 "",
622                 "bored",
623                 "away,,,i",
624                 /* XXX The following actually is parsed correctly. Should that
625                  * be changed?
626                  * "away,,,,e",
627                  */
628         };
629
630         switch (cmd) {
631         case TEST_INIT:
632                 info->name = "parse_invalid_presence_data";
633                 info->category = "/funcs/func_presence/";
634                 info->summary = "PRESENCESTATE parsing test";
635                 info->description =
636                         "Ensure that parsing function rejects improper values";
637                 return AST_TEST_NOT_RUN;
638         case TEST_EXECUTE:
639                 break;
640         }
641
642         for (i = 0; i < ARRAY_LEN(tests); ++i) {
643                 int parse_result;
644                 char *parse_string = ast_strdup(tests[i]);
645                 if (!parse_string) {
646                         res = AST_TEST_FAIL;
647                         break;
648                 }
649                 parse_result = parse_data(parse_string, &state, &subtype, &message, &options);
650                 if (parse_result == 0) {
651                         ast_log(LOG_WARNING, "Invalid string parsing failed on %s\n", tests[i]);
652                         res = AST_TEST_FAIL;
653                         ast_free(parse_string);
654                         break;
655                 }
656                 ast_free(parse_string);
657         }
658
659         return res;
660 }
661
662 #define PRES_STATE "away"
663 #define PRES_SUBTYPE "down the hall"
664 #define PRES_MESSAGE "Quarterly financial meeting"
665
666 struct test_cb_data {
667         struct ast_presence_state_message *presence_state;
668         /* That's right. I'm using a semaphore */
669         struct ast_sem sem;
670 };
671
672 static struct test_cb_data *test_cb_data_alloc(void)
673 {
674         struct test_cb_data *cb_data = ast_calloc(1, sizeof(*cb_data));
675
676         if (!cb_data) {
677                 return NULL;
678         }
679
680         if (ast_sem_init(&cb_data->sem, 0, 0)) {
681                 ast_free(cb_data);
682                 return NULL;
683         }
684
685         return cb_data;
686 }
687
688 static void test_cb_data_destroy(struct test_cb_data *cb_data)
689 {
690         ao2_cleanup(cb_data->presence_state);
691         ast_sem_destroy(&cb_data->sem);
692         ast_free(cb_data);
693 }
694
695 static void test_cb(void *userdata, struct stasis_subscription *sub, struct stasis_message *msg)
696 {
697         struct test_cb_data *cb_data = userdata;
698         if (stasis_message_type(msg) != ast_presence_state_message_type()) {
699                 return;
700         }
701         cb_data->presence_state = stasis_message_data(msg);
702         ao2_ref(cb_data->presence_state, +1);
703
704         ast_sem_post(&cb_data->sem);
705 }
706
707 static enum ast_test_result_state presence_change_common(struct ast_test *test,
708                 const char *state, const char *subtype, const char *message, const char *options,
709                 char *out_state, size_t out_state_size,
710                 char *out_subtype, size_t out_subtype_size,
711                 char *out_message, size_t out_message_size)
712 {
713         RAII_VAR(struct test_cb_data *, cb_data, test_cb_data_alloc(), test_cb_data_destroy);
714         struct stasis_subscription *test_sub;
715         char pres[1301];
716
717         if (!(test_sub = stasis_subscribe(ast_presence_state_topic_all(), test_cb, cb_data))) {
718                 return AST_TEST_FAIL;
719         }
720
721         if (ast_strlen_zero(options)) {
722                 snprintf(pres, sizeof(pres), "%s,%s,%s", state, subtype, message);
723         } else {
724                 snprintf(pres, sizeof(pres), "%s,%s,%s,%s", state, subtype, message, options);
725         }
726
727         if (presence_write(NULL, "PRESENCESTATE", "CustomPresence:TestPresenceStateChange", pres)) {
728                 test_sub = stasis_unsubscribe_and_join(test_sub);
729                 return AST_TEST_FAIL;
730         }
731
732         ast_sem_wait(&cb_data->sem);
733
734         ast_copy_string(out_state, ast_presence_state2str(cb_data->presence_state->state), out_state_size);
735         ast_copy_string(out_subtype, cb_data->presence_state->subtype, out_subtype_size);
736         ast_copy_string(out_message, cb_data->presence_state->message, out_message_size);
737
738         test_sub = stasis_unsubscribe_and_join(test_sub);
739         ast_db_del("CustomPresence", "TestPresenceStateChange");
740
741         return AST_TEST_PASS;
742 }
743
744 AST_TEST_DEFINE(test_presence_state_change)
745 {
746         char out_state[32];
747         char out_subtype[32];
748         char out_message[32];
749
750         switch (cmd) {
751         case TEST_INIT:
752                 info->name = "test_presence_state_change";
753                 info->category = "/funcs/func_presence/";
754                 info->summary = "presence state change subscription";
755                 info->description =
756                         "Ensure that presence state changes are communicated to subscribers";
757                 return AST_TEST_NOT_RUN;
758         case TEST_EXECUTE:
759                 break;
760         }
761
762         if (presence_change_common(test, PRES_STATE, PRES_SUBTYPE, PRES_MESSAGE, NULL,
763                                 out_state, sizeof(out_state),
764                                 out_subtype, sizeof(out_subtype),
765                                 out_message, sizeof(out_message)) == AST_TEST_FAIL) {
766                 return AST_TEST_FAIL;
767         }
768
769         if (strcmp(out_state, PRES_STATE) ||
770                         strcmp(out_subtype, PRES_SUBTYPE) ||
771                         strcmp(out_message, PRES_MESSAGE)) {
772                 ast_test_status_update(test, "Unexpected presence values, %s != %s, %s != %s, or %s != %s\n",
773                                 PRES_STATE, out_state,
774                                 PRES_SUBTYPE, out_subtype,
775                                 PRES_MESSAGE, out_message);
776                 return AST_TEST_FAIL;
777         }
778
779         return AST_TEST_PASS;
780 }
781
782 AST_TEST_DEFINE(test_presence_state_base64_encode)
783 {
784         char out_state[32];
785         char out_subtype[32];
786         char out_message[32];
787         char encoded_subtype[64];
788         char encoded_message[64];
789
790         switch (cmd) {
791         case TEST_INIT:
792                 info->name = "test_presence_state_base64_encode";
793                 info->category = "/funcs/func_presence/";
794                 info->summary = "presence state base64 encoding";
795                 info->description =
796                         "Ensure that base64-encoded presence state is stored base64-encoded but\n"
797                         "is presented to consumers decoded.";
798                 return AST_TEST_NOT_RUN;
799         case TEST_EXECUTE:
800                 break;
801         }
802
803         ast_base64encode(encoded_subtype, (unsigned char *) PRES_SUBTYPE, strlen(PRES_SUBTYPE), sizeof(encoded_subtype) - 1);
804         ast_base64encode(encoded_message, (unsigned char *) PRES_MESSAGE, strlen(PRES_MESSAGE), sizeof(encoded_message) - 1);
805
806         if (presence_change_common(test, PRES_STATE, encoded_subtype, encoded_message, "e",
807                                 out_state, sizeof(out_state),
808                                 out_subtype, sizeof(out_subtype),
809                                 out_message, sizeof(out_message)) == AST_TEST_FAIL) {
810                 return AST_TEST_FAIL;
811         }
812
813         if (strcmp(out_state, PRES_STATE) ||
814                         strcmp(out_subtype, PRES_SUBTYPE) ||
815                         strcmp(out_message, PRES_MESSAGE)) {
816                 ast_test_status_update(test, "Unexpected presence values, %s != %s, %s != %s, or %s != %s\n",
817                                 PRES_STATE, out_state,
818                                 PRES_SUBTYPE, out_subtype,
819                                 PRES_MESSAGE, out_message);
820                 return AST_TEST_FAIL;
821         }
822
823         return AST_TEST_PASS;
824 }
825
826 #endif
827
828 static int unload_module(void)
829 {
830         int res = 0;
831
832         res |= ast_custom_function_unregister(&presence_function);
833         res |= ast_presence_state_prov_del("CustomPresence");
834         res |= ast_cli_unregister_multiple(cli_funcpresencestate, ARRAY_LEN(cli_funcpresencestate));
835 #ifdef TEST_FRAMEWORK
836         AST_TEST_UNREGISTER(test_valid_parse_data);
837         AST_TEST_UNREGISTER(test_invalid_parse_data);
838         AST_TEST_UNREGISTER(test_presence_state_change);
839         AST_TEST_UNREGISTER(test_presence_state_base64_encode);
840 #endif
841         return res;
842 }
843
844 static int load_module(void)
845 {
846         int res = 0;
847         struct ast_db_entry *db_entry, *db_tree;
848
849         /* Populate the presence state cache on the system with all of the currently
850          * known custom presence states. */
851         db_entry = db_tree = ast_db_gettree(astdb_family, NULL);
852         for (; db_entry; db_entry = db_entry->next) {
853                 const char *dev_name = strrchr(db_entry->key, '/') + 1;
854                 enum ast_presence_state state;
855                 char *message;
856                 char *subtype;
857                 if (dev_name <= (const char *) 1) {
858                         continue;
859                 }
860                 state = custom_presence_callback(dev_name, &subtype, &message);
861                 ast_presence_state_changed(state, subtype, message, "CustomPresence:%s", dev_name);
862                 ast_free(subtype);
863                 ast_free(message);
864         }
865         ast_db_freetree(db_tree);
866         db_tree = NULL;
867
868         res |= ast_custom_function_register(&presence_function);
869         res |= ast_presence_state_prov_add("CustomPresence", custom_presence_callback);
870         res |= ast_cli_register_multiple(cli_funcpresencestate, ARRAY_LEN(cli_funcpresencestate));
871 #ifdef TEST_FRAMEWORK
872         AST_TEST_REGISTER(test_valid_parse_data);
873         AST_TEST_REGISTER(test_invalid_parse_data);
874         AST_TEST_REGISTER(test_presence_state_change);
875         AST_TEST_REGISTER(test_presence_state_base64_encode);
876 #endif
877
878         return res;
879 }
880
881 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Gets or sets a presence state in the dialplan",
882         .support_level = AST_MODULE_SUPPORT_CORE,
883         .load = load_module,
884         .unload = unload_module,
885         .load_pri = AST_MODPRI_DEVSTATE_PROVIDER,
886 );
887