res_smdi: Fix shutdown ref.
[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 #include "asterisk/module.h"
32 #include "asterisk/channel.h"
33 #include "asterisk/pbx.h"
34 #include "asterisk/utils.h"
35 #include "asterisk/linkedlists.h"
36 #include "asterisk/presencestate.h"
37 #include "asterisk/cli.h"
38 #include "asterisk/astdb.h"
39 #include "asterisk/app.h"
40 #ifdef TEST_FRAMEWORK
41 #include "asterisk/test.h"
42 #include "asterisk/sem.h"
43 #endif
44
45 /*** DOCUMENTATION
46         <function name="PRESENCE_STATE" language="en_US">
47                 <synopsis>
48                         Get or Set a presence state.
49                 </synopsis>
50                 <syntax>
51                         <parameter name="provider" required="true">
52                           <para>The provider of the presence, such as <literal>CustomPresence</literal></para>
53                         </parameter>
54                         <parameter name="field" required="true">
55                           <para>Which field of the presence state information is wanted.</para>
56                           <optionlist>
57                                 <option name="value">
58                                   <para>The current presence, such as <literal>away</literal></para>
59                                 </option>
60                                 <option name="subtype">
61                                   <para>Further information about the current presence</para>
62                                 </option>
63                             <option name="message">
64                                   <para>A custom message that may indicate further details about the presence</para>
65                                 </option>
66                           </optionlist>
67                         </parameter>
68                         <parameter name="options" required="false">
69                           <optionlist>
70                             <option name="e">
71                                   <para>On Write - Use this option when the subtype and message provided are Base64
72                                         encoded. The values will be stored encoded within Asterisk, but all consumers of
73                                         the presence state (e.g. the SIP presence event package) will receive decoded values.</para>
74                                         <para>On Read - Retrieves unencoded message/subtype in Base64 encoded form.</para>
75                                 </option>
76                           </optionlist>
77                         </parameter>
78                 </syntax>
79                 <description>
80                         <para>The PRESENCE_STATE function can be used to retrieve the presence from any
81                         presence provider. For example:</para>
82                         <para>NoOp(SIP/mypeer has presence ${PRESENCE_STATE(SIP/mypeer,value)})</para>
83                         <para>NoOp(Conference number 1234 has presence message ${PRESENCE_STATE(MeetMe:1234,message)})</para>
84                         <para>The PRESENCE_STATE function can also be used to set custom presence state from
85                         the dialplan.  The <literal>CustomPresence:</literal> prefix must be used. For example:</para>
86                         <para>Set(PRESENCE_STATE(CustomPresence:lamp1)=away,temporary,Out to lunch)</para>
87                         <para>Set(PRESENCE_STATE(CustomPresence:lamp2)=dnd,,Trying to get work done)</para>
88                         <para>Set(PRESENCE_STATE(CustomPresence:lamp3)=xa,T24gdmFjYXRpb24=,,e)</para>
89                         <para>Set(BASE64_LAMP3_PRESENCE=${PRESENCE_STATE(CustomPresence:lamp3,subtype,e)})</para>
90                         <para>You can subscribe to the status of a custom presence state using a hint in
91                         the dialplan:</para>
92                         <para>exten => 1234,hint,,CustomPresence:lamp1</para>
93                         <para>The possible values for both uses of this function are:</para>
94                         <para>not_set | unavailable | available | away | xa | chat | dnd</para>
95                 </description>
96         </function>
97  ***/
98
99
100 static const char astdb_family[] = "CustomPresence";
101
102 static int presence_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
103 {
104         int state;
105         char *message = NULL;
106         char *subtype = NULL;
107         char *parse;
108         int base64encode = 0;
109         AST_DECLARE_APP_ARGS(args,
110                 AST_APP_ARG(provider);
111                 AST_APP_ARG(field);
112                 AST_APP_ARG(options);
113         );
114
115         if (ast_strlen_zero(data)) {
116                 ast_log(LOG_WARNING, "PRESENCE_STATE reading requires an argument \n");
117                 return -1;
118         }
119
120         parse = ast_strdupa(data);
121
122         AST_STANDARD_APP_ARGS(args, parse);
123
124         if (ast_strlen_zero(args.provider) || ast_strlen_zero(args.field)) {
125                 ast_log(LOG_WARNING, "PRESENCE_STATE reading requires both presence provider and presence field arguments. \n");
126                 return -1;
127         }
128
129         state = ast_presence_state_nocache(args.provider, &subtype, &message);
130         if (state == AST_PRESENCE_INVALID) {
131                 ast_log(LOG_WARNING, "PRESENCE_STATE unknown \n");
132                 return -1;
133         }
134
135         if (!(ast_strlen_zero(args.options)) && (strchr(args.options, 'e'))) {
136                 base64encode = 1;
137         }
138
139         if (!ast_strlen_zero(subtype) && !strcasecmp(args.field, "subtype")) {
140                 if (base64encode) {
141                         ast_base64encode(buf, (unsigned char *) subtype, strlen(subtype), len);
142                 } else {
143                         ast_copy_string(buf, subtype, len);
144                 }
145         } else if (!ast_strlen_zero(message) && !strcasecmp(args.field, "message")) {
146                 if (base64encode) {
147                         ast_base64encode(buf, (unsigned char *) message, strlen(message), len);
148                 } else {
149                         ast_copy_string(buf, message, len);
150                 }
151
152         } else if (!strcasecmp(args.field, "value")) {
153                 ast_copy_string(buf, ast_presence_state2str(state), len);
154         }
155
156         ast_free(message);
157         ast_free(subtype);
158
159         return 0;
160 }
161
162 static int parse_data(char *data, enum ast_presence_state *state, char **subtype, char **message, char **options)
163 {
164         char *state_str;
165
166         /* data syntax is state,subtype,message,options */
167         *subtype = "";
168         *message = "";
169         *options = "";
170
171         state_str = strsep(&data, ",");
172         if (ast_strlen_zero(state_str)) {
173                 return -1; /* state is required */
174         }
175
176         *state = ast_presence_state_val(state_str);
177
178         /* not a valid state */
179         if (*state == AST_PRESENCE_INVALID) {
180                 ast_log(LOG_WARNING, "Unknown presence state value %s\n", state_str);
181                 return -1;
182         }
183
184         if (!(*subtype = strsep(&data,","))) {
185                 *subtype = "";
186                 return 0;
187         }
188
189         if (!(*message = strsep(&data, ","))) {
190                 *message = "";
191                 return 0;
192         }
193
194         if (!(*options = strsep(&data, ","))) {
195                 *options = "";
196                 return 0;
197         }
198
199         if (!ast_strlen_zero(*options) && !(strchr(*options, 'e'))) {
200                 ast_log(LOG_NOTICE, "Invalid options  '%s'\n", *options);
201                 return -1;
202         }
203
204         return 0;
205 }
206
207 static int presence_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
208 {
209         size_t len = strlen("CustomPresence:");
210         char *tmp = data;
211         char *args = ast_strdupa(value);
212         enum ast_presence_state state;
213         char *options, *message, *subtype;
214
215         if (strncasecmp(data, "CustomPresence:", len)) {
216                 ast_log(LOG_WARNING, "The PRESENCE_STATE function can only set CustomPresence: presence providers.\n");
217                 return -1;
218         }
219         data += len;
220         if (ast_strlen_zero(data)) {
221                 ast_log(LOG_WARNING, "PRESENCE_STATE function called with no custom device name!\n");
222                 return -1;
223         }
224
225         if (parse_data(args, &state, &subtype, &message, &options)) {
226                 ast_log(LOG_WARNING, "Invalid arguments to PRESENCE_STATE\n");
227                 return -1;
228         }
229
230         ast_db_put(astdb_family, data, value);
231
232         if (strchr(options, 'e')) {
233                 /* Let's decode the values before sending them to stasis, yes? */
234                 char decoded_subtype[256] = { 0, };
235                 char decoded_message[256] = { 0, };
236
237                 ast_base64decode((unsigned char *) decoded_subtype, subtype, sizeof(decoded_subtype) -1);
238                 ast_base64decode((unsigned char *) decoded_message, message, sizeof(decoded_message) -1);
239
240                 ast_presence_state_changed_literal(state, decoded_subtype, decoded_message, tmp);
241         } else {
242                 ast_presence_state_changed_literal(state, subtype, message, tmp);
243         }
244
245         return 0;
246 }
247
248 static enum ast_presence_state custom_presence_callback(const char *data, char **subtype, char **message)
249 {
250         char buf[1301] = "";
251         enum ast_presence_state state;
252         char *_options;
253         char *_message;
254         char *_subtype;
255
256         if (ast_db_get(astdb_family, data, buf, sizeof(buf))) {
257                 return AST_PRESENCE_NOT_SET;
258         }
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