9737 lines
268 KiB
C
9737 lines
268 KiB
C
/*
|
|
* An implementation of Common ISDN API 2.0 for Asterisk
|
|
*
|
|
* Copyright (C) 2005-2010 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 <math.h>
|
|
#include <sys/types.h>
|
|
|
|
#include "chan_capi_platform.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_ecma.h"
|
|
#include "chan_capi_qsig_asn197ade.h"
|
|
#include "chan_capi_qsig_asn197no.h"
|
|
#include "chan_capi_utils.h"
|
|
#include "chan_capi_supplementary.h"
|
|
#include "chan_capi_chat.h"
|
|
#include "chan_capi_command.h"
|
|
#ifdef CC_AST_HAS_VERSION_1_8
|
|
#include <asterisk/callerid.h>
|
|
#endif
|
|
#ifdef CC_AST_HAS_VERSION_13_0
|
|
#include "asterisk/smoother.h"
|
|
#include "asterisk/pickup.h"
|
|
#include "asterisk/features_config.h"
|
|
#endif
|
|
struct _diva_streaming_vector* vind;
|
|
#ifdef DIVA_STREAMING
|
|
#include "platform.h"
|
|
#include "diva_streaming_result.h"
|
|
#include "diva_streaming_messages.h"
|
|
#include "diva_streaming_vector.h"
|
|
#include "diva_streaming_manager.h"
|
|
#include "chan_capi_divastreaming_utils.h"
|
|
#endif
|
|
#ifdef DIVA_STATUS
|
|
#include "divastatus_ifc.h"
|
|
#endif
|
|
#include "chan_capi_mwi.h"
|
|
#include "chan_capi_cli.h"
|
|
#include "chan_capi_ami.h"
|
|
#include "chan_capi_devstate.h"
|
|
#include "divaverbose.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;
|
|
|
|
#define CAPI_PLCI_VAR_NAME "CAPIPLCI"
|
|
#define CAPI_ECT_PLCI_VAR_NAME "CAPIECTPLCI"
|
|
#define CAPI_DETECTED_TONE_NAME "CAPIDETECTEDTONE"
|
|
|
|
typedef struct _diva_supported_tones {
|
|
unsigned char tone;
|
|
const char* name;
|
|
} diva_supported_tones_t;
|
|
static const char* pbx_capi_map_detected_tone(unsigned char tone);
|
|
|
|
static const char tdesc[] = "Common ISDN API Driver (" CC_VERSION ")";
|
|
static const char channeltype[] = CC_MESSAGE_BIGNAME;
|
|
#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 = CC_MESSAGE_BIGNAME " command interface.\n"
|
|
"The dial command:\n"
|
|
"Dial(" CC_MESSAGE_BIGNAME "/g<group>/[<callerid>:]<destination>[/<params>])\n"
|
|
"Dial(" CC_MESSAGE_BIGNAME "/contr<controller>/[<callerid>:]<destination>[/<params>])\n"
|
|
"Dial(" CC_MESSAGE_BIGNAME "/<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"
|
|
"\"proceeding\" send proceeding (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,options\" receive a " CC_MESSAGE_BIGNAME " fax\n"
|
|
"\"sendfax,filename.sff,stationID,headline\" send a " CC_MESSAGE_BIGNAME " fax\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, 8=native\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,ISDNPI1,ISDNPI2\n"
|
|
"!!! for more details and samples, check the README of chan_capi !!!\n";
|
|
|
|
static char *commandapp = "capicommand";
|
|
static char *commandsynopsis = "Execute special chan_capi commands";
|
|
#ifndef CC_AST_HAS_VERSION_1_4
|
|
STANDARD_LOCAL_USER;
|
|
LOCAL_USER_DECL;
|
|
#endif
|
|
|
|
static int usecnt;
|
|
|
|
#ifdef CC_AST_HAS_VERSION_1_4
|
|
struct ast_module *myself;
|
|
#endif
|
|
|
|
/*
|
|
* 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 pthread_t capi_device_thread = (pthread_t)(0-1);
|
|
|
|
struct capi_pvt *capi_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 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 struct capi_pvt *interface_for_task;
|
|
static int interface_task;
|
|
#define CAPI_INTERFACE_TASK_NONE 0
|
|
#define CAPI_INTERFACE_TASK_NULLIFREMOVE 1
|
|
|
|
static char capi_national_prefix[AST_MAX_EXTENSION];
|
|
static char capi_international_prefix[AST_MAX_EXTENSION];
|
|
static char capi_subscriber_prefix[AST_MAX_EXTENSION];
|
|
|
|
static char default_language[MAX_LANGUAGE] = "";
|
|
|
|
cc_format_t capi_capability = CC_FORMAT_ALAW;
|
|
|
|
static int null_plci_dtmf_support = 1;
|
|
|
|
#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 */
|
|
#define CC_B_INTERFACE_NOT_FREE(__x__) (((__x__)->used) || ((__x__)->reserved) || \
|
|
((__x__)->channeltype != CAPI_CHANNELTYPE_B) || \
|
|
(capi_controllers[(__x__)->controller]->nfreebchannels < capi_controllers[(__x__)->controller]->nfreebchannelsHardThr))
|
|
|
|
/*!
|
|
* \brief Acquire lock in correct order. Called if locking from non
|
|
* ast_channel context (thread, ...)
|
|
*/
|
|
static struct ast_channel* capidev_acquire_locks_from_thread_context(struct capi_pvt *i);
|
|
static int pbx_capi_hold(struct ast_channel *c, char *param);
|
|
static int pbx_capi_retrieve(struct ast_channel *c, char *param);
|
|
#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
|
|
static struct capi_pvt* get_active_plci(struct ast_channel *c);
|
|
static void clear_channel_fax_loop(struct ast_channel *c, struct capi_pvt *i);
|
|
static void pbx_capi_add_diva_protocol_independent_extension(
|
|
struct capi_pvt *i,
|
|
unsigned char *facilityarray,
|
|
struct ast_channel *c,
|
|
const char* variable);
|
|
#ifdef DIVA_STATUS
|
|
static void pbx_capi_interface_status_changed(int controller, diva_status_interface_state_t newInterfaceState);
|
|
static void pbx_capi_hw_status_changed(int controller, diva_status_hardware_state_t newHwState);
|
|
#endif
|
|
static int pbx_capi_check_controller_status(int controller);
|
|
|
|
/*
|
|
* 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, 0x05, /* 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"
|
|
},
|
|
{ 0x1f, 1, 0, /* 3 */
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
},
|
|
{ 0x04, 0x04, 0x04, /* 4 */
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
}
|
|
};
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* 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 B3 up
|
|
*/
|
|
int capi_wait_for_b3_up(struct capi_pvt *i)
|
|
{
|
|
struct timespec abstime;
|
|
int ret = 1;
|
|
|
|
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);
|
|
ret = 0;
|
|
} else {
|
|
cc_verbose(4, 1, "%s: cond signal received for b3 up.\n",
|
|
i->vname);
|
|
}
|
|
}
|
|
cc_mutex_unlock(&i->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* wait for finishing answering state
|
|
*/
|
|
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;
|
|
}
|
|
|
|
unsigned char capi_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 an interface which need to be done out of lock
|
|
* ( after the capi thread loop )
|
|
*/
|
|
static void capi_interface_task(struct capi_pvt *i, int task)
|
|
{
|
|
interface_for_task = i;
|
|
interface_task = task;
|
|
|
|
cc_verbose(4, 1, VERBOSE_PREFIX_4 "%s: set interface task to %d\n",
|
|
i->name, task);
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
#ifdef CC_AST_HAS_VERSION_11_0
|
|
const char *cur_name = ast_channel_name(c);
|
|
#else /* !defined(CC_AST_HAS_VERSION_11_0) */
|
|
const char *cur_name = c->name;
|
|
#endif /* defined(CC_AST_HAS_VERSION_11_0) */
|
|
|
|
cc_verbose(4, 1, VERBOSE_PREFIX_4 "%s: set channel task to %d\n",
|
|
cur_name, task);
|
|
}
|
|
|
|
/*
|
|
* Added date/time IE to facility structure
|
|
*/
|
|
static void capi_facility_add_datetime(unsigned char *facilityarray)
|
|
{
|
|
unsigned int idx;
|
|
time_t current_time;
|
|
struct tm *time_local;
|
|
unsigned char year;
|
|
|
|
if (!facilityarray)
|
|
return;
|
|
|
|
current_time = time(NULL);
|
|
time_local = localtime(¤t_time);
|
|
year = time_local->tm_year;
|
|
|
|
while (year > 99) {
|
|
year -= 100;
|
|
}
|
|
|
|
idx = facilityarray[0] + 1;
|
|
|
|
facilityarray[idx++] = 0x29; /* date/time IE */
|
|
facilityarray[idx++] = 5; /* length */
|
|
facilityarray[idx++] = year;
|
|
facilityarray[idx++] = time_local->tm_mon + 1;
|
|
facilityarray[idx++] = time_local->tm_mday;
|
|
facilityarray[idx++] = time_local->tm_hour;
|
|
facilityarray[idx++] = time_local->tm_min;
|
|
|
|
facilityarray[0] = idx - 1;
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Echo cancellation is for cards w/ integrated echo cancellation only
|
|
*/
|
|
void capi_echo_canceller(struct capi_pvt *i, int function)
|
|
{
|
|
int ecAvail = 0;
|
|
|
|
if ((i->isdnstate & CAPI_ISDN_STATE_DISCONNECT))
|
|
return;
|
|
|
|
if ((i->channeltype == CAPI_CHANNELTYPE_NULL) &&
|
|
(i->line_plci == NULL)) {
|
|
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 ((i->channeltype == CAPI_CHANNELTYPE_NULL) &&
|
|
(i->line_plci == NULL)) {
|
|
return;
|
|
}
|
|
|
|
if ((i->channeltype == CAPI_CHANNELTYPE_NULL) &&
|
|
(capi_controllers[i->controller]->ecPath & EC_ECHOCANCEL_PATH_IP) == 0) {
|
|
return;
|
|
}
|
|
if ((i->channeltype != CAPI_CHANNELTYPE_NULL) &&
|
|
(capi_controllers[i->controller]->ecPath & EC_ECHOCANCEL_PATH_IFC) == 0) {
|
|
return;
|
|
}
|
|
|
|
/* If echo cancellation is not requested or supported, don't attempt to enable it */
|
|
if (!ecAvail || !i->doEC) {
|
|
return;
|
|
}
|
|
|
|
if (capi_tcap_is_digital(i->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);
|
|
|
|
if (function == EC_FUNCTION_ENABLE) {
|
|
i->isdnstate |= CAPI_ISDN_STATE_EC;
|
|
} else {
|
|
i->isdnstate &= ~CAPI_ISDN_STATE_EC;
|
|
}
|
|
|
|
capi_sendf(i, 0, CAPI_FACILITY_REQ, i->PLCI, get_capi_MessageNumber(),
|
|
"w(w(www))",
|
|
i->ecSelector,
|
|
function,
|
|
i->ecOption, /* bit field - ignore echo canceller disable tone */
|
|
i->ecTail, /* Tail length, ms */
|
|
0
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
static int capi_check_diva_tone_function_allowed(struct capi_pvt *i, int useLinePLCI)
|
|
{
|
|
int ecAvail = 0;
|
|
|
|
if ((i->isdnstate & CAPI_ISDN_STATE_DISCONNECT))
|
|
return -1;
|
|
|
|
if ((i->channeltype == CAPI_CHANNELTYPE_NULL) &&
|
|
(i->line_plci == NULL)) {
|
|
return -1;
|
|
}
|
|
|
|
if ((i->channeltype == CAPI_CHANNELTYPE_NULL) && (useLinePLCI != 0)) {
|
|
if ((i->line_plci->isdnstate & CAPI_ISDN_STATE_DISCONNECT))
|
|
return -1;
|
|
if (capi_verify_resource_plci(i->line_plci) != 0)
|
|
return -1;
|
|
}
|
|
|
|
/* 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 ((ecAvail == 0) ||
|
|
(capi_controllers[i->controller]->divaExtendedFeaturesAvailable == 0)) {
|
|
return -1;
|
|
}
|
|
|
|
if (capi_tcap_is_digital(i->transfercapability)) {
|
|
cc_verbose(3, 1, VERBOSE_PREFIX_2 "%s: No audio features in digital mode (PLCI=%#x)\n",
|
|
i->vname, i->PLCI);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* diva audio features
|
|
*/
|
|
static void capi_diva_audio_features(struct capi_pvt *i, int useLinePLCI)
|
|
{
|
|
struct capi_pvt *effectiveIfc;
|
|
unsigned short divaAudioFlags, divaDigitalTxGain, divaDigitalRxGain;
|
|
const char* plciName;
|
|
|
|
if (capi_check_diva_tone_function_allowed(i, useLinePLCI) != 0)
|
|
return;
|
|
|
|
effectiveIfc = ((i->channeltype == CAPI_CHANNELTYPE_NULL) && (useLinePLCI != 0)) ? i->line_plci : i;
|
|
|
|
if (i->channeltype != CAPI_CHANNELTYPE_NULL) {
|
|
/* ISDN connection */
|
|
divaAudioFlags = (i->divaAudioFlags | i->divaDataStubAudioFlags);
|
|
divaDigitalTxGain = i->divaDigitalTxGain;
|
|
divaDigitalRxGain = i->divaDigitalRxGain;
|
|
plciName = "";
|
|
} else {
|
|
/* Resource PLCI */
|
|
if (useLinePLCI != 0) {
|
|
/* Command for line stub */
|
|
divaAudioFlags = i->divaAudioFlags;
|
|
divaDigitalTxGain = i->divaDigitalTxGain;
|
|
divaDigitalRxGain = i->divaDigitalRxGain;
|
|
plciName = "LINE-";
|
|
} else {
|
|
/* Command for data stub */
|
|
divaAudioFlags = i->divaDataStubAudioFlags;
|
|
divaDigitalTxGain = 0;
|
|
divaDigitalRxGain = 0;
|
|
plciName = "DATA-";
|
|
}
|
|
}
|
|
|
|
cc_verbose(3, 0, VERBOSE_PREFIX_2 "%s: Setting up audio features (%sPLCI=%#x, function=%04x, rx=%u, tx=%u)\n",
|
|
i->vname, plciName, i->PLCI, divaAudioFlags, divaDigitalRxGain, divaDigitalTxGain);
|
|
|
|
capi_sendf (effectiveIfc, 0, CAPI_MANUFACTURER_REQ, effectiveIfc->PLCI, get_capi_MessageNumber(),
|
|
"dw(b(bwww))",
|
|
_DI_MANU_ID,
|
|
_DI_DSP_CTRL,
|
|
0x1c,
|
|
0x0b,
|
|
divaAudioFlags,
|
|
divaDigitalTxGain,
|
|
divaDigitalRxGain);
|
|
}
|
|
|
|
static void capi_diva_clamping(struct capi_pvt *i, unsigned int duration)
|
|
{
|
|
if (capi_check_diva_tone_function_allowed(i, 0) != 0)
|
|
return;
|
|
|
|
if (duration != 0) {
|
|
cc_verbose(3, 0, VERBOSE_PREFIX_2 "%s: Setting DTMF clamping ON for %u mSec (PLCI=%#x)\n", i->vname, duration, i->PLCI);
|
|
capi_sendf (i, 0, CAPI_FACILITY_REQ, i->PLCI, get_capi_MessageNumber(), "w(www())", 1, 244, duration, duration);
|
|
} else {
|
|
cc_verbose(3, 0, VERBOSE_PREFIX_2 "%s: Setting DTMF clamping OFF (PLCI=%#x)\n", i->vname, i->PLCI);
|
|
capi_sendf (i, 0, CAPI_FACILITY_REQ, i->PLCI, get_capi_MessageNumber(), "w(www())", 1, 245, 0, 0);
|
|
}
|
|
}
|
|
|
|
static void capi_diva_tone_processing_function(struct capi_pvt *i, unsigned char function)
|
|
{
|
|
if (capi_check_diva_tone_function_allowed(i, 0) != 0)
|
|
return;
|
|
|
|
cc_verbose(3, 0, VERBOSE_PREFIX_2 "%s: Apply tone processing function %u (PLCI=%#x)\n",
|
|
i->vname, function, i->PLCI);
|
|
capi_sendf(i, 0, CAPI_FACILITY_REQ, i->PLCI, get_capi_MessageNumber(),
|
|
"w(www())", 1, function, 0, 0);
|
|
}
|
|
|
|
static void capi_diva_send_tone_function(struct capi_pvt *i, unsigned char tone)
|
|
{
|
|
if (capi_check_diva_tone_function_allowed(i, 0) != 0)
|
|
return;
|
|
|
|
capi_sendf (i, 0, CAPI_FACILITY_REQ, i->PLCI, get_capi_MessageNumber(), "w(www(b)())",
|
|
FACILITYSELECTOR_DTMF, 252, /* send tone */ 0, 0, tone);
|
|
}
|
|
|
|
static void capi_diva_pitch_control_command(
|
|
struct capi_pvt *i,
|
|
int enable,
|
|
unsigned short rxpitch,
|
|
unsigned short txpitch)
|
|
{
|
|
if (capi_check_diva_tone_function_allowed(i, 0) != 0)
|
|
return;
|
|
|
|
capi_sendf (i, 0, CAPI_MANUFACTURER_REQ, i->PLCI, get_capi_MessageNumber(),
|
|
"dw(b(bwww))",
|
|
_DI_MANU_ID,
|
|
_DI_DSP_CTRL,
|
|
0x1c,
|
|
0x0a,
|
|
enable == 0 ? 0x0000 : 0x0001,
|
|
enable == 0 ? 0 : rxpitch,
|
|
enable == 0 ? 0 : txpitch);
|
|
}
|
|
|
|
/*
|
|
* turn on/off DTMF detection
|
|
*/
|
|
static int capi_detect_dtmf(struct capi_pvt *i, int flag)
|
|
{
|
|
MESSAGE_EXCHANGE_ERROR error;
|
|
|
|
if ((i->isdnstate & CAPI_ISDN_STATE_DISCONNECT))
|
|
return 0;
|
|
|
|
if ((i->channeltype == CAPI_CHANNELTYPE_NULL) &&
|
|
(((i->line_plci == NULL) && (null_plci_dtmf_support == 0)) ||
|
|
(i->resource_plci_type == CAPI_RESOURCE_PLCI_LINE))) {
|
|
return 0;
|
|
}
|
|
|
|
if (capi_tcap_is_digital(i->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;
|
|
|
|
cc_verbose(3, 0, VERBOSE_PREFIX_2 "%s: Setting up DTMF detector (PLCI=%#x, flag=%d)\n",
|
|
i->vname, i->PLCI, flag);
|
|
|
|
error = capi_sendf(i, 0, CAPI_FACILITY_REQ, i->PLCI, get_capi_MessageNumber(),
|
|
"w(www()())",
|
|
((i->channeltype != CAPI_CHANNELTYPE_NULL) || (i->line_plci != 0)) ? FACILITYSELECTOR_DTMF : PRIV_SELECTOR_DTMF_ONDATA,
|
|
(flag == 1) ? 1:2, /* start/stop DTMF listen */
|
|
CAPI_DTMF_DURATION,
|
|
CAPI_DTMF_DURATION
|
|
);
|
|
|
|
if (error != 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)
|
|
{
|
|
unsigned char *wbuf;
|
|
int wbuflen;
|
|
|
|
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, FRAME_SUBCLASS_INTEGER(f->subclass));
|
|
return 0;
|
|
}
|
|
|
|
if ((capidebug) && (f->frametype != AST_FRAME_VOICE)) {
|
|
ast_frame_dump(i->vname, f, VERBOSE_PREFIX_3 "chan_capi queue frame:");
|
|
}
|
|
|
|
if ((f->frametype == AST_FRAME_CONTROL) &&
|
|
(FRAME_SUBCLASS_INTEGER(f->subclass) == AST_CONTROL_HANGUP)) {
|
|
i->isdnstate |= CAPI_ISDN_STATE_HANGUP;
|
|
}
|
|
|
|
if (i->writerfd == -1) {
|
|
if (i->resource_plci_type == 0) {
|
|
cc_log(LOG_ERROR, "No writerfd in local_queue_frame for %s\n",
|
|
i->vname);
|
|
return -1;
|
|
} else {
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
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->FRAME_DATA_PTR, f->datalen);
|
|
}
|
|
|
|
if (write(i->writerfd, wbuf, wbuflen) != wbuflen) {
|
|
cc_log(LOG_ERROR, "Could not write to pipe for %s fd:%d errno:%d\n",
|
|
i->vname, i->writerfd, errno);
|
|
}
|
|
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, CC_MESSAGE_BIGNAME "/%s/%s-%x",
|
|
i->vname, i->dnid, capi_counter++);
|
|
if (i->owner) {
|
|
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;
|
|
char buf[64];
|
|
int a;
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
|
|
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];
|
|
}
|
|
|
|
error = capi_sendf(NULL, 0, CAPI_INFO_REQ, i->PLCI, get_capi_MessageNumber(),
|
|
"s()",
|
|
buf
|
|
);
|
|
if (error != 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)
|
|
{
|
|
struct capi_pvt *i = CC_CHANNEL_PVT(c);
|
|
|
|
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 */
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* send DTMF digit
|
|
*/
|
|
static int capi_send_dtmf_digits(struct capi_pvt *i, char digit)
|
|
{
|
|
int ret;
|
|
|
|
if (!(i->isdnstate & CAPI_ISDN_STATE_B3_UP)) {
|
|
cc_verbose(2, 1, VERBOSE_PREFIX_3 "%s: send DTMF: B-channel not connected.\n",
|
|
i->vname);
|
|
return -1;
|
|
}
|
|
|
|
cc_verbose(3, 1, VERBOSE_PREFIX_3 "%s: send DTMF '%c'.\n",
|
|
i->vname, digit);
|
|
|
|
if ((capi_controllers[i->controller]->dtmf == 0) || (i->doDTMF > 0)) {
|
|
/* let * fake it */
|
|
return -1;
|
|
}
|
|
|
|
ret = capi_sendf(i, 0, CAPI_FACILITY_REQ, i->NCCI, get_capi_MessageNumber(),
|
|
"w(www(b)())",
|
|
FACILITYSELECTOR_DTMF,
|
|
3, /* send DTMF digit */
|
|
CAPI_DTMF_DURATION, /* XXX: duration comes from asterisk in 1.4 */
|
|
CAPI_DTMF_DURATION,
|
|
digit
|
|
);
|
|
|
|
if (ret == 0) {
|
|
cc_verbose(3, 0, VERBOSE_PREFIX_4 "%s: sent dtmf '%c'\n",
|
|
i->vname, digit);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* send a 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);
|
|
char did[2];
|
|
int ret = 0;
|
|
|
|
if (i == NULL) {
|
|
cc_log(LOG_ERROR, "No interface!\n");
|
|
return -1;
|
|
}
|
|
|
|
#ifdef CC_AST_HAS_VERSION_11_0
|
|
const enum ast_channel_state cur_state = ast_channel_state(c);
|
|
#else /* !defined(CC_AST_HAS_VERSION_11_0) */
|
|
const enum ast_channel_state cur_state = c->_state;
|
|
#endif /* defined(CC_AST_HAS_VERSION_11_0) */
|
|
|
|
cc_verbose(3, 1, VERBOSE_PREFIX_3 "%s: send_digit '%c' in state %d(%d)\n",
|
|
i->vname, digit, i->state, cur_state);
|
|
|
|
cc_mutex_lock(&i->lock);
|
|
|
|
if ((cur_state == AST_STATE_DIALING) &&
|
|
(i->state != CAPI_STATE_DISCONNECTING)) {
|
|
if (!(i->isdnstate & CAPI_ISDN_STATE_ISDNPROGRESS)) {
|
|
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;
|
|
} else {
|
|
/* if PROGRESS arrived, we sent as DTMF */
|
|
ret = capi_send_dtmf_digits(i, digit);
|
|
cc_mutex_unlock(&i->lock);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
if (i->state == CAPI_STATE_CONNECTED) {
|
|
/* we have a real connection, so send real DTMF */
|
|
ret = capi_send_dtmf_digits(i, 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);
|
|
unsigned char *facilityarray = NULL;
|
|
|
|
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;
|
|
}
|
|
|
|
facilityarray = alloca(CAPI_MAX_FACILITYDATAARRAY_SIZE);
|
|
facilityarray[0] = 0;
|
|
cc_qsig_add_call_alert_data(facilityarray, i, c);
|
|
pbx_capi_add_diva_protocol_independent_extension (i, facilityarray, c, "CALLEDNAME");
|
|
|
|
if (capi_sendf(NULL, 0, CAPI_ALERT_REQ, i->PLCI, get_capi_MessageNumber(),
|
|
"(()()()s())",
|
|
facilityarray
|
|
) != 0) {
|
|
return -1;
|
|
}
|
|
|
|
i->state = CAPI_STATE_ALERTING;
|
|
#ifdef CC_AST_HAS_VERSION_13_0
|
|
ast_channel_lock(c);
|
|
#endif
|
|
ast_setstate(c, AST_STATE_RING);
|
|
#ifdef CC_AST_HAS_VERSION_13_0
|
|
ast_channel_unlock(c);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*!
|
|
\brief Send CALL PROCEEDING (if supported by hardware)
|
|
|
|
\note Sending of Proceeding is not defined by CAPI spec.
|
|
Diva hardware uses ALERT with sending complete set.
|
|
Other hardware can send alert only
|
|
|
|
*/
|
|
static int pbx_capi_signal_proceeding(struct ast_channel *c, char *param)
|
|
{
|
|
static const unsigned char sending_complete[] = { 2, 1, 0 };
|
|
struct capi_pvt *i = CC_CHANNEL_PVT(c);
|
|
|
|
if ((i->state != CAPI_STATE_INCALL) &&
|
|
(i->state != CAPI_STATE_DID)) {
|
|
cc_verbose(2, 1, VERBOSE_PREFIX_3 "%s: attempting PROCEEDING in state %d\n",
|
|
i->vname, i->state);
|
|
}
|
|
|
|
if ((i->ntmode == 0)
|
|
/*! \todo || (capi_controllers[i->controlle]->mnufacturer != ManufacturerDiva) */) {
|
|
return (pbx_capi_alert(c));
|
|
}
|
|
|
|
if (((i->isdnstate2 & CAPI_ISDN_STATE2_PROCEEDING) != 0) ||
|
|
((i->isdnstate2 & CAPI_ISDN_STATE2_PROCEEDING_PENDING) != 0)) {
|
|
return 0;
|
|
}
|
|
|
|
|
|
if (capi_sendf(NULL, 0, CAPI_ALERT_REQ, i->PLCI, get_capi_MessageNumber(),
|
|
"(()()()()s)", sending_complete) != 0) {
|
|
return -1;
|
|
}
|
|
|
|
i->isdnstate2 |= CAPI_ISDN_STATE2_PROCEEDING_PENDING;
|
|
|
|
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);
|
|
|
|
#ifdef DIVA_STREAMING
|
|
capi_DivaStreamingRemove(i);
|
|
#endif
|
|
|
|
pbx_capi_voicecommand_cleanup(i);
|
|
|
|
if (i->readerfd != -1) {
|
|
close(i->readerfd);
|
|
i->readerfd = -1;
|
|
}
|
|
if (i->writerfd != -1) {
|
|
close(i->writerfd);
|
|
i->writerfd = -1;
|
|
}
|
|
|
|
i->isdnstate = 0;
|
|
i->isdnstate2 = 0;
|
|
i->cause = 0;
|
|
i->fsetting = 0;
|
|
|
|
i->whentohangup = 0;
|
|
i->whentoqueuehangup = 0;
|
|
i->whentoretrieve = 0;
|
|
|
|
i->FaxState &= ~CAPI_FAX_STATE_MASK;
|
|
|
|
i->PLCI = 0;
|
|
i->MessageNumber = 0;
|
|
i->NCCI = 0;
|
|
i->onholdPLCI = 0;
|
|
i->doEC = i->doEC_global;
|
|
i->ccbsnrhandle = 0;
|
|
|
|
memset(i->cid, 0, sizeof(i->cid));
|
|
memset(i->dnid, 0, sizeof(i->dnid));
|
|
i->cid_ton = 0;
|
|
|
|
i->rtpcodec = 0;
|
|
if (i->rtp) {
|
|
#ifdef CC_AST_HAS_RTP_ENGINE_H
|
|
ast_rtp_instance_destroy(i->rtp);
|
|
#else
|
|
ast_rtp_destroy(i->rtp);
|
|
#endif
|
|
i->rtp = NULL;
|
|
}
|
|
|
|
interface_cleanup_qsig(i);
|
|
|
|
i->peer = NULL;
|
|
i->owner = NULL;
|
|
#ifdef CC_AST_HAS_VERSION_1_4
|
|
ast_module_unref(myself);
|
|
#endif
|
|
i->used = NULL;
|
|
i->reserved = 0;
|
|
|
|
if (i->channeltype == CAPI_CHANNELTYPE_NULL) {
|
|
capi_interface_task(i, CAPI_INTERFACE_TASK_NULLIFREMOVE);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* disconnect b3 and wait for confirmation
|
|
*/
|
|
static void cc_disconnect_b3(struct capi_pvt *i, int wait)
|
|
{
|
|
struct timespec abstime;
|
|
|
|
if (!(i->isdnstate & (CAPI_ISDN_STATE_B3_UP | CAPI_ISDN_STATE_B3_PEND)))
|
|
return;
|
|
|
|
if (wait) {
|
|
cc_mutex_lock(&i->lock);
|
|
capi_sendf(i, 1, CAPI_DISCONNECT_B3_REQ, i->NCCI, get_capi_MessageNumber(), "()");
|
|
} else {
|
|
capi_sendf(NULL, 0, CAPI_DISCONNECT_B3_REQ, i->NCCI, get_capi_MessageNumber(), "()");
|
|
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)
|
|
{
|
|
if (!(i->isdnstate & (CAPI_ISDN_STATE_B3_UP | CAPI_ISDN_STATE_B3_PEND))) {
|
|
i->isdnstate |= CAPI_ISDN_STATE_B3_PEND;
|
|
capi_sendf(NULL, 0, CAPI_CONNECT_B3_REQ, i->PLCI, get_capi_MessageNumber(),
|
|
"s", capi_rtp_ncpi(i));
|
|
cc_verbose(4, 1, VERBOSE_PREFIX_3 "%s: sent CONNECT_B3_REQ PLCI=%#x\n",
|
|
i->vname, i->PLCI);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* signal 'progress' to PBX
|
|
*/
|
|
static void send_progress(struct capi_pvt *i)
|
|
{
|
|
struct ast_frame fr = { AST_FRAME_CONTROL, };
|
|
|
|
if (i->doB3 != CAPI_B3_DONT &&
|
|
!(i->fsetting & CAPI_FSETTING_EARLYB3_ONLY_WHEN_TONES_AVAIL)) {
|
|
/* we do early B3 Connect */
|
|
cc_start_b3(i);
|
|
}
|
|
|
|
if (!(i->isdnstate & CAPI_ISDN_STATE_PROGRESS)) {
|
|
i->isdnstate |= CAPI_ISDN_STATE_PROGRESS;
|
|
FRAME_SUBCLASS_INTEGER(fr.subclass) = AST_CONTROL_PROGRESS;
|
|
local_queue_frame(i, &fr);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* send disconnect_req
|
|
*/
|
|
static void capi_send_disconnect(unsigned int PLCI)
|
|
{
|
|
if (PLCI == 0) {
|
|
return;
|
|
}
|
|
capi_sendf(NULL, 0, CAPI_DISCONNECT_REQ, PLCI, get_capi_MessageNumber(), "()");
|
|
}
|
|
|
|
/*
|
|
* hangup a line (CAPI messages)
|
|
* (this must be called with i->lock held)
|
|
*/
|
|
void capi_activehangup(struct capi_pvt *i, int state)
|
|
{
|
|
struct ast_channel *c = i->owner;
|
|
const char *cause;
|
|
|
|
if (c) {
|
|
#ifdef CC_AST_HAS_VERSION_11_0
|
|
int hangupcause = ast_channel_hangupcause(c);
|
|
#else /* !defined(CC_AST_HAS_VERSION_11_0) */
|
|
int hangupcause = c->hangupcause;
|
|
#endif /* defined(CC_AST_HAS_VERSION_11_0) */
|
|
|
|
i->cause = 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);
|
|
}
|
|
}
|
|
|
|
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)) {
|
|
capi_sendf(NULL, 0, CAPI_CONNECT_RESP, i->PLCI, i->MessageNumber,
|
|
"w()()()()()",
|
|
(i->cause) ? (0x3480 | (i->cause & 0x7f)) : 2);
|
|
return;
|
|
}
|
|
|
|
if ((i->fsetting & CAPI_FSETTING_STAYONLINE)) {
|
|
/* user has requested to leave channel online for further actions
|
|
like CCBS */
|
|
cc_verbose(2, 1, VERBOSE_PREFIX_4 "%s: disconnect deferred, stay-online mode PLCI=%#x\n",
|
|
i->vname, i->PLCI);
|
|
i->whentohangup = time(NULL) + 18; /* timeout 18 seconds */
|
|
return;
|
|
}
|
|
|
|
/* active disconnect */
|
|
if ((i->isdnstate & CAPI_ISDN_STATE_B3_UP)) {
|
|
cc_disconnect_b3(i, 0);
|
|
return;
|
|
}
|
|
|
|
if (i->channeltype == CAPI_CHANNELTYPE_NULL) {
|
|
if (i->PLCI == 0) {
|
|
interface_cleanup(i);
|
|
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);
|
|
}
|
|
capi_send_disconnect(i->PLCI);
|
|
}
|
|
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: " CC_MESSAGE_BIGNAME
|
|
" 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(i, state);
|
|
}
|
|
|
|
i->owner = NULL;
|
|
#ifdef CC_AST_HAS_VERSION_1_4
|
|
ast_module_unref(myself);
|
|
#endif
|
|
|
|
#ifdef CC_AST_HAS_VERSION_11_0
|
|
ast_channel_tech_pvt_set(c, NULL);
|
|
#else /* !defined(CC_AST_HAS_VERSION_11_0) */
|
|
CC_CHANNEL_PVT(c) = NULL;
|
|
#endif /* defined(CC_AST_HAS_VERSION_11_0) */
|
|
|
|
cc_mutex_unlock(&i->lock);
|
|
|
|
#ifdef CC_AST_HAS_VERSION_13_0
|
|
ast_channel_lock(c);
|
|
#endif
|
|
ast_setstate(c, AST_STATE_DOWN);
|
|
#ifdef CC_AST_HAS_VERSION_13_0
|
|
ast_channel_unlock(c);
|
|
#endif
|
|
|
|
#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;
|
|
}
|
|
|
|
static void pbx_capi_call_build_calling_party_number(
|
|
struct ast_channel *c,
|
|
char* calling,
|
|
int max_calling,
|
|
int use_defaultcid,
|
|
const char* ocid)
|
|
{
|
|
struct capi_pvt *i = CC_CHANNEL_PVT(c);
|
|
const char *ton;
|
|
char callerid[AST_MAX_EXTENSION];
|
|
int CLIR;
|
|
int callernplan;
|
|
|
|
#ifdef CC_AST_HAS_VERSION_11_0
|
|
if (ast_channel_connected(c)->id.number.valid) {
|
|
CLIR = ast_channel_connected(c)->id.number.presentation;
|
|
callernplan = ast_channel_connected(c)->id.number.plan & 0x7f;
|
|
} else {
|
|
CLIR = 0;
|
|
callernplan = 0;
|
|
}
|
|
if (ast_channel_connected(c)->id.number.valid && !ast_strlen_zero(ast_channel_connected(c)->id.number.str)) {
|
|
ast_copy_string(callerid, ast_channel_connected(c)->id.number.str, sizeof(callerid));
|
|
} else {
|
|
memset(callerid, 0, sizeof(callerid));
|
|
}
|
|
#elif defined(CC_AST_HAS_VERSION_1_8)
|
|
if (c->connected.id.number.valid) {
|
|
CLIR = c->connected.id.number.presentation;
|
|
callernplan = c->connected.id.number.plan & 0x7f;
|
|
} else {
|
|
CLIR = 0;
|
|
callernplan = 0;
|
|
}
|
|
if (c->connected.id.number.valid && !ast_strlen_zero(c->connected.id.number.str)) {
|
|
ast_copy_string(callerid, c->connected.id.number.str, sizeof(callerid));
|
|
} else {
|
|
memset(callerid, 0, sizeof(callerid));
|
|
}
|
|
#else /* !(defined(CC_AST_HAS_VERSION_11_0) || defined(CC_AST_HAS_VERSION_1_8)) */
|
|
CLIR = c->cid.cid_pres;
|
|
callernplan = c->cid.cid_ton & 0x7f;
|
|
|
|
if (c->cid.cid_num) {
|
|
cc_copy_string(callerid, c->cid.cid_num, sizeof(callerid));
|
|
} else {
|
|
memset(callerid, 0, sizeof(callerid));
|
|
}
|
|
#endif /* defined(CC_AST_HAS_VERSION_11_0) || defined(CC_AST_HAS_VERSION_1_8) */
|
|
|
|
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));
|
|
|
|
if ((ton = pbx_builtin_getvar_helper(c, "CALLERTON"))) {
|
|
callernplan = atoi(ton) & 0x7f;
|
|
}
|
|
i->cid_ton = callernplan;
|
|
|
|
calling[0] = strlen(callerid) + 2;
|
|
calling[1] = callernplan;
|
|
calling[2] = 0x80 | (CLIR & 0x63);
|
|
strncpy(&calling[3], callerid, max_calling - 4);
|
|
|
|
#ifdef CC_AST_HAS_VERSION_11_0
|
|
const char *cur_name = ast_channel_name(c);
|
|
#else /* !defined(CC_AST_HAS_VERSION_11_0) */
|
|
const char *cur_name = c->name;
|
|
#endif /* defined(CC_AST_HAS_VERSION_11_0) */
|
|
|
|
cc_verbose(1, 1, VERBOSE_PREFIX_2 "%s: Call %s %s%s (pres=0x%02x, ton=0x%02x)\n",
|
|
i->vname, cur_name, i->doB3 ? "with B3 ":" ",
|
|
i->doOverlap ? "overlap":"", CLIR, callernplan);
|
|
}
|
|
|
|
/*
|
|
* PBX tells us to make a call
|
|
*/
|
|
#ifdef CC_AST_HAS_VERSION_11_0
|
|
static int pbx_capi_call(struct ast_channel *c, const char *idest, int timeout)
|
|
#else /* !defined(CC_AST_HAS_VERSION_11_0) */
|
|
static int pbx_capi_call(struct ast_channel *c, void *idest, int timeout)
|
|
#endif /* defined(CC_AST_HAS_VERSION_11_0) */
|
|
{
|
|
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];
|
|
int use_defaultcid = 0;
|
|
_cword cip;
|
|
const char *p;
|
|
char *osa = NULL;
|
|
char *dsa = NULL;
|
|
char callingsubaddress[AST_MAX_EXTENSION];
|
|
char calledsubaddress[AST_MAX_EXTENSION];
|
|
int doqsig;
|
|
char *sending_complete;
|
|
unsigned char *facilityarray = NULL, *bc_s = NULL, *llc_s = 0, *hlc_s = 0;
|
|
int no_sending_complete = 0;
|
|
|
|
MESSAGE_EXCHANGE_ERROR error;
|
|
|
|
cc_copy_string(buffer, idest, sizeof(buffer));
|
|
capi_parse_dialstring(buffer, &interface, &dest, ¶m, &ocid);
|
|
|
|
/* init param settings */
|
|
i->doB3 = CAPI_B3_DONT;
|
|
i->doOverlap = 0;
|
|
memset(i->overlapdigits, 0, sizeof(i->overlapdigits));
|
|
doqsig = i->qsigfeat || i->divaqsig;
|
|
|
|
/* 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 't': /* enable B3 only on in-band tones available indication */
|
|
if ((i->fsetting & CAPI_FSETTING_EARLYB3_ONLY_WHEN_TONES_AVAIL))
|
|
cc_log(LOG_WARNING,
|
|
"B3 on in-band tones avail only already set in '%s'\n",
|
|
idest);
|
|
i->fsetting |= CAPI_FSETTING_EARLYB3_ONLY_WHEN_TONES_AVAIL;
|
|
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 'c': /* Do not send sending complete */
|
|
if (no_sending_complete != 0)
|
|
cc_log(LOG_WARNING, "No sending complete already set in '%s'\n", idest);
|
|
no_sending_complete = 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 's': /* stay online */
|
|
if ((i->fsetting & CAPI_FSETTING_STAYONLINE))
|
|
cc_log(LOG_WARNING, "'stay-online' already set in '%s'\n", idest);
|
|
i->fsetting |= CAPI_FSETTING_STAYONLINE;
|
|
break;
|
|
case 'G': /* early bridge */
|
|
if ((i->fsetting & CAPI_FSETTING_EARLY_BRIDGE))
|
|
cc_log(LOG_WARNING, "'early-bridge' already set in '%s'\n", idest);
|
|
i->fsetting |= CAPI_FSETTING_EARLY_BRIDGE;
|
|
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;
|
|
}
|
|
|
|
if (((!dest) || (!dest[0])) &&
|
|
i->fsetting & CAPI_FSETTING_EARLYB3_ONLY_WHEN_TONES_AVAIL)
|
|
cc_log(LOG_WARNING,
|
|
"dialtone request with B3 on in-band tones avail setting might not work on all exchanges in '%s'\n",
|
|
idest);
|
|
|
|
if (i->doB3 == CAPI_B3_DONT &&
|
|
i->fsetting & CAPI_FSETTING_EARLYB3_ONLY_WHEN_TONES_AVAIL)
|
|
cc_log(LOG_WARNING,
|
|
"B3 on in-band tones avail setting ignored when early B3 is disabled in '%s'\n",
|
|
idest);
|
|
|
|
i->peer = cc_get_peer_link_id(pbx_builtin_getvar_helper(c, "CAPIPEERLINKID"));
|
|
i->outgoing = 1;
|
|
|
|
#ifdef CC_AST_HAS_VERSION_11_0
|
|
unsigned short cur_xfercap = ast_channel_transfercapability(c);
|
|
#else /* !defined(CC_AST_HAS_VERSION_11_0) */
|
|
unsigned short cur_xfercap = c->transfercapability;
|
|
#endif /* defined(CC_AST_HAS_VERSION_11_0) */
|
|
|
|
i->transfercapability = cur_xfercap;
|
|
i->isdnstate |= CAPI_ISDN_STATE_PBX;
|
|
i->state = CAPI_STATE_CONNECTPENDING;
|
|
ast_setstate(c, AST_STATE_DIALING);
|
|
i->MessageNumber = get_capi_MessageNumber();
|
|
|
|
/* if this is a CCBS/CCNR callback call */
|
|
if (i->ccbsnrhandle) {
|
|
_cword rbref;
|
|
|
|
cip = (_cword)tcap2cip(i->transfercapability);
|
|
i->doOverlap = 0;
|
|
rbref = capi_ccbsnr_take_ref(i->ccbsnrhandle);
|
|
|
|
if ((rbref == 0xdead) ||
|
|
((capi_sendf(NULL, 0, CAPI_FACILITY_REQ, i->controller, i->MessageNumber,
|
|
"w(w(www(wwwsss())()()()()))",
|
|
FACILITYSELECTOR_SUPPLEMENTARY,
|
|
0x0012, /* CCBS call */
|
|
rbref, /* reference */
|
|
cip, /* CIP */
|
|
0, /* reserved */
|
|
/* B protocol */
|
|
b_protocol_table[i->bproto].b1protocol,
|
|
b_protocol_table[i->bproto].b2protocol,
|
|
b_protocol_table[i->bproto].b3protocol,
|
|
diva_get_b1_conf(i),
|
|
b_protocol_table[i->bproto].b2configuration,
|
|
b_protocol_table[i->bproto].b3configuration
|
|
/* */ /* BC */
|
|
/* */ /* LLC */
|
|
/* */ /* HLC */
|
|
/* */ /* Additional Info */
|
|
)))) {
|
|
i->state = CAPI_STATE_DISCONNECTED;
|
|
ast_setstate(c, AST_STATE_RESERVED);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if ((p = pbx_builtin_getvar_helper(c, "CAPI_CIP"))) {
|
|
cip = (_cword)atoi(p);
|
|
i->transfercapability = cip2tcap(cip);
|
|
} else {
|
|
cip = tcap2cip(i->transfercapability);
|
|
}
|
|
|
|
#if defined(CC_FORMAT_G722) || defined(CC_FORMAT_SIREN7) || defined(CC_FORMAT_SIREN14) || defined(CC_FORMAT_SLINEAR16)
|
|
if (capi_tcap_is_digital(i->transfercapability) == 0 && i->bproto == CC_BPROTO_VOCODER) {
|
|
static unsigned char llc_s_template[] = { 0x04, 0x00, 0xc0, 0x90, 0xa5 };
|
|
static unsigned char hlc_s_template[] = { 0x02, 0x91, 0x81 };
|
|
switch(i->codec) {
|
|
#if defined(CC_FORMAT_G722)
|
|
case CC_FORMAT_G722:
|
|
llc_s = llc_s_template;
|
|
hlc_s = hlc_s_template;
|
|
break;
|
|
#endif
|
|
#if defined(CC_FORMAT_SIREN7)
|
|
case CC_FORMAT_SIREN7:
|
|
llc_s = llc_s_template;
|
|
hlc_s = hlc_s_template;
|
|
break;
|
|
#endif
|
|
#if defined(CC_FORMAT_SIREN14)
|
|
case CC_FORMAT_SIREN14:
|
|
llc_s = llc_s_template;
|
|
hlc_s = hlc_s_template;
|
|
break;
|
|
#endif
|
|
#if defined(CC_FORMAT_SLINEAR16)
|
|
case CC_FORMAT_SLINEAR16:
|
|
llc_s = llc_s_template;
|
|
hlc_s = hlc_s_template;
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (capi_tcap_is_digital(i->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))) {
|
|
called[0] = 1;
|
|
sending_complete = "\x00";
|
|
if (strlen(dest)) {
|
|
cc_copy_string(i->overlapdigits, dest, sizeof(i->overlapdigits));
|
|
} else {
|
|
i->doOverlap = 0;
|
|
}
|
|
} else {
|
|
called[0] = strlen(dest) + 1;
|
|
sending_complete = (no_sending_complete == 0) ? "\x02\x01\x00" : "\x00";
|
|
}
|
|
|
|
if ((p = pbx_builtin_getvar_helper(c, "CALLEDTON"))) {
|
|
unsigned char ton = (unsigned char)atoi(p);
|
|
called[1] = ton | 0x80;
|
|
} else {
|
|
called[1] = 0x80;
|
|
}
|
|
strncpy(&called[2], dest, sizeof(called) - 3);
|
|
|
|
pbx_capi_call_build_calling_party_number(c, calling, sizeof(calling), use_defaultcid, ocid);
|
|
|
|
if (doqsig != 0) {
|
|
facilityarray = alloca(CAPI_MAX_FACILITYDATAARRAY_SIZE);
|
|
facilityarray[0] = 0;
|
|
if (i->qsigfeat != 0)
|
|
cc_qsig_add_call_setup_data(facilityarray, i, c);
|
|
pbx_capi_add_diva_protocol_independent_extension (i, facilityarray, NULL, "CALLED/CONNECTED NAME");
|
|
}
|
|
|
|
#ifdef DIVA_STREAMING
|
|
i->diva_stream_entry = 0;
|
|
if (pbx_capi_streaming_supported (i) != 0) {
|
|
capi_DivaStreamingOn(i, 1, i->MessageNumber);
|
|
}
|
|
#endif
|
|
|
|
error = capi_sendf(NULL, 0, CAPI_CONNECT_REQ, i->controller, i->MessageNumber,
|
|
"wssss(wwwsss())sss((w)()()ss)",
|
|
cip, /* CIP value */
|
|
called, /* called party number */
|
|
calling, /* calling party number */
|
|
dsa, /* called party subaddress */
|
|
osa, /* calling party subaddress */
|
|
/* B protocol */
|
|
b_protocol_table[i->bproto].b1protocol,
|
|
b_protocol_table[i->bproto].b2protocol,
|
|
b_protocol_table[i->bproto].b3protocol,
|
|
diva_get_b1_conf(i),
|
|
b_protocol_table[i->bproto].b2configuration,
|
|
b_protocol_table[i->bproto].b3configuration,
|
|
bc_s, /* BC */
|
|
llc_s, /* LLC */
|
|
hlc_s, /* HLC */
|
|
/* Additional Info */
|
|
0x0000, /* B channel info */
|
|
/* Keypad facility */
|
|
/* User-User data */
|
|
facilityarray, /* Facility data array */
|
|
sending_complete /* Sending complete */
|
|
);
|
|
|
|
if (error) {
|
|
i->state = CAPI_STATE_DISCONNECTED;
|
|
ast_setstate(c, AST_STATE_RESERVED);
|
|
return error;
|
|
}
|
|
|
|
/* now we shall return .... the rest has to be done by handle_msg */
|
|
return 0;
|
|
}
|
|
|
|
_cstruct diva_get_b1_conf (struct capi_pvt *i) {
|
|
_cstruct b1conf = b_protocol_table[i->bproto].b1configuration;
|
|
|
|
if (i->bproto == CC_BPROTO_VOCODER) {
|
|
switch(i->codec) {
|
|
case CC_FORMAT_ALAW:
|
|
b1conf = (_cstruct)"\x06\x08\x04\x03\x00\xa0\x00";
|
|
break;
|
|
case CC_FORMAT_ULAW:
|
|
b1conf = (_cstruct)"\x06\x00\x04\x03\x00\xa0\x00";
|
|
break;
|
|
case CC_FORMAT_GSM:
|
|
b1conf = (_cstruct)"\x06\x03\x04\x0f\x00\xa0\x00";
|
|
break;
|
|
case CC_FORMAT_G723_1:
|
|
b1conf = (_cstruct)"\x06\x04\x04\x01\x00\xa0\x00";
|
|
break;
|
|
case CC_FORMAT_G726:
|
|
b1conf = (_cstruct)"\x06\x02\x04\x0f\x00\xa0\x00";
|
|
break;
|
|
case CC_FORMAT_ILBC: /* 30 mSec 240 samples */
|
|
b1conf = (_cstruct)"\x06\x1b\x04\x03\x00\xf0\x00";
|
|
break;
|
|
case CC_FORMAT_G729A:
|
|
b1conf = (_cstruct)"\x06\x12\x04\x0f\x00\xa0\x00";
|
|
break;
|
|
#ifdef CC_FORMAT_G722
|
|
case CC_FORMAT_G722:
|
|
b1conf = (_cstruct)"\x06\x09\x04\x03\x00\xa0\x00";
|
|
break;
|
|
#endif
|
|
#ifdef CC_FORMAT_SIREN7
|
|
case CC_FORMAT_SIREN7:
|
|
b1conf = (_cstruct)"\x06\x24\x04\x0f\x02\xa0\x00"; /* 32 kBit/s */
|
|
break;
|
|
#endif
|
|
#ifdef CC_FORMAT_SIREN14
|
|
case CC_FORMAT_SIREN14:
|
|
b1conf = (_cstruct)"\x06\x24\x04\x0f\x07\xa0\x00"; /* 48 kBit/s */
|
|
break;
|
|
#endif
|
|
#if defined(CC_FORMAT_SLINEAR)
|
|
case CC_FORMAT_SLINEAR:
|
|
b1conf = (_cstruct)"\x06\x01\x04\x0f\x01\xa0\x00";
|
|
break;
|
|
#endif
|
|
#if defined(CC_FORMAT_SLINEAR16)
|
|
case CC_FORMAT_SLINEAR16:
|
|
b1conf = (_cstruct)"\x06\x01\x04\x0f\x05\xa0\x00";
|
|
break;
|
|
#endif
|
|
default:
|
|
cc_log(LOG_ERROR, "%s: format %s(%d) invalid.\n",
|
|
i->vname, cc_getformatname(i->codec), i->codec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return (b1conf);
|
|
}
|
|
|
|
/*
|
|
* answer a capi call
|
|
*/
|
|
static int capi_send_answer(struct ast_channel *c, _cstruct b3conf)
|
|
{
|
|
struct capi_pvt *i = CC_CHANNEL_PVT(c);
|
|
char buf[CAPI_MAX_STRING];
|
|
const char *dnid;
|
|
const char *connectednumber;
|
|
unsigned char *facilityarray = NULL;
|
|
_cstruct b1conf;
|
|
unsigned char* llc_s = NULL;
|
|
|
|
if (i->state == CAPI_STATE_DISCONNECTED) {
|
|
cc_verbose(3, 0, VERBOSE_PREFIX_2 "%s: Not answering disconnected call.\n",
|
|
i->vname);
|
|
return -1;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (strlen(dnid)) {
|
|
const char *p = pbx_builtin_getvar_helper(c, "CALLEDTON");
|
|
|
|
buf[0] = strlen(dnid) + 2;
|
|
buf[1] = (p != 0) ? (((unsigned char)atoi(p)) & ~0x80) : 0x01;
|
|
buf[2] = 0x80;
|
|
strncpy(&buf[3], dnid, sizeof(buf) - 4);
|
|
} else {
|
|
buf[0] = 0x00;
|
|
}
|
|
if (!b3conf) {
|
|
b3conf = b_protocol_table[i->bproto].b3configuration;
|
|
}
|
|
|
|
b1conf = diva_get_b1_conf(i);
|
|
|
|
cc_verbose(3, 0, VERBOSE_PREFIX_2 "%s: Answering for %s\n",
|
|
i->vname, dnid);
|
|
|
|
facilityarray = alloca(CAPI_MAX_FACILITYDATAARRAY_SIZE);
|
|
cc_qsig_add_call_answer_data(facilityarray, i, c);
|
|
pbx_capi_add_diva_protocol_independent_extension(i, facilityarray, c, "CONNECTEDNAME");
|
|
|
|
if (i->ntmode) {
|
|
/* in NT-mode we send the current local time to device */
|
|
capi_facility_add_datetime(facilityarray);
|
|
}
|
|
|
|
#if defined(CC_FORMAT_G722) || defined(CC_FORMAT_SIREN7) || defined(CC_FORMAT_SIREN14) || defined(CC_FORMAT_SLINEAR16)
|
|
if (i->bproto == CC_BPROTO_VOCODER) {
|
|
static unsigned char llc_s_template[] = { 0x03, 0x91, 0x90, 0xa5 };
|
|
switch(i->codec) {
|
|
#if defined(CC_FORMAT_G722)
|
|
case CC_FORMAT_G722:
|
|
llc_s = llc_s_template;
|
|
break;
|
|
#endif
|
|
#if defined(CC_FORMAT_SIREN7)
|
|
case CC_FORMAT_SIREN7:
|
|
llc_s = llc_s_template;
|
|
break;
|
|
#endif
|
|
#if defined(CC_FORMAT_SIREN14)
|
|
case CC_FORMAT_SIREN14:
|
|
llc_s = llc_s_template;
|
|
break;
|
|
#endif
|
|
#if defined(CC_FORMAT_SLINEAR16)
|
|
case CC_FORMAT_SLINEAR16:
|
|
llc_s = llc_s_template;
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (capi_sendf(NULL, 0, CAPI_CONNECT_RESP, i->PLCI, i->MessageNumber,
|
|
"w(wwwssss)s()s(()()()s())",
|
|
0, /* accept call */
|
|
/* B protocol */
|
|
b_protocol_table[i->bproto].b1protocol,
|
|
b_protocol_table[i->bproto].b2protocol,
|
|
b_protocol_table[i->bproto].b3protocol,
|
|
b1conf,
|
|
b_protocol_table[i->bproto].b2configuration,
|
|
b3conf,
|
|
capi_set_global_configuration(i),
|
|
buf, /* connected number */
|
|
/* connected subaddress */
|
|
llc_s,/* LLC */
|
|
/* Additional info */
|
|
facilityarray
|
|
) != 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 = ((i->bproto == CC_BPROTO_VOCODER) && (i->codec != 0)) ? i->bproto : CC_BPROTO_TRANSPARENT;
|
|
|
|
if (i->rtp) {
|
|
if (!capi_tcap_is_digital(i->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;
|
|
|
|
f = capi_read_pipeframe(i);
|
|
|
|
if (f != NULL) {
|
|
if (f->frametype == AST_FRAME_VOICE) {
|
|
if ((f->datalen > 0) && (i->doDTMF > 0) && (i->vad != NULL)) {
|
|
f = ast_dsp_process(c, i->vad, f);
|
|
}
|
|
#ifdef CC_AST_HAS_VERSION_1_4
|
|
} else if (f->frametype == AST_FRAME_DTMF) {
|
|
/* Work around problem with recognition of fast sequences of events,
|
|
* see main/channel.c for details
|
|
* ... or better yet, get rid of this ugly hack alltogether.
|
|
*/
|
|
#ifdef CC_AST_HAS_VERSION_11_0
|
|
if (!(ast_test_flag(ast_channel_flags(c), AST_FLAG_END_DTMF_ONLY) ||
|
|
ast_test_flag(ast_channel_flags(c), AST_FLAG_EMULATE_DTMF) ||
|
|
ast_test_flag(ast_channel_flags(c), AST_FLAG_IN_DTMF))) {
|
|
ast_set_flag(ast_channel_flags(c), AST_FLAG_IN_DTMF);
|
|
struct timeval back_to_the_past = ast_tvsub(ast_tvnow(), ast_tv(0, 250*1000 /* to msec */));
|
|
ast_channel_dtmf_tv_set(c, &back_to_the_past);
|
|
#else /* !defined(CC_AST_HAS_VERSION_11_0 */
|
|
if (!(ast_test_flag(c, AST_FLAG_END_DTMF_ONLY) ||
|
|
ast_test_flag(c, AST_FLAG_EMULATE_DTMF) ||
|
|
ast_test_flag(c, AST_FLAG_IN_DTMF))) {
|
|
ast_set_flag(c, AST_FLAG_IN_DTMF);
|
|
c->dtmf_tv = ast_tvsub(ast_tvnow(),ast_tv(0,250*1000));
|
|
#endif /* defined(CC_AST_HAS_VERSION_11_0) */
|
|
if (!f->len)
|
|
f->len = 100;
|
|
}
|
|
#endif /* defined(CC_AST_HAS_VERSION_1_4) */
|
|
}
|
|
}
|
|
|
|
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);
|
|
int ret = 0;
|
|
|
|
ret = capi_write_frame(i, f);
|
|
|
|
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);
|
|
|
|
#ifdef CC_AST_HAS_VERSION_11_0
|
|
const char *old_name = ast_channel_name(oldchan);
|
|
const char *new_name = ast_channel_name(newchan);
|
|
#else /* !defined(CC_AST_HAS_VERSION_11_0) */
|
|
const char *old_name = oldchan->name;
|
|
const char *new_name = newchan->name;
|
|
#endif /* defined(CC_AST_HAS_VERSION_11_0) */
|
|
|
|
cc_verbose(3, 1, VERBOSE_PREFIX_2 "%s: %s fixup now %s\n",
|
|
i->vname, old_name, new_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)
|
|
{
|
|
if (!b3conf) {
|
|
b3conf = b_protocol_table[i->bproto].b3configuration;
|
|
}
|
|
|
|
capi_sendf(NULL, 0, CAPI_SELECT_B_PROTOCOL_REQ, i->PLCI, get_capi_MessageNumber(),
|
|
"(wwwssss)",
|
|
b_protocol_table[i->bproto].b1protocol,
|
|
b_protocol_table[i->bproto].b2protocol,
|
|
b_protocol_table[i->bproto].b3protocol,
|
|
diva_get_b1_conf(i),
|
|
b_protocol_table[i->bproto].b2configuration,
|
|
b3conf,
|
|
capi_set_global_configuration(i)
|
|
);
|
|
}
|
|
|
|
/*
|
|
* do line initerconnect
|
|
*/
|
|
static int line_interconnect(struct capi_pvt *i0, struct capi_pvt *i1, int start)
|
|
{
|
|
if ((i0->isdnstate & CAPI_ISDN_STATE_DISCONNECT) ||
|
|
(i1->isdnstate & CAPI_ISDN_STATE_DISCONNECT))
|
|
return -1;
|
|
|
|
if (start) {
|
|
/* connect */
|
|
capi_sendf(i1, 0, CAPI_FACILITY_REQ, i0->PLCI, get_capi_MessageNumber(),
|
|
"w(w(d((dd))))",
|
|
FACILITYSELECTOR_LINE_INTERCONNECT,
|
|
0x0001,
|
|
/* struct LI Request Parameter */
|
|
0x00000000, /* Data Path */
|
|
/* struct */
|
|
/* struct LI Request Connect Participant */
|
|
i1->PLCI,
|
|
0x00000003 /* Data Path Participant */
|
|
);
|
|
i0->isdnstate |= CAPI_ISDN_STATE_LI;
|
|
i1->isdnstate |= CAPI_ISDN_STATE_LI;
|
|
} else {
|
|
/* disconnect */
|
|
capi_sendf(i1, 0, CAPI_FACILITY_REQ, i0->PLCI, get_capi_MessageNumber(),
|
|
"w(w(d))",
|
|
FACILITYSELECTOR_LINE_INTERCONNECT,
|
|
0x0002,
|
|
i1->PLCI
|
|
);
|
|
i0->isdnstate &= ~CAPI_ISDN_STATE_LI;
|
|
i1->isdnstate &= ~CAPI_ISDN_STATE_LI;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* try call transfer instead of bridge
|
|
*/
|
|
#ifndef CC_AST_HAS_VERSION_13_0
|
|
static int pbx_capi_bridge_transfer(
|
|
struct ast_channel *c0,
|
|
struct ast_channel *c1,
|
|
struct capi_pvt *i0,
|
|
struct capi_pvt *i1)
|
|
{
|
|
int ret = 0;
|
|
ast_group_t tgroup0 = i0->transfergroup;
|
|
ast_group_t tgroup1 = i1->transfergroup;
|
|
struct timespec abstime;
|
|
const char* var;
|
|
struct capi_pvt *heldcall;
|
|
struct capi_pvt *consultationcall;
|
|
|
|
/* variable may override config */
|
|
if ((var = pbx_builtin_getvar_helper(c0, "TRANSFERGROUP")) != NULL) {
|
|
tgroup0 = ast_get_group((char *)var);
|
|
}
|
|
if ((var = pbx_builtin_getvar_helper(c1, "TRANSFERGROUP")) != NULL) {
|
|
tgroup1 = ast_get_group((char *)var);
|
|
}
|
|
|
|
if ((!((1 << i0->controller) & tgroup1)) ||
|
|
(!((1 << i1->controller) & tgroup0))) {
|
|
/* transfer between those controllers is not allowed */
|
|
cc_verbose(4, 1, "%s: transfergroup mismatch %d(0x%x),%d(0x%x)\n",
|
|
i0->vname, i0->controller, tgroup1, i1->controller, tgroup0);
|
|
return 0;
|
|
}
|
|
|
|
if ((i0->qsigfeat) && (i1->qsigfeat)) {
|
|
/* QSIG */
|
|
ret = pbx_capi_qsig_bridge(i0, i1);
|
|
|
|
if (ret == 2) {
|
|
/* don't do bridge - call transfer is active */
|
|
#ifdef CC_AST_HAS_VERSION_11_0
|
|
const char *c0_name = ast_channel_name(c0);
|
|
const char *c1_name = ast_channel_name(c1);
|
|
#else /* !defined(CC_AST_HAS_VERSION_11_0) */
|
|
const char *c0_name = c0->name;
|
|
const char *c1_name = c1->name;
|
|
#endif /* defined(CC_AST_HAS_VERSION_11_0) */
|
|
cc_verbose(3, 1, VERBOSE_PREFIX_2 "%s:%s cancelled bridge (path replacement was sent) for %s and %s\n",
|
|
i0->vname, i1->vname, c0_name, c1_name);
|
|
}
|
|
} else {
|
|
/* standard ECT */
|
|
if (i0->isdnstate & CAPI_ISDN_STATE_HOLD) {
|
|
if (i1->isdnstate & CAPI_ISDN_STATE_HOLD) {
|
|
cc_verbose(3, 1, VERBOSE_PREFIX_2 "%s:%s both channels on hold, retrieving second one.\n",
|
|
i0->vname, i1->vname);
|
|
pbx_capi_retrieve(c1, NULL);
|
|
capi_wait_for_b3_up(i1);
|
|
}
|
|
heldcall = i0;
|
|
consultationcall = i1;
|
|
} else if (i1->isdnstate & CAPI_ISDN_STATE_HOLD) {
|
|
heldcall = i1;
|
|
consultationcall = i0;
|
|
} else {
|
|
/* no one on hold */
|
|
/* put first call on hold */
|
|
cc_mutex_lock(&i0->lock);
|
|
pbx_capi_hold(c0, NULL);
|
|
if ((i0->onholdPLCI != 0) && (i0->state != CAPI_STATE_ONHOLD)) {
|
|
i0->waitevent = CAPI_WAITEVENT_HOLD_IND;
|
|
abstime.tv_sec = time(NULL) + 2;
|
|
abstime.tv_nsec = 0;
|
|
if (ast_cond_timedwait(&i0->event_trigger, &i0->lock, &abstime) != 0) {
|
|
cc_log(LOG_WARNING, "%s: timed out waiting for HOLD.\n", i0->vname);
|
|
} else {
|
|
cc_verbose(4, 1, "%s: cond signal received for HOLD.\n", i0->vname);
|
|
}
|
|
}
|
|
cc_mutex_unlock(&i0->lock);
|
|
|
|
if (i0->state != CAPI_STATE_ONHOLD) {
|
|
cc_verbose(4, 1, "%s: HOLD impossible, transfer aborted.\n", i0->vname);
|
|
return 0;
|
|
}
|
|
heldcall = i0;
|
|
consultationcall = i1;
|
|
}
|
|
heldcall->whentoretrieve = 0;
|
|
|
|
/* start the ECT */
|
|
cc_disconnect_b3(consultationcall, 1);
|
|
|
|
cc_mutex_lock(&consultationcall->lock);
|
|
|
|
/* ECT */
|
|
capi_sendf(consultationcall, 1, CAPI_FACILITY_REQ, consultationcall->PLCI,
|
|
get_capi_MessageNumber(),
|
|
"w(w(d))",
|
|
FACILITYSELECTOR_SUPPLEMENTARY,
|
|
0x0006, /* ECT */
|
|
heldcall->PLCI
|
|
);
|
|
|
|
heldcall->isdnstate &= ~CAPI_ISDN_STATE_HOLD;
|
|
heldcall->isdnstate |= CAPI_ISDN_STATE_ECT;
|
|
consultationcall->isdnstate |= CAPI_ISDN_STATE_ECT;
|
|
|
|
cc_verbose(2, 1, VERBOSE_PREFIX_4 "%s: sent ECT for PLCI=%#x to PLCI=%#x\n",
|
|
consultationcall->vname, heldcall->PLCI, consultationcall->PLCI);
|
|
|
|
consultationcall->waitevent = CAPI_WAITEVENT_ECT_IND;
|
|
abstime.tv_sec = time(NULL) + 2;
|
|
abstime.tv_nsec = 0;
|
|
if (ast_cond_timedwait(&consultationcall->event_trigger,
|
|
&consultationcall->lock, &abstime) != 0) {
|
|
cc_log(LOG_WARNING, "%s: timed out waiting for ECT.\n", consultationcall->vname);
|
|
} else {
|
|
cc_verbose(4, 1, "%s: cond signal received for ECT.\n", consultationcall->vname);
|
|
}
|
|
|
|
cc_mutex_unlock(&consultationcall->lock);
|
|
|
|
if (consultationcall->isdnstate & CAPI_ISDN_STATE_ECT) {
|
|
/* ECT was activated */
|
|
ret = 1;
|
|
} else {
|
|
cc_log(LOG_WARNING, "%s: ECT was not activated, trying to resume normal operation.\n",
|
|
consultationcall->vname);
|
|
cc_start_b3(consultationcall);
|
|
capi_wait_for_b3_up(consultationcall);
|
|
pbx_capi_retrieve(heldcall->owner, NULL);
|
|
ret = 0;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* activate / deactivate b-channel bridge
|
|
*/
|
|
static int capi_bridge(int start, struct capi_pvt *i0, struct capi_pvt *i1, int flags)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (start) {
|
|
if ((i0->isdnstate & CAPI_ISDN_STATE_LI) ||
|
|
(i1->isdnstate & CAPI_ISDN_STATE_LI)) {
|
|
/* already in bridge */
|
|
cc_verbose(4, 1, VERBOSE_PREFIX_3 "%s/%s: already in bridge.\n",
|
|
i0->vname, i1->vname);
|
|
return 0;
|
|
}
|
|
if (!(flags & AST_BRIDGE_DTMF_CHANNEL_0))
|
|
capi_detect_dtmf(i0, 0);
|
|
|
|
if (!(flags & AST_BRIDGE_DTMF_CHANNEL_1))
|
|
capi_detect_dtmf(i1, 0);
|
|
|
|
if ((capi_controllers[i0->controller]->ecOnTransit & EC_ECHOCANCEL_TRANSIT_A) == 0) {
|
|
capi_echo_canceller(i0, EC_FUNCTION_DISABLE);
|
|
}
|
|
if ((capi_controllers[i1->controller]->ecOnTransit & EC_ECHOCANCEL_TRANSIT_B) == 0) {
|
|
capi_echo_canceller(i1, EC_FUNCTION_DISABLE);
|
|
}
|
|
cc_verbose(4, 1, VERBOSE_PREFIX_3 "%s/%s: activating bridge.\n",
|
|
i0->vname, i1->vname);
|
|
ret = line_interconnect(i0, i1, 1);
|
|
} else {
|
|
cc_verbose(4, 1, VERBOSE_PREFIX_3 "%s/%s: deactivating bridge.\n",
|
|
i0->vname, i1->vname);
|
|
line_interconnect(i0, i1, 0);
|
|
capi_detect_dtmf(i0, 1);
|
|
capi_detect_dtmf(i1, 1);
|
|
capi_echo_canceller(i0, EC_FUNCTION_ENABLE);
|
|
capi_echo_canceller(i1, EC_FUNCTION_ENABLE);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* native bridging / line interconnect
|
|
*/
|
|
#ifndef CC_AST_HAS_VERSION_13_0
|
|
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;
|
|
|
|
#ifdef CC_AST_HAS_VERSION_11_0
|
|
const char *c0_name = ast_channel_name(c0);
|
|
const char *c1_name = ast_channel_name(c1);
|
|
#else /* !defined(CC_AST_HAS_VERSION_11_0) */
|
|
const char *c0_name = c0->name;
|
|
const char *c1_name = c1->name;
|
|
#endif /* defined(CC_AST_HAS_VERSION_11_0) */
|
|
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->isdnstate & CAPI_ISDN_STATE_ECT) ||
|
|
(i1->isdnstate & CAPI_ISDN_STATE_ECT)) {
|
|
/* If an ECT is already in progress, forget about the bridge */
|
|
return AST_BRIDGE_FAILED;
|
|
}
|
|
|
|
switch(pbx_capi_bridge_transfer(c0, c1, i0, i1)) {
|
|
case 1: /* transfer was sucessful */
|
|
return ret;
|
|
case 2: /* not successful, abort */
|
|
return AST_BRIDGE_FAILED_NOWARN;
|
|
default: /* go on with line-interconnect */
|
|
break;
|
|
}
|
|
|
|
/* do bridge aka line-interconnect here */
|
|
|
|
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;
|
|
}
|
|
|
|
capi_wait_for_b3_up(i0);
|
|
capi_wait_for_b3_up(i1);
|
|
|
|
if (capi_bridge(1, i0, i1, flags)) {
|
|
return AST_BRIDGE_FAILED;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
capi_bridge(0, i0, i1, 0);
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* a new channel is needed
|
|
*/
|
|
#ifdef CC_AST_HAS_VERSION_13_0
|
|
static struct ast_channel *capi_new(struct capi_pvt *i, int state, const struct ast_channel *requestor)
|
|
#else
|
|
static struct ast_channel *capi_new(struct capi_pvt *i, int state, const char *linkedid)
|
|
#endif
|
|
{
|
|
struct ast_channel *tmp;
|
|
int fmt;
|
|
|
|
#ifdef CC_AST_HAS_EXT_CHAN_ALLOC
|
|
tmp = ast_channel_alloc(0, state, i->cid, emptyid,
|
|
#ifdef CC_AST_HAS_EXT2_CHAN_ALLOC
|
|
i->accountcode, i->dnid, i->context,
|
|
#ifdef CC_AST_HAS_VERSION_13_0
|
|
NULL, requestor,
|
|
#endif
|
|
#ifdef CC_AST_HAS_LINKEDID_CHAN_ALLOC
|
|
linkedid,
|
|
#endif
|
|
i->amaflags,
|
|
#endif
|
|
CC_MESSAGE_BIGNAME "/%s/%s-%x", i->vname, i->dnid, capi_counter++);
|
|
#else
|
|
tmp = ast_channel_alloc(0);
|
|
#endif
|
|
|
|
cc_mutex_lock(&iflock);
|
|
i->reserved = 0;
|
|
|
|
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, CC_MESSAGE_BIGNAME "/%s/%s-%x",
|
|
i->vname, i->dnid, capi_counter++);
|
|
#else
|
|
snprintf(tmp->name, sizeof(tmp->name) - 1, CC_MESSAGE_BIGNAME "/%s/%s-%x",
|
|
i->vname, i->dnid, capi_counter++);
|
|
#endif
|
|
#endif
|
|
#ifndef CC_AST_HAS_VERSION_1_4
|
|
tmp->type = channeltype;
|
|
#endif
|
|
|
|
if (!(capi_create_reader_writer_pipe(i))) {
|
|
ast_channel_release(tmp);
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef CC_AST_HAS_VERSION_1_6
|
|
ast_channel_set_fd(tmp, 0, i->readerfd);
|
|
#else
|
|
tmp->fds[0] = i->readerfd;
|
|
#endif
|
|
|
|
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);
|
|
|
|
i->divaAudioFlags = 0;
|
|
i->divaDataStubAudioFlags = 0;
|
|
i->divaDigitalRxGain = 0;
|
|
i->divaDigitalRxGainDB = 0;
|
|
i->divaDigitalTxGain = 0;
|
|
i->divaDigitalTxGainDB = 0;
|
|
i->rxPitch = 8000;
|
|
i->txPitch = 8000;
|
|
i->special_tone_extension[0] = 0;
|
|
pbx_capi_voicecommand_cleanup(i);
|
|
|
|
if (i->doDTMF > 0) {
|
|
i->vad = ast_dsp_new();
|
|
#ifdef CC_AST_HAS_DSP_SET_DIGITMODE
|
|
ast_dsp_set_features(i->vad, DSP_FEATURE_DIGIT_DETECT);
|
|
if (i->doDTMF > 1) {
|
|
ast_dsp_set_digitmode(i->vad, DSP_DIGITMODE_DTMF | DSP_DIGITMODE_RELAXDTMF);
|
|
}
|
|
#else
|
|
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);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef CC_AST_HAS_VERSION_11_0
|
|
ast_channel_tech_pvt_set(tmp, i);
|
|
|
|
ast_channel_callgroup_set(tmp, i->callgroup);
|
|
ast_channel_pickupgroup_set(tmp, i->pickupgroup);
|
|
#else /* !defined(CC_AST_HAS_VERSION_11_0) */
|
|
CC_CHANNEL_PVT(tmp) = i;
|
|
|
|
tmp->callgroup = i->callgroup;
|
|
tmp->pickupgroup = i->pickupgroup;
|
|
#endif /* defined(CC_AST_HAS_VERSION_11_0) */
|
|
|
|
#if 0
|
|
i->bproto = CC_BPROTO_TRANSPARENT;
|
|
tmp->nativeformats = capi_capability;
|
|
|
|
if ((i->rtpcodec = (capi_controllers[i->controller]->rtpcodec & i->capability))) {
|
|
tmp->nativeformats = i->rtpcodec;
|
|
i->bproto = CC_BPROTO_VOCODER;
|
|
}
|
|
|
|
fmt = ast_best_codec(tmp->nativeformats);
|
|
|
|
i->codec = fmt;
|
|
tmp->readformat = fmt;
|
|
tmp->writeformat = fmt;
|
|
tmp->rawreadformat = fmt;
|
|
tmp->rawwriteformat = fmt;
|
|
#else
|
|
if ((i->rtpcodec = (capi_controllers[i->controller]->rtpcodec & i->capability))) {
|
|
i->bproto = CC_BPROTO_VOCODER;
|
|
#ifdef CC_AST_HAS_VERSION_11_0
|
|
struct ast_format_cap *cur_nativefmts = ast_channel_nativeformats(tmp);
|
|
#else /* !defined(CC_AST_HAS_VERSION_11_0) */
|
|
struct ast_format_cap *cur_nativefmts = tmp->nativeformats;
|
|
#endif /* defined(CC_AST_HAS_VERSION_11_0) */
|
|
cc_add_formats(cur_nativefmts, i->rtpcodec);
|
|
} else {
|
|
i->bproto = CC_BPROTO_TRANSPARENT;
|
|
#ifdef CC_AST_HAS_VERSION_11_0
|
|
struct ast_format_cap *cur_nativefmts = ast_channel_nativeformats(tmp);
|
|
#else /* !defined(CC_AST_HAS_VERSION_11_0) */
|
|
struct ast_format_cap *cur_nativefmts = tmp->nativeformats;
|
|
#endif /* defined(CC_AST_HAS_VERSION_11_0) */
|
|
cc_add_formats(cur_nativefmts, capi_capability);
|
|
}
|
|
fmt = cc_set_best_codec(tmp);
|
|
i->codec = fmt;
|
|
#endif
|
|
|
|
#ifdef CC_AST_HAS_VERSION_11_0
|
|
ast_channel_tech_set(tmp, &capi_tech);
|
|
struct ast_format_cap *cur_nativefmts = ast_channel_nativeformats(tmp);
|
|
#else /* !defined(CC_AST_HAS_VERSION_11_0) */
|
|
tmp->tech = &capi_tech;
|
|
struct ast_format_cap *cur_nativefmts = tmp->nativeformats;
|
|
#endif /* defined(CC_AST_HAS_VERSION_11_0) */
|
|
|
|
#ifdef CC_AST_HAS_VERSION_13_0
|
|
struct ast_str *format_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN);
|
|
#endif
|
|
|
|
cc_verbose(3, 1, VERBOSE_PREFIX_2 "%s: setting format %s - %s%s\n",
|
|
i->vname, cc_getformatname(fmt),
|
|
#ifdef CC_AST_HAS_VERSION_13_0
|
|
ast_format_cap_get_names(cur_nativefmts, &format_buf),
|
|
#else
|
|
ast_getformatname_multiple(alloca(80), 80, cur_nativefmts),
|
|
#endif
|
|
(i->bproto == CC_BPROTO_VOCODER) ? "VOCODER" : ((i->rtp) ? " (RTP)" : ""));
|
|
|
|
if (!ast_strlen_zero(i->cid)) {
|
|
#ifdef CC_AST_HAS_VERSION_11_0
|
|
ast_free (ast_channel_connected(tmp)->id.number.str);
|
|
ast_channel_connected(tmp)->id.number.valid = 1;
|
|
ast_channel_connected(tmp)->id.number.str = ast_strdup(i->cid);
|
|
ast_channel_connected(tmp)->id.number.plan = i->cid_ton;
|
|
#elif defined(CC_AST_HAS_VERSION_1_8)
|
|
ast_free (tmp->connected.id.number.str);
|
|
tmp->connected.id.number.valid = 1;
|
|
tmp->connected.id.number.str = ast_strdup(i->cid);
|
|
tmp->connected.id.number.plan = i->cid_ton;
|
|
#else /* !(defined(CC_AST_HAS_VERSION_11_0) || defined(CC_AST_HAS_VERSION_1_8)) */
|
|
ast_free(tmp->cid.cid_num);
|
|
tmp->cid.cid_num = ast_strdup(i->cid);
|
|
#endif /* defined(CC_AST_HAS_VERSION_11_0) || defined(CC_AST_HAS_VERSION_1_8) */
|
|
}
|
|
if (!ast_strlen_zero(i->dnid)) {
|
|
#ifdef CC_AST_HAS_VERSION_11_0
|
|
ast_free (ast_channel_dialed(tmp)->number.str);
|
|
ast_channel_dialed(tmp)->number.str = ast_strdup (i->dnid);
|
|
#elif defined(CC_AST_HAS_VERSION_1_8)
|
|
ast_free (tmp->dialed.number.str);
|
|
tmp->dialed.number.str = ast_strdup (i->dnid);
|
|
#else /* !(defined(CC_AST_HAS_VERSION_11_0) || defined(CC_AST_HAS_VERSION_1_8)) */
|
|
ast_free(tmp->cid.cid_dnid);
|
|
tmp->cid.cid_dnid = ast_strdup(i->dnid);
|
|
#endif /* defined(CC_AST_HAS_VERSION_11_0) || defined(CC_AST_HAS_VERSION_1_8) */
|
|