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