123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597 |
- /*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2011-2012, Digium, Inc.
- *
- * David Vossel <dvossel@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
- /*! \file
- *
- * \brief Custom presence provider
- * \ingroup functions
- */
- #include "asterisk.h"
- ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
- #include "asterisk/module.h"
- #include "asterisk/channel.h"
- #include "asterisk/pbx.h"
- #include "asterisk/utils.h"
- #include "asterisk/linkedlists.h"
- #include "asterisk/presencestate.h"
- #include "asterisk/cli.h"
- #include "asterisk/astdb.h"
- #include "asterisk/app.h"
- #ifdef TEST_FRAMEWORK
- #include "asterisk/test.h"
- #include "asterisk/event.h"
- #include <semaphore.h>
- #endif
- /*** DOCUMENTATION
- <function name="PRESENCE_STATE" language="en_US">
- <synopsis>
- Get or Set a presence state.
- </synopsis>
- <syntax>
- <parameter name="provider" required="true">
- <para>The provider of the presence, such as <literal>CustomPresence</literal></para>
- </parameter>
- <parameter name="field" required="true">
- <para>Which field of the presence state information is wanted.</para>
- <optionlist>
- <option name="value">
- <para>The current presence, such as <literal>away</literal></para>
- </option>
- <option name="subtype">
- <para>Further information about the current presence</para>
- </option>
- <option name="message">
- <para>A custom message that may indicate further details about the presence</para>
- </option>
- </optionlist>
- </parameter>
- <parameter name="options" required="false">
- <optionlist>
- <option name="e">
- <para>Base-64 encode the data.</para>
- </option>
- </optionlist>
- </parameter>
- </syntax>
- <description>
- <para>The PRESENCE_STATE function can be used to retrieve the presence from any
- presence provider. For example:</para>
- <para>NoOp(SIP/mypeer has presence ${PRESENCE_STATE(SIP/mypeer,value)})</para>
- <para>NoOp(Conference number 1234 has presence message ${PRESENCE_STATE(MeetMe:1234,message)})</para>
- <para>The PRESENCE_STATE function can also be used to set custom presence state from
- the dialplan. The <literal>CustomPresence:</literal> prefix must be used. For example:</para>
- <para>Set(PRESENCE_STATE(CustomPresence:lamp1)=away,temporary,Out to lunch)</para>
- <para>Set(PRESENCE_STATE(CustomPresence:lamp2)=dnd,,Trying to get work done)</para>
- <para>You can subscribe to the status of a custom presence state using a hint in
- the dialplan:</para>
- <para>exten => 1234,hint,CustomPresence:lamp1</para>
- <para>The possible values for both uses of this function are:</para>
- <para>not_set | unavailable | available | away | xa | chat | dnd</para>
- </description>
- </function>
- ***/
- static const char astdb_family[] = "CustomPresence";
- static int presence_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
- {
- int state;
- char *message = NULL;
- char *subtype = NULL;
- char *parse;
- int base64encode = 0;
- AST_DECLARE_APP_ARGS(args,
- AST_APP_ARG(provider);
- AST_APP_ARG(field);
- AST_APP_ARG(options);
- );
- if (ast_strlen_zero(data)) {
- ast_log(LOG_WARNING, "PRESENCE_STATE reading requires an argument \n");
- return -1;
- }
- parse = ast_strdupa(data);
- AST_STANDARD_APP_ARGS(args, parse);
- if (ast_strlen_zero(args.provider) || ast_strlen_zero(args.field)) {
- ast_log(LOG_WARNING, "PRESENCE_STATE reading requires both presence provider and presence field arguments. \n");
- return -1;
- }
- state = ast_presence_state(args.provider, &subtype, &message);
- if (state < 0) {
- ast_log(LOG_WARNING, "PRESENCE_STATE unknown \n");
- return -1;
- }
- if (!(ast_strlen_zero(args.options)) && (strchr(args.options, 'e'))) {
- base64encode = 1;
- }
- if (!ast_strlen_zero(subtype) && !strcasecmp(args.field, "subtype")) {
- if (base64encode) {
- ast_base64encode(buf, (unsigned char *) subtype, strlen(subtype), len);
- } else {
- ast_copy_string(buf, subtype, len);
- }
- } else if (!ast_strlen_zero(message) && !strcasecmp(args.field, "message")) {
- if (base64encode) {
- ast_base64encode(buf, (unsigned char *) message, strlen(message), len);
- } else {
- ast_copy_string(buf, message, len);
- }
- } else if (!strcasecmp(args.field, "value")) {
- ast_copy_string(buf, ast_presence_state2str(state), len);
- }
- ast_free(message);
- ast_free(subtype);
- return 0;
- }
- static int parse_data(char *data, int *state, char **subtype, char **message, char **options)
- {
- char *state_str;
- /* data syntax is state,subtype,message,options */
- *subtype = "";
- *message = "";
- *options = "";
- state_str = strsep(&data, ",");
- if (ast_strlen_zero(state_str)) {
- return -1; /* state is required */
- }
- *state = ast_presence_state_val(state_str);
- /* not a valid state */
- if (*state < 0) {
- ast_log(LOG_WARNING, "Unknown presence state value %s\n", state_str);
- return -1;
- }
- if (!(*subtype = strsep(&data,","))) {
- *subtype = "";
- return 0;
- }
- if (!(*message = strsep(&data, ","))) {
- *message = "";
- return 0;
- }
- if (!(*options = strsep(&data, ","))) {
- *options = "";
- return 0;
- }
- if (!ast_strlen_zero(*options) && !(strchr(*options, 'e'))) {
- ast_log(LOG_NOTICE, "Invalid options '%s'\n", *options);
- return -1;
- }
- return 0;
- }
- static int presence_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
- {
- size_t len = strlen("CustomPresence:");
- char *tmp = data;
- char *args = ast_strdupa(value);
- int state;
- char *options, *message, *subtype;
- if (strncasecmp(data, "CustomPresence:", len)) {
- ast_log(LOG_WARNING, "The PRESENCE_STATE function can only set CustomPresence: presence providers.\n");
- return -1;
- }
- data += len;
- if (ast_strlen_zero(data)) {
- ast_log(LOG_WARNING, "PRESENCE_STATE function called with no custom device name!\n");
- return -1;
- }
- if (parse_data(args, &state, &subtype, &message, &options)) {
- ast_log(LOG_WARNING, "Invalid arguments to PRESENCE_STATE\n");
- return -1;
- }
- ast_db_put(astdb_family, data, value);
- ast_presence_state_changed(tmp);
- return 0;
- }
- static enum ast_presence_state custom_presence_callback(const char *data, char **subtype, char **message)
- {
- char buf[1301] = "";
- int state;
- char *_options;
- char *_message;
- char *_subtype;
- ast_db_get(astdb_family, data, buf, sizeof(buf));
- if (parse_data(buf, &state, &_subtype, &_message, &_options)) {
- return -1;
- }
- if ((strchr(_options, 'e'))) {
- char tmp[1301];
- if (ast_strlen_zero(_subtype)) {
- *subtype = NULL;
- } else {
- memset(tmp, 0, sizeof(tmp));
- ast_base64decode((unsigned char *) tmp, _subtype, sizeof(tmp) - 1);
- *subtype = ast_strdup(tmp);
- }
- if (ast_strlen_zero(_message)) {
- *message = NULL;
- } else {
- memset(tmp, 0, sizeof(tmp));
- ast_base64decode((unsigned char *) tmp, _message, sizeof(tmp) - 1);
- *message = ast_strdup(tmp);
- }
- } else {
- *subtype = ast_strlen_zero(_subtype) ? NULL : ast_strdup(_subtype);
- *message = ast_strlen_zero(_message) ? NULL : ast_strdup(_message);
- }
- return state;
- }
- static struct ast_custom_function presence_function = {
- .name = "PRESENCE_STATE",
- .read = presence_read,
- .write = presence_write,
- };
- #ifdef TEST_FRAMEWORK
- struct test_string {
- char *parse_string;
- struct {
- int value;
- const char *subtype;
- const char *message;
- const char *options;
- } outputs;
- };
- AST_TEST_DEFINE(test_valid_parse_data)
- {
- int i;
- int state;
- char *subtype;
- char *message;
- char *options;
- enum ast_test_result_state res = AST_TEST_PASS;
-
- struct test_string tests [] = {
- { "away",
- { AST_PRESENCE_AWAY,
- "",
- "",
- ""
- }
- },
- { "not_set",
- { AST_PRESENCE_NOT_SET,
- "",
- "",
- ""
- }
- },
- { "unavailable",
- { AST_PRESENCE_UNAVAILABLE,
- "",
- "",
- ""
- }
- },
- { "available",
- { AST_PRESENCE_AVAILABLE,
- "",
- "",
- ""
- }
- },
- { "xa",
- { AST_PRESENCE_XA,
- "",
- "",
- ""
- }
- },
- { "chat",
- { AST_PRESENCE_CHAT,
- "",
- "",
- ""
- }
- },
- { "dnd",
- { AST_PRESENCE_DND,
- "",
- "",
- ""
- }
- },
- { "away,down the hall",
- { AST_PRESENCE_AWAY,
- "down the hall",
- "",
- ""
- }
- },
- { "away,down the hall,Quarterly financial meeting",
- { AST_PRESENCE_AWAY,
- "down the hall",
- "Quarterly financial meeting",
- ""
- }
- },
- { "away,,Quarterly financial meeting",
- { AST_PRESENCE_AWAY,
- "",
- "Quarterly financial meeting",
- ""
- }
- },
- { "away,,,e",
- { AST_PRESENCE_AWAY,
- "",
- "",
- "e",
- }
- },
- { "away,down the hall,,e",
- { AST_PRESENCE_AWAY,
- "down the hall",
- "",
- "e"
- }
- },
- { "away,down the hall,Quarterly financial meeting,e",
- { AST_PRESENCE_AWAY,
- "down the hall",
- "Quarterly financial meeting",
- "e"
- }
- },
- { "away,,Quarterly financial meeting,e",
- { AST_PRESENCE_AWAY,
- "",
- "Quarterly financial meeting",
- "e"
- }
- }
- };
- switch (cmd) {
- case TEST_INIT:
- info->name = "parse_valid_presence_data";
- info->category = "/funcs/func_presence";
- info->summary = "PRESENCESTATE parsing test";
- info->description =
- "Ensure that parsing function accepts proper values, and gives proper outputs";
- return AST_TEST_NOT_RUN;
- case TEST_EXECUTE:
- break;
- }
- for (i = 0; i < ARRAY_LEN(tests); ++i) {
- int parse_result;
- char *parse_string = ast_strdup(tests[i].parse_string);
- if (!parse_string) {
- res = AST_TEST_FAIL;
- break;
- }
- parse_result = parse_data(parse_string, &state, &subtype, &message, &options);
- if (parse_result == -1) {
- res = AST_TEST_FAIL;
- ast_free(parse_string);
- break;
- }
- if (tests[i].outputs.value != state ||
- strcmp(tests[i].outputs.subtype, subtype) ||
- strcmp(tests[i].outputs.message, message) ||
- strcmp(tests[i].outputs.options, options)) {
- res = AST_TEST_FAIL;
- ast_free(parse_string);
- break;
- }
- ast_free(parse_string);
- }
- return res;
- }
- AST_TEST_DEFINE(test_invalid_parse_data)
- {
- int i;
- int state;
- char *subtype;
- char *message;
- char *options;
- enum ast_test_result_state res = AST_TEST_PASS;
- char *tests[] = {
- "",
- "bored",
- "away,,,i",
- /* XXX The following actually is parsed correctly. Should that
- * be changed?
- * "away,,,,e",
- */
- };
- switch (cmd) {
- case TEST_INIT:
- info->name = "parse_invalid_presence_data";
- info->category = "/funcs/func_presence";
- info->summary = "PRESENCESTATE parsing test";
- info->description =
- "Ensure that parsing function rejects improper values";
- return AST_TEST_NOT_RUN;
- case TEST_EXECUTE:
- break;
- }
- for (i = 0; i < ARRAY_LEN(tests); ++i) {
- int parse_result;
- char *parse_string = ast_strdup(tests[i]);
- if (!parse_string) {
- res = AST_TEST_FAIL;
- break;
- }
- printf("parse string is %s\n", parse_string);
- parse_result = parse_data(parse_string, &state, &subtype, &message, &options);
- if (parse_result == 0) {
- res = AST_TEST_FAIL;
- ast_free(parse_string);
- break;
- }
- ast_free(parse_string);
- }
- return res;
- }
- struct test_cb_data {
- enum ast_presence_state presence;
- const char *provider;
- const char *subtype;
- const char *message;
- /* That's right. I'm using a semaphore */
- sem_t sem;
- };
- static void test_cb(const struct ast_event *event, void *userdata)
- {
- struct test_cb_data *cb_data = userdata;
- cb_data->presence = ast_event_get_ie_uint(event, AST_EVENT_IE_PRESENCE_STATE);
- cb_data->provider = ast_strdup(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_PROVIDER));
- cb_data->subtype = ast_strdup(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_SUBTYPE));
- cb_data->message = ast_strdup(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_MESSAGE));
- sem_post(&cb_data->sem);
- ast_log(LOG_NOTICE, "Callback called\n");
- }
- /* XXX This test could probably stand to be moved since
- * it does not test func_presencestate but rather code in
- * presencestate.h and presencestate.c. However, the convenience
- * of presence_write() makes this a nice location for this test.
- */
- AST_TEST_DEFINE(test_presence_state_change)
- {
- struct ast_event_sub *test_sub;
- struct test_cb_data *cb_data;
- switch (cmd) {
- case TEST_INIT:
- info->name = "test_presence_state_change";
- info->category = "/funcs/func_presence";
- info->summary = "presence state change subscription";
- info->description =
- "Ensure that presence state changes are communicated to subscribers";
- return AST_TEST_NOT_RUN;
- case TEST_EXECUTE:
- break;
- }
- cb_data = ast_calloc(1, sizeof(*cb_data));
- if (!cb_data) {
- return AST_TEST_FAIL;
- }
- if (!(test_sub = ast_event_subscribe(AST_EVENT_PRESENCE_STATE,
- test_cb, "Test presence state callbacks", cb_data, AST_EVENT_IE_END))) {
- return AST_TEST_FAIL;
- }
- if (sem_init(&cb_data->sem, 0, 0)) {
- return AST_TEST_FAIL;
- }
- presence_write(NULL, "PRESENCESTATE", "CustomPresence:Bob", "away,down the hall,Quarterly financial meeting");
- sem_wait(&cb_data->sem);
- if (cb_data->presence != AST_PRESENCE_AWAY ||
- strcmp(cb_data->provider, "CustomPresence:Bob") ||
- strcmp(cb_data->subtype, "down the hall") ||
- strcmp(cb_data->message, "Quarterly financial meeting")) {
- return AST_TEST_FAIL;
- }
- ast_free((char *)cb_data->provider);
- ast_free((char *)cb_data->subtype);
- ast_free((char *)cb_data->message);
- ast_free((char *)cb_data);
- return AST_TEST_PASS;
- }
- #endif
- static int unload_module(void)
- {
- int res = 0;
- res |= ast_custom_function_unregister(&presence_function);
- res |= ast_presence_state_prov_del("CustomPresence");
- #ifdef TEST_FRAMEWORK
- AST_TEST_UNREGISTER(test_valid_parse_data);
- AST_TEST_UNREGISTER(test_invalid_parse_data);
- AST_TEST_UNREGISTER(test_presence_state_change);
- #endif
- return res;
- }
- static int load_module(void)
- {
- int res = 0;
- res |= ast_custom_function_register(&presence_function);
- res |= ast_presence_state_prov_add("CustomPresence", custom_presence_callback);
- #ifdef TEST_FRAMEWORK
- AST_TEST_REGISTER(test_valid_parse_data);
- AST_TEST_REGISTER(test_invalid_parse_data);
- AST_TEST_REGISTER(test_presence_state_change);
- #endif
- return res;
- }
- AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Gets or sets a presence state in the dialplan",
- .load = load_module,
- .unload = unload_module,
- .load_pri = AST_MODPRI_DEVSTATE_PROVIDER,
- );
|