Asterisk - The Open Source Telephony Project GIT-master-f36a736
func_json.c
Go to the documentation of this file.
1/*
2 * Asterisk -- An open source telephony toolkit.
3 *
4 * Copyright (C) 2021-2022, Naveen Albert
5 *
6 * See http://www.asterisk.org for more information about
7 * the Asterisk project. Please do not directly contact
8 * any of the maintainers of this project for assistance;
9 * the project provides a web site, mailing lists and IRC
10 * channels for your use.
11 *
12 * This program is free software, distributed under the terms of
13 * the GNU General Public License Version 2. See the LICENSE file
14 * at the top of the source tree.
15 */
16
17/*! \file
18 *
19 * \brief JSON parsing function
20 *
21 * \author Naveen Albert <asterisk@phreaknet.org>
22 *
23 * \ingroup functions
24 */
25
26/*** MODULEINFO
27 <support_level>extended</support_level>
28 ***/
29
30#include "asterisk.h"
31
32#include "asterisk/module.h"
33#include "asterisk/channel.h"
34#include "asterisk/pbx.h"
35#include "asterisk/utils.h"
36#include "asterisk/test.h"
37#include "asterisk/app.h"
39
40/*** DOCUMENTATION
41 <function name="JSON_DECODE" language="en_US">
42 <since>
43 <version>16.24.0</version>
44 <version>18.10.0</version>
45 <version>19.2.0</version>
46 </since>
47 <synopsis>
48 Returns the string value of a JSON object key from a string containing a
49 JSON array.
50 </synopsis>
51 <syntax>
52 <parameter name="varname" required="true">
53 <para>The name of the variable containing the JSON string to parse.</para>
54 </parameter>
55 <parameter name="item" required="true">
56 <para>The name of the key whose value to return.</para>
57 <para>Multiple keys can be listed separated by a hierarchy delimeter, which will recursively index into a nested JSON string to retrieve a specific subkey's value.</para>
58 </parameter>
59 <parameter name="separator" required="false">
60 <para>A single character that delimits a key hierarchy for nested indexing. Default is a period (.)</para>
61 <para>This value should not appear in the key or hierarchy of keys itself, except to delimit the hierarchy of keys.</para>
62 </parameter>
63 <parameter name="options" required="no">
64 <optionlist>
65 <option name="c">
66 <para>For keys that reference a JSON array, return
67 the number of items in the array.</para>
68 <para>This option has no effect on any other type
69 of value.</para>
70 </option>
71 </optionlist>
72 </parameter>
73 </syntax>
74 <description>
75 <para>The JSON_DECODE function retrieves the value of the given variable name
76 and parses it as JSON, returning the value at a specified key. If the key cannot
77 be found, an empty string is returned.</para>
78 </description>
79 <see-also>
80 <ref type="function">CURL</ref>
81 </see-also>
82 </function>
83 ***/
84
86
88 OPT_COUNT = (1 << 0),
89};
90
93});
94
95#define MAX_JSON_STACK 32
96
97static int parse_node(char **key, char *currentkey, char *nestchar, int count, struct ast_json *json, char *buf, size_t len, int *depth)
98{
99 const char *result = NULL;
100 char *previouskey;
101 struct ast_json *jsonval = json;
102
103 /* Prevent a huge JSON string from blowing the stack. */
104 (*depth)++;
105 if (*depth > MAX_JSON_STACK) {
106 ast_log(LOG_WARNING, "Max JSON stack (%d) exceeded\n", MAX_JSON_STACK);
107 return -1;
108 }
109
110 snprintf(buf, len, "%s", ""); /* clear the buffer from previous round if necessary */
111 if (!json) { /* no error or warning should be thrown */
112 ast_debug(1, "Could not find key '%s' in parsed JSON\n", currentkey);
113 return -1;
114 }
115
116 switch(ast_json_typeof(jsonval)) {
117 unsigned long int size;
118 int r;
119 double d;
120
121 case AST_JSON_STRING:
122 result = ast_json_string_get(jsonval);
123 ast_debug(1, "Got JSON string: %s\n", result);
125 break;
126 case AST_JSON_INTEGER:
127 r = ast_json_integer_get(jsonval);
128 ast_debug(1, "Got JSON integer: %d\n", r);
129 snprintf(buf, len, "%d", r); /* the snprintf below is mutually exclusive with this one */
130 break;
131 case AST_JSON_REAL:
132 d = ast_json_real_get(jsonval);
133 ast_debug(1, "Got JSON real: %.17g\n", d);
134 snprintf(buf, len, "%.17g", d); /* the snprintf below is mutually exclusive with this one */
135 break;
136 case AST_JSON_ARRAY:
137 ast_debug(1, "Got JSON array\n");
138 previouskey = currentkey;
139 currentkey = strsep(key, nestchar); /* retrieve the desired index */
140 size = ast_json_array_size(jsonval);
141 ast_debug(1, "Parsed JSON array of size %lu, key: %s\n", size, currentkey);
142 if (!currentkey) { /* this is the end, so just dump the array */
143 if (count) {
144 ast_debug(1, "No key on which to index in the array, so returning count: %lu\n", size);
145 snprintf(buf, len, "%lu", size);
146 return 0;
147 } else {
148 char *result2 = ast_json_dump_string(jsonval);
149 ast_debug(1, "No key on which to index in the array, so dumping '%s' array\n", previouskey);
150 ast_copy_string(buf, result2, len);
151 ast_json_free(result2);
152 }
153 } else if (ast_str_to_int(currentkey, &r) || r < 0) {
154 ast_debug(1, "Requested index '%s' is not numeric or is invalid\n", currentkey);
155 } else if (r >= size) {
156 ast_debug(1, "Requested index '%d' does not exist in parsed array\n", r);
157 } else {
158 ast_debug(1, "Recursing on index %d in array\n", r);
159 if (parse_node(key, currentkey, nestchar, count, ast_json_array_get(jsonval, r), buf, len, depth)) { /* recurse on this node */
160 return -1;
161 }
162 }
163 break;
164 case AST_JSON_TRUE:
165 case AST_JSON_FALSE:
166 r = ast_json_is_true(jsonval);
167 ast_debug(1, "Got JSON %s for key %s\n", r ? "true" : "false", currentkey);
168 snprintf(buf, len, "%d", r); /* the snprintf below is mutually exclusive with this one */
169 break;
170 case AST_JSON_NULL:
171 ast_debug(1, "Got JSON null for key %s\n", currentkey);
172 break;
173 case AST_JSON_OBJECT:
174 ast_debug(1, "Got generic JSON object for key %s\n", currentkey);
175 previouskey = currentkey;
176 currentkey = strsep(key, nestchar); /* retrieve the desired index */
177 if (!currentkey) { /* this is the end, so just dump the object */
178 char *result2 = ast_json_dump_string(jsonval);
179 ast_copy_string(buf, result2, len);
180 ast_json_free(result2);
181 } else {
182 ast_debug(1, "Recursing on object (key was '%s' and is now '%s')\n", previouskey, currentkey);
183 if (parse_node(key, currentkey, nestchar, count, ast_json_object_get(jsonval, currentkey), buf, len, depth)) { /* recurse on this node */
184 return -1;
185 }
186 }
187 break;
188 default:
189 ast_log(LOG_WARNING, "Got unsuported type %d\n", ast_json_typeof(jsonval));
190 return -1;
191 }
192 return 0;
193}
194
195static int json_decode_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
196{
197 int count = 0;
198 struct ast_flags flags = {0};
199 struct ast_json *json = NULL, *start = NULL;
200 char *nestchar = "."; /* default delimeter for nesting key indexing is . */
201 int index, res, depth = 0;
202
204 AST_APP_ARG(varname);
205 AST_APP_ARG(key);
206 AST_APP_ARG(nestchar);
208 );
209 char *varsubst, *key, *currentkey, *nextkey, *firstkey, *tmp;
211
213
214 if (!ast_strlen_zero(args.options)) {
215 ast_app_parse_options(json_options, &flags, NULL, args.options);
216 if (ast_test_flag(&flags, OPT_COUNT)) {
217 count = 1;
218 }
219 }
220
221 if (ast_strlen_zero(args.varname)) {
222 ast_log(LOG_WARNING, "%s requires a variable name\n", cmd);
223 return -1;
224 }
225
226 if (ast_strlen_zero(args.key)) {
227 ast_log(LOG_WARNING, "%s requires a key\n", cmd);
228 return -1;
229 }
230
231 key = ast_strdupa(args.key);
232 if (!ast_strlen_zero(args.nestchar)) {
233 int seplen = strlen(args.nestchar);
234 if (seplen != 1) {
235 ast_log(LOG_WARNING, "Nesting separator '%s' has length %d and is invalid (must be a single character)\n", args.nestchar, seplen);
236 } else {
237 nestchar = args.nestchar;
238 }
239 }
240
241 varsubst = ast_alloca(strlen(args.varname) + 4); /* +4 for ${} and null terminator */
242 if (!varsubst) {
243 ast_log(LOG_ERROR, "Failed to allocate string\n");
244 return -1;
245 }
246 sprintf(varsubst, "${%s}", args.varname); /* safe, because of the above allocation */
247 ast_str_substitute_variables(&str, 0, chan, varsubst);
248
249 ast_debug(1, "Parsing JSON using nesting delimeter '%s'\n", nestchar);
250
251 if (ast_str_strlen(str) == 0) {
252 ast_debug(1, "Variable '%s' contains no data, nothing to search!\n", args.varname);
253 return -1; /* empty json string */
254 }
255
256 /* allow for multiple key nesting */
257 currentkey = key;
258 firstkey = ast_strdupa(currentkey);
259 tmp = strstr(firstkey, nestchar);
260 if (tmp) {
261 *tmp = '\0';
262 }
263
264 /* parse a string as JSON */
265 ast_debug(1, "Parsing JSON: %s (key: '%s')\n", ast_str_buffer(str), currentkey);
266 if (ast_strlen_zero(currentkey)) {
267 ast_debug(1, "Empty JSON key\n");
268 return -1;
269 }
270 if (ast_str_strlen(str) == 0) {
271 ast_debug(1, "JSON node '%s', contains no data, nothing to search!\n", currentkey);
272 return -1; /* empty json string */
273 }
274
275 json = ast_json_load_str(str, NULL);
276 if (!json) {
277 ast_log(LOG_WARNING, "Failed to parse as JSON: %s\n", ast_str_buffer(str));
278 return -1;
279 }
280
281 /* parse the JSON object, potentially recursively */
282 nextkey = strsep(&key, nestchar);
283 if (ast_json_is_object(json)) {
284 start = ast_json_object_get(json, firstkey);
285 } else {
286 if (ast_str_to_int(currentkey, &index)) {
287 ast_debug(1, "Requested index '%s' is not numeric or is invalid\n", currentkey);
288 return -1;
289 }
290 start = ast_json_array_get(json, index);
291 }
292
293 res = parse_node(&key, nextkey, nestchar, count, start, buf, len, &depth);
294 ast_json_unref(json);
295 return res;
296}
297
299 .name = "JSON_DECODE",
300 .read = json_decode_read,
301};
302
303#ifdef TEST_FRAMEWORK
304AST_TEST_DEFINE(test_JSON_DECODE)
305{
306 int i, res = AST_TEST_PASS;
307 struct ast_channel *chan; /* dummy channel */
308 struct ast_str *str; /* fancy string for holding comparing value */
309
310 const char *test_strings[][6] = {
311 {"{\"myboolean\": true, \"state\": \"USA\"}", "", "myboolean", "1"},
312 {"{\"myboolean\": false, \"state\": \"USA\"}", "", "myboolean", "0"},
313 {"{\"myreal\": 1E+2, \"state\": \"USA\"}", "", "myreal", "100"},
314 {"{\"myreal\": 1.23, \"state\": \"USA\"}", "", "myreal", "1.23"},
315 {"{\"myarray\": [[1]], \"state\": \"USA\"}", "", "myarray.0.0", "1"},
316 {"{\"myarray\": [null], \"state\": \"USA\"}", "", "myarray.0", ""},
317 {"{\"myarray\": [0, 1], \"state\": \"USA\"}", "", "myarray", "[0,1]"},
318 {"[0, 1]", "", "", ""},
319 {"[0, 1]", "", "0", "0"},
320 {"[0, 1]", "", "foo", ""},
321 {"{\"mynull\": null, \"state\": \"USA\"}", "", "mynull", ""},
322 {"{\"city\": \"Anytown\", \"state\": \"USA\"}", "", "city", "Anytown"},
323 {"{\"city\": \"Anytown\", \"state\": \"USA\"}", "", "state", "USA"},
324 {"{\"city\": \"Anytown\", \"state\": \"USA\"}", "", "blah", ""},
325 {"{\"key1\": \"123\", \"key2\": \"456\"}", "", "key1", "123"},
326 {"{\"key1\": 123, \"key2\": 456}", "", "key1", "123"},
327 {"{ \"path\": { \"to\": { \"elem\": \"someVar\" } } }", "/", "path/to/elem", "someVar"},
328 {"{ \"path\": { \"to\": { \"elem\": \"someVar\" } } }", "", "path.to.elem2", ""},
329 {"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", "/", "path/to/arr/2", ""}, /* nonexistent index */
330 {"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", "/", "path/to/arr/-1", ""}, /* bogus index */
331 {"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", "/", "path/to/arr/test", ""}, /* bogus index */
332 {"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", "", "path.to.arr.test.test2.subkey", ""}, /* bogus index */
333 {"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", ",c", "path.to.arr", "2"}, /* test count */
334 {"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", "", "path.to.arr", "[\"item0\",\"item1\"]"},
335 {"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", ".", "path.to.arr.1", "item1"},
336 {"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", "/", "path/to/arr", "[\"item0\",\"item1\"]"},
337 {"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", "/", "path/to/arr/1", "item1"},
338 {"{ \"path\": { \"to\": { \"arr\": [ {\"name\": \"John Smith\", \"phone\": \"123\"}, {\"name\": \"Jane Doe\", \"phone\": \"234\"} ] } } }", ",c", "path.to.arr.0.name", "John Smith"},
339 {"{ \"path\": { \"to\": { \"arr\": [ {\"name\": 1, \"phone\": 123}, {\"name\": 2, \"phone\": 234} ] } } }", ",c", "path.to.arr.0.name", "1"},
340 {"{ \"path\": { \"to\": { \"arr\": [ {\"name\": [ \"item11\", \"item12\" ], \"phone\": [ \"item13\", \"item14\" ]}, {\"name\": [ \"item15\", \"item16\" ], \"phone\": [ \"item17\", \"item18\" ]} ] } } }", ",c", "path.to.arr.0.name.1", "item12"},
341 {"{ \"startId\": \"foobar\", \"abcd\": { \"id\": \"abcd\", \"type\": \"EXT\" }, \"bcde\": { \"id\": \"bcde\", \"type\": \"CONDITION\" }, \"defg\": { \"id\": \"defg\", \"type\": \"EXT\" }, \"efgh\": { \"id\": \"efgh\", \"type\": \"VOICEMAIL\" } }", "", "bcde", "{\"id\":\"bcde\",\"type\":\"CONDITION\"}"},
342 };
343
344 switch (cmd) {
345 case TEST_INIT:
346 info->name = "func_JSON_DECODE";
347 info->category = "/funcs/func_json/";
348 info->summary = "Test JSON_DECODE function";
349 info->description = "Verify JSON_DECODE behavior";
350 return AST_TEST_NOT_RUN;
351 case TEST_EXECUTE:
352 break;
353 }
354
355 if (!(chan = ast_dummy_channel_alloc())) {
356 ast_test_status_update(test, "Unable to allocate dummy channel\n");
357 return AST_TEST_FAIL;
358 }
359
360 if (!(str = ast_str_create(64))) {
361 ast_test_status_update(test, "Unable to allocate dynamic string buffer\n");
363 return AST_TEST_FAIL;
364 }
365
366 for (i = 0; i < ARRAY_LEN(test_strings); i++) {
367 char tmp[512];
368
369 struct ast_var_t *var = ast_var_assign("test_string", test_strings[i][0]);
370 if (!var) {
371 ast_test_status_update(test, "Unable to allocate variable\n");
372 ast_free(str);
374 return AST_TEST_FAIL;
375 }
376
378
379 snprintf(tmp, sizeof(tmp), "${JSON_DECODE(%s,%s,%s)}", "test_string", test_strings[i][2], test_strings[i][1]);
380
382 if (strcmp(test_strings[i][3], ast_str_buffer(str))) {
383 ast_test_status_update(test, "Format string '%s' substituted to '%s' (key: %s). Expected '%s'.\n", test_strings[i][0], ast_str_buffer(str), test_strings[i][2], test_strings[i][3]);
384 res = AST_TEST_FAIL;
385 }
386 }
387
388 ast_free(str);
390
391 return res;
392}
393#endif
394
395static int unload_module(void)
396{
397 int res;
398
399#ifdef TEST_FRAMEWORK
400 AST_TEST_UNREGISTER(test_JSON_DECODE);
401#endif
403
404 return res;
405}
406
407static int load_module(void)
408{
409 int res;
410
411#ifdef TEST_FRAMEWORK
412 AST_TEST_REGISTER(test_JSON_DECODE);
413#endif
415
416 return res;
417}
418
const char * str
Definition: app_jack.c:147
#define var
Definition: ast_expr2f.c:605
Asterisk main include file. File version handling, generic pbx functions.
#define ast_alloca(size)
call __builtin_alloca to ensure we get gcc builtin semantics
Definition: astmm.h:288
#define ast_free(a)
Definition: astmm.h:180
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
#define ast_log
Definition: astobj2.c:42
static int tmp()
Definition: bt_open.c:389
static PGresult * result
Definition: cel_pgsql.c:84
General Asterisk PBX channel definitions.
struct varshead * ast_channel_varshead(struct ast_channel *chan)
struct ast_channel * ast_channel_release(struct ast_channel *chan)
Unlink and release reference to a channel.
Definition: channel.c:1603
#define ast_dummy_channel_alloc()
Create a fake channel structure.
Definition: channel.h:1328
#define ast_var_assign(name, value)
Definition: chanvars.h:40
Conversion utility functions.
int ast_str_to_int(const char *str, int *res)
Convert the given string to a signed integer.
Definition: conversions.c:44
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
static struct ast_threadstorage result_buf
Definition: func_json.c:85
static const struct ast_app_option json_options[128]
Definition: func_json.c:93
static struct ast_custom_function json_decode_function
Definition: func_json.c:298
#define MAX_JSON_STACK
Definition: func_json.c:95
AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "JSON decoding function")
static int load_module(void)
Definition: func_json.c:407
static int unload_module(void)
Definition: func_json.c:395
static int json_decode_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
Definition: func_json.c:195
static int parse_node(char **key, char *currentkey, char *nestchar, int count, struct ast_json *json, char *buf, size_t len, int *depth)
Definition: func_json.c:97
json_option_flags
Definition: func_json.c:87
@ OPT_COUNT
Definition: func_json.c:88
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
Application convenience functions, designed to give consistent look and feel to Asterisk apps.
#define AST_APP_ARG(name)
Define an application argument.
#define AST_APP_OPTIONS(holder, options...)
Declares an array of options for an application.
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application's arguments.
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the 'standard' argument separation process for an application.
#define AST_APP_OPTION(option, flagno)
Declares an application option that does not accept an argument.
int ast_app_parse_options(const struct ast_app_option *options, struct ast_flags *flags, char **args, char *optstr)
Parses a string containing application options and sets flags/arguments.
Definition: main/app.c:3066
char * strsep(char **str, const char *delims)
#define ast_debug(level,...)
Log a DEBUG message.
#define LOG_ERROR
#define LOG_WARNING
enum ast_json_type ast_json_typeof(const struct ast_json *value)
Get the type of value.
Definition: json.c:78
void ast_json_unref(struct ast_json *value)
Decrease refcount on value. If refcount reaches zero, value is freed.
Definition: json.c:73
void ast_json_free(void *p)
Asterisk's custom JSON allocator. Exposed for use by unit tests.
Definition: json.c:52
struct ast_json * ast_json_array_get(const struct ast_json *array, size_t index)
Get an element from an array.
Definition: json.c:370
#define ast_json_dump_string(root)
Encode a JSON value to a compact string.
Definition: json.h:810
struct ast_json * ast_json_load_str(const struct ast_str *input, struct ast_json_error *error)
Parse ast_str into a JSON object or array.
Definition: json.c:580
@ AST_JSON_STRING
Definition: json.h:166
@ AST_JSON_ARRAY
Definition: json.h:165
@ AST_JSON_NULL
Definition: json.h:171
@ AST_JSON_OBJECT
Definition: json.h:164
@ AST_JSON_FALSE
Definition: json.h:170
@ AST_JSON_REAL
Definition: json.h:168
@ AST_JSON_INTEGER
Definition: json.h:167
@ AST_JSON_TRUE
Definition: json.h:169
const char * ast_json_string_get(const struct ast_json *string)
Get the value of a JSON string.
Definition: json.c:283
struct ast_json * ast_json_object_get(struct ast_json *object, const char *key)
Get a field from a JSON object.
Definition: json.c:407
intmax_t ast_json_integer_get(const struct ast_json *integer)
Get the value from a JSON integer.
Definition: json.c:332
int ast_json_is_object(const struct ast_json *value)
Check if value is JSON object.
Definition: json.c:258
int ast_json_is_true(const struct ast_json *value)
Check if value is JSON true.
Definition: json.c:263
size_t ast_json_array_size(const struct ast_json *array)
Get the size of a JSON array.
Definition: json.c:366
double ast_json_real_get(const struct ast_json *real)
Get the value from a JSON real number.
Definition: json.c:347
#define AST_LIST_INSERT_HEAD(head, elm, field)
Inserts a list entry at the head of a list.
Definition: linkedlists.h:711
Asterisk module definitions.
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
def info(msg)
Core PBX routines and definitions.
void ast_str_substitute_variables(struct ast_str **buf, ssize_t maxlen, struct ast_channel *chan, const char *templ)
#define ast_custom_function_register(acf)
Register a custom function.
Definition: pbx.h:1558
int ast_custom_function_unregister(struct ast_custom_function *acf)
Unregister a custom function.
#define NULL
Definition: resample.c:96
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:761
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:659
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition: strings.h:730
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
struct ast_str * ast_str_thread_get(struct ast_threadstorage *ts, size_t init_len)
Retrieve a thread locally stored dynamic string.
Definition: strings.h:909
Main Channel structure associated with a channel.
Data structure associated with a custom dialplan function.
Definition: pbx.h:118
const char * name
Definition: pbx.h:119
Structure used to handle boolean flags.
Definition: utils.h:199
unsigned int flags
Definition: utils.h:200
Abstract JSON element (object, array, string, int, ...).
Support for dynamic strings.
Definition: strings.h:623
struct ast_var_t::@211 entries
Test Framework API.
@ TEST_INIT
Definition: test.h:200
@ TEST_EXECUTE
Definition: test.h:201
#define AST_TEST_REGISTER(cb)
Definition: test.h:127
#define ast_test_status_update(a, b, c...)
Definition: test.h:129
#define AST_TEST_UNREGISTER(cb)
Definition: test.h:128
#define AST_TEST_DEFINE(hdr)
Definition: test.h:126
@ AST_TEST_PASS
Definition: test.h:195
@ AST_TEST_FAIL
Definition: test.h:196
@ AST_TEST_NOT_RUN
Definition: test.h:194
static char * test_strings[][2]
const char * args
static struct test_options options
static struct test_val d
#define AST_THREADSTORAGE(name)
Define a thread storage variable.
Definition: threadstorage.h:86
Utility functions.
#define ast_test_flag(p, flag)
Definition: utils.h:63
#define ARRAY_LEN(a)
Definition: utils.h:666