res_stasis_snoop.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. /*
  2. * Asterisk -- An open source telephony toolkit.
  3. *
  4. * Copyright (C) 2013, Digium, Inc.
  5. *
  6. * Joshua Colp <jcolp@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 Stasis application snoop control support.
  21. *
  22. * \author Joshua Colp <jcolp@digium.com>
  23. */
  24. /*** MODULEINFO
  25. <depend type="module">res_stasis</depend>
  26. <support_level>core</support_level>
  27. ***/
  28. #include "asterisk.h"
  29. ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  30. #include "asterisk/module.h"
  31. #include "asterisk/stasis_app_impl.h"
  32. #include "asterisk/stasis_app_snoop.h"
  33. #include "asterisk/audiohook.h"
  34. #include "asterisk/pbx.h"
  35. #include "asterisk/timing.h"
  36. #include "asterisk/stasis_channels.h"
  37. #include "asterisk/json.h"
  38. #include "asterisk/format_cache.h"
  39. /*! \brief The interval (in milliseconds) that the Snoop timer is triggered, also controls length of audio within frames */
  40. #define SNOOP_INTERVAL 20
  41. /*! \brief Index used to keep Snoop channel names unique */
  42. static unsigned int chan_idx;
  43. /*! \brief Structure which contains all of the snoop information */
  44. struct stasis_app_snoop {
  45. /*! \brief Timer used for waking up Stasis thread */
  46. struct ast_timer *timer;
  47. /*! \brief Audiohook used to spy on the channel */
  48. struct ast_audiohook spy;
  49. /*! \brief Direction for spying */
  50. enum ast_audiohook_direction spy_direction;
  51. /*! \brief Number of samples to be read in when spying */
  52. unsigned int spy_samples;
  53. /*! \brief Format in use by the spy audiohook */
  54. struct ast_format *spy_format;
  55. /*! \brief Audiohook used to whisper on the channel */
  56. struct ast_audiohook whisper;
  57. /*! \brief Direction for whispering */
  58. enum ast_audiohook_direction whisper_direction;
  59. /*! \brief Stasis application and arguments */
  60. struct ast_str *app;
  61. /*! \brief Snoop channel */
  62. struct ast_channel *chan;
  63. /*! \brief Whether the spy capability is active or not */
  64. unsigned int spy_active:1;
  65. /*! \brief Whether the whisper capability is active or not */
  66. unsigned int whisper_active:1;
  67. /*! \brief Uniqueid of the channel this snoop is snooping on */
  68. char uniqueid[AST_MAX_UNIQUEID];
  69. };
  70. /*! \brief Destructor for snoop structure */
  71. static void snoop_destroy(void *obj)
  72. {
  73. struct stasis_app_snoop *snoop = obj;
  74. if (snoop->timer) {
  75. ast_timer_close(snoop->timer);
  76. }
  77. if (snoop->spy_active) {
  78. ast_audiohook_destroy(&snoop->spy);
  79. }
  80. if (snoop->whisper_active) {
  81. ast_audiohook_destroy(&snoop->whisper);
  82. }
  83. ast_free(snoop->app);
  84. ast_channel_cleanup(snoop->chan);
  85. }
  86. /*! \internal
  87. * \brief Publish the chanspy message over Stasis-Core
  88. * \param snoop The snoop structure
  89. * \start start If non-zero, the spying is starting. Otherwise, the spyer is
  90. * finishing
  91. */
  92. static void publish_chanspy_message(struct stasis_app_snoop *snoop, int start)
  93. {
  94. RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
  95. RAII_VAR(struct ast_multi_channel_blob *, payload, NULL, ao2_cleanup);
  96. RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
  97. RAII_VAR(struct ast_channel_snapshot *, snoop_snapshot, NULL, ao2_cleanup);
  98. RAII_VAR(struct ast_channel_snapshot *, spyee_snapshot, NULL, ao2_cleanup);
  99. struct stasis_message_type *type = start ? ast_channel_chanspy_start_type(): ast_channel_chanspy_stop_type();
  100. blob = ast_json_null();
  101. if (!blob || !type) {
  102. return;
  103. }
  104. payload = ast_multi_channel_blob_create(blob);
  105. if (!payload) {
  106. return;
  107. }
  108. snoop_snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(snoop->chan));
  109. if (!snoop_snapshot) {
  110. return;
  111. }
  112. ast_multi_channel_blob_add_channel(payload, "spyer_channel", snoop_snapshot);
  113. spyee_snapshot = ast_channel_snapshot_get_latest(snoop->uniqueid);
  114. if (spyee_snapshot) {
  115. ast_multi_channel_blob_add_channel(payload, "spyee_channel", spyee_snapshot);
  116. }
  117. message = stasis_message_create(type, payload);
  118. if (!message) {
  119. return;
  120. }
  121. stasis_publish(ast_channel_topic(snoop->chan), message);
  122. }
  123. /*! \brief Callback function for writing to a Snoop whisper audiohook */
  124. static int snoop_write(struct ast_channel *chan, struct ast_frame *frame)
  125. {
  126. struct stasis_app_snoop *snoop = ast_channel_tech_pvt(chan);
  127. if (!snoop->whisper_active) {
  128. return 0;
  129. }
  130. ast_audiohook_lock(&snoop->whisper);
  131. if (snoop->whisper_direction == AST_AUDIOHOOK_DIRECTION_BOTH) {
  132. ast_audiohook_write_frame(&snoop->whisper, AST_AUDIOHOOK_DIRECTION_READ, frame);
  133. ast_audiohook_write_frame(&snoop->whisper, AST_AUDIOHOOK_DIRECTION_WRITE, frame);
  134. } else {
  135. ast_audiohook_write_frame(&snoop->whisper, snoop->whisper_direction, frame);
  136. }
  137. ast_audiohook_unlock(&snoop->whisper);
  138. return 0;
  139. }
  140. /*! \brief Callback function for reading from a Snoop channel */
  141. static struct ast_frame *snoop_read(struct ast_channel *chan)
  142. {
  143. struct stasis_app_snoop *snoop = ast_channel_tech_pvt(chan);
  144. struct ast_frame *frame = NULL;
  145. /* If we fail to ack the timer OR if any active audiohooks are done hangup */
  146. if ((ast_timer_ack(snoop->timer, 1) < 0) ||
  147. (snoop->spy_active && snoop->spy.status != AST_AUDIOHOOK_STATUS_RUNNING) ||
  148. (snoop->whisper_active && snoop->whisper.status != AST_AUDIOHOOK_STATUS_RUNNING)) {
  149. return NULL;
  150. }
  151. /* Only get audio from the spy audiohook if it is active */
  152. if (snoop->spy_active) {
  153. ast_audiohook_lock(&snoop->spy);
  154. frame = ast_audiohook_read_frame(&snoop->spy, snoop->spy_samples, snoop->spy_direction, snoop->spy_format);
  155. ast_audiohook_unlock(&snoop->spy);
  156. }
  157. return frame ? frame : &ast_null_frame;
  158. }
  159. /*! \brief Callback function for hanging up a Snoop channel */
  160. static int snoop_hangup(struct ast_channel *chan)
  161. {
  162. struct stasis_app_snoop *snoop = ast_channel_tech_pvt(chan);
  163. if (snoop->spy_active) {
  164. ast_audiohook_lock(&snoop->spy);
  165. ast_audiohook_detach(&snoop->spy);
  166. ast_audiohook_unlock(&snoop->spy);
  167. }
  168. if (snoop->whisper_active) {
  169. ast_audiohook_lock(&snoop->whisper);
  170. ast_audiohook_detach(&snoop->whisper);
  171. ast_audiohook_unlock(&snoop->whisper);
  172. }
  173. publish_chanspy_message(snoop, 0);
  174. ao2_cleanup(snoop);
  175. ast_channel_tech_pvt_set(chan, NULL);
  176. return 0;
  177. }
  178. static int snoop_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
  179. {
  180. struct stasis_app_snoop *snoop = ast_channel_tech_pvt(oldchan);
  181. if (snoop->chan != oldchan) {
  182. return -1;
  183. }
  184. ast_channel_unref(snoop->chan);
  185. ast_channel_ref(newchan);
  186. snoop->chan = newchan;
  187. return 0;
  188. }
  189. /*! \brief Channel interface declaration */
  190. static struct ast_channel_tech snoop_tech = {
  191. .type = "Snoop",
  192. .description = "Snoop Channel Driver",
  193. .write = snoop_write,
  194. .read = snoop_read,
  195. .hangup = snoop_hangup,
  196. .fixup = snoop_fixup,
  197. };
  198. /*! \brief Thread used for running the Stasis application */
  199. static void *snoop_stasis_thread(void *obj)
  200. {
  201. RAII_VAR(struct stasis_app_snoop *, snoop, obj, ao2_cleanup);
  202. struct ast_app *stasis = pbx_findapp("Stasis");
  203. if (!stasis) {
  204. ast_hangup(snoop->chan);
  205. return NULL;
  206. }
  207. pbx_exec(snoop->chan, stasis, ast_str_buffer(snoop->app));
  208. ast_hangup(snoop->chan);
  209. return NULL;
  210. }
  211. /*! \brief Internal helper function which sets up and attaches a snoop audiohook */
  212. static int snoop_setup_audiohook(struct ast_channel *chan, enum ast_audiohook_type type, enum stasis_app_snoop_direction requested_direction,
  213. enum ast_audiohook_direction *direction, struct ast_audiohook *audiohook)
  214. {
  215. ast_audiohook_init(audiohook, type, "Snoop", 0);
  216. if (requested_direction == STASIS_SNOOP_DIRECTION_OUT) {
  217. *direction = AST_AUDIOHOOK_DIRECTION_WRITE;
  218. } else if (requested_direction == STASIS_SNOOP_DIRECTION_IN) {
  219. *direction = AST_AUDIOHOOK_DIRECTION_READ;
  220. } else if (requested_direction == STASIS_SNOOP_DIRECTION_BOTH) {
  221. *direction = AST_AUDIOHOOK_DIRECTION_BOTH;
  222. } else {
  223. return -1;
  224. }
  225. return ast_audiohook_attach(chan, audiohook);
  226. }
  227. /*! \brief Helper function which gets the format for a Snoop channel based on the channel being snooped on */
  228. static void snoop_determine_format(struct ast_channel *chan, struct stasis_app_snoop *snoop)
  229. {
  230. SCOPED_CHANNELLOCK(lock, chan);
  231. unsigned int rate = MAX(ast_format_get_sample_rate(ast_channel_rawwriteformat(chan)),
  232. ast_format_get_sample_rate(ast_channel_rawreadformat(chan)));
  233. snoop->spy_format = ast_format_cache_get_slin_by_rate(rate);
  234. }
  235. struct ast_channel *stasis_app_control_snoop(struct ast_channel *chan,
  236. enum stasis_app_snoop_direction spy, enum stasis_app_snoop_direction whisper,
  237. const char *app, const char *app_args, const char *snoop_id)
  238. {
  239. RAII_VAR(struct stasis_app_snoop *, snoop, NULL, ao2_cleanup);
  240. struct ast_format_cap *caps;
  241. pthread_t thread;
  242. struct ast_assigned_ids assignedids = {
  243. .uniqueid = snoop_id,
  244. };
  245. if (spy == STASIS_SNOOP_DIRECTION_NONE &&
  246. whisper == STASIS_SNOOP_DIRECTION_NONE) {
  247. return NULL;
  248. }
  249. snoop = ao2_alloc_options(sizeof(*snoop), snoop_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
  250. if (!snoop) {
  251. return NULL;
  252. }
  253. /* Allocate a buffer to store the Stasis application and arguments in */
  254. snoop->app = ast_str_create(64);
  255. if (!snoop->app) {
  256. return NULL;
  257. }
  258. ast_str_set(&snoop->app, 0, "%s", app);
  259. if (!ast_strlen_zero(app_args)) {
  260. ast_str_append(&snoop->app, 0, ",%s", app_args);
  261. }
  262. /* Set up a timer for the Snoop channel so it wakes up at a specific interval */
  263. snoop->timer = ast_timer_open();
  264. if (!snoop->timer) {
  265. return NULL;
  266. }
  267. ast_timer_set_rate(snoop->timer, 1000 / SNOOP_INTERVAL);
  268. /* Determine which signed linear format should be used */
  269. snoop_determine_format(chan, snoop);
  270. /* Allocate a Snoop channel and set up various parameters */
  271. snoop->chan = ast_channel_alloc(1, AST_STATE_UP, "", "", "", "", "", &assignedids, NULL, 0, "Snoop/%s-%08x", ast_channel_uniqueid(chan),
  272. (unsigned)ast_atomic_fetchadd_int((int *)&chan_idx, +1));
  273. if (!snoop->chan) {
  274. return NULL;
  275. }
  276. ast_copy_string(snoop->uniqueid, ast_channel_uniqueid(chan), sizeof(snoop->uniqueid));
  277. /* To keep the channel valid on the Snoop structure until it is destroyed we bump the ref up here */
  278. ast_channel_ref(snoop->chan);
  279. ast_channel_tech_set(snoop->chan, &snoop_tech);
  280. ao2_ref(snoop, +1);
  281. ast_channel_tech_pvt_set(snoop->chan, snoop);
  282. ast_channel_set_fd(snoop->chan, 0, ast_timer_fd(snoop->timer));
  283. /* The format on the Snoop channel will be this signed linear format, and it will never change */
  284. caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
  285. if (!caps) {
  286. ast_channel_unlock(snoop->chan);
  287. ast_hangup(snoop->chan);
  288. return NULL;
  289. }
  290. ast_format_cap_append(caps, snoop->spy_format, 0);
  291. ast_channel_nativeformats_set(snoop->chan, caps);
  292. ao2_ref(caps, -1);
  293. ast_channel_set_writeformat(snoop->chan, snoop->spy_format);
  294. ast_channel_set_rawwriteformat(snoop->chan, snoop->spy_format);
  295. ast_channel_set_readformat(snoop->chan, snoop->spy_format);
  296. ast_channel_set_rawreadformat(snoop->chan, snoop->spy_format);
  297. ast_channel_unlock(snoop->chan);
  298. if (spy != STASIS_SNOOP_DIRECTION_NONE) {
  299. if (snoop_setup_audiohook(chan, AST_AUDIOHOOK_TYPE_SPY, spy, &snoop->spy_direction, &snoop->spy)) {
  300. ast_hangup(snoop->chan);
  301. return NULL;
  302. }
  303. snoop->spy_samples = ast_format_get_sample_rate(snoop->spy_format) / (1000 / SNOOP_INTERVAL);
  304. snoop->spy_active = 1;
  305. }
  306. /* If whispering is enabled set up the audiohook */
  307. if (whisper != STASIS_SNOOP_DIRECTION_NONE) {
  308. if (snoop_setup_audiohook(chan, AST_AUDIOHOOK_TYPE_WHISPER, whisper, &snoop->whisper_direction, &snoop->whisper)) {
  309. ast_hangup(snoop->chan);
  310. return NULL;
  311. }
  312. snoop->whisper_active = 1;
  313. }
  314. /* Create the thread which services the Snoop channel */
  315. ao2_ref(snoop, +1);
  316. if (ast_pthread_create_detached_background(&thread, NULL, snoop_stasis_thread, snoop)) {
  317. ao2_cleanup(snoop);
  318. /* No other thread is servicing this channel so we can immediately hang it up */
  319. ast_hangup(snoop->chan);
  320. return NULL;
  321. }
  322. publish_chanspy_message(snoop, 1);
  323. /* The caller of this has a reference as well */
  324. return ast_channel_ref(snoop->chan);
  325. }
  326. static int load_module(void)
  327. {
  328. return AST_MODULE_LOAD_SUCCESS;
  329. }
  330. static int unload_module(void)
  331. {
  332. return 0;
  333. }
  334. AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Stasis application snoop support",
  335. .support_level = AST_MODULE_SUPPORT_CORE,
  336. .load = load_module,
  337. .unload = unload_module,
  338. .nonoptreq = "res_stasis");