git migration: Refactor the ASTERISK_FILE_VERSION macro
[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_REGISTER_FILE()
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         if (!chan) {
396                 ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd);
397                 return -1;
398         }
399
400         ast_channel_lock(chan);
401
402         if (!(var = pbx_builtin_getvar_helper(chan, data))) {
403                 ast_log(LOG_NOTICE, "Failed to obtain variable %s, bailing out\n", data);
404                 ast_channel_unlock(chan);
405                 return -1;
406         }
407
408         if (ast_strlen_zero(var)) {
409                 ast_log(LOG_NOTICE, "Variable %s doesn't exist - are you sure you wrote it correctly?\n", data);
410                 ast_channel_unlock(chan);
411                 return -1;
412         }
413
414         if (sscanf(var, "%30d%1c", &int_value, &endchar) == 0 || endchar != 0) {
415                 ast_log(LOG_NOTICE, "The content of ${%s} is not a numeric value - bailing out!\n", data);
416                 ast_channel_unlock(chan);
417                 return -1;
418         }
419
420         /* now we'll actually do something useful */
421         if (!strcasecmp(cmd, "INC")) {              /* Increment variable */
422                 int_value++;
423                 modify_orig = 1;
424         } else if (!strcasecmp(cmd, "DEC")) {       /* Decrement variable */
425                 int_value--;
426                 modify_orig = 1;
427         }
428
429         if (snprintf(returnvar, sizeof(returnvar), "%d", int_value) > 0) {
430                 pbx_builtin_setvar_helper(chan, data, returnvar);
431                 if (modify_orig) {
432                         ast_copy_string(buf, returnvar, len);
433                 }
434                 ret = 0;
435         } else {
436                 pbx_builtin_setvar_helper(chan, data, "0");
437                 if (modify_orig) {
438                         ast_copy_string(buf, "0", len);
439                 }
440                 ast_log(LOG_NOTICE, "Variable %s refused to be %sREMENTED, setting value to 0", data, cmd);
441                 ret = 0;
442         }
443
444         ast_channel_unlock(chan);
445
446         return ret;
447 }
448
449
450 static struct ast_custom_function math_function = {
451         .name = "MATH",
452         .read = math
453 };
454
455 static struct ast_custom_function increment_function = {
456         .name = "INC",
457         .read = crement_function_read,
458 };
459
460 static struct ast_custom_function decrement_function = {
461         .name = "DEC",
462         .read = crement_function_read,
463 };
464
465 #ifdef TEST_FRAMEWORK
466 AST_TEST_DEFINE(test_MATH_function)
467 {
468         enum ast_test_result_state res = AST_TEST_PASS;
469         struct ast_str *expr, *result;
470
471         switch (cmd) {
472         case TEST_INIT:
473                 info->name = "test_MATH_function";
474                 info->category = "/main/pbx/";
475                 info->summary = "Test MATH function substitution";
476                 info->description =
477                         "Executes a series of variable substitutions using the MATH function and ensures that the expected results are received.";
478                 return AST_TEST_NOT_RUN;
479         case TEST_EXECUTE:
480                 break;
481         }
482
483         ast_test_status_update(test, "Testing MATH() substitution ...\n");
484
485         if (!(expr = ast_str_create(16))) {
486                 return AST_TEST_FAIL;
487         }
488         if (!(result = ast_str_create(16))) {
489                 ast_free(expr);
490                 return AST_TEST_FAIL;
491         }
492
493         ast_str_set(&expr, 0, "${MATH(170 AND 63,i)}");
494         ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
495         if (strcmp(ast_str_buffer(result), "42") != 0) {
496                 ast_test_status_update(test, "Expected result '42' not returned! ('%s')\n",
497                                 ast_str_buffer(result));
498                 res = AST_TEST_FAIL;
499         }
500
501         ast_str_set(&expr, 0, "${MATH(170AND63,i)}");
502         ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
503         if (strcmp(ast_str_buffer(result), "42") != 0) {
504                 ast_test_status_update(test, "Expected result '42' not returned! ('%s')\n",
505                                 ast_str_buffer(result));
506                 res = AST_TEST_FAIL;
507         }
508
509         ast_free(expr);
510         ast_free(result);
511
512         return res;
513 }
514 #endif
515
516 static int unload_module(void)
517 {
518         int res = 0;
519
520         res |= ast_custom_function_unregister(&math_function);
521         res |= ast_custom_function_unregister(&increment_function);
522         res |= ast_custom_function_unregister(&decrement_function);
523         AST_TEST_UNREGISTER(test_MATH_function);
524
525         return res;
526 }
527
528 static int load_module(void)
529 {
530         int res = 0;
531
532         res |= ast_custom_function_register(&math_function);
533         res |= ast_custom_function_register(&increment_function);
534         res |= ast_custom_function_register(&decrement_function);
535         AST_TEST_REGISTER(test_MATH_function);
536
537         return res;
538 }
539
540 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mathematical dialplan function");