Enable understanding of service identifier and provisioning version
[asterisk/asterisk.git] / channels / iax2-parser.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Implementation of Inter-Asterisk eXchange
5  * 
6  * Copyright (C) 2003, Digium
7  *
8  * Mark Spencer <markster@linux-support.net>
9  *
10  * This program is free software, distributed under the terms of
11  * the GNU General Public License
12  */
13
14 #include <sys/types.h>
15 #include <sys/socket.h>
16 #include <string.h>
17 #include <netinet/in.h>
18 #include <asterisk/frame.h>
19 #include <arpa/inet.h>
20 #include <unistd.h>
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include "iax2.h"
24 #include "iax2-parser.h"
25
26
27 static int frames = 0;
28 static int iframes = 0;
29 static int oframes = 0;
30
31 static void internaloutput(const char *str)
32 {
33         printf(str);
34 }
35
36 static void internalerror(const char *str)
37 {
38         fprintf(stderr, "WARNING: %s", str);
39 }
40
41 static void (*outputf)(const char *str) = internaloutput;
42 static void (*errorf)(const char *str) = internalerror;
43
44 static void dump_addr(char *output, int maxlen, void *value, int len)
45 {
46         struct sockaddr_in sin;
47         if (len == sizeof(sin)) {
48                 memcpy(&sin, value, len);
49                 snprintf(output, maxlen, "IPV4 %s:%d", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
50         } else {
51                 snprintf(output, maxlen, "Invalid Address");
52         }
53 }
54
55 static void dump_string(char *output, int maxlen, void *value, int len)
56 {
57         maxlen--;
58         if (maxlen > len)
59                 maxlen = len;
60         strncpy(output,value, maxlen);
61         output[maxlen] = '\0';
62 }
63
64 static void dump_int(char *output, int maxlen, void *value, int len)
65 {
66         if (len == sizeof(unsigned int))
67                 snprintf(output, maxlen, "%lu", (unsigned long)ntohl(*((unsigned int *)value)));
68         else
69                 snprintf(output, maxlen, "Invalid INT");
70 }
71
72 static void dump_short(char *output, int maxlen, void *value, int len)
73 {
74         if (len == sizeof(unsigned short))
75                 snprintf(output, maxlen, "%d", ntohs(*((unsigned short *)value)));
76         else
77                 snprintf(output, maxlen, "Invalid SHORT");
78 }
79
80 static void dump_byte(char *output, int maxlen, void *value, int len)
81 {
82         if (len == sizeof(unsigned char))
83                 snprintf(output, maxlen, "%d", ntohs(*((unsigned char *)value)));
84         else
85                 snprintf(output, maxlen, "Invalid BYTE");
86 }
87
88 static struct iax2_ie {
89         int ie;
90         char *name;
91         void (*dump)(char *output, int maxlen, void *value, int len);
92 } ies[] = {
93         { IAX_IE_CALLED_NUMBER, "CALLED NUMBER", dump_string },
94         { IAX_IE_CALLING_NUMBER, "CALLING NUMBER", dump_string },
95         { IAX_IE_CALLING_ANI, "ANI", dump_string },
96         { IAX_IE_CALLING_NAME, "CALLING NAME", dump_string },
97         { IAX_IE_CALLED_CONTEXT, "CALLED CONTEXT", dump_string },
98         { IAX_IE_USERNAME, "USERNAME", dump_string },
99         { IAX_IE_PASSWORD, "PASSWORD", dump_string },
100         { IAX_IE_CAPABILITY, "CAPABILITY", dump_int },
101         { IAX_IE_FORMAT, "FORMAT", dump_int },
102         { IAX_IE_LANGUAGE, "LANGUAGE", dump_string },
103         { IAX_IE_VERSION, "VERSION", dump_short },
104         { IAX_IE_ADSICPE, "ADSICPE", dump_short },
105         { IAX_IE_DNID, "DNID", dump_string },
106         { IAX_IE_AUTHMETHODS, "AUTHMETHODS", dump_short },
107         { IAX_IE_CHALLENGE, "CHALLENGE", dump_string },
108         { IAX_IE_MD5_RESULT, "MD5 RESULT", dump_string },
109         { IAX_IE_RSA_RESULT, "RSA RESULT", dump_string },
110         { IAX_IE_APPARENT_ADDR, "APPARENT ADDRESS", dump_addr },
111         { IAX_IE_REFRESH, "REFRESH", dump_short },
112         { IAX_IE_DPSTATUS, "DIALPLAN STATUS", dump_short },
113         { IAX_IE_CALLNO, "CALL NUMBER", dump_short },
114         { IAX_IE_CAUSE, "CAUSE", dump_string },
115         { IAX_IE_IAX_UNKNOWN, "UNKNOWN IAX CMD", dump_byte },
116         { IAX_IE_MSGCOUNT, "MESSAGE COUNT", dump_short },
117         { IAX_IE_AUTOANSWER, "AUTO ANSWER REQ" },
118         { IAX_IE_TRANSFERID, "TRANSFER ID", dump_int },
119         { IAX_IE_RDNIS, "REFERRING DNIS", dump_string },
120         { IAX_IE_PROVISIONING, "PROVISIONING" },
121         { IAX_IE_AESPROVISIONING, "AES PROVISIONG" },
122         { IAX_IE_DATETIME, "DATE TIME", dump_int },
123         { IAX_IE_DEVICETYPE, "DEVICE TYPE", dump_string },
124         { IAX_IE_SERVICEIDENT, "SERVICE IDENT", dump_string },
125         { IAX_IE_FIRMWAREVER, "FIRMWARE VER", dump_short },
126         { IAX_IE_FWBLOCKDESC, "FW BLOCK DESC", dump_int },
127         { IAX_IE_FWBLOCKDATA, "FW BLOCK DATA" },
128         { IAX_IE_PROVVER, "PROVISIONG VER", dump_int },
129 };
130
131 const char *iax_ie2str(int ie)
132 {
133         int x;
134         for (x=0;x<sizeof(ies) / sizeof(ies[0]); x++) {
135                 if (ies[x].ie == ie)
136                         return ies[x].name;
137         }
138         return "Unknown IE";
139 }
140
141 static void dump_ies(unsigned char *iedata, int len)
142 {
143         int ielen;
144         int ie;
145         int x;
146         int found;
147         char interp[80];
148         char tmp[256];
149         if (len < 2)
150                 return;
151         while(len > 2) {
152                 ie = iedata[0];
153                 ielen = iedata[1];
154                 if (ielen + 2> len) {
155                         snprintf(tmp, sizeof(tmp), "Total IE length of %d bytes exceeds remaining frame length of %d bytes\n", ielen + 2, len);
156                         outputf(tmp);
157                         return;
158                 }
159                 found = 0;
160                 for (x=0;x<sizeof(ies) / sizeof(ies[0]); x++) {
161                         if (ies[x].ie == ie) {
162                                 if (ies[x].dump) {
163                                         ies[x].dump(interp, sizeof(interp), iedata + 2, ielen);
164                                         snprintf(tmp, sizeof(tmp), "   %-15.15s : %s\n", ies[x].name, interp);
165                                         outputf(tmp);
166                                 } else {
167                                         if (ielen)
168                                                 snprintf(interp, sizeof(interp), "%d bytes", ielen);
169                                         else
170                                                 strcpy(interp, "Present");
171                                         snprintf(tmp, sizeof(tmp), "   %-15.15s : %s\n", ies[x].name, interp);
172                                         outputf(tmp);
173                                 }
174                                 found++;
175                         }
176                 }
177                 if (!found) {
178                         snprintf(tmp, sizeof(tmp), "   Unknown IE %03d  : Present\n", ie);
179                         outputf(tmp);
180                 }
181                 iedata += (2 + ielen);
182                 len -= (2 + ielen);
183         }
184         outputf("\n");
185 }
186
187 void iax_showframe(struct iax_frame *f, struct ast_iax2_full_hdr *fhi, int rx, struct sockaddr_in *sin, int datalen)
188 {
189         char *frames[] = {
190                 "(0?)",
191                 "DTMF   ",
192                 "VOICE  ",
193                 "VIDEO  ",
194                 "CONTROL",
195                 "NULL   ",
196                 "IAX    ",
197                 "TEXT   ",
198                 "IMAGE  " };
199         char *iaxs[] = {
200                 "(0?)",
201                 "NEW    ",
202                 "PING   ",
203                 "PONG   ",
204                 "ACK    ",
205                 "HANGUP ",
206                 "REJECT ",
207                 "ACCEPT ",
208                 "AUTHREQ",
209                 "AUTHREP",
210                 "INVAL  ",
211                 "LAGRQ  ",
212                 "LAGRP  ",
213                 "REGREQ ",
214                 "REGAUTH",
215                 "REGACK ",
216                 "REGREJ ",
217                 "REGREL ",
218                 "VNAK   ",
219                 "DPREQ  ",
220                 "DPREP  ",
221                 "DIAL   ",
222                 "TXREQ  ",
223                 "TXCNT  ",
224                 "TXACC  ",
225                 "TXREADY",
226                 "TXREL  ",
227                 "TXREJ  ",
228                 "QUELCH ",
229                 "UNQULCH",
230                 "POKE",
231                 "PAGE",
232                 "MWI",
233                 "UNSUPPORTED",
234                 "TRANSFER",
235                 "PROVISION",
236                 "FWDOWNLD",
237                 "FWDATA"
238         };
239         char *cmds[] = {
240                 "(0?)",
241                 "HANGUP ",
242                 "RING   ",
243                 "RINGING",
244                 "ANSWER ",
245                 "BUSY   ",
246                 "TKOFFHK ",
247                 "OFFHOOK" };
248         struct ast_iax2_full_hdr *fh;
249         char retries[20];
250         char class2[20];
251         char subclass2[20];
252         char *class;
253         char *subclass;
254         char tmp[256];
255         if (f) {
256                 fh = f->data;
257                 snprintf(retries, sizeof(retries), "%03d", f->retries);
258         } else {
259                 fh = fhi;
260                 if (ntohs(fh->dcallno) & IAX_FLAG_RETRANS)
261                         strcpy(retries, "Yes");
262                 else
263                         strcpy(retries, "No");
264         }
265         if (!(ntohs(fh->scallno) & IAX_FLAG_FULL)) {
266                 /* Don't mess with mini-frames */
267                 return;
268         }
269         if (fh->type > sizeof(frames)/sizeof(char *)) {
270                 snprintf(class2, sizeof(class2), "(%d?)", fh->type);
271                 class = class2;
272         } else {
273                 class = frames[(int)fh->type];
274         }
275         if (fh->type == AST_FRAME_DTMF) {
276                 sprintf(subclass2, "%c", fh->csub);
277                 subclass = subclass2;
278         } else if (fh->type == AST_FRAME_IAX) {
279                 if (fh->csub >= sizeof(iaxs)/sizeof(iaxs[0])) {
280                         snprintf(subclass2, sizeof(subclass2), "(%d?)", fh->csub);
281                         subclass = subclass2;
282                 } else {
283                         subclass = iaxs[(int)fh->csub];
284                 }
285         } else if (fh->type == AST_FRAME_CONTROL) {
286                 if (fh->csub > sizeof(cmds)/sizeof(char *)) {
287                         snprintf(subclass2, sizeof(subclass2), "(%d?)", fh->csub);
288                         subclass = subclass2;
289                 } else {
290                         subclass = cmds[(int)fh->csub];
291                 }
292         } else {
293                 snprintf(subclass2, sizeof(subclass2), "%d", fh->csub);
294                 subclass = subclass2;
295         }
296 snprintf(tmp, sizeof(tmp), 
297 "%s-Frame Retry[%s] -- OSeqno: %3.3d ISeqno: %3.3d Type: %s Subclass: %s\n",
298         (rx ? "Rx" : "Tx"),
299         retries, fh->oseqno, fh->iseqno, class, subclass);
300         outputf(tmp);
301 snprintf(tmp, sizeof(tmp), 
302 "   Timestamp: %05lums  SCall: %5.5d  DCall: %5.5d [%s:%d]\n",
303         (unsigned long)ntohl(fh->ts),
304         ntohs(fh->scallno) & ~IAX_FLAG_FULL, ntohs(fh->dcallno) & ~IAX_FLAG_RETRANS,
305                 inet_ntoa(sin->sin_addr), ntohs(sin->sin_port));
306         outputf(tmp);
307         if (fh->type == AST_FRAME_IAX)
308                 dump_ies(fh->iedata, datalen);
309 }
310
311 int iax_ie_append_raw(struct iax_ie_data *ied, unsigned char ie, void *data, int datalen)
312 {
313         char tmp[256];
314         if (datalen > (sizeof(ied->buf) - ied->pos)) {
315                 snprintf(tmp, sizeof(tmp), "Out of space for ie '%s' (%d), need %d have %d\n", iax_ie2str(ie), ie, datalen, sizeof(ied->buf) - ied->pos);
316                 errorf(tmp);
317                 return -1;
318         }
319         ied->buf[ied->pos++] = ie;
320         ied->buf[ied->pos++] = datalen;
321         memcpy(ied->buf + ied->pos, data, datalen);
322         ied->pos += datalen;
323         return 0;
324 }
325
326 int iax_ie_append_addr(struct iax_ie_data *ied, unsigned char ie, struct sockaddr_in *sin)
327 {
328         return iax_ie_append_raw(ied, ie, sin, sizeof(struct sockaddr_in));
329 }
330
331 int iax_ie_append_int(struct iax_ie_data *ied, unsigned char ie, unsigned int value) 
332 {
333         unsigned int newval;
334         newval = htonl(value);
335         return iax_ie_append_raw(ied, ie, &newval, sizeof(newval));
336 }
337
338 int iax_ie_append_short(struct iax_ie_data *ied, unsigned char ie, unsigned short value) 
339 {
340         unsigned short newval;
341         newval = htons(value);
342         return iax_ie_append_raw(ied, ie, &newval, sizeof(newval));
343 }
344
345 int iax_ie_append_str(struct iax_ie_data *ied, unsigned char ie, unsigned char *str)
346 {
347         return iax_ie_append_raw(ied, ie, str, strlen(str));
348 }
349
350 int iax_ie_append_byte(struct iax_ie_data *ied, unsigned char ie, unsigned char dat)
351 {
352         return iax_ie_append_raw(ied, ie, &dat, 1);
353 }
354
355 int iax_ie_append(struct iax_ie_data *ied, unsigned char ie) 
356 {
357         return iax_ie_append_raw(ied, ie, NULL, 0);
358 }
359
360 void iax_set_output(void (*func)(const char *))
361 {
362         outputf = func;
363 }
364
365 void iax_set_error(void (*func)(const char *))
366 {
367         errorf = func;
368 }
369
370 int iax_parse_ies(struct iax_ies *ies, unsigned char *data, int datalen)
371 {
372         /* Parse data into information elements */
373         int len;
374         int ie;
375         char tmp[256];
376         memset(ies, 0, sizeof(struct iax_ies));
377         ies->msgcount = -1;
378         ies->firmwarever = -1;
379         while(datalen >= 2) {
380                 ie = data[0];
381                 len = data[1];
382                 if (len > datalen - 2) {
383                         errorf("Information element length exceeds message size\n");
384                         return -1;
385                 }
386                 switch(ie) {
387                 case IAX_IE_CALLED_NUMBER:
388                         ies->called_number = data + 2;
389                         break;
390                 case IAX_IE_CALLING_NUMBER:
391                         ies->calling_number = data + 2;
392                         break;
393                 case IAX_IE_CALLING_ANI:
394                         ies->calling_ani = data + 2;
395                         break;
396                 case IAX_IE_CALLING_NAME:
397                         ies->calling_name = data + 2;
398                         break;
399                 case IAX_IE_CALLED_CONTEXT:
400                         ies->called_context = data + 2;
401                         break;
402                 case IAX_IE_USERNAME:
403                         ies->username = data + 2;
404                         break;
405                 case IAX_IE_PASSWORD:
406                         ies->password = data + 2;
407                         break;
408                 case IAX_IE_CAPABILITY:
409                         if (len != sizeof(unsigned int)) {
410                                 snprintf(tmp, sizeof(tmp), "Expecting capability to be %d bytes long but was %d\n", sizeof(unsigned int), len);
411                                 errorf(tmp);
412                         } else
413                                 ies->capability = ntohl(*((unsigned int *)(data + 2)));
414                         break;
415                 case IAX_IE_FORMAT:
416                         if (len != sizeof(unsigned int)) {
417                                 snprintf(tmp, sizeof(tmp), "Expecting format to be %d bytes long but was %d\n", sizeof(unsigned int), len);
418                                 errorf(tmp);
419                         } else
420                                 ies->format = ntohl(*((unsigned int *)(data + 2)));
421                         break;
422                 case IAX_IE_LANGUAGE:
423                         ies->language = data + 2;
424                         break;
425                 case IAX_IE_VERSION:
426                         if (len != sizeof(unsigned short)) {
427                                 snprintf(tmp, sizeof(tmp),  "Expecting version to be %d bytes long but was %d\n", sizeof(unsigned short), len);
428                                 errorf(tmp);
429                         } else
430                                 ies->version = ntohs(*((unsigned short *)(data + 2)));
431                         break;
432                 case IAX_IE_ADSICPE:
433                         if (len != sizeof(unsigned short)) {
434                                 snprintf(tmp, sizeof(tmp), "Expecting adsicpe to be %d bytes long but was %d\n", sizeof(unsigned short), len);
435                                 errorf(tmp);
436                         } else
437                                 ies->adsicpe = ntohs(*((unsigned short *)(data + 2)));
438                         break;
439                 case IAX_IE_DNID:
440                         ies->dnid = data + 2;
441                         break;
442                 case IAX_IE_RDNIS:
443                         ies->rdnis = data + 2;
444                         break;
445                 case IAX_IE_AUTHMETHODS:
446                         if (len != sizeof(unsigned short))  {
447                                 snprintf(tmp, sizeof(tmp), "Expecting authmethods to be %d bytes long but was %d\n", sizeof(unsigned short), len);
448                                 errorf(tmp);
449                         } else
450                                 ies->authmethods = ntohs(*((unsigned short *)(data + 2)));
451                         break;
452                 case IAX_IE_CHALLENGE:
453                         ies->challenge = data + 2;
454                         break;
455                 case IAX_IE_MD5_RESULT:
456                         ies->md5_result = data + 2;
457                         break;
458                 case IAX_IE_RSA_RESULT:
459                         ies->rsa_result = data + 2;
460                         break;
461                 case IAX_IE_APPARENT_ADDR:
462                         ies->apparent_addr = ((struct sockaddr_in *)(data + 2));
463                         break;
464                 case IAX_IE_REFRESH:
465                         if (len != sizeof(unsigned short)) {
466                                 snprintf(tmp, sizeof(tmp),  "Expecting refresh to be %d bytes long but was %d\n", sizeof(unsigned short), len);
467                                 errorf(tmp);
468                         } else
469                                 ies->refresh = ntohs(*((unsigned short *)(data + 2)));
470                         break;
471                 case IAX_IE_DPSTATUS:
472                         if (len != sizeof(unsigned short)) {
473                                 snprintf(tmp, sizeof(tmp),  "Expecting dpstatus to be %d bytes long but was %d\n", sizeof(unsigned short), len);
474                                 errorf(tmp);
475                         } else
476                                 ies->dpstatus = ntohs(*((unsigned short *)(data + 2)));
477                         break;
478                 case IAX_IE_CALLNO:
479                         if (len != sizeof(unsigned short)) {
480                                 snprintf(tmp, sizeof(tmp),  "Expecting callno to be %d bytes long but was %d\n", sizeof(unsigned short), len);
481                                 errorf(tmp);
482                         } else
483                                 ies->callno = ntohs(*((unsigned short *)(data + 2)));
484                         break;
485                 case IAX_IE_CAUSE:
486                         ies->cause = data + 2;
487                         break;
488                 case IAX_IE_IAX_UNKNOWN:
489                         if (len == 1)
490                                 ies->iax_unknown = data[2];
491                         else {
492                                 snprintf(tmp, sizeof(tmp), "Expected single byte Unknown command, but was %d long\n", len);
493                                 errorf(tmp);
494                         }
495                         break;
496                 case IAX_IE_MSGCOUNT:
497                         if (len != sizeof(unsigned short)) {
498                                 snprintf(tmp, sizeof(tmp), "Expecting msgcount to be %d bytes long but was %d\n", sizeof(unsigned short), len);
499                                 errorf(tmp);
500                         } else
501                                 ies->msgcount = ntohs(*((unsigned short *)(data + 2))); 
502                         break;
503                 case IAX_IE_AUTOANSWER:
504                         ies->autoanswer = 1;
505                         break;
506                 case IAX_IE_MUSICONHOLD:
507                         ies->musiconhold = 1;
508                         break;
509                 case IAX_IE_TRANSFERID:
510                         if (len != sizeof(unsigned int)) {
511                                 snprintf(tmp, sizeof(tmp), "Expecting transferid to be %d bytes long but was %d\n", sizeof(unsigned int), len);
512                                 errorf(tmp);
513                         } else
514                                 ies->transferid = ntohl(*((unsigned int *)(data + 2)));
515                         break;
516                 case IAX_IE_DATETIME:
517                         if (len != sizeof(unsigned int)) {
518                                 snprintf(tmp, sizeof(tmp), "Expecting date/time to be %d bytes long but was %d\n", sizeof(unsigned int), len);
519                                 errorf(tmp);
520                         } else
521                                 ies->datetime = ntohl(*((unsigned int *)(data + 2)));
522                         break;
523                 case IAX_IE_FIRMWAREVER:
524                         if (len != sizeof(unsigned short)) {
525                                 snprintf(tmp, sizeof(tmp), "Expecting firmwarever to be %d bytes long but was %d\n", sizeof(unsigned short), len);
526                                 errorf(tmp);
527                         } else
528                                 ies->firmwarever = ntohs(*((unsigned short *)(data + 2)));      
529                         break;
530                 case IAX_IE_DEVICETYPE:
531                         ies->devicetype = data + 2;
532                         break;
533                 case IAX_IE_SERVICEIDENT:
534                         ies->serviceident = data + 2;
535                         break;
536                 case IAX_IE_FWBLOCKDESC:
537                         if (len != sizeof(unsigned int)) {
538                                 snprintf(tmp, sizeof(tmp), "Expected block desc to be %d bytes long but was %d\n", sizeof(unsigned int), len);
539                                 errorf(tmp);
540                         } else
541                                 ies->fwdesc = ntohl(*((unsigned int *)(data + 2)));
542                         break;
543                 case IAX_IE_FWBLOCKDATA:
544                         ies->fwdata = data + 2;
545                         ies->fwdatalen = len;
546                         break;
547                 case IAX_IE_PROVVER:
548                         if (len != sizeof(unsigned int)) {
549                                 snprintf(tmp, sizeof(tmp), "Expected provisioning version to be %d bytes long but was %d\n", sizeof(unsigned int), len);
550                                 errorf(tmp);
551                         } else
552                                 ies->provver = ntohl(*((unsigned int *)(data + 2)));
553                         break;
554                 default:
555                         snprintf(tmp, sizeof(tmp), "Ignoring unknown information element '%s' (%d) of length %d\n", iax_ie2str(ie), ie, len);
556                         errorf(tmp);
557                 }
558                 /* Overwrite information element with 0, to null terminate previous portion */
559                 data[0] = 0;
560                 datalen -= (len + 2);
561                 data += (len + 2);
562         }
563         /* Null-terminate last field */
564         *data = '\0';
565         if (datalen) {
566                 errorf("Invalid information element contents, strange boundary\n");
567                 return -1;
568         }
569         return 0;
570 }
571
572 void iax_frame_wrap(struct iax_frame *fr, struct ast_frame *f)
573 {
574         fr->af.frametype = f->frametype;
575         fr->af.subclass = f->subclass;
576         fr->af.mallocd = 0;                             /* Our frame is static relative to the container */
577         fr->af.datalen = f->datalen;
578         fr->af.samples = f->samples;
579         fr->af.offset = AST_FRIENDLY_OFFSET;
580         fr->af.src = f->src;
581         fr->af.delivery.tv_sec = 0;
582         fr->af.delivery.tv_usec = 0;
583         fr->af.data = fr->afdata;
584         if (fr->af.datalen) 
585                 memcpy(fr->af.data, f->data, fr->af.datalen);
586 }
587
588 struct iax_frame *iax_frame_new(int direction, int datalen)
589 {
590         struct iax_frame *fr;
591         fr = malloc(sizeof(struct iax_frame) + datalen);
592         if (fr) {
593                 fr->direction = direction;
594                 fr->retrans = -1;
595                 frames++;
596                 if (fr->direction == DIRECTION_INGRESS)
597                         iframes++;
598                 else
599                         oframes++;
600         }
601         return fr;
602 }
603
604 void iax_frame_free(struct iax_frame *fr)
605 {
606         /* Note: does not remove from scheduler! */
607         if (fr->direction == DIRECTION_INGRESS)
608                 iframes--;
609         else if (fr->direction == DIRECTION_OUTGRESS)
610                 oframes--;
611         else {
612                 errorf("Attempt to double free frame detected\n");
613                 return;
614         }
615         fr->direction = 0;
616         free(fr);
617         frames--;
618 }
619
620 int iax_get_frames(void) { return frames; }
621 int iax_get_iframes(void) { return iframes; }
622 int iax_get_oframes(void) { return oframes; }