diff options
author | (no author) <(no author)@f38db490-d61c-443f-a65b-d21fe96a405b> | 2000-01-07 11:02:42 +0000 |
---|---|---|
committer | (no author) <(no author)@f38db490-d61c-443f-a65b-d21fe96a405b> | 2000-01-07 11:02:42 +0000 |
commit | 19c91752e018ddfdec305793389170c34bab05af (patch) | |
tree | ffd13e39bf481d25b687694fbe66ee4514da8041 | |
parent | 758d7ac6f9c4db1450b46c6efd2f98280e193ca8 (diff) |
This commit was manufactured by cvs2svn to create tag 'v0-1-2'.
git-svn-id: http://svn.digium.com/svn/asterisk/tags/v0-1-2@200 f38db490-d61c-443f-a65b-d21fe96a405b
-rwxr-xr-x | CHANGES | 15 | ||||
-rwxr-xr-x | Makefile | 64 | ||||
-rwxr-xr-x | README | 56 | ||||
-rwxr-xr-x | channels/chan_iax.c | 2238 | ||||
-rwxr-xr-x | channels/chan_modem.c | 16 | ||||
-rwxr-xr-x | channels/chan_modem_aopen.c | 13 | ||||
-rwxr-xr-x | channels/chan_oss.c | 791 | ||||
-rwxr-xr-x | channels/chan_phone.c (renamed from channels/chan_ixj.c) | 378 | ||||
-rwxr-xr-x | channels/ixjuser.h | 4 | ||||
-rwxr-xr-x | codecs/Makefile | 12 | ||||
-rwxr-xr-x | codecs/codec_lpc10.c | 348 | ||||
-rwxr-xr-x | codecs/lpc10/lpc10.h | 4 | ||||
-rwxr-xr-x | codecs/lpc10_slin_ex.h | 13 | ||||
-rwxr-xr-x | codecs/slin_lpc10_ex.h | 21 | ||||
-rwxr-xr-x | config.c | 2 | ||||
-rwxr-xr-x | configs/iax.conf.sample | 72 | ||||
-rwxr-xr-x | configs/ixj.conf.sample | 19 | ||||
-rwxr-xr-x | configs/oss.conf.sample | 23 | ||||
-rwxr-xr-x | configs/phone.conf.sample | 35 | ||||
-rwxr-xr-x | frame.c | 102 | ||||
-rwxr-xr-x | pbx.c | 110 | ||||
-rwxr-xr-x | sounds/demo-moreinfo.gsm | bin | 0 -> 4158 bytes | |||
-rwxr-xr-x | translate.c | 266 |
23 files changed, 4325 insertions, 277 deletions
@@ -1,3 +1,18 @@ +* Asterisk 0.1.2 + -- Updated README file with a "Getting Started" section + -- Added sample sounds and configuration files. + -- Added LPC10 very low bandwidth (low quality) compression + -- Enhanced translation selection mechanism. + -- Enhanced IAX jitter buffer, improved reliability + -- Support echo cancelation on PhoneJack + -- Updated PhoneJack driver to std. Telephony interface + -- Added app_echo for evaluating VoIP latency + -- Added app_system to execute arbitrary programs + -- Updated sample configuration files + -- Added OSS channel driver (full duplex only) + -- Added IAX implementation + -- Fixed some deadlocks. + -- A whole bunch of bug fixes * Asterisk 0.1.1 -- Revised translator, fixed some general race conditions throughout * -- Made dialer somewhat more aware of incompatible voice channels @@ -16,16 +16,31 @@ MODULES_DIR=/usr/lib/asterisk/modules +# Pentium Pro Optimize +#PROC=i686 +# Pentium Optimize +PROC=i586 + DEBUG=-g #-pg INCLUDE=-Iinclude -I../include CFLAGS=-pipe -Wall -Werror -Wmissing-prototypes -Wmissing-declarations -O6 $(DEBUG) $(INCLUDE) -D_REENTRANT -CFLAGS+=$(shell if $(CC) -march=i686 -S -o /dev/null -xc /dev/null >/dev/null 2>&1; then echo "-march=i686"; fi) +CFLAGS+=$(shell if $(CC) -march=$(PROC) -S -o /dev/null -xc /dev/null >/dev/null 2>&1; then echo "-march=$(PROC)"; fi) SUBDIRS=channels pbx apps codecs formats -LIBS=-ldl -lpthread -lreadline # -lefence -OBJS=io.o sched.o logger.o frame.o loader.o config.o channel.o translate.o file.o say.o pbx.o cli.o asterisk.o +LIBS=-ldl -lpthread -lreadline #-lefence +OBJS=io.o sched.o logger.o frame.o loader.o config.o channel.o translate.o file.o say.o pbx.o cli.o md5.o asterisk.o CC=gcc INSTALL=install +_all: all + @echo " +--------- Asterisk Build Complete ---------+" + @echo " + Asterisk has successfully been built, but +" + @echo " + cannot be run before being installed by +" + @echo " + running: +" + @echo " + +" + @echo " + make install +" + @echo " + +" + @echo " +-------------------------------------------+" + all: asterisk subdirs asterisk: $(OBJS) @@ -38,8 +53,51 @@ clean: for x in $(SUBDIRS); do $(MAKE) -C $$x clean || exit 1 ; done rm -f *.o *.so asterisk +datafiles: all + mkdir -p /var/lib/asterisk/sounds/digits + for x in sounds/digits/*; do \ + install $$x /var/lib/asterisk/sounds/digits ; \ + done + for x in sounds/vm-* sounds/transfer* ; do \ + install $$x /var/lib/asterisk/sounds ; \ + done + install: all mkdir -p $(MODULES_DIR) for x in $(SUBDIRS); do $(MAKE) -C $$x install || exit 1 ; done install -d /usr/include/asterisk install include/asterisk/*.h /usr/include/asterisk + rm -f /var/lib/asterisk/sounds/vm + mkdir -p /var/spool/asterisk/vm + rm -f /usr/lib/asterisk/modules/chan_ixj.so + ( cd /var/lib/asterisk/sounds ; ln -s ../../../spool/asterisk/vm . ) + @echo " +---- Asterisk Installation Complete -------+" + @echo " + Asterisk has successfully been installed. +" + @echo " + If you would like to install the sample +" + @echo " + configuration files (overwriting any +" + @echo " + existing config files), run: +" + @echo " + +" + @echo " + make samples +" + @echo " + +" + @echo " +-------------------------------------------+" + +samples: all datafiles + mkdir -p /etc/asterisk + for x in configs/*.sample; do \ + if [ -f /etc/asterisk/`basename $$x .sample` ]; then \ + mv -f /etc/asterisk/`basename $$x .sample` /etc/asterisk/`basename $$x .sample`.old ; \ + fi ; \ + install $$x /etc/asterisk/`basename $$x .sample` ;\ + done + for x in sounds/demo-*; do \ + install $$x /var/lib/asterisk/sounds; \ + done + mkdir -p /var/lib/asterisk/sounds/vm/1234 + :> /var/lib/asterisk/sounds/vm/1234/unavail.gsm + for x in vm-theperson digits/1 digits/2 digits/3 digits/4 vm-isunavail; do \ + cat /var/lib/asterisk/sounds/$$x.gsm >> /var/lib/asterisk/sounds/vm/1234/unavail.gsm ; \ + done + :> /var/lib/asterisk/sounds/vm/1234/busy.gsm + for x in vm-theperson digits/1 digits/2 digits/3 digits/4 vm-isonphone; do \ + cat /var/lib/asterisk/sounds/$$x.gsm >> /var/lib/asterisk/sounds/vm/1234/busy.gsm ; \ + done @@ -22,6 +22,60 @@ as well. If you want to use format_wav module, then you need a very recent version of libaudiofile (at least version 0.2.0, or you can apply the -following patch to version 0.1.9): +included patch. RPMS for the patched libaudiofile are available at: +ftp://ftp.asteriskpbx.com/pub/asterisk/support +* GETTING STARTED + +First, be sure you've installed the required libaudiofile upgrade if +you want to use the non-GSM WAV format. Next, be sure you've got +supported hardware. To use Asterisk right now, you will need one of +the following: + + * Adtran Atlas 800 Plus + * QuickNet Internet PhoneJack + * Full Duplex Sound Card supported by Linux + +Assuming you have one of these (most likely the third) you're ready to +proceed: + +1) Run "make" +2) Run "make install" + +If this is your first time working with Asterisk, you may wish to install +the sample PBX, with demonstration extensions, etc. If so, run: + + "make samples" + +Doing so will overwrite any existing config files you have. + +Finally, you can launch Asterisk with: + + ./asterisk -vvvc + +If you get an error about unresolved symbols, install the updated +libaudiofile (available at ftp://ftp.asteriskpbx.com/pub/asterisk/support + +You'll see a bunch of verbose messages fly by your screen as Asterisk +initializes (that's the "very very verbose" mode). When it's ready, if +you specified the "c" then you'll get a command line console, that looks +like this: + +*CLI> + +You can type "help" at any time to get help with the system. For help +with a specific command, type "help <command>". To start the PBX using +your sound card, you can type "dial" to dial the PBX. Then you can use +"answer", "hangup", and "dial" to simulate the actions of a telephone. +Remember that if you don't have a full duplex sound card (And asterisk +will tell you somewhere in its verbose messages if you do/don't) than it +won't work right (not yet). + +Feel free to look over the configuration files in /etc/asterisk, where +you'll find a lot of information about what you can do with Asterisk. + +Finally, you may wish to visit the web site and join the mailing list if +you're interested in getting more information. + +Mark diff --git a/channels/chan_iax.c b/channels/chan_iax.c new file mode 100755 index 000000000..f3e7d337e --- /dev/null +++ b/channels/chan_iax.c @@ -0,0 +1,2238 @@ +/* + * Asterisk -- A telephony toolkit for Linux. + * + * Implementation of Inter-Asterisk eXchange + * + * Copyright (C) 1999, Mark Spencer + * + * Mark Spencer <markster@linux-support.net> + * + * This program is free software, distributed under the terms of + * the GNU General Public License + */ + +#include <asterisk/frame.h> +#include <asterisk/channel.h> +#include <asterisk/channel_pvt.h> +#include <asterisk/logger.h> +#include <asterisk/module.h> +#include <asterisk/pbx.h> +#include <asterisk/sched.h> +#include <asterisk/io.h> +#include <asterisk/config.h> +#include <asterisk/options.h> +#include <asterisk/cli.h> +#include <asterisk/translate.h> +#include <asterisk/md5.h> +#include <arpa/inet.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <sys/time.h> +#include <sys/signal.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <netdb.h> + +#include "iax.h" + +#define DEFAULT_RETRY_TIME 1000 +#define MEMORY_SIZE 100 +#define DEFAULT_DROP 3 + +/* If you want to use the simulator, then define IAX_SIMULATOR. */ + +/* +#define IAX_SIMULATOR +*/ +static char *desc = "Inter Asterisk eXchange"; +static char *tdesc = "Inter Asterisk eXchange Drver"; +static char *type = "IAX"; +static char *config = "iax.conf"; + +static char context[80] = "default"; + +static int max_retries = 4; +static int ping_time = 20; +static int lagrq_time = 10; +static int nextcallno = 0; +static int maxjitterbuffer=4000; + +static int netsocket = -1; + +static int usecnt; +static pthread_mutex_t usecnt_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t iaxs_lock = PTHREAD_MUTEX_INITIALIZER; + +/* Ethernet, etc */ +#define IAX_CAPABILITY_FULLBANDWIDTH 0x7FFFFFFF +/* T1, maybe ISDN */ +#define IAX_CAPABILITY_MEDBANDWIDTH (IAX_CAPABILITY_FULLBANDWIDTH & \ + ~AST_FORMAT_SLINEAR & \ + ~AST_FORMAT_ULAW & \ + ~AST_FORMAT_ALAW) +/* A modem */ +#define IAX_CAPABILITY_LOWBANDWIDTH (IAX_CAPABILITY_MEDBANDWIDTH & \ + ~AST_FORMAT_MP3 & \ + ~AST_FORMAT_ADPCM) + +#define IAX_CAPABILITY_LOWFREE (IAX_CAPABILITY_LOWBANDWIDTH & \ + ~AST_FORMAT_G723_1) + +static struct io_context *io; +static struct sched_context *sched; + +static int iax_capability = IAX_CAPABILITY_FULLBANDWIDTH; + +static int iax_dropcount = DEFAULT_DROP; + +static int use_jitterbuffer = 1; + +static pthread_t netthreadid; + +#define IAX_STATE_STARTED (1 << 0) +#define IAX_STATE_AUTHENTICATED (1 << 1) + +#define IAX_SENSE_DENY 0 +#define IAX_SENSE_ALLOW 1 + +struct iax_ha { + /* Host access rule */ + struct in_addr netaddr; + struct in_addr netmask; + int sense; + struct iax_ha *next; +}; + +struct iax_context { + char context[AST_MAX_EXTENSION]; + struct iax_context *next; +}; + +struct iax_user { + char name[80]; + char secret[80]; + char methods[80]; + struct iax_ha *ha; + struct iax_context *contexts; + struct iax_user *next; +}; + +struct iax_peer { + char name[80]; + char username[80]; + char secret[80]; + struct sockaddr_in addr; + struct in_addr mask; + struct iax_peer *next; +}; + +/* Don't retry more frequently than every 10 ms, or less frequently than every 5 seconds */ +#define MIN_RETRY_TIME 10 +#define MAX_RETRY_TIME 10000 +#define MAX_JITTER_BUFFER 50 + +/* If we have more than this much excess real jitter buffer, srhink it. */ +static int max_jitter_buffer = MAX_JITTER_BUFFER; + +struct chan_iax_pvt { + /* Pipes for communication. pipe[1] belongs to the + network thread (write), and pipe[0] belongs to the individual + channel (read) */ + int pipe[2]; + /* Last received voice format */ + int voiceformat; + /* Last sent voice format */ + int svoiceformat; + /* Last received timestamp */ + unsigned int last; + /* Last sent timestamp - never send the same timestamp twice in a single call */ + unsigned int lastsent; + /* Ping time */ + unsigned int pingtime; + /* Peer Address */ + struct sockaddr_in addr; + /* Our call number */ + int callno; + /* Peer callno */ + int peercallno; + /* Peer supported formats */ + int peerformats; + /* timeval that we base our transmission on */ + struct timeval offset; + /* timeval that we base our delivery on */ + struct timeval rxcore; + /* Historical delivery time */ + int history[MEMORY_SIZE]; + /* Current base jitterbuffer */ + int jitterbuffer; + /* Current jitter measure */ + int jitter; + /* LAG */ + int lag; + /* Error, as discovered by the manager */ + int error; + /* Onwer if we have one */ + struct ast_channel *owner; + /* What's our state? */ + int state; + /* Next outgoing sequence number */ + unsigned short oseqno; + /* Next incoming sequence number */ + unsigned short iseqno; + /* Peer name */ + char peer[80]; + /* Default Context */ + char context[80]; + /* Caller ID if available */ + char callerid[80]; + /* DNID */ + char dnid[80]; + /* Requested Extension */ + char exten[AST_MAX_EXTENSION]; + /* Expected Username */ + char username[80]; + /* Expected Secret */ + char secret[80]; + /* permitted authentication methods */ + char methods[80]; + /* MD5 challenge */ + char challenge[10]; +}; + +struct ast_iax_frame { + /* Actual, isolated frame */ + struct ast_frame *f; + /* /Our/ call number */ + short callno; + /* Start of raw frame (outgoing only) */ + void *data; + /* Length of frame (outgoing only) */ + int datalen; + /* How many retries so far? */ + int retries; + /* Outgoing relative timestamp (ms) */ + unsigned int ts; + /* How long to wait before retrying */ + int retrytime; + /* Are we received out of order? */ + int outoforder; + /* Have we been sent at all yet? */ + int sentyet; + /* Packet sequence number */ + int seqno; + /* Easy linking */ + struct ast_iax_frame *next; + struct ast_iax_frame *prev; +}; + +static struct ast_iax_queue { + struct ast_iax_frame *head; + struct ast_iax_frame *tail; + int count; + pthread_mutex_t lock; +} iaxq; + +static struct ast_user_list { + struct iax_user *users; + pthread_mutex_t lock; +} userl; + +static struct ast_peer_list { + struct iax_peer *peers; + pthread_mutex_t lock; +} peerl; + +/* XXX We probably should use a mutex when working with this XXX */ +static struct chan_iax_pvt *iaxs[AST_IAX_MAX_CALLS]; + +static int send_command(struct chan_iax_pvt *, char, int, unsigned int, char *, int, int); + +static unsigned int calc_timestamp(struct chan_iax_pvt *p, unsigned int ts); + +static int send_ping(void *data) +{ + int callno = (long)data; + if (iaxs[callno]) { + send_command(iaxs[callno], AST_FRAME_IAX, AST_IAX_COMMAND_PING, 0, NULL, 0, -1); + return 1; + } else + return 0; +} + +static int send_lagrq(void *data) +{ + int callno = (long)data; + if (iaxs[callno]) { + send_command(iaxs[callno], AST_FRAME_IAX, AST_IAX_COMMAND_LAGRQ, 0, NULL, 0, -1); + return 1; + } else + return 0; +} + +static unsigned char compress_subclass(int subclass) +{ + int x; + int power=-1; + /* If it's 128 or smaller, just return it */ + if (subclass < AST_FLAG_SC_LOG) + return subclass; + /* Otherwise find its power */ + for (x = 0; x < AST_MAX_SHIFT; x++) { + if (subclass & (1 << x)) { + if (power > -1) { + ast_log(LOG_WARNING, "Can't compress subclass %d\n", subclass); + return 0; + } else + power = x; + } + } + return power | AST_FLAG_SC_LOG; +} + +static int uncompress_subclass(unsigned char csub) +{ + /* If the SC_LOG flag is set, return 2^csub otherwise csub */ + if (csub & AST_FLAG_SC_LOG) + return 1 << (csub & ~AST_FLAG_SC_LOG & AST_MAX_SHIFT); + else + return csub; +} + +static struct chan_iax_pvt *new_iax(void) +{ + struct chan_iax_pvt *tmp; + tmp = malloc(sizeof(struct chan_iax_pvt)); + if (tmp) { + memset(tmp, 0, sizeof(struct chan_iax_pvt)); + /* On my linux system, pipe's are more than 2x as fast as socketpairs */ + if (pipe(tmp->pipe)) { + ast_log(LOG_WARNING, "Unable to create pipe: %s\n", strerror(errno)); + free(tmp); + return NULL; + } + tmp->callno = -1; + tmp->peercallno = -1; + /* strncpy(tmp->context, context, sizeof(tmp->context)); */ + strncpy(tmp->exten, "s", sizeof(tmp->exten)); + } + return tmp; +} + +static int get_timelen(struct ast_frame *f) +{ + int timelen=0; + switch(f->subclass) { + case AST_FORMAT_G723_1: + timelen = 30; + break; + case AST_FORMAT_GSM: + timelen = 20; + break; + case AST_FORMAT_SLINEAR: + timelen = f->datalen / 8; + break; + case AST_FORMAT_LPC10: + timelen = 22; + timelen += ((char *)(f->data))[7] & 0x1; + break; + default: + ast_log(LOG_WARNING, "Don't know how to calculate timelen on %d packets\n", f->subclass); + } + return timelen; +} + +#if 0 +static struct ast_iax_frame *iaxfrdup(struct ast_iax_frame *fr) +{ + /* Malloc() a copy of a frame */ + struct ast_iax_frame *new = malloc(sizeof(struct ast_iax_frame)); + if (new) + memcpy(new, fr, sizeof(struct ast_iax_frame)); + return new; +} +#endif + +static struct ast_iax_frame *iaxfrdup2(struct ast_iax_frame *fr, int ch) +{ + /* Malloc() a copy of a frame */ + struct ast_iax_frame *new = malloc(sizeof(struct ast_iax_frame)); + if (new) { + memcpy(new, fr, sizeof(struct ast_iax_frame)); + new->f = ast_frdup(fr->f); + /* Copy full header */ + if (ch) { + memcpy(new->f->data - sizeof(struct ast_iax_full_hdr), + fr->f->data - sizeof(struct ast_iax_full_hdr), + sizeof(struct ast_iax_full_hdr)); + /* Grab new data pointer */ + new->data = new->f->data - (fr->f->data - fr->data); + } else { + new->data = NULL; + new->datalen = 0; + } + } + return new; +} + +#define NEW_PREVENT 0 +#define NEW_ALLOW 1 +#define NEW_FORCE 2 + +static int find_callno(short callno, short dcallno ,struct sockaddr_in *sin, int new) +{ + int res = -1; + int x; + int start; + if (new <= NEW_ALLOW) { + /* Look for an existing connection first */ + for (x=0;x<AST_IAX_MAX_CALLS;x++) { + if (iaxs[x]) { + /* Look for an exact match */ + if ((sin->sin_port == iaxs[x]->addr.sin_port) && + (sin->sin_addr.s_addr == iaxs[x]->addr.sin_addr.s_addr) && + ((callno == iaxs[x]->peercallno) || /* Our expected source call number is the same */ + ((dcallno == x) && (iaxs[x]->peercallno = -1)) + /* We have no expected source number, and the destination is right */ + )) { + res = x; + break; + } + } + } + } + if ((res < 0) && (new >= NEW_ALLOW)) { + /* Create a new one */ + start = nextcallno; + for (x = nextcallno + 1; iaxs[x] && (x != start); x = (x + 1) % AST_IAX_MAX_CALLS) + if (x == start) { + ast_log(LOG_WARNING, "Unable to accept more calls\n"); + return -1; + } + iaxs[x] = new_iax(); + if (iaxs[x]) { + if (option_debug) + ast_log(LOG_DEBUG, "Creating new call structure %d\n", x); + iaxs[x]->addr.sin_port = sin->sin_port; + iaxs[x]->addr.sin_family = sin->sin_family; + iaxs[x]->addr.sin_addr.s_addr = sin->sin_addr.s_addr; + iaxs[x]->peercallno = callno; + iaxs[x]->callno = x; + iaxs[x]->pingtime = DEFAULT_RETRY_TIME; + ast_sched_add(sched, ping_time * 1000, send_ping, (void *)x); + ast_sched_add(sched, lagrq_time * 1000, send_lagrq, (void *)x); + } else + ast_log(LOG_DEBUG, "Out of memory\n"); + res = x; + nextcallno = x; + } + return res; +} + +static int iax_send(struct chan_iax_pvt *pvt, struct ast_frame *f, unsigned int ts, int seqno); + +static int do_deliver(void *data) +{ + /* Just deliver the packet by writing it to half of the pipe. */ + struct ast_iax_frame *fr = data; + unsigned int ts; + if (iaxs[fr->callno]) { + if (fr->f->frametype == AST_FRAME_IAX) { + /* We have to treat some of these packets specially because + they're LAG measurement packets */ + if (fr->f->subclass == AST_IAX_COMMAND_LAGRQ) { + /* If we got a queued request, build a reply and send it */ + fr->f->subclass = AST_IAX_COMMAND_LAGRP; + iax_send(iaxs[fr->callno], fr->f, fr->ts, -1); + } else if (fr->f->subclass == AST_IAX_COMMAND_LAGRP) { + /* This is a reply we've been given, actually measure the difference */ + ts = calc_timestamp(iaxs[fr->callno], 0); + iaxs[fr->callno]->lag = ts - fr->ts; + } + } else + ast_fr_fdwrite(iaxs[fr->callno]->pipe[1], fr->f); + } + /* Free the packet */ + ast_frfree(fr->f); + /* And our iax frame */ + free(fr); + /* And don't run again */ + return 0; +} + +static int handle_error() +{ + /* XXX Ideally we should figure out why an error occured and then abort those + rather than continuing to try. Unfortunately, the published interface does + not seem to work XXX */ +#if 0 + struct sockaddr_in *sin; + int res; + struct msghdr m; + struct sock_extended_err e; + m.msg_name = NULL; + m.msg_namelen = 0; + m.msg_iov = NULL; + m.msg_control = &e; + m.msg_controllen = sizeof(e); + m.msg_flags = 0; + res = recvmsg(netsocket, &m, MSG_ERRQUEUE); + if (res < 0) + ast_log(LOG_WARNING, "Error detected, but unable to read error: %s\n", strerror(errno)); + else { + if (m.msg_controllen) { + sin = (struct sockaddr_in *)SO_EE_OFFENDER(&e); + if (sin) + ast_log(LOG_WARNING, "Receive error from %s\n", inet_ntoa(sin->sin_addr)); + else + ast_log(LOG_WARNING, "No address detected??\n"); + } else { + ast_log(LOG_WARNING, "Local error: %s\n", strerror(e.ee_errno)); + } + } +#endif + return 0; +} + +#ifdef IAX_SIMULATOR +static int __send_packet(struct ast_iax_frame *f) +#else +static int send_packet(struct ast_iax_frame *f) +#endif +{ + int res; + if (option_debug) + ast_log(LOG_DEBUG, "Sending %d on %d/%d to %s:%d\n", f->ts, f->callno, iaxs[f->callno]->peercallno, inet_ntoa(iaxs[f->callno]->addr.sin_addr), ntohs(iaxs[f->callno]->addr.sin_port)); + /* Don't send if there was an error, but return error instead */ + if (f->callno < 0) { + ast_log(LOG_WARNING, "Call number = %d\n", f->callno); + return -1; + } + if (iaxs[f->callno]->error) + return -1; + res = sendto(netsocket, f->data, f->datalen, 0, &iaxs[f->callno]->addr, + sizeof(iaxs[f->callno]->addr)); + if (res < 0) { + ast_log(LOG_WARNING, "Received error: %s\n", strerror(errno)); + handle_error(); + } else + res = 0; + return res; +} + +#ifdef IAX_SIMULATOR + +/* Average amount of delay in the connection */ +static int average_delay = 0; +/* Permitted deviation either side of the average delay */ +static int delay_deviation = 0; +/* Percent chance that a packet arrives O.K. */ +static int reliability = 100; + +static int iax_sim_calc_delay() +{ + int ms; + ms = average_delay - delay_deviation; + ms += ((float)(delay_deviation * 2)) * rand() / (RAND_MAX + 1.0); + if (ms < 0) + ms = 0; + if ((float)rand()/(RAND_MAX + 1.0) < ((float)reliability)/100) + return ms; + else + return -1; +} + +static int d_send_packet(void *v) +{ + struct ast_iax_frame *f = (struct ast_iax_frame *)v; + if (iaxs[f->callno]) + __send_packet(f); + ast_frfree(f->f); + free(f); + return 0; +} + +static int send_packet(struct ast_iax_frame *f) +{ + struct ast_iax_frame *fn; + int ms; + ms = iax_sim_calc_delay(); + if (ms == 0) + return __send_packet(f); + else if (ms > 0) { + /* Make a completely independent frame, in case the other + is destroyed -- still doesn't make things like hangups + arrive if the main channel is destroyed, but close enough */ + fn = iaxfrdup2(f, 1); + ast_sched_add(sched, ms, d_send_packet, fn); + } /* else we drop the packet */ + return 0; +} + +static int iax_sim_set(int fd, int argc, char *argv[]) +{ + if (argc != 4) + return RESULT_SHOWUSAGE; + if (!strcasecmp(argv[2], "delay")) + average_delay = atoi(argv[3]); + else if (!strcasecmp(argv[2], "deviation")) + delay_deviation = atoi(argv[3]); + else if (!strcasecmp(argv[2], "reliability")) + reliability = atoi(argv[3]); + else + return RESULT_SHOWUSAGE; + if (reliability > 100) + reliability = 100; + if (reliability < 0) + reliability = 0; + if (delay_deviation > average_delay) + delay_deviation = average_delay; + return RESULT_SUCCESS; +} + +static char delay_usage[] = +"Usage: sim set delay <value>\n" +" Configure the IAX network simulator to generate average\n" +" delays equal to the specified value (in milliseconds).\n"; + +static char deviation_usage[] = +"Usage: sim set deviation <value>\n" +" Configures the IAX network simulator's deviation value.\n" +" The delays generated by the simulator will always be within\n" +" this value of milliseconds (postive or negative) of the \n" +" average delay.\n"; + +static char reliability_usage[] = +"Usage: sim set reliability <value>\n" +" Configure the probability that a packet will be delivered.\n" +" The value specified is a percentage from 0 to 100\n"; + +static int iax_sim_show(int fd, int argc, char *argv[]) +{ + if (argc != 2) + return RESULT_SHOWUSAGE; + ast_cli(fd, "Average Delay: %d ms\n", average_delay); + ast_cli(fd, "Delay Deviation: %d ms\n", delay_deviation); + ast_cli(fd, "Reliability: %d %\n", reliability); + return RESULT_SUCCESS; +} + +static char sim_show_usage[] = +"Usage: sim show\n" +" Displays average delay, deviation, and reliability\n" +" used by the network simulator.\n"; + +static struct ast_cli_entry delay_cli = +{ { "sim", "set", "delay", NULL }, iax_sim_set, "Sets simulated average delay", delay_usage }; +static struct ast_cli_entry deviation_cli = +{ { "sim", "set", "deviation", NULL }, iax_sim_set, "Sets simulated delay deviation", deviation_usage }; +static struct ast_cli_entry reliability_cli = +{ { "sim", "set", "reliability", NULL }, iax_sim_set, "Sets simulated reliability", reliability_usage }; +static struct ast_cli_entry sim_show_cli = +{ { "sim", "show", NULL }, iax_sim_show, "Displays simulation parameters", sim_show_usage }; + +#endif + +static int attempt_transmit(void *data) +{ + /* Attempt to transmit the frame to the remote peer */ + char zero = 0; + struct ast_iax_frame *f = data; + int res = 0; + /* Make sure this call is still active */ + if (iaxs[f->callno]) { + if ((f->retries == -1) /* Already ACK'd */ || + (f->retries >= max_retries) /* Too many attempts */) { + /* Record an error if we've transmitted too many times */ + if (f->retries >= max_retries) { + ast_log(LOG_WARNING, "Max retries exceeded to host %s (type = %d, subclass = %d, ts=%d, seqno=%d)\n", inet_ntoa(iaxs[f->callno]->addr.sin_addr), f->f->frametype, f->f->subclass, f->ts, f->seqno); + iaxs[f->callno]->error = ETIMEDOUT; + /* Send a bogus frame to wake up the waiting process */ + write(iaxs[f->callno]->pipe[1], &zero, 1); + } + /* Don't attempt delivery, just remove it from the queue */ + pthread_mutex_lock(&iaxq.lock); + if (f->prev) + f->prev->next = f->next; + else + iaxq.head = f->next; + if (f->next) + f->next->prev = f->prev; + else + iaxq.tail = f->prev; + iaxq.count--; + pthread_mutex_unlock(&iaxq.lock); + } else { + /* Attempt transmission */ + send_packet(f); + f->retries++; + /* Try again later after 2 times as long */ + f->retrytime *= 2; + if (f->retrytime > MAX_RETRY_TIME) + f->retrytime = MAX_RETRY_TIME; + ast_sched_add(sched, f->retrytime, attempt_transmit, f); + res=0; + } + } + /* Do not try again */ + return res; +} + +static int iax_set_jitter(int fd, int argc, char *argv[]) +{ + if ((argc != 4) && (argc != 5)) + return RESULT_SHOWUSAGE; + if (argc == 4) { + max_jitter_buffer = atoi(argv[3]); + if (max_jitter_buffer < 0) + max_jitter_buffer = 0; + } else { + if (argc == 5) { + pthread_mutex_lock(&iaxs_lock); + if ((atoi(argv[3]) >= 0) && (atoi(argv[3]) < AST_IAX_MAX_CALLS)) { + if (iaxs[atoi(argv[3])]) { + iaxs[atoi(argv[3])]->jitterbuffer = atoi(argv[4]); + if (iaxs[atoi(argv[3])]->jitterbuffer < 0) + iaxs[atoi(argv[3])]->jitterbuffer = 0; + } else + ast_cli(fd, "No such call '%d'\n", atoi(argv[3])); + } else + ast_cli(fd, "%d is not a valid call number\n", atoi(argv[3])); + pthread_mutex_unlock(&iaxs_lock); + } + } + return RESULT_SUCCESS; +} + +static char jitter_usage[] = +"Usage: iax set jitter [callid] <value>\n" +" If used with a callid, it sets the jitter buffer to the given static\n" +"value (until its next calculation). If used without a callid, the value is used\n" +"to establish the maximum excess jitter buffer that is permitted before the jitter\n" +"buffer size is reduced."; + +static struct ast_cli_entry cli_set_jitter = +{ { "iax", "set", "jitter", NULL }, iax_set_jitter, "Sets IAX jitter buffer", jitter_usage }; + +static unsigned int calc_rxstamp(struct chan_iax_pvt *p); + +static int schedule_delivery(struct ast_iax_frame *fr) +{ + /* XXX FIXME: I should delay delivery with a sliding jitter buffer XXX */ + int ms,x; + int drops[MEMORY_SIZE]; + int min, max=0, maxone=0,y,z, match; + /* ms is a measure of the "lateness" of the packet relative to the first + packet we received, which always has a lateness of 1. */ + ms = calc_rxstamp(iaxs[fr->callno]) - fr->ts; + + /* Rotate our history queue of "lateness". Don't worry about those initial + zeros because the first entry will always be zero */ + for (x=0;x<MEMORY_SIZE - 1;x++) + iaxs[fr->callno]->history[x] = iaxs[fr->callno]->history[x+1]; + /* Add a history entry for this one */ + iaxs[fr->callno]->history[x] = ms; + + /* Initialize the minimum to reasonable values. It's too much + work to do the same for the maximum, repeatedly */ + min=iaxs[fr->callno]->history[0]; + for (z=0;z < iax_dropcount + 1;z++) { + /* Start very pessimistic ;-) */ + max=-999999999; + for (x=0;x<MEMORY_SIZE;x++) { + if (max < iaxs[fr->callno]->history[x]) { + /* We have a candidate new maximum value. Make + sure it's not in our drop list */ + match = 0; + for (y=0;!match && (y<z);y++) + match |= (drops[y] == x); + if (!match) { + /* It's not in our list, use it as the new maximum */ + max = iaxs[fr->callno]->history[x]; + maxone = x; + } + + } + if (!z) { + /* On our first pass, find the minimum too */ + if (min > iaxs[fr->callno]->history[x]) + min = iaxs[fr->callno]->history[x]; + } + } +#if 1 + drops[z] = maxone; +#endif + } + /* Just for reference, keep the "jitter" value, the difference between the + earliest and the latest. */ + iaxs[fr->callno]->jitter = max - min; + + /* If our jitter buffer is too big (by a significant margin), then we slowly + shrink it by about 1 ms each time to avoid letting the change be perceived */ + if (max < iaxs[fr->callno]->jitterbuffer - max_jitter_buffer) + iaxs[fr->callno]->jitterbuffer -= 2; + +#if 1 + /* Constrain our maximum jitter buffer appropriately */ + if (max > min + maxjitterbuffer) { + if (option_debug) + ast_log(LOG_DEBUG, "Constraining buffer from %d to %d + %d\n", max, min , maxjitterbuffer); + max = min + maxjitterbuffer; + } +#endif + + /* If our jitter buffer is smaller than our maximum delay, grow the jitter + buffer immediately to accomodate it (and a little more). */ + if (max > iaxs[fr->callno]->jitterbuffer) + iaxs[fr->callno]->jitterbuffer = max + /* + ((float)iaxs[fr->callno]->jitter) * 0.1 */; + + + if (option_debug) + ast_log(LOG_DEBUG, "min = %d, max = %d, jb = %d, lateness = %d\n", min, max, iaxs[fr->callno]->jitterbuffer, ms); + + /* Subtract the lateness from our jitter buffer to know how long to wait + before sending our packet. */ + ms = iaxs[fr->callno]->jitterbuffer - ms; + + if (!use_jitterbuffer) + ms = 0; + + if (ms < 1) { + if (option_debug) + ast_log(LOG_DEBUG, "Calculated ms is %d\n", ms); + /* Don't deliver it more than 4 ms late */ + if ((ms > -4) || (fr->f->frametype != AST_FRAME_VOICE)) { + do_deliver(fr); + } + else { + /* Free the packet */ + ast_frfree(fr->f); + /* And our iax frame */ + free(fr); + } + } else { + if (option_debug) + ast_log(LOG_DEBUG, "Scheduling delivery in %d ms\n", ms); + ast_sched_add(sched, ms, do_deliver, fr); + } + return 0; +} + +static int iax_transmit(struct ast_iax_frame *fr) +{ + /* Lock the queue and place this packet at the end */ + fr->next = NULL; + fr->prev = NULL; + /* By setting this to 0, the network thread will send it for us, and + queue retransmission if necessary */ + fr->sentyet = 0; + pthread_mutex_lock(&iaxq.lock); + if (!iaxq.head) { + /* Empty queue */ + iaxq.head = fr; + iaxq.tail = fr; + } else { + /* Double link */ + iaxq.tail->next = fr; + fr->prev = iaxq.tail; + iaxq.tail = fr; + } + iaxq.count++; + pthread_mutex_unlock(&iaxq.lock); + /* Wake up the network thread */ + pthread_kill(netthreadid, SIGURG); + return 0; +} + + + +static int iax_digit(struct ast_channel *c, char digit) +{ + return send_command(c->pvt->pvt, AST_FRAME_DTMF, digit, 0, NULL, 0, -1); +} + +static int create_addr(struct sockaddr_in *sin, char *peer) +{ + struct hostent *hp; + struct iax_peer *p; + sin->sin_family = AF_INET; + pthread_mutex_lock(&peerl.lock); + p = peerl.peers; + while(p) { + if (!strcasecmp(p->name, peer)) { + sin->sin_addr = p->addr.sin_addr; + sin->sin_port = p->addr.sin_port; + break; + } + p = p->next; + } + pthread_mutex_unlock(&peerl.lock); + if (!p) { + hp = gethostbyname(peer); + if (hp) { + memcpy(&sin->sin_addr, hp->h_addr, sizeof(sin->sin_addr)); + sin->sin_port = htons(AST_DEFAULT_IAX_PORTNO); + return 0; + } else + return -1; + } else + return 0; +} +static int iax_call(struct ast_channel *c, char *dest, int timeout) +{ + struct sockaddr_in sin; + char host[256]; + char *rdest; + char *rcontext; + char *username; + char *hname; + char requeststr[256] = ""; + char myrdest [5] = "s"; + if ((c->state != AST_STATE_DOWN) && (c->state != AST_STATE_RESERVED)) { + ast_log(LOG_WARNING, "Line is already in use (%s)?\n", c->name); + return -1; + } + strncpy(host, dest, sizeof(host)); + strtok(host, ":"); + /* If no destination extension specified, use 's' */ + rdest = strtok(NULL, ":"); + if (!rdest) + rdest = myrdest; + strtok(rdest, "@"); + rcontext = strtok(NULL, "@"); + strtok(host, "@"); + username = strtok(NULL, "@"); + if (username) { + /* Really the second argument is the host, not the username */ + hname = username; + username = host; + } else { + hname = host; + } + if (create_addr(&sin, hname)) { + ast_log(LOG_WARNING, "No address associated with '%s'\n", hname); + return -1; + } + /* Now we build our request string */ +#define MYSNPRINTF snprintf(requeststr + strlen(requeststr), sizeof(requeststr) - strlen(requeststr), + MYSNPRINTF "exten=%s;", rdest); + if (c->callerid) + MYSNPRINTF "callerid=%s;", c->callerid); + if (c->dnid) + MYSNPRINTF "dnid=%s;", c->dnid); + if (rcontext) + MYSNPRINTF "context=%s;", rcontext); + if (username) + MYSNPRINTF "username=%s;", username); + MYSNPRINTF "formats=%d;", c->format); + MYSNPRINTF "version=%d;", AST_IAX_PROTO_VERSION); + /* Trim the trailing ";" */ + if (strlen(requeststr)) + requeststr[strlen(requeststr) - 1] = '\0'; + /* Transmit the string in a "NEW" request */ + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Calling using options '%s'\n", requeststr); + send_command((struct chan_iax_pvt *)c->pvt->pvt, AST_FRAME_IAX, + AST_IAX_COMMAND_NEW, 0, requeststr, strlen(requeststr) + 1, -1); + c->state = AST_STATE_RINGING; + return 0; +} + +static void iax_destroy(int callno) +{ + char zero=0; + struct chan_iax_pvt *pvt = iaxs[callno]; + if (pvt) { + if (pvt->owner) { + /* If there's an owner, prod it to give up */ + write(pvt->pipe[1], &zero, 1); + return; + } + iaxs[callno] = NULL; + close(pvt->pipe[0]); + close(pvt->pipe[1]); + free(pvt); + } +} + +static int iax_hangup(struct ast_channel *c) { + struct chan_iax_pvt *pvt = c->pvt->pvt; + /* Send the hangup unless we have had a transmission error */ + if (!pvt->error) { + send_command(pvt, AST_FRAME_IAX, AST_IAX_COMMAND_HANGUP, 0, NULL, 0, -1); + /* Wait for the network thread to transmit our command -- of course, if + it doesn't, that's okay too -- the other end will find out + soon enough, but it's a nicity if it can know now. */ + sleep(1); + } + pthread_mutex_lock(&iaxs_lock); + c->pvt->pvt = NULL; + 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", c->name); + iax_destroy(pvt->callno); + pthread_mutex_unlock(&iaxs_lock); + return 0; +} + +static struct ast_frame *iax_read(struct ast_channel *c) +{ + struct chan_iax_pvt *pvt = c->pvt->pvt; + struct ast_frame *f; + if (pvt->error) { + ast_log(LOG_DEBUG, "Connection closed, error: %s\n", strerror(pvt->error)); + return NULL; + } + f = ast_fr_fdread(pvt->pipe[0]); + if (f) { + if ((f->frametype == AST_FRAME_CONTROL) && + (f->subclass == AST_CONTROL_ANSWER)) + c->state = AST_STATE_UP; + } + return f; +} + +static int iax_answer(struct ast_channel *c) +{ + struct chan_iax_pvt *pvt = c->pvt->pvt; + if (option_debug) + ast_log(LOG_DEBUG, "Answering\n"); + return send_command(pvt, AST_FRAME_CONTROL, AST_CONTROL_ANSWER, 0, NULL, 0, -1); +} + +static int iax_write(struct ast_channel *c, struct ast_frame *f); + +static struct ast_channel *ast_iax_new(struct chan_iax_pvt *i, int state) +{ + struct ast_channel *tmp; + tmp = ast_channel_alloc(); + if (tmp) { + snprintf(tmp->name, sizeof(tmp->name), "IAX[%s:%d]/%d", inet_ntoa(i->addr.sin_addr), ntohs(i->addr.sin_port), i->callno); + tmp->type = type; + tmp->fd = i->pipe[0]; + /* We can support any format by default, until we get restricted */ + tmp->format = iax_capability; + tmp->pvt->pvt = i; + tmp->pvt->send_digit = iax_digit; + tmp->pvt->call = iax_call; + tmp->pvt->hangup = iax_hangup; + tmp->pvt->answer = iax_answer; + tmp->pvt->read = iax_read; + tmp->pvt->write = iax_write; + if (strlen(i->callerid)) + tmp->callerid = strdup(i->callerid); + if (strlen(i->dnid)) + tmp->dnid = strdup(i->dnid); + strncpy(tmp->context, i->context, sizeof(tmp->context)); + strncpy(tmp->exten, i->exten, sizeof(tmp->exten)); + i->owner = tmp; + tmp->state = state; + pthread_mutex_lock(&usecnt_lock); + usecnt++; + pthread_mutex_unlock(&usecnt_lock); + ast_update_use_count(); + 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); + tmp = NULL; + } + } + } + return tmp; +} + +static unsigned int calc_timestamp(struct chan_iax_pvt *p, unsigned int ts) +{ + struct timeval tv; + unsigned int ms; + if (!p->offset.tv_sec && !p->offset.tv_usec) + gettimeofday(&p->offset, NULL); + /* If the timestamp is specified, just send it as is */ + if (ts) + return ts; + gettimeofday(&tv, NULL); + ms = (tv.tv_sec - p->offset.tv_sec) * 1000 + (tv.tv_usec - p->offset.tv_usec) / 1000; + /* We never send the same timestamp twice, so fudge a little if we must */ + if (ms <= p->lastsent) + ms = p->lastsent + 1; + p->lastsent = ms; + return ms; +} + +static unsigned int calc_rxstamp(struct chan_iax_pvt *p) +{ + /* Returns where in "receive time" we are */ + struct timeval tv; + unsigned int ms; + if (!p->rxcore.tv_sec && !p->rxcore.tv_usec) + gettimeofday(&p->rxcore, NULL); + gettimeofday(&tv, NULL); + ms = (tv.tv_sec - p->offset.tv_sec) * 1000 + (tv.tv_usec - p->offset.tv_usec) / 1000; + return ms; +} +static int iax_send(struct chan_iax_pvt *pvt, struct ast_frame *f, unsigned int ts, int seqno) +{ + /* Queue a packet for delivery on a given private structure. Use "ts" for + timestamp, or calculate if ts is 0 */ + struct ast_iax_full_hdr *fh; + struct ast_iax_mini_hdr *mh; + struct ast_iax_frame *fr; + int res; + unsigned int lastsent; + /* Allocate an ast_iax_frame */ + fr = malloc(sizeof(struct ast_iax_frame)); + if (!fr) { + ast_log(LOG_WARNING, "Out of memory\n"); + return -1; + } + if (!pvt) { + ast_log(LOG_WARNING, "No private structure for packet (%d)?\n", fr->callno); + free(fr); + return -1; + } + /* Isolate our frame for transmission */ + fr->f = ast_frdup(f); + if (!fr->f) { + ast_log(LOG_WARNING, "Out of memory\n"); + free(fr); + return -1; + } + if (fr->f->offset < sizeof(struct ast_iax_full_hdr)) { + ast_log(LOG_WARNING, "Packet from '%s' not friendly\n", fr->f->src); + free(fr); + return -1; + } + lastsent = pvt->lastsent; + fr->ts = calc_timestamp(pvt, ts); + if (!fr->ts) { + ast_log(LOG_WARNING, "timestamp is 0?\n"); + return -1; + } + fr->callno = pvt->callno; + if (((fr->ts & 0xFFFF0000L) != (lastsent & 0xFFFF0000L)) + /* High two bits of timestamp differ */ || + (fr->f->frametype != AST_FRAME_VOICE) + /* or not a voice frame */ || + (fr->f->subclass != pvt->svoiceformat) + /* or new voice format */ ) { + /* We need a full frame */ + if (seqno > -1) + fr->seqno = seqno; + else + fr->seqno = pvt->oseqno++; + fh = (struct ast_iax_full_hdr *)(fr->f->data - sizeof(struct ast_iax_full_hdr)); + fh->callno = htons(fr->callno | AST_FLAG_FULL); + fh->ts = htonl(fr->ts); + fh->seqno = htons(fr->seqno); + fh->type = fr->f->frametype & 0xFF; + fh->csub = compress_subclass(fr->f->subclass); +#if 0 + fh->subclasshigh = (fr->f->subclass & 0xFF0000) >> 16; + fh->subclasslow = htons(fr->f->subclass & 0xFFFF); +#endif + fh->dcallno = htons(pvt->peercallno); + fr->datalen = fr->f->datalen + sizeof(struct ast_iax_full_hdr); + fr->data = fh; + fr->retries = 0; + /* Retry after 2x the ping time has passed */ + fr->retrytime = pvt->pingtime * 2; + if (fr->retrytime < MIN_RETRY_TIME) + fr->retrytime = MIN_RETRY_TIME; + if (fr->retrytime > MAX_RETRY_TIME) + fr->retrytime = MAX_RETRY_TIME; + /* Acks' don't get retried */ + if ((f->frametype == AST_FRAME_IAX) && (f->subclass == AST_IAX_COMMAND_ACK)) + fr->retries = -1; + if (f->frametype == AST_FRAME_VOICE) { + pvt->svoiceformat = f->subclass; + } + res = iax_transmit(fr); + } else { + /* Mini-frames have no sequence number */ + fr->seqno = -1; + /* Mini frame will do */ + mh = (struct ast_iax_mini_hdr *)(fr->f->data - sizeof(struct ast_iax_mini_hdr)); + mh->callno = htons(fr->callno); + mh->ts = htons(fr->ts & 0xFFFF); + fr->datalen = fr->f->datalen + sizeof(struct ast_iax_mini_hdr); + fr->data = mh; + fr->retries = -1; + res = iax_transmit(fr); + } + return res; +} + + + +static int iax_show_users(int fd, int argc, char *argv[]) +{ +#define FORMAT "%-15.15s %-15.15s %-15.15s %-15.15s %-5.5s\n" + struct iax_user *user; + if (argc != 3) + return RESULT_SHOWUSAGE; + pthread_mutex_lock(&userl.lock); + ast_cli(fd, FORMAT, "Username", "Secret", "Authen", "Def.Context", "A/C"); + for(user=userl.users;user;user=user->next) { + ast_cli(fd, FORMAT, user->name, user->secret, user->methods, + user->contexts ? user->contexts->context : context, + user->ha ? "Yes" : "No"); + } + pthread_mutex_unlock(&userl.lock); + return RESULT_SUCCESS; +#undef FORMAT +} + +static int iax_show_peers(int fd, int argc, char *argv[]) +{ +#define FORMAT2 "%-15.15s %-15.15s %-15.15s %-15.15s %s\n" +#define FORMAT "%-15.15s %-15.15s %-15.15s %-15.15s %d\n" + struct iax_peer *peer; + if (argc != 3) + return RESULT_SHOWUSAGE; + pthread_mutex_lock(&peerl.lock); + ast_cli(fd, FORMAT2, "Name", "Username", "Host", "Mask", "Port"); + for (peer = peerl.peers;peer;peer = peer->next) { + char nm[20]; + strncpy(nm, inet_ntoa(peer->mask), sizeof(nm)); + ast_cli(fd, FORMAT, peer->name, + peer->username ? peer->username : "(Any)", + peer->addr.sin_addr.s_addr ? inet_ntoa(peer->addr.sin_addr) : "(Any)", + nm, + ntohs(peer->addr.sin_port)); + } + pthread_mutex_unlock(&peerl.lock); + return RESULT_SUCCESS; +#undef FORMAT +#undef FORMAT2 +} + +static int iax_show_channels(int fd, int argc, char *argv[]) +{ +#define FORMAT2 "%-15.15s %-10.10s %-11.11s %-11.11s %-7.7s %-6.6s %s\n" +#define FORMAT "%-15.15s %-10.10s %5.5d/%5.5d %5.5d/%5.5d %-5.5dms %-4.4dms %d\n" + int x; + if (argc != 3) + return RESULT_SHOWUSAGE; + pthread_mutex_lock(&iaxs_lock); + ast_cli(fd, FORMAT2, "Peer", "Username", "ID (Lo/Rem)", "Seq (Tx/Rx)", "Lag", "Jitter", "Format"); + for (x=0;x<AST_IAX_MAX_CALLS;x++) + if (iaxs[x]) + ast_cli(fd, FORMAT, inet_ntoa(iaxs[x]->addr.sin_addr), + strlen(iaxs[x]->username) ? iaxs[x]->username : "(None)", + iaxs[x]->callno, iaxs[x]->peercallno, + iaxs[x]->oseqno, iaxs[x]->iseqno, + iaxs[x]->lag, + iaxs[x]->jitter, + iaxs[x]->voiceformat); + pthread_mutex_unlock(&iaxs_lock); + return RESULT_SUCCESS; +#undef FORMAT +#undef FORMAT2 +} + +static char show_users_usage[] = +"Usage: iax show users\n" +" Lists all users known to the IAX (Inter-Asterisk eXchange) subsystem.\n"; + +static char show_channels_usage[] = +"Usage: iax show channels\n" +" Lists all currently active IAX channels.\n"; + +static char show_peers_usage[] = +"Usage: iax show peers\n" +" Lists all known IAX peers.\n"; + +static struct ast_cli_entry cli_show_users = + { { "iax", "show", "users", NULL }, iax_show_users, "Show defined IAX users", show_users_usage }; +static struct ast_cli_entry cli_show_channels = + { { "iax", "show", "channels", NULL }, iax_show_channels, "Show active IAX channels", show_channels_usage }; +static struct ast_cli_entry cli_show_peers = + { { "iax", "show", "peers", NULL }, iax_show_peers, "Show defined IAX peers", show_peers_usage }; + +static int iax_write(struct ast_channel *c, struct ast_frame *f) +{ + struct chan_iax_pvt *i = c->pvt->pvt; + /* If there's an outstanding error, return failure now */ + if (i->error) { + ast_log(LOG_DEBUG, "Write error: %s\n", strerror(errno)); + return -1; + } + /* Don't waste bandwidth sending null frames */ + if (f->frametype == AST_FRAME_NULL) + return 0; + /* Simple, just queue for transmission */ + return iax_send(i, f, 0, -1); +} + +static int send_command(struct chan_iax_pvt *i, char type, int command, unsigned int ts, char *data, int datalen, int seqno) +{ + struct ast_frame f; + f.frametype = type; + f.subclass = command; + f.datalen = datalen; + f.timelen = 0; + f.mallocd = 0; + f.offset = 0; + f.src = __FUNCTION__; + f.data = data; + return iax_send(i, &f, ts, seqno); +} + +static int apply_context(struct iax_context *con, char *context) +{ + while(con) { + if (!strcmp(con->context, context)) + return -1; + con = con->next; + } + return 0; +} + +static int apply_ha(struct iax_ha *ha, struct sockaddr_in *sin) +{ + /* Start optimistic */ + int res = IAX_SENSE_ALLOW; + while(ha) { + /* For each rule, if this address and the netmask = the net address + apply the current rule */ + if ((sin->sin_addr.s_addr & ha->netmask.s_addr) == (ha->netaddr.s_addr)) + res = ha->sense; + ha = ha->next; + } + return res; +} + +static int check_access(int callno, struct sockaddr_in *sin, char *orequest, int requestl) +{ + /* Start pessimistic */ + int res = -1; + int version = 1; + char *var, *value; + struct iax_user *user; + char request[256]; + strncpy(request, orequest, sizeof(request)); + if (!iaxs[callno]) + return res; + var = strtok(request, ";"); + while(var) { + value = strchr(var, '='); + if (value) { + *value='\0'; + value++; + if (!strcmp(var, "exten")) + strncpy(iaxs[callno]->exten, value, sizeof(iaxs[callno]->exten)); + else if (!strcmp(var, "callerid")) + strncpy(iaxs[callno]->callerid, value, sizeof(iaxs[callno]->callerid)); + else if (!strcmp(var, "dnid")) + strncpy(iaxs[callno]->dnid, value, sizeof(iaxs[callno]->dnid)); + else if (!strcmp(var, "context")) + strncpy(iaxs[callno]->context, value, sizeof(iaxs[callno]->context)); + else if (!strcmp(var, "username")) + strncpy(iaxs[callno]->username, value, sizeof(iaxs[callno]->username)); + else if (!strcmp(var, "formats")) + iaxs[callno]->peerformats = atoi(value); + else if (!strcmp(var, "version")) + version = atoi(value); + else + ast_log(LOG_WARNING, "Unknown variable '%s' with value '%s'\n", var, value); + } + var = strtok(NULL, ";"); + } + if (version > AST_IAX_PROTO_VERSION) { + ast_log(LOG_WARNING, "Peer '%s' has too new a protocol version (%d) for me\n", + inet_ntoa(sin->sin_addr), version); + return res; + } + pthread_mutex_lock(&userl.lock); + /* Search the userlist for a compatible entry, and fill in the rest */ + user = userl.users; + while(user) { + if ((!strlen(iaxs[callno]->username) || /* No username specified */ + !strcmp(iaxs[callno]->username, user->name)) /* Or this username specified */ + && (apply_ha(user->ha, sin) == IAX_SENSE_ALLOW) /* Access is permitted from this IP */ + && (!strlen(iaxs[callno]->context) || /* No context specified */ + apply_context(user->contexts, iaxs[callno]->context))) { /* Context is permitted */ + /* We found our match (use the first) */ + + /* Store the requested username if not specified */ + if (!strlen(iaxs[callno]->username)) + strncpy(iaxs[callno]->username, user->name, sizeof(iaxs[callno]->username)); + /* And use the default context */ + if (!strlen(iaxs[callno]->context)) { + if (user->contexts) + strncpy(iaxs[callno]->context, user->contexts->context, sizeof(iaxs[callno]->context)); + else + strncpy(iaxs[callno]->context, context, sizeof(iaxs[callno]->context)); + } + /* Copy the secret */ + strncpy(iaxs[callno]->secret, user->secret, sizeof(iaxs[callno]->secret)); + /* And the permitted authentication methods */ + strncpy(iaxs[callno]->methods, user->methods, sizeof(iaxs[callno]->methods)); + res = 0; + break; + } + user = user->next; + } + pthread_mutex_unlock(&userl.lock); + return res; +} + +static int raw_hangup(struct sockaddr_in *sin, short src, short dst) +{ + struct ast_iax_full_hdr fh; + fh.callno = htons(src | AST_FLAG_FULL); + fh.dcallno = htons(dst); + fh.ts = 0; + fh.seqno = 0; + fh.type = AST_FRAME_IAX; + fh.csub = compress_subclass(AST_IAX_COMMAND_INVAL); + if (option_debug) + ast_log(LOG_DEBUG, "Raw Hangup\n"); + return sendto(netsocket, &fh, sizeof(fh), 0, sin, sizeof(*sin)); +} + +static int authenticate_request(struct chan_iax_pvt *p) +{ + char requeststr[256] = ""; + MYSNPRINTF "methods=%s;", p->methods); + if (strstr(p->methods, "md5")) { + /* Build the challenge */ + srand(time(NULL)); + snprintf(p->challenge, sizeof(p->challenge), "%d", rand()); + MYSNPRINTF "challenge=%s;", p->challenge); + } + MYSNPRINTF "username=%s;", p->username); + if (strlen(requeststr)) + requeststr[strlen(requeststr) - 1] = '\0'; + return send_command(p, AST_FRAME_IAX, AST_IAX_COMMAND_AUTHREQ, 0, requeststr, strlen(requeststr) + 1, -1); +} + +static int authenticate_verify(struct chan_iax_pvt *p, char *orequest) +{ + char requeststr[256] = ""; + char *var, *value, request[256]; + char md5secret[256] = ""; + char secret[256] = ""; + int res = -1; + int x; + + if (!(p->state & IAX_STATE_AUTHENTICATED)) + return res; + strncpy(request, orequest, sizeof(request)); + var = strtok(request, ";"); + while(var) { + value = strchr(var, '='); + if (value) { + *value='\0'; + value++; + if (!strcmp(var, "secret")) + strncpy(secret, value, sizeof(secret)); + else if (!strcmp(var, "md5secret")) + strncpy(md5secret, value, sizeof(md5secret)); + else + ast_log(LOG_WARNING, "Unknown variable '%s' with value '%s'\n", var, value); + } + var = strtok(NULL, ";"); + } + if (strstr(p->methods, "md5")) { + struct MD5Context md5; + unsigned char digest[16]; + MD5Init(&md5); + MD5Update(&md5, p->challenge, strlen(p->challenge)); + MD5Update(&md5, p->secret, strlen(p->secret)); + MD5Final(digest, &md5); + /* If they support md5, authenticate with it. */ + for (x=0;x<16;x++) + MYSNPRINTF "%2.2x", digest[x]); + if (!strcasecmp(requeststr, md5secret)) + res = 0; + } else if (strstr(p->methods, "plaintext")) { + if (!strcmp(secret, p->secret)) + res = 0; + } + return res; +} + +static int authenticate_reply(struct chan_iax_pvt *p, struct sockaddr_in *sin, char *orequest) +{ + struct iax_peer *peer; + /* Start pessimistic */ + int res = -1; + char request[256]; + char methods[80] = ""; + char requeststr[256] = ""; + char *var, *value; + int x; + strncpy(request, orequest, sizeof(request)); + var = strtok(request, ";"); + while(var) { + value = strchr(var, '='); + if (value) { + *value='\0'; + value++; + if (!strcmp(var, "username")) + strncpy(p->username, value, sizeof(p->username)); + else if (!strcmp(var, "challenge")) + strncpy(p->challenge, value, sizeof(p->challenge)); + else if (!strcmp(var, "methods")) + strncpy(methods, value, sizeof(methods)); + else + ast_log(LOG_WARNING, "Unknown variable '%s' with value '%s'\n", var, value); + } + var = strtok(NULL, ";"); + } + pthread_mutex_lock(&peerl.lock); + peer = peerl.peers; + while(peer) { + if ((!strlen(p->peer) || !strcmp(p->peer, peer->name)) + /* No peer specified at our end, or this is the peer */ + && (!strlen(peer->username) || (!strcmp(peer->username, p->username))) + /* No username specified in peer rule, or this is the right username */ + && (!peer->addr.sin_addr.s_addr || ((sin->sin_addr.s_addr & peer->mask.s_addr) == (peer->addr.sin_addr.s_addr & peer->mask.s_addr))) + /* No specified host, or this is our host */ + ) { + /* We have a match, authenticate it. */ + res = 0; + if (strstr(methods, "md5")) { + struct MD5Context md5; + unsigned char digest[16]; + MD5Init(&md5); + MD5Update(&md5, p->challenge, strlen(p->challenge)); + MD5Update(&md5, peer->secret, strlen(peer->secret)); + MD5Final(digest, &md5); + /* If they support md5, authenticate with it. */ + MYSNPRINTF "md5secret="); + for (x=0;x<16;x++) + MYSNPRINTF "%2.2x", digest[x]); + MYSNPRINTF ";"); + } else if (strstr(methods, "plaintext")) { + MYSNPRINTF "secret=%s;", peer->secret); + } else + res = -1; + if (strlen(requeststr)) + requeststr[strlen(requeststr)-1] = '\0'; + if (!res) + res = send_command(p, AST_FRAME_IAX, AST_IAX_COMMAND_AUTHREP, 0, requeststr, strlen(requeststr) + 1, -1); + break; + } + peer = peer->next; + } + pthread_mutex_unlock(&peerl.lock); + return res; +} + +static int socket_read(int *id, int fd, short events, void *cbdata) +{ + struct sockaddr_in sin; + int res; + int new = NEW_PREVENT; + char buf[4096]; + char src[80]; + int len = sizeof(sin); + int dcallno = -1; + struct ast_iax_full_hdr *fh = (struct ast_iax_full_hdr *)buf; + struct ast_iax_mini_hdr *mh = (struct ast_iax_mini_hdr *)buf; + struct ast_iax_frame fr, *cur; + struct ast_frame f; + struct ast_channel *c; + res = recvfrom(netsocket, buf, sizeof(buf), 0, &sin, &len); + if (res < 0) { + ast_log(LOG_WARNING, "Error: %s\n", strerror(errno)); + handle_error(); + return 1; + } + if (res < sizeof(struct ast_iax_mini_hdr)) { + ast_log(LOG_WARNING, "midget packet received (%d of %d min)\n", res, sizeof(struct ast_iax_mini_hdr)); + return 1; + } + if (ntohs(mh->callno) & AST_FLAG_FULL) { + /* Get the destination call number */ + dcallno = ntohs(fh->dcallno); + /* Retrieve the type and subclass */ + f.frametype = fh->type; + f.subclass = uncompress_subclass(fh->csub); +#if 0 + f.subclass = fh->subclasshigh << 16; + f.subclass += ntohs(fh->subclasslow); +#endif + if ((f.frametype == AST_FRAME_IAX) && (f.subclass == AST_IAX_COMMAND_NEW)) + new = NEW_ALLOW; + } + pthread_mutex_lock(&iaxs_lock); + fr.callno = find_callno(ntohs(mh->callno) & ~AST_FLAG_FULL, dcallno, &sin, new); + if ((fr.callno < 0) || !iaxs[fr.callno]) { + /* A call arrived for a non-existant destination. Unless it's an "inval" + frame, reply with an inval */ + if (ntohs(mh->callno) & AST_FLAG_FULL) { + /* We can only raw hangup control frames */ + if ((f.subclass != AST_IAX_COMMAND_INVAL) || (f.frametype != AST_FRAME_IAX)) + raw_hangup(&sin, ntohs(fh->dcallno), ntohs(mh->callno)); + } + pthread_mutex_unlock(&iaxs_lock); + return 1; + } + iaxs[fr.callno]->peercallno = ntohs(mh->callno) & ~AST_FLAG_FULL; + if (ntohs(mh->callno) & AST_FLAG_FULL) { + if (option_debug) + ast_log(LOG_DEBUG, "Received packet %d, (%d, %d)\n", ntohs(fh->seqno), f.frametype, f.subclass); + /* Check if it's out of order (and not an ACK or INVAL) */ + fr.seqno = ntohs(fh->seqno); + if (iaxs[fr.callno]->iseqno != fr.seqno) { + if ( + ((f.subclass != AST_IAX_COMMAND_ACK) && (f.subclass != AST_IAX_COMMAND_INVAL)) || + (f.frametype != AST_FRAME_IAX)) { + /* If it's not an ACK packet, it's out of order. */ + if (option_debug) + ast_log(LOG_DEBUG, "Packet arrived out of order (expecting %d, got %d) (frametype = %d, subclass = %d)\n", + iaxs[fr.callno]->iseqno, fr.seqno, f.frametype, f.subclass); + if (iaxs[fr.callno]->iseqno > fr.seqno) { + /* If we've already seen it, ack it XXX There's a border condition here XXX */ + if ((f.frametype != AST_FRAME_IAX) || + ((f.subclass != AST_IAX_COMMAND_ACK) && (f.subclass != AST_IAX_COMMAND_INVAL))) { + if (option_debug) + ast_log(LOG_DEBUG, "Acking anyway\n"); + send_command(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_ACK, fr.ts, NULL, 0,fr.seqno); + } + } + pthread_mutex_unlock(&iaxs_lock); + return 1; + } + } else { + /* Increment unless it's an ACK */ + if ((f.subclass != AST_IAX_COMMAND_ACK) || + (f.frametype != AST_FRAME_IAX)) + iaxs[fr.callno]->iseqno++; + } + /* A full frame */ + if (res < sizeof(struct ast_iax_full_hdr)) { + ast_log(LOG_WARNING, "midget packet received (%d of %d min)\n", res, sizeof(struct ast_iax_full_hdr)); + pthread_mutex_unlock(&iaxs_lock); + return 1; + } + f.datalen = res - sizeof(struct ast_iax_full_hdr); + if (f.datalen) + f.data = buf + sizeof(struct ast_iax_full_hdr); + else + f.data = NULL; + fr.ts = ntohl(fh->ts); + /* Unless this is an ACK or INVAL frame, ack it */ + if ((f.frametype != AST_FRAME_IAX) || + ((f.subclass != AST_IAX_COMMAND_ACK) && (f.subclass != AST_IAX_COMMAND_INVAL))) + send_command(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_ACK, fr.ts, NULL, 0,fr.seqno); + if (f.frametype == AST_FRAME_VOICE) + iaxs[fr.callno]->voiceformat = f.subclass; + if (f.frametype == AST_FRAME_IAX) { + /* Handle the IAX pseudo frame itself */ + if (option_debug) + ast_log(LOG_DEBUG, "IAX subclass %d received\n", f.subclass); + switch(f.subclass) { + case AST_IAX_COMMAND_ACK: + /* Ack the packet with the given timestamp */ + pthread_mutex_lock(&iaxq.lock); + for (cur = iaxq.head; cur ; cur = cur->next) { + /* If it's our call, and our timestamp, mark -1 retries */ + if ((fr.callno == cur->callno) && (fr.seqno == cur->seqno)) + cur->retries = -1; + } + pthread_mutex_unlock(&iaxq.lock); + break; + case AST_IAX_COMMAND_NEW: + ((char *)f.data)[f.datalen] = '\0'; + if (check_access(fr.callno, &sin, f.data, f.datalen)) { + /* They're not allowed on */ + send_command(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_REJECT, 0, "No authority found", strlen("No authority found"), -1); + ast_log(LOG_NOTICE, "Rejected connect attempt from %s, request '%s'\n", inet_ntoa(sin.sin_addr), f.data); + /* XXX Not guaranteed to work, but probably does XXX */ + pthread_mutex_lock(&iaxq.lock); + send_packet(iaxq.tail); + pthread_mutex_unlock(&iaxq.lock); + iax_destroy(fr.callno); + break; + } + if (!strlen(iaxs[fr.callno]->secret)) { + /* No authentication required, let them in */ + send_command(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_ACCEPT, 0, NULL, 0, -1); + iaxs[fr.callno]->state |= IAX_STATE_STARTED; + if(!(c = ast_iax_new(iaxs[fr.callno], AST_STATE_RING))) + iax_destroy(fr.callno); + else + c->format = iaxs[fr.callno]->peerformats; + break; + } + authenticate_request(iaxs[fr.callno]); + iaxs[fr.callno]->state |= IAX_STATE_AUTHENTICATED; + break; + case AST_IAX_COMMAND_HANGUP: +#if 0 + iaxs[fr.callno]->error = ENOTCONN; +#endif + iax_destroy(fr.callno); + break; + case AST_IAX_COMMAND_REJECT: + ((char *)f.data)[f.datalen] = '\0'; + ast_log(LOG_WARNING, "Call rejected by %s: %s\n", inet_ntoa(iaxs[fr.callno]->addr.sin_addr), f.data); + iaxs[fr.callno]->error = EPERM; + iax_destroy(fr.callno); + break; + case AST_IAX_COMMAND_ACCEPT: + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Call accepted by %s\n", inet_ntoa(iaxs[fr.callno]->addr.sin_addr)); + iaxs[fr.callno]->state |= IAX_STATE_STARTED; + break; + case AST_IAX_COMMAND_PING: + /* Send back a pong packet with the original timestamp */ + send_command(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_PONG, fr.ts, NULL, 0, -1); + break; + case AST_IAX_COMMAND_PONG: + iaxs[fr.callno]->pingtime = calc_timestamp(iaxs[fr.callno], 0) - fr.ts; + break; + case AST_IAX_COMMAND_LAGRQ: + case AST_IAX_COMMAND_LAGRP: + /* A little strange -- We have to actually go through the motions of + delivering the packet. In the very last step, it will be properly + handled by do_deliver */ + snprintf(src, sizeof(src), "LAGRQ-IAX/%s/%d", inet_ntoa(sin.sin_addr),fr.callno); + f.src = src; + f.mallocd = 0; + f.offset = 0; + fr.f = &f; + f.timelen = 0; + schedule_delivery(iaxfrdup2(&fr, 0)); + break; + case AST_IAX_COMMAND_AUTHREQ: + ((char *)f.data)[f.datalen] = '\0'; + if (authenticate_reply(iaxs[fr.callno], &iaxs[fr.callno]->addr, (char *)f.data)) { + ast_log(LOG_WARNING, + "I don't know how to authenticate %s to %s\n", + f.data, inet_ntoa(iaxs[fr.callno]->addr.sin_addr)); + iax_destroy(fr.callno); + } + break; + case AST_IAX_COMMAND_AUTHREP: + ((char *)f.data)[f.datalen] = '\0'; + if (authenticate_verify(iaxs[fr.callno], (char *)f.data)) { + ast_log(LOG_NOTICE, "Host %s failed to authenticate as %s\n", inet_ntoa(iaxs[fr.callno]->addr.sin_addr), iaxs[fr.callno]->username); + send_command(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_REJECT, 0, "No authority found", strlen("No authority found"), -1); + /* XXX Not guaranteed to work, but probably does XXX */ + pthread_mutex_lock(&iaxq.lock); + send_packet(iaxq.tail); + pthread_mutex_unlock(&iaxq.lock); + iax_destroy(fr.callno); + break; + } + /* Authentication is fine, go ahead */ + send_command(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_ACCEPT, 0, NULL, 0, -1); + iaxs[fr.callno]->state |= IAX_STATE_STARTED; + if(!(c = ast_iax_new(iaxs[fr.callno], AST_STATE_RING))) + iax_destroy(fr.callno); + else + c->format = iaxs[fr.callno]->peerformats; + break; + case AST_IAX_COMMAND_INVAL: + iaxs[fr.callno]->error = ENOTCONN; + iax_destroy(fr.callno); + if (option_debug) + ast_log(LOG_DEBUG, "Destroying call %d\n", fr.callno); + break; + default: + ast_log(LOG_DEBUG, "Unknown IAX command %d on %d/%d\n", f.subclass, fr.callno, iaxs[fr.callno]->peercallno); + } + /* Don't actually pass these frames along */ + pthread_mutex_unlock(&iaxs_lock); + return 1; + } + } else { + /* A mini frame */ + f.frametype = AST_FRAME_VOICE; + if (iaxs[fr.callno]->voiceformat > 0) + f.subclass = iaxs[fr.callno]->voiceformat; + else { + ast_log(LOG_WARNING, "Received mini frame before first full voice frame\n "); + pthread_mutex_unlock(&iaxs_lock); + return 1; + } + f.datalen = res - sizeof(struct ast_iax_mini_hdr); + if (f.datalen < 0) { + ast_log(LOG_WARNING, "Datalen < 0?\n"); + pthread_mutex_unlock(&iaxs_lock); + return 1; + } + if (f.datalen) + f.data = buf + sizeof(struct ast_iax_mini_hdr); + else + f.data = NULL; + fr.ts = (iaxs[fr.callno]->last & 0xFFFF0000L) | ntohs(mh->ts); + } + /* Don't pass any packets until we're started */ + if (!(iaxs[fr.callno]->state & IAX_STATE_STARTED)) { + pthread_mutex_unlock(&iaxs_lock); + return 1; + } + /* Common things */ + snprintf(src, sizeof(src), "IAX/%s/%d", inet_ntoa(sin.sin_addr),fr.callno); + f.src = src; + f.mallocd = 0; + f.offset = 0; + fr.f = &f; + if (f.datalen && (f.frametype == AST_FRAME_VOICE)) + f.timelen = get_timelen(&f); + else + f.timelen = 0; + + /* If this is our most recent packet, use it as our basis for timestamping */ + if (iaxs[fr.callno]->last < fr.ts) { + iaxs[fr.callno]->last = fr.ts; + fr.outoforder = 0; + } else { + ast_log(LOG_DEBUG, "Received out of order packet... (type=%d, subclass %d, ts = %d, last = %d)\n", f.frametype, f.subclass, fr.ts, iaxs[fr.callno]->last); + fr.outoforder = -1; + } + schedule_delivery(iaxfrdup2(&fr, 0)); + /* Always run again */ + pthread_mutex_unlock(&iaxs_lock); + return 1; +} + +static void free_ha(struct iax_ha *ha) +{ + struct iax_ha *hal; + while(ha) { + hal = ha; + ha = ha->next; + free(hal); + } +} + +static void free_context(struct iax_context *con) +{ + struct iax_context *conl; + while(con) { + conl = con; + con = con->next; + free(conl); + } +} + +static struct ast_channel *iax_request(char *type, int format, void *data) +{ + int callno; + struct sockaddr_in sin; + char s[256]; + char *st; + struct ast_channel *c; + strncpy(s, (char *)data, sizeof(s)); + strtok(s, ":"); + strtok(s, "@"); + st = strtok(NULL, "@"); + if (!st) + st = s; + /* Populate our address from the given */ + if (create_addr(&sin, st)) { + ast_log(LOG_WARNING, "Unable to assign address\n"); + return NULL; + } + pthread_mutex_lock(&iaxs_lock); + callno = find_callno(-1, -1, &sin, NEW_FORCE); + if (callno < 0) { + ast_log(LOG_WARNING, "Unable to create call\n"); + return NULL; + } + c = ast_iax_new(iaxs[callno], AST_STATE_DOWN); + if (c) { + /* Choose a format we can live with */ + if (c->format & format) + c->format &= format; + else + c->format = ast_translator_best_choice(format, c->format); + } + pthread_mutex_unlock(&iaxs_lock); + return c; +} + +static void *network_thread(void *ignore) +{ + /* Our job is simple: Send queued messages, retrying if necessary. Read frames + from the network, and queue them for delivery to the channels */ + int res; + struct ast_iax_frame *f, *freeme; + /* Establish I/O callback for socket read */ + ast_io_add(io, netsocket, socket_read, AST_IO_IN, NULL); + pthread_mutex_lock(&iaxs_lock); + for(;;) { + /* Go through the queue, sending messages which have not yet been + sent, and scheduling retransmissions if appropriate */ + pthread_mutex_lock(&iaxq.lock); + f = iaxq.head; + while(f) { + freeme = NULL; + if (!f->sentyet) { + f->sentyet++; + /* Send a copy immediately */ + if (iaxs[f->callno]) { + send_packet(f); + } + if (f->retries < 0) { + /* This is not supposed to be retransmitted */ + if (f->prev) + f->prev->next = f->next; + else + iaxq.head = f->next; + if (f->next) + f->next->prev = f->prev; + else + iaxq.tail = f->prev; + iaxq.count--; + /* Free the frame */ + ast_frfree(f->f); + /* Free the iax frame */ + freeme = f; + } else { + /* We need reliable delivery. Schedule a retransmission */ + f->retries++; + ast_sched_add(sched, f->retrytime, attempt_transmit, f); + } + } + f = f->next; + if (freeme) + free(freeme); + } + pthread_mutex_unlock(&iaxq.lock); + pthread_mutex_unlock(&iaxs_lock); + res = ast_sched_wait(sched); + res = ast_io_wait(io, res); + pthread_mutex_lock(&iaxs_lock); + if (res >= 0) { + ast_sched_runq(sched); + } + } +} + +static int start_network_thread() +{ + return pthread_create(&netthreadid, NULL, network_thread, NULL); +} + +static struct iax_context *build_context(char *context) +{ + struct iax_context *con = malloc(sizeof(struct iax_context)); + if (con) { + strncpy(con->context, context, sizeof(con->context)); + con->next = NULL; + } + return con; +} + +static struct iax_ha *build_ha(char *sense, char *stuff) +{ + struct iax_ha *ha = malloc(sizeof(struct iax_ha)); + char *nm; + if (ha) { + strtok(stuff, "/"); + nm = strtok(NULL, "/"); + if (!nm) + nm = "255.255.255.255"; + if (!inet_aton(stuff, &ha->netaddr)) { + ast_log(LOG_WARNING, "%s not a valid IP\n", stuff); + free(ha); + return NULL; + } + if (!inet_aton(nm, &ha->netmask)) { + ast_log(LOG_WARNING, "%s not a valid netmask\n", nm); + free(ha); + return NULL; + } + ha->netaddr.s_addr &= ha->netmask.s_addr; + if (!strcasecmp(sense, "a")) { + ha->sense = IAX_SENSE_ALLOW; + } else { + ha->sense = IAX_SENSE_DENY; + } + ha->next = NULL; + } + return ha; +} + +static struct iax_peer *build_peer(char *name, struct ast_variable *v) +{ + struct iax_peer *peer; + int maskfound=0; + struct hostent *hp; + peer = malloc(sizeof(struct iax_peer)); + if (peer) { + memset(peer, 0, sizeof(struct iax_peer)); + strncpy(peer->name, name, sizeof(peer->name)); + peer->addr.sin_port = htons(AST_DEFAULT_IAX_PORTNO); + while(v) { + if (!strcasecmp(v->name, "secret")) + strncpy(peer->secret, v->value, sizeof(peer->secret)); + else if (!strcasecmp(v->name, "host")) { + hp = gethostbyname(v->value); + if (hp) { + memcpy(&peer->addr.sin_addr, hp->h_addr, sizeof(peer->addr.sin_addr)); + } else { + ast_log(LOG_WARNING, "Unable to lookup '%s'\n", v->value); + free(peer); + return NULL; + } + if (!maskfound) + inet_aton("255.255.255.255", &peer->mask); + } + else if (!strcasecmp(v->name, "mask")) { + maskfound++; + inet_aton(v->value, &peer->mask); + } else if (!strcasecmp(v->name, "port")) + peer->addr.sin_port = htons(atoi(v->value)); + else if (!strcasecmp(v->name, "username")) + strncpy(peer->username, v->value, sizeof(peer->username)); + v=v->next; + } + } + return peer; +} + +static struct iax_user *build_user(char *name, struct ast_variable *v) +{ + struct iax_user *user; + struct iax_context *con, *conl = NULL; + struct iax_ha *ha, *hal = NULL; + user = (struct iax_user *)malloc(sizeof(struct iax_user)); + if (user) { + memset(user, 0, sizeof(struct iax_user)); + strncpy(user->name, name, sizeof(user->name)); + while(v) { + if (!strcasecmp(v->name, "context")) { + con = build_context(v->value); + if (con) { + if (conl) + conl->next = con; + else + user->contexts = con; + conl = con; + } + } else if (!strcasecmp(v->name, "allow") || + !strcasecmp(v->name, "deny")) { + ha = build_ha(v->name, v->value); + if (ha) { + if (hal) + hal->next = ha; + else + user->ha = ha; + hal = ha; + } + } else if (!strcasecmp(v->name, "auth")) { + strncpy(user->methods, v->value, sizeof(user->methods)); + } else if (!strcasecmp(v->name, "secret")) { + strncpy(user->secret, v->value, sizeof(user->secret)); + } + v = v->next; + } + } + return user; +} + +int load_module() +{ + int res = 0; + struct ast_config *cfg; + struct ast_variable *v; + struct iax_user *user; + struct iax_peer *peer; + char *cat; + char *utype; + int format; + + struct sockaddr_in sin; + + sin.sin_family = AF_INET; + sin.sin_port = ntohs(AST_DEFAULT_IAX_PORTNO); + sin.sin_addr.s_addr = INADDR_ANY; + + io = io_context_create(); + sched = sched_context_create(); + + if (!io || !sched) { + ast_log(LOG_ERROR, "Out of memory\n"); + return -1; + } + + pthread_mutex_init(&iaxq.lock, NULL); + pthread_mutex_init(&userl.lock, NULL); + + ast_cli_register(&cli_show_users); + ast_cli_register(&cli_show_channels); + ast_cli_register(&cli_show_peers); + ast_cli_register(&cli_set_jitter); +#ifdef IAX_SIMULATOR + ast_cli_register(&delay_cli); + ast_cli_register(&deviation_cli); + ast_cli_register(&reliability_cli); + ast_cli_register(&sim_show_cli); +#endif + cfg = ast_load(config); + + if (!cfg) { + ast_log(LOG_ERROR, "Unable to load config %s\n", config); + return -1; + } + v = ast_variable_browse(cfg, "general"); + while(v) { + if (!strcasecmp(v->name, "port")) + sin.sin_port = ntohs(atoi(v->value)); + else if (!strcasecmp(v->name, "pingtime")) + ping_time = atoi(v->value); + else if (!strcasecmp(v->name, "maxjitterbuffer")) + maxjitterbuffer = atoi(v->value); + else if (!strcasecmp(v->name, "maxexcessbuffer")) + max_jitter_buffer = atoi(v->value); + else if (!strcasecmp(v->name, "lagrqtime")) + lagrq_time = atoi(v->value); + else if (!strcasecmp(v->name, "dropcount")) + iax_dropcount = atoi(v->value); + else if (!strcasecmp(v->name, "bindaddr")) + inet_aton(v->value, &sin.sin_addr); + else if (!strcasecmp(v->name, "jitterbuffer")) + use_jitterbuffer = ast_true(v->value); + else if (!strcasecmp(v->name, "bandwidth")) { + if (!strcasecmp(v->value, "low")) { + iax_capability = IAX_CAPABILITY_LOWBANDWIDTH; + } else if (!strcasecmp(v->value, "medium")) { + iax_capability = IAX_CAPABILITY_MEDBANDWIDTH; + } else if (!strcasecmp(v->value, "high")) { + iax_capability = IAX_CAPABILITY_FULLBANDWIDTH; + } else + ast_log(LOG_WARNING, "bandwidth must be either low, medium, or high\n"); + } else if (!strcasecmp(v->name, "allow")) { + format = ast_getformatbyname(v->value); + if (format < 1) + ast_log(LOG_WARNING, "Cannot allow unknown format '%s'\n", v->value); + else + iax_capability |= format; + } else if (!strcasecmp(v->name, "disallow")) { + format = ast_getformatbyname(v->value); + if (format < 1) + ast_log(LOG_WARNING, "Cannot disallow unknown format '%s'\n", v->value); + else + iax_capability &= ~format; + } + v = v->next; + } + cat = ast_category_browse(cfg, NULL); + while(cat) { + if (strcasecmp(cat, "general")) { + utype = ast_variable_retrieve(cfg, cat, "type"); + if (utype) { + if (!strcasecmp(utype, "user")) { + user = build_user(cat, ast_variable_browse(cfg, cat)); + if (user) { + pthread_mutex_lock(&userl.lock); + user->next = userl.users; + userl.users = user; + pthread_mutex_unlock(&userl.lock); + } + } else if (!strcasecmp(utype, "peer")) { + peer = build_peer(cat, ast_variable_browse(cfg, cat)); + if (peer) { + pthread_mutex_lock(&peerl.lock); + peer->next = peerl.peers; + peerl.peers = peer; + pthread_mutex_unlock(&peerl.lock); + } + } else { + ast_log(LOG_WARNING, "Unknown type '%s' for '%s' in %s\n", utype, cat, config); + } + } else + ast_log(LOG_WARNING, "Section '%s' lacks type\n", cat); + } + cat = ast_category_browse(cfg, cat); + } + ast_destroy(cfg); + if (ast_channel_register(type, tdesc, iax_capability, iax_request)) { + ast_log(LOG_ERROR, "Unable to register channel class %s\n", type); + unload_module(); + return -1; + } + + /* Make a UDP socket */ + netsocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + + if (netsocket < 0) { + ast_log(LOG_ERROR, "Unable to create network socket: %s\n", strerror(errno)); + return -1; + } + if (bind(netsocket, &sin, sizeof(sin))) { + ast_log(LOG_ERROR, "Unable to bind to %s port %d\n", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); + return -1; + } + + if (!res) { + res = start_network_thread(); + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "IAX Ready and Listening on %s port %d\n", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); + } else { + ast_log(LOG_ERROR, "Unable to start network thread\n"); + close(netsocket); + } + return res; +} + +char *description() +{ + return desc; +} + +int unload_module() +{ + struct iax_user *user, *userlast; + struct iax_peer *peer, *peerlast; + int x; + /* Cancel the network thread, close the net socket */ + pthread_cancel(netthreadid); + pthread_join(netthreadid, NULL); + close(netsocket); + for (x=0;x<AST_IAX_MAX_CALLS;x++) + if (iaxs[x]) + iax_destroy(x); + ast_cli_unregister(&cli_show_users); + ast_cli_unregister(&cli_show_channels); + ast_cli_unregister(&cli_show_peers); + ast_cli_unregister(&cli_set_jitter); +#ifdef IAX_SIMULATOR + ast_cli_unregister(&delay_cli); + ast_cli_unregister(&deviation_cli); + ast_cli_unregister(&reliability_cli); + ast_cli_unregister(&sim_show_cli); +#endif + for (user=userl.users;user;) { + free_ha(user->ha); + free_context(user->contexts); + userlast = user; + user=user->next; + free(userlast); + } + for (peer=peerl.peers;peer;) { + peerlast = peer; + peer=peer->next; + free(peerlast); + } + return 0; +} + +int usecount() +{ + int res; + pthread_mutex_lock(&usecnt_lock); + res = usecnt; + pthread_mutex_unlock(&usecnt_lock); + return res; +} + diff --git a/channels/chan_modem.c b/channels/chan_modem.c index 7c6fedbf2..bf4a89c60 100755 --- a/channels/chan_modem.c +++ b/channels/chan_modem.c @@ -419,7 +419,6 @@ struct ast_channel *ast_modem_new(struct ast_modem_pvt *i, int state) snprintf(tmp->name, sizeof(tmp->name), "Modem[%s]/%s", i->mc->name, i->dev + 5); tmp->type = type; tmp->fd = i->fd; - /* XXX Switching formats silently causes kernel panics XXX */ tmp->format = i->mc->formats; tmp->state = state; if (state == AST_STATE_RING) @@ -433,7 +432,7 @@ struct ast_channel *ast_modem_new(struct ast_modem_pvt *i, int state) tmp->pvt->write = modem_write; strncpy(tmp->context, i->context, sizeof(tmp->context)); if (strlen(i->cid)) - strncpy(tmp->callerid, i->cid, sizeof(tmp->callerid)); + tmp->callerid = strdup(i->cid); i->owner = tmp; pthread_mutex_lock(&usecnt_lock); usecnt++; @@ -443,6 +442,7 @@ struct ast_channel *ast_modem_new(struct ast_modem_pvt *i, int state) if (ast_pbx_start(tmp)) { ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmp->name); ast_hangup(tmp); + tmp = NULL; } } } else @@ -454,6 +454,12 @@ static void modem_mini_packet(struct ast_modem_pvt *i) { struct ast_frame *fr; fr = i->mc->read(i); + if (fr->frametype == AST_FRAME_CONTROL) { + if (fr->subclass == AST_CONTROL_RING) { + ast_modem_new(i, AST_STATE_RING); + restart_monitor(); + } + } } static void *do_monitor(void *data) @@ -701,10 +707,10 @@ int load_module() ast_verbose(VERBOSE_PREFIX_2 "Loading modem driver %s", driver); if (ast_load_resource(driver)) { - ast_log(LOG_ERROR, "Failed to laod driver %s\n", driver); + ast_log(LOG_ERROR, "Failed to load driver %s\n", driver); ast_destroy(cfg); - unload_module(); pthread_mutex_unlock(&iflock); + unload_module(); return -1; } } else if (!strcasecmp(v->name, "mode")) { @@ -742,8 +748,6 @@ int load_module() return 0; } - - int unload_module() { struct ast_modem_pvt *p, *pl; diff --git a/channels/chan_modem_aopen.c b/channels/chan_modem_aopen.c index ae2b7e41a..f344cb149 100755 --- a/channels/chan_modem_aopen.c +++ b/channels/chan_modem_aopen.c @@ -194,9 +194,13 @@ static struct ast_frame *aopen_handle_escape(struct ast_modem_pvt *p, char esc) ast_log(LOG_DEBUG, "Escaped character '%c'\n", esc); switch(esc) { + case 'R': /* Pseudo ring */ + p->fr.frametype = AST_FRAME_CONTROL; + p->fr.subclass = AST_CONTROL_RING; + return &p->fr; case 'X': /* Pseudo connect */ p->fr.frametype = AST_FRAME_CONTROL; - p->fr.subclass = AST_CONTROL_ANSWER; + p->fr.subclass = AST_CONTROL_RING; if (p->owner) p->owner->state = AST_STATE_UP; if (aopen_startrec(p)) @@ -255,11 +259,14 @@ static struct ast_frame *aopen_read(struct ast_modem_pvt *p) /* If we're in immediate mode, reply now */ if (p->mode == MODEM_MODE_IMMEDIATE) return aopen_handle_escape(p, 'X'); - } + } else if (!strcasecmp(result, "BUSY")) { /* Same as a busy signal */ return aopen_handle_escape(p, 'b'); - } + } else + if (!strcasecmp(result, "RING")) { + return aopen_handle_escape(p, 'R'); + } else if (!strcasecmp(result, "NO DIALTONE")) { /* There's no dialtone, so the line isn't working */ ast_log(LOG_WARNING, "Device '%s' lacking dialtone\n", p->dev); diff --git a/channels/chan_oss.c b/channels/chan_oss.c new file mode 100755 index 000000000..caf2403c1 --- /dev/null +++ b/channels/chan_oss.c @@ -0,0 +1,791 @@ +/* + * Asterisk -- A telephony toolkit for Linux. + * + * Use /dev/dsp as a channel, and the console to command it :). + * + * The full-duplex "simulation" is pretty weak. This is generally a + * VERY BADLY WRITTEN DRIVER so please don't use it as a model for + * writing a driver. + * + * Copyright (C) 1999, Mark Spencer + * + * Mark Spencer <markster@linux-support.net> + * + * This program is free software, distributed under the terms of + * the GNU General Public License + */ + +#include <asterisk/frame.h> +#include <asterisk/logger.h> +#include <asterisk/channel.h> +#include <asterisk/module.h> +#include <asterisk/channel_pvt.h> +#include <asterisk/options.h> +#include <asterisk/pbx.h> +#include <asterisk/config.h> +#include <asterisk/cli.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/ioctl.h> +#include <sys/time.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <linux/soundcard.h> + +/* Which device to use */ +#define DEV_DSP "/dev/dsp" + +/* Lets use 160 sample frames, just like GSM. */ +#define FRAME_SIZE 160 + +/* When you set the frame size, you have to come up with + the right buffer format as well. */ +/* 5 64-byte frames = one frame */ +#define BUFFER_FMT ((buffersize * 5) << 16) | (0x0006); + +/* Don't switch between read/write modes faster than every 300 ms */ +#define MIN_SWITCH_TIME 600 + +static struct timeval lasttime; + +static int usecnt; +static int needanswer = 0; +static int needhangup = 0; +static int silencesuppression = 0; +static int silencethreshold = 1000; + +static char digits[80] = ""; + +static pthread_mutex_t usecnt_lock = PTHREAD_MUTEX_INITIALIZER; + +static char *type = "Console"; +static char *desc = "OSS Console Channel Driver"; +static char *tdesc = "OSS Console Channel Driver"; +static char *config = "oss.conf"; + +static char context[AST_MAX_EXTENSION] = "default"; +static char exten[AST_MAX_EXTENSION] = "s"; + +/* Some pipes to prevent overflow */ +static int funnel[2]; +static pthread_mutex_t sound_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_t silly; + +static struct chan_oss_pvt { + /* We only have one OSS structure -- near sighted perhaps, but it + keeps this driver as simple as possible -- as it should be. */ + struct ast_channel *owner; + char exten[AST_MAX_EXTENSION]; + char context[AST_MAX_EXTENSION]; +} oss; + +static int time_has_passed() +{ + struct timeval tv; + int ms; + gettimeofday(&tv, NULL); + ms = (tv.tv_sec - lasttime.tv_sec) * 1000 + + (tv.tv_usec - lasttime.tv_usec) / 1000; + if (ms > MIN_SWITCH_TIME) + return -1; + return 0; +} + +/* Number of buffers... Each is FRAMESIZE/8 ms long. For example + with 160 sample frames, and a buffer size of 3, we have a 60ms buffer, + usually plenty. */ + + +#define MAX_BUFFER_SIZE 100 +static int buffersize = 3; + +static int full_duplex = 0; + +/* Are we reading or writing (simulated full duplex) */ +static int readmode = 1; + +/* File descriptor for sound device */ +static int sounddev = -1; + +static int autoanswer = 1; + +static int calc_loudness(short *frame) +{ + int sum = 0; + int x; + for (x=0;x<FRAME_SIZE;x++) { + if (frame[x] < 0) + sum -= frame[x]; + else + sum += frame[x]; + } + sum = sum/FRAME_SIZE; + return sum; +} + +static int silence_suppress(short *buf) +{ +#define SILBUF 3 + int loudness; + static int silentframes = 0; + static char silbuf[FRAME_SIZE * 2 * SILBUF]; + static int silbufcnt=0; + if (!silencesuppression) + return 0; + loudness = calc_loudness((short *)(buf)); + if (option_debug) + ast_log(LOG_DEBUG, "loudness is %d\n", loudness); + if (loudness < silencethreshold) { + silentframes++; + silbufcnt++; + /* Keep track of the last few bits of silence so we can play + them as lead-in when the time is right */ + if (silbufcnt >= SILBUF) { + /* Make way for more buffer */ + memmove(silbuf, silbuf + FRAME_SIZE * 2, FRAME_SIZE * 2 * (SILBUF - 1)); + silbufcnt--; + } + memcpy(silbuf + FRAME_SIZE * 2 * silbufcnt, buf, FRAME_SIZE * 2); + if (silentframes > 10) { + /* We've had plenty of silence, so compress it now */ + return 1; + } + } else { + silentframes=0; + /* Write any buffered silence we have, it may have something + important */ + if (silbufcnt) { + write(funnel[1], silbuf, silbufcnt * FRAME_SIZE); + silbufcnt = 0; + } + } + return 0; +} + +static void *silly_thread(void *ignore) +{ + char buf[FRAME_SIZE * 2]; + int pos=0; + int res=0; + /* Read from the sound device, and write to the pipe. */ + for (;;) { + /* Give the writer a better shot at the lock */ +#if 0 + usleep(1000); +#endif + pthread_testcancel(); + pthread_mutex_lock(&sound_lock); + res = read(sounddev, buf + pos, FRAME_SIZE * 2 - pos); + pthread_mutex_unlock(&sound_lock); + if (res > 0) { + pos += res; + if (pos == FRAME_SIZE * 2) { + if (needhangup || needanswer || strlen(digits) || + !silence_suppress((short *)buf)) { + res = write(funnel[1], buf, sizeof(buf)); + } + pos = 0; + } + } else { + close(funnel[1]); + break; + } + pthread_testcancel(); + } + return NULL; +} + +static int setformat(void) +{ + int fmt, desired, res, fd = sounddev; + static int warnedalready = 0; + static int warnedalready2 = 0; + pthread_mutex_lock(&sound_lock); + fmt = AFMT_S16_LE; + res = ioctl(fd, SNDCTL_DSP_SETFMT, &fmt); + if (res < 0) { + ast_log(LOG_WARNING, "Unable to set format to 16-bit signed\n"); + pthread_mutex_unlock(&sound_lock); + return -1; + } + res = ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0); + if (res >= 0) { + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Console is full duplex\n"); + full_duplex = -1; + } + fmt = 0; + res = ioctl(fd, SNDCTL_DSP_STEREO, &fmt); + if (res < 0) { + ast_log(LOG_WARNING, "Failed to set audio device to mono\n"); + pthread_mutex_unlock(&sound_lock); + return -1; + } + /* 8000 Hz desired */ + desired = 8000; + fmt = desired; + res = ioctl(fd, SNDCTL_DSP_SPEED, &fmt); + if (res < 0) { + ast_log(LOG_WARNING, "Failed to set audio device to mono\n"); + pthread_mutex_unlock(&sound_lock); + return -1; + } + if (fmt != desired) { + if (!warnedalready++) + ast_log(LOG_WARNING, "Requested %d Hz, got %d Hz -- sound may be choppy\n", desired, fmt); + } +#if 1 + fmt = BUFFER_FMT; + res = ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &fmt); + if (res < 0) { + if (!warnedalready2++) + ast_log(LOG_WARNING, "Unable to set fragment size -- sound may be choppy\n"); + } +#endif + pthread_mutex_unlock(&sound_lock); + return 0; +} + +static int soundcard_setoutput(int force) +{ + /* Make sure the soundcard is in output mode. */ + int fd = sounddev; + if (full_duplex || (!readmode && !force)) + return 0; + pthread_mutex_lock(&sound_lock); + readmode = 0; + if (force || time_has_passed()) { + ioctl(sounddev, SNDCTL_DSP_RESET); + /* Keep the same fd reserved by closing the sound device and copying stdin at the same + time. */ + /* dup2(0, sound); */ + close(sounddev); + fd = open(DEV_DSP, O_WRONLY); + if (fd < 0) { + ast_log(LOG_WARNING, "Unable to re-open DSP device: %s\n", strerror(errno)); + pthread_mutex_unlock(&sound_lock); + return -1; + } + /* dup2 will close the original and make fd be sound */ + if (dup2(fd, sounddev) < 0) { + ast_log(LOG_WARNING, "dup2() failed: %s\n", strerror(errno)); + pthread_mutex_unlock(&sound_lock); + return -1; + } + if (setformat()) { + pthread_mutex_unlock(&sound_lock); + return -1; + } + pthread_mutex_unlock(&sound_lock); + return 0; + } + pthread_mutex_unlock(&sound_lock); + return 1; +} + +static int soundcard_setinput(int force) +{ + int fd = sounddev; + if (full_duplex || (readmode && !force)) + return 0; + pthread_mutex_lock(&sound_lock); + readmode = -1; + if (force || time_has_passed()) { + ioctl(sounddev, SNDCTL_DSP_RESET); + close(sounddev); + /* dup2(0, sound); */ + fd = open(DEV_DSP, O_RDONLY); + if (fd < 0) { + ast_log(LOG_WARNING, "Unable to re-open DSP device: %s\n", strerror(errno)); + pthread_mutex_unlock(&sound_lock); + return -1; + } + /* dup2 will close the original and make fd be sound */ + if (dup2(fd, sounddev) < 0) { + ast_log(LOG_WARNING, "dup2() failed: %s\n", strerror(errno)); + pthread_mutex_unlock(&sound_lock); + return -1; + } + if (setformat()) { + pthread_mutex_unlock(&sound_lock); + return -1; + } + pthread_mutex_unlock(&sound_lock); + return 0; + } + pthread_mutex_unlock(&sound_lock); + return 1; +} + +static int soundcard_init() +{ + /* Assume it's full duplex for starters */ + int fd = open(DEV_DSP, O_RDWR); + if (fd < 0) { + ast_log(LOG_ERROR, "Unable to open %s: %s\n", DEV_DSP, strerror(errno)); + return fd; + } + gettimeofday(&lasttime, NULL); + sounddev = fd; + setformat(); + if (!full_duplex) + soundcard_setinput(1); + return sounddev; +} + +static int oss_digit(struct ast_channel *c, char digit) +{ + ast_verbose( " << Console Received digit %c >> \n", digit); + return 0; +} + +static int oss_call(struct ast_channel *c, char *dest, int timeout) +{ + ast_verbose( " << Call placed to '%s' on console >> \n", dest); + if (autoanswer) { + ast_verbose( " << Auto-answered >> \n" ); + needanswer = 1; + } else { + ast_verbose( " << Type 'answer' to answer, or use 'autoanswer' for future calls >> \n"); + } + return 0; +} + +static int oss_answer(struct ast_channel *c) +{ + ast_verbose( " << Console call has been answered >> \n"); + c->state = AST_STATE_UP; + return 0; +} + +static int oss_hangup(struct ast_channel *c) +{ + c->pvt->pvt = NULL; + oss.owner = NULL; + ast_verbose( " << Hangup on console >> \n"); + pthread_mutex_lock(&usecnt_lock); + usecnt--; + pthread_mutex_unlock(&usecnt_lock); + needhangup = 0; + needanswer = 0; + return 0; +} + +static int soundcard_writeframe(short *data) +{ + /* Write an exactly FRAME_SIZE sized of frame */ + static int bufcnt = 0; + static char buffer[FRAME_SIZE * 2 * MAX_BUFFER_SIZE * 5]; + struct audio_buf_info info; + int res; + int fd = sounddev; + static int warned=0; + pthread_mutex_lock(&sound_lock); + if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &info)) { + if (!warned) + ast_log(LOG_WARNING, "Error reading output space\n"); + bufcnt = buffersize; + warned++; + } + if ((info.fragments >= buffersize * 5) && (bufcnt == buffersize)) { + /* We've run out of stuff, buffer again */ + bufcnt = 0; + } + if (bufcnt == buffersize) { + /* Write sample immediately */ + res = write(fd, ((void *)data), FRAME_SIZE * 2); + } else { + /* Copy the data into our buffer */ + res = FRAME_SIZE * 2; + memcpy(buffer + (bufcnt * FRAME_SIZE * 2), data, FRAME_SIZE * 2); + bufcnt++; + if (bufcnt == buffersize) { + res = write(fd, ((void *)buffer), FRAME_SIZE * 2 * buffersize); + } + } + pthread_mutex_unlock(&sound_lock); + return res; +} + + +static int oss_write(struct ast_channel *chan, struct ast_frame *f) +{ + int res; + static char sizbuf[8000]; + static int sizpos = 0; + int len = sizpos; + int pos; + if (!full_duplex && (strlen(digits) || needhangup || needanswer)) { + /* If we're half duplex, we have to switch to read mode + to honor immediate needs if necessary */ + res = soundcard_setinput(1); + if (res < 0) { + ast_log(LOG_WARNING, "Unable to set device to input mode\n"); + return -1; + } + return 0; + } + res = soundcard_setoutput(0); + if (res < 0) { + ast_log(LOG_WARNING, "Unable to set output device\n"); + return -1; + } else if (res > 0) { + /* The device is still in read mode, and it's too soon to change it, + so just pretend we wrote it */ + return 0; + } + /* We have to digest the frame in 160-byte portions */ + if (f->datalen > sizeof(sizbuf) - sizpos) { + ast_log(LOG_WARNING, "Frame too large\n"); + return -1; + } + memcpy(sizbuf + sizpos, f->data, f->datalen); + len += f->datalen; + pos = 0; + while(len - pos > FRAME_SIZE * 2) { + soundcard_writeframe((short *)(sizbuf + pos)); + pos += FRAME_SIZE * 2; + } + if (len - pos) + memmove(sizbuf, sizbuf + pos, len - pos); + sizpos = len - pos; + return 0; +} + +static struct ast_frame *oss_read(struct ast_channel *chan) +{ + static struct ast_frame f; + static char buf[FRAME_SIZE * 2 + AST_FRIENDLY_OFFSET]; + static int readpos = 0; + int res; + +#if 0 + ast_log(LOG_DEBUG, "oss_read()\n"); +#endif + + f.frametype = AST_FRAME_NULL; + f.subclass = 0; + f.timelen = 0; + f.datalen = 0; + f.data = NULL; + f.offset = 0; + f.src = type; + f.mallocd = 0; + + if (needhangup) { + return NULL; + } + if (strlen(digits)) { + f.frametype = AST_FRAME_DTMF; + f.subclass = digits[0]; + for (res=0;res<strlen(digits);res++) + digits[res] = digits[res + 1]; + return &f; + } + + if (needanswer) { + needanswer = 0; + f.frametype = AST_FRAME_CONTROL; + f.subclass = AST_CONTROL_ANSWER; + chan->state = AST_STATE_UP; + return &f; + } + + res = soundcard_setinput(0); + if (res < 0) { + ast_log(LOG_WARNING, "Unable to set input mode\n"); + return NULL; + } + if (res > 0) { + /* Theoretically shouldn't happen, but anyway, return a NULL frame */ + return &f; + } + res = read(funnel[0], buf + AST_FRIENDLY_OFFSET + readpos, FRAME_SIZE * 2 - readpos); + if (res < 0) { + ast_log(LOG_WARNING, "Error reading from sound device: %s\n", strerror(errno)); + return NULL; + } + readpos += res; + + if (readpos == FRAME_SIZE * 2) { + /* A real frame */ + readpos = 0; + f.frametype = AST_FRAME_VOICE; + f.subclass = AST_FORMAT_SLINEAR; + f.timelen = FRAME_SIZE / 8; + f.datalen = FRAME_SIZE * 2; + f.data = buf + AST_FRIENDLY_OFFSET; + f.offset = AST_FRIENDLY_OFFSET; + f.src = type; + f.mallocd = 0; + } + return &f; +} + +static struct ast_channel *oss_new(struct chan_oss_pvt *p, int state) +{ + struct ast_channel *tmp; + tmp = ast_channel_alloc(); + if (tmp) { + snprintf(tmp->name, sizeof(tmp->name), "OSS/%s", DEV_DSP + 5); + tmp->type = type; + tmp->fd = funnel[0]; + tmp->format = AST_FORMAT_SLINEAR; + tmp->pvt->pvt = p; + tmp->pvt->send_digit = oss_digit; + tmp->pvt->hangup = oss_hangup; + tmp->pvt->answer = oss_answer; + tmp->pvt->read = oss_read; + tmp->pvt->write = oss_write; + if (strlen(p->context)) + strncpy(tmp->context, p->context, sizeof(tmp->context)); + if (strlen(p->exten)) + strncpy(tmp->exten, p->exten, sizeof(tmp->exten)); + p->owner = tmp; + tmp->state = state; + pthread_mutex_lock(&usecnt_lock); + usecnt++; + pthread_mutex_unlock(&usecnt_lock); + ast_update_use_count(); + 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); + tmp = NULL; + } + } + } + return tmp; +} + +static struct ast_channel *oss_request(char *type, int format, void *data) +{ + int oldformat = format; + format &= AST_FORMAT_SLINEAR; + if (!format) { + ast_log(LOG_NOTICE, "Asked to get a channel of format '%d'\n", oldformat); + return NULL; + } + if (oss.owner) { + ast_log(LOG_NOTICE, "Already have a call on the OSS channel\n"); + return NULL; + } + return oss_new(&oss, AST_STATE_DOWN); +} + +static int console_autoanswer(int fd, int argc, char *argv[]) +{ + if ((argc != 1) && (argc != 2)) + return RESULT_SHOWUSAGE; + if (argc == 1) { + ast_cli(fd, "Auto answer is %s.\n", autoanswer ? "on" : "off"); + return RESULT_SUCCESS; + } else { + if (!strcasecmp(argv[1], "on")) + autoanswer = -1; + else if (!strcasecmp(argv[1], "off")) + autoanswer = 0; + else + return RESULT_SHOWUSAGE; + } + return RESULT_SUCCESS; +} + +static char *autoanswer_complete(char *line, char *word, int pos, int state) +{ +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + switch(state) { + case 0: + if (strlen(word) && !strncasecmp(word, "on", MIN(strlen(word), 2))) + return strdup("on"); + case 1: + if (strlen(word) && !strncasecmp(word, "off", MIN(strlen(word), 3))) + return strdup("off"); + default: + return NULL; + } + return NULL; +} + +static char autoanswer_usage[] = +"Usage: autoanswer [on|off]\n" +" Enables or disables autoanswer feature. If used without\n" +" argument, displays the current on/off status of autoanswer.\n" +" The default value of autoanswer is in 'oss.conf'.\n"; + +static int console_answer(int fd, int argc, char *argv[]) +{ + if (argc != 1) + return RESULT_SHOWUSAGE; + if (!oss.owner) { + ast_cli(fd, "No one is calling us\n"); + return RESULT_FAILURE; + } + needanswer++; + return RESULT_SUCCESS; +} + +static char answer_usage[] = +"Usage: answer\n" +" Answers an incoming call on the console (OSS) channel.\n"; + +static int console_hangup(int fd, int argc, char *argv[]) +{ + if (argc != 1) + return RESULT_SHOWUSAGE; + if (!oss.owner) { + ast_cli(fd, "No call to hangup up\n"); + return RESULT_FAILURE; + } + needhangup++; + return RESULT_SUCCESS; +} + +static char hangup_usage[] = +"Usage: hangup\n" +" Hangs up any call currently placed on the console.\n"; + + +static int console_dial(int fd, int argc, char *argv[]) +{ + char tmp[256], *tmp2; + char *mye, *myc; + if ((argc != 1) && (argc != 2)) + return RESULT_SHOWUSAGE; + if (oss.owner) { + if (argc == 2) + strncat(digits, argv[1], sizeof(digits) - strlen(digits)); + else { + ast_cli(fd, "You're already in a call. You can use this only to dial digits until you hangup\n"); + return RESULT_FAILURE; + } + return RESULT_SUCCESS; + } + mye = exten; + myc = context; + if (argc == 2) { + strncpy(tmp, argv[1], sizeof(tmp)); + strtok(tmp, "@"); + tmp2 = strtok(NULL, "@"); + if (strlen(tmp)) + mye = tmp; + if (tmp2 && strlen(tmp2)) + myc = tmp2; + } + if (ast_exists_extension(NULL, myc, mye, 1)) { + strncpy(oss.exten, mye, sizeof(oss.exten)); + strncpy(oss.context, myc, sizeof(oss.context)); + oss_new(&oss, AST_STATE_UP); + } else + ast_cli(fd, "No such extension '%s' in context '%s'\n", mye, myc); + return RESULT_SUCCESS; +} + +static char dial_usage[] = +"Usage: dial [extension[@context]]\n" +" Dials a given extensison ("; + + +static struct ast_cli_entry myclis[] = { + { { "answer", NULL }, console_answer, "Answer an incoming console call", answer_usage }, + { { "hangup", NULL }, console_hangup, "Hangup a call on the console", hangup_usage }, + { { "dial", NULL }, console_dial, "Dial an extension on the console", dial_usage }, + { { "autoanswer", NULL }, console_autoanswer, "Sets/displays autoanswer", autoanswer_usage, autoanswer_complete } +}; + +int load_module() +{ + int res; + int x; + int flags; + struct ast_config *cfg = ast_load(config); + struct ast_variable *v; + res = pipe(funnel); + if (res) { + ast_log(LOG_ERROR, "Unable to create pipe\n"); + return -1; + } + /* We make the funnel so that writes to the funnel don't block... + Our "silly" thread can read to its heart content, preventing + recording overruns */ + flags = fcntl(funnel[1], F_GETFL); +#if 0 + fcntl(funnel[0], F_SETFL, flags | O_NONBLOCK); +#endif + fcntl(funnel[1], F_SETFL, flags | O_NONBLOCK); + res = soundcard_init(); + if (res < 0) { + close(funnel[1]); + close(funnel[0]); + return -1; + } + if (!full_duplex) + ast_log(LOG_WARNING, "XXX I don't work right with non-full duplex sound cards XXX\n"); + pthread_create(&silly, NULL, silly_thread, NULL); + res = ast_channel_register(type, tdesc, AST_FORMAT_SLINEAR, oss_request); + if (res < 0) { + ast_log(LOG_ERROR, "Unable to register channel class '%s'\n", type); + return -1; + } + for (x=0;x<sizeof(myclis)/sizeof(struct ast_cli_entry); x++) + ast_cli_register(myclis + x); + if (cfg) { + v = ast_variable_browse(cfg, "general"); + while(v) { + if (!strcasecmp(v->name, "autoanswer")) + autoanswer = ast_true(v->value); + else if (!strcasecmp(v->name, "silencesuppression")) + silencesuppression = ast_true(v->value); + else if (!strcasecmp(v->name, "silencethreshold")) + silencethreshold = atoi(v->value); + else if (!strcasecmp(v->name, "context")) + strncpy(context, v->value, sizeof(context)); + else if (!strcasecmp(v->name, "extension")) + strncpy(exten, v->value, sizeof(exten)); + v=v->next; + } + ast_destroy(cfg); + } + return 0; +} + + + +int unload_module() +{ + int x; + for (x=0;x<sizeof(myclis)/sizeof(struct ast_cli_entry); x++) + ast_cli_unregister(myclis + x); + close(sounddev); + if (funnel[0] > 0) { + close(funnel[0]); + close(funnel[1]); + } + if (silly) { + pthread_cancel(silly); + pthread_join(silly, NULL); + } + if (oss.owner) + ast_softhangup(oss.owner); + if (oss.owner) + return -1; + return 0; +} + +char *description() +{ + return desc; +} + +int usecount() +{ + int res; + pthread_mutex_lock(&usecnt_lock); + res = usecnt; + pthread_mutex_unlock(&usecnt_lock); + return res; +} diff --git a/channels/chan_ixj.c b/channels/chan_phone.c index c8cdb91dd..26826d233 100755 --- a/channels/chan_ixj.c +++ b/channels/chan_phone.c @@ -1,7 +1,7 @@ /* * Asterisk -- A telephony toolkit for Linux. * - * QuickNet Internet Phone Jack Channel + * Generic Linux Telephony Interface driver * * Copyright (C) 1999, Mark Spencer * @@ -29,27 +29,32 @@ #include <arpa/inet.h> #include <fcntl.h> #include <sys/ioctl.h> -#include <linux/if_packet.h> -#include <linux/if_ether.h> -#include "ixjuser.h" +#include <linux/telephony.h> +/* Still use some IXJ specific stuff */ +#include <linux/ixjuser.h> #include "DialTone.h" -#define IXJ_MAX_BUF 480 +#define phone_MAX_BUF 480 -static char *desc = "QuickNet Internet Phone Jack"; -static char *type = "PhoneJack"; -static char *tdesc = "QuickNet Internet Phone Jack"; -static char *config = "ixj.conf"; +static char *desc = "Linux Telephony API Support"; +static char *type = "Phone"; +static char *tdesc = "Standard Linux Telephony API Driver"; +static char *config = "phone.conf"; /* Default context for dialtone mode */ static char context[AST_MAX_EXTENSION] = "default"; -char *ignore_rcs_id_for_chan_ixj = ixjuser_h_rcsid; - static int usecnt =0; + +static int echocancel = AEC_OFF; + +static int silencesupression = 0; + +static int prefformat = AST_FORMAT_G723_1 | AST_FORMAT_SLINEAR; + static pthread_mutex_t usecnt_lock = PTHREAD_MUTEX_INITIALIZER; -/* Protect the interface list (of ixj_pvt's) */ +/* Protect the interface list (of phone_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 @@ -68,7 +73,7 @@ static int restart_monitor(void); #define MODE_DIALTONE 1 #define MODE_IMMEDIATE 2 -static struct ixj_pvt { +static struct phone_pvt { int fd; /* Raw file descriptor for this device */ struct ast_channel *owner; /* Channel we belong to, possibly NULL */ int mode; /* Is this in the */ @@ -76,20 +81,21 @@ static struct ixj_pvt { int lastinput; /* Last input format */ int ministate; /* Miniature state, for dialtone mode */ char dev[256]; /* Device name */ - struct ixj_pvt *next; /* Next channel in list */ + struct phone_pvt *next; /* Next channel in list */ struct ast_frame fr; /* Frame */ char offset[AST_FRIENDLY_OFFSET]; - char buf[IXJ_MAX_BUF]; /* Static buffer for reading frames */ + char buf[phone_MAX_BUF]; /* Static buffer for reading frames */ int obuflen; int dialtone; + int silencesupression; char context[AST_MAX_EXTENSION]; - char obuf[IXJ_MAX_BUF * 2]; + char obuf[phone_MAX_BUF * 2]; char ext[AST_MAX_EXTENSION]; } *iflist = NULL; -static int ixj_digit(struct ast_channel *ast, char digit) +static int phone_digit(struct ast_channel *ast, char digit) { - struct ixj_pvt *p; + struct phone_pvt *p; int outdigit; p = ast->pvt->pvt; switch(digit) { @@ -115,57 +121,60 @@ static int ixj_digit(struct ast_channel *ast, char digit) ast_log(LOG_WARNING, "Unknown digit '%c'\n", digit); return -1; } - ioctl(p->fd, IXJCTL_PLAY_TONE, digit); + ioctl(p->fd, PHONE_PLAY_TONE, digit); return 0; } -static int ixj_call(struct ast_channel *ast, char *dest, int timeout) +static int phone_call(struct ast_channel *ast, char *dest, int timeout) { - struct ixj_pvt *p; + struct phone_pvt *p; p = ast->pvt->pvt; if ((ast->state != AST_STATE_DOWN) && (ast->state != AST_STATE_RESERVED)) { - ast_log(LOG_WARNING, "ixj_call called on %s, neither down nor reserved\n", ast->name); + ast_log(LOG_WARNING, "phone_call called on %s, neither down nor reserved\n", ast->name); return -1; } /* When we call, it just works, really, there's no destination... Just ring the phone and wait for someone to answer */ if (option_debug) ast_log(LOG_DEBUG, "Ringing %s on %s (%d)\n", dest, ast->name, ast->fd); - ioctl(p->fd, IXJCTL_RING_START); + ioctl(p->fd, PHONE_RING_START); ast->state = AST_STATE_RINGING; return 0; } -static int ixj_hangup(struct ast_channel *ast) +static int phone_hangup(struct ast_channel *ast) { - struct ixj_pvt *p; + struct phone_pvt *p; p = ast->pvt->pvt; if (option_debug) - ast_log(LOG_DEBUG, "ixj_hangup(%s)\n", ast->name); + ast_log(LOG_DEBUG, "phone_hangup(%s)\n", ast->name); if (!ast->pvt->pvt) { ast_log(LOG_WARNING, "Asked to hangup channel not connected\n"); return 0; } /* XXX Is there anything we can do to really hang up except stop recording? */ ast->state = AST_STATE_DOWN; - if (ioctl(p->fd, IXJCTL_REC_STOP)) + if (ioctl(p->fd, PHONE_REC_STOP)) ast_log(LOG_WARNING, "Failed to stop recording\n"); - if (ioctl(p->fd, IXJCTL_PLAY_STOP)) + if (ioctl(p->fd, PHONE_PLAY_STOP)) ast_log(LOG_WARNING, "Failed to stop playing\n"); - if (ioctl(p->fd, IXJCTL_RING_STOP)) + if (ioctl(p->fd, PHONE_RING_STOP)) ast_log(LOG_WARNING, "Failed to stop ringing\n"); - if (ioctl(p->fd, IXJCTL_CPT_STOP)) + if (ioctl(p->fd, PHONE_CPT_STOP)) ast_log(LOG_WARNING, "Failed to stop sounds\n"); /* If they're off hook, give a busy signal */ - if (ioctl(p->fd, IXJCTL_HOOKSTATE)) - ioctl(p->fd, IXJCTL_BUSY); + if (ioctl(p->fd, PHONE_HOOKSTATE)) { + if (option_debug) + ast_log(LOG_DEBUG, "Got hunghup, giving busy signal\n"); + ioctl(p->fd, PHONE_BUSY); + } p->lastformat = -1; p->lastinput = -1; p->ministate = 0; p->obuflen = 0; p->dialtone = 0; memset(p->ext, 0, sizeof(p->ext)); - ((struct ixj_pvt *)(ast->pvt->pvt))->owner = NULL; + ((struct phone_pvt *)(ast->pvt->pvt))->owner = NULL; pthread_mutex_lock(&usecnt_lock); usecnt--; if (usecnt < 0) @@ -180,27 +189,27 @@ static int ixj_hangup(struct ast_channel *ast) return 0; } -static int ixj_setup(struct ast_channel *ast) +static int phone_setup(struct ast_channel *ast) { - struct ixj_pvt *p; + struct phone_pvt *p; p = ast->pvt->pvt; - ioctl(p->fd, IXJCTL_CPT_STOP); + ioctl(p->fd, PHONE_CPT_STOP); /* Nothing to answering really, just start recording */ if (ast->format & AST_FORMAT_G723_1) { /* Prefer g723 */ - ioctl(p->fd, IXJCTL_REC_STOP); + ioctl(p->fd, PHONE_REC_STOP); if (p->lastinput != AST_FORMAT_G723_1) { p->lastinput = AST_FORMAT_G723_1; - if (ioctl(p->fd, IXJCTL_REC_CODEC, G723_63)) { + if (ioctl(p->fd, PHONE_REC_CODEC, G723_63)) { ast_log(LOG_WARNING, "Failed to set codec to g723.1\n"); return -1; } } } else if (ast->format & AST_FORMAT_SLINEAR) { - ioctl(p->fd, IXJCTL_REC_STOP); + ioctl(p->fd, PHONE_REC_STOP); if (p->lastinput != AST_FORMAT_SLINEAR) { p->lastinput = AST_FORMAT_SLINEAR; - if (ioctl(p->fd, IXJCTL_REC_CODEC, LINEAR16)) { + if (ioctl(p->fd, PHONE_REC_CODEC, LINEAR16)) { ast_log(LOG_WARNING, "Failed to set codec to signed linear 16\n"); return -1; } @@ -209,24 +218,24 @@ static int ixj_setup(struct ast_channel *ast) ast_log(LOG_WARNING, "Can't do format %d\n", ast->format); return -1; } - if (ioctl(p->fd, IXJCTL_REC_START)) { + if (ioctl(p->fd, PHONE_REC_START)) { ast_log(LOG_WARNING, "Failed to start recording\n"); return -1; } return 0; } -static int ixj_answer(struct ast_channel *ast) +static int phone_answer(struct ast_channel *ast) { - ixj_setup(ast); + phone_setup(ast); if (option_debug) - ast_log(LOG_DEBUG, "ixj_answer(%s)\n", ast->name); + ast_log(LOG_DEBUG, "phone_answer(%s)\n", ast->name); ast->rings = 0; ast->state = AST_STATE_UP; return 0; } -static char ixj_2digit(char c) +static char phone_2digit(char c) { if (c == 12) return '#'; @@ -238,11 +247,11 @@ static char ixj_2digit(char c) return '?'; } -static struct ast_frame *ixj_read(struct ast_channel *ast) +static struct ast_frame *phone_read(struct ast_channel *ast) { int res; - IXJ_EXCEPTION ixje; - struct ixj_pvt *p = ast->pvt->pvt; + union telephony_exception phonee; + struct phone_pvt *p = ast->pvt->pvt; char digit; /* Some nice norms */ @@ -253,16 +262,16 @@ static struct ast_frame *ixj_read(struct ast_channel *ast) p->fr.offset = 0; p->fr.mallocd=0; - ixje.bytes = ioctl(p->fd, IXJCTL_EXCEPTION); - if (ixje.bits.dtmf_ready) { + phonee.bytes = ioctl(p->fd, PHONE_EXCEPTION); + if (phonee.bits.dtmf_ready) { /* We've got a digit -- Just handle this nicely and easily */ - digit = ioctl(p->fd, IXJCTL_GET_DTMF_ASCII); + digit = ioctl(p->fd, PHONE_GET_DTMF_ASCII); p->fr.subclass = digit; p->fr.frametype = AST_FRAME_DTMF; return &p->fr; } - if (ixje.bits.hookstate) { - res = ioctl(p->fd, IXJCTL_HOOKSTATE); + if (phonee.bits.hookstate) { + res = ioctl(p->fd, PHONE_HOOKSTATE); /* See if we've gone on hook, if so, notify by returning NULL */ if (!res) return NULL; @@ -271,23 +280,23 @@ static struct ast_frame *ixj_read(struct ast_channel *ast) /* They've picked up the phone */ p->fr.frametype = AST_FRAME_CONTROL; p->fr.subclass = AST_CONTROL_ANSWER; - ixj_setup(ast); + phone_setup(ast); ast->state = AST_STATE_UP; return &p->fr; } else - ast_log(LOG_WARNING, "Got off hook in weird state\n"); + ast_log(LOG_WARNING, "Got off hook in weird state %d\n", ast->state); } } #if 0 - if (ixje.bits.pstn_ring) + if (phonee.bits.pstn_ring) ast_verbose("Unit is ringing\n"); - if (ixje.bits.caller_id) { + if (phonee.bits.caller_id) { ast_verbose("We have caller ID: %s\n"); } #endif /* Try to read some data... */ CHECK_BLOCKING(ast); - res = read(p->fd, p->buf, IXJ_MAX_BUF); + res = read(p->fd, p->buf, phone_MAX_BUF); ast->blocking = 0; if (res < 0) { #if 0 @@ -302,6 +311,17 @@ static struct ast_frame *ixj_read(struct ast_channel *ast) return NULL; } p->fr.data = p->buf; + switch(p->buf[0] & 0x3) { + case '0': + case '1': + /* Normal */ + break; + case '2': + case '3': + /* VAD/CNG, only send two words */ + res = 4; + break; + } p->fr.datalen = res; p->fr.frametype = AST_FRAME_VOICE; p->fr.subclass = p->lastinput; @@ -309,7 +329,7 @@ static struct ast_frame *ixj_read(struct ast_channel *ast) return &p->fr; } -static int ixj_write_buf(struct ixj_pvt *p, char *buf, int len, int frlen) +static int phone_write_buf(struct phone_pvt *p, char *buf, int len, int frlen) { int res; /* Store as much of the buffer as we can, then write fixed frames */ @@ -342,14 +362,15 @@ static int ixj_write_buf(struct ixj_pvt *p, char *buf, int len, int frlen) return len; } -static int ixj_write(struct ast_channel *ast, struct ast_frame *frame) +static int phone_write(struct ast_channel *ast, struct ast_frame *frame) { - struct ixj_pvt *p = ast->pvt->pvt; + struct phone_pvt *p = ast->pvt->pvt; int res; int maxfr=0; char *pos; int sofar; int expected; + char tmpbuf[4]; /* 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); @@ -361,14 +382,25 @@ static int ixj_write(struct ast_channel *ast, struct ast_frame *frame) ast_frfree(frame); return -1; } + /* If we're not in up mode, go into up mode now */ + if (ast->state != AST_STATE_UP) { + ast->state = AST_STATE_UP; + phone_setup(ast); + } if (frame->subclass == AST_FORMAT_G723_1) { if (p->lastformat != AST_FORMAT_G723_1) { - ioctl(p->fd, IXJCTL_PLAY_STOP); - if (ioctl(p->fd, IXJCTL_PLAY_CODEC, G723_63)) { + ioctl(p->fd, PHONE_PLAY_STOP); + ioctl(p->fd, PHONE_REC_STOP); + if (ioctl(p->fd, PHONE_PLAY_CODEC, G723_63)) { + ast_log(LOG_WARNING, "Unable to set G723.1 mode\n"); + return -1; + } + if (ioctl(p->fd, PHONE_REC_CODEC, G723_63)) { ast_log(LOG_WARNING, "Unable to set G723.1 mode\n"); return -1; } p->lastformat = AST_FORMAT_G723_1; + p->lastinput = AST_FORMAT_G723_1; /* Reset output buffer */ p->obuflen = 0; } @@ -379,18 +411,28 @@ static int ixj_write(struct ast_channel *ast, struct ast_frame *frame) maxfr = 24; } else if (frame->subclass == AST_FORMAT_SLINEAR) { if (p->lastformat != AST_FORMAT_SLINEAR) { - ioctl(p->fd, IXJCTL_PLAY_STOP); - if (ioctl(p->fd, IXJCTL_PLAY_CODEC, LINEAR16)) { + ioctl(p->fd, PHONE_PLAY_STOP); + ioctl(p->fd, PHONE_REC_STOP); + if (ioctl(p->fd, PHONE_PLAY_CODEC, LINEAR16)) { + ast_log(LOG_WARNING, "Unable to set 16-bit linear mode\n"); + return -1; + } + if (ioctl(p->fd, PHONE_REC_CODEC, LINEAR16)) { ast_log(LOG_WARNING, "Unable to set 16-bit linear mode\n"); return -1; } p->lastformat = AST_FORMAT_SLINEAR; + p->lastinput = AST_FORMAT_SLINEAR; /* Reset output buffer */ p->obuflen = 0; } maxfr = 480; } - if (ioctl(p->fd, IXJCTL_PLAY_START)) { + if (ioctl(p->fd, PHONE_PLAY_START)) { + ast_log(LOG_WARNING, "Failed to start playback\n"); + return -1; + } + if (ioctl(p->fd, PHONE_REC_START)) { ast_log(LOG_WARNING, "Failed to start recording\n"); return -1; } @@ -402,45 +444,54 @@ static int ixj_write(struct ast_channel *ast, struct ast_frame *frame) expected = frame->datalen - sofar; if (maxfr < expected) expected = maxfr; - /* XXX Internet Phone Jack does not handle the 4-byte VAD frame properly! XXX */ - if (frame->datalen != 4) { - res = ixj_write_buf(p, pos, expected, maxfr); - if (res != expected) { - if (res < 0) - ast_log(LOG_WARNING, "Write returned error (%s)\n", strerror(errno)); - else - ast_log(LOG_WARNING, "Only wrote %d of %d bytes\n", res, frame->datalen); - return -1; + /* XXX Internet Phone Jack does not handle the 4-byte VAD frame properly! XXX + we have to pad it to 24 bytes still. */ + if (frame->datalen == 4) { + if (p->silencesupression) { + memset(tmpbuf + 4, 0, sizeof(tmpbuf) - 4); + memcpy(tmpbuf, frame->data, 4); + expected = 24; + res = phone_write_buf(p, tmpbuf, expected, maxfr); } - sofar += res; - pos += res; - } else - sofar += 4; + res = 4; + expected=4; + } else { + res = phone_write_buf(p, pos, expected, maxfr); + } + if (res != expected) { + if (res < 0) + ast_log(LOG_WARNING, "Write returned error (%s)\n", strerror(errno)); + else + ast_log(LOG_WARNING, "Only wrote %d of %d bytes\n", res, frame->datalen); + return -1; + } + sofar += res; + pos += res; } return 0; } -static struct ast_channel *ixj_new(struct ixj_pvt *i, int state) +static struct ast_channel *phone_new(struct phone_pvt *i, int state, char *context) { struct ast_channel *tmp; tmp = ast_channel_alloc(); if (tmp) { - snprintf(tmp->name, sizeof(tmp->name), "PhoneJack/%s", i->dev + 5); + snprintf(tmp->name, sizeof(tmp->name), "Phone/%s", i->dev + 5); tmp->type = type; tmp->fd = i->fd; /* XXX Switching formats silently causes kernel panics XXX */ - tmp->format = AST_FORMAT_G723_1 /* | AST_FORMAT_SLINEAR */; + tmp->format = prefformat; tmp->state = state; if (state == AST_STATE_RING) tmp->rings = 1; tmp->pvt->pvt = i; - tmp->pvt->send_digit = ixj_digit; - tmp->pvt->call = ixj_call; - tmp->pvt->hangup = ixj_hangup; - tmp->pvt->answer = ixj_answer; - tmp->pvt->read = ixj_read; - tmp->pvt->write = ixj_write; - strncpy(tmp->context, i->context, sizeof(tmp->context)); + tmp->pvt->send_digit = phone_digit; + tmp->pvt->call = phone_call; + tmp->pvt->hangup = phone_hangup; + tmp->pvt->answer = phone_answer; + tmp->pvt->read = phone_read; + tmp->pvt->write = phone_write; + strncpy(tmp->context, context, sizeof(tmp->context)); if (strlen(i->ext)) strncpy(tmp->exten, i->ext, sizeof(tmp->exten)); i->owner = tmp; @@ -449,8 +500,9 @@ static struct ast_channel *ixj_new(struct ixj_pvt *i, int state) pthread_mutex_unlock(&usecnt_lock); ast_update_use_count(); if (state != AST_STATE_DOWN) { - if (state == AST_STATE_RING) - ioctl(tmp->fd, IXJCTL_RINGBACK); + if (state == AST_STATE_RING) { + ioctl(tmp->fd, PHONE_RINGBACK); + } if (ast_pbx_start(tmp)) { ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmp->name); ast_hangup(tmp); @@ -461,7 +513,7 @@ static struct ast_channel *ixj_new(struct ixj_pvt *i, int state) return tmp; } -static void ixj_mini_packet(struct ixj_pvt *i) +static void phone_mini_packet(struct phone_pvt *i) { int res; char buf[1024]; @@ -473,75 +525,77 @@ static void ixj_mini_packet(struct ixj_pvt *i) } } -static void ixj_check_exception(struct ixj_pvt *i) +static void phone_check_exception(struct phone_pvt *i) { int offhook=0; char digit[2] = {0 , 0}; - IXJ_EXCEPTION ixje; + union telephony_exception phonee; /* XXX Do something XXX */ #if 0 ast_log(LOG_DEBUG, "Exception!\n"); #endif - ixje.bytes = ioctl(i->fd, IXJCTL_EXCEPTION); - if (ixje.bits.dtmf_ready) { - digit[0] = ioctl(i->fd, IXJCTL_GET_DTMF_ASCII); + phonee.bytes = ioctl(i->fd, PHONE_EXCEPTION); + if (phonee.bits.dtmf_ready) { + digit[0] = ioctl(i->fd, PHONE_GET_DTMF_ASCII); if (i->mode == MODE_DIALTONE) { - ioctl(i->fd, IXJCTL_PLAY_STOP); - ioctl(i->fd, IXJCTL_REC_STOP); - ioctl(i->fd, IXJCTL_CPT_STOP); + ioctl(i->fd, PHONE_PLAY_STOP); + ioctl(i->fd, PHONE_REC_STOP); + ioctl(i->fd, PHONE_CPT_STOP); i->dialtone = 0; if (strlen(i->ext) < AST_MAX_EXTENSION - 1) strcat(i->ext, digit); if (ast_exists_extension(NULL, i->context, i->ext, 1)) { /* It's a valid extension in its context, get moving! */ - ixj_new(i, AST_STATE_UP); + phone_new(i, AST_STATE_RING, i->context); /* No need to restart monitor, we are the monitor */ if (i->owner) { pthread_mutex_lock(&usecnt_lock); usecnt--; pthread_mutex_unlock(&usecnt_lock); ast_update_use_count(); - ixj_setup(i->owner); } - } else if (ast_exists_extension(NULL, "default", i->ext, 1)) { - /* Check the default, too... */ - /* XXX This should probably be justified better XXX */ - strncpy(i->context, "default", sizeof(i->context)); - ixj_new(i, AST_STATE_UP); - if (i->owner) { - pthread_mutex_lock(&usecnt_lock); - usecnt--; - pthread_mutex_unlock(&usecnt_lock); - ast_update_use_count(); - ixj_setup(i->owner); + } else if (!ast_canmatch_extension(NULL, i->context, i->ext, 1)) { + /* There is nothing in the specified extension that can match anymore. + Try the default */ + if (ast_exists_extension(NULL, "default", i->ext, 1)) { + /* Check the default, too... */ + phone_new(i, AST_STATE_RING, "default"); + if (i->owner) { + pthread_mutex_lock(&usecnt_lock); + usecnt--; + pthread_mutex_unlock(&usecnt_lock); + ast_update_use_count(); + } + /* XXX This should probably be justified better XXX */ + } else if (!ast_canmatch_extension(NULL, "default", i->ext, 1)) { + /* It's not a valid extension, give a busy signal */ + if (option_debug) + ast_log(LOG_DEBUG, "%s can't match anything in %s or default\n", i->ext, i->context); + ioctl(i->fd, PHONE_BUSY); } - } else if ((strlen(i->ext) >= ast_pbx_longest_extension(i->context)) && - (strlen(i->ext) >= ast_pbx_longest_extension("default"))) { - if (option_debug) - ast_log(LOG_DEBUG, "%s is too long\n", i->ext); - /* It's not a valid extension, give a busy signal */ - ioctl(i->fd, IXJCTL_BUSY); } #if 0 ast_verbose("Extension is %s\n", i->ext); #endif } } - if (ixje.bits.hookstate) { - offhook = ioctl(i->fd, IXJCTL_HOOKSTATE); + if (phonee.bits.hookstate) { + offhook = ioctl(i->fd, PHONE_HOOKSTATE); if (offhook) { if (i->mode == MODE_IMMEDIATE) { - ixj_new(i, AST_STATE_RING); + phone_new(i, AST_STATE_RING, i->context); } else if (i->mode == MODE_DIALTONE) { pthread_mutex_lock(&usecnt_lock); usecnt++; pthread_mutex_unlock(&usecnt_lock); ast_update_use_count(); + /* Reset the extension */ + i->ext[0] = '\0'; /* Play the dialtone */ i->dialtone++; - ioctl(i->fd, IXJCTL_PLAY_STOP); - ioctl(i->fd, IXJCTL_PLAY_CODEC, ULAW); - ioctl(i->fd, IXJCTL_PLAY_START); + ioctl(i->fd, PHONE_PLAY_STOP); + ioctl(i->fd, PHONE_PLAY_CODEC, ULAW); + ioctl(i->fd, PHONE_PLAY_START); } } else { if (i->dialtone) { @@ -551,15 +605,15 @@ static void ixj_check_exception(struct ixj_pvt *i) ast_update_use_count(); } memset(i->ext, 0, sizeof(i->ext)); - ioctl(i->fd, IXJCTL_CPT_STOP); - ioctl(i->fd, IXJCTL_PLAY_STOP); - ioctl(i->fd, IXJCTL_REC_STOP); + ioctl(i->fd, PHONE_CPT_STOP); + ioctl(i->fd, PHONE_PLAY_STOP); + ioctl(i->fd, PHONE_REC_STOP); i->dialtone = 0; } } - if (ixje.bits.pstn_ring) + if (phonee.bits.pstn_ring) ast_verbose("Unit is ringing\n"); - if (ixje.bits.caller_id) + if (phonee.bits.caller_id) ast_verbose("We have caller ID\n"); @@ -569,7 +623,7 @@ static void *do_monitor(void *data) { fd_set rfds, efds; int n, res; - struct ixj_pvt *i; + struct phone_pvt *i; int tonepos = 0; /* The tone we're playing this round */ struct timeval tv = {0,0}; @@ -595,7 +649,7 @@ static void *do_monitor(void *data) return NULL; } /* Build the stuff we're going to select on, that is the socket of every - ixj_pvt that does not have an associated owner channel */ + phone_pvt that does not have an associated owner channel */ n = -1; FD_ZERO(&rfds); FD_ZERO(&efds); @@ -668,14 +722,14 @@ static void *do_monitor(void *data) ast_log(LOG_WARNING, "Whoa.... I'm owned but found (%d, %s)...\n", i->fd, i->dev); continue; } - ixj_mini_packet(i); + phone_mini_packet(i); } if (FD_ISSET(i->fd, &efds)) { if (i->owner) { ast_log(LOG_WARNING, "Whoa.... I'm owned but found (%d, %s)...\n", i->fd, i->dev); continue; } - ixj_check_exception(i); + phone_check_exception(i); } i=i->next; } @@ -716,15 +770,15 @@ static int restart_monitor() return 0; } -static struct ixj_pvt *mkif(char *iface, int mode) +static struct phone_pvt *mkif(char *iface, int mode) { - /* Make a ixj_pvt structure for this interface */ - struct ixj_pvt *tmp; + /* Make a phone_pvt structure for this interface */ + struct phone_pvt *tmp; #if 0 int flags; #endif - tmp = malloc(sizeof(struct ixj_pvt)); + tmp = malloc(sizeof(struct phone_pvt)); if (tmp) { tmp->fd = open(iface, O_RDWR); if (tmp->fd < 0) { @@ -732,10 +786,16 @@ static struct ixj_pvt *mkif(char *iface, int mode) free(tmp); return NULL; } - ioctl(tmp->fd, IXJCTL_PLAY_STOP); - ioctl(tmp->fd, IXJCTL_REC_STOP); - ioctl(tmp->fd, IXJCTL_RING_STOP); - ioctl(tmp->fd, IXJCTL_CPT_STOP); + ioctl(tmp->fd, PHONE_PLAY_STOP); + ioctl(tmp->fd, PHONE_REC_STOP); + ioctl(tmp->fd, PHONE_RING_STOP); + ioctl(tmp->fd, PHONE_CPT_STOP); + ioctl(tmp->fd, PHONE_REC_DEPTH, 4); + if (echocancel != AEC_OFF) + ioctl(tmp->fd, IXJCTL_AEC_START, echocancel); + if (silencesupression) + tmp->silencesupression = 1; + ioctl(tmp->fd, PHONE_VAD, tmp->silencesupression); tmp->mode = mode; #if 0 flags = fcntl(tmp->fd, F_GETFL); @@ -755,10 +815,10 @@ static struct ixj_pvt *mkif(char *iface, int mode) return tmp; } -static struct ast_channel *ixj_request(char *type, int format, void *data) +static struct ast_channel *phone_request(char *type, int format, void *data) { int oldformat; - struct ixj_pvt *p; + struct phone_pvt *p; struct ast_channel *tmp = NULL; char *name = data; @@ -777,7 +837,7 @@ static struct ast_channel *ixj_request(char *type, int format, void *data) while(p) { if (!strcmp(name, p->dev + 5)) { if (!p->owner) { - tmp = ixj_new(p, AST_STATE_DOWN); + tmp = phone_new(p, AST_STATE_DOWN, p->context); break; } } @@ -792,7 +852,7 @@ int load_module() { struct ast_config *cfg; struct ast_variable *v; - struct ixj_pvt *tmp; + struct phone_pvt *tmp; int mode = MODE_IMMEDIATE; cfg = ast_load(config); @@ -822,6 +882,8 @@ int load_module() unload_module(); return -1; } + } else if (!strcasecmp(v->name, "silencesupression")) { + silencesupression = ast_true(v->value); } else if (!strcasecmp(v->name, "mode")) { if (!strncasecmp(v->value, "di", 2)) mode = MODE_DIALTONE; @@ -831,12 +893,30 @@ int load_module() ast_log(LOG_WARNING, "Unknown mode: %s\n", v->value); } else if (!strcasecmp(v->name, "context")) { strncpy(context, v->value, sizeof(context)); + } else if (!strcasecmp(v->name, "format")) { + if (!strcasecmp(v->value, "g723.1")) { + prefformat = AST_FORMAT_G723_1; + } else if (!strcasecmp(v->value, "slinear")) { + prefformat = AST_FORMAT_SLINEAR; + } else + ast_log(LOG_WARNING, "Unknown format '%s'\n", v->value); + } else if (!strcasecmp(v->name, "echocancel")) { + if (!strcasecmp(v->value, "off")) { + echocancel = AEC_OFF; + } else if (!strcasecmp(v->value, "low")) { + echocancel = AEC_LOW; + } else if (!strcasecmp(v->value, "medium")) { + echocancel = AEC_MED; + } else if (!strcasecmp(v->value, "high")) { + echocancel = AEC_HIGH; + } else + ast_log(LOG_WARNING, "Unknown echo cancellation '%s'\n", v->value); } v = v->next; } pthread_mutex_unlock(&iflock); - /* Make sure we can register our Adtranixj channel type */ - if (ast_channel_register(type, tdesc, AST_FORMAT_G723_1, ixj_request)) { + /* Make sure we can register our Adtranphone channel type */ + if (ast_channel_register(type, tdesc, AST_FORMAT_G723_1, phone_request)) { ast_log(LOG_ERROR, "Unable to register channel class %s\n", type); ast_destroy(cfg); unload_module(); @@ -852,7 +932,7 @@ int load_module() int unload_module() { - struct ixj_pvt *p, *pl; + struct phone_pvt *p, *pl; /* First, take us out of the channel loop */ ast_channel_unregister(type); if (!pthread_mutex_lock(&iflock)) { diff --git a/channels/ixjuser.h b/channels/ixjuser.h index 2de8abd1a..188485ca1 100755 --- a/channels/ixjuser.h +++ b/channels/ixjuser.h @@ -1,8 +1,8 @@ /****************************************************************************** $Id$ $Log$ -Revision 1.15 1999/12/01 05:25:58 markster -Version 0.3.0 from FTP +Revision 1.3 1999/12/01 05:25:58 markster +Version 0.1.2 from FTP Revision 1.1 1999/12/01 05:25:58 markster Start on the Internet Phone Jack channel diff --git a/codecs/Makefile b/codecs/Makefile index 0e015cf92..baec412a5 100755 --- a/codecs/Makefile +++ b/codecs/Makefile @@ -26,8 +26,9 @@ LIBG723=g723.1/libg723.a LIBG723B=g723.1b/libg723b.a LIBGSM=gsm/lib/libgsm.a LIBMP3=mp3/libmp3.a +LIBLPC10=lpc10/liblpc10.a -CODECS+=$(MODG723) codec_gsm.so codec_mp3_d.so +CODECS+=$(MODG723) codec_gsm.so codec_mp3_d.so codec_lpc10.so all: $(CODECS) @@ -37,6 +38,7 @@ clean: ! [ -d g723.1b ] || make -C g723.1b clean make -C gsm clean make -C mp3 clean + make -C lpc10 clean $(LIBG723): make -C g723.1 all @@ -50,11 +52,14 @@ $(LIBG723B): $(LIBMP3): make -C mp3 all +$(LIBLPC10): + make -C lpc10 all + codec_g723_1.so : codec_g723_1.o $(LIBG723) $(CC) -shared -Xlinker -x -o $@ $< $(LIBG723) codec_g723_1b.o : codec_g723_1.c - $(CC) -c -o $@ $(CFLAGS) -DANNEX_B $< + $(CC) -c -o $@ $(CFLAGS) -DANNEX_B -Dsingle $< codec_g723_1b.so : codec_g723_1b.o $(LIBG723B) $(CC) -shared -Xlinker -x -o $@ $< $(LIBG723B) -lm @@ -62,6 +67,9 @@ codec_g723_1b.so : codec_g723_1b.o $(LIBG723B) codec_gsm.so: codec_gsm.o $(LIBGSM) $(CC) -shared -Xlinker -x -o $@ $< $(LIBGSM) +codec_lpc10.so: codec_lpc10.o $(LIBLPC10) + $(CC) -shared -Xlinker -x -o $@ $< $(LIBLPC10) -lm + codec_mp3_d.so: codec_mp3_d.o $(LIBMP3) $(CC) -shared -Xlinker -x -o $@ $< $(LIBMP3) diff --git a/codecs/codec_lpc10.c b/codecs/codec_lpc10.c new file mode 100755 index 000000000..6316241dd --- /dev/null +++ b/codecs/codec_lpc10.c @@ -0,0 +1,348 @@ +/* + * Asterisk -- A telephony toolkit for Linux. + * + * Translate between signed linear and LPC10 (Linear Predictor Code) + * + * The lpc10 code is from a library used by nautilus, modified to be a bit + * nicer to the compiler. + * + * See http://www.arl.wustl.edu/~jaf/ + * + * Copyright (C) 1999, Mark Spencer + * + * Mark Spencer <markster@linux-support.net> + * + * This program is free software, distributed under the terms of + * the GNU General Public License + */ + + +#include <asterisk/translate.h> +#include <asterisk/module.h> +#include <asterisk/logger.h> +#include <pthread.h> +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> +#include <netinet/in.h> +#include <string.h> +#include <stdio.h> + +#include "lpc10/lpc10.h" + +/* Sample frame data */ +#include "slin_lpc10_ex.h" +#include "lpc10_slin_ex.h" + +/* We use a very strange format here... I have no idea why... The frames are 180 + samples long, which isn't even an even number of milliseconds... Not only that + but we hvae to waste two bits of each frame to keep them ending on a byte boundary + because the frames are 54 bits long */ + +#define LPC10_BYTES_IN_COMPRESSED_FRAME (LPC10_BITS_IN_COMPRESSED_FRAME + 7)/8 + +static pthread_mutex_t localuser_lock = PTHREAD_MUTEX_INITIALIZER; +static int localusecnt=0; + +static char *tdesc = "LPC10 2.4kbps (signed linear) Voice Coder"; + +struct ast_translator_pvt { + union { + struct lpc10_encoder_state *enc; + struct lpc10_decoder_state *dec; + } lpc10; + struct ast_frame f; + /* Space to build offset */ + char offset[AST_FRIENDLY_OFFSET]; + /* Buffer for our outgoing frame */ + short outbuf[LPC10_SAMPLES_PER_FRAME]; + /* Enough to store a full second */ + short buf[8000]; + int tail; + int longer; +}; + +#define lpc10_coder_pvt ast_translator_pvt + +static struct ast_translator_pvt *lpc10_enc_new() +{ + struct lpc10_coder_pvt *tmp; + tmp = malloc(sizeof(struct lpc10_coder_pvt)); + if (tmp) { + if (!(tmp->lpc10.enc = create_lpc10_encoder_state())) { + free(tmp); + tmp = NULL; + } + tmp->tail = 0; + tmp->longer = 0; + localusecnt++; + } + return tmp; +} + +static struct ast_translator_pvt *lpc10_dec_new() +{ + struct lpc10_coder_pvt *tmp; + tmp = malloc(sizeof(struct lpc10_coder_pvt)); + if (tmp) { + if (!(tmp->lpc10.dec = create_lpc10_decoder_state())) { + free(tmp); + tmp = NULL; + } + tmp->tail = 0; + tmp->longer = 0; + localusecnt++; + } + return tmp; +} +static struct ast_frame *lintolpc10_sample() +{ + static struct ast_frame f; + static int longer = 0; + f.frametype = AST_FRAME_VOICE; + f.subclass = AST_FORMAT_SLINEAR; + f.datalen = sizeof(slin_lpc10_ex); + /* Assume 8000 Hz */ + f.timelen = LPC10_SAMPLES_PER_FRAME/8; + f.timelen += longer; + longer = 1- longer; + f.mallocd = 0; + f.offset = 0; + f.src = __PRETTY_FUNCTION__; + f.data = slin_lpc10_ex; + return &f; +} + +static struct ast_frame *lpc10tolin_sample() +{ + static struct ast_frame f; + f.frametype = AST_FRAME_VOICE; + f.subclass = AST_FORMAT_LPC10; + f.datalen = sizeof(lpc10_slin_ex); + /* All frames are 22 ms long (maybe a little more -- why did he choose + LPC10_SAMPLES_PER_FRAME sample frames anyway?? */ + f.timelen = LPC10_SAMPLES_PER_FRAME/8; + f.mallocd = 0; + f.offset = 0; + f.src = __PRETTY_FUNCTION__; + f.data = lpc10_slin_ex; + return &f; +} + +static struct ast_frame *lpc10tolin_frameout(struct ast_translator_pvt *tmp) +{ + if (!tmp->tail) + return NULL; + /* Signed linear is no particular frame size, so just send whatever + we have in the buffer in one lump sum */ + tmp->f.frametype = AST_FRAME_VOICE; + tmp->f.subclass = AST_FORMAT_SLINEAR; + tmp->f.datalen = tmp->tail * 2; + /* Assume 8000 Hz */ + tmp->f.timelen = tmp->tail / 8; + tmp->f.mallocd = 0; + tmp->f.offset = AST_FRIENDLY_OFFSET; + tmp->f.src = __PRETTY_FUNCTION__; + tmp->f.data = tmp->buf; + /* Reset tail pointer */ + tmp->tail = 0; + +#if 0 + /* Save a sample frame */ + { static int samplefr = 0; + if (samplefr == 80) { + int fd; + fd = open("lpc10.example", O_WRONLY | O_CREAT, 0644); + write(fd, tmp->f.data, tmp->f.datalen); + close(fd); + } + samplefr++; + } +#endif + return &tmp->f; +} + +static void extract_bits(INT32 *bits, unsigned char *c) +{ + int x; + for (x=0;x<LPC10_BITS_IN_COMPRESSED_FRAME;x++) { + if (*c & (0x80 >> (x & 7))) + bits[x] = 1; + else + bits[x] = 0; + if ((x & 7) == 7) + c++; + } +} + +static void build_bits(unsigned char *c, INT32 *bits) +{ + unsigned char mask=0x80; + int x; + *c = 0; + for (x=0;x<LPC10_BITS_IN_COMPRESSED_FRAME;x++) { + if (bits[x]) + *c |= mask; + mask = mask >> 1; + if ((x % 8)==7) { + c++; + *c = 0; + mask = 0x80; + } + } +} + +static int lpc10tolin_framein(struct ast_translator_pvt *tmp, struct ast_frame *f) +{ + /* Assuming there's space left, decode into the current buffer at + the tail location */ + int x; + float tmpbuf[LPC10_SAMPLES_PER_FRAME]; + short *sd; + INT32 bits[LPC10_BITS_IN_COMPRESSED_FRAME]; + if (tmp->tail + LPC10_SAMPLES_PER_FRAME < sizeof(tmp->buf)/2) { + sd = tmp->buf + tmp->tail; + extract_bits(bits, f->data); + if (lpc10_decode(bits, tmpbuf, tmp->lpc10.dec)) { + ast_log(LOG_WARNING, "Invalid lpc10 data\n"); + return -1; + } + for (x=0;x<LPC10_SAMPLES_PER_FRAME;x++) { + /* Convert to a real between -1.0 and 1.0 */ + sd[x] = 32768.0 * tmpbuf[x]; + } + + tmp->tail+=LPC10_SAMPLES_PER_FRAME; + } else { + ast_log(LOG_WARNING, "Out of buffer space\n"); + return -1; + } + return 0; +} + +static int lintolpc10_framein(struct ast_translator_pvt *tmp, struct ast_frame *f) +{ + /* Just add the frames to our stream */ + /* XXX We should look at how old the rest of our stream is, and if it + is too old, then we should overwrite it entirely, otherwise we can + get artifacts of earlier talk that do not belong */ + if (tmp->tail + f->datalen < sizeof(tmp->buf) / 2) { + memcpy((tmp->buf + tmp->tail), f->data, f->datalen); + tmp->tail += f->datalen/2; + } else { + ast_log(LOG_WARNING, "Out of buffer space\n"); + return -1; + } + return 0; +} + +static struct ast_frame *lintolpc10_frameout(struct ast_translator_pvt *tmp) +{ + int x; + float tmpbuf[LPC10_SAMPLES_PER_FRAME]; + INT32 bits[LPC10_BITS_IN_COMPRESSED_FRAME]; + /* We can't work on anything less than a frame in size */ + if (tmp->tail < LPC10_SAMPLES_PER_FRAME) + return NULL; + /* Encode a frame of data */ + for (x=0;x<LPC10_SAMPLES_PER_FRAME;x++) { + tmpbuf[x] = (float)tmp->buf[x] / 32768.0; + } + lpc10_encode(tmpbuf, bits, tmp->lpc10.enc); + build_bits((unsigned char *)tmp->outbuf, bits); + tmp->f.frametype = AST_FRAME_VOICE; + tmp->f.subclass = AST_FORMAT_LPC10; + tmp->f.datalen = LPC10_BYTES_IN_COMPRESSED_FRAME; + tmp->f.timelen = 22; + /* We alternate between 22 and 23 ms to simulate 22.5 ms */ + tmp->f.timelen += tmp->longer; + /* Use one of the two left over bits to record if this is a 22 or 23 ms frame... + important for IAX use */ + tmp->longer = 1 - tmp->longer; + tmp->f.mallocd = 0; + tmp->f.offset = AST_FRIENDLY_OFFSET; + tmp->f.src = __PRETTY_FUNCTION__; + tmp->f.data = tmp->outbuf; + ((char *)(tmp->f.data))[LPC10_BYTES_IN_COMPRESSED_FRAME - 1] |= tmp->longer; + tmp->tail -= LPC10_SAMPLES_PER_FRAME; + /* Move the data at the end of the buffer to the front */ + if (tmp->tail) + memmove(tmp->buf, tmp->buf + LPC10_SAMPLES_PER_FRAME, tmp->tail * 2); +#if 0 + /* Save a sample frame */ + { static int samplefr = 0; + if (samplefr == 0) { + int fd; + fd = open("lpc10.example", O_WRONLY | O_CREAT, 0644); + write(fd, tmp->f.data, tmp->f.datalen); + close(fd); + } + samplefr++; + } +#endif + return &tmp->f; +} + +static void lpc10_destroy(struct ast_translator_pvt *pvt) +{ + /* Enc and DEC are both just allocated, so they can be freed */ + free(pvt->lpc10.enc); + free(pvt); + localusecnt--; +} + +static struct ast_translator lpc10tolin = + { "lpc10tolin", + AST_FORMAT_LPC10, AST_FORMAT_SLINEAR, + lpc10_dec_new, + lpc10tolin_framein, + lpc10tolin_frameout, + lpc10_destroy, + lpc10tolin_sample + }; + +static struct ast_translator lintolpc10 = + { "lintolpc10", + AST_FORMAT_SLINEAR, AST_FORMAT_LPC10, + lpc10_enc_new, + lintolpc10_framein, + lintolpc10_frameout, + lpc10_destroy, + lintolpc10_sample + }; + +int unload_module(void) +{ + int res; + pthread_mutex_lock(&localuser_lock); + res = ast_unregister_translator(&lintolpc10); + if (!res) + res = ast_unregister_translator(&lpc10tolin); + if (localusecnt) + res = -1; + pthread_mutex_unlock(&localuser_lock); + return res; +} + +int load_module(void) +{ + int res; + res=ast_register_translator(&lpc10tolin); + if (!res) + res=ast_register_translator(&lintolpc10); + else + ast_unregister_translator(&lpc10tolin); + return res; +} + +char *description(void) +{ + return tdesc; +} + +int usecount(void) +{ + int res; + STANDARD_USECOUNT(res); + return res; +} diff --git a/codecs/lpc10/lpc10.h b/codecs/lpc10/lpc10.h index 5804fc57b..4194ac64e 100755 --- a/codecs/lpc10/lpc10.h +++ b/codecs/lpc10/lpc10.h @@ -1,8 +1,8 @@ /* $Log$ -Revision 1.13 2000/01/05 00:20:06 markster -Version 0.3.0 from FTP +Revision 1.1 2000/01/05 00:20:06 markster +Version 0.1.2 from FTP Revision 1.1 2000/01/05 00:20:06 markster Add broken lpc10 code... It's not too far from working I don't think... diff --git a/codecs/lpc10_slin_ex.h b/codecs/lpc10_slin_ex.h new file mode 100755 index 000000000..802c3f785 --- /dev/null +++ b/codecs/lpc10_slin_ex.h @@ -0,0 +1,13 @@ +/* + * 8-bit raw data + * + * Source: example.lpc10 + * + * Copyright (C) 1999, Mark Spencer and Linux Support Services + * + * Distributed under the terms of the GNU General Public License + * + */ + +static unsigned char lpc10_slin_ex[] = { +0x1, 0x8, 0x31, 0x8, 0x31, 0x80, 0x30 }; diff --git a/codecs/slin_lpc10_ex.h b/codecs/slin_lpc10_ex.h new file mode 100755 index 000000000..d738ae41e --- /dev/null +++ b/codecs/slin_lpc10_ex.h @@ -0,0 +1,21 @@ +/* + * Signed 16-bit audio data + * + * Source: example.slin + * + * Copyright (C) 1999, Mark Spencer and Linux Support Services + * + * Distributed under the terms of the GNU General Public License + * + */ + +static signed short slin_lpc10_ex[] = { +000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, +000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, +000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, +000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, +000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, +000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, +000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, +000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, +000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000, 000000 }; @@ -70,6 +70,8 @@ void ast_destroy(struct ast_config *ast) int ast_true(char *s) { + if (!s) + return 0; /* Determine if this is a true value */ if (!strcasecmp(s, "yes") || !strcasecmp(s, "true") || diff --git a/configs/iax.conf.sample b/configs/iax.conf.sample new file mode 100755 index 000000000..62307d5b9 --- /dev/null +++ b/configs/iax.conf.sample @@ -0,0 +1,72 @@ +; +; Inter-Asterisk eXchange driver definition +; +; +; General settings, like port number to bind to, and +; an option address (the default is to bind to all +; local addresses). +; +[general] +port=5036 +;bindaddr=192.168.0.1 +; +; Specify bandwidth of low, medium, or high to control which codecs are used +; in general. +; +bandwidth=low +; +; You can also fine tune codecs here using "allow" and "disallow" clauses +; with specific codecs. Use "all" to represent all formats. +; +;allow=all ; same as bandwidth=high +;disallow=g723.1 ; Hm... Proprietary, don't use it... +disallow=lpc10 ; Icky sound quality... Mr. Roboto. +;allow=gsm ; Always allow GSM, it's cool :) +; +; You can also adjust several parameters relating to the jitter +; buffer. Specifically, you can provide a maximum jitter buffer, +; you can turn it off entirely, and you can specify an acceptable +; drop rate (per MEMORY_SIZE, by default 3 of 100). Disabling the +; jitter buffer is not recommended. Finally, you can specify the maximum +; excess jitter buffer, which if exceeded, causes the jitter buffer to +; slowly shrink in order to improve latency. +; +;jitterbuffer=no +;dropcount=3 +;maxjitterbuffer=500 +;maxexccessbuffer=100 + +; +; Guest sections for unauthenticated connection attempts. Just +; specify an empty secret, or provide no secret section. +; +[guest] +type=user +context=default +; +; Further user sections may be added, specifying a context and a +; secret used for connections with that given authentication name. +; Limited IP based access control is allowed by use of "allow" and +; "deny" keywords. Multiple rules are permitted. Multiple permitted +; contexts may be specified, in which case the first will be the default. +; +;[markster] +;type=user +;context=default +;context=local +;auth=md5,cleartext +;secret=markpasswd +;deny=0.0.0.0/0.0.0.0 +;allow=209.16.236.73/255.255.255.0 +; +; Peers may also be specified, with a secret and +; a remote hostname. +; +[demo] +type=peer +username=asterisk +secret=supersecret +host=209.16.236.91 +;host=asterisk.linux-support.net +;port=5036 +;mask=255.255.255.255 diff --git a/configs/ixj.conf.sample b/configs/ixj.conf.sample deleted file mode 100755 index ed6be96c1..000000000 --- a/configs/ixj.conf.sample +++ /dev/null @@ -1,19 +0,0 @@ -; -; Internet Phone Jack -; -; Configuration file -; -[interfaces] -; -; Select a mode, either the line jack provides dialtone, reads digits, -; then starts PBX with the given extension (dialtone mode), or -; immediately provides the PBX without reading any digits or providing -; any dialtone (this is the immediate mode, the default) -; -;mode=immediate -mode=dialtone -; -; List all devices we can use. -; -context=local -device=/dev/ixj0 diff --git a/configs/oss.conf.sample b/configs/oss.conf.sample new file mode 100755 index 000000000..138a7376b --- /dev/null +++ b/configs/oss.conf.sample @@ -0,0 +1,23 @@ +; +; Open Sound System Console Driver Configuration File +; +[general] +; +; Automatically answer incoming calls on the console? Choose yes if +; for example you want to use this as an intercom. +; +autoanswer=yes +; +; Default context (is overridden with @context syntax) +; +;context=local +; +; Default extension to call +; +extension=s +; +; Silence supression can be enabled when sound is over a certain threshold. +; The value for the threshold should probably be between 500 and 2000 or so, +; but your mileage may vary. Use the echo test to evaluate the best setting. +;silencesuppression = yes +;silencethreshold = 1000 diff --git a/configs/phone.conf.sample b/configs/phone.conf.sample new file mode 100755 index 000000000..72c5393ab --- /dev/null +++ b/configs/phone.conf.sample @@ -0,0 +1,35 @@ +; +; Linux Telephony Interface +; +; Configuration file +; +[interfaces] +; +; Select a mode, either the line jack provides dialtone, reads digits, +; then starts PBX with the given extension (dialtone mode), or +; immediately provides the PBX without reading any digits or providing +; any dialtone (this is the immediate mode, the default) +; +;mode=immediate +mode=dialtone +; +; You can decide which format to use by default, "g723.1" or "slinear". +; XXX Be careful, sometimes the card causes kernel panics when running +; in signed linear mode for some reason... XXX +; +;format=slinear +format=g723.1 +; +; And set the echo cancellation to "off", "low", "medium", and "high". +; This is not supported on all phones. +; +echocancel=medium +; +; You can optionally use VAD/CNG silence supression +; +;silencesupression=yes +; +; List all devices we can use. Contexts may also be specified +; +;context=local +;device=/dev/ixj0 @@ -13,7 +13,9 @@ #include <asterisk/frame.h> #include <asterisk/logger.h> +#include <asterisk/options.h> #include <stdlib.h> +#include <unistd.h> #include <string.h> /* @@ -74,16 +76,106 @@ struct ast_frame *ast_frisolate(struct ast_frame *fr) } else out->src = fr->src; if (!(fr->mallocd & AST_MALLOCD_DATA)) { - out->data = malloc(fr->datalen + fr->offset); - out->data += fr->offset; - out->offset = fr->offset; - out->datalen = fr->datalen; - memcpy(out->data, fr->data, fr->datalen); + out->data = malloc(fr->datalen + AST_FRIENDLY_OFFSET); if (!out->data) { + free(out); ast_log(LOG_WARNING, "Out of memory\n"); return NULL; } + out->data += AST_FRIENDLY_OFFSET; + out->offset = AST_FRIENDLY_OFFSET; + out->datalen = fr->datalen; + memcpy(out->data, fr->data, fr->datalen); } out->mallocd = AST_MALLOCD_HDR | AST_MALLOCD_SRC | AST_MALLOCD_DATA; return out; } + +struct ast_frame *ast_frdup(struct ast_frame *f) +{ + struct ast_frame *ret; + int p; + p = f->mallocd; + f->mallocd = 0; + /* Make frisolate think this is a 100% static frame, and make a duplicate */ + ret = ast_frisolate(f); + /* Restore its true malloc status */ + f->mallocd = p; + return ret; +} + +struct ast_frame *ast_fr_fdread(int fd) +{ + char buf[4096]; + int res; + struct ast_frame *f = (struct ast_frame *)buf; + /* Read a frame directly from there. They're always in the + right format. */ + + if (read(fd, buf, sizeof(struct ast_frame)) + == sizeof(struct ast_frame)) { + /* read the frame header */ + f->mallocd = 0; + /* Re-write data position */ + f->data = buf + sizeof(struct ast_frame); + f->offset = 0; + /* Forget about being mallocd */ + f->mallocd = 0; + /* Re-write the source */ + f->src = __FUNCTION__; + if (f->datalen > sizeof(buf) - sizeof(struct ast_frame)) { + /* Really bad read */ + ast_log(LOG_WARNING, "Strange read (%d bytes)\n", f->datalen); + return NULL; + } + if (f->datalen) { + if ((res = read(fd, f->data, f->datalen)) != f->datalen) { + /* Bad read */ + ast_log(LOG_WARNING, "How very strange, expected %d, got %d\n", f->datalen, res); + return NULL; + } + } + return ast_frisolate(f); + } else if (option_debug) + ast_log(LOG_DEBUG, "NULL or invalid header\n"); + /* Null if there was an error */ + return NULL; +} + +/* Some convenient routines for sending frames to/from stream or datagram + sockets, pipes, etc (maybe even files) */ + +int ast_fr_fdwrite(int fd, struct ast_frame *frame) +{ + /* Write the frame exactly */ + if (write(fd, frame, sizeof(struct ast_frame)) != sizeof(struct ast_frame)) { + ast_log(LOG_WARNING, "Write error\n"); + return -1; + } + if (write(fd, frame->data, frame->datalen) != frame->datalen) { + ast_log(LOG_WARNING, "Write error\n"); + return -1; + } + return 0; +} + +int ast_getformatbyname(char *name) +{ + if (!strcasecmp(name, "g723.1")) + return AST_FORMAT_G723_1; + else if (!strcasecmp(name, "gsm")) + return AST_FORMAT_GSM; + else if (!strcasecmp(name, "ulaw")) + return AST_FORMAT_ULAW; + else if (!strcasecmp(name, "alaw")) + return AST_FORMAT_ALAW; + else if (!strcasecmp(name, "mp3")) + return AST_FORMAT_MP3; + else if (!strcasecmp(name, "slinear")) + return AST_FORMAT_SLINEAR; + else if (!strcasecmp(name, "lpc10")) + return AST_FORMAT_LPC10; + else if (!strcasecmp(name, "all")) + return 0x7FFFFFFF; + return 0; +} @@ -82,6 +82,8 @@ struct ast_app { struct ast_app *next; }; +static int pbx_builtin_prefix(struct ast_channel *, void *); +static int pbx_builtin_stripmsd(struct ast_channel *, void *); static int pbx_builtin_answer(struct ast_channel *, void *); static int pbx_builtin_goto(struct ast_channel *, void *); static int pbx_builtin_hangup(struct ast_channel *, void *); @@ -104,6 +106,8 @@ static struct pbx_builtin { { "ResponseTimeout", pbx_builtin_rtimeout }, { "BackGround", pbx_builtin_background }, { "Wait", pbx_builtin_wait }, + { "StripMSD", pbx_builtin_stripmsd }, + { "Prefix", pbx_builtin_prefix }, }; /* Lock for the application list */ @@ -155,6 +159,7 @@ static int pbx_exec(struct ast_channel *c, /* Channel */ #define HELPER_EXISTS 0 #define HELPER_SPAWN 1 #define HELPER_EXEC 2 +#define HELPER_CANMATCH 3 static struct ast_app *pbx_findapp(char *app) { @@ -213,6 +218,42 @@ static int extension_match(char *pattern, char *data) return match; } +static int extension_close(char *pattern, char *data) +{ + int match; + /* If "data" is longer, it can'be a subset of pattern */ + if (strlen(pattern) < strlen(data)) + return 0; + + if (!strncasecmp(pattern, data, strlen(data))) { + return 1; + } + /* All patterns begin with _ */ + if (pattern[0] != '_') + return 0; + /* Start optimistic */ + match=1; + pattern++; + while(match && *data && *pattern) { + switch(toupper(*pattern)) { + case 'N': + if ((*data < '2') || (*data > '9')) + match=0; + break; + case 'X': + if ((*data < '0') || (*data > '9')) + match = 0; + break; + default: + if (*data != *pattern) + match =0; + } + data++; + pattern++; + } + return match; +} + static int pbx_extension_helper(struct ast_channel *c, char *context, char *exten, int priority, int action) { struct ast_context *tmp; @@ -222,7 +263,7 @@ static int pbx_extension_helper(struct ast_channel *c, char *context, char *exte int res; if (pthread_mutex_lock(&conlock)) { ast_log(LOG_WARNING, "Unable to obtain lock\n"); - if (action == HELPER_EXISTS) + if ((action == HELPER_EXISTS) || (action == HELPER_CANMATCH)) return 0; else return -1; @@ -230,27 +271,32 @@ static int pbx_extension_helper(struct ast_channel *c, char *context, char *exte tmp = contexts; while(tmp) { if (!strcasecmp(tmp->name, context)) { +#if 0 /* By locking tmp, not only can the state of its entries not change, but it cannot be destroyed either. */ pthread_mutex_lock(&tmp->lock); - /* But we can relieve the conlock, as tmp will not change */ - pthread_mutex_unlock(&conlock); +#endif e = tmp->root; while(e) { - if (extension_match(e->exten, exten)) { + if (extension_match(e->exten, exten) || + ((action == HELPER_CANMATCH) && extension_close(e->exten, exten))) { while(e) { if (e->priority == priority) { - pthread_mutex_unlock(&tmp->lock); /* We have a winner! Maybe there are some races in here though. XXX */ switch(action) { + case HELPER_CANMATCH: + pthread_mutex_unlock(&conlock); + return -1; case HELPER_EXISTS: + pthread_mutex_unlock(&conlock); return -1; case HELPER_SPAWN: newstack++; /* Fall through */ case HELPER_EXEC: app = pbx_findapp(e->app); + pthread_mutex_unlock(&conlock); if (app) { strncpy(c->context, context, sizeof(c->context)); strncpy(c->exten, exten, sizeof(c->exten)); @@ -265,6 +311,7 @@ static int pbx_extension_helper(struct ast_channel *c, char *context, char *exte res = pbx_exec(c, app->execute, e->data, newstack); c->appl = NULL; c->data = NULL; + pthread_mutex_unlock(&conlock); return res; } else { ast_log(LOG_WARNING, "No application '%s' for extension (%s, %s, %d)\n", e->app, context, exten, priority); @@ -277,20 +324,25 @@ static int pbx_extension_helper(struct ast_channel *c, char *context, char *exte e = e->peer; } pthread_mutex_unlock(&tmp->lock); - if (action != HELPER_EXISTS) { + if ((action != HELPER_EXISTS) && (action != HELPER_CANMATCH)) { ast_log(LOG_WARNING, "No such priority '%d' in '%s' in '%s'\n", priority, exten, context); + pthread_mutex_unlock(&conlock); return -1; - } else + } else { + pthread_mutex_unlock(&conlock); return 0; + } } e = e->next; } - pthread_mutex_unlock(&tmp->lock); - if (action != HELPER_EXISTS) { + if ((action != HELPER_EXISTS) && (action != HELPER_CANMATCH)) { + pthread_mutex_unlock(&conlock); ast_log(LOG_WARNING, "No such extension '%s' in '%s'\n", exten, context); return -1; - } else + } else { + pthread_mutex_unlock(&conlock); return 0; + } } tmp = tmp->next; } @@ -338,6 +390,11 @@ int ast_exists_extension(struct ast_channel *c, char *context, char *exten, int return pbx_extension_helper(c, context, exten, priority, HELPER_EXISTS); } +int ast_canmatch_extension(struct ast_channel *c, char *context, char *exten, int priority) +{ + return pbx_extension_helper(c, context, exten, priority, HELPER_CANMATCH); +} + int ast_spawn_extension(struct ast_channel *c, char *context, char *exten, int priority) { return pbx_extension_helper(c, context, exten, priority, HELPER_SPAWN); @@ -382,14 +439,13 @@ static void *pbx_thread(void *data) goto out; } /* If we're playing something in the background, wait for it to finish or for a digit */ - if (c->stream) { + if (c->stream || (c->trans && c->trans->stream)) { digit = ast_waitstream(c, AST_DIGIT_ANY); ast_stopstream(c); /* Hang up if something goes wrong */ if (digit < 0) goto out; else if (digit) { - ast_stopstream(c); exten[pos++] = digit; break; } @@ -402,8 +458,8 @@ static void *pbx_thread(void *data) waittime = c->pbx->dtimeout; else waittime = c->pbx->rtimeout; - while(!ast_exists_extension(c, c->context, exten, 1) && ( - strlen(exten) < ast_pbx_longest_extension(c->context))) { + while(!ast_exists_extension(c, c->context, exten, 1) && + ast_canmatch_extension(c, c->context, exten, 1)) { /* As long as we're willing to wait, and as long as it's not defined, keep reading digits until we can't possibly get a right answer anymore. */ digit = ast_waitfordigit(c, waittime * 1000); @@ -770,6 +826,32 @@ int pbx_builtin_hangup(struct ast_channel *chan, void *data) return -1; } +int pbx_builtin_stripmsd(struct ast_channel *chan, void *data) +{ + char newexten[AST_MAX_EXTENSION] = ""; + if (!data || !atoi(data)) { + ast_log(LOG_DEBUG, "Ignoring, since number of digits to strip is 0\n"); + return 0; + } + if (strlen(chan->exten) > atoi(data)) { + strncpy(newexten, chan->exten + atoi(data), sizeof(newexten)); + } + strncpy(chan->exten, newexten, sizeof(chan->exten)); + return 0; +} + +int pbx_builtin_prefix(struct ast_channel *chan, void *data) +{ + char newexten[AST_MAX_EXTENSION] = ""; + if (!data || !strlen(data)) { + ast_log(LOG_DEBUG, "Ignoring, since there is no prefix to add\n"); + return 0; + } + snprintf(newexten, sizeof(newexten), "%s%s", (char *)data, chan->exten); + strncpy(chan->exten, newexten, sizeof(chan->exten)); + return 0; +} + int pbx_builtin_wait(struct ast_channel *chan, void *data) { /* Wait for "n" seconds */ diff --git a/sounds/demo-moreinfo.gsm b/sounds/demo-moreinfo.gsm Binary files differnew file mode 100755 index 000000000..f31b31230 --- /dev/null +++ b/sounds/demo-moreinfo.gsm diff --git a/translate.c b/translate.c index c2264cc4f..7546b59a2 100755 --- a/translate.c +++ b/translate.c @@ -16,6 +16,9 @@ #include <asterisk/logger.h> #include <asterisk/translate.h> #include <asterisk/options.h> +#include <asterisk/frame.h> +#include <asterisk/sched.h> +#include <asterisk/cli.h> #include <sys/socket.h> #include <sys/time.h> #include <unistd.h> @@ -24,6 +27,16 @@ #include <string.h> #include <stdio.h> +/* Uncomment the EXPERIMENTAL_TRANSLATION to enable a more complicated, but probably more + correct way of handling full duplex translation */ + +/* +#define EXPERIMENTAL_TRANSLATION +*/ + +/* This could all be done more efficiently *IF* we chained packets together + by default, but it would also complicate virtually every application. */ + static char *type = "Trans"; static pthread_mutex_t list_lock = PTHREAD_MUTEX_INITIALIZER; @@ -34,6 +47,15 @@ struct ast_translator_dir { int cost; /* Complete cost to destination */ }; +struct ast_frame_delivery { + struct ast_frame *f; + struct ast_channel *chan; + int fd; + struct translator_pvt *owner; + struct ast_frame_delivery *prev; + struct ast_frame_delivery *next; +}; + static struct ast_translator_dir tr_matrix[MAX_FORMAT][MAX_FORMAT]; struct ast_trans_pvt { @@ -59,9 +81,69 @@ struct translator_pvt { struct ast_trans_pvt *system; struct ast_trans_pvt *rsystem; struct timeval lastpass; +#ifdef EXPERIMENTAL_TRANSLATION + struct ast_frame_delivery *head; + struct ast_frame_delivery *tail; + struct sched_context *sched; +#endif pthread_t threadid; }; + +#ifdef EXPERIMENTAL_TRANSLATION +static int deliver(void *data) +{ + struct ast_frame_delivery *del = data; + ast_log(LOG_DEBUG, "Delivering a packet\n"); + if (del->f) { + if (del->chan) + ast_write(del->chan, del->f); + else + ast_fr_fdwrite(del->fd, del->f); + ast_frfree(del->f); + } + /* Take us out of the list */ + if (del->prev) + del->prev->next = del->next; + else + del->owner->head = del->next; + if (del->next) + del->next->prev = del->prev; + else + del->owner->tail = del->prev; + /* Free used memory */ + free(del); + /* Never run again */ + return 0; +} + +/* Schedule the delivery of a packet in the near future, using the given context */ +static int schedule_delivery(struct sched_context *sched, struct translator_pvt *p, struct ast_channel *c, + int fd, struct ast_frame *f, int ms) +{ + struct ast_frame_delivery *del; + ast_log(LOG_DEBUG, "Scheduling a packet delivery\n"); + del = malloc(sizeof(struct ast_frame_delivery)); + if (del) { + del->f = ast_frdup(f); + del->chan = c; + del->fd = fd; + del->owner = p; + if (p->tail) { + del->prev = p->tail; + p->tail = del; + del->next = NULL; + } else { + p->head = p->tail = del; + del->next = NULL; + del->prev = NULL; + } + ast_sched_add(sched, ms, deliver, del); + return 0; + } else + return -1; +} +#endif static int translator_hangup(struct ast_channel *chan) { ast_log(LOG_WARNING, "Explicit hangup on '%s' not recommended! Call translator_destroy() instead.\n", chan->name); @@ -107,6 +189,18 @@ void ast_translator_free_path(struct ast_trans_pvt *p) static void ast_translator_free(struct translator_pvt *pvt) { +#ifdef EXPERIMENTAL_TRANSLATION + struct ast_frame_delivery *d, *dl; + if (pvt->sched) + sched_context_destroy(pvt->sched); + d = pvt->head; + while(d) { + dl = d; + d = d->next; + ast_frfree(dl->f); + free(dl); + } +#endif ast_translator_free_path(pvt->system); ast_translator_free_path(pvt->rsystem); if (pvt->comm[0] > -1) @@ -150,71 +244,23 @@ struct ast_trans_pvt *ast_translator_build_path(int source, int dest) ast_log(LOG_WARNING, "Out of memory\n"); return NULL; } + } else { + /* We shouldn't have allocated any memory */ + ast_log(LOG_WARNING, "No translator path from %d to %d\n", source, dest); + return NULL; } } return tmpr; } -static struct ast_frame *fd_read(int fd) -{ - char buf[4096]; - int res; - struct ast_frame *f = (struct ast_frame *)buf; - /* Read a frame directly from there. They're always in the - right format. */ - - if (read(fd, buf, sizeof(struct ast_frame)) - == sizeof(struct ast_frame)) { - /* read the frame header */ - f->mallocd = 0; - /* Re-write data position */ - f->data = buf + sizeof(struct ast_frame) + AST_FRIENDLY_OFFSET; - f->offset = AST_FRIENDLY_OFFSET; - /* Forget about being mallocd */ - f->mallocd = 0; - /* Re-write the source */ - f->src = __FUNCTION__; - if (f->datalen > sizeof(buf) - sizeof(struct ast_frame) - AST_FRIENDLY_OFFSET) { - /* Really bad read */ - ast_log(LOG_WARNING, "Strange read (%d bytes)\n", f->datalen); - return NULL; - } - if (f->datalen) { - if ((res = read(fd, f->data, f->datalen)) != f->datalen) { - /* Bad read */ - ast_log(LOG_WARNING, "How very strange, expected %d, got %d\n", f->datalen, res); - return NULL; - } - } - return ast_frisolate(f); - } else if (option_debug) - ast_log(LOG_DEBUG, "NULL or invalid header\n"); - /* Null if there was an error */ - return NULL; -} - static struct ast_frame *translator_read(struct ast_channel *chan) { - return fd_read(chan->fd); -} - -static int fd_write(int fd, struct ast_frame *frame) -{ - /* Write the frame exactly */ - if (write(fd, frame, sizeof(struct ast_frame)) != sizeof(struct ast_frame)) { - ast_log(LOG_WARNING, "Write error\n"); - return -1; - } - if (write(fd, frame->data, frame->datalen) != frame->datalen) { - ast_log(LOG_WARNING, "Write error\n"); - return -1; - } - return 0; + return ast_fr_fdread(chan->fd); } static int translator_write(struct ast_channel *chan, struct ast_frame *frame) { - return fd_write(chan->fd, frame); + return ast_fr_fdwrite(chan->fd, frame); } struct ast_frame_chain *ast_translate(struct ast_trans_pvt *path, struct ast_frame *f) @@ -256,9 +302,9 @@ struct ast_frame_chain *ast_translate(struct ast_trans_pvt *path, struct ast_fra return outc; } -#define FUDGE 2 +#define FUDGE 0 -static void translator_apply(struct ast_trans_pvt *path, struct ast_frame *f, int fd, struct ast_channel *c, struct timeval *last) +static void translator_apply(struct translator_pvt *pvt, struct ast_trans_pvt *path, struct ast_frame *f, int fd, struct ast_channel *c, struct timeval *last) { struct ast_trans_pvt *p; struct ast_frame *out; @@ -279,15 +325,33 @@ static void translator_apply(struct ast_trans_pvt *path, struct ast_frame *f, in gettimeofday(&tv, NULL); ms = 1000 * (tv.tv_sec - last->tv_sec) + (tv.tv_usec - last->tv_usec) / 1000; +#ifdef EXPERIMENTAL_TRANSLATION + if (ms + FUDGE < out->timelen) + schedule_delivery(pvt->sched, pvt, + c, fd, out, ms); + else { + if (c) + ast_write(c, out); + else + ast_fr_fdwrite(fd, out); + } + last->tv_sec = tv.tv_sec; + last->tv_usec = tv.tv_usec; + /* Schedule this packet to be delivered at the + right time */ + } else +#else + /* XXX Not correct in the full duplex case XXX */ if (ms + FUDGE < out->timelen) usleep((out->timelen - ms - FUDGE) * 1000); last->tv_sec = tv.tv_sec; last->tv_usec = tv.tv_usec; } +#endif if (c) ast_write(c, out); else - fd_write(fd, out); + ast_fr_fdwrite(fd, out); } ast_frfree(out); } @@ -331,14 +395,14 @@ static void *translator_thread(void *data) } if (f->frametype == AST_FRAME_VOICE) { if (pvt->system) - translator_apply(pvt->system, f, fd, NULL, &pvt->lastpass); + translator_apply(pvt, pvt->system, f, fd, NULL, &pvt->lastpass); } else { /* If it's not voice, just pass it along */ - fd_write(fd, f); + ast_fr_fdwrite(fd, f); } ast_frfree(f); } else { - f = fd_read(res); + f = ast_fr_fdread(res); if (!f) { if (option_debug) ast_log(LOG_DEBUG, "Empty (hangup) frame\n"); @@ -347,7 +411,7 @@ static void *translator_thread(void *data) if (f->frametype == AST_FRAME_VOICE) { if (pvt->rsystem) - translator_apply(pvt->rsystem, f, -1, real, &pvt->lastpass); + translator_apply(pvt, pvt->rsystem, f, -1, real, &pvt->lastpass); } else { ast_write(real, f); } @@ -384,9 +448,21 @@ struct ast_channel *ast_translator_create(struct ast_channel *real, int format, pvt->comm[1] = -1; pvt->lastpass.tv_usec = 0; pvt->lastpass.tv_sec = 0; + +#ifdef EXPERIMENTAL_TRANSLATION + pvt->head = NULL; + pvt->tail = NULL; + pvt->sched = sched_context_create(); + if (!pvt->sched) { + ast_log(LOG_WARNING, "Out of memory\n"); + ast_translator_free(pvt); + return NULL; + } +#endif if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pvt->comm)) { ast_log(LOG_WARNING, "Unable to create UNIX domain socket on '%s'\n", real->name); ast_translator_free(pvt); + return NULL; } /* In to the system */ if (direction & AST_DIRECTION_IN) @@ -534,6 +610,46 @@ static void calc_cost(struct ast_translator *t) t->cost = cost; } +static int show_translation(int fd, int argc, char *argv[]) +{ +#define SHOW_TRANS 14 + int x,y; + char line[80]; + if (argc != 2) + return RESULT_SHOWUSAGE; + ast_cli(fd, " Translation times between formats (in milliseconds)\n"); + ast_cli(fd, " Destination Format\n"); + pthread_mutex_lock(&list_lock); + for (x=0;x<SHOW_TRANS; x++) { + if (x == 1) + strcpy(line, " Src "); + else if (x == 2) + strcpy(line, " Fmt "); + else + strcpy(line, " "); + for (y=0;y<SHOW_TRANS;y++) { + if (tr_matrix[x][y].step) + snprintf(line + strlen(line), sizeof(line) - strlen(line), " %4d", tr_matrix[x][y].cost); + else + snprintf(line + strlen(line), sizeof(line) - strlen(line), " n/a"); + } + snprintf(line + strlen(line), sizeof(line) - strlen(line), "\n"); + ast_cli(fd, line); + } + pthread_mutex_unlock(&list_lock); + return RESULT_SUCCESS; +} + +static int added_cli = 0; + +static char show_trans_usage[] = +"Usage: show translation\n" +" Displays known codec translators and the cost associated\n" +"with each conversion.\n"; + +static struct ast_cli_entry show_trans = +{ { "show", "translation", NULL }, show_translation, "Display translation matrix", show_trans_usage }; + int ast_register_translator(struct ast_translator *t) { t->srcfmt = powerof(t->srcfmt); @@ -546,6 +662,10 @@ int ast_register_translator(struct ast_translator *t) if (option_verbose > 1) ast_verbose(VERBOSE_PREFIX_2 "Registered translator '%s' from format %d to %d, cost %d\n", t->name, t->srcfmt, t->dstfmt, t->cost); pthread_mutex_lock(&list_lock); + if (!added_cli) { + ast_cli_register(&show_trans); + added_cli++; + } t->next = list; list = t; rebuild_matrix(); @@ -566,6 +686,7 @@ int ast_unregister_translator(struct ast_translator *t) list = u->next; break; } + ul = u; u = u->next; } rebuild_matrix(); @@ -599,20 +720,23 @@ void ast_translator_destroy(struct ast_channel *trans) int ast_translator_best_choice(int dst, int srcs) { /* Calculate our best source format, given costs, and a desired destination */ - int x; + int x,y; int best=-1; + int cur = 1; int besttime=999999999; - dst = powerof(dst); pthread_mutex_lock(&list_lock); - for (x=0;x<MAX_FORMAT;x++) { - if (tr_matrix[x][dst].step && /* There's a step */ - (tr_matrix[x][dst].cost < besttime) && /* We're better than what exists now */ - (srcs & (1 << x))) /* x is a valid source format */ - { - best = 1 << x; - besttime = tr_matrix[x][dst].cost; + for (y=0;y<MAX_FORMAT;y++) { + if (cur & dst) + for (x=0;x<MAX_FORMAT;x++) { + if (tr_matrix[x][y].step && /* There's a step */ + (tr_matrix[x][y].cost < besttime) && /* We're better than what exists now */ + (srcs & (1 << x))) /* x is a valid source format */ + { + best = 1 << x; + besttime = tr_matrix[x][dst].cost; + } } - + cur = cur << 1; } pthread_mutex_unlock(&list_lock); return best; |