From: Andreas Eversberg Date: Sat, 26 Sep 2009 11:20:29 +0000 (+0200) Subject: Added support for signalling system no. 5. X-Git-Url: http://git.eversberg.eu/gitweb.cgi?p=lcr.git;a=commitdiff_plain;h=323cbc387b1a068f8e2bcfd1034666406ba18c93 Added support for signalling system no. 5. More infos will follow on the isdn4linux mailing list. modified: Makefile.am modified: Makefile.in modified: README modified: apppbx.cpp modified: configure modified: configure.ac modified: default/options.conf modified: dss1.cpp modified: ie.cpp modified: interface.c modified: interface.h modified: lcradmin.c modified: lcrsocket.h modified: mISDN.cpp modified: mISDN.h modified: main.c modified: main.h modified: port.h modified: socket_server.c new file: ss5.cpp new file: ss5.h new file: ss5_decode.c new file: ss5_decode.h new file: ss5_encode.c new file: ss5_encode.h --- diff --git a/Makefile.am b/Makefile.am index 84bfd89..bce48d1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -56,6 +56,14 @@ GSM_LIB = /usr/lib/libgsm.a ./openbsc/src/libbsc.a ./openbsc/src/libmsc.a ./open endif +if ENABLE_SS5 + +SS5_INCLUDE = -DWITH_SS5 + +SS5_SOURCE = ss5.cpp ss5_encode.c ss5_decode.c + +endif + bin_PROGRAMS = lcradmin gentones genwave sbin_PROGRAMS = lcr genrc genextension @@ -84,9 +92,9 @@ install-exec-hook: $(INSTALL) chan_lcr.so $(astmoddir) endif -INCLUDES = $(all_includes) $(GSM_INCLUDE) -Wall -I/usr/include/mISDNuser $(INSTALLATION_DEFINES) +INCLUDES = $(all_includes) $(GSM_INCLUDE) $(SS5_INCLUDE) -Wall -I/usr/include/mISDNuser $(INSTALLATION_DEFINES) -lcr_SOURCES = $(GSM_SOURCE) action.cpp mISDN.cpp tones.c \ +lcr_SOURCES = $(GSM_SOURCE) $(SS5_SOURCE) action.cpp mISDN.cpp tones.c \ action_efi.cpp crypt.cpp mail.c trace.c \ action_vbox.cpp dss1.cpp main.c \ vbox.cpp alawulaw.c endpoint.cpp interface.c message.c \ diff --git a/Makefile.in b/Makefile.in index 56f2e2d..efdabb3 100644 --- a/Makefile.in +++ b/Makefile.in @@ -77,26 +77,29 @@ genwave_OBJECTS = genwave.$(OBJEXT) genwave_LDADD = $(LDADD) am__lcr_SOURCES_DIST = gsm_audio.c gsm.cpp gsm_conf.c \ openbsc/src/bsc_init.c openbsc/src/vty_interface.c \ - openbsc/src/vty_interface_layer3.c action.cpp mISDN.cpp \ - tones.c action_efi.cpp crypt.cpp mail.c trace.c \ - action_vbox.cpp dss1.cpp main.c vbox.cpp alawulaw.c \ - endpoint.cpp interface.c message.c apppbx.cpp endpointapp.cpp \ - join.cpp options.c extension.c joinpbx.cpp port.cpp callerid.c \ - joinremote.cpp route.c cause.c socket_server.c + openbsc/src/vty_interface_layer3.c ss5.cpp ss5_encode.c \ + ss5_decode.c action.cpp mISDN.cpp tones.c action_efi.cpp \ + crypt.cpp mail.c trace.c action_vbox.cpp dss1.cpp main.c \ + vbox.cpp alawulaw.c endpoint.cpp interface.c message.c \ + apppbx.cpp endpointapp.cpp join.cpp options.c extension.c \ + joinpbx.cpp port.cpp callerid.c joinremote.cpp route.c cause.c \ + socket_server.c @ENABLE_GSM_TRUE@am__objects_1 = gsm_audio.$(OBJEXT) gsm.$(OBJEXT) \ @ENABLE_GSM_TRUE@ gsm_conf.$(OBJEXT) bsc_init.$(OBJEXT) \ @ENABLE_GSM_TRUE@ vty_interface.$(OBJEXT) \ @ENABLE_GSM_TRUE@ vty_interface_layer3.$(OBJEXT) -am_lcr_OBJECTS = $(am__objects_1) action.$(OBJEXT) mISDN.$(OBJEXT) \ - tones.$(OBJEXT) action_efi.$(OBJEXT) crypt.$(OBJEXT) \ - mail.$(OBJEXT) trace.$(OBJEXT) action_vbox.$(OBJEXT) \ - dss1.$(OBJEXT) main.$(OBJEXT) vbox.$(OBJEXT) \ - alawulaw.$(OBJEXT) endpoint.$(OBJEXT) interface.$(OBJEXT) \ - message.$(OBJEXT) apppbx.$(OBJEXT) endpointapp.$(OBJEXT) \ - join.$(OBJEXT) options.$(OBJEXT) extension.$(OBJEXT) \ - joinpbx.$(OBJEXT) port.$(OBJEXT) callerid.$(OBJEXT) \ - joinremote.$(OBJEXT) route.$(OBJEXT) cause.$(OBJEXT) \ - socket_server.$(OBJEXT) +@ENABLE_SS5_TRUE@am__objects_2 = ss5.$(OBJEXT) ss5_encode.$(OBJEXT) \ +@ENABLE_SS5_TRUE@ ss5_decode.$(OBJEXT) +am_lcr_OBJECTS = $(am__objects_1) $(am__objects_2) action.$(OBJEXT) \ + mISDN.$(OBJEXT) tones.$(OBJEXT) action_efi.$(OBJEXT) \ + crypt.$(OBJEXT) mail.$(OBJEXT) trace.$(OBJEXT) \ + action_vbox.$(OBJEXT) dss1.$(OBJEXT) main.$(OBJEXT) \ + vbox.$(OBJEXT) alawulaw.$(OBJEXT) endpoint.$(OBJEXT) \ + interface.$(OBJEXT) message.$(OBJEXT) apppbx.$(OBJEXT) \ + endpointapp.$(OBJEXT) join.$(OBJEXT) options.$(OBJEXT) \ + extension.$(OBJEXT) joinpbx.$(OBJEXT) port.$(OBJEXT) \ + callerid.$(OBJEXT) joinremote.$(OBJEXT) route.$(OBJEXT) \ + cause.$(OBJEXT) socket_server.$(OBJEXT) lcr_OBJECTS = $(am_lcr_OBJECTS) am__DEPENDENCIES_1 = @ENABLE_GSM_TRUE@am__DEPENDENCIES_2 = /usr/lib/libgsm.a \ @@ -161,6 +164,8 @@ ENABLE_ASTERISK_CHANNEL_DRIVER_FALSE = @ENABLE_ASTERISK_CHANNEL_DRIVER_FALSE@ ENABLE_ASTERISK_CHANNEL_DRIVER_TRUE = @ENABLE_ASTERISK_CHANNEL_DRIVER_TRUE@ ENABLE_GSM_FALSE = @ENABLE_GSM_FALSE@ ENABLE_GSM_TRUE = @ENABLE_GSM_TRUE@ +ENABLE_SS5_FALSE = @ENABLE_SS5_FALSE@ +ENABLE_SS5_TRUE = @ENABLE_SS5_TRUE@ EXEEXT = @EXEEXT@ GREP = @GREP@ INSTALL_DATA = @INSTALL_DATA@ @@ -246,11 +251,16 @@ INSTALLATION_DEFINES = \ @ENABLE_GSM_TRUE@GSM_INCLUDE = -DWITH_GSM -I./openbsc/include @ENABLE_GSM_TRUE@GSM_SOURCE = gsm_audio.c gsm.cpp gsm_conf.c openbsc/src/bsc_init.c openbsc/src/vty_interface.c openbsc/src/vty_interface_layer3.c @ENABLE_GSM_TRUE@GSM_LIB = /usr/lib/libgsm.a ./openbsc/src/libbsc.a ./openbsc/src/libmsc.a ./openbsc/src/libvty.a -ldbi -lcrypt + +#gsm_audio.po: gsm_audio.c gsm_audio.h +# $(CC) -D_GNU_SOURCE -fPIC -c gsm_audio.c -o gsm_audio.po +@ENABLE_SS5_TRUE@SS5_INCLUDE = -DWITH_SS5 +@ENABLE_SS5_TRUE@SS5_SOURCE = ss5.cpp ss5_encode.c ss5_decode.c @ENABLE_ASTERISK_CHANNEL_DRIVER_TRUE@chan_lcr_so_SOURCES = @ENABLE_ASTERISK_CHANNEL_DRIVER_TRUE@chan_lcr_so_LDFLAGS = -shared @ENABLE_ASTERISK_CHANNEL_DRIVER_TRUE@chan_lcr_so_LDADD = chan_lcr.po bchannel.po options.po callerid.po -INCLUDES = $(all_includes) $(GSM_INCLUDE) -Wall -I/usr/include/mISDNuser $(INSTALLATION_DEFINES) -lcr_SOURCES = $(GSM_SOURCE) action.cpp mISDN.cpp tones.c \ +INCLUDES = $(all_includes) $(GSM_INCLUDE) $(SS5_INCLUDE) -Wall -I/usr/include/mISDNuser $(INSTALLATION_DEFINES) +lcr_SOURCES = $(GSM_SOURCE) $(SS5_SOURCE) action.cpp mISDN.cpp tones.c \ action_efi.cpp crypt.cpp mail.c trace.c \ action_vbox.cpp dss1.cpp main.c \ vbox.cpp alawulaw.c endpoint.cpp interface.c message.c \ @@ -437,6 +447,9 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/port.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/route.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/socket_server.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ss5.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ss5_decode.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ss5_encode.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tones.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trace.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/vbox.Po@am__quote@ diff --git a/README b/README index 5496175..85e8d96 100644 --- a/README +++ b/README @@ -517,8 +517,9 @@ Changes after Version 1.6 - Fixed bad call/conference bug in joinpbx.c - External interfaces must now be specified using 'extern' keyword. -> This prevents from selecting other interfaces when dialing out. - -> Just add 'extern' right below your external interface, or define + -> Just add 'extern' right below your external interface definition, or give external interface name in routing.conf: ": extern interfaces=XXXXX" +- Added experimental CCITT No. 5 signalling system. (for educational purpose) diff --git a/apppbx.cpp b/apppbx.cpp index 25521dd..06e3d92 100644 --- a/apppbx.cpp +++ b/apppbx.cpp @@ -136,7 +136,7 @@ void EndpointAppPBX::trace_header(const char *name, int direction) SCPY(msgtext, name); /* init trace with given values */ - start_trace(0, + start_trace(-1, NULL, numberrize_callerinfo(e_callerinfo.id, e_callerinfo.ntype, options.national, options.international), e_dialinginfo.id, @@ -648,92 +648,108 @@ foundif: /* check for channel form selection list */ *channel = 0; - selchannel = ifport->out_channel; - while(selchannel) { - switch(selchannel->channel) { - case CHANNEL_FREE: /* free channel */ - if (mISDNport->b_reserved >= mISDNport->b_num) - break; /* all channel in use or reserverd */ - /* find channel */ - i = 0; - while(i < mISDNport->b_num) { - if (mISDNport->b_port[i] == NULL) { - *channel = i+1+(i>=15); - trace_header("CHANNEL SELECTION (selecting free channel)", DIRECTION_NONE); - add_trace("port", NULL, "%d", ifport->portnum); - add_trace("position", NULL, "%d", index); - add_trace("channel", NULL, "%d", *channel); - end_trace(); - break; - } - i++; - } - if (*channel) - break; - trace_header("CHANNEL SELECTION (no channel is 'free')", DIRECTION_NONE); +#ifdef WITH_SS5 + if (mISDNport->ss5) { + class Pss5 *port; + port = ss5_hunt_line(mISDNport); + if (port) { + *channel = port->p_m_b_channel; + trace_header("CHANNEL SELECTION (selecting SS5 channel)", DIRECTION_NONE); add_trace("port", NULL, "%d", ifport->portnum); add_trace("position", NULL, "%d", index); + add_trace("channel", NULL, "%d", *channel); end_trace(); - break; - - case CHANNEL_ANY: /* don't ask for channel */ - if (mISDNport->b_reserved >= mISDNport->b_num) { - trace_header("CHANNEL SELECTION (cannot ask for 'any' channel, all reserved)", DIRECTION_NONE); + } + } else +#endif + { + selchannel = ifport->out_channel; + while(selchannel) { + switch(selchannel->channel) { + case CHANNEL_FREE: /* free channel */ + if (mISDNport->b_reserved >= mISDNport->b_num) + break; /* all channel in use or reserverd */ + /* find channel */ + i = 0; + while(i < mISDNport->b_num) { + if (mISDNport->b_port[i] == NULL) { + *channel = i+1+(i>=15); + trace_header("CHANNEL SELECTION (selecting free channel)", DIRECTION_NONE); + add_trace("port", NULL, "%d", ifport->portnum); + add_trace("position", NULL, "%d", index); + add_trace("channel", NULL, "%d", *channel); + end_trace(); + break; + } + i++; + } + if (*channel) + break; + trace_header("CHANNEL SELECTION (no channel is 'free')", DIRECTION_NONE); add_trace("port", NULL, "%d", ifport->portnum); add_trace("position", NULL, "%d", index); - add_trace("total", NULL, "%d", mISDNport->b_num); - add_trace("reserved", NULL, "%d", mISDNport->b_reserved); end_trace(); - break; /* all channel in use or reserverd */ - } - trace_header("CHANNEL SELECTION (using 'any' channel)", DIRECTION_NONE); - add_trace("port", NULL, "%d", ifport->portnum); - add_trace("position", NULL, "%d", index); - end_trace(); - *channel = CHANNEL_ANY; - break; - - case CHANNEL_NO: /* call waiting */ - trace_header("CHANNEL SELECTION (using 'no' channel, call-waiting)", DIRECTION_NONE); - add_trace("port", NULL, "%d", ifport->portnum); - add_trace("position", NULL, "%d", index); - end_trace(); - *channel = CHANNEL_NO; - break; + break; - default: - if (selchannel->channel<1 || selchannel->channel==16) { - trace_header("CHANNEL SELECTION (channel out of range)", DIRECTION_NONE); - add_trace("port", NULL, "%d", ifport->portnum); - add_trace("position", NULL, "%d", index); - add_trace("channel", NULL, "%d", selchannel->channel); - end_trace(); - break; /* invalid channels */ - } - i = selchannel->channel-1-(selchannel->channel>=17); - if (i >= mISDNport->b_num) { - trace_header("CHANNEL SELECTION (channel out of range)", DIRECTION_NONE); + case CHANNEL_ANY: /* don't ask for channel */ + if (mISDNport->b_reserved >= mISDNport->b_num) { + trace_header("CHANNEL SELECTION (cannot ask for 'any' channel, all reserved)", DIRECTION_NONE); + add_trace("port", NULL, "%d", ifport->portnum); + add_trace("position", NULL, "%d", index); + add_trace("total", NULL, "%d", mISDNport->b_num); + add_trace("reserved", NULL, "%d", mISDNport->b_reserved); + end_trace(); + break; /* all channel in use or reserverd */ + } + trace_header("CHANNEL SELECTION (using 'any' channel)", DIRECTION_NONE); add_trace("port", NULL, "%d", ifport->portnum); add_trace("position", NULL, "%d", index); - add_trace("channel", NULL, "%d", selchannel->channel); - add_trace("channels", NULL, "%d", mISDNport->b_num); end_trace(); - break; /* channel not in port */ - } - if (mISDNport->b_port[i] == NULL) { - *channel = selchannel->channel; - trace_header("CHANNEL SELECTION (selecting given channel)", DIRECTION_NONE); + *channel = CHANNEL_ANY; + break; + + case CHANNEL_NO: /* call waiting */ + trace_header("CHANNEL SELECTION (using 'no' channel, call-waiting)", DIRECTION_NONE); add_trace("port", NULL, "%d", ifport->portnum); add_trace("position", NULL, "%d", index); - add_trace("channel", NULL, "%d", *channel); end_trace(); + *channel = CHANNEL_NO; + break; + + default: + if (selchannel->channel<1 || selchannel->channel==16) { + trace_header("CHANNEL SELECTION (channel out of range)", DIRECTION_NONE); + add_trace("port", NULL, "%d", ifport->portnum); + add_trace("position", NULL, "%d", index); + add_trace("channel", NULL, "%d", selchannel->channel); + end_trace(); + break; /* invalid channels */ + } + i = selchannel->channel-1-(selchannel->channel>=17); + if (i >= mISDNport->b_num) { + trace_header("CHANNEL SELECTION (channel out of range)", DIRECTION_NONE); + add_trace("port", NULL, "%d", ifport->portnum); + add_trace("position", NULL, "%d", index); + add_trace("channel", NULL, "%d", selchannel->channel); + add_trace("channels", NULL, "%d", mISDNport->b_num); + end_trace(); + break; /* channel not in port */ + } + if (mISDNport->b_port[i] == NULL) { + *channel = selchannel->channel; + trace_header("CHANNEL SELECTION (selecting given channel)", DIRECTION_NONE); + add_trace("port", NULL, "%d", ifport->portnum); + add_trace("position", NULL, "%d", index); + add_trace("channel", NULL, "%d", *channel); + end_trace(); + break; + } break; } - break; + if (*channel) + break; /* found channel */ + selchannel = selchannel->next; } - if (*channel) - break; /* found channel */ - selchannel = selchannel->next; } /* if channel was found, return mISDNport and channel */ @@ -921,6 +937,11 @@ void EndpointAppPBX::out_setup(void) } /* creating INTERNAL port */ SPRINT(portname, "%s-%d-out", mISDNport->ifport->interface->name, mISDNport->portnum); +#ifdef WITH_SS5 + if (mISDNport->ss5) + port = ss5_hunt_line(mISDNport); + else +#endif if (!mISDNport->gsm) port = new Pdss1((mISDNport->ntmode)?PORT_TYPE_DSS1_NT_OUT:PORT_TYPE_DSS1_TE_OUT, mISDNport, portname, &port_settings, channel, mISDNport->ifport->channel_force, mode); else @@ -1034,6 +1055,11 @@ void EndpointAppPBX::out_setup(void) if (mISDNport) { /* creating EXTERNAL port*/ SPRINT(portname, "%s-%d-out", mISDNport->ifport->interface->name, mISDNport->portnum); +#ifdef WITH_SS5 + if (mISDNport->ss5) + port = ss5_hunt_line(mISDNport); + else +#endif port = new Pdss1((mISDNport->ntmode)?PORT_TYPE_DSS1_NT_OUT:PORT_TYPE_DSS1_TE_OUT, mISDNport, portname, &port_settings, channel, mISDNport->ifport->channel_force, mode); if (!port) FATAL("No memory for Port instance\n"); @@ -1124,6 +1150,11 @@ void EndpointAppPBX::out_setup(void) } /* creating EXTERNAL port*/ SPRINT(portname, "%s-%d-out", mISDNport->ifport->interface->name, mISDNport->portnum); +#ifdef WITH_SS5 + if (mISDNport->ss5) + port = ss5_hunt_line(mISDNport); + else +#endif if (!mISDNport->gsm) port = new Pdss1((mISDNport->ntmode)?PORT_TYPE_DSS1_NT_OUT:PORT_TYPE_DSS1_TE_OUT, mISDNport, portname, &port_settings, channel, mISDNport->ifport->channel_force, mode); else diff --git a/configure b/configure index a924db7..d9875f8 100755 --- a/configure +++ b/configure @@ -640,6 +640,8 @@ ac_includes_default="\ ac_subst_vars='LTLIBOBJS POW_LIB LIBOBJS +ENABLE_SS5_FALSE +ENABLE_SS5_TRUE ENABLE_GSM_FALSE ENABLE_GSM_TRUE LIBCRYPTO @@ -735,6 +737,7 @@ enable_dependency_tracking with_asterisk with_ssl with_gsm +with_ss5 ' ac_precious_vars='build_alias host_alias @@ -1385,7 +1388,9 @@ Optional Packages: --with-ssl compile with ssl support (libcrypto) [default=check] - --with-gsm compile with OpenBSC support [default=no] + --with-gsm compile with OpenBSC support [default=check] + + --with-ss5 compile with CCITT No. 5 support [default=no] Some influential environment variables: @@ -6316,6 +6321,27 @@ else fi +# check for ss5 + +# Check whether --with-ss5 was given. +if test "${with_ss5+set}" = set; then + withval=$with_ss5; +else + with_ss5="check" +fi + + + + +if test "x$with_ss5" == "xyes" ; then + ENABLE_SS5_TRUE= + ENABLE_SS5_FALSE='#' +else + ENABLE_SS5_TRUE='#' + ENABLE_SS5_FALSE= +fi + + # Checks for libraries. { $as_echo "$as_me:$LINENO: checking for main in -lm" >&5 @@ -9826,6 +9852,13 @@ $as_echo "$as_me: error: conditional \"ENABLE_GSM\" was never defined. Usually this means the macro was only invoked conditionally." >&2;} { (exit 1); exit 1; }; } fi +if test -z "${ENABLE_SS5_TRUE}" && test -z "${ENABLE_SS5_FALSE}"; then + { { $as_echo "$as_me:$LINENO: error: conditional \"ENABLE_SS5\" was never defined. +Usually this means the macro was only invoked conditionally." >&5 +$as_echo "$as_me: error: conditional \"ENABLE_SS5\" was never defined. +Usually this means the macro was only invoked conditionally." >&2;} + { (exit 1); exit 1; }; } +fi : ${CONFIG_STATUS=./config.status} ac_write_fail=0 diff --git a/configure.ac b/configure.ac index eaa0af5..494b352 100644 --- a/configure.ac +++ b/configure.ac @@ -98,7 +98,7 @@ AS_IF([test "x$with_ssl" != xno], # check for gsm AC_ARG_WITH([gsm], [AS_HELP_STRING([--with-gsm], - [compile with OpenBSC support @<:@default=no@:>@]) + [compile with OpenBSC support @<:@default=check@:>@]) ], [], [with_gsm="check"]) @@ -115,6 +115,16 @@ AS_IF([test "x$with_gsm" != xno], AM_CONDITIONAL(ENABLE_GSM, test "x$with_gsm" == "xyes" ) +# check for ss5 +AC_ARG_WITH([ss5], + [AS_HELP_STRING([--with-ss5], + [compile with CCITT No. 5 support @<:@default=no@:>@]) + ], + [], + [with_ss5="check"]) + +AM_CONDITIONAL(ENABLE_SS5, test "x$with_ss5" == "xyes" ) + # Checks for libraries. AC_CHECK_LIB([m], [main]) AC_CHECK_LIB([ncurses], [main]) diff --git a/default/options.conf b/default/options.conf index 4b77211..4875773 100644 --- a/default/options.conf +++ b/default/options.conf @@ -12,6 +12,7 @@ #define DEBUG_PORT 0x0100 #define DEBUG_ISDN 0x0110 #define DEBUG_GSM 0x0120 +#define DEBUG_SS5 0x0130 #define DEBUG_VBOX 0x0180 #define DEBUG_EPOINT 0x0200 #define DEBUG_JOIN 0x0400 diff --git a/dss1.cpp b/dss1.cpp index a5d32bd..1cb52d1 100644 --- a/dss1.cpp +++ b/dss1.cpp @@ -1900,8 +1900,6 @@ void Pdss1::message_information(unsigned int epoint_id, int message_id, union pa } -int newteid = 0; - /* MESSAGE_SETUP */ void Pdss1::message_setup(unsigned int epoint_id, int message_id, union parameter *param) { diff --git a/ie.cpp b/ie.cpp index 933287e..ddb6c1b 100644 --- a/ie.cpp +++ b/ie.cpp @@ -616,7 +616,7 @@ void Pdss1::enc_ie_cause(struct l3_msg *l3m, int location, int cause) unsigned char p[256]; int l; - if (location<0 || location>7) { + if (location<0 || location>10) { PERROR("location(%d) is out of range.\n", location); return; } diff --git a/interface.c b/interface.c index df39b78..42f409c 100644 --- a/interface.c +++ b/interface.c @@ -916,6 +916,56 @@ static int inter_gsm(struct interface *interface, char *filename, int line, char return(0); #endif } +#ifdef WITH_SS5 +static int inter_ss5(struct interface *interface, char *filename, int line, char *parameter, char *value) +{ + struct interface_port *ifport; + char *element; + + /* port in chain ? */ + if (!interface->ifport) { + SPRINT(interface_error, "Error in %s (line %d): parameter '%s' expects previous 'port' definition.\n", filename, line, parameter); + return(-1); + } + /* goto end of chain */ + ifport = interface->ifport; + while(ifport->next) + ifport = ifport->next; + ifport->ss5 |= SS5_ENABLE; + while((element = strsep(&value, " "))) { + if (element[0] == '\0') + continue; + if (!strcasecmp(element, "connect")) + ifport->ss5 |= SS5_FEATURE_CONNECT; + else + if (!strcasecmp(element, "nodisconnect")) + ifport->ss5 |= SS5_FEATURE_NODISCONNECT; + else + if (!strcasecmp(element, "releaseguardtimer")) + ifport->ss5 |= SS5_FEATURE_RELEASEGUARDTIMER; + else + if (!strcasecmp(element, "bell")) + ifport->ss5 |= SS5_FEATURE_BELL; + else + if (!strcasecmp(element, "pulsedialing")) + ifport->ss5 |= SS5_FEATURE_PULSEDIALING; + else + if (!strcasecmp(element, "delay")) + ifport->ss5 |= SS5_FEATURE_DELAY; + else + if (!strcasecmp(element, "starrelease")) + ifport->ss5 |= SS5_FEATURE_STAR_RELEASE; + else + if (!strcasecmp(element, "suppress")) + ifport->ss5 |= SS5_FEATURE_SUPPRESS; + else { + SPRINT(interface_error, "Error in %s (line %d): parameter '%s' does not allow value element '%s'.\n", filename, line, parameter, element); + return(-1); + } + } + return(0); +} +#endif /* @@ -1049,6 +1099,22 @@ struct interface_param interface_param[] = { "This interface must be a loopback interface. The second loopback interface\n" "must be assigned to OpenBSC"}, +#ifdef WITH_SS5 + {"ccitt5", &inter_ss5, "[ [feature ...]]", + "Interface uses CCITT No. 5 inband signalling rather than D-channel.\n" + "This feature causes CPU load to rise and has no practical intend.\n" + "If you don't know what it is, you don't need it.\n" + "Features apply to protocol behaviour and blueboxing specials, they are:\n" + " connect - Connect incomming call to throughconnect audio, if required.\n" + " nodisconnect - Don't disconnect if incomming exchange disconnects.\n" + " releaseguardtimer - Tries to prevent Blueboxing by a longer release-guard.\n" + " bell - Allow releasing and pulse-dialing via 2600 Hz like old Bell systems.\n" + " pulsedialing - Use pulse dialing on outgoing exchange. (takes long!)\n" + " delay - Use on incomming exchange, to make you feel a delay when blueboxing.\n" + " starrelease - Pulse dialing a star (11 pulses per digit) clears current call.\n" + " suppress - Suppress received tones, as they will be recognized."}, +#endif + {NULL, NULL, NULL, NULL} }; @@ -1275,12 +1341,12 @@ static void set_defaults(struct interface_port *ifport) if (ifport->interface->is_tones) ifport->mISDNport->tones = (ifport->interface->is_tones==IS_YES); else - ifport->mISDNport->tones = (ifport->mISDNport->ntmode)?1:0; + ifport->mISDNport->tones = (ifport->mISDNport->ntmode || ifport->mISDNport->ss5)?1:0; /* default is_earlyb */ if (ifport->interface->is_earlyb) ifport->mISDNport->earlyb = (ifport->interface->is_earlyb==IS_YES); else - ifport->mISDNport->earlyb = (ifport->mISDNport->ntmode)?0:1; + ifport->mISDNport->earlyb = (ifport->mISDNport->ntmode && !ifport->mISDNport->ss5)?0:1; /* set locally flag */ if (ifport->interface->extension) ifport->mISDNport->locally = 1; @@ -1366,7 +1432,7 @@ void load_port(struct interface_port *ifport) struct mISDNport *mISDNport; /* open new port */ - mISDNport = mISDNport_open(ifport->portnum, ifport->portname, ifport->ptp, ifport->nt, ifport->tespecial, ifport->l1hold, ifport->l2hold, ifport->interface, ifport->gsm); + mISDNport = mISDNport_open(ifport->portnum, ifport->portname, ifport->ptp, ifport->nt, ifport->tespecial, ifport->l1hold, ifport->l2hold, ifport->interface, ifport->gsm, ifport->ss5); if (mISDNport) { /* link port */ ifport->mISDNport = mISDNport; @@ -1376,6 +1442,8 @@ void load_port(struct interface_port *ifport) SCPY(ifport->portname, mISDNport->name); /* set defaults */ set_defaults(ifport); + /* load static port instances */ + mISDNport_static(mISDNport); } else { ifport->block = 2; /* not available */ } diff --git a/interface.h b/interface.h index 2c2ee23..508a33e 100644 --- a/interface.h +++ b/interface.h @@ -52,6 +52,7 @@ struct interface_port { int l1hold; /* hold layer 1 (1=on, 0=off) */ int l2hold; /* hold layer 2 (1=force, -1=disable, 0=default) */ int gsm; /* interface is an GSM interface */ + unsigned int ss5; /* set, if SS5 signalling enabled, also holds feature bits */ int channel_force; /* forces channel by protocol */ int nodtmf; /* disables DTMF */ struct select_channel *out_channel; /* list of channels to select */ diff --git a/lcradmin.c b/lcradmin.c index 0138de9..222700b 100644 --- a/lcradmin.c +++ b/lcradmin.c @@ -179,6 +179,10 @@ int debug_port(struct admin_message *msg, struct admin_message *m, int line, int color(blue); addstr("'out >> disc'"); break; + case ADMIN_STATE_RELEASE: + color(blue); + addstr("'release'"); + break; default: color(blue); addstr("'--NONE--'"); @@ -729,7 +733,7 @@ const char *admin_state(int sock, char *argv[]) break; case B_STATE_ACTIVE: color(green); - addstr("busy "); + addstr("active "); break; case B_STATE_DEACTIVATING: color(yellow); diff --git a/lcrsocket.h b/lcrsocket.h index edb882b..43543c1 100644 --- a/lcrsocket.h +++ b/lcrsocket.h @@ -188,4 +188,5 @@ enum { ADMIN_STATE_CONNECT, ADMIN_STATE_IN_DISCONNECT, ADMIN_STATE_OUT_DISCONNECT, + ADMIN_STATE_RELEASE, }; diff --git a/mISDN.cpp b/mISDN.cpp index 5f55a36..c92e427 100644 --- a/mISDN.cpp +++ b/mISDN.cpp @@ -108,12 +108,15 @@ PmISDN::PmISDN(int type, mISDNport *mISDNport, char *portname, struct port_setti p_m_tx_gain = mISDNport->ifport->interface->tx_gain; p_m_rx_gain = mISDNport->ifport->interface->rx_gain; p_m_conf = 0; + p_m_mute = 0; p_m_txdata = 0; p_m_delay = 0; p_m_echo = 0; p_m_tone = 0; p_m_rxoff = 0; p_m_joindata = 0; + p_m_inband_send_on = 0; + p_m_inband_receive_on = 0; p_m_dtmf = !mISDNport->ifport->nodtmf; p_m_timeout = 0; p_m_timer = 0; @@ -159,6 +162,7 @@ PmISDN::PmISDN(int type, mISDNport *mISDNport, char *portname, struct port_setti /* we increase the number of objects: */ mISDNport->use++; PDEBUG(DEBUG_ISDN, "Created new mISDNPort(%s). Currently %d objects use, port #%d\n", portname, mISDNport->use, p_m_portnum); +//inband_receive_on(); } @@ -464,7 +468,7 @@ static void _bchannel_configure(struct mISDNport *mISDNport, int i) ph_control(mISDNport, port, handle, DSP_VOL_CHANGE_RX, port->p_m_rx_gain, "DSP-RX_GAIN", port->p_m_rx_gain); if (port->p_m_pipeline[0] && mode == B_MODE_TRANSPARENT) ph_control_block(mISDNport, port, handle, DSP_PIPELINE_CFG, port->p_m_pipeline, strlen(port->p_m_pipeline)+1, "DSP-PIPELINE", 0); - if (port->p_m_conf) + if (port->p_m_conf && !port->p_m_mute) ph_control(mISDNport, port, handle, DSP_CONF_JOIN, port->p_m_conf, "DSP-CONF", port->p_m_conf); if (port->p_m_echo) ph_control(mISDNport, port, handle, DSP_ECHO_ON, 0, "DSP-ECHO", 1); @@ -480,6 +484,19 @@ static void _bchannel_configure(struct mISDNport *mISDNport, int i) ph_control_block(mISDNport, port, handle, DSP_BF_ENABLE_KEY, port->p_m_crypt_key, port->p_m_crypt_key_len, "DSP-CRYPT", port->p_m_crypt_key_len); } + +void PmISDN::set_conf(int oldconf, int newconf) +{ + if (oldconf != newconf) { + PDEBUG(DEBUG_BCHANNEL, "we change conference from conf=%d to conf=%d.\n", oldconf, newconf); + if (p_m_b_index > -1) + if (p_m_mISDNport->b_state[p_m_b_index] == B_STATE_ACTIVE) + ph_control(p_m_mISDNport, this, p_m_mISDNport->b_socket[p_m_b_index], (newconf)?DSP_CONF_JOIN:DSP_CONF_SPLIT, newconf, "DSP-CONF", newconf); + } else + PDEBUG(DEBUG_BCHANNEL, "we already have conf=%d.\n", newconf); +} + + /* * subfunction for bchannel_event * destroy stack @@ -1218,15 +1235,20 @@ int PmISDN::handler(void) else p_m_load = 0; - /* to send data, tone must be active OR crypt messages must be on */ - if ((p_tone_name[0] || p_m_crypt_msg_loops) - && (p_m_load < ISDN_LOAD) - && (p_state==PORT_STATE_CONNECT || p_m_mISDNport->tones)) { + /* to send data, tone must be on */ + if ((p_tone_name[0] || p_m_crypt_msg_loops || p_m_inband_send_on) /* what tones? */ + && (p_m_load < ISDN_LOAD) /* enough load? */ + && (p_state==PORT_STATE_CONNECT || p_m_mISDNport->tones || p_m_inband_send_on)) { /* connected or inband-tones? */ int tosend = ISDN_LOAD - p_m_load, length; unsigned char buf[MISDN_HEADER_LEN+tosend]; struct mISDNhead *frm = (struct mISDNhead *)buf; unsigned char *p = buf+MISDN_HEADER_LEN; + /* copy inband signalling (e.g. used by ss5) */ + if (p_m_inband_send_on && tosend) { + tosend -= inband_send(p, tosend); + } + /* copy crypto loops */ while (p_m_crypt_msg_loops && tosend) { /* how much do we have to send */ @@ -1372,6 +1394,10 @@ void PmISDN::bchannel_receive(struct mISDNhead *hh, unsigned char *data, int len return; } + /* inband is processed */ + if (p_m_inband_receive_on) + inband_receive(data, len); + /* calls will not process any audio data unless * the call is connected OR tones feature is enabled. */ @@ -1566,6 +1592,7 @@ void PmISDN::set_tone(const char *dir, const char *tone) //extern struct lcr_msg *dddebug; void PmISDN::message_mISDNsignal(unsigned int epoint_id, int message_id, union parameter *param) { + int oldconf, newconf; switch(param->mISDNsignal.message) { case mISDNSIGNAL_VOLUME: if (p_m_tx_gain != param->mISDNsignal.tx_gain) { @@ -1587,19 +1614,10 @@ void PmISDN::message_mISDNsignal(unsigned int epoint_id, int message_id, union p break; case mISDNSIGNAL_CONF: -//if (dddebug) PDEBUG(DEBUG_ISDN, "dddebug = %d\n", dddebug->type); -//tone if (!p_m_tone && p_m_conf!=param->mISDNsignal.conf) - if (p_m_conf != param->mISDNsignal.conf) { - p_m_conf = param->mISDNsignal.conf; - PDEBUG(DEBUG_BCHANNEL, "we change conference to conf=%d.\n", p_m_conf); - if (p_m_b_index > -1) - if (p_m_mISDNport->b_state[p_m_b_index] == B_STATE_ACTIVE) - ph_control(p_m_mISDNport, this, p_m_mISDNport->b_socket[p_m_b_index], (p_m_conf)?DSP_CONF_JOIN:DSP_CONF_SPLIT, p_m_conf, "DSP-CONF", p_m_conf); - } else - PDEBUG(DEBUG_BCHANNEL, "we already have conf=%d.\n", p_m_conf); - /* we must set, even if currently tone forbids conf */ + oldconf = p_m_mute?0:p_m_conf; p_m_conf = param->mISDNsignal.conf; -//if (dddebug) PDEBUG(DEBUG_ISDN, "dddebug = %d\n", dddebug->type); + newconf = p_m_mute?0:p_m_conf; + set_conf(oldconf, newconf); break; case mISDNSIGNAL_JOINDATA: @@ -1744,7 +1762,7 @@ int mISDN_handler(void) isdnport=mISDNport->b_port[i]; if (isdnport) { /* call bridges in user space OR crypto OR recording */ - if (isdnport->p_m_joindata || isdnport->p_m_crypt_msg_loops || isdnport->p_m_crypt_listen || isdnport->p_record) { + if (isdnport->p_m_joindata || isdnport->p_m_crypt_msg_loops || isdnport->p_m_crypt_listen || isdnport->p_record || isdnport->p_m_inband_receive_on) { /* rx IS required */ if (isdnport->p_m_rxoff) { /* turn on RX */ @@ -1947,7 +1965,7 @@ int mISDN_handler(void) mISDNport->l2establish = 0; if (!mISDNport->gsm && mISDNport->l2hold && (mISDNport->ptp || !mISDNport->ntmode)) { - PDEBUG(DEBUG_ISDN, "the L2 establish timer expired, we try to establish the link portnum=%d.\n", mISDNport->portnum); +// PDEBUG(DEBUG_ISDN, "the L2 establish timer expired, we try to establish the link portnum=%d.\n", mISDNport->portnum); mISDNport->ml3->to_layer3(mISDNport->ml3, MT_L2ESTABLISH, 0, NULL); time(&mISDNport->l2establish); return(1); @@ -2027,7 +2045,7 @@ int mISDN_getportbyname(int sock, int cnt, char *portname) /* * global function to add a new card (port) */ -struct mISDNport *mISDNport_open(int port, char *portname, int ptp, int force_nt, int te_special, int l1hold, int l2hold, struct interface *interface, int gsm) +struct mISDNport *mISDNport_open(int port, char *portname, int ptp, int force_nt, int te_special, int l1hold, int l2hold, struct interface *interface, int gsm, unsigned int ss5) { int ret; struct mISDNport *mISDNport, **mISDNportp; @@ -2168,8 +2186,8 @@ struct mISDNport *mISDNport_open(int port, char *portname, int ptp, int force_nt while(*mISDNportp) mISDNportp = &((*mISDNportp)->next); mISDNport = (struct mISDNport *)MALLOC(sizeof(struct mISDNport)); - if (gsm) { - /* gsm audio is always active */ + if (gsm | ss5) { + /* gsm/ss5 link is always active */ mISDNport->l1link = 1; mISDNport->l2link = 1; } else { @@ -2183,7 +2201,13 @@ struct mISDNport *mISDNport_open(int port, char *portname, int ptp, int force_nt /* if pri, must set PTP */ if (pri) ptp = 1; - + + /* set ss5 params */ + if (ss5) { + /* try to keep interface enabled */ + l1hold = 1; + l2hold = 1; + } /* set l2hold */ switch (l2hold) { case -1: // off @@ -2273,6 +2297,7 @@ struct mISDNport *mISDNport_open(int port, char *portname, int ptp, int force_nt mISDNport->ptp = ptp; mISDNport->l1hold = l1hold; mISDNport->l2hold = l2hold; + mISDNport->ss5 = ss5; PDEBUG(DEBUG_ISDN, "Port has %d b-channels.\n", mISDNport->b_num); i = 0; while(i < mISDNport->b_num) { @@ -2306,12 +2331,33 @@ struct mISDNport *mISDNport_open(int port, char *portname, int ptp, int force_nt "PORT (open)"); add_trace("mode", NULL, (mISDNport->ntmode)?"network":"terminal"); add_trace("channels", NULL, "%d", mISDNport->b_num); + if (mISDNport->ss5) + add_trace("ccitt#5", NULL, "enabled"); end_trace(); + return(mISDNport); } /* + * load static port instances, if required by mISDNport + */ +void mISDNport_static(struct mISDNport *mISDNport) +{ + int i; + + i = 0; + while(i < mISDNport->b_num) { +#ifdef WITH_SS5 + if (mISDNport->ss5) + ss5_create_channel(mISDNport, i); +#endif + i++; + } +} + + +/* * function to free ALL cards (ports) */ void mISDNport_close_all(void) @@ -2418,7 +2464,7 @@ void PmISDN::txfromup(unsigned char *data, int length) /* check if high priority tones exist * ignore data in this case */ - if (p_tone_name[0] || p_m_crypt_msg_loops) + if (p_tone_name[0] || p_m_crypt_msg_loops || p_m_inband_send_on) return; /* preload procedure @@ -2451,3 +2497,61 @@ void PmISDN::txfromup(unsigned char *data, int length) p_m_load += length; } +int PmISDN::inband_send(unsigned char *buffer, int len) +{ + PERROR("this function must be derived to function!\n"); + return 0; +} + +void PmISDN::inband_send_on(void) +{ + PDEBUG(DEBUG_PORT, "turning inband signalling send on.\n"); + p_m_inband_send_on = 1; +} + +void PmISDN::inband_send_off(void) +{ + PDEBUG(DEBUG_PORT, "turning inband signalling send off.\n"); + p_m_inband_send_on = 0; +} + +void PmISDN::inband_receive(unsigned char *buffer, int len) +{ +// +// if (len >= SS5_DECODER_NPOINTS) +// ss5_decode(buffer, SS5_DECODER_NPOINTS); + PERROR("this function must be derived to function!\n"); +} + +void PmISDN::inband_receive_on(void) +{ + /* this must work during constructor, see ss5.cpp */ + PDEBUG(DEBUG_PORT, "turning inband signalling receive on.\n"); + p_m_inband_receive_on = 1; +} + +void PmISDN::inband_receive_off(void) +{ + PDEBUG(DEBUG_PORT, "turning inband signalling receive off.\n"); + p_m_inband_receive_on = 0; +} + +void PmISDN::mute_on(void) +{ + if (p_m_mute) + return; + PDEBUG(DEBUG_PORT, "turning mute on.\n"); + p_m_mute = 1; + set_conf(p_m_conf, 0); +} + +void PmISDN::mute_off(void) +{ + if (!p_m_mute) + return; + PDEBUG(DEBUG_PORT, "turning mute off.\n"); + p_m_mute = 0; + set_conf(0, p_m_conf); +} + + diff --git a/mISDN.h b/mISDN.h index 4f1e596..f8d2341 100644 --- a/mISDN.h +++ b/mISDN.h @@ -67,6 +67,9 @@ struct mISDNport { /* gsm */ int gsm; /* this is the (only) GSM interface */ int lcr_sock; /* socket of loopback on LCR side */ + + /* ss5 */ + unsigned int ss5; /* set, if SS5 signalling enabled, also holds feature bits */ }; extern mISDNport *mISDNport_first; @@ -89,7 +92,8 @@ calls with no bchannel (call waiting, call on hold). int mISDN_initialize(void); void mISDN_deinitialize(void); int mISDN_getportbyname(int sock, int cnt, char *portname); -struct mISDNport *mISDNport_open(int port, char *portname, int ptp, int force_nt, int te_special, int l1hold, int l2hold, struct interface *interface, int gsm); +struct mISDNport *mISDNport_open(int port, char *portname, int ptp, int force_nt, int te_special, int l1hold, int l2hold, struct interface *interface, int gsm, unsigned int ss5); +void mISDNport_static(struct mISDNport *mISDNport); void mISDNport_close_all(void); void mISDNport_close(struct mISDNport *mISDNport); void mISDN_port_reorder(void); @@ -121,6 +125,7 @@ class PmISDN : public Port int p_m_tx_gain, p_m_rx_gain; /* volume shift (0 = no change) */ char p_m_pipeline[256]; /* filter pipeline */ int p_m_echo, p_m_conf; /* remote echo, conference number */ + int p_m_mute; /* if set, conf is disconnected */ int p_m_tone; /* current kernel space tone */ int p_m_rxoff; /* rx from driver is disabled */ // int p_m_nodata; /* all parties within a conf are isdn ports, so pure bridging is possible */ @@ -152,6 +157,7 @@ class PmISDN : public Port void set_tone(const char *dir, const char *name); void set_echotest(int echotest); + void set_conf(int oldconf, int newconf); int p_m_portnum; /* used port number (1...n) */ int p_m_b_index; /* index 0,1 0..29 */ @@ -168,6 +174,18 @@ class PmISDN : public Port unsigned int p_m_remote_ref; /* join to export bchannel to */ int p_m_remote_id; /* sock to export bchannel to */ + int p_m_inband_send_on; /* triggers optional send function */ + int p_m_inband_receive_on; /* triggers optional receive function */ + int p_m_mute_on; /* if mute is on, bridge is removed */ + virtual int inband_send(unsigned char *buffer, int len); + void inband_send_on(void); + void inband_send_off(void); + virtual void inband_receive(unsigned char *buffer, int len); + void inband_receive_on(void); + void inband_receive_off(void); + void mute_on(void); + void mute_off(void); + int seize_bchannel(int channel, int exclusive); /* requests / reserves / links bchannels, but does not open it! */ void drop_bchannel(void); }; diff --git a/main.c b/main.c index 8d06aea..da21c23 100644 --- a/main.c +++ b/main.c @@ -360,6 +360,12 @@ int main(int argc, char *argv[]) /* generate alaw / ulaw tables */ generate_tables(options.law); +#ifdef WITH_SS5 + /* init ss5 sine tables */ + ss5_sine_generate(); + ss5_test_decode(); +#endif + /* load tones (if requested) */ if (fetch_tones() == 0) { fprintf(stderr, "Unable to fetch tones into memory.\n"); diff --git a/main.h b/main.h index 0b9f754..e5802de 100644 --- a/main.h +++ b/main.h @@ -68,6 +68,7 @@ void debug(const char *function, int line, const char *prefix, char *buffer); #define DEBUG_PORT 0x0100 #define DEBUG_ISDN 0x0110 #define DEBUG_GSM 0x0120 +#define DEBUG_SS5 0x0130 //#define DEBUG_KNOCK 0x0140 #define DEBUG_VBOX 0x0180 #define DEBUG_EPOINT 0x0200 @@ -151,6 +152,11 @@ extern "C" { #ifdef WITH_GSM #include "gsm.h" #endif +#ifdef WITH_SS5 +#include "ss5_encode.h" +#include "ss5_decode.h" +#include "ss5.h" +#endif #include "vbox.h" #include "join.h" #include "joinpbx.h" diff --git a/port.h b/port.h index be2598f..5e8eba9 100644 --- a/port.h +++ b/port.h @@ -18,6 +18,7 @@ #define PORT_CLASS_MASK 0xff00 #define PORT_CLASS_mISDN_DSS1 0x0110 #define PORT_CLASS_mISDN_GSM 0x0120 +#define PORT_CLASS_mISDN_SS5 0x0130 #define PORT_CLASS_mISDN_MASK 0xfff0 /* nt-mode */ #define PORT_TYPE_DSS1_NT_IN 0x0111 @@ -28,6 +29,10 @@ /* gsm */ #define PORT_TYPE_GSM_IN 0x0121 #define PORT_TYPE_GSM_OUT 0x0122 + /* ss5 */ +#define PORT_TYPE_SS5_IN 0x0131 +#define PORT_TYPE_SS5_OUT 0x0132 +#define PORT_TYPE_SS5_IDLE 0x0133 /* answering machine */ #define PORT_TYPE_VBOX_OUT 0x0311 diff --git a/socket_server.c b/socket_server.c index cea929c..7a08680 100644 --- a/socket_server.c +++ b/socket_server.c @@ -1035,6 +1035,9 @@ int admin_state(struct admin_queue **responsep) case PORT_STATE_OUT_DISCONNECT: response->am[num].u.p.state = ADMIN_STATE_OUT_DISCONNECT; break; + case PORT_STATE_RELEASE: + response->am[num].u.p.state = ADMIN_STATE_RELEASE; + break; default: response->am[num].u.p.state = ADMIN_STATE_IDLE; } diff --git a/ss5.cpp b/ss5.cpp new file mode 100644 index 0000000..d038769 --- /dev/null +++ b/ss5.cpp @@ -0,0 +1,1972 @@ +/*****************************************************************************\ +** ** +** 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_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" + +/* 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 5.0 /* timeout for overlap digits received on outgoing 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, +}; +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}, + {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 int busy signal + * this makes the line more authentic + */ +void Pss5::set_tone(const char *dir, const char *name) +{ + if (name && !strcmp(name, "cause_10")) + name = "busy"; + + 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, 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; +} + + +/* + * constructor + */ +Pss5::Pss5(int type, struct mISDNport *mISDNport, char *portname, struct port_settings *settings, int channel, int exclusive, int mode) : PmISDN(type, mISDNport, portname, settings, 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_signal_loss = 0; + p_m_s_decoder_count = 0; + //p_m_s_decoder_buffer; + p_m_s_sample_nr = 0; + p_m_s_recog = 0; + p_m_s_timer = 0.0; + p_m_s_timer_fn = NULL; + p_m_s_answer = 0; + p_m_s_busy_flash = 0; + p_m_s_clear_back = 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)); + + /* 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() +{ +} + + +/* + * 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; +} +void Pss5::_new_ss5_signal(int signal, const char *func, int line) +{ + if (p_m_s_signal) + PDEBUG(DEBUG_SS5, "%s(%s:%d): changing SS5 signal state from %s to %s\n", p_name, func, line, ss5_signal_name[p_m_s_signal], ss5_signal_name[signal]); + else + PDEBUG(DEBUG_SS5, "%s(%s:%d): changing SS5 signal state to %s\n", p_name, func, line, 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; + + 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); + p_m_s_decoder_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 */ + 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 { + /* storing last signal, in case it is lost */ + p_m_s_last_digit = digit; + } + } + + /* update mute */ + if ((p_m_mISDNport->ss5 & SS5_FEATURE_SUPPRESS)) { + 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 (p_m_mute) { + /* mute is on */ + if (mdigit != 'A' && mdigit != 'B' && mdigit != 'C') + mute_off(); + } else { + /* mute is off */ + if (mdigit == 'A' || mdigit == 'B' || mdigit == 'C') + mute_on(); + } + } + + /* 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_state(PORT_STATE_RELEASE); + 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_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; + } + + /* 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_BEYOND); + 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); +} + +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); + 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(); + /* special star release feature */ + if ((p_m_mISDNport->ss5 & SS5_FEATURE_STAR_RELEASE) && dial[1] == '*') { + ss5_trace_header(p_m_mISDNport, this, SS5_DIALING_IND, p_m_b_channel); + add_trace("star", NULL, "releases call"); + end_trace(); + goto star_release; + } + if (p_state == PORT_STATE_IN_SETUP) { + /* sending digit as setup */ + do_setup(dial); /* 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(); + star_release: + /* 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) +{ + 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); + } + + /* start clear-forward */ + ss5_trace_header(p_m_mISDNport, this, SS5_CLEAR_FORWARD_REQ, p_m_b_channel); + end_trace(); + start_signal(SS5_STATE_CLEAR_FORWARD); + + new_state(PORT_STATE_RELEASE); +} + + +/* + * create endpoint and send setup + */ +void Pss5::do_setup(char *dial) +{ + class Endpoint *epoint; + struct lcr_msg *message; + + SCPY(p_dialinginfo.id, dial); + p_dialinginfo.sending_complete = 1; + 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"); + if (!(epoint->ep_app = new DEFAULT_ENDPOINT_APP(epoint, 0))) //incoming + FATAL("No memory for Endpoint Application instance\n"); + 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); + +} + + +/* + * handler + */ +int Pss5::handler(void) +{ + int ret; + + if ((ret = PmISDN::handler())) + return(ret); + + /* handle timer */ + if (p_m_s_timer && p_m_s_timer < now) { + p_m_s_timer = 0.0; + (this->*(p_m_s_timer_fn))(); + } + + /* if answer signal is queued */ + if (p_m_s_answer && p_m_s_state == SS5_STATE_IDLE) { + p_m_s_answer = 0; + /* start answer */ + ss5_trace_header(p_m_mISDNport, this, SS5_ANSWER_REQ, p_m_b_channel); + end_trace(); + start_signal(SS5_STATE_ANSWER); + } + + /* if busy-flash signal is queued */ + if (p_m_s_busy_flash && p_m_s_state == SS5_STATE_IDLE) { + p_m_s_busy_flash = 0; + /* start busy-flash */ + ss5_trace_header(p_m_mISDNport, this, SS5_BUSY_FLASH_REQ, p_m_b_channel); + end_trace(); + start_signal(SS5_STATE_BUSY_FLASH); + } + + /* if clear-back signal is queued */ + if (p_m_s_clear_back && p_m_s_state == SS5_STATE_IDLE) { + p_m_s_clear_back = 0; + /* start clear-back */ + ss5_trace_header(p_m_mISDNport, this, SS5_CLEAR_BACK_REQ, p_m_b_channel); + end_trace(); + start_signal(SS5_STATE_CLEAR_BACK); + } + + return(0); +} + + +/* + * 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); + do_screen(1, p_callerinfo.id2, sizeof(p_callerinfo.id2), &p_callerinfo.ntype2, &p_callerinfo.present2, p_m_mISDNport->ifport->interface); + + /* 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) { + new_state(PORT_STATE_CONNECT); + p_m_s_answer = 1; + } + + set_tone("", NULL); +} + +/* MESSAGE_DISCONNECT */ +void Pss5::message_disconnect(unsigned int epoint_id, int message_id, union parameter *param) +{ + /* disconnect and clear forward (release guard) */ +// if ((p_type==PORT_TYPE_SS5_IN && !p_m_mISDNport->tones) /* incomming exchange with no tones */ +if (0 || p_type==PORT_TYPE_SS5_OUT) { /* outgoing exchange */ + do_release(param->disconnectinfo.cause, param->disconnectinfo.location); + return; + } + + ss5_trace_header(p_m_mISDNport, this, SS5_CLEAR_BACK_REQ, p_m_b_channel); + end_trace(); + start_signal(SS5_STATE_CLEAR_BACK); + + new_state(PORT_STATE_OUT_DISCONNECT); +} + +/* MESSAGE_RELEASE */ +void Pss5::message_release(unsigned int epoint_id, int message_id, union parameter *param) +{ + do_release(param->disconnectinfo.cause, param->disconnectinfo.location); +} + + +/* + * 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); +} + diff --git a/ss5.h b/ss5.h new file mode 100644 index 0000000..6897abd --- /dev/null +++ b/ss5.h @@ -0,0 +1,86 @@ +/*****************************************************************************\ +** ** +** LCR ** +** ** +**---------------------------------------------------------------------------** +** Copyright: Andreas Eversberg ** +** ** +** ss5-port header file ** +** ** +\*****************************************************************************/ + +#define SS5_ENABLE 0x00000001 +#define SS5_FEATURE_CONNECT 0x00000002 +#define SS5_FEATURE_NODISCONNECT 0x00000004 +#define SS5_FEATURE_RELEASEGUARDTIMER 0x00000008 +#define SS5_FEATURE_BELL 0x00000010 +#define SS5_FEATURE_PULSEDIALING 0x00000020 +#define SS5_FEATURE_DELAY 0x00000040 +#define SS5_FEATURE_STAR_RELEASE 0x00000080 +#define SS5_FEATURE_SUPPRESS 0x00000100 + +/* SS5 port classes */ +class Pss5 : public PmISDN +{ + public: + Pss5(int type, struct mISDNport *mISDNport, char *portname, struct port_settings *settings, int channel, int exclusive, int mode); + ~Pss5(); + int handler(void); + int message_epoint(unsigned int epoint_id, int message, union parameter *param); + void set_tone(const char *dir, const char *name); + + int p_m_s_state; /* current signalling state */ + int p_m_s_signal; /* current state of current signal */ + char p_m_s_dial[64]; /* current dialing register */ + int p_m_s_digit_i; /* current digit of register counter */ + int p_m_s_pulsecount; /* counts pule dialing half cycles */ + char p_m_s_last_digit; /* stores last digit to fill short signal losses */ + int p_m_s_signal_loss; /* sample counter for loss of signal check */ + int p_m_s_decoder_count; /* samples currently decoded */ + unsigned char p_m_s_decoder_buffer[SS5_DECODER_NPOINTS]; /* buffer for storing one goertzel window */ + unsigned char p_m_s_delay_digits[3000/SS5_DECODER_NPOINTS]; /* delay buffer for received digits */ + unsigned char p_m_s_delay_mute[400/SS5_DECODER_NPOINTS]; /* 40 ms delay on mute, so a 'chirp' can be heared */ + int p_m_s_sample_nr; /* decoder's sample number, counter */ + int p_m_s_recog; /* sample counter to wait for signal recognition time */ + double p_m_s_timer; + void (Pss5::*p_m_s_timer_fn)(void); + int p_m_s_answer; /* queued signal */ + int p_m_s_busy_flash; /* queued signal */ + int p_m_s_clear_back; /* queued signal */ + + void _new_ss5_state(int state, const char *func, int line); + void _new_ss5_signal(int signal, const char *func, int line); + void inband_receive(unsigned char *buffer, int len); + int inband_send(unsigned char *buffer, int len); + int inband_dial_mf(unsigned char *buffer, int len, int count); + int inband_dial_pulse(unsigned char *buffer, int len, int count); + void start_signal(int); + void start_outgoing(void); + void do_release(int cause, int location); + void do_setup(char *digit); + + void seizing_ind(void); + void digit_ind(char digit); + void pulse_ind(int on); + void proceed_to_send_ind(void); + void busy_flash_ind(void); + void answer_ind(void); + void forward_ind(void); + void clear_back_ind(void); + void clear_forward_ind(void); + void release_guard_ind(void); + void double_seizure_ind(void); + void forward_transfer_ind(void); + void message_setup(unsigned int epoint_id, int message_id, union parameter *param); + void message_connect(unsigned int epoint_id, int message_id, union parameter *param); + void message_disconnect(unsigned int epoint_id, int message_id, union parameter *param); + void message_release(unsigned int epoint_id, int message_id, union parameter *param); + +}; + +#define new_ss5_state(a) _new_ss5_state(a, __FUNCTION__, __LINE__) +#define new_ss5_signal(a) _new_ss5_signal(a, __FUNCTION__, __LINE__) + +void ss5_create_channel(struct mISDNport *mISDNport, int i); +class Pss5 *ss5_hunt_line(struct mISDNport *mISDNport); + diff --git a/ss5_decode.c b/ss5_decode.c new file mode 100644 index 0000000..541f6be --- /dev/null +++ b/ss5_decode.c @@ -0,0 +1,181 @@ +/* + * SS5 signal decoder. + * + * Copyright by Andreas Eversberg (jolly@eversberg.eu) + * based on different decoders such as ISDN4Linux + * copyright by Karsten Keil + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include "main.h" +#include "ss5_decode.h" + +/* enable level debugging */ +//#define DEBUG_LEVELS + +#define NCOEFF 8 /* number of frequencies to be analyzed */ + +#define MIN_DB 0.01995262 /* -17 db */ +#define DIFF_DB 0.31622777 /* -5 db */ +#define SNR 1.3 /* noise may not exceed signal by that factor */ + +/* For DTMF recognition: + * 2 * cos(2 * PI * k / N) precalculated for all k + */ +static signed long long cos2pik[NCOEFF] = +{ + /* k = 2*cos(2*PI*f/8000), k << 15 + * 700, 900, 1100, 1300, 1500, 1700, 2400, 2600 */ + 55879, 49834, 42562, 34242, 25080, 15299, -20252, -29753 +}; + +/* detection matrix for two frequencies */ +static char decode_two[8][8] = +{ + {' ', '1', '2', '4', '7', '*', ' ', ' '}, /* * = code 11 */ + {'1', ' ', '3', '5', '8', '#', ' ', ' '}, /* # = code 12 */ + {'2', '3', ' ', '6', '9', 'a', ' ', ' '}, /* a = KP1 */ + {'4', '5', '6', ' ', '0', 'b', ' ', ' '}, /* b = KP2 */ + {'7', '8', '9', '0', ' ', 'c', ' ', ' '}, /* c = ST */ + {'*', '#', 'a', 'b', 'c', ' ', ' ', ' '}, + {' ', ' ', ' ', ' ', ' ', ' ', ' ', 'C'}, /* C = 2600+2400 */ + {' ', ' ', ' ', ' ', ' ', ' ', 'C', ' '} +}; + +static char decode_one[8] = + {' ', ' ', ' ', ' ', ' ', ' ', 'A', 'B'}; /* A = 2400, B = 2600 */ +/* + * calculate the coefficients of the given sample and decode + */ + +char ss5_decode(unsigned char *data, int len) +{ + signed short buf[len]; + signed long sk, sk1, sk2, low, high; + int k, n, i; + int f1 = 0, f2 = 0, f3 = 0; + double result[NCOEFF], power, noise, snr; + signed long long cos2pik_; + char digit = ' '; + + /* convert samples */ + for (i = 0; i < len; i++) + buf[i] = audio_law_to_s32[*data++]; + + /* now we have a full buffer of signed long samples - we do goertzel */ + for (k = 0; k < NCOEFF; k++) { + sk = 0; + sk1 = 0; + sk2 = 0; + cos2pik_ = cos2pik[k]; + for (n = 0; n < len; n++) { + sk = ((cos2pik_*sk1)>>15) - sk2 + buf[n]; + sk2 = sk1; + sk1 = sk; + } + sk >>= 8; + sk2 >>= 8; + if (sk > 32767 || sk < -32767 || sk2 > 32767 || sk2 < -32767) + PERROR("Tone-Detection overflow\n"); + /* compute |X(k)|**2 */ + result[k] = sqrt ( + (sk * sk) - + (((cos2pik[k] * sk) >> 15) * sk2) + + (sk2 * sk2) + ) / len / 62; /* level of 1 is 0 db*/ + } + + /* now we do noise level calculation */ + low = 32767; + high = -32768; + for (n = 0; n < len; n++) { + sk = buf[n]; + if (sk < low) + low = sk; + if (sk > high) + high = sk; + } + noise = ((double)(high-low) / 65536.0); + + /* find the two loudest frequencies + one less lower frequency to detect noise */ + power = 0.0; + for (i = 0; i < NCOEFF; i++) { + if (result[i] > power) { + power = result[i]; + f1 = i; + } + } + power = 0.0; + for (i = 0; i < NCOEFF; i++) { + if (i != f1 && result[i] > power) { + power = result[i]; + f2 = i; + } + } + power = 0.0; + for (i = 0; i < NCOEFF; i++) { + if (i != f1 && i != f2 && result[i] > power) { + power = result[i]; + f3 = i; + } + } + +#if 0 + /* check one frequency */ + if (result[f1] > MIN_DB /* must be at least -17 db */ + && result[f1]*DIFF_DB > result[f2]) /* must be 5 db above other tones */ + digit = decode_one[f1]; + /* check two frequencies */ + if (result[f1] > MIN_DB && result[f2] > MIN_DB /* must be at lease -17 db */ + && result[f1]*DIFF_DB <= result[f2] /* f2 must be not less than 5 db below f1 */ + && result[f1]*DIFF_DB > result[f3]) /* f1 must be 5 db above other tones */ + digit = decode_two[f1][f2]; +#endif + snr = 0; + /* check one frequency */ + if (result[f1] > MIN_DB /* must be at least -17 db */ + && result[f1]*SNR > noise) { /* */ + digit = decode_one[f1]; + snr = result[f1] / noise; + } + /* check two frequencies */ + if (result[f1] > MIN_DB && result[f2] > MIN_DB /* must be at lease -17 db */ + && result[f1]*DIFF_DB <= result[f2] /* f2 must be not less than 5 db below f1 */ + && (result[f1]+result[f2])*SNR > noise) { /* */ + digit = decode_two[f1][f2]; + snr = (result[f1]+result[f2]) / noise; + } + + /* debug powers */ +#ifdef DEBUG_LEVELS + for (i = 0; i < NCOEFF; i++) + printf("%d:%3d %c ", i, (int)(result[i]*100), (f1==i || f2==i)?'*':' '); + printf("N:%3d digit:%c snr=%3d\n", (int)(noise*100), digit, (int)(snr*100)); +#endif + + return digit; +} + +void ss5_test_decode(void) +{ +#ifdef DEBUG_LEVELS + double phase; + int i, j; + signed short sample; + + unsigned char buffer[SS5_DECODER_NPOINTS]; + for (i = 0; i < 4000; i += 10) { + phase = 2.0 * 3.14159265 * i / 8000.0; + for (j = 0; j < SS5_DECODER_NPOINTS; j++) { + sample = sin(phase * j) * 1000; + buffer[j] = audio_s16_to_law[sample & 0xffff]; + } + printf("FRQ:%04d:", i); + ss5_decode(buffer, SS5_DECODER_NPOINTS); + } +#endif +} + diff --git a/ss5_decode.h b/ss5_decode.h new file mode 100644 index 0000000..4acaecb --- /dev/null +++ b/ss5_decode.h @@ -0,0 +1,10 @@ +/* + * SS5 signal decoder header file + * + */ + +#define SS5_DECODER_NPOINTS 80 /* size of goertzel window */ + +char ss5_decode(unsigned char *data, int len); +void ss5_test_decode(void); + diff --git a/ss5_encode.c b/ss5_encode.c new file mode 100644 index 0000000..0b2ab54 --- /dev/null +++ b/ss5_encode.c @@ -0,0 +1,102 @@ +/* + * SS5 signal coder. + * + * Copyright by Andreas Eversberg (jolly@eversberg.eu) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include "main.h" +#include "ss5_encode.h" + + /* 2*PI*f/8000 */ +static double ss5_freq[9][2] = { + {0.0, 0}, /* 0: 0 */ + {700, 0.19952623}, /* 1: 700, -7db */ + {900, 0.19952623}, /* 2: 900, -7db */ + {1100, 0.19952623}, /* 3: 1100, -7db */ + {1300, 0.19952623}, /* 4: 1300, -7db */ + {1500, 0.19952623}, /* 5: 1500, -7db */ + {1700, 0.19952623}, /* 6: 1700, -7db */ + {2400, 0.12589254}, /* 7: 2400, -9db */ + {2600, 0.12589254}, /* 8: 2600, -9db */ +}; + +static char ss5_digits[][3] = { + {'1', 1, 2}, + {'2', 1, 3}, + {'3', 2, 3}, + {'4', 1, 4}, + {'5', 2, 4}, + {'6', 3, 4}, + {'7', 1, 5}, + {'8', 2, 5}, + {'9', 3, 5}, + {'0', 4, 5}, + {'*', 1, 6}, /* code 11 */ + {'#', 2, 6}, /* code 12 */ + {'a', 3, 6}, /* KP1 */ + {'b', 4, 6}, /* KP2 */ + {'c', 5, 6}, /* ST */ + {'A', 7, 0}, /* 2400 answer, acknowledge */ + {'B', 8, 0}, /* 2600 busy */ + {'C', 7, 8}, /* 2600+2400 clear forward */ + {0 , 0, 0}, +}; + +static unsigned char sintab[15+3][8192]; /* sine tables of about one second sound (error <1Hz) */ + +/* generate sine tables */ +void ss5_sine_generate(void) +{ + int i, j; + int cycles1, cycles2; + double vol1, vol2, phase1, phase2; + signed short sample; + + for (i = 0; i < 15+3; i++) { + /* how many cycles are within 8192 samples (rounded!) */ + cycles1 = (int)(ss5_freq[(int)ss5_digits[i][1]][0] / 8000.0 * 8192.0 + 0.5); + cycles2 = (int)(ss5_freq[(int)ss5_digits[i][2]][0] / 8000.0 * 8192.0 + 0.5); + /* how much phase shift within one cycle */ + phase1 = 2.0 * 3.14159265 * cycles1 / 8192.0; + phase2 = 2.0 * 3.14159265 * cycles2 / 8192.0; + /* volume */ + vol1 = ss5_freq[(int)ss5_digits[i][1]][1] * 32768.0; + vol2 = ss5_freq[(int)ss5_digits[i][2]][1] * 32768.0; + for (j = 0; j < 8192; j++) { + sample = (int)(sin(phase1 * j) * vol1); + sample += (int)(sin(phase2 * j) * vol2); + sintab[i][j] = audio_s16_to_law[sample & 0xffff]; + } + } +} + +/* encode digit at given sample_nr with given lengt and return law-encoded audio */ +unsigned char *ss5_encode(unsigned char *buffer, int len, char digit, int sample_nr) +{ + int i, j; + + /* get frequency from digit */ + i = 0; + while(ss5_digits[i][0]) { + if (digit == ss5_digits[i][0]) + break; + i++; + } + if (!ss5_digits[i][0]) { + PERROR("Digit '%c' does not exist.\n", digit); + memset(buffer, audio_s16_to_law[0], sizeof(buffer)); + return buffer; + } + + /* copy tones */ + for (j = 0; j < len; j++) + *buffer++ = sintab[i][sample_nr++ & 8191]; + + return buffer; +} + + diff --git a/ss5_encode.h b/ss5_encode.h new file mode 100644 index 0000000..50e79cf --- /dev/null +++ b/ss5_encode.h @@ -0,0 +1,7 @@ +/* + * SS5 signal coder header file + */ + +void ss5_sine_generate(void); +unsigned char *ss5_encode(unsigned char *buffer, int len, char digit, int sample_nr); +