func_presencestate.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. /*
  2. * Asterisk -- An open source telephony toolkit.
  3. *
  4. * Copyright (C) 2011-2012, 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. /*! \file
  19. *
  20. * \brief Custom presence provider
  21. * \ingroup functions
  22. */
  23. #include "asterisk.h"
  24. ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  25. #include "asterisk/module.h"
  26. #include "asterisk/channel.h"
  27. #include "asterisk/pbx.h"
  28. #include "asterisk/utils.h"
  29. #include "asterisk/linkedlists.h"
  30. #include "asterisk/presencestate.h"
  31. #include "asterisk/cli.h"
  32. #include "asterisk/astdb.h"
  33. #include "asterisk/app.h"
  34. #ifdef TEST_FRAMEWORK
  35. #include "asterisk/test.h"
  36. #include "asterisk/event.h"
  37. #include <semaphore.h>
  38. #endif
  39. /*** DOCUMENTATION
  40. <function name="PRESENCE_STATE" language="en_US">
  41. <synopsis>
  42. Get or Set a presence state.
  43. </synopsis>
  44. <syntax>
  45. <parameter name="provider" required="true">
  46. <para>The provider of the presence, such as <literal>CustomPresence</literal></para>
  47. </parameter>
  48. <parameter name="field" required="true">
  49. <para>Which field of the presence state information is wanted.</para>
  50. <optionlist>
  51. <option name="value">
  52. <para>The current presence, such as <literal>away</literal></para>
  53. </option>
  54. <option name="subtype">
  55. <para>Further information about the current presence</para>
  56. </option>
  57. <option name="message">
  58. <para>A custom message that may indicate further details about the presence</para>
  59. </option>
  60. </optionlist>
  61. </parameter>
  62. <parameter name="options" required="false">
  63. <optionlist>
  64. <option name="e">
  65. <para>Base-64 encode the data.</para>
  66. </option>
  67. </optionlist>
  68. </parameter>
  69. </syntax>
  70. <description>
  71. <para>The PRESENCE_STATE function can be used to retrieve the presence from any
  72. presence provider. For example:</para>
  73. <para>NoOp(SIP/mypeer has presence ${PRESENCE_STATE(SIP/mypeer,value)})</para>
  74. <para>NoOp(Conference number 1234 has presence message ${PRESENCE_STATE(MeetMe:1234,message)})</para>
  75. <para>The PRESENCE_STATE function can also be used to set custom presence state from
  76. the dialplan. The <literal>CustomPresence:</literal> prefix must be used. For example:</para>
  77. <para>Set(PRESENCE_STATE(CustomPresence:lamp1)=away,temporary,Out to lunch)</para>
  78. <para>Set(PRESENCE_STATE(CustomPresence:lamp2)=dnd,,Trying to get work done)</para>
  79. <para>You can subscribe to the status of a custom presence state using a hint in
  80. the dialplan:</para>
  81. <para>exten => 1234,hint,CustomPresence:lamp1</para>
  82. <para>The possible values for both uses of this function are:</para>
  83. <para>not_set | unavailable | available | away | xa | chat | dnd</para>
  84. </description>
  85. </function>
  86. ***/
  87. static const char astdb_family[] = "CustomPresence";
  88. static int presence_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
  89. {
  90. int state;
  91. char *message = NULL;
  92. char *subtype = NULL;
  93. char *parse;
  94. int base64encode = 0;
  95. AST_DECLARE_APP_ARGS(args,
  96. AST_APP_ARG(provider);
  97. AST_APP_ARG(field);
  98. AST_APP_ARG(options);
  99. );
  100. if (ast_strlen_zero(data)) {
  101. ast_log(LOG_WARNING, "PRESENCE_STATE reading requires an argument \n");
  102. return -1;
  103. }
  104. parse = ast_strdupa(data);
  105. AST_STANDARD_APP_ARGS(args, parse);
  106. if (ast_strlen_zero(args.provider) || ast_strlen_zero(args.field)) {
  107. ast_log(LOG_WARNING, "PRESENCE_STATE reading requires both presence provider and presence field arguments. \n");
  108. return -1;
  109. }
  110. state = ast_presence_state(args.provider, &subtype, &message);
  111. if (state < 0) {
  112. ast_log(LOG_WARNING, "PRESENCE_STATE unknown \n");
  113. return -1;
  114. }
  115. if (!(ast_strlen_zero(args.options)) && (strchr(args.options, 'e'))) {
  116. base64encode = 1;
  117. }
  118. if (!ast_strlen_zero(subtype) && !strcasecmp(args.field, "subtype")) {
  119. if (base64encode) {
  120. ast_base64encode(buf, (unsigned char *) subtype, strlen(subtype), len);
  121. } else {
  122. ast_copy_string(buf, subtype, len);
  123. }
  124. } else if (!ast_strlen_zero(message) && !strcasecmp(args.field, "message")) {
  125. if (base64encode) {
  126. ast_base64encode(buf, (unsigned char *) message, strlen(message), len);
  127. } else {
  128. ast_copy_string(buf, message, len);
  129. }
  130. } else if (!strcasecmp(args.field, "value")) {
  131. ast_copy_string(buf, ast_presence_state2str(state), len);
  132. }
  133. ast_free(message);
  134. ast_free(subtype);
  135. return 0;
  136. }
  137. static int parse_data(char *data, int *state, char **subtype, char **message, char **options)
  138. {
  139. char *state_str;
  140. /* data syntax is state,subtype,message,options */
  141. *subtype = "";
  142. *message = "";
  143. *options = "";
  144. state_str = strsep(&data, ",");
  145. if (ast_strlen_zero(state_str)) {
  146. return -1; /* state is required */
  147. }
  148. *state = ast_presence_state_val(state_str);
  149. /* not a valid state */
  150. if (*state < 0) {
  151. ast_log(LOG_WARNING, "Unknown presence state value %s\n", state_str);
  152. return -1;
  153. }
  154. if (!(*subtype = strsep(&data,","))) {
  155. *subtype = "";
  156. return 0;
  157. }
  158. if (!(*message = strsep(&data, ","))) {
  159. *message = "";
  160. return 0;
  161. }
  162. if (!(*options = strsep(&data, ","))) {
  163. *options = "";
  164. return 0;
  165. }
  166. if (!ast_strlen_zero(*options) && !(strchr(*options, 'e'))) {
  167. ast_log(LOG_NOTICE, "Invalid options '%s'\n", *options);
  168. return -1;
  169. }
  170. return 0;
  171. }
  172. static int presence_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
  173. {
  174. size_t len = strlen("CustomPresence:");
  175. char *tmp = data;
  176. char *args = ast_strdupa(value);
  177. int state;
  178. char *options, *message, *subtype;
  179. if (strncasecmp(data, "CustomPresence:", len)) {
  180. ast_log(LOG_WARNING, "The PRESENCE_STATE function can only set CustomPresence: presence providers.\n");
  181. return -1;
  182. }
  183. data += len;
  184. if (ast_strlen_zero(data)) {
  185. ast_log(LOG_WARNING, "PRESENCE_STATE function called with no custom device name!\n");
  186. return -1;
  187. }
  188. if (parse_data(args, &state, &subtype, &message, &options)) {
  189. ast_log(LOG_WARNING, "Invalid arguments to PRESENCE_STATE\n");
  190. return -1;
  191. }
  192. ast_db_put(astdb_family, data, value);
  193. ast_presence_state_changed(tmp);
  194. return 0;
  195. }
  196. static enum ast_presence_state custom_presence_callback(const char *data, char **subtype, char **message)
  197. {
  198. char buf[1301] = "";
  199. int state;
  200. char *_options;
  201. char *_message;
  202. char *_subtype;
  203. ast_db_get(astdb_family, data, buf, sizeof(buf));
  204. if (parse_data(buf, &state, &_subtype, &_message, &_options)) {
  205. return -1;
  206. }
  207. if ((strchr(_options, 'e'))) {
  208. char tmp[1301];
  209. if (ast_strlen_zero(_subtype)) {
  210. *subtype = NULL;
  211. } else {
  212. memset(tmp, 0, sizeof(tmp));
  213. ast_base64decode((unsigned char *) tmp, _subtype, sizeof(tmp) - 1);
  214. *subtype = ast_strdup(tmp);
  215. }
  216. if (ast_strlen_zero(_message)) {
  217. *message = NULL;
  218. } else {
  219. memset(tmp, 0, sizeof(tmp));
  220. ast_base64decode((unsigned char *) tmp, _message, sizeof(tmp) - 1);
  221. *message = ast_strdup(tmp);
  222. }
  223. } else {
  224. *subtype = ast_strlen_zero(_subtype) ? NULL : ast_strdup(_subtype);
  225. *message = ast_strlen_zero(_message) ? NULL : ast_strdup(_message);
  226. }
  227. return state;
  228. }
  229. static struct ast_custom_function presence_function = {
  230. .name = "PRESENCE_STATE",
  231. .read = presence_read,
  232. .write = presence_write,
  233. };
  234. #ifdef TEST_FRAMEWORK
  235. struct test_string {
  236. char *parse_string;
  237. struct {
  238. int value;
  239. const char *subtype;
  240. const char *message;
  241. const char *options;
  242. } outputs;
  243. };
  244. AST_TEST_DEFINE(test_valid_parse_data)
  245. {
  246. int i;
  247. int state;
  248. char *subtype;
  249. char *message;
  250. char *options;
  251. enum ast_test_result_state res = AST_TEST_PASS;
  252. struct test_string tests [] = {
  253. { "away",
  254. { AST_PRESENCE_AWAY,
  255. "",
  256. "",
  257. ""
  258. }
  259. },
  260. { "not_set",
  261. { AST_PRESENCE_NOT_SET,
  262. "",
  263. "",
  264. ""
  265. }
  266. },
  267. { "unavailable",
  268. { AST_PRESENCE_UNAVAILABLE,
  269. "",
  270. "",
  271. ""
  272. }
  273. },
  274. { "available",
  275. { AST_PRESENCE_AVAILABLE,
  276. "",
  277. "",
  278. ""
  279. }
  280. },
  281. { "xa",
  282. { AST_PRESENCE_XA,
  283. "",
  284. "",
  285. ""
  286. }
  287. },
  288. { "chat",
  289. { AST_PRESENCE_CHAT,
  290. "",
  291. "",
  292. ""
  293. }
  294. },
  295. { "dnd",
  296. { AST_PRESENCE_DND,
  297. "",
  298. "",
  299. ""
  300. }
  301. },
  302. { "away,down the hall",
  303. { AST_PRESENCE_AWAY,
  304. "down the hall",
  305. "",
  306. ""
  307. }
  308. },
  309. { "away,down the hall,Quarterly financial meeting",
  310. { AST_PRESENCE_AWAY,
  311. "down the hall",
  312. "Quarterly financial meeting",
  313. ""
  314. }
  315. },
  316. { "away,,Quarterly financial meeting",
  317. { AST_PRESENCE_AWAY,
  318. "",
  319. "Quarterly financial meeting",
  320. ""
  321. }
  322. },
  323. { "away,,,e",
  324. { AST_PRESENCE_AWAY,
  325. "",
  326. "",
  327. "e",
  328. }
  329. },
  330. { "away,down the hall,,e",
  331. { AST_PRESENCE_AWAY,
  332. "down the hall",
  333. "",
  334. "e"
  335. }
  336. },
  337. { "away,down the hall,Quarterly financial meeting,e",
  338. { AST_PRESENCE_AWAY,
  339. "down the hall",
  340. "Quarterly financial meeting",
  341. "e"
  342. }
  343. },
  344. { "away,,Quarterly financial meeting,e",
  345. { AST_PRESENCE_AWAY,
  346. "",
  347. "Quarterly financial meeting",
  348. "e"
  349. }
  350. }
  351. };
  352. switch (cmd) {
  353. case TEST_INIT:
  354. info->name = "parse_valid_presence_data";
  355. info->category = "/funcs/func_presence";
  356. info->summary = "PRESENCESTATE parsing test";
  357. info->description =
  358. "Ensure that parsing function accepts proper values, and gives proper outputs";
  359. return AST_TEST_NOT_RUN;
  360. case TEST_EXECUTE:
  361. break;
  362. }
  363. for (i = 0; i < ARRAY_LEN(tests); ++i) {
  364. int parse_result;
  365. char *parse_string = ast_strdup(tests[i].parse_string);
  366. if (!parse_string) {
  367. res = AST_TEST_FAIL;
  368. break;
  369. }
  370. parse_result = parse_data(parse_string, &state, &subtype, &message, &options);
  371. if (parse_result == -1) {
  372. res = AST_TEST_FAIL;
  373. ast_free(parse_string);
  374. break;
  375. }
  376. if (tests[i].outputs.value != state ||
  377. strcmp(tests[i].outputs.subtype, subtype) ||
  378. strcmp(tests[i].outputs.message, message) ||
  379. strcmp(tests[i].outputs.options, options)) {
  380. res = AST_TEST_FAIL;
  381. ast_free(parse_string);
  382. break;
  383. }
  384. ast_free(parse_string);
  385. }
  386. return res;
  387. }
  388. AST_TEST_DEFINE(test_invalid_parse_data)
  389. {
  390. int i;
  391. int state;
  392. char *subtype;
  393. char *message;
  394. char *options;
  395. enum ast_test_result_state res = AST_TEST_PASS;
  396. char *tests[] = {
  397. "",
  398. "bored",
  399. "away,,,i",
  400. /* XXX The following actually is parsed correctly. Should that
  401. * be changed?
  402. * "away,,,,e",
  403. */
  404. };
  405. switch (cmd) {
  406. case TEST_INIT:
  407. info->name = "parse_invalid_presence_data";
  408. info->category = "/funcs/func_presence";
  409. info->summary = "PRESENCESTATE parsing test";
  410. info->description =
  411. "Ensure that parsing function rejects improper values";
  412. return AST_TEST_NOT_RUN;
  413. case TEST_EXECUTE:
  414. break;
  415. }
  416. for (i = 0; i < ARRAY_LEN(tests); ++i) {
  417. int parse_result;
  418. char *parse_string = ast_strdup(tests[i]);
  419. if (!parse_string) {
  420. res = AST_TEST_FAIL;
  421. break;
  422. }
  423. printf("parse string is %s\n", parse_string);
  424. parse_result = parse_data(parse_string, &state, &subtype, &message, &options);
  425. if (parse_result == 0) {
  426. res = AST_TEST_FAIL;
  427. ast_free(parse_string);
  428. break;
  429. }
  430. ast_free(parse_string);
  431. }
  432. return res;
  433. }
  434. struct test_cb_data {
  435. enum ast_presence_state presence;
  436. const char *provider;
  437. const char *subtype;
  438. const char *message;
  439. /* That's right. I'm using a semaphore */
  440. sem_t sem;
  441. };
  442. static void test_cb(const struct ast_event *event, void *userdata)
  443. {
  444. struct test_cb_data *cb_data = userdata;
  445. cb_data->presence = ast_event_get_ie_uint(event, AST_EVENT_IE_PRESENCE_STATE);
  446. cb_data->provider = ast_strdup(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_PROVIDER));
  447. cb_data->subtype = ast_strdup(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_SUBTYPE));
  448. cb_data->message = ast_strdup(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_MESSAGE));
  449. sem_post(&cb_data->sem);
  450. ast_log(LOG_NOTICE, "Callback called\n");
  451. }
  452. /* XXX This test could probably stand to be moved since
  453. * it does not test func_presencestate but rather code in
  454. * presencestate.h and presencestate.c. However, the convenience
  455. * of presence_write() makes this a nice location for this test.
  456. */
  457. AST_TEST_DEFINE(test_presence_state_change)
  458. {
  459. struct ast_event_sub *test_sub;
  460. struct test_cb_data *cb_data;
  461. switch (cmd) {
  462. case TEST_INIT:
  463. info->name = "test_presence_state_change";
  464. info->category = "/funcs/func_presence";
  465. info->summary = "presence state change subscription";
  466. info->description =
  467. "Ensure that presence state changes are communicated to subscribers";
  468. return AST_TEST_NOT_RUN;
  469. case TEST_EXECUTE:
  470. break;
  471. }
  472. cb_data = ast_calloc(1, sizeof(*cb_data));
  473. if (!cb_data) {
  474. return AST_TEST_FAIL;
  475. }
  476. if (!(test_sub = ast_event_subscribe(AST_EVENT_PRESENCE_STATE,
  477. test_cb, "Test presence state callbacks", cb_data, AST_EVENT_IE_END))) {
  478. return AST_TEST_FAIL;
  479. }
  480. if (sem_init(&cb_data->sem, 0, 0)) {
  481. return AST_TEST_FAIL;
  482. }
  483. presence_write(NULL, "PRESENCESTATE", "CustomPresence:Bob", "away,down the hall,Quarterly financial meeting");
  484. sem_wait(&cb_data->sem);
  485. if (cb_data->presence != AST_PRESENCE_AWAY ||
  486. strcmp(cb_data->provider, "CustomPresence:Bob") ||
  487. strcmp(cb_data->subtype, "down the hall") ||
  488. strcmp(cb_data->message, "Quarterly financial meeting")) {
  489. return AST_TEST_FAIL;
  490. }
  491. ast_free((char *)cb_data->provider);
  492. ast_free((char *)cb_data->subtype);
  493. ast_free((char *)cb_data->message);
  494. ast_free((char *)cb_data);
  495. return AST_TEST_PASS;
  496. }
  497. #endif
  498. static int unload_module(void)
  499. {
  500. int res = 0;
  501. res |= ast_custom_function_unregister(&presence_function);
  502. res |= ast_presence_state_prov_del("CustomPresence");
  503. #ifdef TEST_FRAMEWORK
  504. AST_TEST_UNREGISTER(test_valid_parse_data);
  505. AST_TEST_UNREGISTER(test_invalid_parse_data);
  506. AST_TEST_UNREGISTER(test_presence_state_change);
  507. #endif
  508. return res;
  509. }
  510. static int load_module(void)
  511. {
  512. int res = 0;
  513. res |= ast_custom_function_register(&presence_function);
  514. res |= ast_presence_state_prov_add("CustomPresence", custom_presence_callback);
  515. #ifdef TEST_FRAMEWORK
  516. AST_TEST_REGISTER(test_valid_parse_data);
  517. AST_TEST_REGISTER(test_invalid_parse_data);
  518. AST_TEST_REGISTER(test_presence_state_change);
  519. #endif
  520. return res;
  521. }
  522. AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Gets or sets a presence state in the dialplan",
  523. .load = load_module,
  524. .unload = unload_module,
  525. .load_pri = AST_MODPRI_DEVSTATE_PROVIDER,
  526. );