lcr/ss5.cpp

2106 lines
61 KiB
C++

/*****************************************************************************\
** **
** LCR **
** **
**---------------------------------------------------------------------------**
** Copyright: Andreas Eversberg **
** **
** mISDN ss5 **
** **
\*****************************************************************************/
/*
* STATES:
*
* there are three types of states
*
* - the port state (p_state): used for current call state
* - the ss5 state (p_m_s_state): used for current tone
* - the ss5 signal state (p_m_s_signal): used for current signal state of current tone
*
* the port state differs from isdn state:
*
* - PORT_STATE_IDLE: used until number is complete. outgoing overlap dialing is received in this state.
* - PORT_STATE_OUT_SETUP: the seizing procedure is started.
* - PORT_STATE_OUT_OVERLAP: the transmitter is sending the digits.
* - PORT_STATE_OUT_PROCEEDING: the digits are sent, we wait until someone answers.
* - PORT_STATE_OUT_DISCONNECT: a clear-back is sent, after DISCONNECT was received
* - PORT_STATE_CONNECT: a call is answered on either side.
* - PORT_STATE_IN_SETUP: the seizing is received, we wait for the first digit.
* - PORT_STATE_IN_OVERLAP: the digits are received.
* - PORT_STATE_IN_PROCEEDING: the number is complete, a SETUP is indicated.
* - PORT_STATE_IN_DISCONNECT: a clear-back was received, an DISCONNECT is indicated.
* - PORT_STATE_RELEASE: the clear forward procedure is started.
*
*/
#include "main.h"
//#define DEBUG_DETECT
/* ss5 signal states */
enum {
SS5_STATE_IDLE, /* no signal */
SS5_STATE_SEIZING, /* seizing */
SS5_STATE_PROCEED_TO_SEND, /* proceed-to-send */
SS5_STATE_BUSY_FLASH, /* busy-flash / clear back */
SS5_STATE_ACK_BUSY_FLASH, /* acknowledge of busy/answer/clear-back */
SS5_STATE_ANSWER, /* answer */
SS5_STATE_ACK_ANSWER, /* acknowledge of busy/answer/clear-back */
SS5_STATE_FORWARD_TRANSFER, /* forward transfer */
SS5_STATE_CLEAR_BACK, /* clear-back */
SS5_STATE_ACK_CLEAR_BACK, /* acknowledge of busy/answer/clear-back */
SS5_STATE_CLEAR_FORWARD, /* clear-forward */
SS5_STATE_RELEASE_GUARD, /* release-guard */
SS5_STATE_DIAL_OUT, /* dialing state (transmitter) */
SS5_STATE_DIAL_IN, /* dialing state (receiver) */
SS5_STATE_DIAL_IN_PULSE, /* dialing state (receiver with pulses) */
SS5_STATE_DELAY, /* after signal wait until next signal can be sent */
SS5_STATE_DOUBLE_SEIZE, /* in case of a double seize, we make the remote size recognize it */
};
const char *ss5_state_name[] = {
"STATE_IDLE",
"STATE_SEIZING",
"STATE_PROCEED_TO_SEND",
"STATE_BUSY_FLASH",
"STATE_ACK_BUSY_FLASH",
"STATE_ANSWER",
"STATE_ACK_ANSWER",
"STATE_FORWARD_TRANSFER",
"STATE_CLEAR_BACK",
"STATE_ACK_CLEAR_BACK",
"STATE_CLEAR_FORWARD",
"STATE_RELEASE_GUARD",
"STATE_DIAL_OUT",
"STATE_DIAL_IN",
"STATE_DIAL_IN_PULSE",
"STATE_DELAY",
"STATE_DOUBLE_SEIZE",
};
enum {
SS5_SIGNAL_NULL,
/* sending signal states */
SS5_SIGNAL_SEND_ON, /* sending signal, waiting for acknowledge */
SS5_SIGNAL_SEND_ON_RECOG, /* sending signal, receiving ack, waiting for recogition timer */
SS5_SIGNAL_SEND_OFF, /* silence, receiving ack, waiting for stop */
/* receiving signal states */
SS5_SIGNAL_RECEIVE_RECOG, /* receiving signal, waiting for recognition timer */
SS5_SIGNAL_RECEIVE, /* receiving signal / send ack, waiting for stop */
SS5_SIGNAL_DELAY, /* delay after release guard to prevent ping-pong */
/* sending / receiving digit states */
SS5_SIGNAL_DIGIT_PAUSE, /* pausing before sending (next) digit */
SS5_SIGNAL_DIGIT_ON, /* sending digit */
SS5_SIGNAL_PULSE_OFF, /* make */
SS5_SIGNAL_PULSE_ON, /* break */
};
const char *ss5_signal_name[] = {
"NULL",
"SIGNAL_SEND_ON",
"SIGNAL_SEND_ON_RECOG",
"SIGNAL_SEND_OFF",
"SIGNAL_RECEIVE_RECOG",
"SIGNAL_RECEIVE",
"SIGNAL_DELAY",
"SIGNAL_DIGIT_PAUSE",
"SIGNAL_DIGIT_ON",
"SIGNAL_PULSE_OFF",
"SIGNAL_PULSE_ON",
};
/* ss5 signal timers (in samples) */
#define SS5_TIMER_AFTER_SIGNAL (100*8) /* wait after signal is terminated */
#define SS5_TIMER_KP (100*8) /* duration of KP1 or KP2 digit */
#define SS5_TIMER_DIGIT (55*8) /* duration of all other digits */
#define SS5_TIMER_PAUSE (55*8) /* pause between digits */
#define SS5_TIMER_FORWARD (850*8) /* forward transfer length */
#define SS5_TIMER_RECOG_SEIZE (40*8) /* recognition time of seizing / proceed-to-send signal */
#define SS5_TIMER_RECOG_OTHER (125*8) /* recognition time of all other f1/f2 signals */
#define SS5_TIMER_SIGNAL_LOSS (15*8) /* minimum time of signal loss for a continous signal */
#define SS5_TIMER_DOUBLE_SEIZE (850*8) /* double seize length */
#define SS5_TIMER_RELEASE_GUARD (850*8) /* be sure to release after clear-forward */
#define SS5_TIMER_RELEASE_MAX (2000*8)/* maximum time for release guard to prevent 'double-releasing' */
#define SS5_TIMER_RELEASE_DELAY (4000*8)/* wait after release guard to prevent ping-pong */
#define BELL_TIMER_BREAK (50*8) /* loop open, tone */
#define BELL_TIMER_MAKE (50*8) /* loop closed, no tone */
#define BELL_TIMER_PAUSE (800*8) /* interdigit delay */
#define BELL_TIMER_RECOG_HANGUP (200*8) /* time to recognize hangup */
#define BELL_TIMER_RECOG_END (300*8) /* recognize end of digit */
/* ss5 timers */
#define SS5_TIMER_OVERLAP 10 /* timeout for overlap digits received on incomming exchange */
#define SS5_TIMER_RELEASE 20 /* timeout after disconnect on incomming exchange */
/*
* ss5 trace header
*/
enum { /* even values are indications, odd values are requests */
SS5_SEIZING_IND,
SS5_SEIZING_REQ,
SS5_PROCEED_TO_SEND_IND,
SS5_PROCEED_TO_SEND_REQ,
SS5_BUSY_FLASH_IND,
SS5_BUSY_FLASH_REQ,
SS5_ANSWER_IND,
SS5_ANSWER_REQ,
SS5_CLEAR_BACK_IND,
SS5_CLEAR_BACK_REQ,
SS5_CLEAR_FORWARD_IND,
SS5_CLEAR_FORWARD_REQ,
SS5_RELEASE_GUARD_IND,
SS5_RELEASE_GUARD_REQ,
SS5_ACKNOWLEDGE_IND,
SS5_ACKNOWLEDGE_REQ,
SS5_DOUBLE_SEIZURE_IND,
SS5_DOUBLE_SEIZURE_REQ,
SS5_DIALING_IND,
SS5_DIALING_REQ,
SS5_FORWARD_TRANSFER_IND,
SS5_FORWARD_TRANSFER_REQ,
SS5_TIMEOUT_IND,
SS5_QUALITY_IND,
};
static struct isdn_message {
const char *name;
unsigned int value;
} ss5_message[] = {
{"SEIZING RECEIVED", SS5_SEIZING_IND},
{"SEIZING SENDING", SS5_SEIZING_REQ},
{"PROCEED-TO-SEND RECEIVED", SS5_PROCEED_TO_SEND_IND},
{"PROCEED-TO-SEND SENDING", SS5_PROCEED_TO_SEND_REQ},
{"BUSY-FLASH RECEIVED", SS5_BUSY_FLASH_IND},
{"BUSY-FLASH SENDING", SS5_BUSY_FLASH_REQ},
{"ANSWER RECEIVED", SS5_ANSWER_IND},
{"ANSWER SENDING", SS5_ANSWER_REQ},
{"CLEAR-BACK RECEIVED", SS5_CLEAR_BACK_IND},
{"CLEAR-BACK SENDING", SS5_CLEAR_BACK_REQ},
{"CLEAR-FORWARD RECEIVED", SS5_CLEAR_FORWARD_IND},
{"CLEAR-FORWARD SENDING", SS5_CLEAR_FORWARD_REQ},
{"RELEASE-GUARD RECEIVED", SS5_RELEASE_GUARD_IND},
{"RELEASE-GUARD SENDING", SS5_RELEASE_GUARD_REQ},
{"ACKNOWLEDGE RECEIVED", SS5_ACKNOWLEDGE_IND},
{"ACKNOWLEDGE SENDING", SS5_ACKNOWLEDGE_REQ},
{"DOUBLE-SEIZURE RECEIVED", SS5_DOUBLE_SEIZURE_IND},
{"DOUBLE-SEIZURE SENDING", SS5_DOUBLE_SEIZURE_REQ},
{"DIALING RECEIVED", SS5_DIALING_IND},
{"DIALING SENDING", SS5_DIALING_REQ},
{"FORWARD-TRANSFER RECEIVED", SS5_FORWARD_TRANSFER_IND},
{"FORWARD-TRANSFER SENDING", SS5_FORWARD_TRANSFER_REQ},
{"TIMEOUT", SS5_TIMEOUT_IND},
{"QUALITY REPORT", SS5_QUALITY_IND},
{NULL, 0},
};
static void ss5_trace_header(struct mISDNport *mISDNport, class PmISDN *port, unsigned int msg, int channel)
{
int i;
char msgtext[64];
SCPY(msgtext, "<<UNKNOWN MESSAGE>>");
/* select message and primitive text */
i = 0;
while(ss5_message[i].name) {
// if (msg == L3_NOTIFY_REQ) printf("val = %x %s\n", isdn_message[i].value, isdn_message[i].name);
if (ss5_message[i].value == msg) {
SCPY(msgtext, ss5_message[i].name);
break;
}
i++;
}
/* init trace with given values */
start_trace(mISDNport?mISDNport->portnum:-1,
mISDNport?(mISDNport->ifport?mISDNport->ifport->interface:NULL):NULL,
port?numberrize_callerinfo(port->p_callerinfo.id, port->p_callerinfo.ntype, options.national, options.international):NULL,
port?port->p_dialinginfo.id:NULL,
(msg&1)?DIRECTION_OUT:DIRECTION_IN,
CATEGORY_CH,
port?port->p_serial:0,
msgtext);
add_trace("channel", NULL, "%d", channel);
switch (port->p_type) {
case PORT_TYPE_SS5_OUT:
add_trace("state", NULL, "outgoing");
break;
case PORT_TYPE_SS5_IN:
add_trace("state", NULL, "incomming");
break;
default:
add_trace("state", NULL, "idle");
break;
}
}
/*
* changes release tone into silence
* this makes the line sound more authentic
*/
void Pss5::set_tone(const char *dir, const char *name)
{
if (name && !strcmp(name, "cause_10"))
name = NULL;
PmISDN::set_tone(dir, name);
}
/*
* creation of static channels
*/
void ss5_create_channel(struct mISDNport *mISDNport, int i)
{
class Pss5 *ss5port;
char portname[32];
struct port_settings port_settings;
SPRINT(portname, "%s-%d", mISDNport->name, i+1);
memset(&port_settings, 0, sizeof(port_settings));
SCPY(port_settings.tones_dir, options.tones_dir);
ss5port = new Pss5(PORT_TYPE_SS5_IDLE, mISDNport, portname, &port_settings, mISDNport->ifport->interface, i + (i>=15) + 1, 1, B_MODE_TRANSPARENT);
if (!ss5port)
FATAL("No memory for Pss5 class.\n");
if (!ss5port->p_m_b_channel)
FATAL("No bchannel on given index.\n");
/* connect channel */
bchannel_event(mISDNport, ss5port->p_m_b_index, B_EVENT_USE);
}
/*
* hunt for a free line
* this function returns a port object in idle state.
*/
class Pss5 *ss5_hunt_line(struct mISDNport *mISDNport)
{
int i;
class Port *port;
class Pss5 *ss5port = NULL;
struct select_channel *selchannel;
PDEBUG(DEBUG_SS5, "Entered name=%s\n", mISDNport->name);
selchannel = mISDNport->ifport->out_channel;
while(selchannel) {
switch(selchannel->channel) {
case CHANNEL_FREE: /* free channel */
case CHANNEL_ANY: /* any channel */
for (i = 0; i < mISDNport->b_num; i++) {
port = mISDNport->b_port[i];
PDEBUG(DEBUG_SS5, "Checking port %p on index\n", port, i);
if (!port)
continue;
if (port->p_type == PORT_TYPE_SS5_IN || port->p_type == PORT_TYPE_SS5_OUT)
PDEBUG(DEBUG_SS5, "Checking port %s: channel %d not available, because port not idle type.\n",
mISDNport->name, i);
if (port->p_type != PORT_TYPE_SS5_IDLE)
continue;
ss5port = (class Pss5 *)port;
/* is really idle ? */
if (ss5port->p_state == PORT_STATE_IDLE
&& ss5port->p_m_s_state == SS5_STATE_IDLE)
return ss5port;
PDEBUG(DEBUG_SS5, "Checking port %s: channel %d not available, because p_state=%d, ss5_state=%d.\n",
mISDNport->name, i, ss5port->p_state,ss5port->p_m_s_state);
}
PDEBUG(DEBUG_SS5, "no free interface\n");
return NULL;
case CHANNEL_NO:
break;
default:
if (selchannel->channel<1 || selchannel->channel==16)
break;
i = selchannel->channel-1-(selchannel->channel>=17);
if (i >= mISDNport->b_num)
break;
port = mISDNport->b_port[i];
if (!port)
break;
if (port->p_type == PORT_TYPE_SS5_IN || port->p_type == PORT_TYPE_SS5_OUT)
PDEBUG(DEBUG_SS5, "Checking port %s: channel %d not available, because port not idle type.\n",
mISDNport->name, i);
if (port->p_type != PORT_TYPE_SS5_IDLE)
break;
ss5port = (class Pss5 *)port;
/* is really idle ? */
if (ss5port->p_state == PORT_STATE_IDLE
&& ss5port->p_m_s_state == SS5_STATE_IDLE)
return ss5port;
PDEBUG(DEBUG_SS5, "Checking port %s: channel %d not available, because p_state=%d, ss5_state=%d.\n",
mISDNport->name, i, ss5port->p_state,ss5port->p_m_s_state);
}
selchannel = selchannel->next;
}
PDEBUG(DEBUG_SS5, "no free interface in channel list\n");
return NULL;
}
static int timeout(struct lcr_timer *timer, void *instance, int i)
{
class Pss5 *ss5 = (class Pss5 *)instance;
ss5->register_timeout();
return 0;
}
int queue_event(struct lcr_work *work, void *instance, int index)
{
class Pss5 *ss5port = (class Pss5 *)instance;
ss5port->process_queue();
return 0;
}
/*
* constructor
*/
Pss5::Pss5(int type, struct mISDNport *mISDNport, char *portname, struct port_settings *settings, struct interface *interface, int channel, int exclusive, int mode) : PmISDN(type, mISDNport, portname, settings, interface, channel, exclusive, mode)
{
p_callerinfo.itype = (mISDNport->ifport->interface->extension)?INFO_ITYPE_ISDN_EXTENSION:INFO_ITYPE_ISDN;
p_m_s_state = SS5_STATE_IDLE;
p_m_s_signal = SS5_SIGNAL_NULL;
p_m_s_dial[0] = '\0';
p_m_s_digit_i = 0;
p_m_s_pulsecount = 0;
p_m_s_last_digit = ' ';
p_m_s_last_digit_used = ' ';
p_m_s_signal_loss = 0;
p_m_s_decoder_count = 0;
//p_m_s_decoder_buffer;
p_m_s_sample_nr = 0;
p_m_s_quality_value = 0;
p_m_s_quality_count = 0;
p_m_s_recog = 0;
memset(&p_m_s_queue, 0, sizeof(p_m_s_queue));
add_work(&p_m_s_queue, queue_event, this, 0);
p_m_s_queued_signal = 0;
memset(p_m_s_delay_digits, ' ', sizeof(p_m_s_delay_digits));
memset(p_m_s_delay_mute, ' ', sizeof(p_m_s_delay_mute));
memset(&p_m_s_timer, 0, sizeof(p_m_s_timer));
add_timer(&p_m_s_timer, timeout, this, 0);
/* turn on signalling receiver */
inband_receive_on();
PDEBUG(DEBUG_SS5, "Created new mISDNPort(%s). Currently %d objects use.\n", portname, mISDNport->use);
}
/*
* destructor
*/
Pss5::~Pss5()
{
del_timer(&p_m_s_timer);
del_work(&p_m_s_queue);
}
/*
* timeout trigger
*/
void Pss5::register_timeout(void)
{
ss5_trace_header(p_m_mISDNport, this, SS5_TIMEOUT_IND, p_m_b_channel);
end_trace();
switch(p_state) {
case PORT_STATE_IN_SETUP:
PDEBUG(DEBUG_SS5, "%s: timeout after seize\n", p_name);
do_release(CAUSE_UNSPECIFIED, LOCATION_PRIVATE_LOCAL, SS5_CLEAR_BACK_REQ);
break;
case PORT_STATE_IN_OVERLAP:
PDEBUG(DEBUG_SS5, "%s: timeout during dialing\n", p_name);
do_release(CAUSE_UNSPECIFIED, LOCATION_PRIVATE_LOCAL, SS5_CLEAR_BACK_REQ);
break;
case PORT_STATE_OUT_DISCONNECT:
PDEBUG(DEBUG_SS5, "%s: timeout after sending busy flash / clear forward\n", p_name);
/* always send clear forward, because release guard only works as a reply to clear forward */
do_release(CAUSE_UNSPECIFIED, LOCATION_PRIVATE_LOCAL, SS5_CLEAR_FORWARD_REQ);
break;
}
}
/*
* change port state
*/
void Pss5::new_state(int state)
{
switch(state) {
case PORT_STATE_IN_SETUP:
case PORT_STATE_IN_OVERLAP:
if (SS5_TIMER_OVERLAP == 0)
break;
PDEBUG(DEBUG_SS5, "%s: starting timeout timer with %d seconds\n", p_name, SS5_TIMER_OVERLAP);
schedule_timer(&p_m_s_timer, SS5_TIMER_OVERLAP, 0);
break;
case PORT_STATE_OUT_DISCONNECT:
if (p_type != PORT_TYPE_SS5_IN)
break;
if (SS5_TIMER_RELEASE == 0)
break;
PDEBUG(DEBUG_SS5, "%s: starting timeout timer with %d seconds\n", p_name, SS5_TIMER_RELEASE);
schedule_timer(&p_m_s_timer, SS5_TIMER_RELEASE, 0);
break;
default:
PDEBUG(DEBUG_SS5, "%s: stopping timeout timer\n", p_name);
unsched_timer(&p_m_s_timer);
}
Port::new_state(state);
}
/*
* change ss5 states
*/
void Pss5::_new_ss5_state(int state, const char *func, int line)
{
PDEBUG(DEBUG_SS5, "%s(%s:%d): changing SS5 state from %s to %s\n", p_name, func, line, ss5_state_name[p_m_s_state], ss5_state_name[state]);
p_m_s_state = state;
p_m_s_signal = SS5_SIGNAL_NULL;
if (p_m_s_state == SS5_STATE_IDLE && p_m_s_queued_signal)
trigger_work(&p_m_s_queue);
}
void Pss5::process_queue(void)
{
/* when clear forward is scheduled, we abort current state */
if (p_m_s_queued_signal == SS5_CLEAR_FORWARD_REQ)
new_ss5_state(SS5_STATE_IDLE);
/* if there is an ongoing signal, we wait until done */
if (p_m_s_state != SS5_STATE_IDLE)
return;
/* this shoud not happen */
if (!p_m_s_queued_signal)
return;
/* start signal */
ss5_trace_header(p_m_mISDNport, this, p_m_s_queued_signal, p_m_b_channel);
end_trace();
switch(p_m_s_queued_signal) {
case SS5_ANSWER_REQ:
/* start answer */
p_m_s_queued_signal = 0; /* prevent trigger loop */
start_signal(SS5_STATE_ANSWER);
break;
case SS5_BUSY_FLASH_REQ:
/* busy flash */
p_m_s_queued_signal = 0; /* prevent trigger loop */
start_signal(SS5_STATE_BUSY_FLASH);
break;
case SS5_CLEAR_BACK_REQ:
/* clear back */
p_m_s_queued_signal = 0; /* prevent trigger loop */
start_signal(SS5_STATE_CLEAR_BACK);
break;
case SS5_CLEAR_FORWARD_REQ:
/* clear forward */
p_m_s_queued_signal = 0; /* prevent trigger loop */
start_signal(SS5_STATE_CLEAR_FORWARD);
break;
default:
PERROR("unhandled event %d\n", p_m_s_queued_signal);
p_m_s_queued_signal = 0;
}
}
void Pss5::_new_ss5_signal(int signal, const char *func, int line)
{
if (p_m_s_signal)
PDEBUG(DEBUG_SS5, "%s: changing SS5 signal state from %s to %s\n", p_name, ss5_signal_name[p_m_s_signal], ss5_signal_name[signal]);
else
PDEBUG(DEBUG_SS5, "%s: changing SS5 signal state to %s\n", p_name, ss5_signal_name[signal]);
p_m_s_signal = signal;
}
/*
* signalling receiver
*
* this function will be called for every audio received.
*/
void Pss5::inband_receive(unsigned char *buffer, int len)
{
int count = 0, tocopy, space;
char digit;
double quality;
int mute = 0;
again:
/* how much to copy ? */
tocopy = len - count;
space = SS5_DECODER_NPOINTS - p_m_s_decoder_count;
if (space < 0)
FATAL("p_m_s_decoder_count overflows\n");
if (space < tocopy)
tocopy = space;
/* copy an count */
memcpy(p_m_s_decoder_buffer+p_m_s_decoder_count, buffer+count, tocopy);
p_m_s_decoder_count += tocopy;
count += tocopy;
/* decoder buffer not completely filled ? */
if (tocopy < space)
return;
/* decode one frame */
digit = ss5_decode(p_m_s_decoder_buffer, SS5_DECODER_NPOINTS, &quality);
p_m_s_decoder_count = 0;
/* indicate quality of received digit */
if ((p_m_mISDNport->ss5 & SS5_FEATURE_QUALITY)) {
if (digit != ' ') {
p_m_s_quality_value += quality;
p_m_s_quality_count++;
} else if (p_m_s_quality_count) {
ss5_trace_header(p_m_mISDNport, this, SS5_QUALITY_IND, p_m_b_channel);
add_trace("digit", NULL, "%c", p_m_s_last_digit);
quality = p_m_s_quality_value/p_m_s_quality_count;
add_trace("quality", NULL, "%3d%%", (int)(quality*100.0));
end_trace();
p_m_s_quality_value = 0;
p_m_s_quality_count = 0;
}
}
#ifdef DEBUG_DETECT
if (p_m_s_last_digit != digit && digit != ' ')
PDEBUG(DEBUG_SS5, "%s: detecting signal '%c' start (state=%s signal=%s)\n", p_name, digit, ss5_state_name[p_m_s_state], ss5_signal_name[p_m_s_signal]);
#endif
/* ignore short loss of signal, or change within one decode window */
if (p_m_s_signal_loss) {
if (digit == ' ') {
/* still lost */
if (p_m_s_signal_loss >= SS5_TIMER_SIGNAL_LOSS) {
#ifdef DEBUG_DETECT
PDEBUG(DEBUG_SS5, "%s: signal '%c' lost too long\n", p_name, p_m_s_last_digit);
#endif
/* long enough, we stop loss-timer */
p_m_s_signal_loss = 0;
} else {
/* not long enough, so we use last signal */
p_m_s_signal_loss += SS5_DECODER_NPOINTS;
digit = p_m_s_last_digit;
}
} else {
/* signal is back, we stop timer and store */
#ifdef DEBUG_DETECT
PDEBUG(DEBUG_SS5, "%s: signal '%c' lost, but continues with '%c'\n", p_name, p_m_s_last_digit, digit);
#endif
p_m_s_signal_loss = 0;
p_m_s_last_digit = digit;
}
} else {
if (p_m_s_last_digit != ' ' && digit == ' ') {
#ifdef DEBUG_DETECT
PDEBUG(DEBUG_SS5, "%s: signal '%c' lost\n", p_name, p_m_s_last_digit);
#endif
/* restore last digit until signal is really lost */
p_m_s_last_digit = digit;
/* starting to loose signal */
p_m_s_signal_loss = SS5_DECODER_NPOINTS;
} else if (digit != p_m_s_last_digit) {
/* digit changes, but we keep old digit until it is detected twice */
#ifdef DEBUG_DETECT
PDEBUG(DEBUG_SS5, "%s: signal '%c' changes to '%c'\n", p_name, p_m_s_last_digit, digit);
#endif
p_m_s_last_digit = digit;
digit = p_m_s_last_digit_used;
} else {
/* storing last signal, in case it is lost */
p_m_s_last_digit = digit;
}
}
p_m_s_last_digit_used = digit;
/* delay decoded tones */
if ((p_m_mISDNport->ss5 & SS5_FEATURE_DELAY)) {
/* shift buffer */
memcpy(p_m_s_delay_digits, p_m_s_delay_digits+1, sizeof(p_m_s_delay_digits)-1);
/* first in */
p_m_s_delay_digits[sizeof(p_m_s_delay_digits)-1] = digit;
/* first out */
digit = p_m_s_delay_digits[0];
}
/* clear forward is always recognized */
if (digit == 'C' && p_m_s_state != SS5_STATE_CLEAR_FORWARD && p_m_s_state != SS5_STATE_RELEASE_GUARD) {
switch (p_type) {
case PORT_TYPE_SS5_OUT:
PDEBUG(DEBUG_SS5, "%s: received release-guard, waiting for recognition\n", p_name);
break;
case PORT_TYPE_SS5_IN:
PDEBUG(DEBUG_SS5, "%s: received clear-forward, waiting for recognition\n", p_name);
break;
default:
PDEBUG(DEBUG_SS5, "%s: received clear-forward in idle state, waiting for recognition\n", p_name);
break;
}
new_ss5_state(SS5_STATE_RELEASE_GUARD);
new_ss5_signal(SS5_SIGNAL_RECEIVE_RECOG);
p_m_s_recog = 0;
} else
switch(p_m_s_state) {
case SS5_STATE_IDLE:
/* seizing only recognized in port idle state */
if (p_state == PORT_STATE_IDLE) {
if (digit != 'A')
break;
seize:
PDEBUG(DEBUG_SS5, "%s: received seize, waiting for recognition\n", p_name);
p_type = PORT_TYPE_SS5_IN;
new_ss5_state(SS5_STATE_PROCEED_TO_SEND);
new_ss5_signal(SS5_SIGNAL_RECEIVE_RECOG);
p_m_s_recog = 0;
break;
}
/* other signals */
if (digit == 'A') {
if (p_type != PORT_TYPE_SS5_OUT)
break;
PDEBUG(DEBUG_SS5, "%s: received answer, waiting for recognition\n", p_name);
new_ss5_state(SS5_STATE_ACK_ANSWER);
new_ss5_signal(SS5_SIGNAL_RECEIVE_RECOG);
p_m_s_recog = 0;
break;
}
if (digit == 'B') {
if (p_type == PORT_TYPE_SS5_IN) {
if ((p_m_mISDNport->ss5 & SS5_FEATURE_BELL)) {
new_ss5_state(SS5_STATE_DIAL_IN_PULSE); /* go pulsing state */
new_ss5_signal(SS5_SIGNAL_PULSE_OFF); /* we are starting with pulse off */
p_m_s_pulsecount = 0; /* init pulse counter */
p_m_s_dial[0] = '\0'; /* init dial string */
pulse_ind(1); /* also inits recogition timer... */
break;
}
PDEBUG(DEBUG_SS5, "%s: received forward-transfer, waiting for recognition\n", p_name);
/* forward transfer on incomming lines */
new_ss5_state(SS5_STATE_FORWARD_TRANSFER);
new_ss5_signal(SS5_SIGNAL_RECEIVE_RECOG);
p_m_s_recog = 0;
break;
}
if (p_state == PORT_STATE_CONNECT) {
PDEBUG(DEBUG_SS5, "%s: received clear-back, waiting for recognition\n", p_name);
new_ss5_state(SS5_STATE_ACK_CLEAR_BACK);
} else {
PDEBUG(DEBUG_SS5, "%s: received busy-flash, waiting for recognition\n", p_name);
new_ss5_state(SS5_STATE_ACK_BUSY_FLASH);
}
new_ss5_signal(SS5_SIGNAL_RECEIVE_RECOG);
p_m_s_recog = 0;
break;
}
/* dialing only allowed in incomming setup state */
if (p_state == PORT_STATE_IN_SETUP) {
if (!strchr("1234567890*#abc", digit))
break;
PDEBUG(DEBUG_SS5, "%s: received dialing start with '%c'\n", p_name, digit);
new_ss5_state(SS5_STATE_DIAL_IN);
new_ss5_signal(SS5_SIGNAL_DIGIT_ON);
p_m_s_dial[0] = '\0';
digit_ind(digit);
break;
}
break;
/* sending seizing */
case SS5_STATE_SEIZING:
switch (p_m_s_signal) {
case SS5_SIGNAL_SEND_ON:
if (digit == 'A') { /* double seize */
PDEBUG(DEBUG_SS5, "%s: received double seizure\n", p_name, digit);
double_seizure_ind();
break;
}
if (digit == 'B') {
PDEBUG(DEBUG_SS5, "%s: received answer to outgoing seize, waiting for recognition\n", p_name);
/* set recognition timer */
new_ss5_signal(SS5_SIGNAL_SEND_ON_RECOG);
p_m_s_recog = 0;
}
break;
case SS5_SIGNAL_SEND_ON_RECOG:
if (digit != 'B') { /* seize */
PDEBUG(DEBUG_SS5, "%s: answer to outgoing seize is gone before recognition\n", p_name);
new_ss5_signal(SS5_SIGNAL_SEND_ON);
// p_m_s_sample_nr = 0;
// inband_send_on();
break;
}
p_m_s_recog += SS5_DECODER_NPOINTS;
if (p_m_s_recog < SS5_TIMER_RECOG_SEIZE)
break;
PDEBUG(DEBUG_SS5, "%s: answer to outgoing seize recognized, turning off, waiting for recognition\n", p_name);
new_ss5_signal(SS5_SIGNAL_SEND_OFF);
break;
case SS5_SIGNAL_SEND_OFF:
if (digit == 'B')
break;
PDEBUG(DEBUG_SS5, "%s: outgoing seizure is complete, proceeding...\n", p_name);
new_ss5_state(SS5_STATE_IDLE);
proceed_to_send_ind();
break;
}
break;
/* answer to seize */
case SS5_STATE_PROCEED_TO_SEND:
if (p_m_s_signal == SS5_SIGNAL_RECEIVE_RECOG) {
if (digit != 'A') {
PDEBUG(DEBUG_SS5, "%s: incomming seize is gone before recognition\n", p_name);
new_ss5_state(SS5_STATE_IDLE);
p_type = PORT_TYPE_SS5_IDLE;
break;
}
p_m_s_recog += SS5_DECODER_NPOINTS;
if (p_m_s_recog < SS5_TIMER_RECOG_SEIZE)
break;
PDEBUG(DEBUG_SS5, "%s: incomming seize is recognized, responding...\n", p_name);
new_ss5_signal(SS5_SIGNAL_RECEIVE);
p_m_s_sample_nr = 0;
inband_send_on();
break;
}
if (digit != 'A') {
PDEBUG(DEBUG_SS5, "%s: incomming seize is gone after responding\n", p_name);
new_ss5_state(SS5_STATE_IDLE);
seizing_ind();
}
break;
/* sending busy flash / answer / clear-back */
case SS5_STATE_BUSY_FLASH:
case SS5_STATE_ANSWER:
case SS5_STATE_CLEAR_BACK:
switch (p_m_s_signal) {
case SS5_SIGNAL_SEND_ON:
if (digit == 'A') {
PDEBUG(DEBUG_SS5, "%s: received acknowledge, waiting for recognition\n", p_name);
/* set recognition timer */
new_ss5_signal(SS5_SIGNAL_SEND_ON_RECOG);
p_m_s_recog = 0;
}
break;
case SS5_SIGNAL_SEND_ON_RECOG:
if (digit != 'A') {
PDEBUG(DEBUG_SS5, "%s: acknowledge is gone before recognition\n", p_name);
new_ss5_signal(SS5_SIGNAL_SEND_ON);
// p_m_s_sample_nr = 0;
// inband_send_on();
break;
}
p_m_s_recog += SS5_DECODER_NPOINTS;
if (p_m_s_recog < SS5_TIMER_RECOG_OTHER)
break;
PDEBUG(DEBUG_SS5, "%s: acknowledge recognized, turning off, waiting for recognition\n", p_name);
new_ss5_signal(SS5_SIGNAL_SEND_OFF);
break;
case SS5_SIGNAL_SEND_OFF:
if (digit == 'A')
break;
PDEBUG(DEBUG_SS5, "%s: outgoing signal is complete\n", p_name);
new_ss5_state(SS5_STATE_IDLE);
break;
}
break;
/* answer to busy-flash / clear back */
case SS5_STATE_ACK_BUSY_FLASH:
case SS5_STATE_ACK_CLEAR_BACK:
if (p_m_s_signal == SS5_SIGNAL_RECEIVE_RECOG) {
if (digit != 'B') {
PDEBUG(DEBUG_SS5, "%s: incomming clear-back/busy-flash is gone before recognition\n", p_name);
new_ss5_state(SS5_STATE_IDLE);
break;
}
p_m_s_recog += SS5_DECODER_NPOINTS;
if (p_m_s_recog < SS5_TIMER_RECOG_OTHER)
break;
PDEBUG(DEBUG_SS5, "%s: incomming clear-back/busy-flash is recognized, responding...\n", p_name);
new_ss5_signal(SS5_SIGNAL_RECEIVE);
p_m_s_sample_nr = 0;
inband_send_on();
break;
}
if (digit != 'B') {
PDEBUG(DEBUG_SS5, "%s: incomming clear-back/busy-flash is gone after responding\n", p_name);
new_ss5_state(SS5_STATE_IDLE);
if (p_m_s_state == SS5_STATE_ACK_BUSY_FLASH)
busy_flash_ind();
else
clear_back_ind();
}
break;
/* answer to answer */
case SS5_STATE_ACK_ANSWER:
if (p_m_s_signal == SS5_SIGNAL_RECEIVE_RECOG) {
if (digit != 'A') {
PDEBUG(DEBUG_SS5, "%s: incomming answer is gone before recognition\n", p_name);
new_ss5_state(SS5_STATE_IDLE);
break;
}
p_m_s_recog += SS5_DECODER_NPOINTS;
if (p_m_s_recog < SS5_TIMER_RECOG_OTHER)
break;
PDEBUG(DEBUG_SS5, "%s: incomming answer is recognized, responding...\n", p_name);
new_ss5_signal(SS5_SIGNAL_RECEIVE);
p_m_s_sample_nr = 0;
inband_send_on();
break;
}
if (digit != 'A') {
PDEBUG(DEBUG_SS5, "%s: incomming answer is gone after responding\n", p_name);
new_ss5_state(SS5_STATE_IDLE);
answer_ind();
}
break;
/* sending clear-forward */
case SS5_STATE_CLEAR_FORWARD:
switch (p_m_s_signal) {
case SS5_SIGNAL_SEND_ON:
if (digit == 'C') {
PDEBUG(DEBUG_SS5, "%s: received answer to clear-forward, waiting for recognition\n", p_name);
/* set recognition timer */
new_ss5_signal(SS5_SIGNAL_SEND_ON_RECOG);
p_m_s_recog = 0;
}
break;
case SS5_SIGNAL_SEND_ON_RECOG:
if (digit != 'C') {
PDEBUG(DEBUG_SS5, "%s: answer to clear-forward is gone before recognition\n", p_name);
new_ss5_signal(SS5_SIGNAL_SEND_ON);
// p_m_s_sample_nr = 0;
// inband_send_on();
break;
}
p_m_s_recog += SS5_DECODER_NPOINTS;
if (p_m_s_recog < SS5_TIMER_RECOG_OTHER)
break;
PDEBUG(DEBUG_SS5, "%s: answer to clear-forward recognized, turning off, waiting for recognition\n", p_name);
new_ss5_signal(SS5_SIGNAL_SEND_OFF);
break;
case SS5_SIGNAL_SEND_OFF:
if (digit == 'A') {
PDEBUG(DEBUG_SS5, "%s: received seize right after clear-forward answer, continue with seize\n", p_name);
new_state(PORT_STATE_IDLE);
goto seize;
}
if (digit == 'C')
break;
PDEBUG(DEBUG_SS5, "%s: answer to clear-forward is complete\n", p_name);
release_guard_ind();
new_ss5_signal(SS5_SIGNAL_DELAY);
p_m_s_recog = 0; /* use recog to delay */
PDEBUG(DEBUG_SS5, "%s: answer to clear-forward on outgoing interface starting delay to prevent ping-pong\n", p_name);
break;
case SS5_SIGNAL_DELAY:
if (digit == 'A') {
PDEBUG(DEBUG_SS5, "%s: received seize right after clear-forward answer, continue with seize\n", p_name);
new_state(PORT_STATE_IDLE);
goto seize;
}
p_m_s_recog += SS5_DECODER_NPOINTS;
if (p_m_s_recog < SS5_TIMER_RELEASE_DELAY)
break;
PDEBUG(DEBUG_SS5, "%s: delay time over, going idle\n", p_name);
new_ss5_state(SS5_STATE_IDLE);
new_state(PORT_STATE_IDLE);
p_type = PORT_TYPE_SS5_IDLE;
break;
}
break;
/* answer to release-guard*/
case SS5_STATE_RELEASE_GUARD:
switch (p_m_s_signal) {
case SS5_SIGNAL_RECEIVE_RECOG:
if (digit != 'C') {
if (p_type == PORT_TYPE_SS5_OUT)
PDEBUG(DEBUG_SS5, "%s: incomming release-guard is gone before recognition\n", p_name);
else
PDEBUG(DEBUG_SS5, "%s: incomming clear forward is gone before recognition\n", p_name);
new_ss5_state(SS5_STATE_IDLE);
break;
}
p_m_s_recog += SS5_DECODER_NPOINTS;
if (p_m_s_recog < SS5_TIMER_RECOG_OTHER)
break;
if (p_type == PORT_TYPE_SS5_OUT)
PDEBUG(DEBUG_SS5, "%s: incomming release-guard is recognized, responding...\n", p_name);
else
PDEBUG(DEBUG_SS5, "%s: incomming clear-forward is recognized, responding...\n", p_name);
new_state(PORT_STATE_RELEASE);
new_ss5_signal(SS5_SIGNAL_RECEIVE);
p_m_s_sample_nr = 0;
inband_send_on();
break;
case SS5_SIGNAL_RECEIVE:
if (digit == 'C'
|| p_m_s_sample_nr < 256) /* small hack to keep answer for at least some time */
break;
#if 0
if (digit == 'A') {
PDEBUG(DEBUG_SS5, "%s: received seize right after clear-forward is received\n", p_name);
new_state(PORT_STATE_IDLE);
goto seize;
}
#endif
/* if clear forward stops right after recognition on the incomming side,
* the release guard signal stops and may be too short to be recognized at the outgoing side.
* to prevent this, a timer can be started to force a release guard that is long
* enough to be recognized on the outgoing side.
* this will prevent braking via blueboxing (other tricks may still be possible).
*/
if ((p_m_mISDNport->ss5 & SS5_FEATURE_RELEASEGUARDTIMER)
&& p_m_s_sample_nr < SS5_TIMER_RELEASE_GUARD)
break;
if (p_type == PORT_TYPE_SS5_OUT)
PDEBUG(DEBUG_SS5, "%s: incomming release-guard is gone after responding\n", p_name);
else
PDEBUG(DEBUG_SS5, "%s: incomming clear-forward is gone after responding\n", p_name);
if (p_type == PORT_TYPE_SS5_OUT) {
release_guard_ind();
new_ss5_signal(SS5_SIGNAL_DELAY);
p_m_s_recog = 0; /* use recog to delay */
PDEBUG(DEBUG_SS5, "%s: incomming release-guard on outgoing interface starting delay to prevent ping-pong\n", p_name);
} else {
clear_forward_ind();
new_ss5_state(SS5_STATE_IDLE);
}
break;
case SS5_SIGNAL_DELAY:
if (digit == 'A') {
PDEBUG(DEBUG_SS5, "%s: received seize right after release guard is gone, continue with seize\n", p_name);
new_state(PORT_STATE_IDLE);
goto seize;
}
p_m_s_recog += SS5_DECODER_NPOINTS;
if (p_m_s_recog < SS5_TIMER_RELEASE_DELAY)
break;
PDEBUG(DEBUG_SS5, "%s: delay time over, going idle\n", p_name);
new_ss5_state(SS5_STATE_IDLE);
new_state(PORT_STATE_IDLE);
p_type = PORT_TYPE_SS5_IDLE;
break;
}
break;
/* wait time to recognize forward transfer */
case SS5_STATE_FORWARD_TRANSFER:
if (p_m_s_signal == SS5_SIGNAL_RECEIVE_RECOG) {
if (digit != 'B') {
PDEBUG(DEBUG_SS5, "%s: incomming forward-transfer is gone before recognition\n", p_name);
new_ss5_state(SS5_STATE_IDLE);
break;
}
p_m_s_recog += SS5_DECODER_NPOINTS;
if (p_m_s_recog < SS5_TIMER_RECOG_OTHER)
break;
PDEBUG(DEBUG_SS5, "%s: incomming forward-transfer is recognized, responding, if BELL feature was selected...\n", p_name);
new_ss5_signal(SS5_SIGNAL_RECEIVE);
#if 0
p_m_s_sample_nr = 0;
inband_send_on();
#endif
break;
}
if (digit != 'B') {
PDEBUG(DEBUG_SS5, "%s: incomming forward-transfer is gone after recognition\n", p_name);
new_ss5_state(SS5_STATE_IDLE);
forward_transfer_ind();
break;
}
break;
/* dialing is received */
case SS5_STATE_DIAL_IN:
if (strchr("1234567890*#abc", digit)) {
if (p_m_s_signal != SS5_SIGNAL_DIGIT_PAUSE)
break;
PDEBUG(DEBUG_SS5, "%s: incomming digit '%c' is recognized\n", p_name, digit);
new_ss5_signal(SS5_SIGNAL_DIGIT_ON);
digit_ind(digit);
} else {
if (p_m_s_signal != SS5_SIGNAL_DIGIT_ON)
break;
PDEBUG(DEBUG_SS5, "%s: incomming digit is gone after recognition\n", p_name);
new_ss5_signal(SS5_SIGNAL_DIGIT_PAUSE);
}
break;
case SS5_STATE_DIAL_IN_PULSE:
if (digit == 'B')
pulse_ind(1);
else
pulse_ind(0);
break;
}
/* update mute on RX */
if ((p_m_mISDNport->ss5 & SS5_FEATURE_MUTE_RX)) {
int mdigit;
memcpy(p_m_s_delay_mute, p_m_s_delay_mute+1, sizeof(p_m_s_delay_mute)-1);
p_m_s_delay_mute[sizeof(p_m_s_delay_mute)-1] = digit;
mdigit = p_m_s_delay_mute[0];
if (mdigit == 'A' || mdigit == 'B' || mdigit == 'C')
mute = 1;
}
/* mute when TX */
if ((p_m_mISDNport->ss5 & SS5_FEATURE_MUTE_TX)) {
switch(p_m_s_signal) {
case SS5_SIGNAL_SEND_ON_RECOG:
case SS5_SIGNAL_RECEIVE_RECOG:
if (p_m_s_recog > SS5_DELAY_MUTE)
mute = 1;
break;
case SS5_SIGNAL_SEND_OFF:
case SS5_SIGNAL_RECEIVE:
mute = 1;
break;
}
}
/* apply mute state */
if (p_m_mute) {
/* mute is on */
if (!mute)
mute_off();
} else {
/* mute is off */
if (mute)
mute_on();
}
/* something more to decode ? */
if (count != len)
goto again;
}
/*
* signalling sender
*
* this function generates tones and assembles dial string with digits and pause
* the result is sent to mISDN. it uses the ss5_encode() function.
* except for dialing and forward-transfer, tones are continuous and will not change state.
*/
int Pss5::inband_send(unsigned char *buffer, int len)
{
int count = 0; /* sample counter */
int duration;
char digit;
int tocode, tosend;
switch(p_m_s_state) {
/* turn off transmitter in idle state */
case SS5_STATE_IDLE:
inband_send_off();
break;
case SS5_STATE_SEIZING:
if (p_m_s_signal != SS5_SIGNAL_SEND_ON
&& p_m_s_signal != SS5_SIGNAL_SEND_ON_RECOG)
break;
duration = -1; /* continuous */
digit = 'A';
send:
/* how much samples do we have left */
if (duration < 0)
tocode = len;
else
tocode = duration - p_m_s_sample_nr;
if (tocode > 0) {
if (tocode > len)
tocode = len;
ss5_encode(buffer, tocode, digit, p_m_s_sample_nr);
/* increase counters */
p_m_s_sample_nr += tocode;
count += tocode;
}
/* more to come ? */
if (duration > 0 && p_m_s_sample_nr >= duration) {
PDEBUG(DEBUG_SS5, "%s: sending tone '%c' complete, starting delay\n", p_name, digit);
if (p_m_s_state == SS5_STATE_DOUBLE_SEIZE) {
do_release(CAUSE_NOCHANNEL, LOCATION_PRIVATE_LOCAL, SS5_CLEAR_FORWARD_REQ);
break;
}
new_ss5_state(SS5_STATE_DELAY);
p_m_s_sample_nr = 0;
}
#if 0
/* stop sending if too long */
if (duration < 0 && p_m_s_sample_nr >= SS5_TIMER_MAX_SIGNAL) {
PDEBUG(DEBUG_SS5, "%s: sending tone '%c' too long, stopping\n", p_name, digit);
inband_send_off();
break;
}
#endif
break;
/* incomming seizing */
case SS5_STATE_PROCEED_TO_SEND:
if (p_m_s_signal != SS5_SIGNAL_RECEIVE)
break;
duration = -1; /* continuous */
digit = 'B';
goto send;
case SS5_STATE_BUSY_FLASH:
case SS5_STATE_CLEAR_BACK:
if (p_m_s_signal != SS5_SIGNAL_SEND_ON
&& p_m_s_signal != SS5_SIGNAL_SEND_ON_RECOG)
break;
duration = -1; /* continuous */
digit = 'B';
goto send;
case SS5_STATE_ANSWER:
if (p_m_s_signal != SS5_SIGNAL_SEND_ON
&& p_m_s_signal != SS5_SIGNAL_SEND_ON_RECOG)
break;
duration = -1; /* continuous */
digit = 'A';
goto send;
case SS5_STATE_ACK_BUSY_FLASH:
case SS5_STATE_ACK_ANSWER:
case SS5_STATE_ACK_CLEAR_BACK:
if (p_m_s_signal != SS5_SIGNAL_RECEIVE)
break;
duration = -1; /* continuous */
digit = 'A';
goto send;
#if 0
case SS5_STATE_FORWARD_TRANSFER:
if (p_m_s_signal != SS5_SIGNAL_RECEIVE)
break;
/* only on bell systems continue and acknowledge tone */
if (!(p_m_mISDNport->ss5 & SS5_FEATURE_BELL))
break;
duration = SS5_TIMER_FORWARD;
digit = 'B';
goto send;
#endif
case SS5_STATE_CLEAR_FORWARD:
if (p_m_s_signal != SS5_SIGNAL_SEND_ON
&& p_m_s_signal != SS5_SIGNAL_SEND_ON_RECOG)
break;
duration = -1; /* continuous */
digit = 'C';
goto send;
case SS5_STATE_RELEASE_GUARD:
if (p_m_s_signal != SS5_SIGNAL_RECEIVE
&& p_m_s_signal != SS5_SIGNAL_DELAY)
break;
/* prevent from sending release guard too long */
if (p_m_s_sample_nr >= SS5_TIMER_RELEASE_MAX)
break;
duration = -1; /* continuous */
digit = 'C';
goto send;
case SS5_STATE_DIAL_OUT:
if ((p_m_mISDNport->ss5 & SS5_FEATURE_PULSEDIALING))
count = inband_dial_pulse(buffer, len, count);
else
count = inband_dial_mf(buffer, len, count);
break;
break;
case SS5_STATE_DELAY:
tosend = len - count;
memset(buffer+count, audio_s16_to_law[0], tosend);
p_m_s_sample_nr += tosend;
count += tosend;
if (p_m_s_sample_nr >= SS5_TIMER_AFTER_SIGNAL) {
PDEBUG(DEBUG_SS5, "%s: delay done, ready for next signal\n", p_name);
new_ss5_state(SS5_STATE_IDLE);
inband_send_off();
}
break;
case SS5_STATE_DOUBLE_SEIZE:
duration = SS5_TIMER_DOUBLE_SEIZE;
digit = 'A';
goto send;
/* nothing to send */
default:
PERROR("inband signalling is turned on, but no signal is processed here.");
new_ss5_state(SS5_STATE_IDLE);
inband_send_off();
return 0;
}
/* return (partly) filled buffer */
return count;
}
int Pss5::inband_dial_mf(unsigned char *buffer, int len, int count)
{
int duration;
int tocode, tosend;
char digit;
/* dialing
*
* p_m_s_dial: digits to be dialed
* p_m_s_digit_i: current digit counter
* p_m_s_signal: current signal state
* p_m_s_sample_nr: current sample number
*/
again:
/* get digit and duration */
digit = p_m_s_dial[p_m_s_digit_i];
if (!digit) { /* if end of string reached */
new_ss5_state(SS5_STATE_DELAY);
p_m_s_sample_nr = 0;
return count;
}
if (p_m_s_signal == SS5_SIGNAL_DIGIT_ON) {
if (!p_m_s_digit_i) // first digit
duration = SS5_TIMER_KP;
else
duration = SS5_TIMER_DIGIT;
} else {
duration = SS5_TIMER_PAUSE;
}
/* end of digit/pause ? */
if (p_m_s_sample_nr >= duration) {
p_m_s_sample_nr = 0;
if (p_m_s_signal == SS5_SIGNAL_DIGIT_PAUSE)
new_ss5_signal(SS5_SIGNAL_DIGIT_ON);
else {
new_ss5_signal(SS5_SIGNAL_DIGIT_PAUSE);
p_m_s_digit_i++;
goto again;
}
}
/* how much samples do we have left */
tosend = len - count;
tocode = duration - p_m_s_sample_nr;
if (tocode < 0)
FATAL("sample_nr overrun duration");
if (tosend < tocode)
tocode = tosend;
/* digit or pause */
if (p_m_s_signal == SS5_SIGNAL_DIGIT_PAUSE) {
memset(buffer+count, audio_s16_to_law[0], tocode);
// printf("coding pause %d bytes\n", tocode);
} else {
ss5_encode(buffer+count, tocode, digit, p_m_s_sample_nr);
// printf("coding digit '%c' %d bytes\n", digit, tocode);
}
/* increase counters */
p_m_s_sample_nr += tocode;
count += tocode;
/* can we take more ? */
if (len != count)
goto again;
return count;
}
int Pss5::inband_dial_pulse(unsigned char *buffer, int len, int count)
{
int tocode, tosend;
int duration;
char digit;
/* dialing
*
* p_m_s_dial: digits to be dialed
* p_m_s_digit_i: current digit counter
* p_m_s_signal: current signal state
* p_m_s_sample_nr: current sample number
*/
again:
/* get digit */
digit = p_m_s_dial[p_m_s_digit_i];
if (!digit) { /* if end of string reached */
new_ss5_state(SS5_STATE_DELAY);
p_m_s_sample_nr = 0;
return count;
}
/* convert digit to pulse */
switch (digit) {
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
digit -= '0';
break;
case '0':
digit = 10;
break;
case '*':
digit = 11;
break;
case '#':
digit = 12;
break;
default:
p_m_s_digit_i++;
goto again;
}
/* get duration */
if (p_m_s_signal == SS5_SIGNAL_DIGIT_ON) {
if (p_m_s_pulsecount & 1)
duration = BELL_TIMER_MAKE; /* loop closed */
else
duration = BELL_TIMER_BREAK; /* loop open, tone */
} else {
duration = BELL_TIMER_PAUSE;
}
/* end of digit/pause ? */
if (p_m_s_sample_nr >= duration) {
p_m_s_sample_nr = 0;
if (p_m_s_signal == SS5_SIGNAL_DIGIT_PAUSE) {
new_ss5_signal(SS5_SIGNAL_DIGIT_ON);
PDEBUG(DEBUG_SS5, "%s: starting pusling digit '%c'\n", p_name, digit);
} else {
p_m_s_pulsecount++; /* toggle pulse */
if (!(p_m_s_pulsecount & 1)) {
/* pulse now on again, but if end is reached... */
if (p_m_s_pulsecount == (digit<<1)) {
new_ss5_signal(SS5_SIGNAL_DIGIT_PAUSE);
p_m_s_pulsecount = 0;
p_m_s_digit_i++;
goto again;
}
}
}
}
/* how much samples do we have left */
tosend = len - count;
tocode = duration - p_m_s_sample_nr;
if (tocode < 0)
FATAL("sample_nr overrun duration");
if (tosend < tocode)
tocode = tosend;
/* digit or pause */
if (p_m_s_signal == SS5_SIGNAL_DIGIT_PAUSE
|| (p_m_s_pulsecount&1)) /* ...or currently on and no pulse */
memset(buffer+count, audio_s16_to_law[0], tocode);
else
ss5_encode(buffer+count, tocode, 'B', p_m_s_sample_nr);
/* increase counters */
p_m_s_sample_nr += tocode;
count += tocode;
/* can we take more ? */
if (len != count)
goto again;
return count;
}
/*
* start signal
*/
void Pss5::start_signal(int state)
{
PDEBUG(DEBUG_SS5, "%s: starting singal '%s'\n", p_name, ss5_state_name[state]);
/* start signal */
new_ss5_state(state);
if (state == SS5_STATE_DIAL_OUT) {
p_m_s_digit_i = 0;
p_m_s_pulsecount = 0;
new_ss5_signal(SS5_SIGNAL_DIGIT_ON);
} else
new_ss5_signal(SS5_SIGNAL_SEND_ON);
/* double seize must continue the current seize tone, so don't reset sample_nr */
if (state != SS5_STATE_DOUBLE_SEIZE) {
/* (re)set sound phase to 0 */
p_m_s_sample_nr = 0;
}
/* turn on inband transmitter */
inband_send_on();
}
/*
* handles all indications
*/
void Pss5::seizing_ind(void)
{
ss5_trace_header(p_m_mISDNport, this, SS5_SEIZING_IND, p_m_b_channel);
end_trace();
new_state(PORT_STATE_IN_SETUP);
set_tone("", "noise");
}
void Pss5::digit_ind(char digit)
{
int i;
char string[128] = "", dial[128] = "";
int dash, first_digit, last_was_digit;
/* add digit */
SCCAT(p_m_s_dial, digit);
if (p_state == PORT_STATE_IN_SETUP)
new_state(PORT_STATE_IN_OVERLAP);
/* not last digit ? */
if (digit != 'c')
return;
/* parse string */
dash = 0; /* dash must be used next time */
first_digit = 1;
last_was_digit = 0;
p_dialinginfo.ntype = INFO_NTYPE_UNKNOWN;
for (i = 0; p_m_s_dial[i]; i++) {
if (dash || (last_was_digit && (p_m_s_dial[i]<'0' || p_m_s_dial[i]>'9')))
SCCAT(string, '-');
dash = 0;
last_was_digit = 0;
switch(p_m_s_dial[i]) {
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '0':
if (first_digit)
dash = 1;
first_digit = 0;
last_was_digit = 1;
SCCAT(string, p_m_s_dial[i]);
SCCAT(dial, p_m_s_dial[i]);
break;
case '*':
SCAT(string, "C11");
SCCAT(dial, p_m_s_dial[i]);
dash = 1;
break;
case '#':
SCAT(string, "C12");
SCCAT(dial, p_m_s_dial[i]);
dash = 1;
break;
case 'a':
SCAT(string, "KP1");
SCCAT(dial, p_m_s_dial[i]);
dash = 1;
break;
case 'b':
SCAT(string, "KP2");
SCCAT(dial, p_m_s_dial[i]);
dash = 1;
break;
case 'c':
SCAT(string, "ST");
dash = 1;
break;
default:
break;
}
}
ss5_trace_header(p_m_mISDNport, this, SS5_DIALING_IND, p_m_b_channel);
add_trace("string", NULL, "%s", string);
add_trace("number", NULL, "%s", dial);
end_trace();
new_ss5_state(SS5_STATE_IDLE);
do_setup(dial, 1);
new_state(PORT_STATE_IN_PROCEEDING);
}
void Pss5::pulse_ind(int on)
{
struct lcr_msg *message;
char dial[3] = "a.";
if (p_m_s_signal == SS5_SIGNAL_PULSE_OFF) {
if (on) {
/* pulse turns on */
p_m_s_recog = 0;
new_ss5_signal(SS5_SIGNAL_PULSE_ON);
/* pulse turns of, count it */
p_m_s_pulsecount++;
PDEBUG(DEBUG_SS5, "%s: pulse turns on, counting\n", p_name);
} else {
/* pulse remains off */
p_m_s_recog += SS5_DECODER_NPOINTS;
/* not recognized end of digit, we wait... */
if (p_m_s_recog < BELL_TIMER_RECOG_END)
return;
PDEBUG(DEBUG_SS5, "%s: pulse remains off, counted %d pulses\n", p_name, p_m_s_pulsecount);
if (p_m_s_pulsecount >= 12)
dial[1] = '#';
else if (p_m_s_pulsecount == 11)
dial[1] = '*';
else if (p_m_s_pulsecount == 10)
dial[1] = '0';
else
dial[1] = p_m_s_pulsecount + '0';
ss5_trace_header(p_m_mISDNport, this, SS5_DIALING_IND, p_m_b_channel);
add_trace("digit", NULL, "%s", dial+1);
add_trace("pulses", NULL, "%d", p_m_s_pulsecount);
end_trace();
if (p_state == PORT_STATE_IN_SETUP) {
/* sending digit as setup */
do_setup(dial, 0); /* include 'a' == KP1 */
new_state(PORT_STATE_IN_OVERLAP);
} else {
/* sending digit as information */
message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_INFORMATION);
SCPY(message->param.information.id, dial+1);
message_put(message);
}
new_ss5_state(SS5_STATE_IDLE);
/* done rx pulses, return to idle */
new_ss5_state(SS5_STATE_IDLE);
}
} else {
if (on) {
/* pulse remains on */
p_m_s_recog += SS5_DECODER_NPOINTS;
} else {
/* pulse turns off */
if (p_m_s_recog >= BELL_TIMER_RECOG_HANGUP) {
PDEBUG(DEBUG_SS5, "%s: long pulse turns off, releasing\n", p_name);
ss5_trace_header(p_m_mISDNport, this, SS5_DIALING_IND, p_m_b_channel);
add_trace("longtone", NULL, "releases call");
end_trace();
/* long pulse is gone, release current connection, if any */
while(p_epointlist) {
message = message_create(p_serial, p_epointlist->epoint_id, PORT_TO_EPOINT, MESSAGE_RELEASE);
message->param.disconnectinfo.location = LOCATION_BEYOND;
message->param.disconnectinfo.cause = CAUSE_NORMAL;
message_put(message);
free_epointlist(p_epointlist);
}
set_tone("", NULL);
/* return to setup state */
new_state(PORT_STATE_IN_SETUP);
new_ss5_state(SS5_STATE_IDLE);
return;
}
PDEBUG(DEBUG_SS5, "%s: short pulse turns off, releasing\n", p_name);
p_m_s_recog = 0;
new_ss5_signal(SS5_SIGNAL_PULSE_OFF);
}
}
}
void Pss5::proceed_to_send_ind(void)
{
ss5_trace_header(p_m_mISDNport, this, SS5_PROCEED_TO_SEND_IND, p_m_b_channel);
end_trace();
SCPY(p_m_s_dial, p_dialinginfo.id);
start_signal(SS5_STATE_DIAL_OUT);
new_state(PORT_STATE_OUT_OVERLAP);
}
void Pss5::busy_flash_ind(void)
{
struct lcr_msg *message;
ss5_trace_header(p_m_mISDNport, this, SS5_BUSY_FLASH_IND, p_m_b_channel);
end_trace();
/* busy before dialing ? */
if (!p_epointlist)
return;
if (!(p_m_mISDNport->ss5 & SS5_FEATURE_NODISCONNECT)) {
message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_DISCONNECT);
message->param.disconnectinfo.location = LOCATION_BEYOND;
message->param.disconnectinfo.cause = CAUSE_BUSY;
message_put(message);
}
new_state(PORT_STATE_IN_DISCONNECT);
}
void Pss5::answer_ind(void)
{
struct lcr_msg *message;
ss5_trace_header(p_m_mISDNport, this, SS5_ANSWER_IND, p_m_b_channel);
end_trace();
/* answer before dialing ? */
if (!p_epointlist)
return;
/* already connected */
if (!(p_m_mISDNport->ss5 & SS5_FEATURE_CONNECT)) {
message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_CONNECT);
message_put(message);
}
new_state(PORT_STATE_CONNECT);
}
void Pss5::forward_transfer_ind(void)
{
// struct lcr_msg *message;
ss5_trace_header(p_m_mISDNport, this, SS5_FORWARD_TRANSFER_IND, p_m_b_channel);
end_trace();
#if 0
/* if BELL flavor bluebox flag is set, use it to seize a new line */
if (!(p_m_mISDNport->ss5 & SS5_FEATURE_BELL))
return;
/* special BELL flavor hack to clear a line and seize a new one */
while(p_epointlist) {
message = message_create(p_serial, p_epointlist->epoint_id, PORT_TO_EPOINT, MESSAGE_RELEASE);
message->param.disconnectinfo.location = LOCATION_BEYOND;
message->param.disconnectinfo.cause = CAUSE_NORMAL;
message_put(message);
free_epointlist(p_epointlist);
}
set_tone("", NULL);
new_state(PORT_STATE_IN_SETUP);
#endif
}
void Pss5::clear_back_ind(void)
{
struct lcr_msg *message;
ss5_trace_header(p_m_mISDNport, this, SS5_CLEAR_BACK_IND, p_m_b_channel);
end_trace();
/* nobody? */
if (!p_epointlist)
return;
if (!(p_m_mISDNport->ss5 & SS5_FEATURE_NODISCONNECT)) {
message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_DISCONNECT);
message->param.disconnectinfo.location = LOCATION_BEYOND;
message->param.disconnectinfo.cause = CAUSE_NORMAL;
message_put(message);
}
new_state(PORT_STATE_IN_DISCONNECT);
}
void Pss5::clear_forward_ind(void)
{
struct lcr_msg *message;
ss5_trace_header(p_m_mISDNport, this, SS5_CLEAR_FORWARD_IND, p_m_b_channel);
end_trace();
new_state(PORT_STATE_IDLE);
set_tone("", NULL);
p_type = PORT_TYPE_SS5_IDLE;
/* someone ? */
if (!p_epointlist)
return;
message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_RELEASE);
message->param.disconnectinfo.location = LOCATION_BEYOND;
message->param.disconnectinfo.cause = CAUSE_NORMAL;
message_put(message);
free_epointlist(p_epointlist);
}
void Pss5::release_guard_ind(void)
{
struct lcr_msg *message;
ss5_trace_header(p_m_mISDNport, this, SS5_RELEASE_GUARD_IND, p_m_b_channel);
end_trace();
set_tone("", NULL);
/* someone ? */
if (!p_epointlist)
return;
message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_RELEASE);
message->param.disconnectinfo.location = LOCATION_BEYOND;
message->param.disconnectinfo.cause = CAUSE_NORMAL;
message_put(message);
free_epointlist(p_epointlist);
}
void Pss5::double_seizure_ind(void)
{
ss5_trace_header(p_m_mISDNport, this, SS5_DOUBLE_SEIZURE_IND, p_m_b_channel);
end_trace();
ss5_trace_header(p_m_mISDNport, this, SS5_DOUBLE_SEIZURE_REQ, p_m_b_channel);
end_trace();
/* start double seizure sequence, so remote exchange will recognize it */
start_signal(SS5_STATE_DOUBLE_SEIZE);
}
/*
* shuts down by sending a clear forward and releasing endpoint
*/
void Pss5::do_release(int cause, int location, int signal)
{
struct lcr_msg *message;
/* sending release to endpoint */
while(p_epointlist) {
message = message_create(p_serial, p_epointlist->epoint_id, PORT_TO_EPOINT, MESSAGE_RELEASE);
message->param.disconnectinfo.location = location;
message->param.disconnectinfo.cause = cause;
message_put(message);
free_epointlist(p_epointlist);
}
// if (p_state != PORT_STATE_RELEASE) {
p_m_s_queued_signal = signal;
if (signal == SS5_CLEAR_FORWARD_REQ) {
new_state(PORT_STATE_RELEASE);
set_tone("", "noise");
} else
new_state(PORT_STATE_OUT_DISCONNECT);
trigger_work(&p_m_s_queue);
// }
}
/*
* create endpoint and send setup
*/
void Pss5::do_setup(char *dial, int complete)
{
class Endpoint *epoint;
struct lcr_msg *message;
SCPY(p_dialinginfo.id, dial);
p_dialinginfo.sending_complete = complete;
p_callerinfo.present = INFO_PRESENT_NOTAVAIL;
p_callerinfo.screen = INFO_SCREEN_NETWORK;
p_callerinfo.ntype = INFO_NTYPE_NOTPRESENT;
p_callerinfo.isdn_port = p_m_portnum;
SCPY(p_callerinfo.interface, p_m_mISDNport->ifport->interface->name);
p_capainfo.bearer_capa = INFO_BC_AUDIO;
p_capainfo.bearer_info1 = (options.law=='a')?3:2;
p_capainfo.bearer_mode = INFO_BMODE_CIRCUIT;
p_capainfo.hlc = INFO_HLC_NONE;
p_capainfo.exthlc = INFO_HLC_NONE;
p_capainfo.source_mode = B_MODE_TRANSPARENT;
/* create endpoint */
if (p_epointlist)
FATAL("Incoming call but already got an endpoint.\n");
if (!(epoint = new Endpoint(p_serial, 0)))
FATAL("No memory for Endpoint instance\n");
epoint->ep_app = new_endpointapp(epoint, 0, p_m_mISDNport->ifport->interface->app); //incoming
epointlist_new(epoint->ep_serial);
/* send setup message to endpoit */
message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_SETUP);
message->param.setup.isdn_port = p_m_portnum;
message->param.setup.port_type = p_type;
// message->param.setup.dtmf = !p_m_mISDNport->ifport->nodtmf;
memcpy(&message->param.setup.dialinginfo, &p_dialinginfo, sizeof(struct dialing_info));
memcpy(&message->param.setup.callerinfo, &p_callerinfo, sizeof(struct caller_info));
memcpy(&message->param.setup.redirinfo, &p_redirinfo, sizeof(struct redir_info));
memcpy(&message->param.setup.capainfo, &p_capainfo, sizeof(struct capa_info));
message_put(message);
}
/*
* handles all messages from endpoint
*/
/* MESSAGE_SETUP */
void Pss5::message_setup(unsigned int epoint_id, int message_id, union parameter *param)
{
struct lcr_msg *message;
int i;
char string[128] = "", dial[128] = "";
int dash, first_digit, last_was_digit;
if (p_epointlist) {
PERROR("endpoint already exist.\n");
message = message_create(p_serial, epoint_id, PORT_TO_EPOINT, MESSAGE_RELEASE);
message->param.disconnectinfo.location = LOCATION_PRIVATE_LOCAL;
message->param.disconnectinfo.cause = CAUSE_UNSPECIFIED;
message_put(message);
return;
}
/* copy setup infos to port */
memcpy(&p_callerinfo, &param->setup.callerinfo, sizeof(p_callerinfo));
memcpy(&p_dialinginfo, &param->setup.dialinginfo, sizeof(p_dialinginfo));
memcpy(&p_capainfo, &param->setup.capainfo, sizeof(p_capainfo));
memcpy(&p_redirinfo, &param->setup.redirinfo, sizeof(p_redirinfo));
/* screen outgoing caller id */
do_screen(1, p_callerinfo.id, sizeof(p_callerinfo.id), &p_callerinfo.ntype, &p_callerinfo.present, p_m_mISDNport->ifport->interface->name);
do_screen(1, p_callerinfo.id2, sizeof(p_callerinfo.id2), &p_callerinfo.ntype2, &p_callerinfo.present2, p_m_mISDNport->ifport->interface->name);
/* parse dial string */
dash = 0; /* dash must be used next time */
first_digit = 1;
last_was_digit = 0;
for (i = 0; p_dialinginfo.id[i]; i++) {
if (dash || (last_was_digit && (p_dialinginfo.id[i]<'0' || p_dialinginfo.id[i]>'9')))
SCCAT(string, '-');
dash = 0;
last_was_digit = 0;
switch(p_dialinginfo.id[i]) {
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '0':
if (i && first_digit)
dash = 1;
first_digit = 0;
last_was_digit = 1;
SCCAT(string, p_dialinginfo.id[i]);
SCCAT(dial, p_dialinginfo.id[i]);
break;
case '*':
SCAT(string, "C11");
SCCAT(dial, '*');
dash = 1;
break;
case '#':
SCAT(string, "C12");
SCCAT(dial, '#');
dash = 1;
break;
case 'a':
SCAT(string, "KP1");
SCCAT(dial, 'a');
dash = 1;
break;
case 'b':
SCAT(string, "KP2");
SCCAT(dial, 'b');
dash = 1;
break;
case 'c':
SCAT(string, "ST");
SCCAT(dial, 'c');
dash = 1;
case 'K':
i++;
if (p_dialinginfo.id[i] != 'P')
goto dial_error;
i++;
if (p_dialinginfo.id[i] == '1') {
SCAT(string, "KP1");
SCCAT(dial, 'a');
dash = 1;
break;
}
if (p_dialinginfo.id[i] == '2') {
SCAT(string, "KP2");
SCCAT(dial, 'b');
dash = 1;
break;
}
goto dial_error;
case 'C':
i++;
if (p_dialinginfo.id[i] != '1')
goto dial_error;
i++;
if (p_dialinginfo.id[i] == '1') {
SCAT(string, "C11");
SCCAT(dial, 'a');
dash = 1;
break;
}
if (p_dialinginfo.id[i] == '2') {
SCAT(string, "C12");
SCCAT(dial, 'b');
dash = 1;
break;
}
goto dial_error;
case 'S':
i++;
if (p_dialinginfo.id[i] != 'T')
goto dial_error;
SCAT(string, "ST");
SCCAT(dial, 'c');
dash = 1;
break;
default:
break;
}
/* stop, if ST */
if (dial[0] && dial[strlen(dial)-1] == 'c')
break;
}
/* terminate */
if (dial[0] && dial[strlen(dial)-1]!='c') {
SCCAT(string, '-');
SCAT(string, "ST");
SCCAT(dial, 'c');
}
/* error in dial string */
if (!dial[0]) {
dial_error:
ss5_trace_header(p_m_mISDNport, this, SS5_DIALING_REQ, p_m_b_channel);
add_trace("string", NULL, "%s", p_dialinginfo.id);
if (!dial[0])
add_trace("error", NULL, "no number", dial);
else if (dial[0]!='a' && dial[0]!='b')
add_trace("error", NULL, "number must start with KP1/KP2", dial);
else
add_trace("error", NULL, "illegal format", dial);
end_trace();
message = message_create(p_serial, epoint_id, PORT_TO_EPOINT, MESSAGE_RELEASE);
message->param.disconnectinfo.location = LOCATION_PRIVATE_LOCAL;
message->param.disconnectinfo.cause = CAUSE_INVALID;
message_put(message);
return;
}
/* copy new dial string */
SCPY(p_dialinginfo.id, dial);
/* attach only if not already */
epointlist_new(epoint_id);
ss5_trace_header(p_m_mISDNport, this, SS5_DIALING_REQ, p_m_b_channel);
add_trace("string", NULL, "%s", string);
add_trace("type", NULL, "%s", (dial[0]=='b')?"international":"national");
add_trace("number", NULL, "%s", dial);
end_trace();
/* connect auto path */
if ((p_m_mISDNport->ss5 & SS5_FEATURE_CONNECT)) {
message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_CONNECT);
message_put(message);
} else {
message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_PROCEEDING);
message_put(message);
}
/* start seizing */
ss5_trace_header(p_m_mISDNport, this, SS5_SEIZING_REQ, p_m_b_channel);
end_trace();
new_ss5_state(SS5_STATE_SEIZING);
new_ss5_signal(SS5_SIGNAL_SEND_ON);
p_m_s_sample_nr = 0;
inband_send_on();
p_type = PORT_TYPE_SS5_OUT;
new_state(PORT_STATE_OUT_SETUP);
}
/* MESSAGE_CONNECT */
void Pss5::message_connect(unsigned int epoint_id, int message_id, union parameter *param)
{
memcpy(&p_connectinfo, &param->connectinfo, sizeof(p_connectinfo));
if (p_state != PORT_STATE_CONNECT) {
p_m_s_queued_signal = SS5_ANSWER_REQ;
new_state(PORT_STATE_CONNECT);
trigger_work(&p_m_s_queue);
}
set_tone("", NULL);
}
/* MESSAGE_DISCONNECT */
void Pss5::message_disconnect(unsigned int epoint_id, int message_id, union parameter *param)
{
/* release and clear forward */
if (p_type==PORT_TYPE_SS5_OUT) { /* outgoing exchange */
do_release(param->disconnectinfo.cause, param->disconnectinfo.location, SS5_CLEAR_FORWARD_REQ);
return;
}
/* release and clear back */
if ((p_m_mISDNport->ss5 & SS5_FEATURE_RELEASE)) {
do_release(param->disconnectinfo.cause, param->disconnectinfo.location, SS5_CLEAR_BACK_REQ);
return;
}
/* disconnect by sending clear back (after answer) or busy flash (before answer) */
if (p_state != PORT_STATE_OUT_DISCONNECT) {
if (p_state == PORT_STATE_CONNECT)
p_m_s_queued_signal = SS5_CLEAR_BACK_REQ;
else
p_m_s_queued_signal = SS5_BUSY_FLASH_REQ;
new_state(PORT_STATE_OUT_DISCONNECT);
trigger_work(&p_m_s_queue);
}
}
/* MESSAGE_RELEASE */
void Pss5::message_release(unsigned int epoint_id, int message_id, union parameter *param)
{
/* always send clear forward, because release guard only works as a reply to clear forward */
do_release(param->disconnectinfo.cause, param->disconnectinfo.location, SS5_CLEAR_FORWARD_REQ);
}
/*
* endpoint sends messages to the port
*/
int Pss5::message_epoint(unsigned int epoint_id, int message_id, union parameter *param)
{
if (PmISDN::message_epoint(epoint_id, message_id, param))
return(1);
switch(message_id) {
case MESSAGE_SETUP: /* dial-out command received from epoint */
if (p_state!=PORT_STATE_IDLE) {
PERROR("Pss5(%s) ignoring setup because isdn port is not in idle state (or connected for sending display info).\n", p_name);
break;
}
if (p_epointlist && p_state==PORT_STATE_IDLE)
FATAL("Pss5(%s): epoint pointer is set in idle state, how bad!!\n", p_name);
message_setup(epoint_id, message_id, param);
break;
case MESSAGE_CONNECT: /* call of endpoint is connected */
message_connect(epoint_id, message_id, param);
break;
case MESSAGE_DISCONNECT: /* call has been disconnected */
message_disconnect(epoint_id, message_id, param);
break;
case MESSAGE_RELEASE: /* release isdn port */
message_release(epoint_id, message_id, param);
break;
default:
PDEBUG(DEBUG_SS5, "Pss5(%s) ss5 port with (caller id %s) received an unhandled message: %d\n", p_name, p_callerinfo.id, message_id);
}
return(1);
}