chan-capi/chan_capi.c

5953 lines
156 KiB
C

/*
* (CAPI*)
*
* An implementation of Common ISDN API 2.0 for Asterisk
*
* Copyright (C) 2005-2007 Cytronics & Melware
*
* Armin Schindler <armin@melware.de>
*
* Reworked, but based on the work of
* Copyright (C) 2002-2005 Junghanns.NET GmbH
*
* Klaus-Peter Junghanns <kapejod@ns1.jnetdns.de>
*
* This program is free software and may be modified and
* distributed under the terms of the GNU Public License.
*/
#include <sys/time.h>
#include <sys/signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include "xlaw.h"
#include "chan_capi20.h"
#include "chan_capi.h"
#include "chan_capi_rtp.h"
#include "chan_capi_qsig.h"
#include "chan_capi_qsig_asn197ade.h"
#include "chan_capi_qsig_asn197no.h"
#include "chan_capi_utils.h"
#include "chan_capi_supplementary.h"
/* #define CC_VERSION "x.y.z" */
#define CC_VERSION "$Revision$"
/*
* personal stuff
*/
#undef CAPI_APPLID_UNUSED
#define CAPI_APPLID_UNUSED 0xffffffff
unsigned capi_ApplID = CAPI_APPLID_UNUSED;
static const char tdesc[] = "Common ISDN API Driver (" CC_VERSION ")";
static const char channeltype[] = "CAPI";
static const struct ast_channel_tech capi_tech;
#ifdef CC_AST_HAS_VERSION_1_4
#define AST_MODULE "chan_capi"
#else
static char *ccdesc = "Common ISDN API for Asterisk";
#endif
static char *commandtdesc = "CAPI command interface.\n"
"The dial command:\n"
"Dial(CAPI/g<group>/[<callerid>:]<destination>[/<params>])\n"
"Dial(CAPI/contr<controller>/[<callerid>:]<destination>[/<params>])\n"
"Dial(CAPI/<interface-name>/[<callerid>:]<destination>[/<params>])\n"
"\"params\" can be:\n"
"early B3:\"b\"=always, \"B\"=on successful calls only\n"
"\"d\":use callerID from capi.conf, \"o\":overlap sending number\n"
"\n\"q\":disable QSIG functions on outgoing call\n"
"\n"
"capicommand() where () can be:\n"
"\"progress\" send progress (for NT mode)\n"
"\"deflect|to_number\" forwards an unanswered call to number\n"
"\"malicous\" report a call of malicious nature\n"
"\"echocancel|<yes> or <no>\" echo-cancel provided by driver/hardware\n"
"\"echosquelch|<yes> or <no>\" very primitive echo-squelch by chan-capi\n"
"\"holdtype|<local> or <hold>\" set type of 'hold'\n"
"\"hold[|MYHOLDVAR]\" puts an answered call on hold\n"
"\"retrieve|${MYHOLDVAR}\" gets back the held call\n"
"\"ect|${MYHOLDVAR})\" explicit call transfer of call on hold\n"
"\"3pty_begin|${MYHOLDVAR})\" Three-Party-Conference (3PTY) with active and held call\n"
"\"receivefax|filename|stationID|headline\" receive a CAPIfax\n"
"\"sendfax|filename.sff|stationID|headline\" send a CAPIfax\n"
"\"qsig_ssct|cidsrc|ciddst\" QSIG single step call transfer\n"
"\"qsig_ct|cidsrc|ciddst|marker|waitconnect\" QSIG call transfer\n"
"\"qsig_callmark|marker\" marks a QSIG call for later identification\n"
"Variables set after fax receive:\n"
"FAXSTATUS :0=OK, 1=Error\n"
"FAXREASON :B3 disconnect reason\n"
"FAXREASONTEXT :FAXREASON as text\n"
"FAXRATE :baud rate of fax connection\n"
"FAXRESOLUTION :0=standard, 1=high\n"
"FAXFORMAT :0=SFF\n"
"FAXPAGES :Number of pages received\n"
"FAXID :ID of the remote fax machine\n"
"Asterisk variables used/set by chan_capi:\n"
"BCHANNELINFO,CALLEDTON,_CALLERHOLDID,CALLINGSUBADDRESS,CALLEDSUBADDRESS\n"
"CONNECTEDNUMBER,FAXEXTEN,PRI_CAUSE,REDIRECTINGNUMBER,REDIRECTREASON\n"
"!!! for more details and samples, check the README of chan-capi !!!\n";
static char *commandapp = "capiCommand";
static char *commandsynopsis = "Execute special CAPI commands";
#ifndef CC_AST_HAS_VERSION_1_4
STANDARD_LOCAL_USER;
LOCAL_USER_DECL;
#endif
static int usecnt;
/*
* LOCKING RULES
* =============
*
* This channel driver uses several locks. One must be
* careful not to reverse the locking order, which will
* lead to a so called deadlock. Here is the locking order
* that must be followed:
*
* struct capi_pvt *i;
*
* 1. cc_mutex_lock(&i->owner->lock); **
*
* 2. cc_mutex_lock(&i->lock);
*
* 3. cc_mutex_lock(&iflock);
* 4. cc_mutex_lock(&messagenumber_lock);
* 5. cc_mutex_lock(&usecnt_lock);
* 6. cc_mutex_lock(&capi_put_lock);
*
*
* ** the PBX will call the callback functions with
* this lock locked. This lock protects the
* structure pointed to by 'i->owner'. Also note
* that calling some PBX functions will lock
* this lock!
*/
#ifndef CC_AST_HAS_VERSION_1_4
AST_MUTEX_DEFINE_STATIC(usecnt_lock);
#endif
AST_MUTEX_DEFINE_STATIC(iflock);
static int capi_capability = AST_FORMAT_ALAW;
static pthread_t monitor_thread = (pthread_t)(0-1);
struct capi_pvt *iflist = NULL;
static struct cc_capi_controller *capi_controllers[CAPI_MAX_CONTROLLERS + 1];
static int capi_num_controllers = 0;
static unsigned int capi_counter = 0;
static unsigned long capi_used_controllers = 0;
static struct ast_channel *chan_for_task;
static int channel_task;
#define CAPI_CHANNEL_TASK_NONE 0
#define CAPI_CHANNEL_TASK_HANGUP 1
#define CAPI_CHANNEL_TASK_SOFTHANGUP 2
#define CAPI_CHANNEL_TASK_PICKUP 3
#define CAPI_CHANNEL_TASK_GOTOFAX 4
static char capi_national_prefix[AST_MAX_EXTENSION];
static char capi_international_prefix[AST_MAX_EXTENSION];
static char default_language[MAX_LANGUAGE] = "";
#ifdef CC_AST_HAS_VERSION_1_4
/* Global jitterbuffer configuration - by default, jb is disabled */
static struct ast_jb_conf default_jbconf =
{
.flags = 0,
.max_size = -1,
.resync_threshold = -1,
.impl = ""
};
static struct ast_jb_conf global_jbconf;
static char global_mohinterpret[MAX_MUSICCLASS] = "default";
#endif
/* local prototypes */
#ifdef CC_AST_HAS_INDICATE_DATA
static int pbx_capi_indicate(struct ast_channel *c, int condition, const void *data, size_t datalen);
#else
static int pbx_capi_indicate(struct ast_channel *c, int condition);
#endif
/*
* B protocol settings
*/
static struct {
_cword b1protocol;
_cword b2protocol;
_cword b3protocol;
_cstruct b1configuration;
_cstruct b2configuration;
_cstruct b3configuration;
} b_protocol_table[] =
{
{ 0x01, 0x01, 0x00, /* 0 */
NULL,
NULL,
NULL
},
{ 0x04, 0x04, 0x04, /* 1 */
NULL,
NULL,
NULL
},
{ 0x1f, 0x1f, 0x1f, /* 2 */
(_cstruct) "\x00",
/* (_cstruct) "\x04\x01\x00\x00\x02", */
(_cstruct) "\x06\x01\x00\x58\x02\x32\x00",
(_cstruct) "\x00"
}
};
#ifndef CC_HAVE_NO_GLOBALCONFIGURATION
/*
* set the global-configuration (b-channel operation)
*/
static _cstruct capi_set_global_configuration(struct capi_pvt *i)
{
unsigned short dtedce = 0;
unsigned char *buf = i->tmpbuf;
buf[0] = 2; /* len */
if (i->FaxState & CAPI_FAX_STATE_ACTIVE) {
if ((i->outgoing) && (!(i->FaxState & CAPI_FAX_STATE_SENDMODE)))
dtedce = 2;
if ((!(i->outgoing)) && ((i->FaxState & CAPI_FAX_STATE_SENDMODE)))
dtedce = 1;
}
write_capi_word(&buf[1], dtedce);
if (dtedce == 0)
buf = NULL;
return (_cstruct)buf;
}
#endif
/*
* command to string function
*/
static const char * capi_command_to_string(unsigned short wCmd)
{
enum { lowest_value = CAPI_P_MIN,
end_value = CAPI_P_MAX,
range = end_value - lowest_value,
};
#undef CHAN_CAPI_COMMAND_DESC
#define CHAN_CAPI_COMMAND_DESC(n, ENUM, value) \
[CAPI_P_REQ(ENUM)-(n)] = #ENUM "_REQ", \
[CAPI_P_CONF(ENUM)-(n)] = #ENUM "_CONF", \
[CAPI_P_IND(ENUM)-(n)] = #ENUM "_IND", \
[CAPI_P_RESP(ENUM)-(n)] = #ENUM "_RESP",
static const char * const table[range] = {
CAPI_COMMANDS(CHAN_CAPI_COMMAND_DESC, lowest_value)
};
wCmd -= lowest_value;
if (wCmd >= range) {
goto error;
}
if (table[wCmd] == NULL) {
goto error;
}
return table[wCmd];
error:
return "UNDEFINED";
}
/*
* wait for a specific message
*/
static MESSAGE_EXCHANGE_ERROR capi_wait_conf(struct capi_pvt *i, unsigned short wCmd)
{
MESSAGE_EXCHANGE_ERROR error = 0;
struct timespec abstime;
unsigned char command, subcommand;
subcommand = wCmd & 0xff;
command = (wCmd & 0xff00) >> 8;
i->waitevent = (unsigned int)wCmd;
abstime.tv_sec = time(NULL) + 2;
abstime.tv_nsec = 0;
cc_verbose(4, 1, "%s: wait for %s (0x%x)\n",
i->vname, capi_cmd2str(command, subcommand), i->waitevent);
if (ast_cond_timedwait(&i->event_trigger, &i->lock, &abstime) != 0) {
error = -1;
cc_log(LOG_WARNING, "%s: timed out waiting for %s\n",
i->vname, capi_cmd2str(command, subcommand));
} else {
cc_verbose(4, 1, "%s: cond signal received for %s\n",
i->vname, capi_cmd2str(command, subcommand));
}
return error;
}
/*
* write a capi message and wait for CONF
* i->lock must be held
*/
MESSAGE_EXCHANGE_ERROR _capi_put_cmsg_wait_conf(struct capi_pvt *i, _cmsg *CMSG)
{
MESSAGE_EXCHANGE_ERROR error;
error = _capi_put_cmsg(CMSG);
if (!(error)) {
unsigned short wCmd = CAPICMD(CMSG->Command, CAPI_CONF);
error = capi_wait_conf(i, wCmd);
}
return error;
}
/*
* wait for B3 up
*/
static void capi_wait_for_b3_up(struct capi_pvt *i)
{
struct timespec abstime;
cc_mutex_lock(&i->lock);
if (!(i->isdnstate & CAPI_ISDN_STATE_B3_UP)) {
i->waitevent = CAPI_WAITEVENT_B3_UP;
abstime.tv_sec = time(NULL) + 2;
abstime.tv_nsec = 0;
cc_verbose(4, 1, "%s: wait for b3 up.\n",
i->vname);
if (ast_cond_timedwait(&i->event_trigger, &i->lock, &abstime) != 0) {
cc_log(LOG_WARNING, "%s: timed out waiting for b3 up.\n",
i->vname);
} else {
cc_verbose(4, 1, "%s: cond signal received for b3 up.\n",
i->vname);
}
}
cc_mutex_unlock(&i->lock);
}
/*
* wait for finishing answering state
*/
static void capi_wait_for_answered(struct capi_pvt *i)
{
struct timespec abstime;
cc_mutex_lock(&i->lock);
if (i->state == CAPI_STATE_ANSWERING) {
i->waitevent = CAPI_WAITEVENT_ANSWER_FINISH;
abstime.tv_sec = time(NULL) + 2;
abstime.tv_nsec = 0;
cc_verbose(4, 1, "%s: wait for finish answer.\n",
i->vname);
if (ast_cond_timedwait(&i->event_trigger, &i->lock, &abstime) != 0) {
cc_log(LOG_WARNING, "%s: timed out waiting for finish answer.\n",
i->vname);
} else {
cc_verbose(4, 1, "%s: cond signal received for finish answer.\n",
i->vname);
}
}
cc_mutex_unlock(&i->lock);
}
/*
* function to tell if fax activity has finished
*/
static int capi_tell_fax_finish(void *data)
{
struct capi_pvt *i = (struct capi_pvt *)data;
if (i->FaxState & CAPI_FAX_STATE_ACTIVE) {
return 1;
}
return 0;
}
/*
* TCAP -> CIP Translation Table (TransferCapability->CommonIsdnProfile)
*/
static struct {
unsigned short tcap;
unsigned short cip;
unsigned char digital;
} translate_tcap2cip[] = {
{ PRI_TRANS_CAP_SPEECH, CAPI_CIPI_SPEECH, 0 },
{ PRI_TRANS_CAP_DIGITAL, CAPI_CIPI_DIGITAL, 1 },
{ PRI_TRANS_CAP_RESTRICTED_DIGITAL, CAPI_CIPI_RESTRICTED_DIGITAL, 1 },
{ PRI_TRANS_CAP_3K1AUDIO, CAPI_CIPI_3K1AUDIO, 0 },
{ PRI_TRANS_CAP_DIGITAL_W_TONES, CAPI_CIPI_DIGITAL_W_TONES, 1 },
{ PRI_TRANS_CAP_VIDEO, CAPI_CIPI_VIDEO, 1 }
};
static int tcap2cip(unsigned short tcap)
{
int x;
for (x = 0; x < sizeof(translate_tcap2cip) / sizeof(translate_tcap2cip[0]); x++) {
if (translate_tcap2cip[x].tcap == tcap)
return (int)translate_tcap2cip[x].cip;
}
return CAPI_CIPI_SPEECH;
}
static unsigned char tcap_is_digital(unsigned short tcap)
{
int x;
for (x = 0; x < sizeof(translate_tcap2cip) / sizeof(translate_tcap2cip[0]); x++) {
if (translate_tcap2cip[x].tcap == tcap)
return translate_tcap2cip[x].digital;
}
return 0;
}
/*
* CIP -> TCAP Translation Table (CommonIsdnProfile->TransferCapability)
*/
static struct {
unsigned short cip;
unsigned short tcap;
} translate_cip2tcap[] = {
{ CAPI_CIPI_SPEECH, PRI_TRANS_CAP_SPEECH },
{ CAPI_CIPI_DIGITAL, PRI_TRANS_CAP_DIGITAL },
{ CAPI_CIPI_RESTRICTED_DIGITAL, PRI_TRANS_CAP_RESTRICTED_DIGITAL },
{ CAPI_CIPI_3K1AUDIO, PRI_TRANS_CAP_3K1AUDIO },
{ CAPI_CIPI_7KAUDIO, PRI_TRANS_CAP_DIGITAL_W_TONES },
{ CAPI_CIPI_VIDEO, PRI_TRANS_CAP_VIDEO },
{ CAPI_CIPI_PACKET_MODE, PRI_TRANS_CAP_DIGITAL },
{ CAPI_CIPI_56KBIT_RATE_ADAPTION, PRI_TRANS_CAP_DIGITAL },
{ CAPI_CIPI_DIGITAL_W_TONES, PRI_TRANS_CAP_DIGITAL_W_TONES },
{ CAPI_CIPI_TELEPHONY, PRI_TRANS_CAP_SPEECH },
{ CAPI_CIPI_FAX_G2_3, PRI_TRANS_CAP_3K1AUDIO },
{ CAPI_CIPI_FAX_G4C1, PRI_TRANS_CAP_DIGITAL },
{ CAPI_CIPI_FAX_G4C2_3, PRI_TRANS_CAP_DIGITAL },
{ CAPI_CIPI_TELETEX_PROCESSABLE, PRI_TRANS_CAP_DIGITAL },
{ CAPI_CIPI_TELETEX_BASIC, PRI_TRANS_CAP_DIGITAL },
{ CAPI_CIPI_VIDEOTEX, PRI_TRANS_CAP_DIGITAL },
{ CAPI_CIPI_TELEX, PRI_TRANS_CAP_DIGITAL },
{ CAPI_CIPI_X400, PRI_TRANS_CAP_DIGITAL },
{ CAPI_CIPI_X200, PRI_TRANS_CAP_DIGITAL },
{ CAPI_CIPI_7K_TELEPHONY, PRI_TRANS_CAP_DIGITAL_W_TONES },
{ CAPI_CIPI_VIDEO_TELEPHONY_C1, PRI_TRANS_CAP_DIGITAL_W_TONES },
{ CAPI_CIPI_VIDEO_TELEPHONY_C2, PRI_TRANS_CAP_DIGITAL }
};
static unsigned short cip2tcap(int cip)
{
int x;
for (x = 0;x < sizeof(translate_cip2tcap) / sizeof(translate_cip2tcap[0]); x++) {
if (translate_cip2tcap[x].cip == (unsigned short)cip)
return translate_cip2tcap[x].tcap;
}
return 0;
}
/*
* TransferCapability to String conversion
*/
static char *transfercapability2str(int transfercapability)
{
switch(transfercapability) {
case PRI_TRANS_CAP_SPEECH:
return "SPEECH";
case PRI_TRANS_CAP_DIGITAL:
return "DIGITAL";
case PRI_TRANS_CAP_RESTRICTED_DIGITAL:
return "RESTRICTED_DIGITAL";
case PRI_TRANS_CAP_3K1AUDIO:
return "3K1AUDIO";
case PRI_TRANS_CAP_DIGITAL_W_TONES:
return "DIGITAL_W_TONES";
case PRI_TRANS_CAP_VIDEO:
return "VIDEO";
default:
return "UNKNOWN";
}
}
/*
* set task for a channel which need to be done out of lock
* ( after the capi thread loop )
*/
static void capi_channel_task(struct ast_channel *c, int task)
{
chan_for_task = c;
channel_task = task;
cc_verbose(4, 1, VERBOSE_PREFIX_4 "%s: set channel task to %d\n",
c->name, task);
}
/*
* Echo cancellation is for cards w/ integrated echo cancellation only
* (i.e. Eicon active cards support it)
*/
#define EC_FUNCTION_ENABLE 1
#define EC_FUNCTION_DISABLE 2
#define EC_FUNCTION_FREEZE 3
#define EC_FUNCTION_RESUME 4
#define EC_FUNCTION_RESET 5
#define EC_OPTION_DISABLE_NEVER 0
#define EC_OPTION_DISABLE_G165 (1<<2)
#define EC_OPTION_DISABLE_G164_OR_G165 (1<<1 | 1<<2)
#define EC_DEFAULT_TAIL 0 /* maximum */
static void capi_echo_canceller(struct ast_channel *c, int function)
{
struct capi_pvt *i = CC_CHANNEL_PVT(c);
_cmsg CMSG;
char buf[10];
int ecAvail = 0;
if ((i->isdnstate & CAPI_ISDN_STATE_DISCONNECT))
return;
if (((function == EC_FUNCTION_ENABLE) && (i->isdnstate & CAPI_ISDN_STATE_EC)) ||
((function != EC_FUNCTION_ENABLE) && (!(i->isdnstate & CAPI_ISDN_STATE_EC)))) {
cc_verbose(3, 1, VERBOSE_PREFIX_4 "%s: echo canceller (PLCI=%#x, function=%d) unchanged\n",
i->vname, i->PLCI, function);
/* nothing to do */
return;
}
/* check for old echo-cancel configuration */
if ((i->ecSelector != FACILITYSELECTOR_ECHO_CANCEL) &&
(capi_controllers[i->controller]->broadband)) {
ecAvail = 1;
}
if ((i->ecSelector == FACILITYSELECTOR_ECHO_CANCEL) &&
(capi_controllers[i->controller]->echocancel)) {
ecAvail = 1;
}
/* If echo cancellation is not requested or supported, don't attempt to enable it */
if (!ecAvail || !i->doEC) {
return;
}
if (tcap_is_digital(c->transfercapability)) {
cc_verbose(3, 1, VERBOSE_PREFIX_2 "%s: No echo canceller in digital mode (PLCI=%#x)\n",
i->vname, i->PLCI);
return;
}
cc_verbose(3, 0, VERBOSE_PREFIX_2 "%s: Setting up echo canceller (PLCI=%#x, function=%d, options=%d, tail=%d)\n",
i->vname, i->PLCI, function, i->ecOption, i->ecTail);
FACILITY_REQ_HEADER(&CMSG, capi_ApplID, get_capi_MessageNumber(), 0);
FACILITY_REQ_PLCI(&CMSG) = i->PLCI;
FACILITY_REQ_FACILITYSELECTOR(&CMSG) = i->ecSelector;
memset(buf, 0, sizeof(buf));
buf[0] = 9; /* msg size */
write_capi_word(&buf[1], function);
if (function == EC_FUNCTION_ENABLE) {
buf[3] = 6; /* echo cancel param struct size */
write_capi_word(&buf[4], i->ecOption); /* bit field - ignore echo canceller disable tone */
write_capi_word(&buf[6], i->ecTail); /* Tail length, ms */
/* buf 8 and 9 are "pre-delay lenght ms" */
i->isdnstate |= CAPI_ISDN_STATE_EC;
} else {
i->isdnstate &= ~CAPI_ISDN_STATE_EC;
}
FACILITY_REQ_FACILITYREQUESTPARAMETER(&CMSG) = (_cstruct)buf;
if (_capi_put_cmsg(&CMSG) != 0) {
return;
}
return;
}
/*
* turn on/off DTMF detection
*/
static int capi_detect_dtmf(struct ast_channel *c, int flag)
{
struct capi_pvt *i = CC_CHANNEL_PVT(c);
MESSAGE_EXCHANGE_ERROR error;
_cmsg CMSG;
char buf[9];
if ((i->isdnstate & CAPI_ISDN_STATE_DISCONNECT))
return 0;
if (tcap_is_digital(c->transfercapability)) {
cc_verbose(3, 1, VERBOSE_PREFIX_2 "%s: No dtmf-detect in digital mode (PLCI=%#x)\n",
i->vname, i->PLCI);
return 0;
}
if (((flag == 1) && (i->isdnstate & CAPI_ISDN_STATE_DTMF)) ||
((flag == 0) && (!(i->isdnstate & CAPI_ISDN_STATE_DTMF)))) {
cc_verbose(3, 1, VERBOSE_PREFIX_4 "%s: dtmf (PLCI=%#x, flag=%d) unchanged\n",
i->vname, i->PLCI, flag);
/* nothing to do */
return 0;
}
/* does the controller support dtmf? and do we want to use it? */
if ((capi_controllers[i->controller]->dtmf != 1) || (i->doDTMF != 0))
return 0;
memset(buf, 0, sizeof(buf));
cc_verbose(3, 0, VERBOSE_PREFIX_2 "%s: Setting up DTMF detector (PLCI=%#x, flag=%d)\n",
i->vname, i->PLCI, flag);
FACILITY_REQ_HEADER(&CMSG, capi_ApplID, get_capi_MessageNumber(), 0);
FACILITY_REQ_PLCI(&CMSG) = i->PLCI;
FACILITY_REQ_FACILITYSELECTOR(&CMSG) = FACILITYSELECTOR_DTMF;
buf[0] = 8; /* msg length */
if (flag == 1) {
write_capi_word(&buf[1], 1); /* start DTMF listen */
} else {
write_capi_word(&buf[1], 2); /* stop DTMF listen */
}
write_capi_word(&buf[3], CAPI_DTMF_DURATION);
write_capi_word(&buf[5], CAPI_DTMF_DURATION);
FACILITY_REQ_FACILITYREQUESTPARAMETER(&CMSG) = (_cstruct)buf;
if ((error = _capi_put_cmsg(&CMSG)) != 0) {
return error;
}
if (flag == 1) {
i->isdnstate |= CAPI_ISDN_STATE_DTMF;
} else {
i->isdnstate &= ~CAPI_ISDN_STATE_DTMF;
}
return 0;
}
/*
* queue a frame to PBX
*/
static int local_queue_frame(struct capi_pvt *i, struct ast_frame *f)
{
struct ast_channel *chan = i->owner;
unsigned char *wbuf;
int wbuflen;
if (chan == NULL) {
cc_log(LOG_ERROR, "No owner in local_queue_frame for %s\n",
i->vname);
return -1;
}
if (!(i->isdnstate & CAPI_ISDN_STATE_PBX)) {
/* if there is no PBX running yet,
we don't need any frames sent */
return -1;
}
if ((i->state == CAPI_STATE_DISCONNECTING) ||
(i->isdnstate & CAPI_ISDN_STATE_HANGUP)) {
cc_verbose(3, 1, VERBOSE_PREFIX_4 "%s: no queue_frame in state disconnecting for %d/%d\n",
i->vname, f->frametype, f->subclass);
return 0;
}
if ((capidebug) && (f->frametype != AST_FRAME_VOICE)) {
ast_frame_dump(i->vname, f, VERBOSE_PREFIX_3 "CAPI queue frame:");
}
if ((f->frametype == AST_FRAME_CONTROL) &&
(f->subclass == AST_CONTROL_HANGUP)) {
i->isdnstate |= CAPI_ISDN_STATE_HANGUP;
}
if (i->writerfd == -1) {
cc_log(LOG_ERROR, "No writerfd in local_queue_frame for %s\n",
i->vname);
return -1;
}
if (f->frametype != AST_FRAME_VOICE)
f->datalen = 0;
wbuflen = sizeof(struct ast_frame) + f->datalen;
wbuf = alloca(wbuflen);
memcpy(wbuf, f, sizeof(struct ast_frame));
if (f->datalen)
memcpy(wbuf + sizeof(struct ast_frame), f->data, f->datalen);
if (write(i->writerfd, wbuf, wbuflen) != wbuflen) {
cc_log(LOG_ERROR, "Could not write to pipe for %s\n",
i->vname);
}
return 0;
}
/*
* set a new name for this channel
*/
static void update_channel_name(struct capi_pvt *i)
{
char name[AST_CHANNEL_NAME];
snprintf(name, sizeof(name) - 1, "CAPI/%s/%s-%x",
i->vname, i->dnid, capi_counter++);
ast_change_name(i->owner, name);
cc_verbose(3, 0, VERBOSE_PREFIX_3 "%s: Updated channel name: %s\n",
i->vname, name);
}
/*
* send digits via INFO_REQ
*/
static int capi_send_info_digits(struct capi_pvt *i, char *digits, int len)
{
MESSAGE_EXCHANGE_ERROR error;
_cmsg CMSG;
char buf[64];
int a;
memset(buf, 0, sizeof(buf));
INFO_REQ_HEADER(&CMSG, capi_ApplID, get_capi_MessageNumber(), 0);
INFO_REQ_PLCI(&CMSG) = i->PLCI;
if (len > (sizeof(buf) - 2))
len = sizeof(buf) - 2;
buf[0] = len + 1;
buf[1] = 0x80;
for (a = 0; a < len; a++) {
buf[a + 2] = digits[a];
}
INFO_REQ_CALLEDPARTYNUMBER(&CMSG) = (_cstruct)buf;
if ((error = _capi_put_cmsg(&CMSG)) != 0) {
return error;
}
cc_verbose(3, 1, VERBOSE_PREFIX_4 "%s: sent CALLEDPARTYNUMBER INFO digits = '%s' (PLCI=%#x)\n",
i->vname, buf + 2, i->PLCI);
return 0;
}
#ifdef CC_AST_HAS_VERSION_1_4
/*
* begin send DMTF
*/
static int pbx_capi_send_digit_begin(struct ast_channel *c, char digit)
{
/* Not needed */
return 0;
}
#endif
/*
* send a DTMF digit
*/
#if defined(CC_AST_HAS_VERSION_1_4) && defined(CC_AST_HAS_SEND_DIGIT_END_DURATION)
static int pbx_capi_send_digit(struct ast_channel *c, char digit, unsigned int duration)
#else
static int pbx_capi_send_digit(struct ast_channel *c, char digit)
#endif
{
struct capi_pvt *i = CC_CHANNEL_PVT(c);
_cmsg CMSG;
char buf[10];
char did[2];
int ret = 0;
if (i == NULL) {
cc_log(LOG_ERROR, "No interface!\n");
return -1;
}
memset(buf, 0, sizeof(buf));
cc_mutex_lock(&i->lock);
if ((c->_state == AST_STATE_DIALING) &&
(i->state != CAPI_STATE_DISCONNECTING)) {
did[0] = digit;
did[1] = 0;
strncat(i->dnid, did, sizeof(i->dnid) - 1);
update_channel_name(i);
if ((i->isdnstate & CAPI_ISDN_STATE_SETUP_ACK) &&
(i->doOverlap == 0)) {
ret = capi_send_info_digits(i, &digit, 1);
} else {
/* if no SETUP-ACK yet, add it to the overlap list */
strncat(i->overlapdigits, &digit, 1);
i->doOverlap = 1;
}
cc_mutex_unlock(&i->lock);
return ret;
}
if ((i->state == CAPI_STATE_CONNECTED) && (i->isdnstate & CAPI_ISDN_STATE_B3_UP)) {
/* we have a real connection, so send real DTMF */
if ((capi_controllers[i->controller]->dtmf == 0) || (i->doDTMF > 0)) {
/* let * fake it */
cc_mutex_unlock(&i->lock);
return -1;
}
FACILITY_REQ_HEADER(&CMSG, capi_ApplID, get_capi_MessageNumber(), 0);
FACILITY_REQ_PLCI(&CMSG) = i->NCCI;
FACILITY_REQ_FACILITYSELECTOR(&CMSG) = FACILITYSELECTOR_DTMF;
buf[0] = 8;
write_capi_word(&buf[1], 3); /* send DTMF digit */
/* XXX: duration comes from asterisk in 1.4 */
write_capi_word(&buf[3], CAPI_DTMF_DURATION);
write_capi_word(&buf[5], CAPI_DTMF_DURATION);
buf[7] = 1;
buf[8] = digit;
FACILITY_REQ_FACILITYREQUESTPARAMETER(&CMSG) = (_cstruct)buf;
if ((ret = _capi_put_cmsg(&CMSG)) == 0) {
cc_verbose(3, 0, VERBOSE_PREFIX_4 "%s: sent dtmf '%c'\n",
i->vname, digit);
}
}
cc_mutex_unlock(&i->lock);
return ret;
}
/*
* send ALERT to ISDN line
*/
static int pbx_capi_alert(struct ast_channel *c)
{
struct capi_pvt *i = CC_CHANNEL_PVT(c);
_cmsg CMSG;
if ((i->state != CAPI_STATE_INCALL) &&
(i->state != CAPI_STATE_DID)) {
cc_verbose(2, 1, VERBOSE_PREFIX_3 "%s: attempting ALERT in state %d\n",
i->vname, i->state);
return -1;
}
ALERT_REQ_HEADER(&CMSG, capi_ApplID, get_capi_MessageNumber(), 0);
ALERT_REQ_PLCI(&CMSG) = i->PLCI;
if (_capi_put_cmsg(&CMSG) != 0) {
return -1;
}
i->state = CAPI_STATE_ALERTING;
ast_setstate(c, AST_STATE_RING);
return 0;
}
/*
* cleanup the interface
*/
static void interface_cleanup(struct capi_pvt *i)
{
if (!i)
return;
cc_verbose(2, 1, VERBOSE_PREFIX_2 "%s: Interface cleanup PLCI=%#x\n",
i->vname, i->PLCI);
if (i->readerfd != -1) {
close(i->readerfd);
i->readerfd = -1;
}
if (i->writerfd != -1) {
close(i->writerfd);
i->writerfd = -1;
}
i->isdnstate = 0;
i->cause = 0;
i->FaxState &= ~CAPI_FAX_STATE_MASK;
i->PLCI = 0;
i->MessageNumber = 0;
i->NCCI = 0;
i->onholdPLCI = 0;
i->doEC = i->doEC_global;
memset(i->cid, 0, sizeof(i->cid));
memset(i->dnid, 0, sizeof(i->dnid));
i->cid_ton = 0;
i->rtpcodec = 0;
if (i->rtp) {
ast_rtp_destroy(i->rtp);
i->rtp = NULL;
}
interface_cleanup_qsig(i);
i->peer = NULL;
i->owner = NULL;
return;
}
/*
* disconnect b3 and wait for confirmation
*/
static void cc_disconnect_b3(struct capi_pvt *i, int wait)
{
_cmsg CMSG;
struct timespec abstime;
if (!(i->isdnstate & (CAPI_ISDN_STATE_B3_UP | CAPI_ISDN_STATE_B3_PEND)))
return;
DISCONNECT_B3_REQ_HEADER(&CMSG, capi_ApplID, get_capi_MessageNumber(), 0);
DISCONNECT_B3_REQ_NCCI(&CMSG) = i->NCCI;
if (wait) {
cc_mutex_lock(&i->lock);
_capi_put_cmsg_wait_conf(i, &CMSG);
} else {
_capi_put_cmsg(&CMSG);
return;
}
/* wait for the B3 layer to go down */
if ((i->isdnstate & (CAPI_ISDN_STATE_B3_UP | CAPI_ISDN_STATE_B3_PEND))) {
i->waitevent = CAPI_WAITEVENT_B3_DOWN;
abstime.tv_sec = time(NULL) + 2;
abstime.tv_nsec = 0;
cc_verbose(4, 1, "%s: wait for b3 down.\n",
i->vname);
if (ast_cond_timedwait(&i->event_trigger, &i->lock, &abstime) != 0) {
cc_log(LOG_WARNING, "%s: timed out waiting for b3 down.\n",
i->vname);
} else {
cc_verbose(4, 1, "%s: cond signal received for b3 down.\n",
i->vname);
}
}
cc_mutex_unlock(&i->lock);
if ((i->isdnstate & CAPI_ISDN_STATE_B3_UP)) {
cc_log(LOG_ERROR, "capi disconnect b3: didn't disconnect NCCI=0x%08x\n",
i->NCCI);
}
return;
}
/*
* send CONNECT_B3_REQ
*/
void cc_start_b3(struct capi_pvt *i)
{
_cmsg CMSG;
if (!(i->isdnstate & (CAPI_ISDN_STATE_B3_UP | CAPI_ISDN_STATE_B3_PEND))) {
i->isdnstate |= CAPI_ISDN_STATE_B3_PEND;
CONNECT_B3_REQ_HEADER(&CMSG, capi_ApplID, get_capi_MessageNumber(), 0);
CONNECT_B3_REQ_PLCI(&CMSG) = i->PLCI;
CONNECT_B3_REQ_NCPI(&CMSG) = capi_rtp_ncpi(i);
_capi_put_cmsg(&CMSG);
cc_verbose(4, 1, VERBOSE_PREFIX_3 "%s: sent CONNECT_B3_REQ PLCI=%#x\n",
i->vname, i->PLCI);
}
}
/*
* start early B3
*/
static void start_early_b3(struct capi_pvt *i)
{
if (i->doB3 != CAPI_B3_DONT) {
/* we do early B3 Connect */
cc_start_b3(i);
}
}
/*
* signal 'progress' to PBX
*/
static void send_progress(struct capi_pvt *i)
{
struct ast_frame fr = { AST_FRAME_CONTROL, };
start_early_b3(i);
if (!(i->isdnstate & CAPI_ISDN_STATE_PROGRESS)) {
i->isdnstate |= CAPI_ISDN_STATE_PROGRESS;
fr.subclass = AST_CONTROL_PROGRESS;
local_queue_frame(i, &fr);
}
return;
}
/*
* hangup a line (CAPI messages)
* (this must be called with i->lock held)
*/
static void capi_activehangup(struct ast_channel *c, int state)
{
struct capi_pvt *i = CC_CHANNEL_PVT(c);
_cmsg CMSG;
const char *cause;
i->cause = c->hangupcause;
if ((cause = pbx_builtin_getvar_helper(c, "PRI_CAUSE"))) {
i->cause = atoi(cause);
}
if ((i->isdnstate & CAPI_ISDN_STATE_ECT)) {
cc_verbose(3, 1, VERBOSE_PREFIX_3 "%s: activehangup ECT call\n",
i->vname);
/* we do nothing, just wait for DISCONNECT_IND */
return;
}
cc_verbose(2, 1, VERBOSE_PREFIX_3 "%s: activehangingup (cause=%d) for PLCI=%#x\n",
i->vname, i->cause, i->PLCI);
if ((state == CAPI_STATE_ALERTING) ||
(state == CAPI_STATE_DID) || (state == CAPI_STATE_INCALL)) {
CONNECT_RESP_HEADER(&CMSG, capi_ApplID, i->MessageNumber, 0);
CONNECT_RESP_PLCI(&CMSG) = i->PLCI;
CONNECT_RESP_REJECT(&CMSG) = (i->cause) ? (0x3480 | (i->cause & 0x7f)) : 2;
_capi_put_cmsg(&CMSG);
return;
}
/* active disconnect */
if ((i->isdnstate & CAPI_ISDN_STATE_B3_UP)) {
cc_disconnect_b3(i, 0);
return;
}
if ((state == CAPI_STATE_CONNECTED) || (state == CAPI_STATE_CONNECTPENDING) ||
(state == CAPI_STATE_ANSWERING) || (state == CAPI_STATE_ONHOLD)) {
if (i->PLCI == 0) {
/* CONNECT_CONF not received yet? */
capi_wait_conf(i, CAPI_CONNECT_CONF);
}
DISCONNECT_REQ_HEADER(&CMSG, capi_ApplID, get_capi_MessageNumber(), 0);
DISCONNECT_REQ_PLCI(&CMSG) = i->PLCI;
_capi_put_cmsg_wait_conf(i, &CMSG);
}
return;
}
/*
* PBX tells us to hangup a line
*/
static int pbx_capi_hangup(struct ast_channel *c)
{
struct capi_pvt *i = CC_CHANNEL_PVT(c);
int cleanup = 0;
int state;
/*
* hmm....ok...this is called to free the capi interface (passive disconnect)
* or to bring down the channel (active disconnect)
*/
if (i == NULL) {
cc_log(LOG_ERROR, "channel has no interface!\n");
return -1;
}
cc_mutex_lock(&i->lock);
state = i->state;
cc_verbose(3, 0, VERBOSE_PREFIX_2 "%s: CAPI Hangingup for PLCI=%#x in state %d\n",
i->vname, i->PLCI, state);
/* are we down, yet? */
if (state != CAPI_STATE_DISCONNECTED) {
/* no */
i->state = CAPI_STATE_DISCONNECTING;
} else {
cleanup = 1;
}
if ((i->doDTMF > 0) && (i->vad != NULL)) {
ast_dsp_free(i->vad);
i->vad = NULL;
}
if (cleanup) {
/* disconnect already done, so cleanup */
interface_cleanup(i);
} else {
/* not disconnected yet, we must actively do it */
capi_activehangup(c, state);
}
cc_mutex_unlock(&i->lock);
CC_CHANNEL_PVT(c) = NULL;
ast_setstate(c, AST_STATE_DOWN);
#ifdef CC_AST_HAS_VERSION_1_4
ast_atomic_fetchadd_int(&usecnt, -1);
#else
cc_mutex_lock(&usecnt_lock);
usecnt--;
cc_mutex_unlock(&usecnt_lock);
#endif
ast_update_use_count();
return 0;
}
/*
* PBX tells us to make a call
*/
static int pbx_capi_call(struct ast_channel *c, char *idest, int timeout)
{
struct capi_pvt *i = CC_CHANNEL_PVT(c);
char *dest, *interface, *param, *ocid;
char buffer[AST_MAX_EXTENSION];
char called[AST_MAX_EXTENSION], calling[AST_MAX_EXTENSION];
char callerid[AST_MAX_EXTENSION];
char bchaninfo[3];
int CLIR;
int callernplan = 0;
int use_defaultcid = 0;
const char *ton, *p;
char *osa = NULL;
char *dsa = NULL;
char callingsubaddress[AST_MAX_EXTENSION];
char calledsubaddress[AST_MAX_EXTENSION];
int doqsig;
_cmsg CMSG;
MESSAGE_EXCHANGE_ERROR error;
cc_copy_string(buffer, idest, sizeof(buffer));
parse_dialstring(buffer, &interface, &dest, &param, &ocid);
/* init param settings */
i->doB3 = CAPI_B3_DONT;
i->doOverlap = 0;
memset(i->overlapdigits, 0, sizeof(i->overlapdigits));
doqsig = i->qsigfeat;
/* parse the parameters */
while ((param) && (*param)) {
switch (*param) {
case 'b': /* always B3 */
if (i->doB3 != CAPI_B3_DONT)
cc_log(LOG_WARNING, "B3 already set in '%s'\n", idest);
i->doB3 = CAPI_B3_ALWAYS;
break;
case 'B': /* only do B3 on successfull calls */
if (i->doB3 != CAPI_B3_DONT)
cc_log(LOG_WARNING, "B3 already set in '%s'\n", idest);
i->doB3 = CAPI_B3_ON_SUCCESS;
break;
case 'o': /* overlap sending of digits */
if (i->doOverlap)
cc_log(LOG_WARNING, "Overlap already set in '%s'\n", idest);
i->doOverlap = 1;
break;
case 'd': /* use default cid */
if (use_defaultcid)
cc_log(LOG_WARNING, "Default CID already set in '%s'\n", idest);
use_defaultcid = 1;
break;
case 'q': /* disable QSIG */
cc_verbose(4, 0, VERBOSE_PREFIX_4 "%s: QSIG extensions for this call disabled\n",
i->vname);
doqsig = 0;
break;
default:
cc_log(LOG_WARNING, "Unknown parameter '%c' in '%s', ignoring.\n",
*param, idest);
}
param++;
}
if (((!dest) || (!dest[0])) && (i->doB3 != CAPI_B3_ALWAYS)) {
cc_log(LOG_ERROR, "No destination or dialtone requested in '%s'\n", idest);
return -1;
}
CLIR = c->cid.cid_pres;
callernplan = c->cid.cid_ton & 0x7f;
if ((ton = pbx_builtin_getvar_helper(c, "CALLERTON"))) {
callernplan = atoi(ton) & 0x7f;
}
cc_verbose(1, 1, VERBOSE_PREFIX_2 "%s: Call %s %s%s (pres=0x%02x, ton=0x%02x)\n",
i->vname, c->name, i->doB3 ? "with B3 ":" ",
i->doOverlap ? "overlap":"", CLIR, callernplan);
if ((p = pbx_builtin_getvar_helper(c, "CALLINGSUBADDRESS"))) {
callingsubaddress[0] = strlen(p) + 1;
callingsubaddress[1] = 0x80;
strncpy(&callingsubaddress[2], p, sizeof(callingsubaddress) - 3);
osa = callingsubaddress;
}
if ((p = pbx_builtin_getvar_helper(c, "CALLEDSUBADDRESS"))) {
calledsubaddress[0] = strlen(p) + 1;
calledsubaddress[1] = 0x80;
strncpy(&calledsubaddress[2], p, sizeof(calledsubaddress) - 3);
dsa = calledsubaddress;
}
i->peer = cc_get_peer_link_id(pbx_builtin_getvar_helper(c, "CAPIPEERLINKID"));
i->MessageNumber = get_capi_MessageNumber();
CONNECT_REQ_HEADER(&CMSG, capi_ApplID, i->MessageNumber, i->controller);
CONNECT_REQ_CONTROLLER(&CMSG) = i->controller;
CONNECT_REQ_CIPVALUE(&CMSG) = tcap2cip(c->transfercapability);
if (tcap_is_digital(c->transfercapability)) {
i->bproto = CC_BPROTO_TRANSPARENT;
cc_verbose(4, 0, VERBOSE_PREFIX_2 "%s: is digital call, set proto to TRANSPARENT\n",
i->vname);
}
if ((i->doOverlap) && (strlen(dest))) {
cc_copy_string(i->overlapdigits, dest, sizeof(i->overlapdigits));
called[0] = 1;
} else {
i->doOverlap = 0;
called[0] = strlen(dest) + 1;
}
called[1] = 0x80;
strncpy(&called[2], dest, sizeof(called) - 3);
CONNECT_REQ_CALLEDPARTYNUMBER(&CMSG) = (_cstruct)called;
CONNECT_REQ_CALLEDPARTYSUBADDRESS(&CMSG) = (_cstruct)dsa;
if (c->cid.cid_num) {
cc_copy_string(callerid, c->cid.cid_num, sizeof(callerid));
} else {
memset(callerid, 0, sizeof(callerid));
}
if (use_defaultcid) {
cc_copy_string(callerid, i->defaultcid, sizeof(callerid));
} else if (ocid) {
cc_copy_string(callerid, ocid, sizeof(callerid));
}
cc_copy_string(i->cid, callerid, sizeof(i->cid));
calling[0] = strlen(callerid) + 2;
calling[1] = callernplan;
calling[2] = 0x80 | (CLIR & 0x63);
strncpy(&calling[3], callerid, sizeof(calling) - 4);
CONNECT_REQ_CALLINGPARTYNUMBER(&CMSG) = (_cstruct)calling;
CONNECT_REQ_CALLINGPARTYSUBADDRESS(&CMSG) = (_cstruct)osa;
CONNECT_REQ_B1PROTOCOL(&CMSG) = b_protocol_table[i->bproto].b1protocol;
CONNECT_REQ_B2PROTOCOL(&CMSG) = b_protocol_table[i->bproto].b2protocol;
CONNECT_REQ_B3PROTOCOL(&CMSG) = b_protocol_table[i->bproto].b3protocol;
CONNECT_REQ_B1CONFIGURATION(&CMSG) = b_protocol_table[i->bproto].b1configuration;
CONNECT_REQ_B2CONFIGURATION(&CMSG) = b_protocol_table[i->bproto].b2configuration;
CONNECT_REQ_B3CONFIGURATION(&CMSG) = b_protocol_table[i->bproto].b3configuration;
bchaninfo[0] = 2;
bchaninfo[1] = 0x0;
bchaninfo[2] = 0x0;
CONNECT_REQ_BCHANNELINFORMATION(&CMSG) = (_cstruct)bchaninfo; /* 0 */
if (doqsig) {
unsigned char *facilityarray = alloca(CAPI_MAX_FACILITYDATAARRAY_SIZE);
cc_qsig_add_call_setup_data(facilityarray, i, c);
CONNECT_REQ_FACILITYDATAARRAY(&CMSG) = facilityarray;
}
cc_mutex_lock(&i->lock);
i->outgoing = 1;
i->isdnstate |= CAPI_ISDN_STATE_PBX;
i->state = CAPI_STATE_CONNECTPENDING;
ast_setstate(c, AST_STATE_DIALING);
if ((error = _capi_put_cmsg(&CMSG))) {
i->state = CAPI_STATE_DISCONNECTED;
ast_setstate(c, AST_STATE_RESERVED);
cc_mutex_unlock(&i->lock);
return error;
}
cc_mutex_unlock(&i->lock);
/* now we shall return .... the rest has to be done by handle_msg */
return 0;
}
/*
* answer a capi call
*/
static int capi_send_answer(struct ast_channel *c, _cstruct b3conf)
{
struct capi_pvt *i = CC_CHANNEL_PVT(c);
_cmsg CMSG;
char buf[CAPI_MAX_STRING];
const char *dnid;
const char *connectednumber;
if ((i->isdnmode == CAPI_ISDNMODE_DID) &&
((strlen(i->incomingmsn) < strlen(i->dnid)) &&
(strcmp(i->incomingmsn, "*")))) {
dnid = i->dnid + strlen(i->incomingmsn);
} else {
dnid = i->dnid;
}
if ((connectednumber = pbx_builtin_getvar_helper(c, "CONNECTEDNUMBER"))) {
dnid = connectednumber;
}
CONNECT_RESP_HEADER(&CMSG, capi_ApplID, i->MessageNumber, 0);
CONNECT_RESP_PLCI(&CMSG) = i->PLCI;
CONNECT_RESP_REJECT(&CMSG) = 0;
if (strlen(dnid)) {
buf[0] = strlen(dnid) + 2;
buf[1] = 0x00;
buf[2] = 0x80;
strncpy(&buf[3], dnid, sizeof(buf) - 4);
CONNECT_RESP_CONNECTEDNUMBER(&CMSG) = (_cstruct)buf;
}
CONNECT_RESP_B1PROTOCOL(&CMSG) = b_protocol_table[i->bproto].b1protocol;
CONNECT_RESP_B2PROTOCOL(&CMSG) = b_protocol_table[i->bproto].b2protocol;
CONNECT_RESP_B3PROTOCOL(&CMSG) = b_protocol_table[i->bproto].b3protocol;
CONNECT_RESP_B1CONFIGURATION(&CMSG) = b_protocol_table[i->bproto].b1configuration;
CONNECT_RESP_B2CONFIGURATION(&CMSG) = b_protocol_table[i->bproto].b2configuration;
if (!b3conf)
b3conf = b_protocol_table[i->bproto].b3configuration;
CONNECT_RESP_B3CONFIGURATION(&CMSG) = b3conf;
#ifndef CC_HAVE_NO_GLOBALCONFIGURATION
CONNECT_RESP_GLOBALCONFIGURATION(&CMSG) = capi_set_global_configuration(i);
#endif
cc_verbose(3, 0, VERBOSE_PREFIX_2 "%s: Answering for %s\n",
i->vname, dnid);
if (_capi_put_cmsg(&CMSG) != 0) {
return -1;
}
i->state = CAPI_STATE_ANSWERING;
i->doB3 = CAPI_B3_DONT;
i->outgoing = 0;
return 0;
}
/*
* PBX tells us to answer a call
*/
static int pbx_capi_answer(struct ast_channel *c)
{
struct capi_pvt *i = CC_CHANNEL_PVT(c);
int ret;
i->bproto = CC_BPROTO_TRANSPARENT;
if (i->rtp) {
if (!tcap_is_digital(c->transfercapability))
i->bproto = CC_BPROTO_RTP;
}
ret = capi_send_answer(c, NULL);
return ret;
}
/*
* read for a channel
*/
static struct ast_frame *pbx_capi_read(struct ast_channel *c)
{
struct capi_pvt *i = CC_CHANNEL_PVT(c);
struct ast_frame *f;
int readsize;
if (i == NULL) {
cc_log(LOG_ERROR, "channel has no interface\n");
return NULL;
}
if (i->readerfd == -1) {
cc_log(LOG_ERROR, "no readerfd\n");
return NULL;
}
f = &i->f;
f->frametype = AST_FRAME_NULL;
f->subclass = 0;
readsize = read(i->readerfd, f, sizeof(struct ast_frame));
if (readsize != sizeof(struct ast_frame)) {
cc_log(LOG_ERROR, "did not read a whole frame\n");
}
f->mallocd = 0;
f->data = NULL;
if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
return NULL;
}
if ((f->frametype == AST_FRAME_VOICE) && (f->datalen > 0)) {
if (f->datalen > sizeof(i->frame_data)) {
cc_log(LOG_ERROR, "f.datalen(%d) greater than space of frame_data(%d)\n",
f->datalen, sizeof(i->frame_data));
f->datalen = sizeof(i->frame_data);
}
readsize = read(i->readerfd, i->frame_data + AST_FRIENDLY_OFFSET, f->datalen);
if (readsize != f->datalen) {
cc_log(LOG_ERROR, "did not read whole frame data\n");
}
f->data = i->frame_data + AST_FRIENDLY_OFFSET;
if ((i->doDTMF > 0) && (i->vad != NULL) ) {
f = ast_dsp_process(c, i->vad, f);
}
}
return f;
}
/*
* PBX tells us to write for a channel
*/
static int pbx_capi_write(struct ast_channel *c, struct ast_frame *f)
{
struct capi_pvt *i = CC_CHANNEL_PVT(c);
MESSAGE_EXCHANGE_ERROR error;
_cmsg CMSG;
int j = 0;
unsigned char *buf;
struct ast_frame *fsmooth;
int txavg=0;
int ret = 0;
if (!i) {
cc_log(LOG_ERROR, "channel has no interface\n");
return -1;
}
if ((!(i->isdnstate & CAPI_ISDN_STATE_B3_UP)) || (!i->NCCI) ||
((i->isdnstate & (CAPI_ISDN_STATE_B3_CHANGE | CAPI_ISDN_STATE_LI)))) {
return 0;
}
if ((!(i->ntmode)) && (i->state != CAPI_STATE_CONNECTED)) {
return 0;
}
if (f->frametype == AST_FRAME_NULL) {
return 0;
}
if (f->frametype == AST_FRAME_DTMF) {
cc_log(LOG_ERROR, "dtmf frame should be written\n");
return 0;
}
if (f->frametype != AST_FRAME_VOICE) {
cc_log(LOG_ERROR,"not a voice frame\n");
return 0;
}
if (i->FaxState & CAPI_FAX_STATE_ACTIVE) {
cc_verbose(3, 1, VERBOSE_PREFIX_2 "%s: write on fax activity?\n",
i->vname);
return 0;
}
if ((!f->data) || (!f->datalen)) {
cc_log(LOG_DEBUG, "No data for FRAME_VOICE %s\n", c->name);
return 0;
}
if (i->isdnstate & CAPI_ISDN_STATE_RTP) {
if ((!(f->subclass & i->codec)) &&
(f->subclass != capi_capability)) {
cc_log(LOG_ERROR, "don't know how to write subclass %s(%d)\n",
ast_getformatname(f->subclass), f->subclass);
return 0;
}
return capi_write_rtp(c, f);
}
if (i->B3count >= CAPI_MAX_B3_BLOCKS) {
cc_verbose(3, 1, VERBOSE_PREFIX_4 "%s: B3count is full, dropping packet.\n",
i->vname);
return 0;
}
if ((!i->smoother) || (ast_smoother_feed(i->smoother, f) != 0)) {
cc_log(LOG_ERROR, "%s: failed to fill smoother\n", i->vname);
return 0;
}
for (fsmooth = ast_smoother_read(i->smoother);
fsmooth != NULL;
fsmooth = ast_smoother_read(i->smoother)) {
DATA_B3_REQ_HEADER(&CMSG, capi_ApplID, get_capi_MessageNumber(), 0);
DATA_B3_REQ_NCCI(&CMSG) = i->NCCI;
DATA_B3_REQ_DATALENGTH(&CMSG) = fsmooth->datalen;
DATA_B3_REQ_FLAGS(&CMSG) = 0;
DATA_B3_REQ_DATAHANDLE(&CMSG) = i->send_buffer_handle;
buf = &(i->send_buffer[(i->send_buffer_handle % CAPI_MAX_B3_BLOCKS) *
(CAPI_MAX_B3_BLOCK_SIZE + AST_FRIENDLY_OFFSET)]);
DATA_B3_REQ_DATA(&CMSG) = buf;
i->send_buffer_handle++;
if ((i->doES == 1) && (!tcap_is_digital(c->transfercapability))) {
for (j = 0; j < fsmooth->datalen; j++) {
buf[j] = reversebits[ ((unsigned char *)fsmooth->data)[j] ];
if (capi_capability == AST_FORMAT_ULAW) {
txavg += abs( capiULAW2INT[reversebits[ ((unsigned char*)fsmooth->data)[j]]] );
} else {
txavg += abs( capiALAW2INT[reversebits[ ((unsigned char*)fsmooth->data)[j]]] );
}
}
txavg = txavg / j;
for(j = 0; j < ECHO_TX_COUNT - 1; j++) {
i->txavg[j] = i->txavg[j+1];
}
i->txavg[ECHO_TX_COUNT - 1] = txavg;
} else {
if ((i->txgain == 1.0) || (tcap_is_digital(c->transfercapability))) {
for (j = 0; j < fsmooth->datalen; j++) {
buf[j] = reversebits[((unsigned char *)fsmooth->data)[j]];
}
} else {
for (j = 0; j < fsmooth->datalen; j++) {
buf[j] = i->g.txgains[reversebits[((unsigned char *)fsmooth->data)[j]]];
}
}
}
error = 1;
if (i->B3q > 0) {
error = _capi_put_cmsg(&CMSG);
} else {
cc_verbose(3, 1, VERBOSE_PREFIX_4 "%s: too much voice to send for NCCI=%#x\n",
i->vname, i->NCCI);
}
if (!error) {
cc_mutex_lock(&i->lock);
i->B3count++;
i->B3q -= fsmooth->datalen;
if (i->B3q < 0)
i->B3q = 0;
cc_mutex_unlock(&i->lock);
}
}
return ret;
}
/*
* new channel (masq)
*/
static int pbx_capi_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
{
struct capi_pvt *i = CC_CHANNEL_PVT(newchan);
cc_verbose(3, 1, VERBOSE_PREFIX_2 "%s: %s fixup now %s\n",
i->vname, oldchan->name, newchan->name);
cc_mutex_lock(&i->lock);
i->owner = newchan;
cc_mutex_unlock(&i->lock);
return 0;
}
/*
* activate (another B protocol)
*/
static void cc_select_b(struct capi_pvt *i, _cstruct b3conf)
{
_cmsg CMSG;
SELECT_B_PROTOCOL_REQ_HEADER(&CMSG, capi_ApplID, get_capi_MessageNumber(), 0);
SELECT_B_PROTOCOL_REQ_PLCI(&CMSG) = i->PLCI;
SELECT_B_PROTOCOL_REQ_B1PROTOCOL(&CMSG) = b_protocol_table[i->bproto].b1protocol;
SELECT_B_PROTOCOL_REQ_B2PROTOCOL(&CMSG) = b_protocol_table[i->bproto].b2protocol;
SELECT_B_PROTOCOL_REQ_B3PROTOCOL(&CMSG) = b_protocol_table[i->bproto].b3protocol;
SELECT_B_PROTOCOL_REQ_B1CONFIGURATION(&CMSG) = b_protocol_table[i->bproto].b1configuration;
SELECT_B_PROTOCOL_REQ_B2CONFIGURATION(&CMSG) = b_protocol_table[i->bproto].b2configuration;
if (!b3conf)
b3conf = b_protocol_table[i->bproto].b3configuration;
SELECT_B_PROTOCOL_REQ_B3CONFIGURATION(&CMSG) = b3conf;
#ifndef CC_HAVE_NO_GLOBALCONFIGURATION
SELECT_B_PROTOCOL_REQ_GLOBALCONFIGURATION(&CMSG) = capi_set_global_configuration(i);
#endif
_capi_put_cmsg(&CMSG);
}
/*
* do line initerconnect
*/
static int line_interconnect(struct capi_pvt *i0, struct capi_pvt *i1, int start)
{
_cmsg CMSG;
char buf[20];
if ((i0->isdnstate & CAPI_ISDN_STATE_DISCONNECT) ||
(i1->isdnstate & CAPI_ISDN_STATE_DISCONNECT))
return -1;
if ((!(i0->isdnstate & CAPI_ISDN_STATE_B3_UP)) ||
(!(i1->isdnstate & CAPI_ISDN_STATE_B3_UP))) {
cc_verbose(3, 1, VERBOSE_PREFIX_2
"%s:%s line interconnect aborted, at least "
"one channel is not connected.\n",
i0->vname, i1->vname);
return -1;
}
FACILITY_REQ_HEADER(&CMSG, capi_ApplID, get_capi_MessageNumber(), 0);
FACILITY_REQ_PLCI(&CMSG) = i0->PLCI;
FACILITY_REQ_FACILITYSELECTOR(&CMSG) = FACILITYSELECTOR_LINE_INTERCONNECT;
memset(buf, 0, sizeof(buf));
if (start) {
/* connect */
buf[0] = 17; /* msg size */
write_capi_word(&buf[1], 0x0001);
buf[3] = 14; /* struct size LI Request Parameter */
write_capi_dword(&buf[4], 0x00000000); /* Data Path */
buf[8] = 9; /* struct size */
buf[9] = 8; /* struct size LI Request Connect Participant */
write_capi_dword(&buf[10], i1->PLCI);
write_capi_dword(&buf[14], 0x00000003); /* Data Path Participant */
} else {
/* disconnect */
buf[0] = 7; /* msg size */
write_capi_word(&buf[1], 0x0002);
buf[3] = 4; /* struct size */
write_capi_dword(&buf[4], i1->PLCI);
}
FACILITY_REQ_FACILITYREQUESTPARAMETER(&CMSG) = (_cstruct)buf;
_capi_put_cmsg(&CMSG);
if (start) {
i0->isdnstate |= CAPI_ISDN_STATE_LI;
i1->isdnstate |= CAPI_ISDN_STATE_LI;
} else {
i0->isdnstate &= ~CAPI_ISDN_STATE_LI;
i1->isdnstate &= ~CAPI_ISDN_STATE_LI;
}
return 0;
}
#if 0
/*
* disconnect b3 and bring it up with another protocol
*/
static void cc_switch_b_protocol(struct capi_pvt *i)
{
int waitcount = 200;
cc_disconnect_b3(i, 1);
i->isdnstate |= CAPI_ISDN_STATE_B3_CHANGE;
cc_select_b(i, NULL);
if (i->outgoing) {
/* on outgoing call we must do the connect-b3 request */
cc_start_b3(i);
}
/* wait for the B3 layer to come up */
while ((waitcount > 0) &&
(!(i->isdnstate & CAPI_ISDN_STATE_B3_UP))) {
usleep(10000);
waitcount--;
}
if (!(i->isdnstate & CAPI_ISDN_STATE_B3_UP)) {
cc_log(LOG_ERROR, "capi switch b3: no b3 up\n");
}
}
/*
* set the b3 protocol to transparent
*/
static int cc_set_transparent(struct capi_pvt *i)
{
if (i->bproto != CC_BPROTO_RTP) {
/* nothing to do */
return 0;
}
i->bproto = CC_BPROTO_TRANSPARENT;
cc_switch_b_protocol(i);
return 1;
}
/*
* set the b3 protocol to RTP (if wanted)
*/
static void cc_unset_transparent(struct capi_pvt *i, int rtpwanted)
{
if ((!rtpwanted) ||
(i->isdnstate & CAPI_ISDN_STATE_DISCONNECT))
return;
i->bproto = CC_BPROTO_RTP;
cc_switch_b_protocol(i);
return;
}
#endif
/*
* native bridging / line interconnect
*/
static CC_BRIDGE_RETURN pbx_capi_bridge(struct ast_channel *c0,
struct ast_channel *c1,
int flags, struct ast_frame **fo,
struct ast_channel **rc,
int timeoutms)
{
struct capi_pvt *i0 = CC_CHANNEL_PVT(c0);
struct capi_pvt *i1 = CC_CHANNEL_PVT(c1);
CC_BRIDGE_RETURN ret = AST_BRIDGE_COMPLETE;
cc_verbose(3, 1, VERBOSE_PREFIX_2 "%s:%s Requested native bridge for %s and %s\n",
i0->vname, i1->vname, c0->name, c1->name);
if ((!i0->bridge) || (!i1->bridge))
return AST_BRIDGE_FAILED_NOWARN;
if ((!capi_controllers[i0->controller]->lineinterconnect) ||
(!capi_controllers[i1->controller]->lineinterconnect)) {
return AST_BRIDGE_FAILED_NOWARN;
}
if ((i0->isdnstate & CAPI_ISDN_STATE_ECT) ||
(i0->isdnstate & CAPI_ISDN_STATE_ECT)) {
return AST_BRIDGE_FAILED;
}
capi_wait_for_b3_up(i0);
capi_wait_for_b3_up(i1);
if (!(flags & AST_BRIDGE_DTMF_CHANNEL_0))
capi_detect_dtmf(i0->owner, 0);
if (!(flags & AST_BRIDGE_DTMF_CHANNEL_1))
capi_detect_dtmf(i1->owner, 0);
capi_echo_canceller(i0->owner, EC_FUNCTION_DISABLE);
capi_echo_canceller(i1->owner, EC_FUNCTION_DISABLE);
if (line_interconnect(i0, i1, 1)) {
ret = AST_BRIDGE_FAILED;
goto return_from_bridge;
}
for (;;) {
struct ast_channel *c0_priority[2] = {c0, c1};
struct ast_channel *c1_priority[2] = {c1, c0};
int priority = 0;
struct ast_frame *f;
struct ast_channel *who;
who = ast_waitfor_n(priority ? c0_priority : c1_priority, 2, &timeoutms);
if (!who) {
if (!timeoutms) {
ret = AST_BRIDGE_RETRY;
break;
}
continue;
}
f = ast_read(who);
if (!f || (f->frametype == AST_FRAME_CONTROL)
|| (f->frametype == AST_FRAME_DTMF)) {
*fo = f;
*rc = who;
ret = AST_BRIDGE_COMPLETE;
break;
}
if (who == c0) {
ast_write(c1, f);
} else {
ast_write(c0, f);
}
ast_frfree(f);
/* Swap who gets priority */
priority = !priority;
}
line_interconnect(i0, i1, 0);
return_from_bridge:
if (!(flags & AST_BRIDGE_DTMF_CHANNEL_0))
capi_detect_dtmf(i0->owner, 1);
if (!(flags & AST_BRIDGE_DTMF_CHANNEL_1))
capi_detect_dtmf(i1->owner, 1);
capi_echo_canceller(i0->owner, EC_FUNCTION_ENABLE);
capi_echo_canceller(i1->owner, EC_FUNCTION_ENABLE);
return ret;
}
/*
* a new channel is needed
*/
static struct ast_channel *capi_new(struct capi_pvt *i, int state)
{
struct ast_channel *tmp;
int fmt;
int fds[2];
int flags;
#ifdef CC_AST_HAS_EXT_CHAN_ALLOC
tmp = ast_channel_alloc(0, state, i->cid, NULL,
#ifdef CC_AST_HAS_EXT2_CHAN_ALLOC
i->accountcode, i->dnid, i->context, i->amaflags,
#endif
"CAPI/%s/%s-%x", i->vname, i->dnid, capi_counter++);
#else
tmp = ast_channel_alloc(0);
#endif
if (tmp == NULL) {
cc_log(LOG_ERROR,"Unable to allocate channel!\n");
return(NULL);
}
#ifndef CC_AST_HAS_EXT_CHAN_ALLOC
#ifdef CC_AST_HAS_STRINGFIELD_IN_CHANNEL
ast_string_field_build(tmp, name, "CAPI/%s/%s-%x",
i->vname, i->dnid, capi_counter++);
#else
snprintf(tmp->name, sizeof(tmp->name) - 1, "CAPI/%s/%s-%x",
i->vname, i->dnid, capi_counter++);
#endif
#endif
#ifndef CC_AST_HAS_VERSION_1_4
tmp->type = channeltype;
#endif
if (pipe(fds) != 0) {
cc_log(LOG_ERROR, "%s: unable to create pipe.\n",
i->vname);
ast_channel_free(tmp);
return NULL;
}
i->readerfd = fds[0];
i->writerfd = fds[1];
flags = fcntl(i->readerfd, F_GETFL);
fcntl(i->readerfd, F_SETFL, flags | O_NONBLOCK);
flags = fcntl(i->writerfd, F_GETFL);
fcntl(i->writerfd, F_SETFL, flags | O_NONBLOCK);
tmp->fds[0] = i->readerfd;
if (i->smoother != NULL) {
ast_smoother_reset(i->smoother, CAPI_MAX_B3_BLOCK_SIZE);
}
i->state = CAPI_STATE_DISCONNECTED;
i->calledPartyIsISDN = 1;
i->doB3 = CAPI_B3_DONT;
i->doES = i->ES;
i->outgoing = 0;
i->onholdPLCI = 0;
i->doholdtype = i->holdtype;
i->B3q = 0;
i->B3count = 0;
memset(i->txavg, 0, ECHO_TX_COUNT);
if (i->doDTMF > 0) {
i->vad = ast_dsp_new();
ast_dsp_set_features(i->vad, DSP_FEATURE_DTMF_DETECT);
if (i->doDTMF > 1) {
ast_dsp_digitmode(i->vad, DSP_DIGITMODE_DTMF | DSP_DIGITMODE_RELAXDTMF);
}
}
CC_CHANNEL_PVT(tmp) = i;
tmp->callgroup = i->callgroup;
tmp->pickupgroup = i->pickupgroup;
tmp->nativeformats = capi_capability;
i->bproto = CC_BPROTO_TRANSPARENT;
if ((i->rtpcodec = (capi_controllers[i->controller]->rtpcodec & i->capability))) {
if (capi_alloc_rtp(i)) {
/* error on rtp alloc */
i->rtpcodec = 0;
} else {
/* start with rtp */
tmp->nativeformats = i->rtpcodec;
i->bproto = CC_BPROTO_RTP;
}
}
fmt = ast_best_codec(tmp->nativeformats);
i->codec = fmt;
tmp->readformat = fmt;
tmp->writeformat = fmt;
tmp->tech = &capi_tech;
tmp->rawreadformat = fmt;
tmp->rawwriteformat = fmt;
cc_verbose(3, 1, VERBOSE_PREFIX_2 "%s: setting format %s - %s%s\n",
i->vname, ast_getformatname(fmt),
ast_getformatname_multiple(alloca(80), 80,
tmp->nativeformats),
(i->rtp) ? " (RTP)" : "");
if (!ast_strlen_zero(i->cid)) {
if (tmp->cid.cid_num) {
free(tmp->cid.cid_num);
}
tmp->cid.cid_num = strdup(i->cid);
}
if (!ast_strlen_zero(i->dnid)) {
if (tmp->cid.cid_dnid) {
free(tmp->cid.cid_dnid);
}
tmp->cid.cid_dnid = strdup(i->dnid);
}
tmp->cid.cid_ton = i->cid_ton;
#ifndef CC_AST_HAS_EXT2_CHAN_ALLOC
if (i->amaflags)
tmp->amaflags = i->amaflags;
cc_copy_string(tmp->context, i->context, sizeof(tmp->context));
cc_copy_string(tmp->exten, i->dnid, sizeof(tmp->exten));
#ifdef CC_AST_HAS_STRINGFIELD_IN_CHANNEL
ast_string_field_set(tmp, accountcode, i->accountcode);
ast_string_field_set(tmp, language, i->language);
#else
cc_copy_string(tmp->accountcode, i->accountcode, sizeof(tmp->accountcode));
cc_copy_string(tmp->language, i->language, sizeof(tmp->language));
#endif
#endif
#ifdef CC_AST_HAS_STRINGFIELD_IN_CHANNEL
ast_string_field_set(tmp, language, i->language);
#else
cc_copy_string(tmp->language, i->language, sizeof(tmp->language));
#endif
i->owner = tmp;
#ifdef CC_AST_HAS_VERSION_1_4
ast_atomic_fetchadd_int(&usecnt, 1);
ast_jb_configure(tmp, &i->jbconf);
#else
cc_mutex_lock(&usecnt_lock);
usecnt++;
cc_mutex_unlock(&usecnt_lock);
#endif
ast_update_use_count();
#ifndef CC_AST_HAS_EXT_CHAN_ALLOC
ast_setstate(tmp, state);
#endif
return tmp;
}
/*
* PBX wants us to dial ...
*/
static struct ast_channel *
pbx_capi_request(const char *type, int format, void *data, int *cause)
{
struct capi_pvt *i;
struct ast_channel *tmp = NULL;
char *dest, *interface, *param, *ocid;
char buffer[CAPI_MAX_STRING];
ast_group_t capigroup = 0;
unsigned int controller = 0;
int notfound = 1;
cc_verbose(1, 1, VERBOSE_PREFIX_4 "data = %s format=%d\n", (char *)data, format);
cc_copy_string(buffer, (char *)data, sizeof(buffer));
parse_dialstring(buffer, &interface, &dest, &param, &ocid);
if ((!interface) || (!dest)) {
cc_log(LOG_ERROR, "Syntax error in dialstring. Read the docs!\n");
*cause = AST_CAUSE_INVALID_NUMBER_FORMAT;
return NULL;
}
if (interface[0] == 'g') {
capigroup = ast_get_group(interface + 1);
cc_verbose(1, 1, VERBOSE_PREFIX_4 "capi request group = %d\n",
(unsigned int)capigroup);
} else if (!strncmp(interface, "contr", 5)) {
controller = atoi(interface + 5);
cc_verbose(1, 1, VERBOSE_PREFIX_4 "capi request controller = %d\n",
controller);
} else {
cc_verbose(1, 1, VERBOSE_PREFIX_4 "capi request for interface '%s'\n",
interface);
}
cc_mutex_lock(&iflock);
for (i = iflist; (i && notfound); i = i->next) {
if ((i->owner) || (i->channeltype != CAPI_CHANNELTYPE_B)) {
/* if already in use or no real channel */
continue;
}
/* unused channel */
if (controller) {
/* DIAL(CAPI/contrX/...) */
if (i->controller != controller) {
/* keep on running! */
continue;
}
} else {
/* DIAL(CAPI/gX/...) */
if ((interface[0] == 'g') && (!(i->group & capigroup))) {
/* keep on running! */
continue;
}
/* DIAL(CAPI/<interface-name>/...) */
if ((interface[0] != 'g') && (strcmp(interface, i->name))) {
/* keep on running! */
continue;
}
}
/* when we come here, we found a free controller match */
cc_copy_string(i->dnid, dest, sizeof(i->dnid));
tmp = capi_new(i, AST_STATE_RESERVED);
if (!tmp) {
cc_log(LOG_ERROR, "cannot create new capi channel\n");
interface_cleanup(i);
}
i->PLCI = 0;
i->outgoing = 1; /* this is an outgoing line */
cc_mutex_unlock(&iflock);
return tmp;
}
cc_mutex_unlock(&iflock);
cc_verbose(2, 0, VERBOSE_PREFIX_3 "didn't find capi device for interface '%s'\n",
interface);
*cause = AST_CAUSE_REQUESTED_CHAN_UNAVAIL;
return NULL;
}
/*
* fill out fax conf struct
*/
static void setup_b3_fax_config(B3_PROTO_FAXG3 *b3conf, int fax_format, char *stationid, char *headline)
{
int len1;
int len2;
cc_verbose(3, 1, VERBOSE_PREFIX_3 "Setup fax b3conf fmt=%d, stationid='%s' headline='%s'\n",
fax_format, stationid, headline);
b3conf->resolution = 0;
b3conf->format = (unsigned short)fax_format;
len1 = strlen(stationid);
b3conf->Infos[0] = (unsigned char)len1;
strcpy((char *)&b3conf->Infos[1], stationid);
len2 = strlen(headline);
b3conf->Infos[len1 + 1] = (unsigned char)len2;
strcpy((char *)&b3conf->Infos[len1 + 2], headline);
b3conf->len = (unsigned char)(2 * sizeof(unsigned short) + len1 + len2 + 2);
return;
}
/*
* change b protocol to fax
*/
static void capi_change_bchan_fax(struct ast_channel *c, B3_PROTO_FAXG3 *b3conf)
{
struct capi_pvt *i = CC_CHANNEL_PVT(c);
i->isdnstate |= CAPI_ISDN_STATE_B3_SELECT;
cc_disconnect_b3(i, 1);
cc_select_b(i, (_cstruct)b3conf);
return;
}
/*
* capicommand 'receivefax'
*/
static int pbx_capi_receive_fax(struct ast_channel *c, char *data)
{
struct capi_pvt *i = CC_CHANNEL_PVT(c);
int res = 0;
int keepbadfax = 0;
char *filename, *stationid, *headline, *options;
B3_PROTO_FAXG3 b3conf;
char buffer[CAPI_MAX_STRING];
if (!data) { /* no data implies no filename or anything is present */
cc_log(LOG_WARNING, "capi receivefax requires a filename\n");
return -1;
}
filename = strsep(&data, "|");
stationid = strsep(&data, "|");
headline = strsep(&data, "|");
options = data;
if (!stationid)
stationid = emptyid;
if (!headline)
headline = emptyid;
if (!options)
options = emptyid;
cc_verbose(3, 1, VERBOSE_PREFIX_3 "capi receivefax: '%s' '%s' '%s' '%s'\n",
filename, stationid, headline, options);
/* parse the options */
while ((options) && (*options)) {
switch (*options) {
case 'k': /* keepbadfax */
cc_verbose(3, 1,
VERBOSE_PREFIX_3 "capi receivefax: if fax is bad, "
"file won't be deleted.\n");
keepbadfax = 1;
break;
default:
cc_log(LOG_WARNING, "Unknown option '%c' for receivefax.\n",
*options);
}
options++;
}
capi_wait_for_answered(i);
i->FaxState &= ~CAPI_FAX_STATE_CONN;
if ((i->fFax = fopen(filename, "wb")) == NULL) {
cc_log(LOG_WARNING, "can't create fax output file (%s)\n", strerror(errno));
return -1;
}
i->FaxState |= CAPI_FAX_STATE_ACTIVE;
setup_b3_fax_config(&b3conf, FAX_SFF_FORMAT, stationid, headline);
i->bproto = CC_BPROTO_FAXG3;
switch (i->state) {
case CAPI_STATE_ALERTING:
case CAPI_STATE_DID:
case CAPI_STATE_INCALL:
capi_send_answer(c, (_cstruct)&b3conf);
break;
case CAPI_STATE_CONNECTED:
capi_change_bchan_fax(c, &b3conf);
break;
default:
i->FaxState &= ~CAPI_FAX_STATE_ACTIVE;
cc_log(LOG_WARNING, "capi receive fax in wrong state (%d)\n",
i->state);
return -1;
}
while (capi_tell_fax_finish(i)) {
if (ast_safe_sleep_conditional(c, 1000, capi_tell_fax_finish, i) != 0) {
/* we got a hangup */
cc_verbose(3, 1,
VERBOSE_PREFIX_3 "capi receivefax: hangup.\n");
break;
}
}
cc_mutex_lock(&i->lock);
res = (i->FaxState & CAPI_FAX_STATE_ERROR) ? 1 : 0;
i->FaxState &= ~(CAPI_FAX_STATE_ACTIVE | CAPI_FAX_STATE_ERROR);
/* if the file has zero length */
if (ftell(i->fFax) == 0L) {
res = 1;
}
cc_verbose(2, 1, VERBOSE_PREFIX_3 "Closing fax file...\n");
fclose(i->fFax);
i->fFax = NULL;
cc_mutex_unlock(&i->lock);
if (res != 0) {
cc_verbose(2, 0,
VERBOSE_PREFIX_1 "capi receivefax: fax receive failed reason=0x%04x reasonB3=0x%04x\n",
i->reason, i->reasonb3);
if (!keepbadfax) {
cc_verbose(3, 1,
VERBOSE_PREFIX_3 "capi receivefax: removing fax file.\n");
unlink(filename);
}
} else {
cc_verbose(2, 0,
VERBOSE_PREFIX_1 "capi receivefax: fax receive successful.\n");
}
snprintf(buffer, CAPI_MAX_STRING-1, "%d", res);
pbx_builtin_setvar_helper(c, "FAXSTATUS", buffer);
return 0;
}
/*
* capicommand 'sendfax'
*/
static int pbx_capi_send_fax(struct ast_channel *c, char *data)
{
struct capi_pvt *i = CC_CHANNEL_PVT(c);
int res = 0;
char *filename, *stationid, *headline;
B3_PROTO_FAXG3 b3conf;
char buffer[CAPI_MAX_STRING];
if (!data) { /* no data implies no filename or anything is present */
cc_log(LOG_WARNING, "capi sendfax requires a filename\n");
return -1;
}
filename = strsep(&data, "|");
stationid = strsep(&data, "|");
headline = data;
if (!stationid)
stationid = emptyid;
if (!headline)
headline = emptyid;
cc_verbose(3, 1, VERBOSE_PREFIX_3 "capi sendfax: '%s' '%s' '%s'\n",
filename, stationid, headline);
capi_wait_for_answered(i);
if ((i->fFax = fopen(filename, "rb")) == NULL) {
cc_log(LOG_WARNING, "can't open fax file (%s)\n", strerror(errno));
return -1;
}
i->FaxState |= (CAPI_FAX_STATE_ACTIVE | CAPI_FAX_STATE_SENDMODE);
setup_b3_fax_config(&b3conf, FAX_SFF_FORMAT, stationid, headline);
i->bproto = CC_BPROTO_FAXG3;
switch (i->state) {
case CAPI_STATE_ALERTING:
case CAPI_STATE_DID:
case CAPI_STATE_INCALL:
capi_send_answer(c, (_cstruct)&b3conf);
break;
case CAPI_STATE_CONNECTED:
capi_change_bchan_fax(c, &b3conf);
break;
default:
i->FaxState &= ~CAPI_FAX_STATE_ACTIVE;
cc_log(LOG_WARNING, "capi send fax in wrong state (%d)\n",
i->state);
return -1;
}
while (capi_tell_fax_finish(i)) {
if (ast_safe_sleep_conditional(c, 1000, capi_tell_fax_finish, i) != 0) {
/* we got a hangup */
cc_verbose(3, 1,
VERBOSE_PREFIX_3 "capi sendfax: hangup.\n");
break;
}
}
cc_mutex_lock(&i->lock);
res = (i->FaxState & CAPI_FAX_STATE_ERROR) ? 1 : 0;
i->FaxState &= ~(CAPI_FAX_STATE_ACTIVE | CAPI_FAX_STATE_ERROR);
cc_verbose(2, 1, VERBOSE_PREFIX_3 "Closing fax file...\n");
fclose(i->fFax);
i->fFax = NULL;
cc_mutex_unlock(&i->lock);
if (res != 0) {
cc_verbose(2, 0,
VERBOSE_PREFIX_1 "capi sendfax: fax send failed reason=0x%04x reasonB3=0x%04x\n",
i->reason, i->reasonb3);
} else {
cc_verbose(2, 0,
VERBOSE_PREFIX_1 "capi sendfax: fax sent successful.\n");
}
snprintf(buffer, CAPI_MAX_STRING-1, "%d", res);
pbx_builtin_setvar_helper(c, "FAXSTATUS", buffer);
return 0;
}
/*
* Fax guard tone -- Handle and return NULL
*/
static void capi_handle_dtmf_fax(struct capi_pvt *i)
{
struct ast_channel *c = i->owner;
if (!c) {
cc_log(LOG_ERROR, "No channel!\n");
return;
}
if (i->FaxState & CAPI_FAX_STATE_HANDLED) {
cc_log(LOG_DEBUG, "Fax already handled\n");
return;
}
i->FaxState |= CAPI_FAX_STATE_HANDLED;
if (((i->outgoing == 1) && (!(i->FaxState & CAPI_FAX_DETECT_OUTGOING))) ||
((i->outgoing == 0) && (!(i->FaxState & CAPI_FAX_DETECT_INCOMING)))) {
cc_verbose(3, 0, VERBOSE_PREFIX_3 "%s: Fax detected, but not configured for redirection\n",
i->vname);
return;
}
if (!strcmp(c->exten, "fax")) {
cc_log(LOG_DEBUG, "Already in a fax extension, not redirecting\n");
return;
}
if (!ast_exists_extension(c, c->context, "fax", 1, i->cid)) {
cc_verbose(3, 0, VERBOSE_PREFIX_3 "Fax tone detected, but no fax extension for %s\n", c->name);
return;
}
cc_verbose(2, 0, VERBOSE_PREFIX_3 "%s: Redirecting %s to fax extension\n",
i->vname, c->name);
capi_channel_task(c, CAPI_CHANNEL_TASK_GOTOFAX);
return;
}
/*
* find the interface (pvt) the PLCI belongs to
*/
static struct capi_pvt *find_interface_by_plci(unsigned int plci)
{
struct capi_pvt *i;
if (plci == 0)
return NULL;
cc_mutex_lock(&iflock);
for (i = iflist; i; i = i->next) {
if (i->PLCI == plci)
break;
}
cc_mutex_unlock(&iflock);
return i;
}
/*
* find the interface (pvt) the messagenumber belongs to
*/
static struct capi_pvt *find_interface_by_msgnum(unsigned short msgnum)
{
struct capi_pvt *i;
if (msgnum == 0x0000)
return NULL;
cc_mutex_lock(&iflock);
for (i = iflist; i; i = i->next) {
if ((i->PLCI == 0) && (i->MessageNumber == msgnum))
break;
}
cc_mutex_unlock(&iflock);
return i;
}
/*
* see if did matches
*/
static int search_did(struct ast_channel *c)
{
/*
* Returns
* -1 = Failure
* 0 = Match
* 1 = possible match
*/
struct capi_pvt *i = CC_CHANNEL_PVT(c);
char *exten;
if (!strlen(i->dnid) && (i->immediate)) {
exten = "s";
cc_verbose(3, 1, VERBOSE_PREFIX_3 "%s: %s: %s matches in context %s for immediate\n",
i->vname, c->name, exten, c->context);
} else {
if (strlen(i->dnid) < strlen(i->incomingmsn))
return 0;
exten = i->dnid;
}
if (ast_exists_extension(NULL, c->context, exten, 1, i->cid)) {
c->priority = 1;
cc_copy_string(c->exten, exten, sizeof(c->exten));
cc_verbose(3, 1, VERBOSE_PREFIX_3 "%s: %s: %s matches in context %s\n",
i->vname, c->name, exten, c->context);
return 0;
}
if (ast_canmatch_extension(NULL, c->context, exten, 1, i->cid)) {
cc_verbose(3, 1, VERBOSE_PREFIX_3 "%s: %s: %s would possibly match in context %s\n",
i->vname, c->name, exten, c->context);
return 1;
}
return -1;
}
/*
* Progress Indicator
*/
static void handle_progress_indicator(_cmsg *CMSG, unsigned int PLCI, struct capi_pvt *i)
{
if (INFO_IND_INFOELEMENT(CMSG)[0] < 2) {
cc_verbose(3, 1, VERBOSE_PREFIX_4 "%s: Progress description missing\n",
i->vname);
return;
}
switch(INFO_IND_INFOELEMENT(CMSG)[2] & 0x7f) {
case 0x01:
cc_verbose(4, 1, VERBOSE_PREFIX_4 "%s: Not end-to-end ISDN\n",
i->vname);
break;
case 0x02:
cc_verbose(4, 1, VERBOSE_PREFIX_4 "%s: Destination is non ISDN\n",
i->vname);
i->calledPartyIsISDN = 0;
break;
case 0x03:
cc_verbose(4, 1, VERBOSE_PREFIX_4 "%s: Origination is non ISDN\n",
i->vname);
break;
case 0x04:
cc_verbose(4, 1, VERBOSE_PREFIX_4 "%s: Call returned to ISDN\n",
i->vname);
break;
case 0x05:
cc_verbose(4, 1, VERBOSE_PREFIX_4 "%s: Interworking occured\n",
i->vname);
break;
case 0x08:
cc_verbose(4, 1, VERBOSE_PREFIX_4 "%s: In-band information available\n",
i->vname);
break;
default:
cc_verbose(3, 1, VERBOSE_PREFIX_4 "%s: Unknown progress description %02x\n",
i->vname, INFO_IND_INFOELEMENT(CMSG)[2]);
}
send_progress(i);
return;
}
/*
* if the dnid matches, start the pbx
*/
static void start_pbx_on_match(struct capi_pvt *i, unsigned int PLCI, _cword MessageNumber)
{
struct ast_channel *c;
_cmsg CMSG2;
c = i->owner;
if ((i->isdnstate & CAPI_ISDN_STATE_PBX_DONT)) {
/* we already found non-match here */
return;
}
if ((i->isdnstate & CAPI_ISDN_STATE_PBX)) {
cc_verbose(3, 1, VERBOSE_PREFIX_2 "%s: pbx already started on channel %s\n",
i->vname, c->name);
return;
}
/* check for internal pickup extension first */
if (!strcmp(i->dnid, ast_pickup_ext())) {
i->isdnstate |= CAPI_ISDN_STATE_PBX;
cc_verbose(3, 1, VERBOSE_PREFIX_2 "%s: Pickup extension '%s' found.\n",
i->vname, i->dnid);
cc_copy_string(c->exten, i->dnid, sizeof(c->exten));
pbx_capi_alert(c);
capi_channel_task(c, CAPI_CHANNEL_TASK_PICKUP);
return;
}
switch(search_did(i->owner)) {
case 0: /* match */
i->isdnstate |= CAPI_ISDN_STATE_PBX;
ast_setstate(c, AST_STATE_RING);
if (ast_pbx_start(i->owner)) {
cc_log(LOG_ERROR, "%s: Unable to start pbx on channel!\n",
i->vname);
capi_channel_task(c, CAPI_CHANNEL_TASK_HANGUP);
} else {
cc_verbose(2, 1, VERBOSE_PREFIX_2 "Started pbx on channel %s\n",
c->name);
}
break;
case 1:
/* would possibly match */
if (i->isdnmode == CAPI_ISDNMODE_DID)
break;
/* fall through for MSN mode, because there won't be a longer msn */
case -1:
default:
/* doesn't match */
i->isdnstate |= CAPI_ISDN_STATE_PBX_DONT; /* don't try again */
cc_log(LOG_NOTICE, "%s: did not find exten for '%s', ignoring call.\n",
i->vname, i->dnid);
CONNECT_RESP_HEADER(&CMSG2, capi_ApplID, MessageNumber, 0);
CONNECT_RESP_PLCI(&CMSG2) = PLCI;
CONNECT_RESP_REJECT(&CMSG2) = 1; /* ignore */
_capi_put_cmsg(&CMSG2);
}
return;
}
/*
* Called Party Number via INFO_IND
*/
static void capidev_handle_did_digits(_cmsg *CMSG, unsigned int PLCI, unsigned int NCCI, struct capi_pvt *i)
{
char *did;
struct ast_frame fr = { AST_FRAME_NULL, };
int a;
if (!i->owner) {
cc_log(LOG_ERROR, "No channel for interface!\n");
return;
}
if (i->state != CAPI_STATE_DID) {
cc_verbose(4, 1, VERBOSE_PREFIX_4 "%s: INFO_IND DID digits not used in this state.\n",
i->vname);
return;
}
did = capi_number(INFO_IND_INFOELEMENT(CMSG), 1);
if ((!(i->isdnstate & CAPI_ISDN_STATE_DID)) &&
(strlen(i->dnid) && !strcasecmp(i->dnid, did))) {
did = NULL;
}
if ((did) && (strlen(i->dnid) < (sizeof(i->dnid) - 1)))
strcat(i->dnid, did);
i->isdnstate |= CAPI_ISDN_STATE_DID;
update_channel_name(i);
if (i->owner->pbx != NULL) {
/* we are already in pbx, so we send the digits as dtmf */
for (a = 0; a < strlen(did); a++) {
fr.frametype = AST_FRAME_DTMF;
fr.subclass = did[a];
local_queue_frame(i, &fr);
}
return;
}
start_pbx_on_match(i, PLCI, HEADER_MSGNUM(CMSG));
return;
}
/*
* send control according to cause code
*/
static void queue_cause_control(struct capi_pvt *i, int control)
{
struct ast_frame fr = { AST_FRAME_CONTROL, AST_CONTROL_HANGUP, };
if ((i->owner) && (control)) {
int cause = i->owner->hangupcause;
if (cause == AST_CAUSE_NORMAL_CIRCUIT_CONGESTION) {
fr.subclass = AST_CONTROL_CONGESTION;
} else if ((cause != AST_CAUSE_NO_USER_RESPONSE) &&
(cause != AST_CAUSE_NO_ANSWER)) {
/* not NOANSWER */
fr.subclass = AST_CONTROL_BUSY;
}
}
local_queue_frame(i, &fr);
return;
}
/*
* Disconnect via INFO_IND
*/
static void capidev_handle_info_disconnect(_cmsg *CMSG, unsigned int PLCI, unsigned int NCCI, struct capi_pvt *i)
{
_cmsg CMSG2;
i->isdnstate |= CAPI_ISDN_STATE_DISCONNECT;
if ((i->isdnstate & CAPI_ISDN_STATE_ECT)) {
cc_verbose(4, 1, VERBOSE_PREFIX_3 "%s: Disconnect ECT call\n",
i->vname);
/* we do nothing, just wait for DISCONNECT_IND */
return;
}
if (PLCI == i->onholdPLCI) {
cc_verbose(4, 1, VERBOSE_PREFIX_3 "%s: Disconnect onhold call\n",
i->vname);
/* the caller onhold hung up (or ECTed away) */
/* send a disconnect_req , we cannot hangup the channel here!!! */
DISCONNECT_REQ_HEADER(&CMSG2, capi_ApplID, get_capi_MessageNumber(), 0);
DISCONNECT_REQ_PLCI(&CMSG2) = i->onholdPLCI;
_capi_put_cmsg(&CMSG2);
return;
}
/* case 1: B3 on success or no B3 at all */
if ((i->doB3 != CAPI_B3_ALWAYS) && (i->outgoing == 1)) {
cc_verbose(4, 1, VERBOSE_PREFIX_3 "%s: Disconnect case 1\n",
i->vname);
if (i->state == CAPI_STATE_CONNECTED)
queue_cause_control(i, 0);
else
queue_cause_control(i, 1);
return;
}
/* case 2: we are doing B3, and receive the 0x8045 after a successful call */
if ((i->doB3 != CAPI_B3_DONT) &&
(i->state == CAPI_STATE_CONNECTED) && (i->outgoing == 1)) {
cc_verbose(4, 1, VERBOSE_PREFIX_3 "%s: Disconnect case 2\n",
i->vname);
queue_cause_control(i, 1);
return;
}
/*
* case 3: this channel is an incoming channel! the user hung up!
* it is much better to hangup now instead of waiting for a timeout and
* network caused DISCONNECT_IND!
*/
if (i->outgoing == 0) {
cc_verbose(4, 1, VERBOSE_PREFIX_3 "%s: Disconnect case 3\n",
i->vname);
if (i->FaxState & CAPI_FAX_STATE_ACTIVE) {
/* in fax mode, we just hangup */
DISCONNECT_REQ_HEADER(&CMSG2, capi_ApplID, get_capi_MessageNumber(), 0);
DISCONNECT_REQ_PLCI(&CMSG2) = i->PLCI;
_capi_put_cmsg(&CMSG2);
return;
}
queue_cause_control(i, 0);
return;
}
/* case 4 (a.k.a. the italian case): B3 always. call is unsuccessful */
if ((i->doB3 == CAPI_B3_ALWAYS) && (i->outgoing == 1)) {
cc_verbose(4, 1, VERBOSE_PREFIX_3 "%s: Disconnect case 4\n",
i