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