Fix func_config list entry allocation
[asterisk/asterisk.git] / funcs / func_math.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2004 - 2006, Andy Powell
5  *
6  * Updated by Mark Spencer <markster@digium.com>
7  * Updated by Nir Simionovich <nirs@greenfieldtech.net>
8  *
9  * See http://www.asterisk.org for more information about
10  * the Asterisk project. Please do not directly contact
11  * any of the maintainers of this project for assistance;
12  * the project provides a web site, mailing lists and IRC
13  * channels for your use.
14  *
15  * This program is free software, distributed under the terms of
16  * the GNU General Public License Version 2. See the LICENSE file
17  * at the top of the source tree.
18  */
19
20 /*! \file
21  *
22  * \brief Math related dialplan function
23  *
24  * \author Andy Powell
25  * \author Mark Spencer <markster@digium.com>
26  * \author Nir Simionovich <nirs@greenfieldtech.net>
27  *
28  * \ingroup functions
29  */
30
31 /*** MODULEINFO
32         <support_level>core</support_level>
33  ***/
34
35 #include "asterisk.h"
36
37 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
38
39 #include <math.h>
40
41 #include "asterisk/module.h"
42 #include "asterisk/channel.h"
43 #include "asterisk/pbx.h"
44 #include "asterisk/utils.h"
45 #include "asterisk/app.h"
46 #include "asterisk/config.h"
47 #include "asterisk/test.h"
48
49 /*** DOCUMENTATION
50         <function name="MATH" language="en_US">
51                 <synopsis>
52                         Performs Mathematical Functions.
53                 </synopsis>
54                 <syntax>
55                         <parameter name="expression" required="true">
56                                 <para>Is of the form:
57                                 <replaceable>number1</replaceable><replaceable>op</replaceable><replaceable>number2</replaceable>
58                                 where the possible values for <replaceable>op</replaceable>
59                                 are:</para>
60                                 <para>+,-,/,*,%,&lt;&lt;,&gt;&gt;,^,AND,OR,XOR,&lt;,&gt;,&lt;=,&gt;=,== (and behave as their C equivalents)</para>
61                         </parameter>
62                         <parameter name="type">
63                                 <para>Wanted type of result:</para>
64                                 <para>f, float - float(default)</para>
65                                 <para>i, int - integer</para>
66                                 <para>h, hex - hex</para>
67                                 <para>c, char - char</para>
68                         </parameter>
69                 </syntax>
70                 <description>
71                         <para>Performs mathematical functions based on two parameters and an operator.  The returned
72                         value type is <replaceable>type</replaceable></para>
73                         <para>Example: Set(i=${MATH(123%16,int)}) - sets var i=11</para>
74                 </description>
75         </function>
76         <function name="INC" language="en_US">
77                 <synopsis>
78                         Increments the value of a variable, while returning the updated value to the dialplan
79                 </synopsis>
80                 <syntax>
81                         <parameter name="variable" required="true">
82                                 <para>
83                                 The variable name to be manipulated, without the braces.
84                                 </para>
85                         </parameter>
86                 </syntax>
87                 <description>
88                         <para>Increments the value of a variable, while returning the updated value to the dialplan</para>
89                         <para>Example: INC(MyVAR) - Increments MyVar</para>
90                         <para>Note: INC(${MyVAR}) - Is wrong, as INC expects the variable name, not its value</para>
91                 </description>
92         </function>
93         <function name="DEC" language="en_US">
94                 <synopsis>
95                         Decrements the value of a variable, while returning the updated value to the dialplan
96                 </synopsis>
97                 <syntax>
98                         <parameter name="variable" required="true">
99                                 <para>
100                                 The variable name to be manipulated, without the braces.
101                                 </para>
102                         </parameter>
103                 </syntax>
104                 <description>
105                         <para>Decrements the value of a variable, while returning the updated value to the dialplan</para>
106                         <para>Example: DEC(MyVAR) - Decrements MyVar</para>
107                         <para>Note: DEC(${MyVAR}) - Is wrong, as DEC expects the variable name, not its value</para>
108                 </description>
109         </function>
110  ***/
111
112 enum TypeOfFunctions {
113         ADDFUNCTION,
114         DIVIDEFUNCTION,
115         MULTIPLYFUNCTION,
116         SUBTRACTFUNCTION,
117         MODULUSFUNCTION,
118         POWFUNCTION,
119         SHLEFTFUNCTION,
120         SHRIGHTFUNCTION,
121         BITWISEANDFUNCTION,
122         BITWISEXORFUNCTION,
123         BITWISEORFUNCTION,
124         GTFUNCTION,
125         LTFUNCTION,
126         GTEFUNCTION,
127         LTEFUNCTION,
128         EQFUNCTION
129 };
130
131 enum TypeOfResult {
132         FLOAT_RESULT,
133         INT_RESULT,
134         HEX_RESULT,
135         CHAR_RESULT
136 };
137
138 static int math(struct ast_channel *chan, const char *cmd, char *parse,
139                 char *buf, size_t len)
140 {
141         double fnum1;
142         double fnum2;
143         double ftmp = 0;
144         char *op;
145         int iaction = -1;
146         int type_of_result = FLOAT_RESULT;
147         char *mvalue1, *mvalue2 = NULL, *mtype_of_result;
148         int negvalue1 = 0;
149         AST_DECLARE_APP_ARGS(args,
150                              AST_APP_ARG(argv0);
151                              AST_APP_ARG(argv1);
152         );
153
154         if (ast_strlen_zero(parse)) {
155                 ast_log(LOG_WARNING, "Syntax: MATH(<number1><op><number 2>[,<type_of_result>]) - missing argument!\n");
156                 return -1;
157         }
158
159         AST_STANDARD_APP_ARGS(args, parse);
160
161         if (args.argc < 1) {
162                 ast_log(LOG_WARNING, "Syntax: MATH(<number1><op><number 2>[,<type_of_result>]) - missing argument!\n");
163                 return -1;
164         }
165
166         mvalue1 = args.argv0;
167
168         if (mvalue1[0] == '-') {
169                 negvalue1 = 1;
170                 mvalue1++;
171         }
172
173         if ((op = strchr(mvalue1, '*'))) {
174                 iaction = MULTIPLYFUNCTION;
175                 *op = '\0';
176         } else if ((op = strchr(mvalue1, '/'))) {
177                 iaction = DIVIDEFUNCTION;
178                 *op = '\0';
179         } else if ((op = strchr(mvalue1, '%'))) {
180                 iaction = MODULUSFUNCTION;
181                 *op = '\0';
182         } else if ((op = strchr(mvalue1, '^'))) {
183                 iaction = POWFUNCTION;
184                 *op = '\0';
185         } else if ((op = strstr(mvalue1, "AND"))) {
186                 iaction = BITWISEANDFUNCTION;
187                 *op = '\0';
188                 op += 2;
189         } else if ((op = strstr(mvalue1, "XOR"))) {
190                 iaction = BITWISEXORFUNCTION;
191                 *op = '\0';
192                 op += 2;
193         } else if ((op = strstr(mvalue1, "OR"))) {
194                 iaction = BITWISEORFUNCTION;
195                 *op = '\0';
196                 ++op;
197         } else if ((op = strchr(mvalue1, '>'))) {
198                 iaction = GTFUNCTION;
199                 *op = '\0';
200                 if (*(op + 1) == '=') {
201                         iaction = GTEFUNCTION;
202                         ++op;
203                 } else if (*(op + 1) == '>') {
204                         iaction = SHRIGHTFUNCTION;
205                         ++op;
206                 }
207         } else if ((op = strchr(mvalue1, '<'))) {
208                 iaction = LTFUNCTION;
209                 *op = '\0';
210                 if (*(op + 1) == '=') {
211                         iaction = LTEFUNCTION;
212                         ++op;
213                 } else if (*(op + 1) == '<') {
214                         iaction = SHLEFTFUNCTION;
215                         ++op;
216                 }
217         } else if ((op = strchr(mvalue1, '='))) {
218                 *op = '\0';
219                 if (*(op + 1) == '=') {
220                         iaction = EQFUNCTION;
221                         ++op;
222                 } else
223                         op = NULL;
224         } else if ((op = strchr(mvalue1, '+'))) {
225                 iaction = ADDFUNCTION;
226                 *op = '\0';
227         } else if ((op = strchr(mvalue1, '-'))) { /* subtraction MUST always be last, in case we have a negative second number */
228                 iaction = SUBTRACTFUNCTION;
229                 *op = '\0';
230         }
231
232         if (op)
233                 mvalue2 = op + 1;
234
235         /* detect wanted type of result */
236         mtype_of_result = args.argv1;
237         if (mtype_of_result) {
238                 if (!strcasecmp(mtype_of_result, "float")
239                     || !strcasecmp(mtype_of_result, "f"))
240                         type_of_result = FLOAT_RESULT;
241                 else if (!strcasecmp(mtype_of_result, "int")
242                          || !strcasecmp(mtype_of_result, "i"))
243                         type_of_result = INT_RESULT;
244                 else if (!strcasecmp(mtype_of_result, "hex")
245                          || !strcasecmp(mtype_of_result, "h"))
246                         type_of_result = HEX_RESULT;
247                 else if (!strcasecmp(mtype_of_result, "char")
248                          || !strcasecmp(mtype_of_result, "c"))
249                         type_of_result = CHAR_RESULT;
250                 else {
251                         ast_log(LOG_WARNING, "Unknown type of result requested '%s'.\n",
252                                         mtype_of_result);
253                         return -1;
254                 }
255         }
256
257         if (!mvalue2) {
258                 ast_log(LOG_WARNING,
259                                 "Supply all the parameters - just this once, please\n");
260                 return -1;
261         }
262
263         if (sscanf(mvalue1, "%30lf", &fnum1) != 1) {
264                 ast_log(LOG_WARNING, "'%s' is not a valid number\n", mvalue1);
265                 return -1;
266         }
267
268         if (sscanf(mvalue2, "%30lf", &fnum2) != 1) {
269                 ast_log(LOG_WARNING, "'%s' is not a valid number\n", mvalue2);
270                 return -1;
271         }
272
273         if (negvalue1)
274                 fnum1 = 0 - fnum1;
275
276         switch (iaction) {
277         case ADDFUNCTION:
278                 ftmp = fnum1 + fnum2;
279                 break;
280         case DIVIDEFUNCTION:
281                 if (fnum2 <= 0)
282                         ftmp = 0;                       /* can't do a divide by 0 */
283                 else
284                         ftmp = (fnum1 / fnum2);
285                 break;
286         case MULTIPLYFUNCTION:
287                 ftmp = (fnum1 * fnum2);
288                 break;
289         case SUBTRACTFUNCTION:
290                 ftmp = (fnum1 - fnum2);
291                 break;
292         case MODULUSFUNCTION:
293                 {
294                         int inum1 = fnum1;
295                         int inum2 = fnum2;
296
297                         if (inum2 == 0) {
298                                 ftmp = 0;
299                         } else {
300                                 ftmp = (inum1 % inum2);
301                         }
302
303                         break;
304                 }
305         case POWFUNCTION:
306                 ftmp = pow(fnum1, fnum2);
307                 break;
308         case SHLEFTFUNCTION:
309                 {
310                         int inum1 = fnum1;
311                         int inum2 = fnum2;
312
313                         ftmp = (inum1 << inum2);
314                         break;
315                 }
316         case SHRIGHTFUNCTION:
317                 {
318                         int inum1 = fnum1;
319                         int inum2 = fnum2;
320
321                         ftmp = (inum1 >> inum2);
322                         break;
323                 }
324         case BITWISEANDFUNCTION:
325                 {
326                         int inum1 = fnum1;
327                         int inum2 = fnum2;
328                         ftmp = (inum1 & inum2);
329                         break;
330                 }
331         case BITWISEXORFUNCTION:
332                 {
333                         int inum1 = fnum1;
334                         int inum2 = fnum2;
335                         ftmp = (inum1 ^ inum2);
336                         break;
337                 }
338         case BITWISEORFUNCTION:
339                 {
340                         int inum1 = fnum1;
341                         int inum2 = fnum2;
342                         ftmp = (inum1 | inum2);
343                         break;
344                 }
345         case GTFUNCTION:
346                 ast_copy_string(buf, (fnum1 > fnum2) ? "TRUE" : "FALSE", len);
347                 break;
348         case LTFUNCTION:
349                 ast_copy_string(buf, (fnum1 < fnum2) ? "TRUE" : "FALSE", len);
350                 break;
351         case GTEFUNCTION:
352                 ast_copy_string(buf, (fnum1 >= fnum2) ? "TRUE" : "FALSE", len);
353                 break;
354         case LTEFUNCTION:
355                 ast_copy_string(buf, (fnum1 <= fnum2) ? "TRUE" : "FALSE", len);
356                 break;
357         case EQFUNCTION:
358                 ast_copy_string(buf, (fnum1 == fnum2) ? "TRUE" : "FALSE", len);
359                 break;
360         default:
361                 ast_log(LOG_WARNING,
362                                 "Something happened that neither of us should be proud of %d\n",
363                                 iaction);
364                 return -1;
365         }
366
367         if (iaction < GTFUNCTION || iaction > EQFUNCTION) {
368                 if (type_of_result == FLOAT_RESULT)
369                         snprintf(buf, len, "%f", ftmp);
370                 else if (type_of_result == INT_RESULT)
371                         snprintf(buf, len, "%i", (int) ftmp);
372                 else if (type_of_result == HEX_RESULT)
373                         snprintf(buf, len, "%x", (unsigned int) ftmp);
374                 else if (type_of_result == CHAR_RESULT)
375                         snprintf(buf, len, "%c", (unsigned char) ftmp);
376         }
377
378         return 0;
379 }
380
381 static int crement_function_read(struct ast_channel *chan, const char *cmd,
382                      char *data, char *buf, size_t len)
383 {
384         int ret = -1;
385         int int_value = 0;
386         int modify_orig = 0;
387         const char *var;
388         char endchar = 0, returnvar[12]; /* If you need a variable longer than 11 digits - something is way wrong */
389
390         if (ast_strlen_zero(data)) {
391                 ast_log(LOG_WARNING, "Syntax: %s(<data>) - missing argument!\n", cmd);
392                 return -1;
393         }
394
395         ast_channel_lock(chan);
396
397         if (!(var = pbx_builtin_getvar_helper(chan, data))) {
398                 ast_log(LOG_NOTICE, "Failed to obtain variable %s, bailing out\n", data);
399                 ast_channel_unlock(chan);
400                 return -1;
401         }
402
403         if (ast_strlen_zero(var)) {
404                 ast_log(LOG_NOTICE, "Variable %s doesn't exist - are you sure you wrote it correctly?\n", data);
405                 ast_channel_unlock(chan);
406                 return -1;
407         }
408
409         if (sscanf(var, "%30d%1c", &int_value, &endchar) == 0 || endchar != 0) {
410                 ast_log(LOG_NOTICE, "The content of ${%s} is not a numeric value - bailing out!\n", data);
411                 ast_channel_unlock(chan);
412                 return -1;
413         }
414
415         /* now we'll actually do something useful */
416         if (!strcasecmp(cmd, "INC")) {              /* Increment variable */
417                 int_value++;
418                 modify_orig = 1;
419         } else if (!strcasecmp(cmd, "DEC")) {       /* Decrement variable */
420                 int_value--;
421                 modify_orig = 1;
422         }
423
424         if (snprintf(returnvar, sizeof(returnvar), "%d", int_value) > 0) {
425                 pbx_builtin_setvar_helper(chan, data, returnvar);
426                 if (modify_orig) {
427                         ast_copy_string(buf, returnvar, len);
428                 }
429                 ret = 0;
430         } else {
431                 pbx_builtin_setvar_helper(chan, data, "0");
432                 if (modify_orig) {
433                         ast_copy_string(buf, "0", len);
434                 }
435                 ast_log(LOG_NOTICE, "Variable %s refused to be %sREMENTED, setting value to 0", data, cmd);
436                 ret = 0;
437         }
438
439         ast_channel_unlock(chan);
440
441         return ret;
442 }
443
444
445 static struct ast_custom_function math_function = {
446         .name = "MATH",
447         .read = math
448 };
449
450 static struct ast_custom_function increment_function = {
451         .name = "INC",
452         .read = crement_function_read,
453 };
454
455 static struct ast_custom_function decrement_function = {
456         .name = "DEC",
457         .read = crement_function_read,
458 };
459
460 #ifdef TEST_FRAMEWORK
461 AST_TEST_DEFINE(test_MATH_function)
462 {
463         enum ast_test_result_state res = AST_TEST_PASS;
464         struct ast_str *expr, *result;
465
466         switch (cmd) {
467         case TEST_INIT:
468                 info->name = "test_MATH_function";
469                 info->category = "/main/pbx/";
470                 info->summary = "Test MATH function substitution";
471                 info->description =
472                         "Executes a series of variable substitutions using the MATH function and ensures that the expected results are received.";
473                 return AST_TEST_NOT_RUN;
474         case TEST_EXECUTE:
475                 break;
476         }
477
478         ast_test_status_update(test, "Testing MATH() substitution ...\n");
479
480         if (!(expr = ast_str_create(16)) || !(result = ast_str_create(16))) {
481                 if (expr) {
482                         ast_free(expr);
483                 }
484                 if (result) {
485                         ast_free(result);
486                 }
487                 return AST_TEST_FAIL;
488         }
489
490         ast_str_set(&expr, 0, "${MATH(170 AND 63,i)}");
491         ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
492         if (strcmp(ast_str_buffer(result), "42") != 0) {
493                 ast_test_status_update(test, "Expected result '42' not returned! ('%s')\n",
494                                 ast_str_buffer(result));
495                 res = AST_TEST_FAIL;
496         }
497
498         ast_str_set(&expr, 0, "${MATH(170AND63,i)}");
499         ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
500         if (strcmp(ast_str_buffer(result), "42") != 0) {
501                 ast_test_status_update(test, "Expected result '42' not returned! ('%s')\n",
502                                 ast_str_buffer(result));
503                 res = AST_TEST_FAIL;
504         }
505
506         return res;
507 }
508 #endif
509
510 static int unload_module(void)
511 {
512         int res = 0;
513
514         res |= ast_custom_function_unregister(&math_function);
515         res |= ast_custom_function_unregister(&increment_function);
516         res |= ast_custom_function_unregister(&decrement_function);
517         AST_TEST_UNREGISTER(test_MATH_function);
518
519         return res;
520 }
521
522 static int load_module(void)
523 {
524         int res = 0;
525
526         res |= ast_custom_function_register(&math_function);
527         res |= ast_custom_function_register(&increment_function);
528         res |= ast_custom_function_register(&decrement_function);
529         AST_TEST_REGISTER(test_MATH_function);
530
531         return res;
532 }
533
534 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mathematical dialplan function");