Reverting last merge since it wasn't completed properly.
[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) - Increments MyVar</para>
107                         <para>Note: DEC(${MyVAR}) - Is wrong, as INC 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         ast_log(LOG_NOTICE, "The value is now: %d\n", int_value);
425
426         if (snprintf(returnvar, sizeof(returnvar), "%d", int_value) > 0) {
427                 pbx_builtin_setvar_helper(chan, data, returnvar);
428                 if (modify_orig) {
429                         ast_copy_string(buf, returnvar, len);
430                 }
431                 ret = 0;
432         } else {
433                 pbx_builtin_setvar_helper(chan, data, "0");
434                 if (modify_orig) {
435                         ast_copy_string(buf, "0", len);
436                 }
437                 ast_log(LOG_NOTICE, "Variable %s refused to be %sREMENTED, setting value to 0", data, cmd);
438                 ret = 0;
439         }
440
441         ast_channel_unlock(chan);
442
443         return ret;
444 }
445
446
447 static struct ast_custom_function math_function = {
448         .name = "MATH",
449         .read = math
450 };
451
452 static struct ast_custom_function increment_function = {
453         .name = "INC",
454         .read = crement_function_read,
455 };
456
457 static struct ast_custom_function decrement_function = {
458         .name = "DEC",
459         .read = crement_function_read,
460 };
461
462 #ifdef TEST_FRAMEWORK
463 AST_TEST_DEFINE(test_MATH_function)
464 {
465         enum ast_test_result_state res = AST_TEST_PASS;
466         struct ast_str *expr, *result;
467
468         switch (cmd) {
469         case TEST_INIT:
470                 info->name = "test_MATH_function";
471                 info->category = "/main/pbx/";
472                 info->summary = "Test MATH function substitution";
473                 info->description =
474                         "Executes a series of variable substitutions using the MATH function and ensures that the expected results are received.";
475                 return AST_TEST_NOT_RUN;
476         case TEST_EXECUTE:
477                 break;
478         }
479
480         ast_test_status_update(test, "Testing MATH() substitution ...\n");
481
482         if (!(expr = ast_str_create(16)) || !(result = ast_str_create(16))) {
483                 if (expr) {
484                         ast_free(expr);
485                 }
486                 if (result) {
487                         ast_free(result);
488                 }
489                 return AST_TEST_FAIL;
490         }
491
492         ast_str_set(&expr, 0, "${MATH(170 AND 63,i)}");
493         ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
494         if (strcmp(ast_str_buffer(result), "42") != 0) {
495                 ast_test_status_update(test, "Expected result '42' not returned! ('%s')\n",
496                                 ast_str_buffer(result));
497                 res = AST_TEST_FAIL;
498         }
499
500         ast_str_set(&expr, 0, "${MATH(170AND63,i)}");
501         ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
502         if (strcmp(ast_str_buffer(result), "42") != 0) {
503                 ast_test_status_update(test, "Expected result '42' not returned! ('%s')\n",
504                                 ast_str_buffer(result));
505                 res = AST_TEST_FAIL;
506         }
507
508         return res;
509 }
510 #endif
511
512 static int unload_module(void)
513 {
514         int res = 0;
515
516         res |= ast_custom_function_unregister(&math_function);
517         res |= ast_custom_function_unregister(&increment_function);
518         res |= ast_custom_function_unregister(&decrement_function);
519         AST_TEST_UNREGISTER(test_MATH_function);
520
521         return res;
522 }
523
524 static int load_module(void)
525 {
526         int res = 0;
527
528         res |= ast_custom_function_register(&math_function);
529         res |= ast_custom_function_register(&increment_function);
530         res |= ast_custom_function_register(&decrement_function);
531         AST_TEST_REGISTER(test_MATH_function);
532
533         return res;
534 }
535
536 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mathematical dialplan function");