git migration: Refactor the ASTERISK_FILE_VERSION macro
[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         ast_db_get(astdb_family, data, buf, sizeof(buf));
259
260         if (parse_data(buf, &state, &_subtype, &_message, &_options)) {
261                 return AST_PRESENCE_INVALID;
262         }
263
264         if ((strchr(_options, 'e'))) {
265                 char tmp[1301];
266
267                 if (ast_strlen_zero(_subtype)) {
268                         *subtype = NULL;
269                 } else {
270                         memset(tmp, 0, sizeof(tmp));
271                         ast_base64decode((unsigned char *) tmp, _subtype, sizeof(tmp) - 1);
272                         *subtype = ast_strdup(tmp);
273                 }
274
275                 if (ast_strlen_zero(_message)) {
276                         *message = NULL;
277                 } else {
278                         memset(tmp, 0, sizeof(tmp));
279                         ast_base64decode((unsigned char *) tmp, _message, sizeof(tmp) - 1);
280                         *message = ast_strdup(tmp);
281                 }
282         } else {
283                 *subtype = ast_strlen_zero(_subtype) ? NULL : ast_strdup(_subtype);
284                 *message = ast_strlen_zero(_message) ? NULL : ast_strdup(_message);
285         }
286         return state;
287 }
288
289 static struct ast_custom_function presence_function = {
290         .name = "PRESENCE_STATE",
291         .read = presence_read,
292         .write = presence_write,
293 };
294
295 static char *handle_cli_presencestate_list(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
296 {
297         struct ast_db_entry *db_entry, *db_tree;
298
299         switch (cmd) {
300         case CLI_INIT:
301                 e->command = "presencestate list";
302                 e->usage =
303                         "Usage: presencestate list\n"
304                         "       List all custom presence states that have been set by using\n"
305                         "       the PRESENCE_STATE dialplan function.\n";
306                 return NULL;
307         case CLI_GENERATE:
308                 return NULL;
309         }
310
311         if (a->argc != e->args) {
312                 return CLI_SHOWUSAGE;
313         }
314
315         ast_cli(a->fd, "\n"
316                 "---------------------------------------------------------------------\n"
317                 "--- Custom Presence States ------------------------------------------\n"
318                 "---------------------------------------------------------------------\n"
319                 "---\n");
320
321         db_entry = db_tree = ast_db_gettree(astdb_family, NULL);
322         if (!db_entry) {
323                 ast_cli(a->fd, "No custom presence states defined\n");
324                 return CLI_SUCCESS;
325         }
326         for (; db_entry; db_entry = db_entry->next) {
327                 const char *object_name = strrchr(db_entry->key, '/') + 1;
328                 char state_info[1301];
329                 enum ast_presence_state state;
330                 char *subtype;
331                 char *message;
332                 char *options;
333
334                 ast_copy_string(state_info, db_entry->data, sizeof(state_info));
335                 if (parse_data(state_info, &state, &subtype, &message, &options)) {
336                         ast_log(LOG_WARNING, "Invalid CustomPresence entry %s encountered\n", db_entry->data);
337                         continue;
338                 }
339
340                 if (object_name <= (const char *) 1) {
341                         continue;
342                 }
343                 ast_cli(a->fd, "--- Name: 'CustomPresence:%s'\n"
344                                        "    --- State: '%s'\n"
345                                            "    --- Subtype: '%s'\n"
346                                            "    --- Message: '%s'\n"
347                                            "    --- Base64 Encoded: '%s'\n"
348                                "---\n",
349                                            object_name,
350                                            ast_presence_state2str(state),
351                                            subtype,
352                                            message,
353                                            AST_CLI_YESNO(strchr(options, 'e')));
354         }
355         ast_db_freetree(db_tree);
356         db_tree = NULL;
357
358         ast_cli(a->fd,
359                 "---------------------------------------------------------------------\n"
360                 "---------------------------------------------------------------------\n"
361                 "\n");
362
363         return CLI_SUCCESS;
364 }
365
366 static char *handle_cli_presencestate_change(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
367 {
368     size_t len;
369         const char *dev, *state, *full_dev;
370         enum ast_presence_state state_val;
371         char *message;
372         char *subtype;
373         char *options;
374         char *args;
375
376         switch (cmd) {
377         case CLI_INIT:
378                 e->command = "presencestate change";
379                 e->usage =
380                         "Usage: presencestate change <entity> <state>[,<subtype>[,message[,options]]]\n"
381                         "       Change a custom presence to a new state.\n"
382                         "       The possible values for the state are:\n"
383                         "NOT_SET | UNAVAILABLE | AVAILABLE | AWAY | XA | CHAT | DND\n"
384                         "Optionally, a custom subtype and message may be provided, along with any options\n"
385                         "accepted by func_presencestate. If the subtype or message provided contain spaces,\n"
386                         "be sure to enclose the data in quotation marks (\"\")\n"
387                         "\n"
388                         "Examples:\n"
389                         "       presencestate change CustomPresence:mystate1 AWAY\n"
390                         "       presencestate change CustomPresence:mystate1 AVAILABLE\n"
391                         "       presencestate change CustomPresence:mystate1 \"Away,upstairs,eating lunch\"\n"
392                         "       \n";
393                 return NULL;
394         case CLI_GENERATE:
395         {
396                 static const char * const cmds[] = { "NOT_SET", "UNAVAILABLE", "AVAILABLE", "AWAY",
397                                                      "XA", "CHAT", "DND", NULL };
398
399                 if (a->pos == e->args + 1) {
400                         return ast_cli_complete(a->word, cmds, a->n);
401                 }
402
403                 return NULL;
404         }
405         }
406
407         if (a->argc != e->args + 2) {
408                 return CLI_SHOWUSAGE;
409         }
410
411         len = strlen("CustomPresence:");
412         full_dev = dev = a->argv[e->args];
413         state = a->argv[e->args + 1];
414
415         if (strncasecmp(dev, "CustomPresence:", len)) {
416                 ast_cli(a->fd, "The presencestate command can only be used to set 'CustomPresence:' presence state!\n");
417                 return CLI_FAILURE;
418         }
419
420         dev += len;
421         if (ast_strlen_zero(dev)) {
422                 return CLI_SHOWUSAGE;
423         }
424
425         args = ast_strdupa(state);
426         if (parse_data(args, &state_val, &subtype, &message, &options)) {
427                 return CLI_SHOWUSAGE;
428         }
429
430         if (state_val == AST_PRESENCE_NOT_SET) {
431                 return CLI_SHOWUSAGE;
432         }
433
434         ast_cli(a->fd, "Changing %s to %s\n", dev, args);
435
436         ast_db_put(astdb_family, dev, state);
437
438         ast_presence_state_changed_literal(state_val, subtype, message, full_dev);
439
440         return CLI_SUCCESS;
441 }
442
443 static struct ast_cli_entry cli_funcpresencestate[] = {
444         AST_CLI_DEFINE(handle_cli_presencestate_list, "List currently know custom presence states"),
445         AST_CLI_DEFINE(handle_cli_presencestate_change, "Change a custom presence state"),
446 };
447
448 #ifdef TEST_FRAMEWORK
449
450 struct test_string {
451         char *parse_string;
452         struct {
453                 int value;
454                 const char *subtype;
455                 const char *message;
456                 const char *options; 
457         } outputs;
458 };
459
460 AST_TEST_DEFINE(test_valid_parse_data)
461 {
462         int i;
463         enum ast_presence_state state;
464         char *subtype;
465         char *message;
466         char *options;
467         enum ast_test_result_state res = AST_TEST_PASS;
468         
469         struct test_string tests [] = {
470                 { "away",
471                         { AST_PRESENCE_AWAY,
472                                 "",
473                                 "",
474                                 ""
475                         }
476                 },
477                 { "not_set",
478                         { AST_PRESENCE_NOT_SET,
479                                 "",
480                                 "",
481                                 ""
482                         }
483                 },
484                 { "unavailable",
485                         { AST_PRESENCE_UNAVAILABLE,
486                                 "",
487                                 "",
488                                 ""
489                         }
490                 },
491                 { "available",
492                         { AST_PRESENCE_AVAILABLE,
493                                 "",
494                                 "",
495                                 ""
496                         }
497                 },
498                 { "xa",
499                         { AST_PRESENCE_XA,
500                                 "",
501                                 "",
502                                 ""
503                         }
504                 },
505                 { "chat",
506                         { AST_PRESENCE_CHAT,
507                                 "",
508                                 "",
509                                 ""
510                         }
511                 },
512                 { "dnd",
513                         { AST_PRESENCE_DND,
514                                 "",
515                                 "",
516                                 ""
517                         }
518                 },
519                 { "away,down the hall",
520                         { AST_PRESENCE_AWAY,
521                                 "down the hall",
522                                 "",
523                                 ""
524                         }
525                 },
526                 { "away,down the hall,Quarterly financial meeting",
527                         { AST_PRESENCE_AWAY,
528                                 "down the hall",
529                                 "Quarterly financial meeting",
530                                 ""
531                         }
532                 },
533                 { "away,,Quarterly financial meeting",
534                         { AST_PRESENCE_AWAY,
535                                 "",
536                                 "Quarterly financial meeting",
537                                 ""
538                         }
539                 },
540                 { "away,,,e",
541                         { AST_PRESENCE_AWAY,
542                                 "",
543                                 "",
544                                 "e",
545                         }
546                 },
547                 { "away,down the hall,,e",
548                         { AST_PRESENCE_AWAY,
549                                 "down the hall",
550                                 "",
551                                 "e"
552                         }
553                 },
554                 { "away,down the hall,Quarterly financial meeting,e",
555                         { AST_PRESENCE_AWAY,
556                                 "down the hall",
557                                 "Quarterly financial meeting",
558                                 "e"
559                         }
560                 },
561                 { "away,,Quarterly financial meeting,e",
562                         { AST_PRESENCE_AWAY,
563                                 "",
564                                 "Quarterly financial meeting",
565                                 "e"
566                         }
567                 }
568         };
569
570         switch (cmd) {
571         case TEST_INIT:
572                 info->name = "parse_valid_presence_data";
573                 info->category = "/funcs/func_presence/";
574                 info->summary = "PRESENCESTATE parsing test";
575                 info->description =
576                         "Ensure that parsing function accepts proper values, and gives proper outputs";
577                 return AST_TEST_NOT_RUN;
578         case TEST_EXECUTE:
579                 break;
580         }
581
582         for (i = 0; i < ARRAY_LEN(tests); ++i) {
583                 int parse_result;
584                 char *parse_string = ast_strdup(tests[i].parse_string);
585                 if (!parse_string) {
586                         res = AST_TEST_FAIL;
587                         break;
588                 }
589                 parse_result = parse_data(parse_string, &state, &subtype, &message, &options);
590                 if (parse_result == -1) {
591                         res = AST_TEST_FAIL;
592                         ast_free(parse_string);
593                         break;
594                 }
595                 if (tests[i].outputs.value != state ||
596                                 strcmp(tests[i].outputs.subtype, subtype) ||
597                                 strcmp(tests[i].outputs.message, message) ||
598                                 strcmp(tests[i].outputs.options, options)) {
599                         res = AST_TEST_FAIL;
600                         ast_free(parse_string);
601                         break;
602                 }
603                 ast_free(parse_string);
604         }
605
606         return res;
607 }
608
609 AST_TEST_DEFINE(test_invalid_parse_data)
610 {
611         int i;
612         enum ast_presence_state state;
613         char *subtype;
614         char *message;
615         char *options;
616         enum ast_test_result_state res = AST_TEST_PASS;
617
618         char *tests[] = {
619                 "",
620                 "bored",
621                 "away,,,i",
622                 /* XXX The following actually is parsed correctly. Should that
623                  * be changed?
624                  * "away,,,,e",
625                  */
626         };
627
628         switch (cmd) {
629         case TEST_INIT:
630                 info->name = "parse_invalid_presence_data";
631                 info->category = "/funcs/func_presence/";
632                 info->summary = "PRESENCESTATE parsing test";
633                 info->description =
634                         "Ensure that parsing function rejects improper values";
635                 return AST_TEST_NOT_RUN;
636         case TEST_EXECUTE:
637                 break;
638         }
639
640         for (i = 0; i < ARRAY_LEN(tests); ++i) {
641                 int parse_result;
642                 char *parse_string = ast_strdup(tests[i]);
643                 if (!parse_string) {
644                         res = AST_TEST_FAIL;
645                         break;
646                 }
647                 parse_result = parse_data(parse_string, &state, &subtype, &message, &options);
648                 if (parse_result == 0) {
649                         ast_log(LOG_WARNING, "Invalid string parsing failed on %s\n", tests[i]);
650                         res = AST_TEST_FAIL;
651                         ast_free(parse_string);
652                         break;
653                 }
654                 ast_free(parse_string);
655         }
656
657         return res;
658 }
659
660 #define PRES_STATE "away"
661 #define PRES_SUBTYPE "down the hall"
662 #define PRES_MESSAGE "Quarterly financial meeting"
663
664 struct test_cb_data {
665         struct ast_presence_state_message *presence_state;
666         /* That's right. I'm using a semaphore */
667         struct ast_sem sem;
668 };
669
670 static struct test_cb_data *test_cb_data_alloc(void)
671 {
672         struct test_cb_data *cb_data = ast_calloc(1, sizeof(*cb_data));
673
674         if (!cb_data) {
675                 return NULL;
676         }
677
678         if (ast_sem_init(&cb_data->sem, 0, 0)) {
679                 ast_free(cb_data);
680                 return NULL;
681         }
682
683         return cb_data;
684 }
685
686 static void test_cb_data_destroy(struct test_cb_data *cb_data)
687 {
688         ao2_cleanup(cb_data->presence_state);
689         ast_sem_destroy(&cb_data->sem);
690         ast_free(cb_data);
691 }
692
693 static void test_cb(void *userdata, struct stasis_subscription *sub, struct stasis_message *msg)
694 {
695         struct test_cb_data *cb_data = userdata;
696         if (stasis_message_type(msg) != ast_presence_state_message_type()) {
697                 return;
698         }
699         cb_data->presence_state = stasis_message_data(msg);
700         ao2_ref(cb_data->presence_state, +1);
701
702         ast_sem_post(&cb_data->sem);
703 }
704
705 static enum ast_test_result_state presence_change_common(struct ast_test *test,
706                 const char *state, const char *subtype, const char *message, const char *options,
707                 char *out_state, size_t out_state_size,
708                 char *out_subtype, size_t out_subtype_size,
709                 char *out_message, size_t out_message_size)
710 {
711         RAII_VAR(struct test_cb_data *, cb_data, test_cb_data_alloc(), test_cb_data_destroy);
712         struct stasis_subscription *test_sub;
713         char pres[1301];
714
715         if (!(test_sub = stasis_subscribe(ast_presence_state_topic_all(), test_cb, cb_data))) {
716                 return AST_TEST_FAIL;
717         }
718
719         if (ast_strlen_zero(options)) {
720                 snprintf(pres, sizeof(pres), "%s,%s,%s", state, subtype, message);
721         } else {
722                 snprintf(pres, sizeof(pres), "%s,%s,%s,%s", state, subtype, message, options);
723         }
724
725         if (presence_write(NULL, "PRESENCESTATE", "CustomPresence:TestPresenceStateChange", pres)) {
726                 test_sub = stasis_unsubscribe_and_join(test_sub);
727                 return AST_TEST_FAIL;
728         }
729
730         ast_sem_wait(&cb_data->sem);
731
732         ast_copy_string(out_state, ast_presence_state2str(cb_data->presence_state->state), out_state_size);
733         ast_copy_string(out_subtype, cb_data->presence_state->subtype, out_subtype_size);
734         ast_copy_string(out_message, cb_data->presence_state->message, out_message_size);
735
736         test_sub = stasis_unsubscribe_and_join(test_sub);
737         ast_db_del("CustomPresence", "TestPresenceStateChange");
738
739         return AST_TEST_PASS;
740 }
741
742 AST_TEST_DEFINE(test_presence_state_change)
743 {
744         char out_state[32];
745         char out_subtype[32];
746         char out_message[32];
747
748         switch (cmd) {
749         case TEST_INIT:
750                 info->name = "test_presence_state_change";
751                 info->category = "/funcs/func_presence/";
752                 info->summary = "presence state change subscription";
753                 info->description =
754                         "Ensure that presence state changes are communicated to subscribers";
755                 return AST_TEST_NOT_RUN;
756         case TEST_EXECUTE:
757                 break;
758         }
759
760         if (presence_change_common(test, PRES_STATE, PRES_SUBTYPE, PRES_MESSAGE, NULL,
761                                 out_state, sizeof(out_state),
762                                 out_subtype, sizeof(out_subtype),
763                                 out_message, sizeof(out_message)) == AST_TEST_FAIL) {
764                 return AST_TEST_FAIL;
765         }
766
767         if (strcmp(out_state, PRES_STATE) ||
768                         strcmp(out_subtype, PRES_SUBTYPE) ||
769                         strcmp(out_message, PRES_MESSAGE)) {
770                 ast_test_status_update(test, "Unexpected presence values, %s != %s, %s != %s, or %s != %s\n",
771                                 PRES_STATE, out_state,
772                                 PRES_SUBTYPE, out_subtype,
773                                 PRES_MESSAGE, out_message);
774                 return AST_TEST_FAIL;
775         }
776
777         return AST_TEST_PASS;
778 }
779
780 AST_TEST_DEFINE(test_presence_state_base64_encode)
781 {
782         char out_state[32];
783         char out_subtype[32];
784         char out_message[32];
785         char encoded_subtype[64];
786         char encoded_message[64];
787
788         switch (cmd) {
789         case TEST_INIT:
790                 info->name = "test_presence_state_base64_encode";
791                 info->category = "/funcs/func_presence/";
792                 info->summary = "presence state base64 encoding";
793                 info->description =
794                         "Ensure that base64-encoded presence state is stored base64-encoded but\n"
795                         "is presented to consumers decoded.";
796                 return AST_TEST_NOT_RUN;
797         case TEST_EXECUTE:
798                 break;
799         }
800
801         ast_base64encode(encoded_subtype, (unsigned char *) PRES_SUBTYPE, strlen(PRES_SUBTYPE), sizeof(encoded_subtype) - 1);
802         ast_base64encode(encoded_message, (unsigned char *) PRES_MESSAGE, strlen(PRES_MESSAGE), sizeof(encoded_message) - 1);
803
804         if (presence_change_common(test, PRES_STATE, encoded_subtype, encoded_message, "e",
805                                 out_state, sizeof(out_state),
806                                 out_subtype, sizeof(out_subtype),
807                                 out_message, sizeof(out_message)) == AST_TEST_FAIL) {
808                 return AST_TEST_FAIL;
809         }
810
811         if (strcmp(out_state, PRES_STATE) ||
812                         strcmp(out_subtype, PRES_SUBTYPE) ||
813                         strcmp(out_message, PRES_MESSAGE)) {
814                 ast_test_status_update(test, "Unexpected presence values, %s != %s, %s != %s, or %s != %s\n",
815                                 PRES_STATE, out_state,
816                                 PRES_SUBTYPE, out_subtype,
817                                 PRES_MESSAGE, out_message);
818                 return AST_TEST_FAIL;
819         }
820
821         return AST_TEST_PASS;
822 }
823
824 #endif
825
826 static int unload_module(void)
827 {
828         int res = 0;
829
830         res |= ast_custom_function_unregister(&presence_function);
831         res |= ast_presence_state_prov_del("CustomPresence");
832         res |= ast_cli_unregister_multiple(cli_funcpresencestate, ARRAY_LEN(cli_funcpresencestate));
833 #ifdef TEST_FRAMEWORK
834         AST_TEST_UNREGISTER(test_valid_parse_data);
835         AST_TEST_UNREGISTER(test_invalid_parse_data);
836         AST_TEST_UNREGISTER(test_presence_state_change);
837         AST_TEST_UNREGISTER(test_presence_state_base64_encode);
838 #endif
839         return res;
840 }
841
842 static int load_module(void)
843 {
844         int res = 0;
845         struct ast_db_entry *db_entry, *db_tree;
846
847         /* Populate the presence state cache on the system with all of the currently
848          * known custom presence states. */
849         db_entry = db_tree = ast_db_gettree(astdb_family, NULL);
850         for (; db_entry; db_entry = db_entry->next) {
851                 const char *dev_name = strrchr(db_entry->key, '/') + 1;
852                 enum ast_presence_state state;
853                 char *message;
854                 char *subtype;
855                 if (dev_name <= (const char *) 1) {
856                         continue;
857                 }
858                 state = custom_presence_callback(dev_name, &subtype, &message);
859                 ast_presence_state_changed(state, subtype, message, "CustomPresence:%s", dev_name);
860                 ast_free(subtype);
861                 ast_free(message);
862         }
863         ast_db_freetree(db_tree);
864         db_tree = NULL;
865
866         res |= ast_custom_function_register(&presence_function);
867         res |= ast_presence_state_prov_add("CustomPresence", custom_presence_callback);
868         res |= ast_cli_register_multiple(cli_funcpresencestate, ARRAY_LEN(cli_funcpresencestate));
869 #ifdef TEST_FRAMEWORK
870         AST_TEST_REGISTER(test_valid_parse_data);
871         AST_TEST_REGISTER(test_invalid_parse_data);
872         AST_TEST_REGISTER(test_presence_state_change);
873         AST_TEST_REGISTER(test_presence_state_base64_encode);
874 #endif
875
876         return res;
877 }
878
879 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Gets or sets a presence state in the dialplan",
880         .support_level = AST_MODULE_SUPPORT_CORE,
881         .load = load_module,
882         .unload = unload_module,
883         .load_pri = AST_MODPRI_DEVSTATE_PROVIDER,
884 );
885