2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 2011, Digium, Inc.
6 * David Vossel <dvossel@digium.com>
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.
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.
21 * \brief Custom presence provider
26 <support_level>core</support_level>
31 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
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"
43 #include "asterisk/test.h"
44 #include "asterisk/event.h"
45 #include <semaphore.h>
49 <function name="PRESENCE_STATE" language="en_US">
51 Get or Set a presence state.
54 <parameter name="provider" required="true">
55 <para>The provider of the presence, such as <literal>CustomPresence</literal></para>
57 <parameter name="field" required="true">
58 <para>Which field of the presence state information is wanted.</para>
61 <para>The current presence, such as <literal>away</literal></para>
63 <option name="subtype">
64 <para>Further information about the current presence</para>
66 <option name="message">
67 <para>A custom message that may indicate further details about the presence</para>
71 <parameter name="options" required="false">
74 <para>On Write - Use this option when the subtype and message provided are Base64
75 encoded. On Read - Retrieves message/subtype in Base64 encoded form.</para>
81 <para>The PRESENCE_STATE function can be used to retrieve the presence from any
82 presence provider. For example:</para>
83 <para>NoOp(SIP/mypeer has presence ${PRESENCE_STATE(SIP/mypeer,value)})</para>
84 <para>NoOp(Conference number 1234 has presence message ${PRESENCE_STATE(MeetMe:1234,message)})</para>
85 <para>The PRESENCE_STATE function can also be used to set custom presence state from
86 the dialplan. The <literal>CustomPresence:</literal> prefix must be used. For example:</para>
87 <para>Set(PRESENCE_STATE(CustomPresence:lamp1)=away,temporary,Out to lunch)</para>
88 <para>Set(PRESENCE_STATE(CustomPresence:lamp2)=dnd,,Trying to get work done)</para>
89 <para>Set(PRESENCE_STATE(CustomPresence:lamp3)=xa,T24gdmFjYXRpb24=,,e)</para>
90 <para>Set(BASE64_LAMP3_PRESENCE=${PRESENCE_STATE(CustomPresence:lamp3,subtype,e)})</para>
91 <para>You can subscribe to the status of a custom presence state using a hint in
93 <para>exten => 1234,hint,CustomPresence:lamp1</para>
94 <para>The possible values for both uses of this function are:</para>
95 <para>not_set | unavailable | available | away | xa | chat | dnd</para>
101 static const char astdb_family[] = "CustomPresence";
103 static int presence_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
106 char *message = NULL;
107 char *subtype = NULL;
109 int base64encode = 0;
110 AST_DECLARE_APP_ARGS(args,
111 AST_APP_ARG(provider);
113 AST_APP_ARG(options);
116 if (ast_strlen_zero(data)) {
117 ast_log(LOG_WARNING, "PRESENCE_STATE reading requires an argument \n");
121 parse = ast_strdupa(data);
123 AST_STANDARD_APP_ARGS(args, parse);
125 if (ast_strlen_zero(args.provider) || ast_strlen_zero(args.field)) {
126 ast_log(LOG_WARNING, "PRESENCE_STATE reading requires both presence provider and presence field arguments. \n");
130 state = ast_presence_state_nocache(args.provider, &subtype, &message);
131 if (state == AST_PRESENCE_INVALID) {
132 ast_log(LOG_WARNING, "PRESENCE_STATE unknown \n");
136 if (!(ast_strlen_zero(args.options)) && (strchr(args.options, 'e'))) {
140 if (!ast_strlen_zero(subtype) && !strcasecmp(args.field, "subtype")) {
142 ast_base64encode(buf, (unsigned char *) subtype, strlen(subtype), len);
144 ast_copy_string(buf, subtype, len);
146 } else if (!ast_strlen_zero(message) && !strcasecmp(args.field, "message")) {
148 ast_base64encode(buf, (unsigned char *) message, strlen(message), len);
150 ast_copy_string(buf, message, len);
153 } else if (!strcasecmp(args.field, "value")) {
154 ast_copy_string(buf, ast_presence_state2str(state), len);
163 static int parse_data(char *data, enum ast_presence_state *state, char **subtype, char **message, char **options)
167 /* data syntax is state,subtype,message,options */
172 state_str = strsep(&data, ",");
173 if (ast_strlen_zero(state_str)) {
174 return -1; /* state is required */
177 *state = ast_presence_state_val(state_str);
179 /* not a valid state */
180 if (*state == AST_PRESENCE_INVALID) {
181 ast_log(LOG_WARNING, "Unknown presence state value %s\n", state_str);
185 if (!(*subtype = strsep(&data,","))) {
190 if (!(*message = strsep(&data, ","))) {
195 if (!(*options = strsep(&data, ","))) {
200 if (!ast_strlen_zero(*options) && !(strchr(*options, 'e'))) {
201 ast_log(LOG_NOTICE, "Invalid options '%s'\n", *options);
208 static int presence_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
210 size_t len = strlen("CustomPresence:");
212 char *args = ast_strdupa(value);
213 enum ast_presence_state state;
214 char *options, *message, *subtype;
216 if (strncasecmp(data, "CustomPresence:", len)) {
217 ast_log(LOG_WARNING, "The PRESENCE_STATE function can only set CustomPresence: presence providers.\n");
221 if (ast_strlen_zero(data)) {
222 ast_log(LOG_WARNING, "PRESENCE_STATE function called with no custom device name!\n");
226 if (parse_data(args, &state, &subtype, &message, &options)) {
227 ast_log(LOG_WARNING, "Invalid arguments to PRESENCE_STATE\n");
231 ast_db_put(astdb_family, data, value);
233 ast_presence_state_changed_literal(state, subtype, message, tmp);
238 static enum ast_presence_state custom_presence_callback(const char *data, char **subtype, char **message)
241 enum ast_presence_state state;
246 ast_db_get(astdb_family, data, buf, sizeof(buf));
248 if (parse_data(buf, &state, &_subtype, &_message, &_options)) {
249 return AST_PRESENCE_INVALID;
252 if ((strchr(_options, 'e'))) {
254 if (ast_strlen_zero(_subtype)) {
257 memset(tmp, 0, sizeof(tmp));
258 ast_base64decode((unsigned char *) tmp, _subtype, sizeof(tmp) - 1);
259 *subtype = ast_strdup(tmp);
262 if (ast_strlen_zero(_message)) {
265 memset(tmp, 0, sizeof(tmp));
266 ast_base64decode((unsigned char *) tmp, _message, sizeof(tmp) - 1);
267 *message = ast_strdup(tmp);
270 *subtype = ast_strlen_zero(_subtype) ? NULL : ast_strdup(_subtype);
271 *message = ast_strlen_zero(_message) ? NULL : ast_strdup(_message);
276 static struct ast_custom_function presence_function = {
277 .name = "PRESENCE_STATE",
278 .read = presence_read,
279 .write = presence_write,
282 static char *handle_cli_presencestate_list(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
284 struct ast_db_entry *db_entry, *db_tree;
288 e->command = "presencestate list";
290 "Usage: presencestate list\n"
291 " List all custom presence states that have been set by using\n"
292 " the PRESENCE_STATE dialplan function.\n";
298 if (a->argc != e->args) {
299 return CLI_SHOWUSAGE;
303 "---------------------------------------------------------------------\n"
304 "--- Custom Presence States ------------------------------------------\n"
305 "---------------------------------------------------------------------\n"
308 db_entry = db_tree = ast_db_gettree(astdb_family, NULL);
310 ast_cli(a->fd, "No custom presence states defined\n");
313 for (; db_entry; db_entry = db_entry->next) {
314 const char *object_name = strrchr(db_entry->key, '/') + 1;
315 char state_info[1301];
316 enum ast_presence_state state;
321 ast_copy_string(state_info, db_entry->data, sizeof(state_info));
322 if (parse_data(state_info, &state, &subtype, &message, &options)) {
323 ast_log(LOG_WARNING, "Invalid CustomPresence entry %s encountered\n", db_entry->data);
327 if (object_name <= (const char *) 1) {
330 ast_cli(a->fd, "--- Name: 'CustomPresence:%s'\n"
332 " --- Subtype: '%s'\n"
333 " --- Message: '%s'\n"
334 " --- Base64 Encoded: '%s'\n"
337 ast_presence_state2str(state),
340 AST_CLI_YESNO(strchr(options, 'e')));
342 ast_db_freetree(db_tree);
346 "---------------------------------------------------------------------\n"
347 "---------------------------------------------------------------------\n"
353 static char *handle_cli_presencestate_change(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
356 const char *dev, *state, *full_dev;
357 enum ast_presence_state state_val;
365 e->command = "presencestate change";
367 "Usage: presencestate change <entity> <state>[,<subtype>[,message[,options]]]\n"
368 " Change a custom presence to a new state.\n"
369 " The possible values for the state are:\n"
370 "NOT_SET | UNAVAILABLE | AVAILABLE | AWAY | XA | CHAT | DND\n"
371 "Optionally, a custom subtype and message may be provided, along with any options\n"
372 "accepted by func_presencestate. If the subtype or message provided contain spaces,\n"
373 "be sure to enclose the data in quotation marks (\"\")\n"
376 " presencestate change CustomPresence:mystate1 AWAY\n"
377 " presencestate change CustomPresence:mystate1 AVAILABLE\n"
378 " presencestate change CustomPresence:mystate1 \"Away,upstairs,eating lunch\"\n"
383 static const char * const cmds[] = { "NOT_SET", "UNAVAILABLE", "AVAILABLE", "AWAY",
384 "XA", "CHAT", "DND", NULL };
386 if (a->pos == e->args + 1) {
387 return ast_cli_complete(a->word, cmds, a->n);
394 if (a->argc != e->args + 2) {
395 return CLI_SHOWUSAGE;
398 len = strlen("CustomPresence:");
399 full_dev = dev = a->argv[e->args];
400 state = a->argv[e->args + 1];
402 if (strncasecmp(dev, "CustomPresence:", len)) {
403 ast_cli(a->fd, "The presencestate command can only be used to set 'CustomPresence:' presence state!\n");
408 if (ast_strlen_zero(dev)) {
409 return CLI_SHOWUSAGE;
412 args = ast_strdupa(state);
413 if (parse_data(args, &state_val, &subtype, &message, &options)) {
414 return CLI_SHOWUSAGE;
417 if (state_val == AST_PRESENCE_NOT_SET) {
418 return CLI_SHOWUSAGE;
421 ast_cli(a->fd, "Changing %s to %s\n", dev, args);
423 ast_db_put(astdb_family, dev, state);
425 ast_presence_state_changed_literal(state_val, subtype, message, full_dev);
430 static struct ast_cli_entry cli_funcpresencestate[] = {
431 AST_CLI_DEFINE(handle_cli_presencestate_list, "List currently know custom presence states"),
432 AST_CLI_DEFINE(handle_cli_presencestate_change, "Change a custom presence state"),
435 #ifdef TEST_FRAMEWORK
447 AST_TEST_DEFINE(test_valid_parse_data)
450 enum ast_presence_state state;
454 enum ast_test_result_state res = AST_TEST_PASS;
456 struct test_string tests [] = {
465 { AST_PRESENCE_NOT_SET,
472 { AST_PRESENCE_UNAVAILABLE,
479 { AST_PRESENCE_AVAILABLE,
506 { "away,down the hall",
513 { "away,down the hall,Quarterly financial meeting",
516 "Quarterly financial meeting",
520 { "away,,Quarterly financial meeting",
523 "Quarterly financial meeting",
534 { "away,down the hall,,e",
541 { "away,down the hall,Quarterly financial meeting,e",
544 "Quarterly financial meeting",
548 { "away,,Quarterly financial meeting,e",
551 "Quarterly financial meeting",
559 info->name = "parse_valid_presence_data";
560 info->category = "/funcs/func_presence/";
561 info->summary = "PRESENCESTATE parsing test";
563 "Ensure that parsing function accepts proper values, and gives proper outputs";
564 return AST_TEST_NOT_RUN;
569 for (i = 0; i < ARRAY_LEN(tests); ++i) {
571 char *parse_string = ast_strdup(tests[i].parse_string);
576 parse_result = parse_data(parse_string, &state, &subtype, &message, &options);
577 if (parse_result == -1) {
579 ast_free(parse_string);
582 if (tests[i].outputs.value != state ||
583 strcmp(tests[i].outputs.subtype, subtype) ||
584 strcmp(tests[i].outputs.message, message) ||
585 strcmp(tests[i].outputs.options, options)) {
587 ast_free(parse_string);
590 ast_free(parse_string);
596 AST_TEST_DEFINE(test_invalid_parse_data)
599 enum ast_presence_state state;
603 enum ast_test_result_state res = AST_TEST_PASS;
609 /* XXX The following actually is parsed correctly. Should that
617 info->name = "parse_invalid_presence_data";
618 info->category = "/funcs/func_presence/";
619 info->summary = "PRESENCESTATE parsing test";
621 "Ensure that parsing function rejects improper values";
622 return AST_TEST_NOT_RUN;
627 for (i = 0; i < ARRAY_LEN(tests); ++i) {
629 char *parse_string = ast_strdup(tests[i]);
634 parse_result = parse_data(parse_string, &state, &subtype, &message, &options);
635 if (parse_result == 0) {
636 ast_log(LOG_WARNING, "Invalid string parsing failed on %s\n", tests[i]);
638 ast_free(parse_string);
641 ast_free(parse_string);
647 struct test_cb_data {
648 enum ast_presence_state presence;
649 const char *provider;
652 /* That's right. I'm using a semaphore */
656 static void test_cb(const struct ast_event *event, void *userdata)
658 struct test_cb_data *cb_data = userdata;
659 cb_data->presence = ast_event_get_ie_uint(event, AST_EVENT_IE_PRESENCE_STATE);
660 cb_data->provider = ast_strdup(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_PROVIDER));
661 cb_data->subtype = ast_strdup(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_SUBTYPE));
662 cb_data->message = ast_strdup(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_MESSAGE));
663 sem_post(&cb_data->sem);
666 /* XXX This test could probably stand to be moved since
667 * it does not test func_presencestate but rather code in
668 * presencestate.h and presencestate.c. However, the convenience
669 * of presence_write() makes this a nice location for this test.
671 AST_TEST_DEFINE(test_presence_state_change)
673 struct ast_event_sub *test_sub;
674 struct test_cb_data *cb_data;
678 info->name = "test_presence_state_change";
679 info->category = "/funcs/func_presence/";
680 info->summary = "presence state change subscription";
682 "Ensure that presence state changes are communicated to subscribers";
683 return AST_TEST_NOT_RUN;
688 cb_data = ast_calloc(1, sizeof(*cb_data));
690 return AST_TEST_FAIL;
693 if (!(test_sub = ast_event_subscribe(AST_EVENT_PRESENCE_STATE,
694 test_cb, "Test presence state callbacks", cb_data, AST_EVENT_IE_END))) {
695 return AST_TEST_FAIL;
698 if (sem_init(&cb_data->sem, 0, 0)) {
699 return AST_TEST_FAIL;
702 presence_write(NULL, "PRESENCESTATE", "CustomPresence:TestPresenceStateChange", "away,down the hall,Quarterly financial meeting");
703 sem_wait(&cb_data->sem);
704 if (cb_data->presence != AST_PRESENCE_AWAY ||
705 strcmp(cb_data->provider, "CustomPresence:TestPresenceStateChange") ||
706 strcmp(cb_data->subtype, "down the hall") ||
707 strcmp(cb_data->message, "Quarterly financial meeting")) {
708 return AST_TEST_FAIL;
711 ast_free((char *)cb_data->provider);
712 ast_free((char *)cb_data->subtype);
713 ast_free((char *)cb_data->message);
714 ast_free((char *)cb_data);
716 ast_db_del("CustomPresence", "TestPresenceStateChange");
718 return AST_TEST_PASS;
723 static int unload_module(void)
727 res |= ast_custom_function_unregister(&presence_function);
728 res |= ast_presence_state_prov_del("CustomPresence");
729 res |= ast_cli_unregister_multiple(cli_funcpresencestate, ARRAY_LEN(cli_funcpresencestate));
730 #ifdef TEST_FRAMEWORK
731 AST_TEST_UNREGISTER(test_valid_parse_data);
732 AST_TEST_UNREGISTER(test_invalid_parse_data);
733 AST_TEST_UNREGISTER(test_presence_state_change);
738 static int load_module(void)
741 struct ast_db_entry *db_entry, *db_tree;
743 /* Populate the presence state cache on the system with all of the currently
744 * known custom presence states. */
745 db_entry = db_tree = ast_db_gettree(astdb_family, NULL);
746 for (; db_entry; db_entry = db_entry->next) {
747 const char *dev_name = strrchr(db_entry->key, '/') + 1;
748 char state_info[1301];
749 enum ast_presence_state state;
753 if (dev_name <= (const char *) 1) {
756 ast_copy_string(state_info, db_entry->data, sizeof(state_info));
757 if (parse_data(state_info, &state, &subtype, &message, &options)) {
758 ast_log(LOG_WARNING, "Invalid CustomPresence entry %s encountered\n", db_entry->data);
761 ast_presence_state_changed(state, subtype, message, "CustomPresence:%s", dev_name);
763 ast_db_freetree(db_tree);
766 res |= ast_custom_function_register(&presence_function);
767 res |= ast_presence_state_prov_add("CustomPresence", custom_presence_callback);
768 res |= ast_cli_register_multiple(cli_funcpresencestate, ARRAY_LEN(cli_funcpresencestate));
769 #ifdef TEST_FRAMEWORK
770 AST_TEST_REGISTER(test_valid_parse_data);
771 AST_TEST_REGISTER(test_invalid_parse_data);
772 AST_TEST_REGISTER(test_presence_state_change);
778 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Gets or sets a presence state in the dialplan",
780 .unload = unload_module,
781 .load_pri = AST_MODPRI_DEVSTATE_PROVIDER,