Asterisk - The Open Source Telephony Project GIT-master-85241bd
app_adsiprog.c
Go to the documentation of this file.
1/*
2 * Asterisk -- An open source telephony toolkit.
3 *
4 * Copyright (C) 1999 - 2005, Digium, Inc.
5 *
6 * Mark Spencer <markster@digium.com>
7 *
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
13 *
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
17 */
18
19/*! \file
20 *
21 * \brief Program Asterisk ADSI Scripts into phone
22 *
23 * \author Mark Spencer <markster@digium.com>
24 *
25 * \ingroup applications
26 */
27
28/*! \li \ref app_adsiprog.c uses the configuration file \ref adsi.conf
29 * \addtogroup configuration_file Configuration Files
30 */
31
32/*!
33 * \page adsi.conf adsi.conf
34 * \verbinclude adsi.conf.sample
35 */
36
37/*** MODULEINFO
38 <depend>res_adsi</depend>
39 <support_level>deprecated</support_level>
40 ***/
41
42#include "asterisk.h"
43
44#include <netinet/in.h>
45#include <ctype.h>
46
47#include "asterisk/paths.h" /* use ast_config_AST_CONFIG_DIR */
48#include "asterisk/file.h"
49#include "asterisk/channel.h"
50#include "asterisk/pbx.h"
51#include "asterisk/module.h"
52#include "asterisk/adsi.h"
53#include "asterisk/utils.h"
54#include "asterisk/lock.h"
55
56static const char app[] = "ADSIProg";
57
58/*** DOCUMENTATION
59 <application name="ADSIProg" language="en_US">
60 <synopsis>
61 Load Asterisk ADSI Scripts into phone
62 </synopsis>
63 <syntax>
64 <parameter name="script" required="false">
65 <para>adsi script to use. If not given uses the default script <filename>asterisk.adsi</filename></para>
66 </parameter>
67 </syntax>
68 <description>
69 <para>This application programs an ADSI Phone with the given script</para>
70 </description>
71 <see-also>
72 <ref type="application">GetCPEID</ref>
73 <ref type="filename">adsi.conf</ref>
74 </see-also>
75 </application>
76 ***/
77
78/* #define DUMP_MESSAGES */
79
80struct adsi_event {
81 int id;
82 const char *name;
83};
84
85static const struct adsi_event events[] = {
86 { 1, "CALLERID" },
87 { 2, "VMWI" },
88 { 3, "NEARANSWER" },
89 { 4, "FARANSWER" },
90 { 5, "ENDOFRING" },
91 { 6, "IDLE" },
92 { 7, "OFFHOOK" },
93 { 8, "CIDCW" },
94 { 9, "BUSY" },
95 { 10, "FARRING" },
96 { 11, "DIALTONE" },
97 { 12, "RECALL" },
98 { 13, "MESSAGE" },
99 { 14, "REORDER" },
100 { 15, "DISTINCTIVERING" },
101 { 16, "RING" },
102 { 17, "REMINDERRING" },
103 { 18, "SPECIALRING" },
104 { 19, "CODEDRING" },
105 { 20, "TIMER" },
106 { 21, "INUSE" },
107 { 22, "EVENT22" },
108 { 23, "EVENT23" },
109 { 24, "CPEID" },
110};
111
112static const struct adsi_event justify[] = {
113 { 0, "CENTER" },
114 { 1, "RIGHT" },
115 { 2, "LEFT" },
116 { 3, "INDENT" },
117};
118
119#define STATE_NORMAL 0
120#define STATE_INKEY 1
121#define STATE_INSUB 2
122#define STATE_INIF 3
123
124#define MAX_RET_CODE 20
125#define MAX_SUB_LEN 255
126#define MAX_MAIN_LEN 1600
127
128#define ARG_STRING (1 << 0)
129#define ARG_NUMBER (1 << 1)
130
132 char vname[40]; /* Which "variable" is associated with it */
133 int retstrlen; /* Length of return string */
134 int initlen; /* initial length */
135 int id;
137 char retstr[80]; /* Return string data */
138};
139
141 char vname[40];
142 int id;
147 char *ifdata;
148 char data[2048];
149};
150
152 char vname[40];
153 int id;
154};
155
156struct adsi_flag {
157 char vname[40];
158 int id;
159};
160
162 char vname[40];
163 int id;
164 char data[70];
166};
167
169 int state;
177 /* Pre-defined displays */
179 /* ADSI States 1 (initial) - 254 */
180 struct adsi_state states[256];
181 /* Keys 2-63 */
182 struct adsi_soft_key keys[62];
183 /* Subscripts 0 (main) to 127 */
184 struct adsi_subscript subs[128];
185 /* Flags 1-7 */
186 struct adsi_flag flags[7];
187
188 /* Stuff from adsi script */
189 unsigned char sec[5];
190 char desc[19];
191 unsigned char fdn[5];
192 int ver;
193};
194
195
196static int process_token(void *out, char *src, int maxlen, int argtype)
197{
198 if ((strlen(src) > 1) && src[0] == '\"') {
199 /* This is a quoted string */
200 if (!(argtype & ARG_STRING))
201 return -1;
202 src++;
203 /* Don't take more than what's there */
204 if (maxlen > strlen(src) - 1)
205 maxlen = strlen(src) - 1;
206 memcpy(out, src, maxlen);
207 ((char *)out)[maxlen] = '\0';
208 } else if (!ast_strlen_zero(src) && (src[0] == '\\')) {
209 if (!(argtype & ARG_NUMBER))
210 return -1;
211 /* Octal value */
212 if (sscanf(src, "%30o", (unsigned *)out) != 1)
213 return -1;
214 if (argtype & ARG_STRING) {
215 /* Convert */
216 *((unsigned int *)out) = htonl(*((unsigned int *)out));
217 }
218 } else if ((strlen(src) > 2) && (src[0] == '0') && (tolower(src[1]) == 'x')) {
219 if (!(argtype & ARG_NUMBER))
220 return -1;
221 /* Hex value */
222 if (sscanf(src + 2, "%30x", (unsigned int *)out) != 1)
223 return -1;
224 if (argtype & ARG_STRING) {
225 /* Convert */
226 *((unsigned int *)out) = htonl(*((unsigned int *)out));
227 }
228 } else if ((!ast_strlen_zero(src) && isdigit(src[0]))) {
229 if (!(argtype & ARG_NUMBER))
230 return -1;
231 /* Hex value */
232 if (sscanf(src, "%30d", (int *)out) != 1)
233 return -1;
234 if (argtype & ARG_STRING) {
235 /* Convert */
236 *((unsigned int *)out) = htonl(*((unsigned int *)out));
237 }
238 } else
239 return -1;
240 return 0;
241}
242
243static char *get_token(char **buf, const char *script, int lineno)
244{
245 char *tmp = *buf, *keyword;
246 int quoted = 0;
247
248 /* Advance past any white space */
249 while(*tmp && (*tmp < 33))
250 tmp++;
251 if (!*tmp)
252 return NULL;
253 keyword = tmp;
254 while(*tmp && ((*tmp > 32) || quoted)) {
255 if (*tmp == '\"') {
256 quoted = !quoted;
257 }
258 tmp++;
259 }
260 if (quoted) {
261 ast_log(LOG_WARNING, "Mismatched quotes at line %d of %s\n", lineno, script);
262 return NULL;
263 }
264 *tmp = '\0';
265 tmp++;
266 while(*tmp && (*tmp < 33))
267 tmp++;
268 /* Note where we left off */
269 *buf = tmp;
270 return keyword;
271}
272
273static char *validdtmf = "123456789*0#ABCD";
274
275static int send_dtmf(char *buf, char *name, int id, char *args, struct adsi_script *state, const char *script, int lineno)
276{
277 char dtmfstr[80], *a;
278 int bytes = 0;
279
280 if (!(a = get_token(&args, script, lineno))) {
281 ast_log(LOG_WARNING, "Expecting something to send for SENDDTMF at line %d of %s\n", lineno, script);
282 return 0;
283 }
284
285 if (process_token(dtmfstr, a, sizeof(dtmfstr) - 1, ARG_STRING)) {
286 ast_log(LOG_WARNING, "Invalid token for SENDDTMF at line %d of %s\n", lineno, script);
287 return 0;
288 }
289
290 a = dtmfstr;
291
292 while (*a) {
293 if (strchr(validdtmf, *a)) {
294 *buf = *a;
295 buf++;
296 bytes++;
297 } else
298 ast_log(LOG_WARNING, "'%c' is not a valid DTMF tone at line %d of %s\n", *a, lineno, script);
299 a++;
300 }
301
302 return bytes;
303}
304
305static int goto_line(char *buf, char *name, int id, char *args, struct adsi_script *state, const char *script, int lineno)
306{
307 char *page = get_token(&args, script, lineno);
308 char *gline = get_token(&args, script, lineno);
309 int line;
310 unsigned char cmd;
311
312 if (!page || !gline) {
313 ast_log(LOG_WARNING, "Expecting page and line number for GOTOLINE at line %d of %s\n", lineno, script);
314 return 0;
315 }
316
317 if (!strcasecmp(page, "INFO"))
318 cmd = 0;
319 else if (!strcasecmp(page, "COMM"))
320 cmd = 0x80;
321 else {
322 ast_log(LOG_WARNING, "Expecting either 'INFO' or 'COMM' page, got '%s' at line %d of %s\n", page, lineno, script);
323 return 0;
324 }
325
326 if (process_token(&line, gline, sizeof(line), ARG_NUMBER)) {
327 ast_log(LOG_WARNING, "Invalid line number '%s' at line %d of %s\n", gline, lineno, script);
328 return 0;
329 }
330
331 cmd |= line;
332 buf[0] = 0x8b;
333 buf[1] = cmd;
334
335 return 2;
336}
337
338static int goto_line_rel(char *buf, char *name, int id, char *args, struct adsi_script *state, const char *script, int lineno)
339{
340 char *dir = get_token(&args, script, lineno);
341 char *gline = get_token(&args, script, lineno);
342 int line;
343 unsigned char cmd;
344
345 if (!dir || !gline) {
346 ast_log(LOG_WARNING, "Expecting direction and number of lines for GOTOLINEREL at line %d of %s\n", lineno, script);
347 return 0;
348 }
349
350 if (!strcasecmp(dir, "UP"))
351 cmd = 0;
352 else if (!strcasecmp(dir, "DOWN"))
353 cmd = 0x20;
354 else {
355 ast_log(LOG_WARNING, "Expecting either 'UP' or 'DOWN' direction, got '%s' at line %d of %s\n", dir, lineno, script);
356 return 0;
357 }
358
359 if (process_token(&line, gline, sizeof(line), ARG_NUMBER)) {
360 ast_log(LOG_WARNING, "Invalid line number '%s' at line %d of %s\n", gline, lineno, script);
361 return 0;
362 }
363
364 cmd |= line;
365 buf[0] = 0x8c;
366 buf[1] = cmd;
367
368 return 2;
369}
370
371static int send_delay(char *buf, char *name, int id, char *args, struct adsi_script *state, const char *script, int lineno)
372{
373 char *gtime = get_token(&args, script, lineno);
374 int ms;
375
376 if (!gtime) {
377 ast_log(LOG_WARNING, "Expecting number of milliseconds to wait at line %d of %s\n", lineno, script);
378 return 0;
379 }
380
381 if (process_token(&ms, gtime, sizeof(ms), ARG_NUMBER)) {
382 ast_log(LOG_WARNING, "Invalid delay milliseconds '%s' at line %d of %s\n", gtime, lineno, script);
383 return 0;
384 }
385
386 buf[0] = 0x90;
387
388 if (id == 11)
389 buf[1] = ms / 100;
390 else
391 buf[1] = ms / 10;
392
393 return 2;
394}
395
396static int set_state(char *buf, char *name, int id, char *args, struct adsi_script *istate, const char *script, int lineno)
397{
398 char *gstate = get_token(&args, script, lineno);
399 int state;
400
401 if (!gstate) {
402 ast_log(LOG_WARNING, "Expecting state number at line %d of %s\n", lineno, script);
403 return 0;
404 }
405
406 if (process_token(&state, gstate, sizeof(state), ARG_NUMBER)) {
407 ast_log(LOG_WARNING, "Invalid state number '%s' at line %d of %s\n", gstate, lineno, script);
408 return 0;
409 }
410
411 buf[0] = id;
412 buf[1] = state;
413
414 return 2;
415}
416
417static int cleartimer(char *buf, char *name, int id, char *args, struct adsi_script *istate, const char *script, int lineno)
418{
419 char *tok = get_token(&args, script, lineno);
420
421 if (tok)
422 ast_log(LOG_WARNING, "Clearing timer requires no arguments ('%s') at line %d of %s\n", tok, lineno, script);
423
424 buf[0] = id;
425
426 /* For some reason the clear code is different slightly */
427 if (id == 7)
428 buf[1] = 0x10;
429 else
430 buf[1] = 0x00;
431
432 return 2;
433}
434
435static struct adsi_flag *getflagbyname(struct adsi_script *state, char *name, const char *script, int lineno, int create)
436{
437 int x;
438
439 for (x = 0; x < state->numflags; x++) {
440 if (!strcasecmp(state->flags[x].vname, name))
441 return &state->flags[x];
442 }
443
444 /* Return now if we're not allowed to create */
445 if (!create)
446 return NULL;
447
448 if (state->numflags > 6) {
449 ast_log(LOG_WARNING, "No more flag space at line %d of %s\n", lineno, script);
450 return NULL;
451 }
452
453 ast_copy_string(state->flags[state->numflags].vname, name, sizeof(state->flags[state->numflags].vname));
454 state->flags[state->numflags].id = state->numflags + 1;
455 state->numflags++;
456
457 return &state->flags[state->numflags-1];
458}
459
460static int setflag(char *buf, char *name, int id, char *args, struct adsi_script *state, const char *script, int lineno)
461{
462 char *tok = get_token(&args, script, lineno);
463 char sname[80];
464 struct adsi_flag *flag;
465
466 if (!tok) {
467 ast_log(LOG_WARNING, "Setting flag requires a flag number at line %d of %s\n", lineno, script);
468 return 0;
469 }
470
471 if (process_token(sname, tok, sizeof(sname) - 1, ARG_STRING)) {
472 ast_log(LOG_WARNING, "Invalid flag '%s' at line %d of %s\n", tok, lineno, script);
473 return 0;
474 }
475
476 if (!(flag = getflagbyname(state, sname, script, lineno, 0))) {
477 ast_log(LOG_WARNING, "Flag '%s' is undeclared at line %d of %s\n", sname, lineno, script);
478 return 0;
479 }
480
481 buf[0] = id;
482 buf[1] = ((flag->id & 0x7) << 4) | 1;
483
484 return 2;
485}
486
487static int clearflag(char *buf, char *name, int id, char *args, struct adsi_script *state, const char *script, int lineno)
488{
489 char *tok = get_token(&args, script, lineno);
490 struct adsi_flag *flag;
491 char sname[80];
492
493 if (!tok) {
494 ast_log(LOG_WARNING, "Clearing flag requires a flag number at line %d of %s\n", lineno, script);
495 return 0;
496 }
497
498 if (process_token(sname, tok, sizeof(sname) - 1, ARG_STRING)) {
499 ast_log(LOG_WARNING, "Invalid flag '%s' at line %d of %s\n", tok, lineno, script);
500 return 0;
501 }
502
503 if (!(flag = getflagbyname(state, sname, script, lineno, 0))) {
504 ast_log(LOG_WARNING, "Flag '%s' is undeclared at line %d of %s\n", sname, lineno, script);
505 return 0;
506 }
507
508 buf[0] = id;
509 buf[1] = ((flag->id & 0x7) << 4);
510
511 return 2;
512}
513
514static int starttimer(char *buf, char *name, int id, char *args, struct adsi_script *istate, const char *script, int lineno)
515{
516 char *tok = get_token(&args, script, lineno);
517 int secs;
518
519 if (!tok) {
520 ast_log(LOG_WARNING, "Missing number of seconds at line %d of %s\n", lineno, script);
521 return 0;
522 }
523
524 if (process_token(&secs, tok, sizeof(secs), ARG_NUMBER)) {
525 ast_log(LOG_WARNING, "Invalid number of seconds '%s' at line %d of %s\n", tok, lineno, script);
526 return 0;
527 }
528
529 buf[0] = id;
530 buf[1] = 0x1;
531 buf[2] = secs;
532
533 return 3;
534}
535
536static int geteventbyname(char *name)
537{
538 int x;
539
540 for (x = 0; x < ARRAY_LEN(events); x++) {
541 if (!strcasecmp(events[x].name, name))
542 return events[x].id;
543 }
544
545 return 0;
546}
547
548static int getjustifybyname(char *name)
549{
550 int x;
551
552 for (x = 0; x < ARRAY_LEN(justify); x++) {
553 if (!strcasecmp(justify[x].name, name))
554 return justify[x].id;
555 }
556
557 return -1;
558}
559
560static struct adsi_soft_key *getkeybyname(struct adsi_script *state, char *name, const char *script, int lineno)
561{
562 int x;
563
564 for (x = 0; x < state->numkeys; x++) {
565 if (!strcasecmp(state->keys[x].vname, name))
566 return &state->keys[x];
567 }
568
569 if (state->numkeys > 61) {
570 ast_log(LOG_WARNING, "No more key space at line %d of %s\n", lineno, script);
571 return NULL;
572 }
573
574 ast_copy_string(state->keys[state->numkeys].vname, name, sizeof(state->keys[state->numkeys].vname));
575 state->keys[state->numkeys].id = state->numkeys + 2;
576 state->numkeys++;
577
578 return &state->keys[state->numkeys-1];
579}
580
581static struct adsi_subscript *getsubbyname(struct adsi_script *state, char *name, const char *script, int lineno)
582{
583 int x;
584
585 for (x = 0; x < state->numsubs; x++) {
586 if (!strcasecmp(state->subs[x].vname, name))
587 return &state->subs[x];
588 }
589
590 if (state->numsubs > 127) {
591 ast_log(LOG_WARNING, "No more subscript space at line %d of %s\n", lineno, S_OR(script, "unknown"));
592 return NULL;
593 }
594
595 ast_copy_string(state->subs[state->numsubs].vname, name, sizeof(state->subs[state->numsubs].vname));
596 state->subs[state->numsubs].id = state->numsubs;
597 state->numsubs++;
598
599 return &state->subs[state->numsubs-1];
600}
601
602static struct adsi_state *getstatebyname(struct adsi_script *state, char *name, const char *script, int lineno, int create)
603{
604 int x;
605
606 for (x = 0; x <state->numstates; x++) {
607 if (!strcasecmp(state->states[x].vname, name))
608 return &state->states[x];
609 }
610
611 /* Return now if we're not allowed to create */
612 if (!create)
613 return NULL;
614
615 if (state->numstates > 253) {
616 ast_log(LOG_WARNING, "No more state space at line %d of %s\n", lineno, script);
617 return NULL;
618 }
619
620 ast_copy_string(state->states[state->numstates].vname, name, sizeof(state->states[state->numstates].vname));
621 state->states[state->numstates].id = state->numstates + 1;
622 state->numstates++;
623
624 return &state->states[state->numstates-1];
625}
626
627static struct adsi_display *getdisplaybyname(struct adsi_script *state, char *name, const char *script, int lineno, int create)
628{
629 int x;
630
631 for (x = 0; x < state->numdisplays; x++) {
632 if (!strcasecmp(state->displays[x].vname, name))
633 return &state->displays[x];
634 }
635
636 /* Return now if we're not allowed to create */
637 if (!create)
638 return NULL;
639
640 if (state->numdisplays > 61) {
641 ast_log(LOG_WARNING, "No more display space at line %d of %s\n", lineno, script);
642 return NULL;
643 }
644
645 ast_copy_string(state->displays[state->numdisplays].vname, name, sizeof(state->displays[state->numdisplays].vname));
646 state->displays[state->numdisplays].id = state->numdisplays + 1;
647 state->numdisplays++;
648
649 return &state->displays[state->numdisplays-1];
650}
651
652static int showkeys(char *buf, char *name, int id, char *args, struct adsi_script *state, const char *script, int lineno)
653{
654 char *tok, newkey[80];
655 int bytes, x, flagid = 0;
656 unsigned char keyid[6];
657 struct adsi_soft_key *key;
658 struct adsi_flag *flag;
659
660 for (x = 0; x < 7; x++) {
661 /* Up to 6 key arguments */
662 if (!(tok = get_token(&args, script, lineno)))
663 break;
664 if (!strcasecmp(tok, "UNLESS")) {
665 /* Check for trailing UNLESS flag */
666 if (!(tok = get_token(&args, script, lineno)))
667 ast_log(LOG_WARNING, "Missing argument for UNLESS clause at line %d of %s\n", lineno, script);
668 else if (process_token(newkey, tok, sizeof(newkey) - 1, ARG_STRING))
669 ast_log(LOG_WARNING, "Invalid flag name '%s' at line %d of %s\n", tok, lineno, script);
670 else if (!(flag = getflagbyname(state, newkey, script, lineno, 0)))
671 ast_log(LOG_WARNING, "Flag '%s' is undeclared at line %d of %s\n", newkey, lineno, script);
672 else
673 flagid = flag->id;
674 if ((tok = get_token(&args, script, lineno)))
675 ast_log(LOG_WARNING, "Extra arguments after UNLESS clause: '%s' at line %d of %s\n", tok, lineno, script);
676 break;
677 }
678 if (x > 5) {
679 ast_log(LOG_WARNING, "Only 6 keys can be defined, ignoring '%s' at line %d of %s\n", tok, lineno, script);
680 break;
681 }
682 if (process_token(newkey, tok, sizeof(newkey) - 1, ARG_STRING)) {
683 ast_log(LOG_WARNING, "Invalid token for key name: %s\n", tok);
684 continue;
685 }
686
687 if (!(key = getkeybyname(state, newkey, script, lineno)))
688 break;
689 keyid[x] = key->id;
690 }
691 buf[0] = id;
692 buf[1] = (flagid & 0x7) << 3 | (x & 0x7);
693 for (bytes = 0; bytes < x; bytes++)
694 buf[bytes + 2] = keyid[bytes];
695
696 return 2 + x;
697}
698
699static int showdisplay(char *buf, char *name, int id, char *args, struct adsi_script *state, const char *script, int lineno)
700{
701 char *tok, dispname[80];
702 int line = 0, flag = 0, cmd = 3;
703 struct adsi_display *disp;
704
705 /* Get display */
706 if (!(tok = get_token(&args, script, lineno)) || process_token(dispname, tok, sizeof(dispname) - 1, ARG_STRING)) {
707 ast_log(LOG_WARNING, "Invalid display name: %s at line %d of %s\n", tok ? tok : "<nothing>", lineno, script);
708 return 0;
709 }
710
711 if (!(disp = getdisplaybyname(state, dispname, script, lineno, 0))) {
712 ast_log(LOG_WARNING, "Display '%s' is undefined at line %d of %s\n", dispname, lineno, script);
713 return 0;
714 }
715
716 if (!(tok = get_token(&args, script, lineno)) || strcasecmp(tok, "AT")) {
717 ast_log(LOG_WARNING, "Missing token 'AT' at line %d of %s\n", lineno, script);
718 return 0;
719 }
720
721 /* Get line number */
722 if (!(tok = get_token(&args, script, lineno)) || process_token(&line, tok, sizeof(line), ARG_NUMBER)) {
723 ast_log(LOG_WARNING, "Invalid line: '%s' at line %d of %s\n", tok ? tok : "<nothing>", lineno, script);
724 return 0;
725 }
726
727 if ((tok = get_token(&args, script, lineno)) && !strcasecmp(tok, "NOUPDATE")) {
728 cmd = 1;
729 tok = get_token(&args, script, lineno);
730 }
731
732 if (tok && !strcasecmp(tok, "UNLESS")) {
733 /* Check for trailing UNLESS flag */
734 if (!(tok = get_token(&args, script, lineno)))
735 ast_log(LOG_WARNING, "Missing argument for UNLESS clause at line %d of %s\n", lineno, script);
736 else if (process_token(&flag, tok, sizeof(flag), ARG_NUMBER))
737 ast_log(LOG_WARNING, "Invalid flag number '%s' at line %d of %s\n", tok, lineno, script);
738
739 if ((tok = get_token(&args, script, lineno)))
740 ast_log(LOG_WARNING, "Extra arguments after UNLESS clause: '%s' at line %d of %s\n", tok, lineno, script);
741 }
742
743 buf[0] = id;
744 buf[1] = (cmd << 6) | (disp->id & 0x3f);
745 buf[2] = ((line & 0x1f) << 3) | (flag & 0x7);
746
747 return 3;
748}
749
750static int cleardisplay(char *buf, char *name, int id, char *args, struct adsi_script *istate, const char *script, int lineno)
751{
752 char *tok = get_token(&args, script, lineno);
753
754 if (tok)
755 ast_log(LOG_WARNING, "Clearing display requires no arguments ('%s') at line %d of %s\n", tok, lineno, script);
756
757 buf[0] = id;
758 buf[1] = 0x00;
759 return 2;
760}
761
762static int digitdirect(char *buf, char *name, int id, char *args, struct adsi_script *istate, const char *script, int lineno)
763{
764 char *tok = get_token(&args, script, lineno);
765
766 if (tok)
767 ast_log(LOG_WARNING, "Digitdirect requires no arguments ('%s') at line %d of %s\n", tok, lineno, script);
768
769 buf[0] = id;
770 buf[1] = 0x7;
771 return 2;
772}
773
774static int clearcbone(char *buf, char *name, int id, char *args, struct adsi_script *istate, const char *script, int lineno)
775{
776 char *tok = get_token(&args, script, lineno);
777
778 if (tok)
779 ast_log(LOG_WARNING, "CLEARCB1 requires no arguments ('%s') at line %d of %s\n", tok, lineno, script);
780
781 buf[0] = id;
782 buf[1] = 0;
783 return 2;
784}
785
786static int digitcollect(char *buf, char *name, int id, char *args, struct adsi_script *istate, const char *script, int lineno)
787{
788 char *tok = get_token(&args, script, lineno);
789
790 if (tok)
791 ast_log(LOG_WARNING, "Digitcollect requires no arguments ('%s') at line %d of %s\n", tok, lineno, script);
792
793 buf[0] = id;
794 buf[1] = 0xf;
795 return 2;
796}
797
798static int subscript(char *buf, char *name, int id, char *args, struct adsi_script *state, const char *script, int lineno)
799{
800 char *tok = get_token(&args, script, lineno);
801 char subscr[80];
802 struct adsi_subscript *sub;
803
804 if (!tok) {
805 ast_log(LOG_WARNING, "Missing subscript to call at line %d of %s\n", lineno, script);
806 return 0;
807 }
808
809 if (process_token(subscr, tok, sizeof(subscr) - 1, ARG_STRING)) {
810 ast_log(LOG_WARNING, "Invalid number of seconds '%s' at line %d of %s\n", tok, lineno, script);
811 return 0;
812 }
813
814 if (!(sub = getsubbyname(state, subscr, script, lineno)))
815 return 0;
816
817 buf[0] = 0x9d;
818 buf[1] = sub->id;
819
820 return 2;
821}
822
823static int onevent(char *buf, char *name, int id, char *args, struct adsi_script *state, const char *script, int lineno)
824{
825 char *tok = get_token(&args, script, lineno);
826 char subscr[80], sname[80];
827 int sawin = 0, event, snums[8], scnt = 0, x;
828 struct adsi_subscript *sub;
829
830 if (!tok) {
831 ast_log(LOG_WARNING, "Missing event for 'ONEVENT' at line %d of %s\n", lineno, script);
832 return 0;
833 }
834
835 if ((event = geteventbyname(tok)) < 1) {
836 ast_log(LOG_WARNING, "'%s' is not a valid event name, at line %d of %s\n", args, lineno, script);
837 return 0;
838 }
839
840 tok = get_token(&args, script, lineno);
841 while ((!sawin && !strcasecmp(tok, "IN")) || (sawin && !strcasecmp(tok, "OR"))) {
842 sawin = 1;
843 if (scnt > 7) {
844 ast_log(LOG_WARNING, "No more than 8 states may be specified for inclusion at line %d of %s\n", lineno, script);
845 return 0;
846 }
847 /* Process 'in' things */
848 tok = get_token(&args, script, lineno);
849 if (process_token(sname, tok, sizeof(sname), ARG_STRING)) {
850 ast_log(LOG_WARNING, "'%s' is not a valid state name at line %d of %s\n", tok, lineno, script);
851 return 0;
852 }
853 if ((snums[scnt] = getstatebyname(state, sname, script, lineno, 0) == NULL)) {
854 ast_log(LOG_WARNING, "State '%s' not declared at line %d of %s\n", sname, lineno, script);
855 return 0;
856 }
857 scnt++;
858 if (!(tok = get_token(&args, script, lineno)))
859 break;
860 }
861 if (!tok || strcasecmp(tok, "GOTO")) {
862 if (!tok)
863 tok = "<nothing>";
864 if (sawin)
865 ast_log(LOG_WARNING, "Got '%s' while looking for 'GOTO' or 'OR' at line %d of %s\n", tok, lineno, script);
866 else
867 ast_log(LOG_WARNING, "Got '%s' while looking for 'GOTO' or 'IN' at line %d of %s\n", tok, lineno, script);
868 }
869 if (!(tok = get_token(&args, script, lineno))) {
870 ast_log(LOG_WARNING, "Missing subscript to call at line %d of %s\n", lineno, script);
871 return 0;
872 }
873 if (process_token(subscr, tok, sizeof(subscr) - 1, ARG_STRING)) {
874 ast_log(LOG_WARNING, "Invalid subscript '%s' at line %d of %s\n", tok, lineno, script);
875 return 0;
876 }
877 if (!(sub = getsubbyname(state, subscr, script, lineno)))
878 return 0;
879 buf[0] = 8;
880 buf[1] = event;
881 buf[2] = sub->id | 0x80;
882 for (x = 0; x < scnt; x++)
883 buf[3 + x] = snums[x];
884 return 3 + scnt;
885}
886
888 char *name;
889 int id;
890 int (*add_args)(char *buf, char *name, int id, char *args, struct adsi_script *state, const char *script, int lineno);
891};
892
893static const struct adsi_key_cmd kcmds[] = {
894 { "SENDDTMF", 0, send_dtmf },
895 /* Encoded DTMF would go here */
896 { "ONHOOK", 0x81 },
897 { "OFFHOOK", 0x82 },
898 { "FLASH", 0x83 },
899 { "WAITDIALTONE", 0x84 },
900 /* Send line number */
901 { "BLANK", 0x86 },
902 { "SENDCHARS", 0x87 },
903 { "CLEARCHARS", 0x88 },
904 { "BACKSPACE", 0x89 },
905 /* Tab column */
906 { "GOTOLINE", 0x8b, goto_line },
907 { "GOTOLINEREL", 0x8c, goto_line_rel },
908 { "PAGEUP", 0x8d },
909 { "PAGEDOWN", 0x8e },
910 /* Extended DTMF */
911 { "DELAY", 0x90, send_delay },
912 { "DIALPULSEONE", 0x91 },
913 { "DATAMODE", 0x92 },
914 { "VOICEMODE", 0x93 },
915 /* Display call buffer 'n' */
916 /* Clear call buffer 'n' */
917 { "CLEARCB1", 0x95, clearcbone },
918 { "DIGITCOLLECT", 0x96, digitcollect },
919 { "DIGITDIRECT", 0x96, digitdirect },
920 { "CLEAR", 0x97 },
921 { "SHOWDISPLAY", 0x98, showdisplay },
922 { "CLEARDISPLAY", 0x98, cleardisplay },
923 { "SHOWKEYS", 0x99, showkeys },
924 { "SETSTATE", 0x9a, set_state },
925 { "TIMERSTART", 0x9b, starttimer },
926 { "TIMERCLEAR", 0x9b, cleartimer },
927 { "SETFLAG", 0x9c, setflag },
928 { "CLEARFLAG", 0x9c, clearflag },
929 { "GOTO", 0x9d, subscript },
930 { "EVENT22", 0x9e },
931 { "EVENT23", 0x9f },
932 { "EXIT", 0xa0 },
933};
934
935static const struct adsi_key_cmd opcmds[] = {
936
937 /* 1 - Branch on event -- handled specially */
938 { "SHOWKEYS", 2, showkeys },
939 /* Display Control */
940 { "SHOWDISPLAY", 3, showdisplay },
941 { "CLEARDISPLAY", 3, cleardisplay },
942 { "CLEAR", 5 },
943 { "SETSTATE", 6, set_state },
944 { "TIMERSTART", 7, starttimer },
945 { "TIMERCLEAR", 7, cleartimer },
946 { "ONEVENT", 8, onevent },
947 /* 9 - Subroutine label, treated specially */
948 { "SETFLAG", 10, setflag },
949 { "CLEARFLAG", 10, clearflag },
950 { "DELAY", 11, send_delay },
951 { "EXIT", 12 },
952};
953
954
955static int process_returncode(struct adsi_soft_key *key, char *code, char *args, struct adsi_script *state, const char *script, int lineno)
956{
957 int x, res;
958 char *unused;
959
960 for (x = 0; x < ARRAY_LEN(kcmds); x++) {
961 if ((kcmds[x].id > -1) && !strcasecmp(kcmds[x].name, code)) {
962 if (kcmds[x].add_args) {
963 res = kcmds[x].add_args(key->retstr + key->retstrlen,
964 code, kcmds[x].id, args, state, script, lineno);
965 if ((key->retstrlen + res - key->initlen) <= MAX_RET_CODE)
966 key->retstrlen += res;
967 else
968 ast_log(LOG_WARNING, "No space for '%s' code in key '%s' at line %d of %s\n", kcmds[x].name, key->vname, lineno, script);
969 } else {
970 if ((unused = get_token(&args, script, lineno)))
971 ast_log(LOG_WARNING, "'%s' takes no arguments at line %d of %s (token is '%s')\n", kcmds[x].name, lineno, script, unused);
972 if ((key->retstrlen + 1 - key->initlen) <= MAX_RET_CODE) {
973 key->retstr[key->retstrlen] = kcmds[x].id;
974 key->retstrlen++;
975 } else
976 ast_log(LOG_WARNING, "No space for '%s' code in key '%s' at line %d of %s\n", kcmds[x].name, key->vname, lineno, script);
977 }
978 return 0;
979 }
980 }
981 return -1;
982}
983
984static int process_opcode(struct adsi_subscript *sub, char *code, char *args, struct adsi_script *state, const char *script, int lineno)
985{
986 int x, res, max = sub->id ? MAX_SUB_LEN : MAX_MAIN_LEN;
987 char *unused;
988
989 for (x = 0; x < ARRAY_LEN(opcmds); x++) {
990 if ((opcmds[x].id > -1) && !strcasecmp(opcmds[x].name, code)) {
991 if (opcmds[x].add_args) {
992 res = opcmds[x].add_args(sub->data + sub->datalen,
993 code, opcmds[x].id, args, state, script, lineno);
994 if ((sub->datalen + res + 1) <= max)
995 sub->datalen += res;
996 else {
997 ast_log(LOG_WARNING, "No space for '%s' code in subscript '%s' at line %d of %s\n", opcmds[x].name, sub->vname, lineno, script);
998 return -1;
999 }
1000 } else {
1001 if ((unused = get_token(&args, script, lineno)))
1002 ast_log(LOG_WARNING, "'%s' takes no arguments at line %d of %s (token is '%s')\n", opcmds[x].name, lineno, script, unused);
1003 if ((sub->datalen + 2) <= max) {
1004 sub->data[sub->datalen] = opcmds[x].id;
1005 sub->datalen++;
1006 } else {
1007 ast_log(LOG_WARNING, "No space for '%s' code in key '%s' at line %d of %s\n", opcmds[x].name, sub->vname, lineno, script);
1008 return -1;
1009 }
1010 }
1011 /* Separate commands with 0xff */
1012 sub->data[sub->datalen] = 0xff;
1013 sub->datalen++;
1014 sub->inscount++;
1015 return 0;
1016 }
1017 }
1018 return -1;
1019}
1020
1021static int adsi_process(struct adsi_script *state, char *buf, const char *script, int lineno)
1022{
1023 char *keyword = get_token(&buf, script, lineno);
1024 char *args, vname[256], tmp[80], tmp2[80];
1025 int lrci, wi, event;
1026 struct adsi_display *disp;
1027 struct adsi_subscript *newsub;
1028
1029 if (!keyword)
1030 return 0;
1031
1032 switch(state->state) {
1033 case STATE_NORMAL:
1034 if (!strcasecmp(keyword, "DESCRIPTION")) {
1035 if ((args = get_token(&buf, script, lineno))) {
1036 if (process_token(state->desc, args, sizeof(state->desc) - 1, ARG_STRING))
1037 ast_log(LOG_WARNING, "'%s' is not a valid token for DESCRIPTION at line %d of %s\n", args, lineno, script);
1038 } else
1039 ast_log(LOG_WARNING, "Missing argument for DESCRIPTION at line %d of %s\n", lineno, script);
1040 } else if (!strcasecmp(keyword, "VERSION")) {
1041 if ((args = get_token(&buf, script, lineno))) {
1042 if (process_token(&state->ver, args, sizeof(state->ver) - 1, ARG_NUMBER))
1043 ast_log(LOG_WARNING, "'%s' is not a valid token for VERSION at line %d of %s\n", args, lineno, script);
1044 } else
1045 ast_log(LOG_WARNING, "Missing argument for VERSION at line %d of %s\n", lineno, script);
1046 } else if (!strcasecmp(keyword, "SECURITY")) {
1047 if ((args = get_token(&buf, script, lineno))) {
1048 if (process_token(state->sec, args, sizeof(state->sec) - 1, ARG_STRING | ARG_NUMBER))
1049 ast_log(LOG_WARNING, "'%s' is not a valid token for SECURITY at line %d of %s\n", args, lineno, script);
1050 } else
1051 ast_log(LOG_WARNING, "Missing argument for SECURITY at line %d of %s\n", lineno, script);
1052 } else if (!strcasecmp(keyword, "FDN")) {
1053 if ((args = get_token(&buf, script, lineno))) {
1054 if (process_token(state->fdn, args, sizeof(state->fdn) - 1, ARG_STRING | ARG_NUMBER))
1055 ast_log(LOG_WARNING, "'%s' is not a valid token for FDN at line %d of %s\n", args, lineno, script);
1056 } else
1057 ast_log(LOG_WARNING, "Missing argument for FDN at line %d of %s\n", lineno, script);
1058 } else if (!strcasecmp(keyword, "KEY")) {
1059 if (!(args = get_token(&buf, script, lineno))) {
1060 ast_log(LOG_WARNING, "KEY definition missing name at line %d of %s\n", lineno, script);
1061 break;
1062 }
1063 if (process_token(vname, args, sizeof(vname) - 1, ARG_STRING)) {
1064 ast_log(LOG_WARNING, "'%s' is not a valid token for a KEY name at line %d of %s\n", args, lineno, script);
1065 break;
1066 }
1067 if (!(state->key = getkeybyname(state, vname, script, lineno))) {
1068 ast_log(LOG_WARNING, "Out of key space at line %d of %s\n", lineno, script);
1069 break;
1070 }
1071 if (state->key->defined) {
1072 ast_log(LOG_WARNING, "Cannot redefine key '%s' at line %d of %s\n", vname, lineno, script);
1073 break;
1074 }
1075 if (!(args = get_token(&buf, script, lineno)) || strcasecmp(args, "IS")) {
1076 ast_log(LOG_WARNING, "Expecting 'IS', but got '%s' at line %d of %s\n", args ? args : "<nothing>", lineno, script);
1077 break;
1078 }
1079 if (!(args = get_token(&buf, script, lineno))) {
1080 ast_log(LOG_WARNING, "KEY definition missing short name at line %d of %s\n", lineno, script);
1081 break;
1082 }
1083 if (process_token(tmp, args, sizeof(tmp) - 1, ARG_STRING)) {
1084 ast_log(LOG_WARNING, "'%s' is not a valid token for a KEY short name at line %d of %s\n", args, lineno, script);
1085 break;
1086 }
1087 if ((args = get_token(&buf, script, lineno))) {
1088 if (strcasecmp(args, "OR")) {
1089 ast_log(LOG_WARNING, "Expecting 'OR' but got '%s' instead at line %d of %s\n", args, lineno, script);
1090 break;
1091 }
1092 if (!(args = get_token(&buf, script, lineno))) {
1093 ast_log(LOG_WARNING, "KEY definition missing optional long name at line %d of %s\n", lineno, script);
1094 break;
1095 }
1096 if (process_token(tmp2, args, sizeof(tmp2) - 1, ARG_STRING)) {
1097 ast_log(LOG_WARNING, "'%s' is not a valid token for a KEY long name at line %d of %s\n", args, lineno, script);
1098 break;
1099 }
1100 } else {
1101 ast_copy_string(tmp2, tmp, sizeof(tmp2));
1102 }
1103 if (strlen(tmp2) > 18) {
1104 ast_log(LOG_WARNING, "Truncating full name to 18 characters at line %d of %s\n", lineno, script);
1105 tmp2[18] = '\0';
1106 }
1107 if (strlen(tmp) > 7) {
1108 ast_log(LOG_WARNING, "Truncating short name to 7 bytes at line %d of %s\n", lineno, script);
1109 tmp[7] = '\0';
1110 }
1111 /* Setup initial stuff */
1112 state->key->retstr[0] = 0x80;
1113 /* 1 has the length */
1114 state->key->retstr[2] = state->key->id;
1115 /* Put the Full name in */
1116 memcpy(state->key->retstr + 3, tmp2, strlen(tmp2));
1117 /* Update length */
1118 state->key->retstrlen = strlen(tmp2) + 3;
1119 /* Put trailing 0xff */
1120 state->key->retstr[state->key->retstrlen++] = 0xff;
1121 /* Put the short name */
1122 memcpy(state->key->retstr + state->key->retstrlen, tmp, strlen(tmp));
1123 /* Update length */
1124 state->key->retstrlen += strlen(tmp);
1125 /* Put trailing 0xff */
1126 state->key->retstr[state->key->retstrlen++] = 0xff;
1127 /* Record initial length */
1128 state->key->initlen = state->key->retstrlen;
1129 state->state = STATE_INKEY;
1130 } else if (!strcasecmp(keyword, "SUB")) {
1131 if (!(args = get_token(&buf, script, lineno))) {
1132 ast_log(LOG_WARNING, "SUB definition missing name at line %d of %s\n", lineno, script);
1133 break;
1134 }
1135 if (process_token(vname, args, sizeof(vname) - 1, ARG_STRING)) {
1136 ast_log(LOG_WARNING, "'%s' is not a valid token for a KEY name at line %d of %s\n", args, lineno, script);
1137 break;
1138 }
1139 if (!(state->sub = getsubbyname(state, vname, script, lineno))) {
1140 ast_log(LOG_WARNING, "Out of subroutine space at line %d of %s\n", lineno, script);
1141 break;
1142 }
1143 if (state->sub->defined) {
1144 ast_log(LOG_WARNING, "Cannot redefine subroutine '%s' at line %d of %s\n", vname, lineno, script);
1145 break;
1146 }
1147 /* Setup sub */
1148 state->sub->data[0] = 0x82;
1149 /* 1 is the length */
1150 state->sub->data[2] = 0x0; /* Clear extensibility bit */
1151 state->sub->datalen = 3;
1152 if (state->sub->id) {
1153 /* If this isn't the main subroutine, make a subroutine label for it */
1154 state->sub->data[3] = 9;
1155 state->sub->data[4] = state->sub->id;
1156 /* 5 is length */
1157 state->sub->data[6] = 0xff;
1158 state->sub->datalen = 7;
1159 }
1160 if (!(args = get_token(&buf, script, lineno)) || strcasecmp(args, "IS")) {
1161 ast_log(LOG_WARNING, "Expecting 'IS', but got '%s' at line %d of %s\n", args ? args : "<nothing>", lineno, script);
1162 break;
1163 }
1164 state->state = STATE_INSUB;
1165 } else if (!strcasecmp(keyword, "STATE")) {
1166 if (!(args = get_token(&buf, script, lineno))) {
1167 ast_log(LOG_WARNING, "STATE definition missing name at line %d of %s\n", lineno, script);
1168 break;
1169 }
1170 if (process_token(vname, args, sizeof(vname) - 1, ARG_STRING)) {
1171 ast_log(LOG_WARNING, "'%s' is not a valid token for a STATE name at line %d of %s\n", args, lineno, script);
1172 break;
1173 }
1174 if (getstatebyname(state, vname, script, lineno, 0)) {
1175 ast_log(LOG_WARNING, "State '%s' is already defined at line %d of %s\n", vname, lineno, script);
1176 break;
1177 }
1178 getstatebyname(state, vname, script, lineno, 1);
1179 } else if (!strcasecmp(keyword, "FLAG")) {
1180 if (!(args = get_token(&buf, script, lineno))) {
1181 ast_log(LOG_WARNING, "FLAG definition missing name at line %d of %s\n", lineno, script);
1182 break;
1183 }
1184 if (process_token(vname, args, sizeof(vname) - 1, ARG_STRING)) {
1185 ast_log(LOG_WARNING, "'%s' is not a valid token for a FLAG name at line %d of %s\n", args, lineno, script);
1186 break;
1187 }
1188 if (getflagbyname(state, vname, script, lineno, 0)) {
1189 ast_log(LOG_WARNING, "Flag '%s' is already defined\n", vname);
1190 break;
1191 }
1192 getflagbyname(state, vname, script, lineno, 1);
1193 } else if (!strcasecmp(keyword, "DISPLAY")) {
1194 lrci = 0;
1195 wi = 0;
1196 if (!(args = get_token(&buf, script, lineno))) {
1197 ast_log(LOG_WARNING, "SUB definition missing name at line %d of %s\n", lineno, script);
1198 break;
1199 }
1200 if (process_token(vname, args, sizeof(vname) - 1, ARG_STRING)) {
1201 ast_log(LOG_WARNING, "'%s' is not a valid token for a KEY name at line %d of %s\n", args, lineno, script);
1202 break;
1203 }
1204 if (getdisplaybyname(state, vname, script, lineno, 0)) {
1205 ast_log(LOG_WARNING, "State '%s' is already defined\n", vname);
1206 break;
1207 }
1208 if (!(disp = getdisplaybyname(state, vname, script, lineno, 1)))
1209 break;
1210 if (!(args = get_token(&buf, script, lineno)) || strcasecmp(args, "IS")) {
1211 ast_log(LOG_WARNING, "Missing 'IS' at line %d of %s\n", lineno, script);
1212 break;
1213 }
1214 if (!(args = get_token(&buf, script, lineno))) {
1215 ast_log(LOG_WARNING, "Missing Column 1 text at line %d of %s\n", lineno, script);
1216 break;
1217 }
1218 if (process_token(tmp, args, sizeof(tmp) - 1, ARG_STRING)) {
1219 ast_log(LOG_WARNING, "Token '%s' is not valid column 1 text at line %d of %s\n", args, lineno, script);
1220 break;
1221 }
1222 if (strlen(tmp) > 20) {
1223 ast_log(LOG_WARNING, "Truncating column one to 20 characters at line %d of %s\n", lineno, script);
1224 tmp[20] = '\0';
1225 }
1226 memcpy(disp->data + 5, tmp, strlen(tmp));
1227 disp->datalen = strlen(tmp) + 5;
1228 disp->data[disp->datalen++] = 0xff;
1229
1230 args = get_token(&buf, script, lineno);
1231 if (args && !process_token(tmp, args, sizeof(tmp) - 1, ARG_STRING)) {
1232 /* Got a column two */
1233 if (strlen(tmp) > 20) {
1234 ast_log(LOG_WARNING, "Truncating column two to 20 characters at line %d of %s\n", lineno, script);
1235 tmp[20] = '\0';
1236 }
1237 memcpy(disp->data + disp->datalen, tmp, strlen(tmp));
1238 disp->datalen += strlen(tmp);
1239 args = get_token(&buf, script, lineno);
1240 }
1241 while (args) {
1242 if (!strcasecmp(args, "JUSTIFY")) {
1243 args = get_token(&buf, script, lineno);
1244 if (!args) {
1245 ast_log(LOG_WARNING, "Qualifier 'JUSTIFY' requires an argument at line %d of %s\n", lineno, script);
1246 break;
1247 }
1248 lrci = getjustifybyname(args);
1249 if (lrci < 0) {
1250 ast_log(LOG_WARNING, "'%s' is not a valid justification at line %d of %s\n", args, lineno, script);
1251 break;
1252 }
1253 } else if (!strcasecmp(args, "WRAP")) {
1254 wi = 0x80;
1255 } else {
1256 ast_log(LOG_WARNING, "'%s' is not a known qualifier at line %d of %s\n", args, lineno, script);
1257 break;
1258 }
1259 args = get_token(&buf, script, lineno);
1260 }
1261 if (args) {
1262 /* Something bad happened */
1263 break;
1264 }
1265 disp->data[0] = 0x81;
1266 disp->data[1] = disp->datalen - 2;
1267 disp->data[2] = ((lrci & 0x3) << 6) | disp->id;
1268 disp->data[3] = wi;
1269 disp->data[4] = 0xff;
1270 } else {
1271 ast_log(LOG_WARNING, "Invalid or Unknown keyword '%s' in PROGRAM\n", keyword);
1272 }
1273 break;
1274 case STATE_INKEY:
1275 if (process_returncode(state->key, keyword, buf, state, script, lineno)) {
1276 if (!strcasecmp(keyword, "ENDKEY")) {
1277 /* Return to normal operation and increment current key */
1278 state->state = STATE_NORMAL;
1279 state->key->defined = 1;
1280 state->key->retstr[1] = state->key->retstrlen - 2;
1281 state->key = NULL;
1282 } else {
1283 ast_log(LOG_WARNING, "Invalid or Unknown keyword '%s' in SOFTKEY definition at line %d of %s\n", keyword, lineno, script);
1284 }
1285 }
1286 break;
1287 case STATE_INIF:
1288 if (process_opcode(state->sub, keyword, buf, state, script, lineno)) {
1289 if (!strcasecmp(keyword, "ENDIF")) {
1290 /* Return to normal SUB operation and increment current key */
1291 state->state = STATE_INSUB;
1292 state->sub->defined = 1;
1293 /* Store the proper number of instructions */
1294 state->sub->ifdata[2] = state->sub->ifinscount;
1295 } else if (!strcasecmp(keyword, "GOTO")) {
1296 if (!(args = get_token(&buf, script, lineno))) {
1297 ast_log(LOG_WARNING, "GOTO clause missing Subscript name at line %d of %s\n", lineno, script);
1298 break;
1299 }
1300 if (process_token(tmp, args, sizeof(tmp) - 1, ARG_STRING)) {
1301 ast_log(LOG_WARNING, "'%s' is not a valid subscript name token at line %d of %s\n", args, lineno, script);
1302 break;
1303 }
1304 if (!(newsub = getsubbyname(state, tmp, script, lineno)))
1305 break;
1306 /* Somehow you use GOTO to go to another place */
1307 state->sub->data[state->sub->datalen++] = 0x8;
1308 state->sub->data[state->sub->datalen++] = state->sub->ifdata[1];
1309 state->sub->data[state->sub->datalen++] = newsub->id;
1310 /* Terminate */
1311 state->sub->data[state->sub->datalen++] = 0xff;
1312 /* Increment counters */
1313 state->sub->inscount++;
1314 state->sub->ifinscount++;
1315 } else {
1316 ast_log(LOG_WARNING, "Invalid or Unknown keyword '%s' in IF clause at line %d of %s\n", keyword, lineno, script);
1317 }
1318 } else
1319 state->sub->ifinscount++;
1320 break;
1321 case STATE_INSUB:
1322 if (process_opcode(state->sub, keyword, buf, state, script, lineno)) {
1323 if (!strcasecmp(keyword, "ENDSUB")) {
1324 /* Return to normal operation and increment current key */
1325 state->state = STATE_NORMAL;
1326 state->sub->defined = 1;
1327 /* Store the proper length */
1328 state->sub->data[1] = state->sub->datalen - 2;
1329 if (state->sub->id) {
1330 /* if this isn't main, store number of instructions, too */
1331 state->sub->data[5] = state->sub->inscount;
1332 }
1333 state->sub = NULL;
1334 } else if (!strcasecmp(keyword, "IFEVENT")) {
1335 if (!(args = get_token(&buf, script, lineno))) {
1336 ast_log(LOG_WARNING, "IFEVENT clause missing Event name at line %d of %s\n", lineno, script);
1337 break;
1338 }
1339 if ((event = geteventbyname(args)) < 1) {
1340 ast_log(LOG_WARNING, "'%s' is not a valid event\n", args);
1341 break;
1342 }
1343 if (!(args = get_token(&buf, script, lineno)) || strcasecmp(args, "THEN")) {
1344 ast_log(LOG_WARNING, "IFEVENT clause missing 'THEN' at line %d of %s\n", lineno, script);
1345 break;
1346 }
1347 state->sub->ifinscount = 0;
1348 state->sub->ifdata = state->sub->data + state->sub->datalen;
1349 /* Reserve header and insert op codes */
1350 state->sub->ifdata[0] = 0x1;
1351 state->sub->ifdata[1] = event;
1352 /* 2 is for the number of instructions */
1353 state->sub->ifdata[3] = 0xff;
1354 state->sub->datalen += 4;
1355 /* Update Subscript instruction count */
1356 state->sub->inscount++;
1357 state->state = STATE_INIF;
1358 } else {
1359 ast_log(LOG_WARNING, "Invalid or Unknown keyword '%s' in SUB definition at line %d of %s\n", keyword, lineno, script);
1360 }
1361 }
1362 break;
1363 default:
1364 ast_log(LOG_WARNING, "Can't process keyword '%s' in weird state %d\n", keyword, state->state);
1365 }
1366 return 0;
1367}
1368
1369static struct adsi_script *compile_script(const char *script)
1370{
1371 FILE *f;
1372 char fn[256], buf[256], *c;
1373 int lineno = 0, x, err;
1374 struct adsi_script *scr;
1375
1376 if (script[0] == '/')
1377 ast_copy_string(fn, script, sizeof(fn));
1378 else
1379 snprintf(fn, sizeof(fn), "%s/%s", ast_config_AST_CONFIG_DIR, script);
1380
1381 if (!(f = fopen(fn, "r"))) {
1382 ast_log(LOG_WARNING, "Can't open file '%s'\n", fn);
1383 return NULL;
1384 }
1385
1386 if (!(scr = ast_calloc(1, sizeof(*scr)))) {
1387 fclose(f);
1388 return NULL;
1389 }
1390
1391 /* Create "main" as first subroutine */
1392 getsubbyname(scr, "main", NULL, 0);
1393 while (!feof(f)) {
1394 if (!fgets(buf, sizeof(buf), f)) {
1395 continue;
1396 }
1397 if (!feof(f)) {
1398 lineno++;
1399 /* Trim off trailing return */
1400 buf[strlen(buf) - 1] = '\0';
1401 /* Strip comments */
1402 if ((c = strchr(buf, ';')))
1403 *c = '\0';
1404 if (!ast_strlen_zero(buf))
1405 adsi_process(scr, buf, script, lineno);
1406 }
1407 }
1408 fclose(f);
1409 /* Make sure we're in the main routine again */
1410 switch(scr->state) {
1411 case STATE_NORMAL:
1412 break;
1413 case STATE_INSUB:
1414 ast_log(LOG_WARNING, "Missing ENDSUB at end of file %s\n", script);
1415 ast_free(scr);
1416 return NULL;
1417 case STATE_INKEY:
1418 ast_log(LOG_WARNING, "Missing ENDKEY at end of file %s\n", script);
1419 ast_free(scr);
1420 return NULL;
1421 }
1422 err = 0;
1423
1424 /* Resolve all keys and record their lengths */
1425 for (x = 0; x < scr->numkeys; x++) {
1426 if (!scr->keys[x].defined) {
1427 ast_log(LOG_WARNING, "Key '%s' referenced but never defined in file %s\n", scr->keys[x].vname, fn);
1428 err++;
1429 }
1430 }
1431
1432 /* Resolve all subs */
1433 for (x = 0; x < scr->numsubs; x++) {
1434 if (!scr->subs[x].defined) {
1435 ast_log(LOG_WARNING, "Subscript '%s' referenced but never defined in file %s\n", scr->subs[x].vname, fn);
1436 err++;
1437 }
1438 if (x == (scr->numsubs - 1)) {
1439 /* Clear out extension bit on last message */
1440 scr->subs[x].data[2] = 0x80;
1441 }
1442 }
1443
1444 if (err) {
1445 ast_free(scr);
1446 return NULL;
1447 }
1448 return scr;
1449}
1450
1451#ifdef DUMP_MESSAGES
1452static void dump_message(char *type, char *vname, unsigned char *buf, int buflen)
1453{
1454 int x;
1455 printf("%s %s: [ ", type, vname);
1456 for (x = 0; x < buflen; x++)
1457 printf("%02hhx ", buf[x]);
1458 printf("]\n");
1459}
1460#endif
1461
1462static int adsi_prog(struct ast_channel *chan, const char *script)
1463{
1464 struct adsi_script *scr;
1465 int x, bytes;
1466 unsigned char buf[1024];
1467
1468 if (!(scr = compile_script(script)))
1469 return -1;
1470
1471 /* Start an empty ADSI Session */
1472 if (ast_adsi_load_session(chan, NULL, 0, 1) < 1)
1473 return -1;
1474
1475 /* Now begin the download attempt */
1476 if (ast_adsi_begin_download(chan, scr->desc, scr->fdn, scr->sec, scr->ver)) {
1477 /* User rejected us for some reason */
1478 ast_verb(3, "User rejected download attempt\n");
1479 ast_log(LOG_NOTICE, "User rejected download on channel %s\n", ast_channel_name(chan));
1480 ast_free(scr);
1481 return -1;
1482 }
1483
1484 bytes = 0;
1485 /* Start with key definitions */
1486 for (x = 0; x < scr->numkeys; x++) {
1487 if (bytes + scr->keys[x].retstrlen > 253) {
1488 /* Send what we've collected so far */
1489 if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD)) {
1490 ast_log(LOG_WARNING, "Unable to send chunk ending at %d\n", x);
1491 return -1;
1492 }
1493 bytes =0;
1494 }
1495 memcpy(buf + bytes, scr->keys[x].retstr, scr->keys[x].retstrlen);
1496 bytes += scr->keys[x].retstrlen;
1497#ifdef DUMP_MESSAGES
1498 dump_message("Key", scr->keys[x].vname, scr->keys[x].retstr, scr->keys[x].retstrlen);
1499#endif
1500 }
1501 if (bytes) {
1502 if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD)) {
1503 ast_log(LOG_WARNING, "Unable to send chunk ending at %d\n", x);
1504 return -1;
1505 }
1506 }
1507
1508 bytes = 0;
1509 /* Continue with the display messages */
1510 for (x = 0; x < scr->numdisplays; x++) {
1511 if (bytes + scr->displays[x].datalen > 253) {
1512 /* Send what we've collected so far */
1513 if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD)) {
1514 ast_log(LOG_WARNING, "Unable to send chunk ending at %d\n", x);
1515 return -1;
1516 }
1517 bytes =0;
1518 }
1519 memcpy(buf + bytes, scr->displays[x].data, scr->displays[x].datalen);
1520 bytes += scr->displays[x].datalen;
1521#ifdef DUMP_MESSAGES
1522 dump_message("Display", scr->displays[x].vname, scr->displays[x].data, scr->displays[x].datalen);
1523#endif
1524 }
1525 if (bytes) {
1526 if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD)) {
1527 ast_log(LOG_WARNING, "Unable to send chunk ending at %d\n", x);
1528 return -1;
1529 }
1530 }
1531
1532 bytes = 0;
1533 /* Send subroutines */
1534 for (x = 0; x < scr->numsubs; x++) {
1535 if (bytes + scr->subs[x].datalen > 253) {
1536 /* Send what we've collected so far */
1537 if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD)) {
1538 ast_log(LOG_WARNING, "Unable to send chunk ending at %d\n", x);
1539 return -1;
1540 }
1541 bytes =0;
1542 }
1543 memcpy(buf + bytes, scr->subs[x].data, scr->subs[x].datalen);
1544 bytes += scr->subs[x].datalen;
1545#ifdef DUMP_MESSAGES
1546 dump_message("Sub", scr->subs[x].vname, scr->subs[x].data, scr->subs[x].datalen);
1547#endif
1548 }
1549 if (bytes) {
1550 if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD)) {
1551 ast_log(LOG_WARNING, "Unable to send chunk ending at %d\n", x);
1552 return -1;
1553 }
1554 }
1555
1556 bytes = 0;
1557 bytes += ast_adsi_display(buf, ADSI_INFO_PAGE, 1, ADSI_JUST_LEFT, 0, "Download complete.", "");
1558 bytes += ast_adsi_set_line(buf, ADSI_INFO_PAGE, 1);
1559 if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY) < 0)
1560 return -1;
1561 if (ast_adsi_end_download(chan)) {
1562 /* Download failed for some reason */
1563 ast_verb(3, "Download attempt failed\n");
1564 ast_log(LOG_NOTICE, "Download failed on %s\n", ast_channel_name(chan));
1565 ast_free(scr);
1566 return -1;
1567 }
1568 ast_free(scr);
1570 return 0;
1571}
1572
1573static int adsi_exec(struct ast_channel *chan, const char *data)
1574{
1575 int res = 0;
1576
1577 if (ast_strlen_zero(data))
1578 data = "asterisk.adsi";
1579
1580 if (!ast_adsi_available(chan)) {
1581 ast_verb(3, "ADSI Unavailable on CPE. Not bothering to try.\n");
1582 } else {
1583 ast_verb(3, "ADSI Available on CPE. Attempting Upload.\n");
1584 res = adsi_prog(chan, data);
1585 }
1586
1587 return res;
1588}
1589
1590static int unload_module(void)
1591{
1593}
1594
1595/*!
1596 * \brief Load the module
1597 *
1598 * Module loading including tests for configuration or dependencies.
1599 * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE,
1600 * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails
1601 * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the
1602 * configuration file or other non-critical problem return
1603 * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS.
1604 */
1605static int load_module(void)
1606{
1610}
1611
1612AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Asterisk ADSI Programming Application",
1613 .support_level = AST_MODULE_SUPPORT_DEPRECATED,
1614 .load = load_module,
1615 .unload = unload_module,
1616 .requires = "res_adsi",
ADSI Support (built upon Caller*ID)
int ast_adsi_display(unsigned char *buf, int page, int line, int just, int wrap, char *col1, char *col2)
Loads a line of info into the display.
Definition: adsi.c:274
#define ADSI_INFO_PAGE
Definition: adsi.h:106
#define ADSI_MSG_DOWNLOAD
Definition: adsi.h:33
int ast_adsi_unload_session(struct ast_channel *chan)
Definition: adsi.c:87
#define ADSI_MSG_DISPLAY
Definition: adsi.h:32
int ast_adsi_begin_download(struct ast_channel *chan, char *service, unsigned char *fdn, unsigned char *sec, int version)
Definition: adsi.c:32
#define ADSI_JUST_LEFT
Definition: adsi.h:112
int ast_adsi_set_line(unsigned char *buf, int page, int line)
Sets the current line and page.
Definition: adsi.c:285
int ast_adsi_load_session(struct ast_channel *chan, unsigned char *app, int ver, int data)
Check if scripts for a given app are already loaded. Version may be -1, if any version is okay,...
Definition: adsi.c:76
int ast_adsi_end_download(struct ast_channel *chan)
Definition: adsi.c:43
int ast_adsi_available(struct ast_channel *chan)
Returns non-zero if Channel does or might support ADSI.
Definition: adsi.c:263
int ast_adsi_transmit_message(struct ast_channel *chan, unsigned char *msg, int msglen, int msgtype)
Definition: adsi.c:98
static int process_returncode(struct adsi_soft_key *key, char *code, char *args, struct adsi_script *state, const char *script, int lineno)
Definition: app_adsiprog.c:955
static int cleardisplay(char *buf, char *name, int id, char *args, struct adsi_script *istate, const char *script, int lineno)
Definition: app_adsiprog.c:750
static int process_token(void *out, char *src, int maxlen, int argtype)
Definition: app_adsiprog.c:196
static const char app[]
Definition: app_adsiprog.c:56
static int clearflag(char *buf, char *name, int id, char *args, struct adsi_script *state, const char *script, int lineno)
Definition: app_adsiprog.c:487
static const struct adsi_event events[]
Definition: app_adsiprog.c:85
static int goto_line(char *buf, char *name, int id, char *args, struct adsi_script *state, const char *script, int lineno)
Definition: app_adsiprog.c:305
static struct adsi_script * compile_script(const char *script)
static int showkeys(char *buf, char *name, int id, char *args, struct adsi_script *state, const char *script, int lineno)
Definition: app_adsiprog.c:652
static struct adsi_flag * getflagbyname(struct adsi_script *state, char *name, const char *script, int lineno, int create)
Definition: app_adsiprog.c:435
#define ARG_NUMBER
Definition: app_adsiprog.c:129
static int adsi_exec(struct ast_channel *chan, const char *data)
static struct adsi_soft_key * getkeybyname(struct adsi_script *state, char *name, const char *script, int lineno)
Definition: app_adsiprog.c:560
#define ARG_STRING
Definition: app_adsiprog.c:128
static int subscript(char *buf, char *name, int id, char *args, struct adsi_script *state, const char *script, int lineno)
Definition: app_adsiprog.c:798
static struct adsi_subscript * getsubbyname(struct adsi_script *state, char *name, const char *script, int lineno)
Definition: app_adsiprog.c:581
static int geteventbyname(char *name)
Definition: app_adsiprog.c:536
static int process_opcode(struct adsi_subscript *sub, char *code, char *args, struct adsi_script *state, const char *script, int lineno)
Definition: app_adsiprog.c:984
static int adsi_process(struct adsi_script *state, char *buf, const char *script, int lineno)
static int send_delay(char *buf, char *name, int id, char *args, struct adsi_script *state, const char *script, int lineno)
Definition: app_adsiprog.c:371
static int cleartimer(char *buf, char *name, int id, char *args, struct adsi_script *istate, const char *script, int lineno)
Definition: app_adsiprog.c:417
#define MAX_MAIN_LEN
Definition: app_adsiprog.c:126
static char * validdtmf
Definition: app_adsiprog.c:273
static const struct adsi_key_cmd kcmds[]
Definition: app_adsiprog.c:893
static int adsi_prog(struct ast_channel *chan, const char *script)
static int goto_line_rel(char *buf, char *name, int id, char *args, struct adsi_script *state, const char *script, int lineno)
Definition: app_adsiprog.c:338
#define STATE_INIF
Definition: app_adsiprog.c:122
static struct adsi_display * getdisplaybyname(struct adsi_script *state, char *name, const char *script, int lineno, int create)
Definition: app_adsiprog.c:627
static int setflag(char *buf, char *name, int id, char *args, struct adsi_script *state, const char *script, int lineno)
Definition: app_adsiprog.c:460
static int set_state(char *buf, char *name, int id, char *args, struct adsi_script *istate, const char *script, int lineno)
Definition: app_adsiprog.c:396
static int starttimer(char *buf, char *name, int id, char *args, struct adsi_script *istate, const char *script, int lineno)
Definition: app_adsiprog.c:514
static const struct adsi_key_cmd opcmds[]
Definition: app_adsiprog.c:935
static int load_module(void)
Load the module.
static int getjustifybyname(char *name)
Definition: app_adsiprog.c:548
#define MAX_SUB_LEN
Definition: app_adsiprog.c:125
static int onevent(char *buf, char *name, int id, char *args, struct adsi_script *state, const char *script, int lineno)
Definition: app_adsiprog.c:823
static int unload_module(void)
#define STATE_INKEY
Definition: app_adsiprog.c:120
static char * get_token(char **buf, const char *script, int lineno)
Definition: app_adsiprog.c:243
static struct adsi_state * getstatebyname(struct adsi_script *state, char *name, const char *script, int lineno, int create)
Definition: app_adsiprog.c:602
static int send_dtmf(char *buf, char *name, int id, char *args, struct adsi_script *state, const char *script, int lineno)
Definition: app_adsiprog.c:275
static const struct adsi_event justify[]
Definition: app_adsiprog.c:112
static int showdisplay(char *buf, char *name, int id, char *args, struct adsi_script *state, const char *script, int lineno)
Definition: app_adsiprog.c:699
#define STATE_NORMAL
Definition: app_adsiprog.c:119
static int clearcbone(char *buf, char *name, int id, char *args, struct adsi_script *istate, const char *script, int lineno)
Definition: app_adsiprog.c:774
#define STATE_INSUB
Definition: app_adsiprog.c:121
static int digitcollect(char *buf, char *name, int id, char *args, struct adsi_script *istate, const char *script, int lineno)
Definition: app_adsiprog.c:786
static int digitdirect(char *buf, char *name, int id, char *args, struct adsi_script *istate, const char *script, int lineno)
Definition: app_adsiprog.c:762
#define MAX_RET_CODE
Definition: app_adsiprog.c:124
enum queue_result id
Definition: app_queue.c:1638
Asterisk main include file. File version handling, generic pbx functions.
#define ast_free(a)
Definition: astmm.h:180
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
#define ast_log
Definition: astobj2.c:42
static int tmp()
Definition: bt_open.c:389
enum cc_state state
Definition: ccss.c:393
static const char type[]
Definition: chan_ooh323.c:109
General Asterisk PBX channel definitions.
const char * ast_channel_name(const struct ast_channel *chan)
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
long int flag
Definition: f2c.h:83
#define max(a, b)
Definition: f2c.h:198
Generic File Format Support. Should be included by clients of the file handling routines....
static const char name[]
Definition: format_mp3.c:68
#define ast_verb(level,...)
#define LOG_NOTICE
#define LOG_WARNING
Asterisk locking-related definitions:
Asterisk module definitions.
@ AST_MODFLAG_DEFAULT
Definition: module.h:329
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition: module.h:557
@ AST_MODULE_SUPPORT_DEPRECATED
Definition: module.h:123
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
int ast_unregister_application(const char *app)
Unregister an application.
Definition: pbx_app.c:392
@ AST_MODULE_LOAD_SUCCESS
Definition: module.h:70
@ AST_MODULE_LOAD_DECLINE
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
#define ast_register_application_xml(app, execute)
Register an application using XML documentation.
Definition: module.h:640
Asterisk file paths, configured in asterisk.conf.
const char * ast_config_AST_CONFIG_DIR
Definition: options.c:151
Core PBX routines and definitions.
struct stasis_forward * sub
Definition: res_corosync.c:240
#define NULL
Definition: resample.c:96
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one.
Definition: strings.h:80
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
char data[70]
Definition: app_adsiprog.c:164
char vname[40]
Definition: app_adsiprog.c:162
const char * name
Definition: app_adsiprog.c:82
char vname[40]
Definition: app_adsiprog.c:157
int(* add_args)(char *buf, char *name, int id, char *args, struct adsi_script *state, const char *script, int lineno)
Definition: app_adsiprog.c:890
struct adsi_subscript subs[128]
Definition: app_adsiprog.c:184
unsigned char fdn[5]
Definition: app_adsiprog.c:191
char desc[19]
Definition: app_adsiprog.c:190
unsigned char sec[5]
Definition: app_adsiprog.c:189
struct adsi_subscript * sub
Definition: app_adsiprog.c:176
struct adsi_state states[256]
Definition: app_adsiprog.c:180
struct adsi_flag flags[7]
Definition: app_adsiprog.c:186
struct adsi_display displays[63]
Definition: app_adsiprog.c:178
struct adsi_soft_key keys[62]
Definition: app_adsiprog.c:182
struct adsi_soft_key * key
Definition: app_adsiprog.c:175
char retstr[80]
Definition: app_adsiprog.c:137
char vname[40]
Definition: app_adsiprog.c:132
char vname[40]
Definition: app_adsiprog.c:152
char data[2048]
Definition: app_adsiprog.c:148
char vname[40]
Definition: app_adsiprog.c:141
Main Channel structure associated with a channel.
Definition: astman.c:222
const char * args
static struct test_val a
static struct test_val c
FILE * out
Definition: utils/frame.c:33
Utility functions.
#define ARRAY_LEN(a)
Definition: utils.h:666