Asterisk - The Open Source Telephony Project  21.4.1
test_mwi.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2019, Sangoma Technologies Corporation
5  *
6  * Kevin Harwell <kharwell@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 /*** MODULEINFO
20  <depend>TEST_FRAMEWORK</depend>
21  <support_level>core</support_level>
22  ***/
23 
24 #include "asterisk.h"
25 
26 #include "asterisk/astobj2.h"
27 #include "asterisk/conversions.h"
28 #include "asterisk/module.h"
29 #include "asterisk/mwi.h"
30 #include "asterisk/stasis.h"
31 #include "asterisk/test.h"
32 
33 #define test_category "/mwi/"
34 
35 #define MAILBOX_PREFIX "test~" /* Hopefully sufficiently unlikely */
36 #define MAILBOX_COUNT 500
37 #define MAILBOX_SIZE 32
38 
41 
42 /*!
43  * For testing purposes each subscribed mailbox is a number. This value is
44  * the summation of all mailboxes.
45  */
46 static size_t sum_total;
47 
48 /*! Test variable that tracks the running total of mailboxes */
49 static size_t running_total;
50 
51 /*! This value is set to check if MWI data is zero before publishing */
52 static int expect_zero;
53 
54 static int num_to_mailbox(char *mailbox, size_t size, size_t num)
55 {
56  if (snprintf(mailbox, 10, MAILBOX_PREFIX "%zu", num) == -1) {
57  ast_log(LOG_ERROR, "Unable to convert mailbox to string\n");
58  return -1;
59  }
60 
61  return 0;
62 }
63 
64 static int mailbox_to_num(const char *mailbox, size_t *num)
65 {
66  uintmax_t tmp;
67  const char *p = strchr(mailbox, '~');
68 
69  if (!p) {
70  ast_log(LOG_ERROR, "Prefix separator '~' not found in '%s'\n", mailbox);
71  return -1;
72  }
73 
74  if (ast_str_to_umax(++p, &tmp)) {
75  ast_log(LOG_ERROR, "Unable to convert mailbox '%s' to numeric\n", mailbox);
76  return -1;
77  }
78  *num = (size_t) tmp;
79 
80  return 0;
81 }
82 
83 static int validate_data(struct ast_mwi_state *mwi_state)
84 {
85  size_t num;
86  size_t val;
87 
88  if (mailbox_to_num(mwi_state->uniqueid, &num)) {
89  return -1;
90  }
91 
92  running_total += num;
93 
94  val = expect_zero ? 0 : num;
95 
96  if (mwi_state->urgent_msgs != val || mwi_state->new_msgs != val ||
97  mwi_state->old_msgs != val) {
98  ast_log(LOG_ERROR, "Unexpected MWI state data for '%s', %d != %zu\n",
99  mwi_state->uniqueid, mwi_state->urgent_msgs, val);
100  return -1;
101  }
102 
103  return num;
104 }
105 
106 static void handle_validate(const char *mailbox, struct ast_mwi_subscriber *sub)
107 {
108  struct ast_mwi_state *mwi_state = ast_mwi_subscriber_data(sub);
109 
110  if (ast_begins_with(mwi_state->uniqueid, MAILBOX_PREFIX)) {
111  validate_data(mwi_state);
112  }
113 
114  ao2_cleanup(mwi_state);
115 }
116 
117 struct ast_mwi_observer mwi_observer = {
118  .on_subscribe = handle_validate,
119  .on_unsubscribe = handle_validate
120 };
121 
122 static void mwi_type_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message)
123 {
124  /* No op since we are not really testing stasis topic handling here */
125 }
126 
127 static int subscriptions_destroy(struct subscriptions *subs)
128 {
129  running_total = expect_zero = 0;
130 
132  AST_VECTOR_FREE(subs);
133 
134  ast_mwi_remove_observer(&mwi_observer);
135 
136  if (running_total != sum_total) {
137  ast_log(LOG_ERROR, "Failed to destroy all MWI subscriptions: running=%zu, sum=%zu\n",
138  running_total, sum_total);
139  return -1;
140  }
141 
142  return 0;
143 }
144 
145 static int subscriptions_create(struct subscriptions *subs)
146 {
147  size_t i;
148 
149  if (ast_mwi_add_observer(&mwi_observer) ||
150  AST_VECTOR_INIT(subs, MAILBOX_COUNT)) {
151  return -1;
152  }
153 
154  sum_total = running_total = 0;
155  expect_zero = 1;
156 
157  for (i = 0; i < MAILBOX_COUNT; ++i) {
158  struct ast_mwi_subscriber *sub;
159  char mailbox[MAILBOX_SIZE];
160 
161  if (num_to_mailbox(mailbox, MAILBOX_SIZE, i)) {
162  break;
163  }
164 
165  sub = ast_mwi_subscribe_pool(mailbox, mwi_type_cb, NULL);
166  if (!sub) {
167  ast_log(LOG_ERROR, "Failed to create a MWI subscriber for mailbox '%s'\n", mailbox);
168  break;
169  }
170 
171  if (AST_VECTOR_APPEND(subs, sub)) {
172  ast_log(LOG_ERROR, "Failed to add to MWI sub to vector for mailbox '%s'\n", mailbox);
173  ao2_ref(sub, -1);
174  break;
175  }
176 
177  sum_total += i;
178  }
179 
180  if (i != MAILBOX_COUNT || running_total != sum_total) {
181  ast_log(LOG_ERROR, "Failed to create all MWI subscriptions: running=%zu, sum=%zu\n",
182  running_total, sum_total);
183  subscriptions_destroy(subs);
184  return -1;
185  }
186 
187  return 0;
188 }
189 
190 static int publishers_destroy(struct publishers *pubs)
191 {
192  size_t i;
193 
194  if (pubs) {
195  /* Remove explicit publishers */
196  AST_VECTOR_CALLBACK_VOID(pubs, ao2_cleanup);
197  AST_VECTOR_FREE(pubs);
198  return 0;
199  }
200 
201  for (i = 0; i < MAILBOX_COUNT; ++i) {
202  char mailbox[MAILBOX_SIZE];
203 
204  /* Remove implicit publishers */
205  if (num_to_mailbox(mailbox, MAILBOX_SIZE, i)) {
206  return -1;
207  }
208 
209  ast_delete_mwi_state(mailbox, NULL);
210  }
211 
212  return 0;
213 }
214 
215 static int publishers_create(struct publishers *pubs)
216 {
217  size_t i;
218 
219  if (AST_VECTOR_INIT(pubs, MAILBOX_COUNT)) {
220  return -1;
221  }
222 
223  for (i = 0; i < MAILBOX_COUNT; ++i) {
224  struct ast_mwi_publisher *pub;
225  char mailbox[MAILBOX_SIZE];
226 
227  if (num_to_mailbox(mailbox, MAILBOX_SIZE, i)) {
228  break;
229  }
230 
231  /* Create the MWI publisher */
232  pub = ast_mwi_add_publisher(mailbox);
233  if (!pub) {
234  ast_log(LOG_ERROR, "Failed to create an MWI publisher for mailbox '%s'\n", mailbox);
235  break;
236  }
237 
238  if (AST_VECTOR_APPEND(pubs, pub)) {
239  ast_log(LOG_ERROR, "Failed to add to an MWI publisher to vector for mailbox '%s'\n", mailbox);
240  ao2_ref(pub, -1);
241  break;
242  }
243  }
244 
245  if (i != MAILBOX_COUNT) {
246  ast_log(LOG_ERROR, "Failed to create all MWI publishers: count=%zu\n", i);
247  publishers_destroy(pubs);
248  return -1;
249  }
250 
251  return 0;
252 }
253 
254 static int implicit_publish_cb(struct ast_mwi_state *mwi_state, void *data)
255 {
256  size_t num;
257 
258  if (!ast_begins_with(mwi_state->uniqueid, MAILBOX_PREFIX)) {
259  /* Ignore any mailboxes not prefixed */
260  return 0;
261  }
262 
263  num = validate_data(mwi_state);
264  if (num < 0) {
265  return CMP_STOP;
266  }
267 
268  ast_mwi_publish_by_mailbox(mwi_state->uniqueid, NULL, num, num, num, NULL, NULL);
269 
270  return 0;
271 }
272 
273 static int explicit_publish_cb(struct ast_mwi_state *mwi_state, void *data)
274 {
275  struct publishers *pubs = data;
276  struct ast_mwi_publisher *pub;
277  size_t num;
278 
279  if (!ast_begins_with(mwi_state->uniqueid, MAILBOX_PREFIX)) {
280  /* Ignore any mailboxes not prefixed */
281  return 0;
282  }
283 
284  num = validate_data(mwi_state);
285  if (num < 0) {
286  return CMP_STOP;
287  }
288 
289  if (mailbox_to_num(mwi_state->uniqueid, &num)) {
290  return CMP_STOP;
291  }
292 
293  /* Mailbox number will always be the index */
294  pub = AST_VECTOR_GET(pubs, num);
295 
296  if (!pub) {
297  ast_log(LOG_ERROR, "Unable to locate MWI publisher for mailbox '%s'\n", mwi_state->uniqueid);
298  return CMP_STOP;
299  }
300 
301  ast_mwi_publish(pub, num, num, num, NULL, NULL);
302 
303  return 0;
304 }
305 
306 static int publish(on_mwi_state cb, void *user_data)
307 {
308  /* First time there is no state data */
309  expect_zero = 1;
310 
311  running_total = 0;
312  ast_mwi_state_callback_all(cb, user_data);
313 
314  if (running_total != sum_total) {
315  ast_log(LOG_ERROR, "Failed MWI state callback (1): running=%zu, sum=%zu\n",
316  running_total, sum_total);
317  return -1;
318  }
319 
320  /* Second time check valid state data exists */
321  running_total = expect_zero = 0;
322  ast_mwi_state_callback_all(cb, user_data);
323 
324  if (running_total != sum_total) {
325  ast_log(LOG_ERROR, "Failed MWI state callback (2): running=%zu, sum=%zu\n",
326  running_total, sum_total);
327  return -1;
328  }
329 
330  return 0;
331 }
332 
333 AST_TEST_DEFINE(implicit_publish)
334 {
335  struct subscriptions subs;
336  int rc = AST_TEST_PASS;
337 
338  switch (cmd) {
339  case TEST_INIT:
340  info->name = __func__;
341  info->category = test_category;
342  info->summary = "Test implicit publishing of MWI state";
343  info->description = info->summary;
344  return AST_TEST_NOT_RUN;
345  case TEST_EXECUTE:
346  break;
347  }
348 
349  ast_test_validate(test, !subscriptions_create(&subs));
350 
351  ast_test_validate_cleanup(test, !publish(implicit_publish_cb, NULL),
352  rc, cleanup);
353 
354 cleanup:
355  if (subscriptions_destroy(&subs) || publishers_destroy(NULL)) {
356  return AST_TEST_FAIL;
357  }
358 
359  return rc;
360 }
361 
362 AST_TEST_DEFINE(explicit_publish)
363 {
364  struct subscriptions subs;
365  struct publishers pubs;
366  int rc = AST_TEST_PASS;
367 
368  switch (cmd) {
369  case TEST_INIT:
370  info->name = __func__;
371  info->category = test_category;
372  info->summary = "Test explicit publishing of MWI state";
373  info->description = info->summary;
374  return AST_TEST_NOT_RUN;
375  case TEST_EXECUTE:
376  break;
377  }
378 
379  ast_test_validate(test, !subscriptions_create(&subs));
380  ast_test_validate_cleanup(test, !publishers_create(&pubs), rc, cleanup);
381 
382  ast_test_validate_cleanup(test, !publish(explicit_publish_cb, &pubs),
383  rc, cleanup);
384 
385 cleanup:
386  if (subscriptions_destroy(&subs) || publishers_destroy(&pubs)) {
387  return AST_TEST_FAIL;
388  }
389 
390  return rc;
391 }
392 
393 static int unload_module(void)
394 {
395  AST_TEST_UNREGISTER(implicit_publish);
396  AST_TEST_UNREGISTER(explicit_publish);
397 
398  return 0;
399 }
400 
401 static int load_module(void)
402 {
403  AST_TEST_REGISTER(implicit_publish);
404  AST_TEST_REGISTER(explicit_publish);
405 
407 }
408 
409 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "MWI testing");
#define AST_VECTOR_FREE(vec)
Deallocates this vector.
Definition: vector.h:174
Asterisk main include file. File version handling, generic pbx functions.
int urgent_msgs
Definition: mwi.h:464
int ast_str_to_umax(const char *str, uintmax_t *res)
Convert the given string to an unsigned max size integer.
Definition: conversions.c:119
Stasis Message Bus API. See Stasis Message Bus API for detailed documentation.
#define AST_VECTOR_APPEND(vec, elem)
Append an element to a vector, growing the vector if needed.
Definition: vector.h:256
Test Framework API.
struct ast_mwi_state * ast_mwi_subscriber_data(struct ast_mwi_subscriber *sub)
Retrieves the state data object associated with the MWI subscriber.
Definition: mwi.c:269
int ast_mwi_add_observer(struct ast_mwi_observer *observer)
Add an observer to receive MWI state related events.
Definition: mwi.c:301
void(* on_subscribe)(const char *mailbox, struct ast_mwi_subscriber *sub)
Raised when MWI is being subscribed.
Definition: mwi.h:255
static void cleanup(void)
Clean up any old apps that we don't need any more.
Definition: res_stasis.c:327
#define AST_VECTOR_INIT(vec, size)
Initialize a vector.
Definition: vector.h:113
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition: astobj2.h:459
Conversion utility functions.
int(* on_mwi_state)(struct ast_mwi_state *mwi_state, void *data)
The delegate called for each managed mailbox state.
Definition: mwi.h:301
#define AST_VECTOR(name, type)
Define a vector structure.
Definition: vector.h:44
int new_msgs
Definition: mwi.h:459
int ast_mwi_publish(struct ast_mwi_publisher *publisher, int urgent_msgs, int new_msgs, int old_msgs, const char *channel_id, struct ast_eid *eid)
Publish MWI for the given mailbox.
Definition: mwi.c:358
void ast_mwi_remove_observer(struct ast_mwi_observer *observer)
Remove an MWI state observer.
Definition: mwi.c:307
#define ast_delete_mwi_state(mailbox, context)
Delete MWI state cached by stasis.
Definition: mwi.h:431
void ast_mwi_state_callback_all(on_mwi_state handler, void *data)
For each managed mailbox call the given handler.
Definition: mwi.c:338
void * ast_mwi_unsubscribe_and_join(struct ast_mwi_subscriber *sub)
Unsubscribe from the stasis topic, block until the final message is received, and then unsubscribe fr...
Definition: mwi.c:259
const ast_string_field uniqueid
Definition: mwi.h:458
#define AST_VECTOR_GET(vec, idx)
Get an element from a vector.
Definition: vector.h:680
struct ast_mwi_publisher * ast_mwi_add_publisher(const char *mailbox)
Add an MWI state publisher to the mailbox.
Definition: mwi.c:295
int old_msgs
Definition: mwi.h:460
MWI state event interface.
Definition: mwi.h:248
Asterisk MWI API.
static int force_inline attribute_pure ast_begins_with(const char *str, const char *prefix)
Checks whether a string begins with another.
Definition: strings.h:97
#define AST_TEST_DEFINE(hdr)
Definition: test.h:126
int ast_mwi_publish_by_mailbox(const char *mailbox, const char *context, int urgent_msgs, int new_msgs, int old_msgs, const char *channel_id, struct ast_eid *eid)
Publish MWI for the given mailbox.
Definition: mwi.c:375
The structure that contains MWI state.
Definition: mwi.h:455
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
Asterisk module definitions.
struct ast_mwi_subscriber * ast_mwi_subscribe_pool(const char *mailbox, stasis_subscription_cb callback, void *data)
Add an MWI state subscriber, and stasis subscription to the mailbox.
Definition: mwi.c:235
#define AST_VECTOR_CALLBACK_VOID(vec, callback,...)
Execute a callback on every element in a vector disregarding callback return.
Definition: vector.h:862