/*****************************************************************************\ ** ** ** 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, "<>"); /* 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, ¶m->setup.callerinfo, sizeof(p_callerinfo)); memcpy(&p_dialinginfo, ¶m->setup.dialinginfo, sizeof(p_dialinginfo)); memcpy(&p_capainfo, ¶m->setup.capainfo, sizeof(p_capainfo)); memcpy(&p_redirinfo, ¶m->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, ¶m->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); }