diff options
author | markster <markster@f38db490-d61c-443f-a65b-d21fe96a405b> | 1999-12-04 21:35:07 +0000 |
---|---|---|
committer | markster <markster@f38db490-d61c-443f-a65b-d21fe96a405b> | 1999-12-04 21:35:07 +0000 |
commit | 1229f5d94d61c960deb1295dc75249d20003382f (patch) | |
tree | 5266c0f25c9d3f912bc8c77a6d6f3cdb7893f2b7 | |
parent | d2d438a1226691ebd589d93105c414557b4b5cbc (diff) |
Version 0.1.0 from FTP
git-svn-id: http://svn.digium.com/svn/asterisk/trunk@84 f38db490-d61c-443f-a65b-d21fe96a405b
-rwxr-xr-x | apps/app_dial.c | 410 | ||||
-rwxr-xr-x | channel.c | 402 | ||||
-rwxr-xr-x | channels/chan_vofr.c | 1138 |
3 files changed, 1950 insertions, 0 deletions
diff --git a/apps/app_dial.c b/apps/app_dial.c new file mode 100755 index 000000000..021f0d7a8 --- /dev/null +++ b/apps/app_dial.c @@ -0,0 +1,410 @@ +/* + * Asterisk -- A telephony toolkit for Linux. + * + * Trivial application to dial a channel + * + * Copyright (C) 1999, Adtran Inc. and Linux Support Services, LLC + * + * Mark Spencer <markster@linux-support.net> + * + * This program is free software, distributed under the terms of + * the GNU General Public License + */ + +#include <asterisk/file.h> +#include <asterisk/logger.h> +#include <asterisk/channel.h> +#include <asterisk/pbx.h> +#include <asterisk/options.h> +#include <asterisk/module.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <sys/time.h> + +#include <pthread.h> + + +static char *tdesc = "Trivial Dialing Application"; + +static char *app = "Dial"; + +/* We define a customer "local user" structure because we + use it not only for keeping track of what is in use but + also for keeping track of who we're dialing. */ + +struct localuser { + struct ast_channel *chan; + int stillgoing; + int allowredirect; + struct localuser *next; +}; + +LOCAL_USER_DECL; + +static void hanguptree(struct localuser *outgoing, struct ast_channel *exception) +{ + /* Hang up a tree of stuff */ + struct localuser *oo; + while(outgoing) { + /* Hangup any existing lines we have open */ + if (outgoing->chan != exception) + ast_hangup(outgoing->chan); + oo = outgoing; + outgoing=outgoing->next; + free(oo); + } +} + +static struct ast_channel *wait_for_answer(struct ast_channel *in, struct localuser *outgoing, int *to, int *allowredir) +{ + fd_set rfds, efds; + struct localuser *o; + int found; + int numlines; + int numbusies = 0; + int orig = *to; + struct timeval tv; + struct ast_frame *f; + struct ast_channel *peer = NULL; + /* Watch all outgoing channels looking for an answer of some sort. */ + tv.tv_sec = *to / 1000; + tv.tv_usec = (*to % 1000) * 1000; + while((tv.tv_sec || tv.tv_usec) && !peer) { + FD_ZERO(&rfds); + FD_ZERO(&efds); + /* Always watch the input fd */ + FD_SET(in->fd, &rfds); + FD_SET(in->fd, &efds); + o = outgoing; + found = -1; + numlines = 0; + while(o) { + if (o->stillgoing) { + /* Pay attention to this one */ + CHECK_BLOCKING(o->chan); + FD_SET(o->chan->fd, &rfds); + FD_SET(o->chan->fd, &efds); + if (o->chan->fd > found) + found = o->chan->fd; + } + numlines++; + o = o->next; + } + /* If nobody is left, just go ahead and stop */ + if (found<0) { + if (numlines == numbusies) { + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_2 "Everyone is busy at this time\n"); + /* See if there is a special busy message */ + if (ast_exists_extension(in, in->context, in->exten, in->priority + 101)) + in->priority+=100; + } else { + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_2 "No one is available to answer at this time\n"); + } + break; + } + if (in->fd > found) + found = in->fd; + if (*to > -1) + found = select(found + 1, &rfds, NULL, &efds, &tv); + else + found = select(found + 1, &rfds, NULL, &efds, NULL); + if (found < 0) { + ast_log(LOG_WARNING, "select failed, returned %d (%s)\n", errno, strerror(errno)); + *to = -1; + o = outgoing; + while(o) { + if (o->stillgoing) { + o->chan->blocking = 0; + } + o = o->next; + } + return NULL; + } + o = outgoing; + while(o) { + if (o->stillgoing) { + o->chan->blocking = 0; + if (FD_ISSET(o->chan->fd, &rfds) || FD_ISSET(o->chan->fd, &efds)) { + f = ast_read(o->chan); + if (f) { + if (f->frametype == AST_FRAME_CONTROL) { + switch(f->subclass) { + case AST_CONTROL_ANSWER: + /* This is our guy if someone answered. */ + if (!peer) { + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "%s answered %s\n", o->chan->name, in->name); + peer = o->chan; + *allowredir = o->allowredirect; + } + break; + case AST_CONTROL_BUSY: + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "%s is busy\n", o->chan->name); + o->stillgoing = 0; + numbusies++; + break; + case AST_CONTROL_RINGING: + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "%s is ringing\n", o->chan->name); + break; + case AST_CONTROL_OFFHOOK: + /* Ignore going off hook */ + break; + default: + ast_log(LOG_DEBUG, "Dunno what to do with control type %d\n", f->subclass); + } + } + ast_frfree(f); + } else { + o->stillgoing = 0; + } + + } + } + o = o->next; + } + if (FD_ISSET(in->fd, &rfds) || FD_ISSET(in->fd, &efds)) { + /* After unblocking the entirity of the list, check for the main channel */ + f = ast_read(in); +#if 0 + if (f && (f->frametype != AST_FRAME_VOICE)) + printf("Frame type: %d, %d\n", f->frametype, f->subclass); +#endif + if (!f || ((f->frametype == AST_FRAME_CONTROL) && (f->subclass = AST_CONTROL_HANGUP))) { + /* Got hung up */ + *to=-1; + return NULL; + } + } + + } + if (!(tv.tv_sec || tv.tv_usec) && (option_verbose > 2)) + ast_verbose( VERBOSE_PREFIX_3 "Nobody picked up in %d ms\n", orig); + *to = 0; + return peer; +} + +static int bridge_call(struct ast_channel *chan, struct ast_channel *peer, int allowredirect) +{ + /* Copy voice back and forth between the two channels. Give the peer + the ability to transfer calls with '#<extension' syntax. */ + struct ast_channel *cs[3]; + int to = -1, len; + struct ast_frame *f; + struct ast_channel *who; + char newext[256], *ptr; + int res; + /* Answer if need be */ + if (chan->state != AST_STATE_UP) + if (ast_answer(chan)) + return -1; + cs[0] = chan; + cs[1] = peer; + for (/* ever */;;) { + who = ast_waitfor_n(cs, 2, &to); + if (!who) { + ast_log(LOG_WARNING, "Nobody there??\n"); + continue; + } + f = ast_read(who); + if (!f || ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP))) + return -1; + if ((f->frametype == AST_FRAME_VOICE) || + (f->frametype == AST_FRAME_DTMF)) { + if ((f->frametype == AST_FRAME_DTMF) && (who == peer) && allowredirect) { + if (f->subclass == '#') { + memset(newext, 0, sizeof(newext)); + ptr = newext; + len = ast_pbx_longest_extension(chan->context) + 1; + + /* Transfer */ + if ((res=ast_streamfile(peer, "pbx-transfer"))) + break; + if ((res=ast_waitstream(peer, AST_DIGIT_ANY)) < 0) + break; + ast_stopstream(peer); + if (res > 0) { + /* If they've typed a digit already, handle it */ + newext[0] = res; + ptr++; + len --; + } + res = ast_readstring(peer, ptr, len, 3000, 2000, "#"); + if (res) + break; + if (ast_exists_extension(chan, chan->context, newext, 1)) { + /* Set the channel's new extension, since it exists */ + strncpy(chan->exten, newext, sizeof(chan->exten)); + chan->priority = 0; + ast_frfree(f); + res=0; + break; + } + res = ast_streamfile(peer, "pbx-invalid"); + if (res) + break; + res = ast_waitstream(peer, AST_DIGIT_ANY); + ast_stopstream(peer); + res = 0; + } + } else { +#if 0 + ast_log(LOG_DEBUG, "Read from %s\n", who->name); +#endif + if (who == chan) + ast_write(peer, f); + else + ast_write(chan, f); + } + ast_frfree(f); + + } else + ast_frfree(f); + /* Swap who gets priority */ + cs[2] = cs[0]; + cs[0] = cs[1]; + cs[1] = cs[2]; + } + return res; +} + +static int dial_exec(struct ast_channel *chan, void *data) +{ + int res=-1; + struct localuser *u; + char *info, *peers, *timeout, *tech, *number, *rest, *cur; + struct localuser *outgoing=NULL, *tmp; + struct ast_channel *peer; + int to; + int allowredir=0; + + if (!data) { + ast_log(LOG_WARNING, "Dial requires an argument (technology1/number1&technology2/number2...|optional timeout)\n"); + return -1; + } + + LOCAL_USER_ADD(u); + + /* Parse our arguments */ + info = strdup((char *)data); + peers = strtok(info, "|"); + if (!peers) { + ast_log(LOG_WARNING, "Dial argument takes format (technology1/number1&technology2/number2...|optional timeout)\n"); + goto out; + } + timeout = strtok(NULL, "|"); + rest = peers; + do { + cur = strtok(rest, "&"); + /* Remember where to start next time */ + rest = strtok(NULL, "\128"); + /* Get a technology/number pair */ + tech = strtok(cur, "/"); + number = strtok(NULL, "&"); + if (!number) { + ast_log(LOG_WARNING, "Dial argument takes format (technology1/number1&technology2/number2...|optional timeout)\n"); + goto out; + } + tmp = malloc(sizeof(struct localuser)); + if (!tmp) { + ast_log(LOG_WARNING, "Out of memory\n"); + goto out; + } + tmp->allowredirect = 1; + /* If we're dialing by extension, look at the extension to know what to dial */ + if (!strcasecmp(number, "BYEXTENSION")) { + if (option_debug) + ast_log(LOG_DEBUG, "Dialing by extension %s\n", chan->exten); + number = chan->exten; + /* By default, if we're dialing by extension, don't permit redirecting */ + tmp->allowredirect = 0; + } + /* Request the peer */ + tmp->chan = ast_request(tech, chan->format, number); + if (!tmp->chan) { + /* If we can't, just go on to the next call */ + ast_log(LOG_WARNING, "Unable to create channel of type '%s'\n", tech); + free(tmp); + continue; + } + /* Place the call, but don't wait on the answer */ + res = ast_call(tmp->chan, number, 0); + if (res) { + /* Again, keep going even if there's an error */ + if (option_debug) + ast_log(LOG_DEBUG, "ast call on peer returned %d\n", res); + else if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Couldn't call %s\n", number); + ast_hangup(tmp->chan); + free(tmp); + continue; + } else + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Called %s\n", number); + /* Put them in the list of outgoing thingies... We're ready now. + XXX If we're forcibly removed, these outgoing calls won't get + hung up XXX */ + tmp->stillgoing = -1; + tmp->next = outgoing; + outgoing = tmp; + } while(rest); + if (timeout) + to = atoi(timeout) * 1000; + else + to = -1; + peer = wait_for_answer(chan, outgoing, &to, &allowredir); + if (!peer) { + if (to) + /* Musta gotten hung up */ + res = -1; + else + /* Nobody answered, next please? */ + res=0; + + goto out; + } + if (peer) { + /* Ah ha! Someone answered within the desired timeframe. Of course after this + we will always return with -1 so that it is hung up properly after the + conversation. */ + hanguptree(outgoing, peer); + outgoing = NULL; + res = bridge_call(chan, peer, allowredir); + ast_hangup(peer); + } +out: + hanguptree(outgoing, NULL); + free(info); + LOCAL_USER_REMOVE(u); + return res; +} + +int unload_module(void) +{ + STANDARD_HANGUP_LOCALUSERS; + return ast_unregister_application(app); +} + +int load_module(void) +{ + return ast_register_application(app, dial_exec); +} + +char *description(void) +{ + return tdesc; +} + +int usecount(void) +{ + int res; + STANDARD_USECOUNT(res); + return res; +} diff --git a/channel.c b/channel.c new file mode 100755 index 000000000..b4359d20e --- /dev/null +++ b/channel.c @@ -0,0 +1,402 @@ +/* + * Asterisk -- A telephony toolkit for Linux. + * + * Channel Management + * + * Copyright (C) 1999, Adtran Inc. and Linux Support Services, LLC + * + * Mark Spencer <markster@linux-support.net> + * + * This program is free software, distributed under the terms of + * the GNU General Public License + */ + +#include <stdio.h> +#include <stdlib.h> +#include <pthread.h> +#include <string.h> +#include <sys/time.h> +#include <signal.h> +#include <errno.h> +#include <asterisk/sched.h> +#include <asterisk/options.h> +#include <asterisk/channel.h> +#include <asterisk/channel_pvt.h> +#include <asterisk/logger.h> +#include <asterisk/file.h> + +struct chanlist { + char type[80]; + char description[80]; + int capabilities; + struct ast_channel * (*requester)(char *type, int format, void *data); + struct chanlist *next; +} *backends = NULL; + +/* Protect the channel list (highly unlikely that two things would change + it at the same time, but still! */ + +static pthread_mutex_t chlock = PTHREAD_MUTEX_INITIALIZER; + +int ast_channel_register(char *type, char *description, int capabilities, + struct ast_channel *(*requester)(char *type, int format, void *data)) +{ + struct chanlist *chan, *last=NULL; + if (pthread_mutex_lock(&chlock)) { + ast_log(LOG_WARNING, "Unable to lock channel list\n"); + return -1; + } + chan = backends; + while(chan) { + if (!strcasecmp(type, chan->type)) { + ast_log(LOG_WARNING, "Already have a handler for type '%s'\n", type); + pthread_mutex_unlock(&chlock); + return -1; + } + last = chan; + chan = chan->next; + } + chan = malloc(sizeof(struct chanlist)); + if (!chan) { + ast_log(LOG_WARNING, "Out of memory\n"); + pthread_mutex_unlock(&chlock); + return -1; + } + strncpy(chan->type, type, sizeof(chan->type)); + strncpy(chan->description, description, sizeof(chan->description)); + chan->capabilities = capabilities; + chan->requester = requester; + chan->next = NULL; + if (last) + last->next = chan; + else + backends = chan; + if (option_debug) + ast_log(LOG_DEBUG, "Registered handler for '%s' (%s)\n", chan->type, chan->description); + else if (option_verbose > 1) + ast_verbose( VERBOSE_PREFIX_2 "Registered channel type '%s' (%s)\n", chan->type, chan->description); + pthread_mutex_unlock(&chlock); + return 0; +} + +struct ast_channel *ast_channel_alloc(void) +{ + struct ast_channel *tmp; + struct ast_channel_pvt *pvt; + tmp = malloc(sizeof(struct ast_channel)); + memset(tmp, 0, sizeof(struct ast_channel)); + if (tmp) { + pvt = malloc(sizeof(struct ast_channel_pvt)); + if (pvt) { + memset(pvt, 0, sizeof(struct ast_channel_pvt)); + tmp->sched = sched_context_create(); + if (tmp->sched) { + tmp->fd = -1; + strncpy(tmp->name, "**Unknown**", sizeof(tmp->name)); + tmp->pvt = pvt; + tmp->state = AST_STATE_DOWN; + tmp->stack = -1; + tmp->streamid = -1; + strncpy(tmp->context, "default", sizeof(tmp->context)); + strncpy(tmp->exten, "s", sizeof(tmp->exten)); + tmp->priority=1; + } else { + ast_log(LOG_WARNING, "Unable to create schedule context\n"); + free(tmp); + tmp = NULL; + } + } else { + ast_log(LOG_WARNING, "Out of memory\n"); + free(tmp); + tmp = NULL; + } + } else + ast_log(LOG_WARNING, "Out of memory\n"); + return tmp; +} + +int ast_softhangup(struct ast_channel *chan) +{ + int res = 0; + if (chan->stream) + ast_stopstream(chan); + if (option_debug) + ast_log(LOG_DEBUG, "Soft-Hanging up channel '%s'\n", chan->name); + if (chan->trans) + ast_log(LOG_WARNING, "Soft hangup called on '%s' while a translator is in place! Expect a failure.\n", chan->name); + if (chan->pvt->hangup) + res = chan->pvt->hangup(chan); + if (chan->pvt->pvt) + ast_log(LOG_WARNING, "Channel '%s' may not have been hung up properly\n", chan->name); + if (chan->pbx) + ast_log(LOG_WARNING, "PBX may not have been terminated properly on '%s'\n", chan->name); + /* Interrupt any select call or such */ + if (chan->blocking) + pthread_kill(chan->blocker, SIGURG); + return res; +} + +int ast_hangup(struct ast_channel *chan) +{ + int res = 0; + if (chan->stream) + ast_stopstream(chan); + if (chan->sched) + sched_context_destroy(chan->sched); + if (chan->blocking) + ast_log(LOG_WARNING, "Hard hangup called, while fd is blocking! Expect a failure\n"); + if (option_debug) + ast_log(LOG_DEBUG, "Hanging up channel '%s'\n", chan->name); + if (chan->pvt->hangup) + res = chan->pvt->hangup(chan); + if (chan->pvt->pvt) + ast_log(LOG_WARNING, "Channel '%s' may not have been hung up properly\n", chan->name); + if (chan->trans) + ast_log(LOG_WARNING, "Hard hangup called on '%s' while a translator is in place! Expect a failure.\n", chan->name); + if (chan->pbx) + ast_log(LOG_WARNING, "PBX may not have been terminated properly on '%s'\n", chan->name); + if (chan->dnid) + free(chan->dnid); + if (chan->callerid) + free(chan->callerid); + free(chan->pvt); + free(chan); + return res; +} + +void ast_channel_unregister(char *type) +{ + struct chanlist *chan, *last=NULL; + if (option_debug) + ast_log(LOG_DEBUG, "Unregistering channel type '%s'\n", type); + if (pthread_mutex_lock(&chlock)) { + ast_log(LOG_WARNING, "Unable to lock channel list\n"); + return; + } + chan = backends; + while(chan) { + if (!strcasecmp(chan->type, type)) { + if (last) + last->next = chan->next; + else + backends = backends->next; + free(chan); + pthread_mutex_unlock(&chlock); + return; + } + last = chan; + chan = chan->next; + } + pthread_mutex_unlock(&chlock); +} + +int ast_answer(struct ast_channel *chan) +{ + /* Answer the line, if possible */ + if (chan->state == AST_STATE_RING) { + if (chan->pvt->answer) + return chan->pvt->answer(chan); + } + return 0; +} + +int ast_waitfor_n_fd(int *fds, int n, int *ms) +{ + /* Wait for x amount of time on a file descriptor to have input. */ + struct timeval tv; + fd_set rfds, efds; + int res; + int x, max=-1; + int winner = -1; + + tv.tv_sec = *ms / 1000; + tv.tv_usec = (*ms % 1000) * 1000; + FD_ZERO(&rfds); + FD_ZERO(&efds); + for (x=0;x<n;x++) { + FD_SET(fds[x], &rfds); + FD_SET(fds[x], &efds); + if (fds[x] > max) + max = fds[x]; + } + if (*ms >= 0) + res = select(max + 1, &rfds, NULL, &efds, &tv); + else + res = select(max + 1, &rfds, NULL, &efds, NULL); + for (x=0;x<n;x++) { + if ((FD_ISSET(fds[x], &rfds) || FD_ISSET(fds[x], &efds)) && (winner < 0)) + winner = fds[x]; + } + *ms = tv.tv_sec * 1000 + tv.tv_usec / 1000; + if (res < 0) + *ms = -10; + return winner; +} + +struct ast_channel *ast_waitfor_n(struct ast_channel **c, int n, int *ms) +{ + /* Wait for x amount of time on a file descriptor to have input. */ + struct timeval tv; + fd_set rfds, efds; + int res; + int x, max=-1; + struct ast_channel *winner = NULL; + + tv.tv_sec = *ms / 1000; + tv.tv_usec = (*ms % 1000) * 1000; + FD_ZERO(&rfds); + FD_ZERO(&efds); + for (x=0;x<n;x++) { + FD_SET(c[x]->fd, &rfds); + FD_SET(c[x]->fd, &efds); + CHECK_BLOCKING(c[x]); + if (c[x]->fd > max) + max = c[x]->fd; + } + if (*ms >= 0) + res = select(max + 1, &rfds, NULL, &efds, &tv); + else + res = select(max + 1, &rfds, NULL, &efds, NULL); + for (x=0;x<n;x++) { + c[x]->blocking = 0; + if ((FD_ISSET(c[x]->fd, &rfds) || FD_ISSET(c[x]->fd, &efds)) && !winner) + winner = c[x]; + } + *ms = tv.tv_sec * 1000 + tv.tv_usec / 1000; + if (res < 0) + *ms = -10; + return winner; +} + +int ast_waitfor(struct ast_channel *c, int ms) +{ + if (ast_waitfor_n(&c, 1, &ms)) { + if (ms < 0) + return -ms; + return ms; + } + /* Error if ms < 0 */ + if (ms < 0) + return -1; + return 0; +} + +char ast_waitfordigit(struct ast_channel *c, int ms) +{ + struct ast_frame *f; + char result = 0; + /* Wait for a digit, no more than ms milliseconds total. */ + while(ms && !result) { + ms = ast_waitfor(c, ms); + if (ms < 0) /* Error */ + result = -1; + else if (ms > 0) { + /* Read something */ + f = ast_read(c); + if (f) { + if (f->frametype == AST_FRAME_DTMF) + result = f->subclass; + ast_frfree(f); + } else + result = -1; + } + } + return result; +} + +struct ast_frame *ast_read(struct ast_channel *chan) +{ + struct ast_frame *f = NULL; + chan->blocker = pthread_self(); + if (chan->pvt->read) + f = chan->pvt->read(chan); + else + ast_log(LOG_WARNING, "No read routine on channel %s\n", chan); + return f; +} + +int ast_write(struct ast_channel *chan, struct ast_frame *fr) +{ + int res = -1; + CHECK_BLOCKING(chan); + switch(fr->frametype) { + case AST_FRAME_CONTROL: + /* XXX Interpret control frames XXX */ + ast_log(LOG_WARNING, "Don't know how to handle control frames yet\n"); + break; + case AST_FRAME_DTMF: + + if (chan->pvt->send_digit) + res = chan->pvt->send_digit(chan, fr->subclass); + break; + default: + if (chan->pvt->write) + res = chan->pvt->write(chan, fr); + } + chan->blocking = 0; + return res; +} + +struct ast_channel *ast_request(char *type, int format, void *data) +{ + struct chanlist *chan; + struct ast_channel *c = NULL; + if (pthread_mutex_lock(&chlock)) { + ast_log(LOG_WARNING, "Unable to lock channel list\n"); + return NULL; + } + chan = backends; + while(chan) { + if (!strcasecmp(type, chan->type)) { + if (chan->requester) + c = chan->requester(type, format, data); + pthread_mutex_unlock(&chlock); + break; + } + chan = chan->next; + } + if (!chan) + ast_log(LOG_WARNING, "No channel type registered for '%s'\n", type); + return c; +} + +int ast_call(struct ast_channel *chan, char *addr, int timeout) +{ + /* Place an outgoing call, but don't wait any longer than timeout ms before returning. + If the remote end does not answer within the timeout, then do NOT hang up, but + return anyway. */ + int res = -1; + if (chan->pvt->call) + res = chan->pvt->call(chan, addr, timeout); + return res; +} + +int ast_readstring(struct ast_channel *c, char *s, int len, int timeout, int ftimeout, char *enders) +{ + int pos=0; + int to = ftimeout; + char d; + if (!len) + return -1; + do { + if (c->streamid > -1) { + d = ast_waitstream(c, AST_DIGIT_ANY); + ast_stopstream(c); + if (!d) + d = ast_waitfordigit(c, to); + } else { + d = ast_waitfordigit(c, to); + } + if (d < 0) + return -1; + if (!strchr(enders, d)) + s[pos++] = d; + if ((d == 0) || strchr(enders, d) || (pos >= len - 1)) { + s[pos]='\0'; + return 0; + } + to = timeout; + } while(1); + /* Never reached */ + return 0; +} diff --git a/channels/chan_vofr.c b/channels/chan_vofr.c new file mode 100755 index 000000000..ea17df951 --- /dev/null +++ b/channels/chan_vofr.c @@ -0,0 +1,1138 @@ +/* + * Asterisk -- A telephony toolkit for Linux. + * + * Implementation of Voice over Frame Relay, Adtran Style + * + * Copyright (C) 1999, Adtran Inc. and Linux Support Services, LLC + * + * Mark Spencer <markster@linux-support.net> + * + * This program is free software, distributed under the terms of + * the GNU General Public License + */ + +#include <stdio.h> +#include <pthread.h> +#include <string.h> +#include <asterisk/channel.h> +#include <asterisk/channel_pvt.h> +#include <asterisk/config.h> +#include <asterisk/logger.h> +#include <asterisk/module.h> +#include <asterisk/pbx.h> +#include <asterisk/options.h> +#include <sys/socket.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <arpa/inet.h> +#include <linux/if_packet.h> +#include <linux/if_ether.h> +#include "adtranvofr.h" + +#define G723_MAX_BUF 2048 + +#define FR_API_MESS 16 + +static char *desc = "Adtran Voice over Frame Relay"; +static char *type = "AdtranVoFR"; +static char *tdesc = "Voice over Frame Relay/Adtran style"; +static char *config = "adtranvofr.conf"; + +static char context[AST_MAX_EXTENSION] = "default"; + +static int usecnt =0; +static pthread_mutex_t usecnt_lock = PTHREAD_MUTEX_INITIALIZER; + +/* Protect the interface list (of vofr_pvt's) */ +static pthread_mutex_t iflock = PTHREAD_MUTEX_INITIALIZER; + +/* Protect the monitoring thread, so only one process can kill or start it, and not + when it's doing something critical. */ +static pthread_mutex_t monlock = PTHREAD_MUTEX_INITIALIZER; + +/* This is the thread for the monitor which checks for input on the channels + which are not currently in use. */ +static pthread_t monitor_thread = -1; + +static int restart_monitor(); + +/* The private structures of the Adtran VoFR channels are linked for + selecting outgoing channels */ + +static struct vofr_pvt { + int s; /* Raw socket for this DLCI */ + struct sockaddr_pkt sa; /* Sockaddr needed for sending, also has iface name */ + struct ast_channel *owner; /* Channel we belong to, possibly NULL */ + int outgoing; /* Does this channel support outgoing calls? */ + struct vofr_pvt *next; /* Next channel in list */ + struct vofr_hdr *hdr; /* VOFR header version of buf */ + struct vofr_hdr *ohdr; + u_int8_t dlcih; /* High two bits of DLCI */ + u_int8_t dlcil; /* Bottom two bits of DLCI */ + u_int8_t cid; /* Call ID */ + char buf[G723_MAX_BUF]; /* Static buffer for reading frames */ + char obuf[G723_MAX_BUF]; /* Output buffer */ + char context[AST_MAX_EXTENSION]; +} *iflist = NULL; + +#ifdef VOFRDUMPER + +/* Some useful debugging routines */ + +static char *set(int val) +{ + return (val ? "Set " : "Unset"); +} + +static char *controlstr(int control) +{ + switch(control) { + case VOFR_CONTROL_ADTRAN: + return "Adtran Proprietary"; + case VOFR_CONTROL_VOICE: + return "Voice"; + case VOFR_CONTROL_RFC1490: + return "RFC 1490"; + } + return "Unknown"; +} + +static char *dtypestr(int control) +{ + switch(control) { + case VOFR_TYPE_SIGNAL: + return "Signal Frame"; + case VOFR_TYPE_VOICE: + return "Voice Frame"; + case VOFR_TYPE_ANSWER: + return "Answer Tone"; + case VOFR_TYPE_FAX: + return "FAX"; + case VOFR_TYPE_DTMF: + return "DTMF Digit"; + } + return "Unknown"; +} + +static char *vflagsstr(int flags) +{ + static char buf[80]; + buf[0] = '\0'; + if (!flags) + return "(None)"; + if (flags & VOFR_ROUTE_LOCAL) + strcat(buf, "Local "); + if (flags & VOFR_ROUTE_VOICE) + strcat(buf, "Voice "); + if (flags & VOFR_ROUTE_DTE) + strcat(buf, "DTE "); + else if (flags & VOFR_ROUTE_DTE1) + strcat(buf, "DTE1 "); + else if (flags & VOFR_ROUTE_DTE2) + strcat(buf, "DTE2 "); + return buf; +} + +static char *remidstr(int remid) +{ + switch(remid) { + case VOFR_CARD_TYPE_UNSPEC: + return "Unspecified"; + case VOFR_CARD_TYPE_FXS: + return "FXS"; + case VOFR_CARD_TYPE_FXO: + return "FXO"; + case VOFR_CARD_TYPE_ENM: + return "E&M"; + case VOFR_CARD_TYPE_VCOM: + return "Atlas/VCOM"; + } + return "Unknown"; +} + +static char *modulationstr(int modulation) +{ + switch(modulation) { + case VOFR_MODULATION_SINGLE: + return "Single Frequency"; + case VOFR_MODULATION_V21: + return "V.21"; + case VOFR_MODULATION_V27ter_2: + return "V.27 (2400bps)"; + case VOFR_MODULATION_V27ter_4: + return "V.27 (4800bps)"; + case VOFR_MODULATION_V29_7: + return "V.29 (7200bps)"; + case VOFR_MODULATION_V29_9: + return "V.29 (9600bps)"; + case VOFR_MODULATION_V33_12: + return "V.33 (12000bps)"; + case VOFR_MODULATION_V33_14: + return "V.33 (14400BPS)"; + } + return "Unknown"; +} + +static char *signalstr(int signal) +{ + switch(signal) { + case VOFR_SIGNAL_ON_HOOK: + return "On Hook"; + case VOFR_SIGNAL_OFF_HOOK: + return "Off Hook"; + case VOFR_SIGNAL_RING: + return "Ring"; + case VOFR_SIGNAL_SWITCHED_DIAL: + return "Switched Dial"; + case VOFR_SIGNAL_BUSY: + return "Busy"; + case VOFR_SIGNAL_TRUNK_BUSY: + return "Trunk Busy"; + } + return "Unknown"; +} + +static char *vofr_digitstr(int val) +{ + static char num[5]; + if (val < 10) { + snprintf(num, sizeof(num), "%d", val); + return num; + } + switch(val) { + case 10: + return "*"; + case 11: + return "#"; + } + return "Unknown"; +} + + +static void vofr_dump_packet(struct vofr_hdr *vh, int len) +{ + printf("VoFR Packet Dump\n"); + printf("================\n"); + printf("EI: %s ", set(vh->control & VOFR_MASK_EI)); + printf("LI: %s\n", set(vh->control & VOFR_MASK_LI)); + printf("Control: %s (0x%02x)\n", + controlstr(vh->control & VOFR_MASK_CONTROL), vh->control & VOFR_MASK_CONTROL); + printf("Data Type: %s (0x%02x)\n", dtypestr(vh->dtype), vh->dtype); + if (vh->dtype == VOFR_TYPE_SIGNAL) { + printf(" \\--Signal: %s (0x%02x)\n", signalstr(vh->data[0]), vh->data[0]); + } + if (vh->dtype == VOFR_TYPE_DTMF) { + printf(" \\--Digit: %s (0x%02x)\n", vofr_digitstr(vh->data[0]), vh->data[0]); + } + printf("Connect Tag: 0x%02x\n", vh->ctag); + printf("Voice Rt Flags: %s\n", vflagsstr(vh->vflags)); + printf("DLCI X-Ref: %d\n", (vh->dlcih << 8) | (vh->dlcil)); + printf("Channel ID: %d\n", vh->cid); + printf("Remote ID: %s (0x%02x)\n", remidstr(vh->remid), vh->remid); + printf("Modulation: %s (0x%02x)\n", modulationstr(vh->mod), vh->mod); + printf("\n"); + fflush(stdout); +} + +#endif + +static int vofr_xmit(struct vofr_pvt *p, char *data, int len) +{ + int res; + res=sendto(p->s, data, len, 0, (struct sockaddr *)&p->sa, sizeof(struct sockaddr_pkt)); + if (res != len) { + ast_log(LOG_WARNING, "vofr_xmit returned %d\n", res); + } + return res; +} + +static int vofr_digit(struct ast_channel *ast, char digit) +{ + /* + * T H I S I S T O T A L L Y U N D O C U M E N T E D + * A N D D O E S N O T S O U N D R I G H T + * XXX Figure out how to really send a decent digit XXX + */ + struct vofr_pvt *p; + struct vofr_hdr *vh; + p = ast->pvt->pvt; + vh = p->ohdr; + vh->control = VOFR_CONTROL_VOICE; + vh->dtype = VOFR_TYPE_DTMF; + vh->vflags = VOFR_ROUTE_NONE; + vh->dlcih = p->dlcih; + vh->dlcil = p->dlcil; + vh->cid = p->cid; + vh->remid = VOFR_CARD_TYPE_ASTERISK; + vh->mod = VOFR_MODULATION_SINGLE; + if ((digit >= '0') && (digit <= '9')) + vh->data[0] = digit - '0'; + else if (digit == '*') + vh->data[0] = 10; + else if (digit == '#') + vh->data[0] = 11; + else { + ast_log(LOG_WARNING, "%s: tried to dial a non digit '%c'\n", ast->name, digit); + return -1; + } + vh->data[1] = 0x14; + vh->data[2] = 0x1f; + vh->data[3] = 0x70; + /* We sorta start the digit */ + vofr_xmit(p, p->obuf, VOFR_HDR_SIZE + 4 + FR_API_MESS); + usleep(30000); + /* And terminate with an empty voice frame */ + vh->control = VOFR_CONTROL_VOICE; + vh->dtype = VOFR_TYPE_VOICE; + vh->vflags = VOFR_ROUTE_NONE; + vh->dlcih = p->dlcih; + vh->dlcil = p->dlcil; + vh->cid = p->cid; + vh->remid = VOFR_CARD_TYPE_ASTERISK; + vh->mod = VOFR_MODULATION_SINGLE; + vofr_xmit(p, p->obuf, VOFR_HDR_SIZE + FR_API_MESS); + return 0; +} + +static int vofr_xmit_signal(struct vofr_pvt *p, int signal, int pad) +{ + /* Prepare and transmit outgoing buffer with given signal and + pad the end with *pad* bytes of data presumed to already + be in the buffer (like DTMF tones, etc) */ + struct vofr_hdr *vh = p->ohdr; + int res; + vh->control = VOFR_CONTROL_VOICE; + vh->dtype = VOFR_TYPE_SIGNAL; + vh->vflags = VOFR_ROUTE_NONE; + vh->dlcih = p->dlcih; + vh->dlcil = p->dlcil; + vh->cid = p->cid; + vh->remid = VOFR_CARD_TYPE_ASTERISK; + vh->mod = VOFR_MODULATION_SINGLE; + vh->data[0] = signal; + if (FR_API_MESS) + memset(p->obuf, 0, FR_API_MESS); + res = vofr_xmit(p, p->obuf, VOFR_HDR_SIZE + pad + 1 + FR_API_MESS); + return res; + +} + +static int vofr_call(struct ast_channel *ast, char *dest, int timeout) +{ + int res; + int otimeout; + struct ast_frame *f; + struct vofr_pvt *p; + p = ast->pvt->pvt; + if ((ast->state != AST_STATE_DOWN) && (ast->state != AST_STATE_RESERVED)) { + ast_log(LOG_WARNING, "vofr_call called on %s, neither down nor reserved\n", ast->name); + return -1; + } + /* Take the system off hook */ + vofr_xmit_signal(p, VOFR_SIGNAL_OFFHOOK, 0); + /* Wait for an acknowledgement */ + otimeout = 1000; + while(otimeout) { + otimeout = ast_waitfor(ast, 1000); + if (otimeout < 1) { + ast_log(LOG_WARNING, "Unable to take line off hook\n"); + /* Musta gotten hung up, or no ack on off hook */ + return -1; + } + f = ast_read(ast); + if (!f) + return -1; + if ((f->frametype == AST_FRAME_CONTROL) && + (f->subclass == AST_CONTROL_OFFHOOK)) + /* Off hook */ + break; + } + if (!otimeout) { + ast_log(LOG_WARNING, "Unable to take line off hook\n"); + return -1; + } + /* Send the digits */ + while(*dest) { + ast->state = AST_STATE_DIALING; + vofr_digit(ast, *dest); + /* Wait .1 seconds before dialing next digit */ + usleep(100000); + dest++; + } + if (timeout) { + /* Wait for the ack that it's ringing */ + otimeout = 1000; + while(otimeout) { + otimeout = ast_waitfor(ast, 1000); + if (otimeout < 1) { + ast_log(LOG_WARNING, "No acknowledgement for ringing\n"); + /* Musta gotten hung up, or no ack on off hook */ + return -1; + } + f = ast_read(ast); + if (!f) + return -1; + + if (f->frametype == AST_FRAME_CONTROL) { + if (f->subclass == AST_CONTROL_RINGING) { + ast->state = AST_STATE_RINGING; + /* We're ringing -- good enough */ + break; + } + if (f->subclass == AST_CONTROL_BUSY) + /* It's busy */ + return -1; + } + ast_frfree(f); + } + } + otimeout = timeout; + while(timeout) { + /* Wait for an answer, up to timeout... */ + res = ast_waitfor(ast, timeout); + if (res < 0) + /* Musta gotten hung up */ + return -1; + else + timeout = res; + if (res) { + /* Ooh, read what's there. */ + f = ast_read(ast); + if (!f) + return -1; + if ((f->frametype == AST_FRAME_CONTROL) && + (f->subclass == AST_CONTROL_ANSWER)) + /* Got an answer -- return the # of ms it took */ + return otimeout - res; + + } + } + return 0; +} + +static int send_hangup(struct vofr_pvt *p) +{ + /* Just send the hangup sequence */ + return vofr_xmit_signal(p, 0x80, 0); +} + +static int vofr_hangup(struct ast_channel *ast) +{ + int res; + if (option_debug) + ast_log(LOG_DEBUG, "vofr_hangup(%s)\n", ast->name); + if (!ast->pvt->pvt) { + ast_log(LOG_WARNING, "Asked to hangup channel not connected\n"); + return 0; + } + res = send_hangup(ast->pvt->pvt); + if (res < 0) { + ast_log(LOG_WARNING, "Unable to hangup line %s\n", ast->name); + return -1; + } + ast->state = AST_STATE_DOWN; + ((struct vofr_pvt *)(ast->pvt->pvt))->owner = NULL; + pthread_mutex_lock(&usecnt_lock); + usecnt--; + if (usecnt < 0) + ast_log(LOG_WARNING, "Usecnt < 0???\n"); + pthread_mutex_unlock(&usecnt_lock); + ast_update_use_count(); + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "Hungup '%s'\n", ast->name); + ast->pvt->pvt = NULL; + ast->state = AST_STATE_DOWN; + restart_monitor(); + return 0; +} + +static int vofr_answer(struct ast_channel *ast) +{ + int res; + int cnt = 1000; + char buf[2048]; + struct vofr_hdr *vh; + ast->rings = 0; + if (option_debug) + ast_log(LOG_DEBUG, "vofr_answer(%s)\n", ast->name); + res = vofr_xmit_signal(ast->pvt->pvt, VOFR_SIGNAL_OFFHOOK, 0); + if (res < 0) + ast_log(LOG_WARNING, "Unable to anaswer line %s\n", ast->name); + ast->state = AST_STATE_UP; + while(cnt > 0) { + cnt = ast_waitfor(ast, cnt); + if (cnt > 0) { + res = read(ast->fd, buf, sizeof(buf)); + res -= FR_API_MESS; + if (res < 0) + ast_log(LOG_WARNING, "Warning: read failed (%s) on %s\n", strerror(errno), ast->name); + else { + /* We're looking for an answer */ + vh = (struct vofr_hdr *)(buf + FR_API_MESS); + switch(vh->dtype) { + case VOFR_TYPE_SIGNAL: + switch(vh->data[0]) { + case VOFR_SIGNAL_UNKNOWN: + switch(vh->data[1]) { + case 0x1: + if (option_debug) + ast_log(LOG_DEBUG, "Answered '%s'\n", ast->name); + else if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "Answered '%s'\n", ast->name); + ast->state = AST_STATE_UP; + return 0; + break; + default: + ast_log(LOG_WARNING, "Unexpected 'unknown' frame type %d\n", vh->data[1]); + } + break; + case VOFR_SIGNAL_ON_HOOK: + /* Ignore onhooks. */ + break; + default: + ast_log(LOG_WARNING, "Unexpected signal type %d\n", vh->data[0]); + } + break; + default: + ast_log(LOG_WARNING, "Unexpected data type %d\n", vh->dtype); + } + } + } + } + ast_log(LOG_WARNING, "Did not get acknowledged answer\n"); + return -1; +} + +static char vofr_2digit(char c) +{ + if (c == 11) + return '#'; + else if (c == 10) + return '*'; + else if ((c < 10) && (c >= 0)) + return '0' + c; + else + return '?'; +} + +static struct ast_frame *vofr_read(struct ast_channel *ast) +{ + int res; + char tone; + int timeout,x; + struct vofr_pvt *p = ast->pvt->pvt; + short *swapping; + struct ast_frame *fr = (struct ast_frame *)(p->buf); + struct vofr_hdr *vh = (struct vofr_hdr *)(p->buf + sizeof(struct ast_frame) + AST_FRIENDLY_OFFSET - sizeof(struct vofr_hdr)); + /* Read into the right place in the buffer, in case we send this + as a voice frame. */ + CHECK_BLOCKING(ast); + res = read(p->s, ((char *)vh) - FR_API_MESS, + G723_MAX_BUF - AST_FRIENDLY_OFFSET - sizeof(struct ast_frame) + sizeof(struct vofr_hdr) + FR_API_MESS); + ast->blocking = 0; + res -= FR_API_MESS; + if (res < sizeof(struct vofr_hdr *)) { + ast_log(LOG_WARNING, "Nonsense frame on %s\n", ast->name); + return NULL; + } + + /* Some nice norms */ + fr->datalen = 0; + fr->timelen = 0; + fr->data = NULL; + fr->src = type; + fr->offset = 0; + fr->mallocd=0; + + /* Now, what we do depends on what we read */ + switch(vh->dtype) { + case VOFR_TYPE_SIGNAL: + switch(vh->data[0]) { + case VOFR_SIGNAL_ON_HOOK: + /* Hang up this line */ + if (ast->state == AST_STATE_UP) + return NULL; + else { + fr->frametype = AST_FRAME_NULL; + fr->subclass = 0; + break; + } + case VOFR_SIGNAL_UNKNOWN: + switch(vh->data[1]) { + case 0x1: + /* This is a little tricky, because it depends + on the context of what state we're in */ + switch(ast->state) { + case AST_STATE_RINGING: + fr->frametype = AST_FRAME_CONTROL; + fr->subclass = AST_CONTROL_ANSWER; + ast->state = AST_STATE_UP; + break; + case AST_STATE_DOWN: + case AST_STATE_UP: + fr->frametype = AST_FRAME_NULL; + fr->subclass = 0; + break; + } + break; + case 0x2: + /* Remote acknowledged off hook */ + fr->frametype = AST_FRAME_CONTROL; + fr->subclass = AST_CONTROL_OFFHOOK; + ast->state = AST_STATE_OFFHOOK; + break; + case 0x3: + /* Busy signal */ + fr->frametype = AST_FRAME_CONTROL; + fr->subclass = AST_CONTROL_BUSY; + ast->state = AST_STATE_BUSY; + break; + case 0x5: + /* Ringing -- acknowledged */ + fr->frametype = AST_FRAME_CONTROL; + fr->subclass = AST_CONTROL_RINGING; + ast->state = AST_STATE_RINGING; + break; + case 0x6: + /* Hang up detected. Return NULL */ + return NULL; + default: + ast_log(LOG_WARNING, "Don't know what to do with 'unknown' signal '%d'\n", vh->data[1]); + fr->frametype = AST_FRAME_NULL; + fr->subclass = 0; + } + return fr; + break; + default: + ast_log(LOG_WARNING, "Don't know what to do with signal '%d'\n", vh->data[0]); + } + break; + case VOFR_TYPE_DTMF: + /* If it's a DTMF tone, then we want to wait until we don't get any more dtmf tones or + the DTMF tone changes. + XXX Note: We will drop at least one frame here though XXX */ + + tone = vofr_2digit(vh->data[0]); + timeout = 50; + do { + if ((timeout = ast_waitfor(ast, timeout)) < 1) + break; + CHECK_BLOCKING(ast); + res = read(p->s, ((char *)vh) - FR_API_MESS, + G723_MAX_BUF - AST_FRIENDLY_OFFSET - sizeof(struct ast_frame) + sizeof(struct vofr_hdr) + FR_API_MESS); + ast->blocking = 0; + res -= FR_API_MESS; + if (res < sizeof(struct vofr_hdr *)) { + ast_log(LOG_WARNING, "Nonsense frame on %s\n", ast->name); + return NULL; + } + if (vh->dtype == VOFR_TYPE_DTMF) { + /* Reset the timeout */ + timeout = 50; + if ((tone != vofr_2digit(vh->data[0])) ) + /* Or not... Something else now.. Just send our first frame */ + break; + } + + } while (timeout); + fr->frametype = AST_FRAME_DTMF; + fr->subclass = tone; + fr->datalen = 0; + fr->data = NULL; + fr->offset = 0; + return fr; + case VOFR_TYPE_VOICE: + /* XXX Bug in the Adtran: Sometimes we don't know when calls are picked up, so if we + get voice frames, go ahead and consider it answered even though it probably has + not been answered XXX */ + if ((ast->state == AST_STATE_RINGING) || (ast->state == AST_STATE_DIALING)) { + ast_log(LOG_DEBUG, "Adtran bug! (state = %d)\n", ast->state); + fr->frametype = AST_FRAME_CONTROL; + fr->subclass = AST_CONTROL_ANSWER; + ast->state = AST_STATE_UP; + return fr; + } else if (ast->state != AST_STATE_UP) { + ast_log(LOG_WARNING, "Voice in weird state %d\n", ast->state); + } + fr->frametype = AST_FRAME_VOICE; + fr->subclass = AST_FORMAT_G723_1; + fr->datalen = res - sizeof(struct vofr_hdr); + fr->data = ((char *)vh) + sizeof(struct vofr_hdr); + fr->src = type; + /* XXX Byte swapping is a bug XXX */ + swapping = fr->data; + for (x=0;x<fr->datalen/2;x++) + swapping[x] = ntohs(swapping[x]); + fr->offset = AST_FRIENDLY_OFFSET; + /* Thirty ms of sound per frame */ + fr->timelen = 30; + return fr; + default: + ast_log(LOG_WARNING, "Don't know what to do with data type %d frames\n", vh->dtype); + } + /* If we don't know what it is, send a NULL frame */ + fr->frametype = AST_FRAME_NULL; + fr->subclass = 0; + return fr; +} + +static int vofr_write(struct ast_channel *ast, struct ast_frame *frame) +{ + struct vofr_hdr *vh; + struct vofr_pvt *p = ast->pvt->pvt; + short *swapping; + int x; + char *start; + int res; + /* Write a frame of (presumably voice) data */ + if (frame->frametype != AST_FRAME_VOICE) { + ast_log(LOG_WARNING, "Don't know what to do with frame type '%d'\n", frame->frametype); + return -1; + } + if (frame->subclass != AST_FORMAT_G723_1) { + ast_log(LOG_WARNING, "Cannot handle frames in %d format\n", frame->subclass); + return -1; + } + /* If we get here, we have a voice frame of G.723.1 data. First check to be + sure we have enough headroom for the vofr header. If there isn't enough + headroom, we're lazy and just error out rather than copying it into the + output buffer, because applications should always leave AST_FRIENDLY_OFFSET + bytes just for this reason. */ + if (frame->offset < sizeof(struct vofr_hdr) + FR_API_MESS) { + ast_log(LOG_WARNING, "Frame source '%s' didn't provide a friendly enough offset\n", (frame->src ? frame->src : "**Unknown**")); + return -1; + } + /* XXX Byte swapping is a bug XXX */ + swapping = frame->data; + for (x=0;x<frame->datalen/2;x++) + swapping[x] = ntohs(swapping[x]); + vh = (struct vofr_hdr *)(frame->data - sizeof(struct vofr_hdr)); + /* Some versions of the API have some header mess that needs to be + zero'd out and acounted for.. */ + start = ((void *)vh) - FR_API_MESS; + if (start) + memset(start, 0, FR_API_MESS); + /* Now we fill in the vofr header */ + vh->control = VOFR_CONTROL_VOICE; + vh->dtype = VOFR_TYPE_VOICE; + vh->vflags = VOFR_ROUTE_NONE; + vh->dlcih = p->dlcih; + vh->dlcil = p->dlcil; + vh->cid = p->cid; + vh->remid = VOFR_CARD_TYPE_ASTERISK; + vh->mod = VOFR_MODULATION_SINGLE; + res = vofr_xmit(p, start, + VOFR_HDR_SIZE + frame->datalen + FR_API_MESS); + res -= FR_API_MESS; + /* XXX Byte swapping is a bug, but get it back to the right format XXX */ + swapping = frame->data; + for (x=0;x<frame->datalen/2;x++) + swapping[x] = htons(swapping[x]); + if (res != VOFR_HDR_SIZE + frame->datalen) { + ast_log(LOG_WARNING, "Unable to write frame correctly\n"); + return -1; + } + return 0; +} + +static struct ast_channel *vofr_new(struct vofr_pvt *i, int state) +{ + struct ast_channel *tmp; + tmp = ast_channel_alloc(); + if (tmp) { + snprintf(tmp->name, sizeof(tmp->name), "AdtranVoFR/%s", i->sa.spkt_device); + tmp->type = type; + tmp->fd = i->s; + /* Adtran VoFR supports only G723.1 format data. G711 (ulaw) would be nice too */ + tmp->format = AST_FORMAT_G723_1; + tmp->state = state; + if (state == AST_STATE_RING) + tmp->rings = 1; + tmp->pvt->pvt = i; + tmp->pvt->send_digit = vofr_digit; + tmp->pvt->call = vofr_call; + tmp->pvt->hangup = vofr_hangup; + tmp->pvt->answer = vofr_answer; + tmp->pvt->read = vofr_read; + tmp->pvt->write = vofr_write; + i->owner = tmp; + pthread_mutex_lock(&usecnt_lock); + usecnt++; + pthread_mutex_unlock(&usecnt_lock); + ast_update_use_count(); + strncpy(tmp->context, i->context, sizeof(tmp->context)); + if (state != AST_STATE_DOWN) { + if (ast_pbx_start(tmp)) { + ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmp->name); + ast_hangup(tmp); + } + } + } else + ast_log(LOG_WARNING, "Unable to allocate channel structure\n"); + return tmp; +} + +static int vofr_mini_packet(struct vofr_pvt *i, struct vofr_hdr *pkt, int len) +{ + /* Here, we're looking for rings or off hooks -- signals that + something is about to happen and we need to start the + PBX thread */ + switch(pkt->dtype) { + case VOFR_TYPE_SIGNAL: + switch(pkt->data[0]) { + case VOFR_SIGNAL_RING: + /* If we get a RING, we definitely want to start a new thread */ + vofr_new(i, AST_STATE_RING); + break; + case VOFR_SIGNAL_ON_HOOK: + break; + case VOFR_SIGNAL_UNKNOWN: + switch(pkt->data[1]) { + case 0x1: + /* ignore */ + break; + case 0x6: + /* A remote hangup request */ + if (option_debug) + ast_log(LOG_DEBUG, "Sending hangup reply\n"); + send_hangup(i); + break; + default: + ast_log(LOG_WARNING, "Unexected 'unknown' signal '%d'\n", pkt->data[1]); + } + break; + default: + ast_log(LOG_DEBUG, "Unknown signal type '%d'\n", pkt->data[0]); + } + break; + case VOFR_TYPE_VOICE: + break; + default: + ast_log(LOG_DEBUG, "Unknown packet type '%d'\n", pkt->dtype); + } + return 0; +} + +static void *do_monitor(void *data) +{ + fd_set rfds; + int n, res; + struct vofr_pvt *i; + /* This thread monitors all the frame relay interfaces which are not yet in use + (and thus do not have a separate thread) indefinitely */ + /* From here on out, we die whenever asked */ + if (pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL)) { + ast_log(LOG_WARNING, "Unable to set cancel type to asynchronous\n"); + return NULL; + } + for(;;) { + /* Don't let anybody kill us right away. Nobody should lock the interface list + and wait for the monitor list, but the other way around is okay. */ + if (pthread_mutex_lock(&monlock)) { + ast_log(LOG_ERROR, "Unable to grab monitor lock\n"); + return NULL; + } + /* Lock the interface list */ + if (pthread_mutex_lock(&iflock)) { + ast_log(LOG_ERROR, "Unable to grab interface lock\n"); + pthread_mutex_unlock(&monlock); + return NULL; + } + /* Build the stuff we're going to select on, that is the socket of every + vofr_pvt that does not have an associated owner channel */ + n = -1; + FD_ZERO(&rfds); + i = iflist; + while(i) { + if (FD_ISSET(i->s, &rfds)) + ast_log(LOG_WARNING, "Descriptor %d appears twice (%s)?\n", i->s, i->sa.spkt_device); + if (!i->owner) { + /* This needs to be watched, as it lacks an owner */ + FD_SET(i->s, &rfds); + if (i->s > n) + n = i->s; + } + i = i->next; + } + /* Okay, now that we know what to do, release the interface lock */ + pthread_mutex_unlock(&iflock); + + /* And from now on, we're okay to be killed, so release the monitor lock as well */ + pthread_mutex_unlock(&monlock); + /* Wait indefinitely for something to happen */ + res = select(n + 1, &rfds, NULL, NULL, NULL); + /* Okay, select has finished. Let's see what happened. */ + if (res < 0) { + ast_log(LOG_WARNING, "select return %d: %s\n", res, strerror(errno)); + continue; + } + /* Alright, lock the interface list again, and let's look and see what has + happened */ + if (pthread_mutex_lock(&iflock)) { + ast_log(LOG_WARNING, "Unable to lock the interface list\n"); + continue; + } + i = iflist; + while(i) { + if (FD_ISSET(i->s, &rfds)) { + if (i->owner) { + ast_log(LOG_WARNING, "Whoa.... I'm owned but found (%d, %s)...\n", i->s, i->sa.spkt_device); + continue; + } + res = read(i->s, i->buf, sizeof(i->buf)); + res -= FR_API_MESS; +#ifdef VOFRDUMPER + vofr_dump_packet(i->hdr, res); +#endif + vofr_mini_packet(i, i->hdr, res); + } + i=i->next; + } + pthread_mutex_unlock(&iflock); + } + /* Never reached */ + return NULL; + +} + +static int restart_monitor() +{ + /* If we're supposed to be stopped -- stay stopped */ + if (monitor_thread == -2) + return 0; + if (pthread_mutex_lock(&monlock)) { + ast_log(LOG_WARNING, "Unable to lock monitor\n"); + return -1; + } + if (monitor_thread == pthread_self()) { + pthread_mutex_unlock(&monlock); + ast_log(LOG_WARNING, "Cannot kill myself\n"); + return -1; + } + if (monitor_thread != -1) { + pthread_cancel(monitor_thread); +#if 0 + pthread_join(monitor_thread, NULL); +#endif + } + /* Start a new monitor */ + if (pthread_create(&monitor_thread, NULL, do_monitor, NULL) < 0) { + pthread_mutex_unlock(&monlock); + ast_log(LOG_ERROR, "Unable to start monitor thread.\n"); + return -1; + } + pthread_mutex_unlock(&monlock); + return 0; +} + +struct vofr_pvt *mkif(char *type, char *iface) +{ + /* Make a vofr_pvt structure for this interface */ + struct vofr_pvt *tmp; + int sndbuf = 4096; + + tmp = malloc(sizeof(struct vofr_pvt)); + if (tmp) { + + /* Allocate a packet socket */ + tmp->s = socket(AF_INET, SOCK_PACKET, htons(ETH_P_ALL)); + if (tmp->s < 0) { + ast_log(LOG_ERROR, "Unable to create socket: %s\n", strerror(errno)); + free(tmp); + return NULL; + } + + /* Prepare sockaddr for binding */ + memset(&tmp->sa, 0, sizeof(tmp->sa)); + strncpy(tmp->sa.spkt_device, iface, sizeof(tmp->sa.spkt_device)); + tmp->sa.spkt_protocol = htons(0x16); + tmp->sa.spkt_family = AF_PACKET; + + /* Bind socket to specific interface */ + if (bind(tmp->s, (struct sockaddr *)&tmp->sa, sizeof(struct sockaddr))) { + ast_log(LOG_ERROR, "Unable to bind to '%s': %s\n", tmp->sa.spkt_device, + strerror(errno)); + free(tmp); + return NULL; + } + + /* Set magic send buffer size */ + if (setsockopt(tmp->s, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf))) { + ast_log(LOG_ERROR, "Unable to set send buffer size to %d: %s\n", sndbuf, strerror(errno)); + free(tmp); + return NULL; + } + tmp->owner = NULL; + tmp->hdr = (struct vofr_hdr *)(tmp->buf + FR_API_MESS); + tmp->ohdr = (struct vofr_hdr *)(tmp->obuf + FR_API_MESS); + tmp->dlcil = 0; + tmp->dlcih = 0; + tmp->cid = 1; + strncpy(tmp->context, context, sizeof(tmp->context)); + /* User terminations are game for outgoing connections */ + if (!strcasecmp(type, "user")) + tmp->outgoing = 1; + else + tmp->outgoing = 0; + tmp->next = NULL; + /* Hang it up to be sure it's good */ + send_hangup(tmp); + + } + return tmp; +} + +static struct ast_channel *vofr_request(char *type, int format, void *data) +{ + int oldformat; + struct vofr_pvt *p; + struct ast_channel *tmp = NULL; + /* We can only support G.723.1 formatted frames, but we should never + be asked to support anything else anyway, since we've published + our capabilities when we registered. */ + oldformat = format; + format &= AST_FORMAT_G723_1; + if (!format) { + ast_log(LOG_NOTICE, "Asked to get a channel of unsupported format '%d'\n", format); + return NULL; + } + /* Search for an unowned channel */ + if (pthread_mutex_lock(&iflock)) { + ast_log(LOG_ERROR, "Unable to lock interface list???\n"); + return NULL; + } + p = iflist; + while(p) { + if (!p->owner) { + tmp = vofr_new(p, AST_STATE_DOWN); + break; + } + p = p->next; + } + pthread_mutex_unlock(&iflock); + restart_monitor(); + return tmp; +} + +int load_module() +{ + struct ast_config *cfg; + struct ast_variable *v; + struct vofr_pvt *tmp; + cfg = ast_load(config); + + /* We *must* have a config file otherwise stop immediately */ + if (!cfg) { + ast_log(LOG_ERROR, "Unable to load config %s\n", config); + return -1; + } + if (pthread_mutex_lock(&iflock)) { + /* It's a little silly to lock it, but we mind as well just to be sure */ + ast_log(LOG_ERROR, "Unable to lock interface list???\n"); + return -1; + } + v = ast_variable_browse(cfg, "interfaces"); + while(v) { + /* Create the interface list */ + if (!strcasecmp(v->name, "user") || + !strcasecmp(v->name, "network")) { + tmp = mkif(v->name, v->value); + if (tmp) { + tmp->next = iflist; + iflist = tmp; + } else { + ast_log(LOG_ERROR, "Unable to register channel '%s'\n", v->value); + ast_destroy(cfg); + pthread_mutex_unlock(&iflock); + unload_module(); + return -1; + } + } else if (!strcasecmp(v->name, "context")) { + strncpy(context, v->value, sizeof(context)); + } + v = v->next; + } + pthread_mutex_unlock(&iflock); + /* Make sure we can register our AdtranVoFR channel type */ + if (ast_channel_register(type, tdesc, AST_FORMAT_G723_1, vofr_request)) { + ast_log(LOG_ERROR, "Unable to register channel class %s\n", type); + ast_destroy(cfg); + unload_module(); + return -1; + } + ast_destroy(cfg); + /* And start the monitor for the first time */ + restart_monitor(); + return 0; +} + + + +int unload_module() +{ + struct vofr_pvt *p, *pl; + /* First, take us out of the channel loop */ + ast_channel_unregister(type); + if (!pthread_mutex_lock(&iflock)) { + /* Hangup all interfaces if they have an owner */ + p = iflist; + while(p) { + if (p->owner) + ast_softhangup(p->owner); + p = p->next; + } + iflist = NULL; + pthread_mutex_unlock(&iflock); + } else { + ast_log(LOG_WARNING, "Unable to lock the monitor\n"); + return -1; + } + if (!pthread_mutex_lock(&monlock)) { + if (monitor_thread > -1) { + pthread_cancel(monitor_thread); + pthread_join(monitor_thread, NULL); + } + monitor_thread = -2; + pthread_mutex_unlock(&monlock); + } else { + ast_log(LOG_WARNING, "Unable to lock the monitor\n"); + return -1; + } + + if (!pthread_mutex_lock(&iflock)) { + /* Destroy all the interfaces and free their memory */ + p = iflist; + while(p) { + /* Close the socket, assuming it's real */ + if (p->s > -1) + close(p->s); + pl = p; + p = p->next; + /* Free associated memory */ + free(pl); + } + iflist = NULL; + pthread_mutex_unlock(&iflock); + } else { + ast_log(LOG_WARNING, "Unable to lock the monitor\n"); + return -1; + } + + return 0; +} + +int usecount() +{ + int res; + pthread_mutex_lock(&usecnt_lock); + res = usecnt; + pthread_mutex_unlock(&usecnt_lock); + return res; +} + +char *description() +{ + return desc; +} + |