Directory reorder #3

git-svn-id: http://yate.null.ro/svn/yate/trunk@1476 acf43c95-373e-0410-b603-e72c3f656dc1
This commit is contained in:
paulc 2007-11-15 23:06:36 +00:00
parent dc7b6f93d0
commit b42a894ddc
70 changed files with 5148 additions and 3715 deletions

View File

@ -16,7 +16,7 @@ CFLAGS := -O2 @MODULE_CPPFLAGS@ @INLINE_FLAGS@
LDFLAGS:=
LDCONFIG:=true
MKDEPS := ./config.status
MKDEPS := @top_builddir@/config.status
PROGS:= yate
YLIB := libyate.so.@PACKAGE_VERSION@
SLIBS:= $(YLIB) libyate.so
@ -35,17 +35,19 @@ DOCGEN_F := $(INCS)
prefix = @prefix@
exec_prefix = @exec_prefix@
datarootdir = @datarootdir@
datadir = @datadir@
basedir = @libdir@/yate
confdir = @sysconfdir@/yate
bindir = @bindir@
libdir = @libdir@
incdir = @includedir@/yate
mandir = @mandir@
docdir = $(prefix)/share/doc/yate-@PACKAGE_VERSION@
docdir = @datarootdir@/doc/yate-@PACKAGE_VERSION@
vardir = @localstatedir@/lib/yate
moddir = $(basedir)/modules
scrdir = $(basedir)/scripts
shrdir = $(datadir)/yate
# include optional local make rules
-include YateLocal.mak
@ -54,18 +56,18 @@ DOCGEN :=
DOCGEN_K :=
DOCGEN_D :=
ifneq (_@KDOC_BIN@,_)
DOCGEN_K := @KDOC_BIN@ -C ./kdoc-filter.sh -d docs/api/ $(DOCGEN_F)
DOCGEN_K := @KDOC_BIN@ -C ./docs/doc-filter.sh -d docs/api/ $(DOCGEN_F)
DOCGEN := $(DOCGEN_K)
endif
ifneq (_@DOXYGEN_BIN@,_)
DOCGEN_D := (cat Doxyfile; echo 'INPUT = $(DOCGEN_F)') | @DOXYGEN_BIN@ -
DOCGEN_D := (cat docs/Doxyfile; echo 'INPUT = $(DOCGEN_F)') | @DOXYGEN_BIN@ -
DOCGEN := $(DOCGEN_D)
endif
.PHONY: all everything debug ddebug xdebug ndebug
all: engine modules clients
everything: engine contrib modules clients test apidocs
everything: engine libs modules clients test apidocs
debug:
$(MAKE) all DEBUG=-g3 MODSTRIP=
@ -85,8 +87,7 @@ clean:
$(MAKE) -C ./engine $@
$(MAKE) -C ./modules $@
$(MAKE) -C ./clients $@
$(MAKE) -C ./test $@
@for i in contrib/*; do \
@for i in libs/*; do \
test ! -f "$$i/Makefile" || $(MAKE) -C "$$i" clean ; \
done
@ -101,8 +102,8 @@ clean-config-files: check-topdir
-rm -f @CONFIGURE_FILES@
clean-tables: check-topdir
-rm -f yate.spec
$(MAKE) -C ./tables -f Makefile.tables mrproper
-rm -f packing/rpm/yate.spec
$(MAKE) -C ./engine/tables -f Makefile.tables mrproper
clean-apidocs: check-topdir
-rm docs/api/*.*
@ -112,7 +113,7 @@ distclean: check-topdir clean clean-config-files
cvsclean: check-topdir clean clean-tables clean-apidocs clean-config-files
-rm -f configure
.PHONY: engine contrib modules clients test apidocs-build apidocs-kdoc apidocs-doxygen check-topdir windows
.PHONY: engine libs modules clients test apidocs-build apidocs-kdoc apidocs-doxygen check-topdir windows
engine: tables library libyate.so $(PROGS)
apidocs-kdoc: check-topdir
@ -138,7 +139,7 @@ apidocs-build:
apidocs: @srcdir@/docs/api/index.html
@srcdir@/docs/api/index.html: @srcdir@/Doxyfile \
@srcdir@/docs/api/index.html: @srcdir@/docs/Doxyfile \
@srcdir@/yateclass.h @srcdir@/yatemime.h @srcdir@/yatengine.h \
@srcdir@/yatephone.h @srcdir@/yatecbase.h
$(MAKE) apidocs-build
@ -160,20 +161,20 @@ war:
modules clients test: engine
$(MAKE) -C ./$@ all
contrib: engine
@for i in contrib/*; do \
libs: engine
@for i in libs/*; do \
test ! -f "$$i/Makefile" || $(MAKE) -C "$$i" all ; \
done
tables: @srcdir@/tables/all.h
tables: @srcdir@/engine/tables/all.h
@srcdir@/tables/all.h:
$(MAKE) -C @srcdir@/tables -f Makefile.tables all
@srcdir@/engine/tables/all.h:
$(MAKE) -C @srcdir@/engine/tables -f Makefile.tables all
yatepaths.h: $(MKDEPS)
@echo '#define MOD_PATH "$(DESTDIR)$(moddir)"' > $@
@echo '#define SCR_PATH "$(DESTDIR)$(scrdir)"' >> $@
@echo '#define CFG_PATH "$(DESTDIR)$(confdir)"' >> $@
@echo '#define CFG_PATH "$(DESTDIR)$(confdir)"' > $@
@echo '#define MOD_PATH "$(DESTDIR)$(moddir)"' >> $@
@echo '#define SHR_PATH "$(DESTDIR)$(shrdir)"' >> $@
windows: check-topdir
@cmp -s yateversn.h $@/yateversn.h || cp -p yateversn.h $@/yateversn.h
@ -196,11 +197,11 @@ install-noapi: all
install $(PROGS) yate-config "$(DESTDIR)$(bindir)/"
$(MAKE) -C ./modules install
$(MAKE) -C ./clients install
$(MAKE) -C ./scripts install
$(MAKE) -C ./share install
$(MAKE) -C ./conf.d install
@mkdir -p "$(DESTDIR)$(mandir)/man8/" && \
for i in $(MAN8) ; do \
install -m 0644 @srcdir@/$$i "$(DESTDIR)$(mandir)/man8/" ; \
install -m 0644 @srcdir@/docs/man/$$i "$(DESTDIR)$(mandir)/man8/" ; \
done
@mkdir -p "$(DESTDIR)$(libdir)/pkgconfig/" && \
install -m 0644 yate.pc "$(DESTDIR)$(libdir)/pkgconfig/"
@ -208,7 +209,7 @@ install-noapi: all
for i in $(INCS) ; do \
install -m 0644 @srcdir@/$$i "$(DESTDIR)$(incdir)/" ; \
done
for i in $(GENS) ; do \
@for i in $(GENS) ; do \
install -m 0644 $$i "$(DESTDIR)$(incdir)/" ; \
done
@mkdir -p "$(DESTDIR)$(docdir)/api/" && \
@ -217,7 +218,7 @@ install-noapi: all
done ;
install-api: apidocs
mkdir -p "$(DESTDIR)$(docdir)/api/" && \
@mkdir -p "$(DESTDIR)$(docdir)/api/" && \
install -m 0644 @srcdir@/docs/*.html "$(DESTDIR)$(docdir)/" && \
install -m 0644 @srcdir@/docs/api/*.* "$(DESTDIR)$(docdir)/api/"
@ -249,24 +250,25 @@ install-root uninstall-root: LDCONFIG:=ldconfig
.PHONY: snapshot tarball rpm srpm
snapshot tarball: check-topdir clean tables windows apidocs
@if [ $@ = snapshot ]; then ver="`date '+CVS-%Y%m%d'`"; else ver="@PACKAGE_VERSION@-@PACKAGE_RELEASE@"; fi ; \
@if [ $@ = snapshot ]; then ver="`date '+SVN-%Y%m%d'`"; else ver="@PACKAGE_VERSION@-@PACKAGE_RELEASE@"; fi ; \
wd=`pwd|sed 's,^.*/,,'`; \
mkdir -p tarballs; cd ..; \
mkdir -p packing/tarballs; cd ..; \
echo $$wd/tar-exclude >$$wd/tar-exclude; \
find $$wd -name Makefile >>$$wd/tar-exclude; \
find $$wd -name YateLocal.mak >>$$wd/tar-exclude; \
find $$wd -name 'YateLocal*' >>$$wd/tar-exclude; \
find $$wd/conf.d -name '*.conf' >>$$wd/tar-exclude; \
find $$wd -name '*.cache' >>$$wd/tar-exclude; \
find $$wd -name '*~' >>$$wd/tar-exclude; \
find $$wd -name '.*.swp' >>$$wd/tar-exclude; \
if [ $@ = tarball ]; then \
find $$wd -name .svn >>$$wd/tar-exclude; \
find $$wd -name CVS >>$$wd/tar-exclude; \
find $$wd -name .cvsignore >>$$wd/tar-exclude; \
else \
echo "$$wd/yate.spec" >>$$wd/tar-exclude; \
echo "$$wd/packing/rpm/yate.spec" >>$$wd/tar-exclude; \
fi ; \
tar czf $$wd/tarballs/$$wd-$$ver.tar.gz \
--exclude $$wd/tarballs \
tar czf $$wd/packing/tarballs/$$wd-$$ver.tar.gz \
--exclude $$wd/packing/tarballs \
--exclude $$wd/config.status \
--exclude $$wd/config.log \
--exclude $$wd/run \
@ -279,10 +281,10 @@ snapshot tarball: check-topdir clean tables windows apidocs
rm $$wd/tar-exclude
rpm: check-root tarball
rpmbuild -tb tarballs/yate-@PACKAGE_VERSION@-@PACKAGE_RELEASE@.tar.gz
rpmbuild -tb packing/tarballs/yate-@PACKAGE_VERSION@-@PACKAGE_RELEASE@.tar.gz
srpm: check-root tarball
rpmbuild -ta tarballs/yate-@PACKAGE_VERSION@-@PACKAGE_RELEASE@.tar.gz
rpmbuild -ta packing/tarballs/yate-@PACKAGE_VERSION@-@PACKAGE_RELEASE@.tar.gz
%.o: @srcdir@/%.cpp $(MKDEPS) @srcdir@/yatengine.h
$(COMPILE) -c $<
@ -294,7 +296,7 @@ config.status: @srcdir@/configure
./config.status --recheck
Makefile: @srcdir@/Makefile.in $(MKDEPS)
./config.status
@top_builddir@/config.status
yate: libyate.so $(OBJS) $(LIBS)
$(LINK) -o $@ $(LIBTHR) $^
@ -308,9 +310,9 @@ library $(YLIB): yatepaths.h
.PHONY: help
help:
@echo -e 'Usual make targets:\n\
all engine contrib modules clients apidocs test everything\n\
install uninstall install-noapi install-root uninstall-root\n\
clean distclean cvsclean (avoid this one!) clean-apidocs\n\
debug ddebug xdebug (carefull!)\n\
snapshot tarball rpm srpm'
@echo -e 'Usual make targets:\n'\
' all engine libs modules clients apidocs test everything\n'\
' install uninstall install-noapi install-root uninstall-root\n'\
' clean distclean cvsclean (avoid this one!) clean-apidocs\n'\
' debug ddebug xdebug (carefull!)\n'\
' snapshot tarball rpm srpm'

View File

@ -2,7 +2,7 @@
# Run this to generate a new configure script
if [ -s tables/a2s.h ]; then
if [ -s engine/tables/a2s.h ]; then
echo "Good! Tables are generated so we don't need sox."
else
if [ -z `which sox 2>/dev/null` ]; then

View File

@ -1,5 +1,5 @@
Makefile
YateLocal.mak
YateLocal*
core*
yate-*
*.o

View File

@ -17,18 +17,19 @@ LDFLAGS:= -L.. -lyate
INCFILES := @top_srcdir@/yatengine.h @top_srcdir@/yatephone.h ../yateversn.h
SUBDIRS :=
MKDEPS := ../config.status
MKDEPS := @top_builddir@/config.status
PROGS :=
LIBS :=
MENUFILES :=
DESKFILES :=
GTKCLIENT := ../contrib/gtk2/libgtk2client.a
GTKCLIENT := gtk2/libgtk2client.a
ifneq (@HAVE_GTK2@,no)
PROGS := $(PROGS) yate-gtk2
MENUFILES := $(MENUFILES) yate-gtk2.menu
DESKFILES := $(DESKFILES) yate-gtk2.desktop
ICONFILES := $(ICONFILES) null_team-16.png null_team-32.png
endif
LOCALFLAGS =
@ -38,10 +39,14 @@ LINK = $(CXX) $(LDFLAGS)
prefix = @prefix@
exec_prefix = @exec_prefix@
datarootdir = @datarootdir@
datadir = @datadir@
bindir = @bindir@
moddir = @libdir@/yate
menudir= @libdir@/menu
deskdir= $(prefix)/share/applications
shrdir = $(datadir)/yate
deskdir= $(datadir)/applications
icondir= $(datadir)/pixmaps
# include optional local make rules
-include YateLocal.mak
@ -68,6 +73,12 @@ install: all do-install
install -D -m 0644 "@srcdir@/$$i" "$(DESTDIR)$(menudir)/$$i" ; \
done \
)
$(if $(ICONFILES),\
@mkdir -p "$(DESTDIR)$(icondir)/" && \
for i in $(ICONFILES) ; do \
install -D -m 0644 "@srcdir@/$$i" "$(DESTDIR)$(icondir)/$$i" ; \
done \
)
$(if $(DESKFILES),\
@mkdir -p "$(DESTDIR)$(deskdir)/" && \
for i in $(DESKFILES) ; do \
@ -93,6 +104,12 @@ uninstall: do-uninstall
done ; \
rmdir "$(DESTDIR)$(deskdir)" \
)
$(if $(ICONFILES),\
@-for i in $(ICONFILES) ; do \
rm "$(DESTDIR)$(icondir)/$$i" ; \
done ; \
rmdir "$(DESTDIR)$(icondir)" \
)
%.o: @srcdir@/%.cpp $(MKDEPS) $(INCFILES)
$(COMPILE) -c $<
@ -108,7 +125,7 @@ do-all do-strip do-clean do-install do-uninstall:
)
Makefile: @srcdir@/Makefile.in $(MKDEPS)
cd .. && ./config.status
cd @top_builddir@ && ./config.status
yate-%: @srcdir@/main-%.cpp $(MKDEPS) ../libyate.so $(INCFILES)
$(COMPILE) -o $@ $(LOCALFLAGS) $< $(LIBTHR) $(LDFLAGS) $(LOCALLIBS)
@ -119,4 +136,4 @@ yate-gtk2: LOCALFLAGS = @GTK2_INC@
yate-gtk2: LOCALLIBS = @GTK2_LIB@
$(GTKCLIENT):
$(MAKE) -C ../contrib/gtk2
$(MAKE) -C gtk2

View File

@ -1,5 +1,5 @@
Makefile
YateLocal.mak
YateLocal*
core*
*.o
*.a

View File

@ -46,5 +46,5 @@ $(PROJECT): $(OBJECTS)
%.o: @srcdir@/%.cpp $(INCFILES)
$(COMPILE) -c $<
Makefile: @srcdir@/Makefile.in ../../config.status
cd ../.. && ./config.status
Makefile: @srcdir@/Makefile.in @top_builddir@/config.status
cd @top_builddir@ && ./config.status

View File

@ -1986,7 +1986,7 @@ GTKClient::GTKClient()
m_oneThread = Engine::config().getBoolValue("client","onethread",ONE_THREAD);
s_skinPath = Engine::config().getValue("client","skinbase");
if (s_skinPath.null())
s_skinPath << Engine::modulePath() << Engine::pathSeparator() << "skin";
s_skinPath << Engine::sharedPath() << Engine::pathSeparator() << "skins";
if (!s_skinPath.endsWith(Engine::pathSeparator()))
s_skinPath << Engine::pathSeparator();
String skin(Engine::config().getValue("client","skin","default"));

View File

@ -24,7 +24,7 @@
#include <yatephone.h>
#include <gtk/gtk.h>
#include "../contrib/gtk2/gtk2client.h"
#include "gtk2/gtk2client.h"
using namespace TelEngine;

BIN
clients/null_team-16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 B

BIN
clients/null_team-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 B

View File

@ -4,7 +4,7 @@
if [ -x yate-gtk2 -a -x ../run ]; then
# Need to put the path to Mozilla libraries here
export LD_LIBRARY_PATH=/usr/lib/mozilla-1.6
export LD_LIBRARY_PATH=/usr/lib64/firefox-2.0.0.3
cd ..; exec ./run --executable clients/yate-gtk2 "$@"
else
echo "Could not find client executable or run script" >&2

View File

@ -1,5 +1,5 @@
Makefile
YateLocal.mak
YateLocal*
core*
*.conf
*.orig

View File

@ -17,7 +17,7 @@ all:
.PHONY: install
install:
@mkdir -p "$(DESTDIR)$(confdir)/" && \
lst="`ls -1 @srcdir@/*.conf @srcdir@/*.sample @srcdir@/*.default @srcdir@/*.sql | sed 's/\.sample//g; s/\.default//g; s/[^ ]*\*\.[^ ]*//g' | sort | uniq`" ; \
lst="`ls -1 @srcdir@/*.conf @srcdir@/*.sample @srcdir@/*.default | sed 's/\.sample//g; s/\.default//g; s/[^ ]*\*\.[^ ]*//g' | sort | uniq`" ; \
for s in $$lst; do \
d="$(DESTDIR)$(confdir)/`echo $$s | sed 's,.*/,,'`" ; \
if [ -f "$$d" ]; then \
@ -35,5 +35,5 @@ install:
uninstall:
@-rmdir "$(DESTDIR)$(confdir)" || echo "Remove conf files by hand if you want so"
Makefile: @srcdir@/Makefile.in ../config.status
cd .. && ./config.status
Makefile: @srcdir@/Makefile.in @top_builddir@/config.status
cd @top_builddir@ && ./config.status

View File

@ -5,7 +5,7 @@
; scripts if no full path is specified
; Note that a trailing path separator should be added
; Uncomment the following line when running in the sources directory
;scripts_dir=scripts/
;scripts_dir=share/scripts/
; priority: int: Priority of the call.execute handler
;priority=100

View File

@ -1,8 +1,5 @@
; This minimal file is here just to set the default skin.
; You can replace it with a more complete version from yate.conf.sample
[client]
skin=tabbed
[localsym]
h323chan.yate=yes

View File

@ -1,5 +1,5 @@
# Process this file with autoconf to produce a configure script.
AC_INIT(Yate, 1.3.0)
AC_INIT(Yate, 2.0.0)
AC_CONFIG_SRCDIR([README])
AC_PREREQ(2.52)
@ -285,13 +285,13 @@ HAVE_MYSQL=yes
fi
fi
AC_MSG_RESULT([$HAVE_MYSQL $MYSQL_VER])
if [[ "x$HAVE_MYSQL" = "xyes" ]]; then
if test "$HAVE_MYSQL" = "yes"; then
save_CPPFLAGS=$CPPFLAGS
CPPFLAGS="$CPPFLAGS $MYSQL_INC"
AC_CHECK_DECLS([MYSQL_OPT_RECONNECT],[MYSQL_INC="$MYSQL_INC -DMYSQL_OPT_RECONNECT=MYSQL_OPT_RECONNECT"],,[#include<mysql.h>])
AC_CHECK_DECLS([MYSQL_OPT_READ_TIMEOUT],[MYSQL_INC="$MYSQL_INC -DMYSQL_OPT_READ_TIMEOUT=MYSQL_OPT_READ_TIMEOUT"],,[#include<mysql.h>])
AC_CHECK_DECLS([MYSQL_OPT_WRITE_TIMEOUT],[MYSQL_INC="$MYSQL_INC -DMYSQL_OPT_WRITE_TIMEOUT=MYSQL_OPT_WRITE_TIMEOUT"],,[#include<mysql.h>])
CPPFLAGS="$save_CPPFLAGS"
CPPFLAGS=$save_CPPFLAGS
fi
fi
AC_SUBST(HAVE_MYSQL)
@ -759,29 +759,32 @@ AC_SUBST(KDOC_BIN)
m4_sinclude(./YateLocal.ac)
AC_CONFIG_FILES([yate.spec
AC_CONFIG_FILES([packing/rpm/yate.spec
yate.pc
yateversn.h
yateiss.inc
Makefile
engine/Makefile
modules/Makefile
modules/skin/Makefile
modules/help/Makefile
modules/test/Makefile
clients/Makefile
scripts/Makefile
conf.d/Makefile
contrib/ilbc/Makefile
contrib/ysip/Makefile
contrib/yrtp/Makefile
contrib/yiax/Makefile
contrib/yxml/Makefile
contrib/yjingle/Makefile
contrib/ypbx/Makefile
contrib/gtk2/Makefile
test/Makefile])
clients/gtk2/Makefile
libs/ilbc/Makefile
libs/ysip/Makefile
libs/yrtp/Makefile
libs/yiax/Makefile
libs/yxml/Makefile
libs/yjingle/Makefile
libs/ymgcp/Makefile
libs/ysig/Makefile
libs/ypbx/Makefile
share/Makefile
share/scripts/Makefile
share/skins/Makefile
share/help/Makefile
conf.d/Makefile])
AC_CONFIG_FILES([yate-config],[chmod +x yate-config])
AC_CONFIG_FILES([run],[chmod +x run])
CONFIGURE_FILES=`echo "$ac_config_files config.status config.log" | sed 's/yate\.spec *//'`
CONFIGURE_FILES=`echo "$ac_config_files config.status config.log" | sed 's,packing/yate\.spec *,,'`
AC_SUBST(CONFIGURE_FILES)
AC_OUTPUT

View File

@ -72,7 +72,7 @@ EXAMPLE_PATH =
EXAMPLE_PATTERNS =
EXAMPLE_RECURSIVE = NO
IMAGE_PATH =
INPUT_FILTER = ./kdoc-filter.sh
INPUT_FILTER = ./docs/doc-filter.sh
FILTER_SOURCE_FILES = NO
#---------------------------------------------------------------------------
# configuration options related to source browsing

View File

@ -16,7 +16,7 @@
.\" Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
.\"
.\"
.TH YATE-CONFIG 8 "March 2004" "YATE" "Telephony Engine"
.TH YATE-CONFIG 8 "September 2007" "YATE" "Telephony Engine"
.SH NAME
\fByate-config\fP \- retrieve metainformation about the YATE telephony engine
.SH SYNOPSIS
@ -44,6 +44,15 @@ Returns the configuration files directory
.TP
.B \-\-modules
Returns the modules directory
.TP
.B \-\-share
Returns the base shared directory
.TP
.B \-\-scripts
Returns the scripts directory
.TP
.B \-\-skins
Returns the base skins directory
.SS Compiler flags
.TP
.B \-\-cflags
@ -67,7 +76,7 @@ All linker options \- the above two concatenated
.SH AUTHORS
Paul Chitescu <paulc@voip.null.ro>
.br
Diana Cionoiu <diana@diana.null.ro>
Diana Cionoiu <diana@voip.null.ro>
.SH SEE ALSO
.BR yate (8),
.BR pkg-config (1)

View File

@ -16,7 +16,7 @@
.\" Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
.\"
.\"
.TH YATE 8 "September 2005" "YATE" "Telephony Engine"
.TH YATE 8 "September 2007" "YATE" "Telephony Engine"
.SH NAME
\fByate\fP \- launch the YATE telephony engine
.SH SYNOPSIS
@ -65,6 +65,9 @@ Path to conf files directory, overrides compiled-in value
.B \-m \fIpathname\fR
Path to modules directory, overrides compiled-in value
.TP
.B \-e \fIpathname\fR
Path to shared directory, overrides compiled-in value
.TP
.B \-x \fIrelpath\fR
Relative path to extra modules directory (can be repeated)
.TP
@ -145,6 +148,6 @@ when the thread terminates.
.SH AUTHORS
Paul Chitescu <paulc@voip.null.ro>
.br
Diana Cionoiu <diana@diana.null.ro>
Diana Cionoiu <diana@voip.null.ro>
.SH SEE ALSO
.BR yate-config (8)

View File

@ -1,5 +1,5 @@
Makefile
YateLocal.mak
YateLocal*
core*
*.o
*.a

View File

@ -26,7 +26,7 @@
#include <stdlib.h>
extern "C" {
#include "tables/all.h"
#include "all.h"
}
using namespace TelEngine;

View File

@ -133,6 +133,7 @@ static void sighandler(int signal)
}
}
String Engine::s_shrpath(SHR_PATH);
String Engine::s_cfgpath(CFG_PATH);
String Engine::s_cfgsuffix(CFG_SUFFIX);
String Engine::s_modpath(MOD_PATH);
@ -605,6 +606,7 @@ int Engine::run()
s_runid = Time::secNow();
DDebug(DebugAll,"Engine::run()");
install(new EngineStatusHandler);
extraPath(clientMode() ? "client" : "server");
loadPlugins();
Debug(DebugAll,"Loaded %d plugins",plugins.count());
if (s_super_handle >= 0) {
@ -632,7 +634,7 @@ int Engine::run()
::signal(SIGUSR1,sighandler);
::signal(SIGUSR2,sighandler);
#endif
Output("Yate engine is initialized and starting up");
Output("Yate%s engine is initialized and starting up",clientMode() ? " client" : "");
while (s_haltcode == -1) {
if (s_cmds) {
Output("Executing initial commands");
@ -999,6 +1001,7 @@ static void usage(bool client, FILE* f)
" -p filename Write PID to file\n"
" -l filename Log to file\n"
" -n configname Use specified configuration name (%s)\n"
" -e pathname Path to shared files directory (" SHR_PATH ")\n"
" -c pathname Path to conf files directory (" CFG_PATH ")\n"
" -m pathname Path to modules directory (" MOD_PATH ")\n"
" -x relpath Relative path to extra modules directory (can be repeated)\n"
@ -1164,6 +1167,14 @@ int Engine::main(int argc, const char** argv, const char** env, RunMode mode, bo
pc = 0;
cfgfile=argv[++i];
break;
case 'e':
if (i+1 >= argc) {
noarg(client,argv[i]);
return ENOENT;
}
pc = 0;
s_shrpath=argv[++i];
break;
case 'c':
if (i+1 >= argc) {
noarg(client,argv[i]);

View File

@ -16,7 +16,7 @@ CPPFLAGS := -O2 @MODULE_CPPFLAGS@ @INLINE_FLAGS@
LDFLAGS:=
LDCONFIG:=true
MKDEPS := ../config.status
MKDEPS := @top_builddir@/config.status
YLIB:= libyate.so.@PACKAGE_VERSION@
CINC := @top_srcdir@/yateclass.h @top_srcdir@/yatemime.h
EINC := $(CINC) @top_srcdir@/yatengine.h
@ -43,7 +43,7 @@ ifneq (@HAVE_SCTP_NETINET@,no)
SCTPOPTS := $(SCTPOPTS) -DHAVE_SCTP_NETINET
endif
ifeq (@INTERNAL_REGEX@,yes)
REGEX_INC:= -I@top_srcdir@/contrib/regex
REGEX_INC:= -I@top_srcdir@/engine/regex
LIBOBJS := $(LIBOBJS) regex.o
else
REGEX_INC:=
@ -55,11 +55,7 @@ exec_prefix = @exec_prefix@
bindir = @bindir@
libdir = @libdir@
incdir = @includedir@/yate
mandir = @mandir@
docdir = $(prefix)/share/doc/yate-@PACKAGE_VERSION@
vardir = @localstatedir@/lib/yate
moddir = @libdir@/yate
confdir = @sysconfdir@/yate
# include optional local make rules
-include YateLocal.mak
@ -68,7 +64,7 @@ confdir = @sysconfdir@/yate
all: ../$(YLIB)
debug:
$(MAKE) all DEBUG=-g3
$(MAKE) all DEBUG=-g3
ddebug:
$(MAKE) all DEBUG='-g3 -DDEBUG'
@ -90,6 +86,9 @@ Engine.o: @srcdir@/Engine.cpp $(MKDEPS) $(EINC) ../yateversn.h ../yatepaths.h
Channel.o: @srcdir@/Channel.cpp $(MKDEPS) $(PINC)
$(COMPILE) -c $<
DataBlock.o: @srcdir@/DataBlock.cpp $(MKDEPS) $(EINC)
$(COMPILE) -I@top_srcdir@/engine/tables -c $<
DataFormat.o: @srcdir@/DataFormat.cpp $(MKDEPS) $(PINC)
$(COMPILE) -c $<
@ -108,14 +107,14 @@ Client.o: @srcdir@/Client.cpp $(MKDEPS) $(CLINC)
String.o: @srcdir@/String.cpp $(MKDEPS) $(CINC)
$(COMPILE) $(REGEX_INC) -c $<
regex.o: @top_srcdir@/contrib/regex/regex.c $(MKDEPS)
regex.o: @top_srcdir@/engine/regex/regex.c $(MKDEPS)
$(CCOMPILE) -DSTDC_HEADERS $(REGEX_INC) -c $<
%.o: @srcdir@/%.cpp $(MKDEPS) $(EINC)
$(COMPILE) -c $<
Makefile: @srcdir@/Makefile.in $(MKDEPS)
cd .. && ./config.status
cd @top_builddir@ && ./config.status
../$(YLIB): $(LIBOBJS) $(LIBS)
$(LINK) -shared -o $@ -Wl,--soname=$(YLIB) $(LIBTHR) $^ $(LIBAUX)

View File

@ -20,6 +20,3 @@ strip: all
install:
uninstall:
Makefile: ./Makefile.in ../config.status
cd .. && ./config.status

View File

@ -22,7 +22,7 @@ for i in ?2?; do
./gen b "$i" <"$i" >"$i.h"
;;
esac
echo "#include \"tables/$i.h\""
echo "#include \"$i.h\""
done >all.h
rm *.raw ?2? gen

View File

@ -1,5 +1,5 @@
Makefile
YateLocal.mak
YateLocal*
core*
*.o
*.a

View File

@ -1,5 +1,5 @@
Makefile
YateLocal.mak
YateLocal*
core*
*.o
*.a

8
libs/ymgcp/.cvsignore Normal file
View File

@ -0,0 +1,8 @@
Makefile
YateLocal*
core*
*.o
*.a
*.orig
*~
.*.swp

56
libs/ymgcp/Makefile.in Normal file
View File

@ -0,0 +1,56 @@
# Makefile
# This file holds the make rules for the libyatemgcp
DEBUG :=
CXX := @CXX@ -Wall
AR := ar
DEFS :=
INCLUDES := -I@top_srcdir@ -I../.. -I@srcdir@
CFLAGS := -O2 @MODULE_CPPFLAGS@ @INLINE_FLAGS@
LDFLAGS:= -L../.. -lyate
INCFILES := @top_srcdir@/yateclass.h @srcdir@/yatemgcp.h
PROGS=
LIBS = libyatemgcp.a
OBJS = engine.o transaction.o message.o endpoint.o
LOCALFLAGS =
LOCALLIBS =
COMPILE = $(CXX) $(DEFS) $(DEBUG) $(INCLUDES) $(CFLAGS)
LINK = $(CC) $(LDFLAGS)
prefix = @prefix@
exec_prefix = @exec_prefix@
# include optional local make rules
-include YateLocal.mak
.PHONY: all debug ddebug xdebug
all: $(LIBS) $(PROGS)
debug:
$(MAKE) all DEBUG=-g3 MODSTRIP=
ddebug:
$(MAKE) all DEBUG='-g3 -DDEBUG' MODSTRIP=
xdebug:
$(MAKE) all DEBUG='-g3 -DXDEBUG' MODSTRIP=
.PHONY: strip
strip: all
strip --strip-debug --discard-locals $(PROGS)
.PHONY: clean
clean:
@-$(RM) $(PROGS) $(LIBS) $(OBJS) core 2>/dev/null
%.o: @srcdir@/%.cpp $(INCFILES)
$(COMPILE) -c $<
Makefile: @srcdir@/Makefile.in ../../config.status
cd ../.. && ./config.status
libyatemgcp.a: $(OBJS)
$(AR) rcs $@ $^

101
libs/ymgcp/endpoint.cpp Normal file
View File

@ -0,0 +1,101 @@
/**
* endpoint.cpp
* Yet Another MGCP Stack
* This file is part of the YATE Project http://YATE.null.ro
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2004-2006 Null Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <yatemgcp.h>
using namespace TelEngine;
/**
* MGCPEndpoint
*/
// Construct the id. Append itself to the engine's list
MGCPEndpoint::MGCPEndpoint(MGCPEngine* engine, const char* user,
const char* host, int port)
: MGCPEndpointId(user,host,port),
m_engine(engine)
{
if (!m_engine) {
Debug(DebugNote,"Can't construct endpoint without engine [%p]",this);
return;
}
m_engine->attach(this);
}
// Remove itself from engine's list
MGCPEndpoint::~MGCPEndpoint()
{
if (m_engine)
m_engine->detach(this);
}
// Append info about a remote endpoint controlled by or controlling this endpoint.
// If the engine owning this endpoint is an MGCP gateway, only 1 remote peer (Call Agent) is allowed
MGCPEpInfo* MGCPEndpoint::append(const char* endpoint, const char* host, int port)
{
if (!m_engine || (m_engine->gateway() && m_remote.count() >= 1))
return 0;
if (!endpoint)
endpoint = user();
if (!port)
port = m_engine->defaultPort(!m_engine->gateway());
MGCPEpInfo* ep = new MGCPEpInfo(endpoint,host,port);
if (!ep->valid() || find(ep->id()))
TelEngine::destruct(ep);
else
m_remote.append(ep);
return ep;
}
// Find the info object associated with a remote peer
MGCPEpInfo* MGCPEndpoint::find(const char* epId)
{
Lock lock(m_mutex);
ObjList* obj = m_remote.find(epId);
return obj ? static_cast<MGCPEpInfo*>(obj->get()) : 0;
}
// Find the info object associated with an unique remote peer
MGCPEpInfo* MGCPEndpoint::peer()
{
return (m_remote.count() == 1) ? static_cast<MGCPEpInfo*>(m_remote.get()) : 0;
}
/**
* MGCPEndpointId
*/
// Set this endpoint id. Convert it to lower case
void MGCPEndpointId::set(const char* endpoint, const char* host, int port)
{
m_id = "";
m_endpoint = endpoint;
m_endpoint.toLower();
m_host = host;
m_host.toLower();
m_port = port;
m_id << m_endpoint << "@" << m_host;
if (m_port)
m_id << ":" << m_port;
}
/* vi: set ts=8 sw=4 sts=4 noet: */

830
libs/ymgcp/engine.cpp Normal file
View File

@ -0,0 +1,830 @@
/**
* engine.cpp
* Yet Another MGCP Stack
* This file is part of the YATE Project http://YATE.null.ro
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2004-2006 Null Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <yatemgcp.h>
#include <string.h>
namespace TelEngine {
// Engine process, receive or check timeouts
class MGCPPrivateThread : public Thread, public GenObject
{
public:
enum Action {
Process = 1,
Receive = 2,
};
// Create a thread to process or receive data for the engine
MGCPPrivateThread(MGCPEngine* engine, bool process, Thread::Priority priority);
virtual ~MGCPPrivateThread();
virtual void run();
private:
MGCPEngine* m_engine;
Action m_action;
};
};
using namespace TelEngine;
#define MAX_TRANS_ID 999999999 // Maximum length for transaction identifier
// Some default values. Time values are given in miliseconds
#define RECV_BUF_LEN 1500 // Receive buffer length
#define TR_RETRANS_INTERVAL 250
#define TR_RETRANS_INTERVAL_MIN 100
#define TR_RETRANS_COUNT 3
#define TR_RETRANS_COUNT_MIN 1
#define TR_EXTRA_TIME 30000
#define TR_EXTRA_TIME_MIN 10000
/**
* MGCPPrivateThread
*/
MGCPPrivateThread::MGCPPrivateThread(MGCPEngine* engine, bool process,
Thread::Priority priority)
: Thread(process?"MGCP private process":"MGCP private receive",priority),
m_engine(engine),
m_action(process?Process:Receive)
{
XDebug(m_engine,DebugInfo,"MGCPPrivateThread::MGCPPrivateThread() [%p]",this);
if (m_engine)
m_engine->appendThread(this);
}
MGCPPrivateThread::~MGCPPrivateThread()
{
XDebug(m_engine,DebugInfo,"MGCPPrivateThread::~MGCPPrivateThread() [%p]",this);
if (m_engine)
m_engine->removeThread(this);
}
void MGCPPrivateThread::run()
{
DDebug(m_engine,DebugInfo,"%s started [%p]",currentName(),this);
if (!m_engine)
return;
switch (m_action) {
case Process:
m_engine->runProcess();
break;
case Receive:
m_engine->runReceive();
break;
}
}
/**
* MGCPEngine
*/
MGCPEngine::MGCPEngine(bool gateway, const char* name, const NamedList* params)
: Mutex(true),
m_gateway(gateway),
m_initialized(false),
m_nextId(1),
m_address(AF_INET),
m_maxRecvPacket(RECV_BUF_LEN),
m_recvBuf(0),
m_allowUnkCmd(false),
m_retransInterval(TR_RETRANS_INTERVAL * 1000),
m_retransCount(TR_RETRANS_COUNT),
m_extraTime(TR_EXTRA_TIME * 1000),
m_parseParamToLower(true),
m_provisional(true)
{
debugName((name && *name) ? name : (gateway ? "mgcp_gw" : "mgcp_ca"));
DDebug(this,DebugAll,"MGCPEngine::MGCPEngine(). Gateway: %s [%p]",
String::boolText(gateway),this);
// Add known commands
for (int i = 0; mgcp_commands[i].token; i++)
m_knownCommands.append(new String(mgcp_commands[i].token));
// Init
if (params)
initialize(*params);
}
MGCPEngine::~MGCPEngine()
{
cleanup(false);
if (m_recvBuf)
delete[] m_recvBuf;
DDebug(this,DebugAll,"MGCPEngine::~MGCPEngine()");
}
// Initialize this engine
void MGCPEngine::initialize(const NamedList& params)
{
int level = params.getIntValue("debuglevel");
if (level)
debugLevel(level);
m_allowUnkCmd = params.getBoolValue("allow_unknown_cmd",false);
int val = params.getIntValue("retrans_interval",TR_RETRANS_INTERVAL);
m_retransInterval = 1000 * (val < TR_RETRANS_INTERVAL_MIN ? TR_RETRANS_INTERVAL_MIN : val);
val = params.getIntValue("retrans_count",TR_RETRANS_COUNT);
m_retransCount = (val < TR_RETRANS_COUNT_MIN ? TR_RETRANS_COUNT_MIN : val);
val = params.getIntValue("extra_time_to_live",TR_EXTRA_TIME);
m_extraTime = 1000 * (val < TR_EXTRA_TIME_MIN ? TR_EXTRA_TIME_MIN : val);
if (!m_initialized) {
val = params.getIntValue("max_recv_packet",RECV_BUF_LEN);
m_maxRecvPacket = val < RECV_BUF_LEN ? RECV_BUF_LEN : val;
}
m_parseParamToLower = params.getBoolValue("lower_case_params",true);
m_provisional = params.getBoolValue("send_provisional",true);
// Bind socket if not valid
if (!m_socket.valid()) {
m_address.host(params.getValue("localip"));
int port = params.getIntValue("port",-1);
m_address.port(port < 0 ? defaultPort(gateway()) : port);
m_socket.create(AF_INET,SOCK_DGRAM);
int reqlen = params.getIntValue("buffer");
if (reqlen > 0) {
#ifdef SO_RCVBUF
int buflen = reqlen;
if ((unsigned int)buflen < maxRecvPacket())
buflen = maxRecvPacket();
if (buflen < 4096)
buflen = 4096;
if (m_socket.setOption(SOL_SOCKET,SO_RCVBUF,&buflen,sizeof(buflen))) {
buflen = 0;
socklen_t sz = sizeof(buflen);
if (m_socket.getOption(SOL_SOCKET,SO_RCVBUF,&buflen,&sz))
Debug(this,DebugAll,"UDP buffer size is %d (requested %d)",buflen,reqlen);
else
Debug(this,DebugWarn,"Could not get UDP buffer size (requested %d)",reqlen);
}
else
Debug(this,DebugWarn,"Could not set UDP buffer size %d (%d: %s)",
buflen,m_socket.error(),::strerror(m_socket.error()));
#else
Debug(this,DebugMild,"Can't set socket receive buffer: unsupported feature");
#endif
}
if (!m_socket.bind(m_address)) {
Debug(this,DebugWarn,"Failed to bind socket to %s:%d. Error: %d: %s",
m_address.host().safe(),m_address.port(),
m_socket.error(),::strerror(m_socket.error()));
m_socket.terminate();
}
else
m_socket.getSockName(m_address);
m_socket.setBlocking(false);
}
// Create private threads
if (!m_initialized) {
Thread::Priority prio = Thread::priority(params.getValue("thread_priority"));
int c = params.getIntValue("private_receive_threads",1);
for (int i = 0; i < c; i++)
(new MGCPPrivateThread(this,false,prio))->startup();
c = params.getIntValue("private_process_threads",1);
for (int i = 0; i < c; i++)
(new MGCPPrivateThread(this,true,prio))->startup();
}
if (debugAt(DebugAll)) {
String tmp;
tmp << "\r\ntype: " << (gateway() ? "Gateway" : "Call Agent");
tmp << "\r\nbind address: " << m_address.host() << ":" << m_address.port();
tmp << "\r\nallow_unknown_cmd: " << String::boolText(m_allowUnkCmd);
tmp << "\r\nretrans_interval: " << m_retransInterval;
tmp << "\r\nretrans_count: " << m_retransCount;
tmp << "\r\nlower_case_params: " << m_parseParamToLower;
tmp << "\r\nmax_recv_packet: " << maxRecvPacket();
tmp << "\r\nsend_provisional: " << provisional();
Debug(this,DebugInfo,"%s:%s",m_initialized?"Reloaded":"Initialized",tmp.c_str());
}
m_initialized = true;
}
// Add a command to the list of known commands
void MGCPEngine::addCommand(const char* cmd)
{
String* tmp = new String(cmd);
Lock lock(this);
tmp->toUpper();
if (tmp->length() == 4 && !knownCommand(*tmp)) {
Debug(this,DebugInfo,"Adding extra command %s",tmp->c_str());
m_knownCommands.append(tmp);
}
else
TelEngine::destruct(tmp);
}
// Append an endpoint to this engine if not already done
void MGCPEngine::attach(MGCPEndpoint* ep)
{
if (!ep)
return;
Lock lock(this);
if (!m_endpoints.find(ep)) {
m_endpoints.append(ep);
Debug(this,DebugInfo,"Attached endpoint '%s'",ep->id().c_str());
}
}
// Remove an endpoint from this engine and, optionally, remove all its transactions
void MGCPEngine::detach(MGCPEndpoint* ep, bool del, bool delTrans)
{
if (!ep)
return;
if (del)
delTrans = true;
Debug(this,DebugInfo,"Detaching endpoint '%s'",ep->id().c_str());
Lock lock(this);
// Remove transactions
if (delTrans) {
ListIterator iter(m_transactions);
for (GenObject* o; 0 != (o = iter.get());) {
MGCPTransaction* tr = static_cast<MGCPTransaction*>(o);
if (ep->id() == tr->ep())
m_transactions.remove(tr,true);
}
}
m_endpoints.remove(ep,del);
}
// Find an endpoint by its pointer
MGCPEndpoint* MGCPEngine::findEp(MGCPEndpoint* ep)
{
Lock lock(this);
ObjList* o = m_endpoints.find(ep);
return o ? static_cast<MGCPEndpoint*>(o->get()) : 0;
}
// Find an endpoint by its id
MGCPEndpoint* MGCPEngine::findEp(const char* epId)
{
Lock lock(this);
ObjList* o = m_endpoints.find(epId);
return o ? static_cast<MGCPEndpoint*>(o->get()) : 0;
}
// find a transaction
MGCPTransaction* MGCPEngine::findTrans(unsigned int id, bool outgoing)
{
Lock lock(this);
for (ObjList* o = m_transactions.skipNull(); o; o = o->skipNext()) {
MGCPTransaction* tr = static_cast<MGCPTransaction*>(o->get());
if (outgoing == tr->outgoing() && id == tr->id())
return tr;
}
return 0;
}
// Generate a new id for an outgoing transaction
unsigned int MGCPEngine::getNextId()
{
Lock lock(this);
if (m_nextId < MAX_TRANS_ID)
return m_nextId++;
m_nextId = 1;
return MAX_TRANS_ID;
}
// Send a command message. Create a transaction for it.
// Fail if the message is not a valid one or isn't a valid command
MGCPTransaction* MGCPEngine::sendCommand(MGCPMessage* cmd, const SocketAddr& addr)
{
if (!cmd)
return 0;
if (!(cmd->valid() && cmd->isCommand())) {
Debug(this,DebugNote,"Can't initiate outgoing transaction for (%p) cmd=%s",
cmd,cmd->name().c_str());
TelEngine::destruct(cmd);
return 0;
}
Lock lock(this);
return new MGCPTransaction(this,cmd,true,addr);
}
// Read data from the socket. Parse and process the received message
bool MGCPEngine::receive(unsigned char* buffer, SocketAddr& addr)
{
if (!m_socket.valid())
return false;
int len = maxRecvPacket();
int rd = m_socket.recvFrom(buffer,len,addr);
if (rd == Socket::socketError()) {
if (!m_socket.canRetry())
Debug(this,DebugWarn,"Socket read error: %d: %s",
m_socket.error(),::strerror(m_socket.error()));
return false;
}
if (rd > 0)
len = rd;
else
return false;
ObjList msgs;
if (!MGCPMessage::parse(this,msgs,buffer,len)) {
ObjList* o = msgs.skipNull();
MGCPMessage* msg = static_cast<MGCPMessage*>(o?o->get():0);
if (msg && msg->valid() && !msg->isCommand()) {
String tmp;
msg->toString(tmp);
sendData(tmp,addr);
}
return false;
}
if (!msgs.skipNull())
return false;
Lock lock(this);
if (debugAt(DebugInfo)) {
String tmp((const char*)buffer,len);
Debug(this,DebugInfo,
"Received %u message(s) from %s:%d\r\n-----\r\n%s\r\n-----",
msgs.count(),addr.host().c_str(),addr.port(),tmp.c_str());
}
// Process received message(s)
while (true) {
MGCPMessage* msg = static_cast<MGCPMessage*>(msgs.remove(false));
if (!msg)
break;
// Command messages may contain ACK'd incoming transaction's responses
// See RFC 3435: 3.2.2.19 and 3.5.1
if (msg->isCommand()) {
String s = msg->params.getValue("k");
if (!(s || m_parseParamToLower))
s = msg->params.getValue("K");
if (s) {
unsigned int len = 0;
unsigned int* trList = decodeAck(s,len);
// Build an ACK message for each of ACK'd transaction response
if (trList) {
for (unsigned int i = 0; i < len; i++) {
MGCPTransaction* tr = findTrans(trList[i],false);
if (tr)
tr->processMessage(new MGCPMessage(tr,0));
else
DDebug(this,DebugNote,
"Message %s carry ACK for unknown transaction %u",
msg->name().c_str(),trList[i]);
}
delete trList;
}
else {
DDebug(this,DebugNote,"Message %s has invalid k: '%s' parameter",
msg->name().c_str(),s.c_str());
MGCPTransaction* tr = findTrans(msg->transactionId(),false);
if (!tr)
tr = new MGCPTransaction(this,msg,false,addr);
tr->setResponse(400,"Bad Transaction Ack");
continue;
}
}
}
// Outgoing transaction id namespace is different then the incoming one
// Check message:
// Command or response ACK: Destination is an incoming transaction
// Response: Destination is an outgoing transaction
bool outgoing = !(msg->isCommand() || msg->isAck());
MGCPTransaction* tr = findTrans(msg->transactionId(),outgoing);
if (tr) {
tr->processMessage(msg);
continue;
}
// No transaction
if (msg->isCommand()) {
new MGCPTransaction(this,msg,false,addr);
continue;
}
DDebug(this,DebugNote,"Received response %d for unknown transaction %u",
msg->code(),msg->transactionId());
TelEngine::destruct(msg);
}
return true;
}
// Try to get an event from a transaction.
// If the event contains an unknown command and this engine is not allowed
// to process such commands, calls the @ref returnEvent() method, otherwise,
// calls the @ref processEvent() method
bool MGCPEngine::process(u_int64_t time)
{
MGCPEvent* event = getEvent(time);
if (!event)
return false;
if (!processEvent(event))
returnEvent(event);
return true;
}
// Repeatedly calls receive() until the calling thread terminates
void MGCPEngine::runReceive()
{
SocketAddr addr(AF_INET);
if (m_recvBuf)
delete[] m_recvBuf;
m_recvBuf = new unsigned char[maxRecvPacket()];
while (true)
if (!receive(m_recvBuf,addr))
Thread::msleep(2,true);
else
Thread::check(true);
}
// Repeatedly calls process() until the calling thread terminates
void MGCPEngine::runProcess()
{
while (true)
if (!process())
Thread::msleep(2,true);
else
Thread::check(true);
}
// Try to get an event from a transaction
MGCPEvent* MGCPEngine::getEvent(u_int64_t time)
{
lock();
ListIterator iter(m_transactions);
while (true) {
if (Thread::check(false))
break;
MGCPTransaction* tr = static_cast<MGCPTransaction*>(iter.get());
// End of iteration? NO: get a reference to the transaction
if (!tr)
break;
RefPointer<MGCPTransaction> sref = tr;
if (!sref)
continue;
// Get an event from the transaction
unlock();
MGCPEvent* event = sref->getEvent(time);
// Remove the transaction if destroying
if (event)
return event;
lock();
}
unlock();
return 0;
}
// Process an event generated by a transaction. Descendants must override this
// method if they want to process events without breaking them apart
bool MGCPEngine::processEvent(MGCPEvent* event)
{
DDebug(this,DebugAll,"MGCPEngine::processEvent(%p)",event);
if (!event)
return false;
MGCPTransaction* trans = event->transaction();
void* data = trans ? trans->userData() : 0;
if (processEvent(trans,event->message(),data)) {
// Get rid of the event, it was handled
delete event;
return true;
}
return false;
}
// Process an event generated by a transaction. Descendants must override this
// method if they want to process events
bool MGCPEngine::processEvent(MGCPTransaction* trans, MGCPMessage* msg, void* data)
{
Debug(this,DebugStub,"MGCPEngine::processEvent(%p,%p,%p)",trans,msg,data);
return false;
}
// Returns an unprocessed event to this engine to be deleted.
// Incoming transactions will be responded. Unknown commands will receive a
// 504 Unknown Command response, the others will receive a 507 Unsupported Functionality one
void MGCPEngine::returnEvent(MGCPEvent* event)
{
if (!event)
return;
DDebug(this,DebugInfo,"Event (%p) returned to the engine",event);
MGCPTransaction* tr = event->transaction();
const MGCPMessage* msg = event->message();
if (tr && !tr->outgoing() && msg && msg->isCommand())
tr->setResponse(knownCommand(msg->name()) ? 507 : 504);
delete event;
}
// Terminate all transactions. Cancel all private threads if any and
// wait for them to terminate
void MGCPEngine::cleanup(bool gracefully, const char* text)
{
DDebug(this,DebugAll,"Cleanup (gracefully=%s text=%s)",
String::boolText(gracefully),text);
// Terminate transactions
lock();
if (gracefully)
for (ObjList* o = m_transactions.skipNull(); o; o = o->skipNext()) {
MGCPTransaction* tr = static_cast<MGCPTransaction*>(o->get());
if (!tr->outgoing())
tr->setResponse(400,text);
}
m_transactions.clear();
unlock();
// Check if we have any private threads to wait
if (!m_threads.skipNull())
return;
// Terminate private threads
XDebug(this,DebugAll,"Terminating %u private threads",m_threads.count());
lock();
ListIterator iter(m_threads);
for (GenObject* o = 0; 0 != (o = iter.get());)
static_cast<MGCPPrivateThread*>(o)->cancel(!gracefully);
unlock();
XDebug(this,DebugAll,"Waiting for private threads to terminate");
while (m_threads.skipNull())
Thread::yield();
XDebug(this,DebugAll,"Private threads terminated");
}
// Write data to socket
bool MGCPEngine::sendData(const String& msg, const SocketAddr& address)
{
if (debugAt(DebugInfo)) {
SocketAddr local;
m_socket.getSockName(local);
Debug(this,DebugInfo,
"Sending message from %s:%d to %s:%d\r\n-----\r\n%s\r\n-----",
local.host().c_str(),local.port(),address.host().c_str(),address.port(),
msg.c_str());
}
int len = m_socket.sendTo(msg.c_str(),msg.length(),address);
if (len != Socket::socketError())
return true;
if (!m_socket.canRetry())
Debug(this,DebugWarn,"Socket write error: %d: %s",
m_socket.error(),::strerror(m_socket.error()));
else
DDebug(this,DebugMild,"Socket temporary unavailable: %d: %s",
m_socket.error(),::strerror(m_socket.error()));
return false;
}
// Append a transaction to the list
void MGCPEngine::appendTrans(MGCPTransaction* trans)
{
if (!trans)
return;
Lock lock(this);
DDebug(this,DebugAll,"Added transaction (%p)",trans);
m_transactions.append(trans);
}
// Remove a transaction from the list
void MGCPEngine::removeTrans(MGCPTransaction* trans, bool del)
{
if (!trans)
return;
Lock lock(this);
DDebug(this,DebugAll,"Removed transaction (%p) del=%u",trans,del);
m_transactions.remove(trans,del);
}
// Append a private thread to the list
void MGCPEngine::appendThread(MGCPPrivateThread* thread)
{
if (!thread)
return;
Lock lock(this);
m_threads.append(thread);
XDebug(this,DebugAll,"Added private thread (%p)",thread);
}
// Remove private thread from the list without deleting it
void MGCPEngine::removeThread(MGCPPrivateThread* thread)
{
if (!thread)
return;
Lock lock(this);
m_threads.remove(thread,false);
XDebug(this,DebugAll,"Removed private thread (%p)",thread);
}
// Process ACK received with a message or response
// Build an ACK message for each of responded incoming transaction
unsigned int* MGCPEngine::decodeAck(const String& param, unsigned int& count)
{
ObjList* list = param.split(',',false);
if (!list->count()) {
TelEngine::destruct(list);
return 0;
}
unsigned int maxArray = 0;
unsigned int* array = 0;
bool ok = true;
int first, last;
for (ObjList* o = list->skipNull(); o; o = o->skipNext()) {
String* s = static_cast<String*>(o->get());
s->trimBlanks();
// Get the interval (may be a single value)
int sep = s->find('-');
if (sep == -1)
first = last = s->toInteger(-1);
else {
first = s->substr(0,sep).toInteger(-1);
last = s->substr(sep + 1).toInteger(-2);
}
if (first < 0 || last < 0 || last < first) {
ok = false;
break;
}
// Resize and copy array if not enough room
unsigned int len = (unsigned int)(last - first + 1);
if (count + len > maxArray) {
maxArray = count + len;
unsigned int* tmp = new unsigned int[maxArray];
if (array) {
::memcpy(tmp,array,sizeof(unsigned int) * count);
delete[] array;
}
array = tmp;
}
// Add to array code list
for (; first <= last; first++)
array[count++] = first;
}
TelEngine::destruct(list);
if (ok && count)
return array;
count = 0;
if (array)
delete[] array;
return 0;
}
/**
* MGCPEvent
*/
// Constructs an event from a transaction
MGCPEvent::MGCPEvent(MGCPTransaction* trans, MGCPMessage* msg)
: m_transaction(0),
m_message(0)
{
if (trans && trans->ref())
m_transaction = trans;
if (msg && msg->ref())
m_message = msg;
}
// Delete the message. Notify and deref the transaction
MGCPEvent::~MGCPEvent()
{
if (m_transaction) {
m_transaction->eventTerminated(this);
m_transaction->deref();
}
TelEngine::destruct(m_message);
}
/**
* The list of known commands defined in RFC 3435
*/
TokenDict MGCPEngine::mgcp_commands[] = {
{"EPCF", 1}, // CA --> GW EndpointConfiguration
{"CRCX", 2}, // CA --> GW CreateConnection
{"MDCX", 3}, // CA --> GW ModifyConnection
{"DLCX", 4}, // CA <--> GW DeleteConnection
{"RQNT", 5}, // CA --> GW NotificationRequest
{"AUEP", 6}, // CA --> GW AuditEndpoint
{"AUCX", 7}, // CA --> GW AuditConnection
{"RSIP", 8}, // GW --> CA RestartInProgress
{"NTFY", 9}, // GW --> CA Notify
{"MESG", 10}, // GW --> CA Message
{0,0}
};
/**
* The list of known responses defined in RFC 3435 2.4
*/
TokenDict MGCPEngine::mgcp_responses[] = {
{"ACK", 0}, // Response Acknowledgement
{"Trying", 100}, // The transaction is currently being executed
{"Queued", 101}, // The transaction has been queued for execution
{"OK", 200}, // The requested transaction was executed normally
{"OK", 250}, // Used only to respond to DeleteConnection
{"Unspecified", 400}, // The transaction could not be executed, due to some unspecified transient error
{"Already Off Hook", 401}, // The phone is already off hook
{"Already On Hook", 402}, // The phone is already on hook
{"No Resources Now", 403}, // The transaction could not be executed, because the endpoint does
// not have sufficient resources at this time
{"Insufficient Bandwidth", 404},
{"Endpoint Is Restarting", 405}, // The transaction could not be executed, because the endpoint is restarting
{"Timeout", 406}, // The transaction did not complete in a reasonable period of time
{"Aborted", 407}, // The transaction was aborted by some external action,
// e.g., a ModifyConnection command aborted by a DeleteConnection command
{"Overload", 409}, // The transaction could not be executed because of internal overload
{"No Endpoint Available", 410}, // A valid "any of" wildcard was used, but there was no endpoint
// available to satisfy the request
{"Unknown Endpoint", 500},
{"Endpoint Not Ready", 501}, // The endpoint is not ready. Includes out-of-service
{"No Resources", 502}, // The endpoint doesn't have sufficient resources (permanent condition)
{"Wildcard Too Complicated", 503}, // "All of" wildcard too complicated
{"Unknown Command", 504}, // Unknown or unsupported command.
{"Unsupported RemoteConnectionDescriptor", 505}, // This SHOULD be used when one or more mandatory parameters
// or values in the RemoteConnectionDescriptor is not supported
{"Unable To Satisfy LocalConnectionOptions And RemoteConnectionDescriptor", 506}, // LocalConnectionOptions and
// RemoteConnectionDescriptor contain one or more mandatory parameters
// or values that conflict with each other
{"Unsupported Functionality", 507},
{"Unknown Or Unsupported Quarantine Handling", 508},
{"Bad RemoteConnectionDescriptor", 509}, // Syntax or semantic error in the RemoteConnectionDescriptor
{"Protocol Error", 510}, // Unspecified protocol error was detected
{"Unrecognized Extension", 511}, // Used for unsupported critical parameter extensions ("X+")
{"Can't Detect Event", 512}, // The gateway is not equipped to detect one of the requested events
{"Can't Generate Signal", 513}, // The gateway is not equipped to generate one of the requested signals
{"Can't Send Announcement", 514}, // The gateway cannot send the specified announcement.
{"No Connection", 515}, // The transaction refers to an incorrect connection-id
{"Bad Call-id", 516}, // Unknown or incorrect call-id (connection-id not associated with this call-id)
{"Unsupported Mode", 517}, // Unsupported or invalid mode
{"Unsupported Package", 518},
{"No Digit Map", 519}, // Endpoint does not have a digit map
{"Endpoint Is Restarting", 520},
{"Endpoint Redirected To Another Call Agent", 521}, // The associated redirection behavior is only well-defined
// when this response is issued for a RestartInProgress command
{"Unknown Event Or Signal", 522}, // The request referred to an event or signal that is not defined in
// the relevant package (which could be the default package)
{"Illegal Action", 523}, // Unknown action or illegal combination of actions
{"Inconsistency In LocalConnectionOptions", 524},
{"Unknown Extension In LocalConnectionOptions", 525}, // Used for unsupported mandatory vendor extensions ("x+")
{"Insufficient Bandwidth", 526},
{"Missing RemoteConnectionDescriptor", 527},
{"Incompatible Protocol Version", 528},
{"Internal Hardware Failure", 529},
{"CAS Signaling Protocol Error", 530},
{"Grouping Of Trunks Failure", 531}, // e.g., facility failure
{"Unsupported LocalConnectionOptions", 532}, // Unsupported value(s) in LocalConnectionOptions.
{"Response Too Large", 533},
{"Codec Negotiation Failure", 534},
{"Packetization Period Not Supported", 535},
{"Unsupported RestartMethod", 536},
{"Unsupported Digit Map Extension", 537},
{"Event/Signal Parameter Error", 538}, // e.g., missing, erroneous, unsupported, unknown, etc.
{"Unsupported Command Parameter", 539}, // Used when the parameter is neither a package or vendor extension parameter
{"Per Endpoint Connection Limit Exceeded", 540},
{"Unsupported LocalConnectionOptions", 541}, // Used when the LocalConnectionOptions is neither a package
// nor a vendor extension LocalConnectionOptions.
{0,0},
};
/**
* The list of known reason codes defined in RFC 3435 2.5
* Reason codes are used by the gateway when deleting a connection to
inform the Call Agent about the reason for deleting the connection.
They may also be used in a RestartInProgress command to inform the
Call Agent of the reason for the RestartInProgress
*/
TokenDict MGCPEngine::mgcp_reasons[] = {
{"Normal", 0}, // Endpoint state is normal (only in response to audit requests)
{"Endpoint Malfunctioning", 900},
{"Endpoint Taken Out-Of-Service", 901},
{"Loss Of Lower Layer Connectivity", 902},
{"QoS Resource Reservation Was Lost", 903},
{"Manual Intervention", 904},
{"Facility failure", 905},
{0,0},
};
/* vi: set ts=8 sw=4 sts=4 noet: */

493
libs/ymgcp/message.cpp Normal file
View File

@ -0,0 +1,493 @@
/**
* message.cpp
* Yet Another MGCP Stack
* This file is part of the YATE Project http://YATE.null.ro
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2004-2006 Null Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <yatemgcp.h>
#include <stdio.h>
using namespace TelEngine;
#ifdef XDEBUG
#define PARSER_DEBUG
#endif
// Ensure response code string representation is 3 digit long
inline void setCode(String& dest, unsigned int code)
{
char c[4];
sprintf(c,"%03u",code);
dest = c;
}
/**
* MGCPMessage
*/
// Construct an outgoing command message
MGCPMessage::MGCPMessage(MGCPEngine* engine, const char* name, const char* ep, const char* ver)
: params(""),
m_name(name),
m_valid(false),
m_code(-1),
m_transaction(0),
m_endpoint(ep),
m_version(ver)
{
if (!(engine && (engine->allowUnkCmd() || engine->knownCommand(name)))) {
Debug(engine,DebugNote,"MGCPMessage. Unknown cmd=%s [%p]",name,this);
return;
}
// Command names MUST be 4 character long
if (m_name.length() != 4) {
Debug(engine,DebugNote,
"MGCPMessage. Invalid command length cmd=%s len=%u [%p]",
m_name.c_str(),m_name.length(),this);
return;
}
m_transaction = engine->getNextId();
m_valid = true;
DDebug(engine,DebugAll,"MGCPMessage. cmd=%s trans=%u ep=%s [%p]",
name,m_transaction,ep,this);
}
// Construct a response message
MGCPMessage::MGCPMessage(MGCPTransaction* trans, unsigned int code, const char* comment)
: params(""),
m_valid(false),
m_code(code),
m_transaction(0),
m_comment(comment)
{
if (!trans) {
Debug(DebugNote,
"MGCPMessage. Can't create response without transaction [%p]",this);
return;
}
if (code > 999) {
Debug(trans->engine(),DebugNote,
"MGCPMessage. Invalid response code=%u [%p]",code,this);
return;
}
setCode(m_name,code);
m_transaction = trans->id();
if (!m_comment.length())
m_comment = lookup(code,MGCPEngine::mgcp_responses);
m_valid = true;
DDebug(trans->engine(),DebugAll,"MGCPMessage code=%d trans=%u comment=%s [%p]",
code,m_transaction,m_comment.c_str(),this);
}
// Constructor. Used by the parser to construct an incoming message
MGCPMessage::MGCPMessage(MGCPEngine* engine, const char* name, int code,
unsigned int transId, const char* epId, const char* ver)
: params(""),
m_valid(true),
m_code(code),
m_transaction(transId),
m_endpoint(epId),
m_version(ver)
{
if (code < 0)
m_name = name;
else {
setCode(m_name,code);
m_comment = name;
if (!m_comment.length())
m_comment = lookup(code,MGCPEngine::mgcp_responses);
}
DDebug(engine,DebugAll,
"Incoming MGCPMessage %s=%s trans=%u ep=%s ver=%s comment=%s [%p]",
isCommand()?"cmd":"rsp",this->name().c_str(),
transId,epId,ver,m_comment.safe(),this);
}
MGCPMessage::~MGCPMessage()
{
DDebug(DebugAll,"MGCPMessage::~MGCPMessage [%p]",this);
}
// Convert this message to a string representation
void MGCPMessage::toString(String& dest) const
{
// Construct first line
dest << name() << " " << transactionId();
if (isCommand())
dest << " " << endpointId() << " " << m_version;
else if (m_comment)
dest << " " << m_comment;
dest << "\r\n";
// Append message parameters
unsigned int n = params.count();
for (unsigned int i = 0; i < n; i++) {
NamedString* ns = params.getParam(i);
if (!ns)
continue;
dest << ns->name() << ": " << *ns << "\r\n";
}
// Append SDP(s)
for (ObjList* obj = sdp.skipNull(); obj; obj = obj->skipNext()) {
String s;
MimeSdpBody* tmp = static_cast<MimeSdpBody*>(obj->get());
for (ObjList* o = tmp->lines().skipNull(); o; o = o->skipNext()) {
NamedString* ns = static_cast<NamedString*>(o->get());
if (*ns)
s << ns->name() << "=" << *ns << "\r\n";
}
if (s)
dest << "\r\n" << s;
}
}
// Check if a character is an end-of-line one
static inline bool isEoln(char c)
{
return c == '\r' || c == '\n';
}
// Check if a character is blank: space or tab
static inline bool isBlank(char c)
{
return c == ' ' || c == '\t';
}
// Skip blank characters. The buffer is assumed to be valid
// Return false if the end of line was reached
static inline bool skipBlanks(const char*& buffer, unsigned int& len)
{
for (; len && isBlank(*buffer); buffer++, len--)
;
return len;
}
// Get a line from a buffer until the first valid end-of-line or end of buffer was reached,
// starting with current index in buffer
// Set the current index to the first character after the end-of-line or at the end of the buffer
// The buffer is assumed to be valid
// Return the line and length or 0 if an invalid end-of-line was found
// RFC 3435 3.1: end-of-line may be CR/LF or LF
static inline const char* getLine(const unsigned char* buffer, unsigned int len,
unsigned int& crt, unsigned int& count, bool skip = true)
{
count = 0;
const char* line = (const char*)buffer + crt;
while (crt < len && !isEoln(buffer[crt])) {
crt++;
count++;
}
if (skip)
skipBlanks(line,count);
// Check end of buffer or end-of-line
if (crt == len)
return line;
if (buffer[crt] == '\r' && (++crt) == len)
return 0;
return buffer[crt++] == '\n' ? line : 0;
}
// Parse a received buffer according to RFC 3435
// See Appendix A for the grammar
bool MGCPMessage::parse(MGCPEngine* engine, ObjList& dest,
const unsigned char* buffer, unsigned int len, const char* sdpType)
{
if (!buffer)
return false;
#ifdef PARSER_DEBUG
String t((const char*)buffer,len);
Debug(engine,DebugAll,"Parse received buffer\r\n%s",t.c_str());
#endif
int errorCode = 510; // Protocol error
unsigned int trans = 0;
String error;
unsigned int crt = 0;
while (crt < len && !error) {
unsigned int count = 0;
const char* line = 0;
// Skip empty lines before a message line and skip trailing blanks on the message line
while (crt < len) {
line = getLine(buffer,len,crt,count);
if (!line) {
error = "Invalid end-of-line";
break;
}
// Exit loop if the line is not empty
if (count)
break;
}
if (!count || error)
break;
// *** Decode the message line
MGCPMessage* msg = decodeMessage(line,count,trans,error,engine);
if (!msg)
break;
dest.append(msg);
#ifdef PARSER_DEBUG
String m((const char*)line,count);
Debug(engine,DebugAll,"Decoded message: %s",m.c_str());
#endif
// *** Decode parameters
if (decodeParams(buffer,len,crt,msg,error,engine))
continue;
if (error) {
if (msg->isCommand())
trans = msg->transactionId();
break;
}
if (crt >= len)
break;
// *** Decode SDP
// Decode SDPs until the end of buffer or
// a line containing a dot (message separator in a piggybacked block)
// SDPs are separated by an empty line
int empty = 0;
while (empty < 2) {
// Skip until an empty line, a line containing a dot or end of buffer
unsigned int start = crt;
unsigned int sdpLen = 0;
while (true) {
line = getLine(buffer,len,crt,count);
if (!line) {
error = "Invalid end-of-line";
break;
}
if (!count || (count == 1 && *line == '.')) {
if (!count)
empty++;
else
empty = 3;
break;
}
empty = 0;
sdpLen = crt - start;
}
if (error)
break;
if (sdpLen)
msg->sdp.append(new MimeSdpBody(sdpType,(const char*)buffer+start,sdpLen));
}
// Found 2 empty lines: skip until end of buffer or line containing '.' or non empty line
if (empty == 2) {
unsigned int start = crt;
while (true) {
line = getLine(buffer,len,crt,count);
if (!line) {
error = "Invalid end-of-line";
break;
}
if (!count) {
if (crt == len)
break;
continue;
}
// Fallback with current index if found non empty line which doesn't start with '.'
if (*line != '.')
crt = start;
break;
}
}
}
if (!error)
return true;
dest.clear();
if (trans && trans <= 999999999)
dest.append(new MGCPMessage(engine,0,errorCode,trans,0,0));
Debug(engine,DebugNote,"Parser error: %s",error.c_str());
return false;
}
// Decode the message line
// Command: verb transaction endpoint proto_name proto_version ...
// Response: code transaction comment ...
MGCPMessage* MGCPMessage::decodeMessage(const char* line, unsigned int len, unsigned int& trans,
String& error, MGCPEngine* engine)
{
String name, ver;
int code = -1;
unsigned int trID = 0;
MGCPEndpointId id;
#ifdef PARSER_DEBUG
String msgLine(line,len);
Debug(engine,DebugAll,"Parse message line (len=%u): %s",
msgLine.length(),msgLine.c_str());
#endif
for (unsigned int item = 1; true; item++) {
if (item == 6) {
#ifdef DEBUG
if (len) {
String rest(line,len);
Debug(engine,DebugAll,"Unparsed data on message line: '%s'",rest.c_str());
}
#endif
break;
}
// Get current item
if (!skipBlanks(line,len)) {
error = "Unexpected end of line";
return 0;
}
unsigned int itemLen = 0;
// Response: the 3rd item is the comment
if (item == 3 && code != -1)
itemLen = len;
else
for (; itemLen < len && !isBlank(line[itemLen]); itemLen++)
;
String tmp(line,itemLen);
len -= itemLen;
line += itemLen;
switch (item) {
// 1st item: verb (command or notification) or response code
// Verbs must be 4-character long. Responses must be numbers in the interval [0..999]
case 1:
if (tmp.length() == 3) {
code = tmp.toInteger(-1,10);
if (code < 0 || code > 999)
error << "Invalid response code " << tmp;
}
else if (tmp.length() == 4)
name = tmp.toUpper();
else
error << "Invalid first item '" << tmp << "' length " << tmp.length();
break;
// 2nd item: the transaction id
// Restriction: must be a number in the interval [1..999999999]
case 2:
trID = tmp.toInteger(-1,10);
if (!trID || trID > 999999999)
error << "Invalid transaction id '" << tmp << "'";
// Set trans for command messages so they can be responded on error
else if (code == -1)
trans = trID;
break;
// 3rd item: endpoint id (code is -1) or response comment (code != -1)
case 3:
if (code != -1)
name = tmp;
else {
id.set(tmp);
if (!id.valid())
error << "Invalid endpoint id '" << tmp << "'";
}
break;
// 4th item: protocol name if this is a verb (command)
case 4:
if (tmp.toUpper() == "MGCP")
ver = "MGCP 1.0";
else
error << "Invalid protocol '" << tmp << "'";
break;
// 5th item: protocol version if this is a verb (command)
case 5:
if (tmp != "1.0")
error << "Invalid protocol version '" << tmp << "'";
break;
}
if (error)
return 0;
// Stop parse the rest if this is a response
if (item == 3 && code != -1)
break;
}
// Check known commands
if (code == -1 &&
!(engine && (engine->allowUnkCmd() || engine->knownCommand(name)))) {
error << "Unknown cmd '" << name << "'";
return 0;
}
return new MGCPMessage(engine,name,code,trID,id.id(),ver);
}
// Decode message parameters. Return true if found a line containing a dot
// Decode parameters until the end of buffer, empty line or
// a line containing a dot (message separator in a piggybacked block)
// Parameters names and values are separated by a ':' character
// See RFC 3435 3.1
bool MGCPMessage::decodeParams(const unsigned char* buffer, unsigned int len,
unsigned int& crt, MGCPMessage* msg, String& error, MGCPEngine* engine)
{
while (crt < len) {
unsigned int count = 0;
const char* line = getLine(buffer,len,crt,count);
if (!line) {
error = "Invalid end-of-line";
break;
}
// Terminate if the line is empty or is a message separator
if (!count)
break;
if (count == 1 && *line == '.')
return true;
#ifdef PARSER_DEBUG
String paramLine(line,count);
Debug(engine,DebugAll,"Parse parameter line(len=%u): %s",count,paramLine.c_str());
#endif
// Decode parameter
int pos = -1;
for (int i = 0; i < (int)count; i++)
if (line[i] == ':')
pos = i;
if (pos == -1) {
error = "Parameter separator is missing";
break;
}
String param(line,pos);
param.trimBlanks();
if (!param) {
error = "Parameter name is empty";
break;
}
String value(line+pos+1,count-pos-1);
value.trimBlanks();
if (engine && engine->parseParamToLower())
msg->params.addParam(param.toLower(),value);
else
msg->params.addParam(param,value);
}
return false;
}
/* vi: set ts=8 sw=4 sts=4 noet: */

476
libs/ymgcp/transaction.cpp Normal file
View File

@ -0,0 +1,476 @@
/**
* transaction.cpp
* Yet Another MGCP Stack
* This file is part of the YATE Project http://YATE.null.ro
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2004-2006 Null Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <yatemgcp.h>
using namespace TelEngine;
// Construct a transaction from its first message
MGCPTransaction::MGCPTransaction(MGCPEngine* engine, MGCPMessage* msg, bool outgoing,
const SocketAddr& address)
: Mutex(true),
m_state(Invalid),
m_outgoing(outgoing),
m_address(address),
m_engine(engine),
m_cmd(msg),
m_provisional(0),
m_response(0),
m_ack(0),
m_lastEvent(0),
m_nextRetrans(0),
m_crtRetransInterval(0),
m_retransCount(0),
m_timeout(false),
m_private(0)
{
if (m_engine)
m_engine->appendTrans(this);
else {
Debug(engine,DebugNote,"Can't create MGCP transaction without engine");
return;
}
if (!(msg && msg->isCommand())) {
Debug(engine,DebugNote,"Can't create MGCP transaction from response");
return;
}
m_id = msg->transactionId();
m_endpoint = m_cmd->endpointId();
m_debug << "Transaction(" << (int)outgoing << "," << m_id << ")";
DDebug(m_engine,DebugAll,"%s. cmd=%s ep=%s addr=%s:%d [%p]",
m_debug.c_str(),m_cmd->name().c_str(),m_cmd->endpointId().c_str(),
m_address.host().c_str(),m_address.port(),this);
// Outgoing: send the message
if (outgoing) {
send(m_cmd);
initTimeout(Time(),false);
}
else
changeState(Initiated);
}
MGCPTransaction::~MGCPTransaction()
{
DDebug(m_engine,DebugAll,"%s. Destroyed [%p]",m_debug.c_str(),this);
}
// Get an event from this transaction. Check timeouts
MGCPEvent* MGCPTransaction::getEvent(u_int64_t time)
{
Lock lock(this);
if (m_lastEvent)
return 0;
switch (state()) {
case Initiated:
// Outgoing: Check if received any kind of response
// Ignore a provisional response if we received a final one
// Stop timer if received a final response
// Incoming: Process the received command
if (outgoing()) {
m_lastEvent = checkResponse(time);
if (!m_lastEvent && m_provisional) {
m_lastEvent = new MGCPEvent(this,m_provisional);
changeState(Trying);
}
}
else {
initTimeout(time,true);
m_lastEvent = new MGCPEvent(this,m_cmd);
if (m_engine && m_engine->provisional()) {
if (!m_provisional)
m_provisional = new MGCPMessage(this,100);
send(m_provisional);
}
else
changeState(Trying);
}
break;
case Trying:
// Outgoing: Check if received any response. If so, send a response ACK
// Incoming: Do nothing. Wait for the user to send a final response
if (outgoing())
m_lastEvent = checkResponse(time);
break;
case Responded:
// Outgoing: Change state to Ack. Should never be in this state
// Incoming: Check if we received a response ACK. Stop timer if received it
if (outgoing())
changeState(Ack);
else {
if (!m_ack)
break;
m_lastEvent = new MGCPEvent(this,m_ack);
m_nextRetrans = time + m_engine->extraTime();
changeState(Ack);
}
break;
case Ack:
// Just check timeouts
break;
case Invalid:
m_lastEvent = terminate();
break;
case Destroying:
break;
}
// Check timeouts
if (!m_lastEvent)
m_lastEvent = checkTimeout(time);
#ifdef DEBUG
if (m_lastEvent) {
const MGCPMessage* m = m_lastEvent->message();
String s = m ? m->name() : String("");
DDebug(m_engine,DebugAll,"%s. Generating event (%p) state=%u msg=%s [%p]",
m_debug.c_str(),m_lastEvent,state(),s.c_str(),this);
}
#endif
return m_lastEvent;
}
// Explicitely transmit a provisional code
bool MGCPTransaction::sendProvisional(int code, const char* comment)
{
if (outgoing() || m_provisional || (state() >= Responded) || (code < 100) || (code > 199))
return false;
m_provisional = new MGCPMessage(this,code,comment);
send(m_provisional);
return true;
}
// Transmits a final response message if this is an incoming transaction
bool MGCPTransaction::setResponse(MGCPMessage* msg)
{
Lock lock(this);
// Check state, message, transaction direction. Also check if we already have a response
bool msgValid = (msg && msg->code() >= 200 || !msg->isCommand());
bool stateValid = (state() >= Initiated || state() <= Ack);
if (m_response || outgoing() || !msgValid || !stateValid) {
TelEngine::destruct(msg);
return false;
}
DDebug(m_engine,DebugAll,"%s. Set response %s in state %u [%p]",
m_debug.c_str(),msg->name().c_str(),state(),this);
m_response = msg;
// Force response ACK request
m_response->params.setParam("K","");
// Send and init timeout
send(m_response);
initTimeout(Time(),false);
return true;
}
// Transmits a final response message if this is an incoming transaction
bool MGCPTransaction::setResponse(int code, const NamedList* params, MimeSdpBody* sdp1,
MimeSdpBody* sdp2)
{
if (m_response || outgoing()) {
TelEngine::destruct(sdp1);
TelEngine::destruct(sdp2);
return false;
}
const char* comment = 0;
if (params)
comment = params->c_str();
MGCPMessage* msg = new MGCPMessage(this,code,comment);
if (params) {
unsigned int n = params->length();
for (unsigned int i = 0; i < n; i++) {
const NamedString* p = params->getParam(i);
if (p)
msg->params.addParam(p->name(),*p);
}
}
if (sdp1) {
msg->sdp.append(sdp1);
if (sdp2)
msg->sdp.append(sdp2);
}
else
TelEngine::destruct(sdp2);
return setResponse(msg);
}
// Gracefully terminate this transaction. Release memory
void MGCPTransaction::destroyed()
{
lock();
if (state() != Destroying) {
if (!outgoing() && !m_response)
setResponse(400);
changeState(Destroying);
}
if (m_engine)
m_engine->removeTrans(this,false);
TelEngine::destruct(m_cmd);
TelEngine::destruct(m_provisional);
TelEngine::destruct(m_response);
TelEngine::destruct(m_ack);
unlock();
RefObject::destroyed();
}
// Consume (process) a received message, other then the initiating one
void MGCPTransaction::processMessage(MGCPMessage* msg)
{
if (!msg)
return;
Lock lock(this);
if (state() < Initiated || state() > Ack) {
bool cmd = msg->isCommand();
Debug(m_engine,DebugInfo,"%s. Can't process %s %s in state %u [%p]",
m_debug.c_str(),msg->name().c_str(),cmd ? "command":"response",
state(),this);
TelEngine::destruct(msg);
return;
}
// Process commands
if (msg->isCommand()) {
// Commands can be received only by incoming transactions
// Check for retransmission
if (outgoing() || msg->name() != m_cmd->name()) {
Debug(m_engine,DebugNote,"%s. Can't accept %s [%p]",
m_debug.c_str(),msg->name().c_str(),this);
TelEngine::destruct(msg);
return;
}
// Retransmit the last response
DDebug(m_engine,DebugAll,
"%s. Received command retransmission in state %u [%p]",
m_debug.c_str(),state(),this);
if (state() == Trying)
send(m_provisional);
else if (state() == Responded)
send(m_response);
// If state is Initiated, wait for getEvent to process the received command
// Send nothing if we received the ACK to our final response
TelEngine::destruct(msg);
return;
}
// Process responses
if (msg->isResponse()) {
// Responses can be received only by outgoing transactions
if (!outgoing()) {
Debug(m_engine,DebugNote,
"%s. Can't accept response %d [%p]",
m_debug.c_str(),msg->code(),this);
TelEngine::destruct(msg);
return;
}
// Check response
// Send ACK for final response tretransmissions
// Don't accept different final responses
// Don't accept provisional responses after final responses
// Don't accept different provisional responses
bool ok = true;
if (msg->code() >= 200) {
bool retrans = false;
ok = !m_response;
if (ok)
m_response = msg;
else if (m_response->code() == msg->code()) {
retrans = true;
send(m_ack);
}
Debug(m_engine,(ok || retrans) ? DebugAll : DebugNote,
"%s. Received %sresponse %d [%p]",m_debug.c_str(),
ok?"":(retrans?"retransmission for ":"different "),msg->code(),this);
}
else {
ok = (!m_response && !m_provisional);
if (ok)
m_provisional = msg;
Debug(m_engine,(ok || m_response)? DebugAll : DebugNote,
"%s. Received %sprovisional response %d [%p]",m_debug.c_str(),
ok?"":(m_response?"late ":"different "),msg->code(),this);
}
if (!ok)
TelEngine::destruct(msg);
return;
}
// Process response ACK
if (msg->isAck()) {
// Responses can be received only by outgoing transactions
if (outgoing()) {
Debug(m_engine,DebugNote,"%s. Can't accept response ACK [%p]",
m_debug.c_str(),this);
TelEngine::destruct(msg);
return;
}
// Keep the ACK if not already received one
if (state() == Responded && !m_ack) {
m_ack = msg;
return;
}
Debug(m_engine,DebugNote,
"%s. Ignoring response ACK in state %u [%p]",
m_debug.c_str(),state(),this);
TelEngine::destruct(msg);
return;
}
// !!! Unknown message type
TelEngine::destruct(msg);
}
// Check timeouts. Manage retransmissions
MGCPEvent* MGCPTransaction::checkTimeout(u_int64_t time)
{
if (!m_nextRetrans || time < m_nextRetrans)
return 0;
// Terminate transaction if we have nothing to retransmit:
// Outgoing: Initiated: retransmit command. Trying: adjust timeout
// Incoming: Responded: retransmit response
while (m_retransCount) {
if ((outgoing() && state() != Initiated && state() != Trying) ||
(!outgoing() && state() != Responded))
break;
MGCPMessage* m = 0;
if (state() == Initiated)
m = m_cmd;
else if (state() == Trying)
;
else
m = m_response;
m_crtRetransInterval *= 2;
m_retransCount--;
m_nextRetrans = time + m_crtRetransInterval;
if (m) {
send(m);
Debug(m_engine,DebugInfo,"%s. Retransmitted %s remaining=%u [%p]",
m_debug.c_str(),m->name().c_str(),m_retransCount,this);
}
else
Debug(m_engine,DebugAll,"%s. Adjusted timeout remaining=%u [%p]",
m_debug.c_str(),m_retransCount,this);
return 0;
}
m_timeout = (state() == Initiated || state() == Trying);
return terminate();
}
// Event termination notification
void MGCPTransaction::eventTerminated(MGCPEvent* event)
{
if (event != m_lastEvent)
return;
DDebug(m_engine,DebugAll,"%s. Event (%p) terminated [%p]",m_debug.c_str(),event,this);
m_lastEvent = 0;
}
// Change transaction's state if the new state is a valid one
void MGCPTransaction::changeState(State newState)
{
if (newState <= m_state)
return;
DDebug(m_engine,DebugInfo,"%s. Changing state from %u to %u [%p]",
m_debug.c_str(),m_state,newState,this);
m_state = newState;
}
// (Re)send one the initial, provisional or final response. Change transaction's state
void MGCPTransaction::send(MGCPMessage* msg)
{
if (!(msg && m_engine))
return;
if (msg == m_cmd)
changeState(Initiated);
else if (msg == m_provisional)
changeState(Trying);
else if (msg == m_response)
changeState(Responded);
else if (msg == m_ack)
changeState(Ack);
else
return;
String tmp;
msg->toString(tmp);
m_engine->sendData(tmp,m_address);
}
// Check if received any final response. Create an event. Init timeout.
// Send a response ACK if requested by the response
MGCPEvent* MGCPTransaction::checkResponse(u_int64_t time)
{
if (!m_response)
return 0;
if (m_response->params.getParam("k") || m_response->params.getParam("K")) {
m_ack = new MGCPMessage(this,0);
send(m_ack);
}
initTimeout(time,true);
changeState(Responded);
return new MGCPEvent(this,m_response);
}
// Init timeout for retransmission or transaction termination
void MGCPTransaction::initTimeout(u_int64_t time, bool extra)
{
if (!extra) {
m_crtRetransInterval = m_engine->retransInterval();
m_retransCount = m_engine->retransCount();
}
else {
m_crtRetransInterval = m_engine->extraTime();
m_retransCount = 0;
}
m_nextRetrans = time + m_crtRetransInterval;
}
// Remove from engine. Create event. Deref the transaction
MGCPEvent* MGCPTransaction::terminate()
{
if (m_engine)
m_engine->removeTrans(this,false);
if (m_timeout)
Debug(m_engine,DebugNote,"%s. Timeout in state %u [%p]",m_debug.c_str(),state(),this);
else
DDebug(m_engine,DebugAll,"%s. Terminated in state %u [%p]",m_debug.c_str(),state(),this);
MGCPEvent* event = new MGCPEvent(this);
deref();
return event;
}
/* vi: set ts=8 sw=4 sts=4 noet: */

1057
libs/ymgcp/yatemgcp.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
Makefile
YateLocal.mak
YateLocal*
core*
*.o
*.a

View File

@ -1,5 +1,5 @@
Makefile
YateLocal.mak
YateLocal*
core*
*.o
*.a

View File

@ -1,5 +1,5 @@
Makefile
YateLocal.mak
YateLocal*
core*
yate-*
*.o

View File

@ -1,5 +1,5 @@
Makefile
YateLocal.mak
YateLocal*
core*
*.o
*.a

View File

@ -1,5 +1,5 @@
Makefile
YateLocal.mak
YateLocal*
core*
*.o
*.a

View File

@ -1,5 +1,5 @@
Makefile
YateLocal.mak
YateLocal*
core*
*.o
*.a

View File

@ -20,28 +20,30 @@ MODRELAX:= @MODULE_LDRELAX@
MODSTRIP:= @MODULE_SYMBOLS@
INCFILES := @top_srcdir@/yateclass.h @top_srcdir@/yatengine.h @top_srcdir@/yatephone.h ../yateversn.h
SUBDIRS := skin help gtk2
MKDEPS := ../config.status
PROGS := cdrbuild.yate cdrfile.yate \
regexroute.yate regfile.yate accfile.yate register.yate \
SUBDIRS :=
MKDEPS := @top_builddir@/config.status
PROGS := cdrbuild.yate cdrfile.yate regexroute.yate \
tonegen.yate tonedetect.yate wavefile.yate \
conference.yate moh.yate \
callgen.yate analyzer.yate rmanager.yate msgsniff.yate \
pbx.yate dbpbx.yate pbxassist.yate park.yate \
dumbchan.yate callfork.yate queues.yate \
extmodule.yate yradius.yate \
extmodule.yate conference.yate moh.yate pbx.yate \
dumbchan.yate callfork.yate \
yrtpchan.yate ystunchan.yate \
ysipchan.yate sipfeatures.yate \
ysipchan.yate \
yiaxchan.yate \
yjinglechan.yate
yjinglechan.yate \
server/pbxassist.yate server/dbpbx.yate \
server/park.yate server/queues.yate \
server/regfile.yate server/accfile.yate server/register.yate \
server/yradius.yate \
server/sipfeatures.yate \
callgen.yate analyzer.yate rmanager.yate msgsniff.yate
LIBS :=
ifneq (@HAVE_PGSQL@,no)
PROGS := $(PROGS) pgsqldb.yate
PROGS := $(PROGS) server/pgsqldb.yate
endif
ifneq (@HAVE_MYSQL@,no)
PROGS := $(PROGS) mysqldb.yate
PROGS := $(PROGS) server/mysqldb.yate
endif
ifneq (@HAVE_RESOLV@,no)
@ -49,11 +51,11 @@ PROGS := $(PROGS) enumroute.yate
endif
ifneq (@HAVE_SOUNDCARD@,no)
PROGS := $(PROGS) osschan.yate
PROGS := $(PROGS) client/osschan.yate
endif
ifneq (@HAVE_ALSA@,no)
PROGS := $(PROGS) alsachan.yate
PROGS := $(PROGS) client/alsachan.yate
endif
ifeq (@HAVE_PRI@_@HAVE_ZAP@,yes_yes)
@ -163,7 +165,7 @@ do-all do-strip do-clean do-install do-uninstall:
)
Makefile: @srcdir@/Makefile.in $(MKDEPS)
cd .. && ./config.status
cd @top_builddir@ && ./config.status
lib%.so: %.o
$(LINK) -shared -o $@ $^
@ -183,40 +185,36 @@ wpchan.yate: LOCALFLAGS = @WANPIPE_HWEC_INC@
wpchan.yate: LOCALLIBS = libypri.o -lpri
ysigchan.yate wpcard.yate zapcard.yate: ../libyatess7.so
ysigchan.yate wpcard.yate zapcard.yate: LOCALFLAGS = -I../contrib/yss7
ysigchan.yate wpcard.yate zapcard.yate: LOCALFLAGS = -I../libs/yss7
ysigchan.yate wpcard.yate zapcard.yate: LOCALLIBS = -lyatess7
h323chan.yate: LOCALFLAGS = -DPHAS_TEMPLATES -D_REENTRANT -DP_HAS_SEMAPHORES @H323_INC@
h323chan.yate: LOCALLIBS = @H323_LIB@
pgsqldb.yate: LOCALFLAGS = @PGSQL_INC@
pgsqldb.yate: LOCALLIBS = -lpq
server/pgsqldb.yate: LOCALFLAGS = @PGSQL_INC@
server/pgsqldb.yate: LOCALLIBS = -lpq
mysqldb.yate: LOCALFLAGS = @MYSQL_INC@
mysqldb.yate: LOCALLIBS = @MYSQL_LIB@
server/mysqldb.yate: LOCALFLAGS = @MYSQL_INC@
server/mysqldb.yate: LOCALLIBS = @MYSQL_LIB@
enumroute.yate: LOCALLIBS = -lresolv
alsachan.yate: LOCALLIBS = -lasound
client/alsachan.yate: LOCALLIBS = -lasound
yiaxchan.yate: ../contrib/yiax/libyateiax.a
yiaxchan.yate: LOCALFLAGS = -I@top_srcdir@/contrib/yiax
yiaxchan.yate: LOCALLIBS = -L../contrib/yiax -lyateiax
yiaxchan.yate: ../libs/yiax/libyateiax.a
yiaxchan.yate: LOCALFLAGS = -I@top_srcdir@/libs/yiax
yiaxchan.yate: LOCALLIBS = -L../libs/yiax -lyateiax
yjinglechan.yate: ../contrib/yxml/libyatexml.a ../contrib/yjingle/libyatejingle.a
yjinglechan.yate: LOCALFLAGS = -I@top_srcdir@/contrib/yxml -I@top_srcdir@/contrib/yjingle
yjinglechan.yate: LOCALLIBS = -L../contrib/yjingle -L../contrib/yxml -lyatejingle -lyatexml
yjinglechan.yate: ../libs/yxml/libyatexml.a ../libs/yjingle/libyatejingle.a
yjinglechan.yate: LOCALFLAGS = -I@top_srcdir@/libs/yxml -I@top_srcdir@/libs/yjingle
yjinglechan.yate: LOCALLIBS = -L../libs/yjingle -L../libs/yxml -lyatejingle -lyatexml
dbpbx.yate: ../contrib/ypbx/libyatepbx.a
dbpbx.yate: LOCALFLAGS = -I@top_srcdir@/contrib/ypbx
dbpbx.yate: LOCALLIBS = ../contrib/ypbx/libyatepbx.a
server/dbpbx.yate server/pbxassist.yate: ../libs/ypbx/libyatepbx.a
server/dbpbx.yate server/pbxassist.yate: LOCALFLAGS = -I@top_srcdir@/libs/ypbx
server/dbpbx.yate server/pbxassist.yate: LOCALLIBS = ../libs/ypbx/libyatepbx.a
pbxassist.yate: ../contrib/ypbx/libyatepbx.a
pbxassist.yate: LOCALFLAGS = -I@top_srcdir@/contrib/ypbx
pbxassist.yate: LOCALLIBS = ../contrib/ypbx/libyatepbx.a
ilbccodec.yate: ../contrib/ilbc/libilbc.a
ilbccodec.yate: LOCALLIBS = ../contrib/ilbc/libilbc.a
ilbccodec.yate: ../libs/ilbc/libilbc.a
ilbccodec.yate: LOCALLIBS = ../libs/ilbc/libilbc.a
ilbccodec.yate: LOCALFLAGS = @ILBC_INC@
gsmcodec.yate: LOCALLIBS = -lgsm
@ -228,16 +226,16 @@ speexcodec.yate: LOCALFLAGS = @SPEEX_INC@
faxchan.yate: LOCALLIBS = -lspandsp
faxchan.yate: LOCALFLAGS = @SPANDSP_INC@
ysipchan.yate: ../contrib/ysip/libyatesip.a
ysipchan.yate: LOCALFLAGS = -I@top_srcdir@/contrib/ysip
ysipchan.yate: LOCALLIBS = ../contrib/ysip/libyatesip.a
ysipchan.yate: ../libs/ysip/libyatesip.a
ysipchan.yate: LOCALFLAGS = -I@top_srcdir@/libs/ysip
ysipchan.yate: LOCALLIBS = ../libs/ysip/libyatesip.a
yrtpchan.yate: ../contrib/yrtp/libyatertp.a
yrtpchan.yate: LOCALFLAGS = -I@top_srcdir@/contrib/yrtp
yrtpchan.yate: LOCALLIBS = ../contrib/yrtp/libyatertp.a
yrtpchan.yate: ../libs/yrtp/libyatertp.a
yrtpchan.yate: LOCALFLAGS = -I@top_srcdir@/libs/yrtp
yrtpchan.yate: LOCALLIBS = ../libs/yrtp/libyatertp.a
gtk2/gtk2mozilla.yate: @top_srcdir@/contrib/gtk2/gtk2client.h
gtk2/gtk2mozilla.yate: LOCALFLAGS = @GTK2_INC@ @GMOZ_INC@ -I@top_srcdir@/contrib/gtk2
gtk2/gtk2mozilla.yate: @top_srcdir@/clients/gtk2/gtk2client.h
gtk2/gtk2mozilla.yate: LOCALFLAGS = @GTK2_INC@ @GMOZ_INC@ -I@top_srcdir@/clients/gtk2
gtk2/gtk2mozilla.yate: LOCALLIBS = @GMOZ_LIB@
gtk2/gtk2mozilla.yate: MODFLAGS = $(MODRELAX)
@ -245,27 +243,27 @@ rmanager.yate: LOCALFLAGS = $(COREDUMP_INC)
rmanager.yate: LOCALLIBS = $(COREDUMP_LIB)
../libyatess7.so:
$(MAKE) -C ../contrib/yss7
../contrib/yss7/libyatess7.a:
$(MAKE) -C ../contrib/yss7
$(MAKE) -C ../libs/yss7
../libs/yss7/libyatess7.a:
$(MAKE) -C ../libs/yss7
../contrib/ilbc/libilbc.a:
$(MAKE) -C ../contrib/ilbc
../libs/ilbc/libilbc.a:
$(MAKE) -C ../libs/ilbc
../contrib/ysip/libyatesip.a:
$(MAKE) -C ../contrib/ysip
../libs/ysip/libyatesip.a:
$(MAKE) -C ../libs/ysip
../contrib/yrtp/libyatertp.a:
$(MAKE) -C ../contrib/yrtp
../libs/yrtp/libyatertp.a:
$(MAKE) -C ../libs/yrtp
../contrib/yiax/libyateiax.a:
$(MAKE) -C ../contrib/yiax
../libs/yiax/libyateiax.a:
$(MAKE) -C ../libs/yiax
../contrib/yxml/libyatexml.a:
$(MAKE) -C ../contrib/yxml
../libs/yxml/libyatexml.a:
$(MAKE) -C ../libs/yxml
../contrib/yjingle/libyatejingle.a:
$(MAKE) -C ../contrib/yjingle
../libs/yjingle/libyatejingle.a:
$(MAKE) -C ../libs/yjingle
../contrib/ypbx/libyatepbx.a:
$(MAKE) -C ../contrib/ypbx
../libs/ypbx/libyatepbx.a:
$(MAKE) -C ../libs/ypbx

View File

@ -0,0 +1,9 @@
Makefile
core*
*.o
*.a
*.so
*.yate
*.orig
*~
.*.swp

View File

@ -866,7 +866,9 @@ bool ExtModReceiver::create(const char *script, const char *args)
HANDLE yate2ext[2];
int x;
if (script[0] != '/') {
tmp = s_cfg.getValue("general","scripts_dir",SCR_PATH);
tmp = Engine::sharedPath();
tmp << Engine::pathSeparator() << "scripts";
tmp = s_cfg.getValue("general","scripts_dir",tmp);
if (!tmp.endsWith(Engine::pathSeparator()))
tmp += Engine::pathSeparator();
tmp += script;

File diff suppressed because it is too large Load Diff

View File

@ -1,260 +0,0 @@
/**
* libypri.h
* This file is part of the YATE Project http://YATE.null.ro
*
* Common C++ base classes for PRI cards telephony drivers
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2004-2006 Null Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <yatephone.h>
extern "C" {
#include <libpri.h>
}
namespace TelEngine {
class Fifo
{
public:
Fifo(int buflen = 0);
~Fifo();
void clear();
bool put(unsigned char value);
unsigned int put(const unsigned char* buf, unsigned int length);
unsigned char get();
private:
int m_buflen;
int m_head;
int m_tail;
unsigned char* m_buffer;
};
class DataErrors
{
public:
inline DataErrors()
: m_events(0), m_bytes(0)
{ }
inline void update(unsigned int nbytes)
{ m_events++; m_bytes += nbytes; }
inline void clear()
{ m_events = 0; m_bytes = 0; }
inline unsigned int events() const
{ return m_events; }
inline unsigned long bytes() const
{ return m_bytes; }
private:
unsigned int m_events;
unsigned long m_bytes;
};
class PriChan;
class PriDriver;
class PriSpan : public GenObject, public Mutex
{
public:
virtual ~PriSpan();
inline struct pri *pri()
{ return m_pri; }
inline PriDriver* driver() const
{ return m_driver; }
inline int span() const
{ return m_span; }
inline bool belongs(int chan) const
{ return (chan >= m_offs) && (chan < m_offs+m_nchans); }
inline int chan1() const
{ return m_offs; }
inline int chans() const
{ return m_nchans; }
inline int bchans() const
{ return m_bchans; }
inline int dplan() const
{ return m_dplan; }
inline int pres() const
{ return m_pres; }
inline unsigned int overlapped() const
{ return m_overlapped; }
inline bool inband() const
{ return m_inband; }
inline bool detect() const
{ return m_detect; }
inline bool outOfOrder() const
{ return !m_ok; }
inline int buflen() const
{ return m_buflen; }
inline int layer1() const
{ return m_layer1; }
int findEmptyChan(int first = 0, int last = 65535) const;
PriChan *getChan(int chan) const;
void idle();
protected:
PriSpan(struct pri *_pri, PriDriver* driver, int span, int first, int chans, int dchan, Configuration& cfg, const String& sect);
void runEvent(bool idleRun);
void handleEvent(pri_event &ev);
bool validChan(int chan) const;
void restartChan(int chan, bool outgoing, bool force = false);
void ringChan(int chan, pri_event_ring &ev);
void infoChan(int chan, pri_event_ring &ev);
void digitsChan(int chan, const char* digits);
void hangupChan(int chan,pri_event_hangup &ev);
void ackChan(int chan);
void answerChan(int chan);
void proceedingChan(int chan);
void ringingChan(int chan);
PriDriver* m_driver;
int m_span;
int m_offs;
int m_nchans;
int m_bchans;
int m_dplan;
int m_pres;
int m_buflen;
int m_layer1;
bool m_inband;
bool m_detect;
unsigned int m_overlapped;
String m_callednumber;
struct pri *m_pri;
u_int64_t m_restart;
u_int64_t m_restartPeriod;
bool m_dumpEvents;
PriChan **m_chans;
bool m_ok;
};
class PriSource : public DataSource
{
public:
PriSource(PriChan *owner, const char* format, unsigned int bufsize);
virtual ~PriSource();
protected:
PriChan *m_owner;
DataBlock m_buffer;
};
class PriConsumer : public DataConsumer
{
public:
PriConsumer(PriChan *owner, const char* format, unsigned int bufsize);
virtual ~PriConsumer();
protected:
PriChan *m_owner;
DataBlock m_buffer;
};
class PriChan : public Channel
{
friend class PriSource;
friend class PriConsumer;
public:
virtual ~PriChan();
virtual void disconnected(bool final, const char *reason);
virtual bool nativeConnect(DataEndpoint *peer);
virtual bool msgProgress(Message& msg);
virtual bool msgRinging(Message& msg);
virtual bool msgAnswered(Message& msg);
virtual bool msgTone(Message& msg, const char* tone);
virtual bool msgText(Message& msg, const char* text);
virtual bool msgDrop(Message& msg, const char* reason);
virtual void callAccept(Message& msg);
virtual void callRejected(const char* error, const char* reason = 0, const Message* msg = 0);
inline PriSpan *span() const
{ return m_span; }
inline int chan() const
{ return m_chan; }
inline int absChan() const
{ return m_abschan; }
inline bool inUse() const
{ return (m_ring || m_call); }
inline bool inband() const
{ return m_inband; }
inline bool detect() const
{ return m_detect; }
void ring(pri_event_ring &ev);
void hangup(int cause = 0);
void sendDigit(char digit);
void gotDigits(const char *digits, bool overlapped = false);
bool call(Message &msg, const char *called = 0);
bool answer();
void answered();
bool progress();
bool ringing();
void idle();
void restart(bool outgoing = false);
virtual bool openData(const char* format, int echoTaps = 0) = 0;
virtual void closeData();
virtual void goneUp();
inline void setTimeout(u_int64_t tout)
{ m_timeout = tout ? Time::now()+tout : 0; }
inline void extTimeout(u_int64_t tout)
{ if (m_timeout) setTimeout(tout); }
const char *chanStatus() const;
bool isISDN() const
{ return m_isdn; }
protected:
PriChan(const PriSpan *parent, int chan, unsigned int bufsize);
virtual void dataChanged();
PriSpan *m_span;
int m_chan;
bool m_ring;
u_int64_t m_timeout;
q931_call* m_call;
unsigned int m_bufsize;
int m_abschan;
bool m_isdn;
bool m_inband;
bool m_detect;
};
class PriDriver : public Driver
{
friend class PriSpan;
public:
virtual ~PriDriver();
virtual bool isBusy() const;
virtual void dropAll();
virtual bool msgExecute(Message& msg, String& dest);
virtual void init(const char* configName);
virtual PriSpan* createSpan(PriDriver* driver, int span, int first, int chans, Configuration& cfg, const String& sect) = 0;
virtual PriChan* createChan(const PriSpan* span, int chan, unsigned int bufsize) = 0;
static void netParams(Configuration& cfg, const String& sect, int chans, int* netType, int* swType, int* dChan);
PriSpan* findSpan(int chan);
PriChan* findFree(int first = -1, int last = -1);
PriChan* findFree(const String& group);
static inline u_int8_t bitswap(u_int8_t v)
{ return s_bitswap[v]; }
protected:
PriDriver(const char* name);
void statusModule(String& str);
void statusParams(String& str);
ObjList m_spans;
ObjList m_groups;
private:
static u_int8_t s_bitswap[256];
static bool s_init;
};
}
/* vi: set ts=8 sw=4 sts=4 noet: */

View File

@ -1,8 +0,0 @@
#!/bin/sh
tests=""
if [ "$1" = "-d" ]; then
for f in $tests; do rm $f.yate; done
else
for f in $tests; do ln -s ../test/$f.yate $f.yate; done
fi

View File

@ -0,0 +1,9 @@
Makefile
core*
*.o
*.a
*.so
*.yate
*.orig
*~
.*.swp

View File

@ -0,0 +1,320 @@
/**
* heartbeat.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Linux-HA compatible heartbeat module
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2004-2006 Null Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <yatengine.h>
#include <stdio.h>
#define AUTH_BLOCKSIZE 64
// This code is copied from plugins/HBauth/crc.c
static u_int32_t const s_crctab[256] =
{
0x0,
0x04C11DB7, 0x09823B6E, 0x0D4326D9, 0x130476DC, 0x17C56B6B,
0x1A864DB2, 0x1E475005, 0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6,
0x2B4BCB61, 0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD,
0x4C11DB70, 0x48D0C6C7, 0x4593E01E, 0x4152FDA9, 0x5F15ADAC,
0x5BD4B01B, 0x569796C2, 0x52568B75, 0x6A1936C8, 0x6ED82B7F,
0x639B0DA6, 0x675A1011, 0x791D4014, 0x7DDC5DA3, 0x709F7B7A,
0x745E66CD, 0x9823B6E0, 0x9CE2AB57, 0x91A18D8E, 0x95609039,
0x8B27C03C, 0x8FE6DD8B, 0x82A5FB52, 0x8664E6E5, 0xBE2B5B58,
0xBAEA46EF, 0xB7A96036, 0xB3687D81, 0xAD2F2D84, 0xA9EE3033,
0xA4AD16EA, 0xA06C0B5D, 0xD4326D90, 0xD0F37027, 0xDDB056FE,
0xD9714B49, 0xC7361B4C, 0xC3F706FB, 0xCEB42022, 0xCA753D95,
0xF23A8028, 0xF6FB9D9F, 0xFBB8BB46, 0xFF79A6F1, 0xE13EF6F4,
0xE5FFEB43, 0xE8BCCD9A, 0xEC7DD02D, 0x34867077, 0x30476DC0,
0x3D044B19, 0x39C556AE, 0x278206AB, 0x23431B1C, 0x2E003DC5,
0x2AC12072, 0x128E9DCF, 0x164F8078, 0x1B0CA6A1, 0x1FCDBB16,
0x018AEB13, 0x054BF6A4, 0x0808D07D, 0x0CC9CDCA, 0x7897AB07,
0x7C56B6B0, 0x71159069, 0x75D48DDE, 0x6B93DDDB, 0x6F52C06C,
0x6211E6B5, 0x66D0FB02, 0x5E9F46BF, 0x5A5E5B08, 0x571D7DD1,
0x53DC6066, 0x4D9B3063, 0x495A2DD4, 0x44190B0D, 0x40D816BA,
0xACA5C697, 0xA864DB20, 0xA527FDF9, 0xA1E6E04E, 0xBFA1B04B,
0xBB60ADFC, 0xB6238B25, 0xB2E29692, 0x8AAD2B2F, 0x8E6C3698,
0x832F1041, 0x87EE0DF6, 0x99A95DF3, 0x9D684044, 0x902B669D,
0x94EA7B2A, 0xE0B41DE7, 0xE4750050, 0xE9362689, 0xEDF73B3E,
0xF3B06B3B, 0xF771768C, 0xFA325055, 0xFEF34DE2, 0xC6BCF05F,
0xC27DEDE8, 0xCF3ECB31, 0xCBFFD686, 0xD5B88683, 0xD1799B34,
0xDC3ABDED, 0xD8FBA05A, 0x690CE0EE, 0x6DCDFD59, 0x608EDB80,
0x644FC637, 0x7A089632, 0x7EC98B85, 0x738AAD5C, 0x774BB0EB,
0x4F040D56, 0x4BC510E1, 0x46863638, 0x42472B8F, 0x5C007B8A,
0x58C1663D, 0x558240E4, 0x51435D53, 0x251D3B9E, 0x21DC2629,
0x2C9F00F0, 0x285E1D47, 0x36194D42, 0x32D850F5, 0x3F9B762C,
0x3B5A6B9B, 0x0315D626, 0x07D4CB91, 0x0A97ED48, 0x0E56F0FF,
0x1011A0FA, 0x14D0BD4D, 0x19939B94, 0x1D528623, 0xF12F560E,
0xF5EE4BB9, 0xF8AD6D60, 0xFC6C70D7, 0xE22B20D2, 0xE6EA3D65,
0xEBA91BBC, 0xEF68060B, 0xD727BBB6, 0xD3E6A601, 0xDEA580D8,
0xDA649D6F, 0xC423CD6A, 0xC0E2D0DD, 0xCDA1F604, 0xC960EBB3,
0xBD3E8D7E, 0xB9FF90C9, 0xB4BCB610, 0xB07DABA7, 0xAE3AFBA2,
0xAAFBE615, 0xA7B8C0CC, 0xA379DD7B, 0x9B3660C6, 0x9FF77D71,
0x92B45BA8, 0x9675461F, 0x8832161A, 0x8CF30BAD, 0x81B02D74,
0x857130C3, 0x5D8A9099, 0x594B8D2E, 0x5408ABF7, 0x50C9B640,
0x4E8EE645, 0x4A4FFBF2, 0x470CDD2B, 0x43CDC09C, 0x7B827D21,
0x7F436096, 0x7200464F, 0x76C15BF8, 0x68860BFD, 0x6C47164A,
0x61043093, 0x65C52D24, 0x119B4BE9, 0x155A565E, 0x18197087,
0x1CD86D30, 0x029F3D35, 0x065E2082, 0x0B1D065B, 0x0FDC1BEC,
0x3793A651, 0x3352BBE6, 0x3E119D3F, 0x3AD08088, 0x2497D08D,
0x2056CD3A, 0x2D15EBE3, 0x29D4F654, 0xC5A92679, 0xC1683BCE,
0xCC2B1D17, 0xC8EA00A0, 0xD6AD50A5, 0xD26C4D12, 0xDF2F6BCB,
0xDBEE767C, 0xE3A1CBC1, 0xE760D676, 0xEA23F0AF, 0xEEE2ED18,
0xF0A5BD1D, 0xF464A0AA, 0xF9278673, 0xFDE69BC4, 0x89B8FD09,
0x8D79E0BE, 0x803AC667, 0x84FBDBD0, 0x9ABC8BD5, 0x9E7D9662,
0x933EB0BB, 0x97FFAD0C, 0xAFB010B1, 0xAB710D06, 0xA6322BDF,
0xA2F33668, 0xBCB4666D, 0xB8757BDA, 0xB5365D03, 0xB1F740B4
};
static u_int32_t crc(const char* buf, unsigned int len)
{
u_int32_t crc = 0;
while (len--)
crc = (crc << 8) ^ s_crctab[((crc >> 24) ^ *(buf++)) & 0xFF];
return ~crc;
}
using namespace TelEngine;
namespace { // anonymous
class TimerHandler : public MessageHandler
{
public:
TimerHandler(unsigned int prio)
: MessageHandler("engine.timer",prio)
{ }
virtual bool received(Message &msg);
};
class HaltHandler : public MessageHandler
{
public:
HaltHandler(unsigned int prio)
: MessageHandler("engine.halt",prio)
{ }
virtual bool received(Message &msg);
};
class HBeatPlugin : public Plugin
{
public:
enum AuthType {
AuthNone = 0,
AuthCRC,
AuthMD5,
AuthSHA1,
};
HBeatPlugin();
virtual ~HBeatPlugin();
virtual void initialize();
void sendHeartbeat(const Time& tStamp, bool goDown);
private:
Mutex m_mutex;
Socket m_socket;
String m_node;
String m_authKey;
int m_seq;
int m_ttl;
int m_authIdx;
AuthType m_authType;
};
static HBeatPlugin splugin;
void HBeatPlugin::sendHeartbeat(const Time& tStamp, bool goDown)
{
m_mutex.lock();
if (m_socket.valid()) {
char hex[16];
String buf;
buf << "t=status\n";
if (goDown)
buf << "st=dead\n";
else if (m_seq)
buf << "st=active\n";
else
buf << "st=up\n";
buf << "src=" << m_node << "\n";
::snprintf(hex,sizeof(hex),"%x",++m_seq);
buf << "seq=" << hex << "\n";
::snprintf(hex,sizeof(hex),"%x",Engine::runId());
buf << "hg=" << hex << "\n";
::snprintf(hex,sizeof(hex),"%x",tStamp.sec());
buf << "ts=" << hex << "\n";
buf << "ld=n/a\n";
buf << "ttl=" << m_ttl << "\n";
if (m_authIdx > 0) {
DataBlock key((void*)m_authKey.c_str(),m_authKey.length());
switch (m_authType) {
case AuthMD5:
if (key.length() > AUTH_BLOCKSIZE) {
MD5 hash(key);
key.assign((void*)hash.rawDigest(),hash.rawLength());
}
break;
case AuthSHA1:
if (key.length() > AUTH_BLOCKSIZE) {
SHA1 hash(key);
key.assign((void*)hash.rawDigest(),hash.rawLength());
}
break;
default:
break;
}
String tmp = "none";
const char* pkey = (const char*)key.data();
switch (m_authType) {
case AuthMD5:
{
unsigned char kbuf[AUTH_BLOCKSIZE];
unsigned int i;
for (i = 0; i < AUTH_BLOCKSIZE; i++)
kbuf[i] = 0x36 ^ (i < key.length() ? pkey[i] : 0);
MD5 hash1(kbuf,AUTH_BLOCKSIZE);
hash1.update(buf);
for (i = 0; i < AUTH_BLOCKSIZE; i++)
kbuf[i] = 0x5c ^ (i < key.length() ? pkey[i] : 0);
MD5 hash2(kbuf,AUTH_BLOCKSIZE);
hash2.update((void*)hash1.rawDigest(),hash1.rawLength());
tmp = hash2.hexDigest();
}
break;
case AuthSHA1:
{
// code duplication as we don't have a hash factory
unsigned char kbuf[AUTH_BLOCKSIZE];
unsigned int i;
for (i = 0; i < AUTH_BLOCKSIZE; i++)
kbuf[i] = 0x36 ^ (i < key.length() ? pkey[i] : 0);
SHA1 hash1(kbuf,AUTH_BLOCKSIZE);
hash1.update(buf);
for (i = 0; i < AUTH_BLOCKSIZE; i++)
kbuf[i] = 0x5c ^ (i < key.length() ? pkey[i] : 0);
SHA1 hash2(kbuf,AUTH_BLOCKSIZE);
hash2.update((void*)hash1.rawDigest(),hash1.rawLength());
tmp = hash2.hexDigest();
}
break;
case AuthCRC:
::snprintf(hex,sizeof(hex),"%x",crc(buf.c_str(),buf.length()));
tmp = hex;
default:
break;
}
buf << "auth=" << m_authIdx << " " << tmp << "\n";
}
buf = ">>>\n" + buf;
buf << "<<<\n";
// send the string including the terminating zero
if (!m_socket.send(buf.c_str(),buf.length()+1))
Debug("heartbeat",DebugWarn,"Could not send Heartbeat packet, error: %d",m_socket.error());
if (goDown)
m_socket.terminate();
}
m_mutex.unlock();
}
bool TimerHandler::received(Message &msg)
{
splugin.sendHeartbeat(msg.msgTime(),Engine::exiting());
return false;
}
bool HaltHandler::received(Message &msg)
{
splugin.sendHeartbeat(msg.msgTime(),true);
return false;
}
HBeatPlugin::HBeatPlugin()
: Plugin("heartbeat"),
m_seq(0), m_ttl(2), m_authIdx(0), m_authType(AuthNone)
{
Output("Loaded module Heartbeat");
}
HBeatPlugin::~HBeatPlugin()
{
Output("Unloading module Heartbeat");
sendHeartbeat(Time(),true);
}
void HBeatPlugin::initialize()
{
Configuration cfg(Engine::configFile("heartbeat"));
Lock lock(m_mutex);
m_authIdx = cfg.getIntValue("authentication","index");
m_authKey = cfg.getValue("authentication","key");
NamedString* auth = cfg.getKey("authentication","method");
if (auth) {
if (*auth == "crc")
m_authType = AuthCRC;
else if (*auth == "md5")
m_authType = AuthMD5;
else if (*auth == "sha1")
m_authType = AuthSHA1;
else
m_authIdx = 0;
}
else
m_authIdx = 0;
if (!m_socket.valid()) {
if (!cfg.getBoolValue("general","enabled",true))
return;
m_node = cfg.getValue("general","node");
if (m_node.null())
return;
SocketAddr addr(AF_INET);
if (!addr.host(cfg.getValue("general","host")))
return;
addr.port(cfg.getIntValue("general","port",694));
if (!m_socket.create(AF_INET,SOCK_DGRAM,IPPROTO_UDP))
return;
Output("Initializing module Heartbeat, node '%s' to %s:%d",
m_node.c_str(),addr.host().c_str(),addr.port());
#ifdef SO_BROADCAST
int opt = 1;
if (cfg.getBoolValue("general","broadcast",true) && !m_socket.setOption(SOL_SOCKET,SO_BROADCAST,&opt,sizeof(opt)))
Debug("heartbeat",DebugMild,"Could not enable broadcast on socket, error: %d",m_socket.error());
#endif
if (!(m_socket.connect(addr) && m_socket.setBlocking())) {
Debug("heartbeat",DebugWarn,"Could not set up socket, error: %d",m_socket.error());
m_socket.terminate();
return;
}
m_ttl = cfg.getIntValue("general","ttl",2);
if (m_ttl < 1)
m_ttl = 1;
Engine::install(new TimerHandler(cfg.getIntValue("priorities","engine.timer",150)));
Engine::install(new HaltHandler(cfg.getIntValue("priorities","engine.halt",50)));
}
}
}; // anonymous namespace
/* vi: set ts=8 sw=4 sts=4 noet: */

550
modules/server/mgcpca.cpp Normal file
View File

@ -0,0 +1,550 @@
/**
* mgcpca.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Media Gateway Control Protocol - Call Agent - also remote data helper
* for other protocols
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2004-2006 Null Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <yatephone.h>
#include <yatemgcp.h>
#include <stdlib.h>
using namespace TelEngine;
namespace { // anonymous
class YMGCPEngine : public MGCPEngine
{
public:
inline YMGCPEngine(const NamedList* params)
: MGCPEngine(false,0,params)
{ }
virtual ~YMGCPEngine();
virtual bool processEvent(MGCPTransaction* trans, MGCPMessage* msg, void* data);
};
class MGCPWrapper : public DataEndpoint
{
YCLASS(MGCPWrapper,DataEndpoint)
public:
MGCPWrapper(CallEndpoint* conn, const char* media, Message& msg);
~MGCPWrapper();
bool sendDTMF(const String& tones);
void gotDTMF(char tone);
inline const String& id() const
{ return m_id; }
inline const String& ntfyId() const
{ return m_notify; }
inline const String& callId() const
{ return m_master; }
inline const String& connEp() const
{ return m_connEp; }
inline const String& connId() const
{ return m_connId; }
inline bool isAudio() const
{ return m_audio; }
static MGCPWrapper* find(const CallEndpoint* conn, const String& media);
static MGCPWrapper* find(const String& id);
static MGCPWrapper* findNotify(const String& id);
bool processEvent(MGCPTransaction* tr, MGCPMessage* mm);
bool rtpMessage(Message& msg);
RefPointer<MGCPMessage> sendSync(MGCPMessage* mm, const SocketAddr& address);
void clearConn();
protected:
virtual bool nativeConnect(DataEndpoint* peer);
private:
void addParams(MGCPMessage* mm);
MGCPTransaction* m_tr;
RefPointer<MGCPMessage> m_msg;
String m_connId;
String m_connEp;
String m_id;
String m_notify;
String m_master;
bool m_audio;
};
class RtpHandler : public MessageHandler
{
public:
RtpHandler(unsigned int prio) : MessageHandler("chan.rtp",prio) { }
virtual bool received(Message &msg);
};
class DTMFHandler : public MessageHandler
{
public:
DTMFHandler() : MessageHandler("chan.dtmf",150) { }
virtual bool received(Message &msg);
};
class MGCPPlugin : public Module
{
public:
MGCPPlugin();
virtual ~MGCPPlugin();
virtual void initialize();
virtual void statusParams(String& str);
virtual void statusDetail(String& str);
};
static YMGCPEngine* s_engine = 0;
static MGCPEndpoint* s_endpoint = 0;
static String s_defaultEp;
static MGCPPlugin splugin;
static ObjList s_wrappers;
static Mutex s_mutex;
// copy parameter (if present) with new name
bool copyRename(NamedList& dest, const char* dname, const NamedList& src, const String& sname)
{
if (!sname)
return false;
const NamedString* value = src.getParam(sname);
if (!value)
return false;
dest.addParam(dname,*value);
return true;
}
YMGCPEngine::~YMGCPEngine()
{
s_engine = 0;
s_endpoint = 0;
}
bool YMGCPEngine::processEvent(MGCPTransaction* trans, MGCPMessage* msg, void* data)
{
MGCPWrapper* wrap = YOBJECT(MGCPWrapper,static_cast<GenObject*>(data));
Debug(this,DebugAll,"YMGCPEngine::processEvent(%p,%p,%p) wrap=%p [%p]",
trans,msg,data,wrap,this);
if (!trans)
return false;
if (wrap)
return wrap->processEvent(trans,msg);
if (!msg)
return false;
if (!data && !trans->outgoing() && msg->isCommand()) {
if (msg->name() == "NTFY") {
Debug(this,DebugStub,"NTFY from '%s'",msg->endpointId().c_str());
wrap = MGCPWrapper::findNotify(msg->params.getValue("x"));
if (wrap)
return wrap->processEvent(trans,msg);
trans->setResponse(515,"Unknown notification-id");
return true;
}
if (msg->name() == "RSIP") {
Debug(this,DebugStub,"RSIP from '%s'",msg->endpointId().c_str());
trans->setResponse(200);
return true;
}
Debug(this,DebugMild,"Unhandled '%s' from '%s'",
msg->name().c_str(),msg->endpointId().c_str());
}
return false;
}
MGCPWrapper::MGCPWrapper(CallEndpoint* conn, const char* media, Message& msg)
: DataEndpoint(conn,media),
m_tr(0)
{
Debug(&splugin,DebugAll,"MGCPWrapper::MGCPWrapper(%p,'%s') [%p]",
conn,media,this);
m_id = "mgcp/";
m_id << (unsigned int)::random();
if (conn)
m_master = conn->id();
m_master = msg.getValue("id",(conn ? conn->id().c_str() : (const char*)0));
m_audio = (name() == "audio");
m_connEp = msg.getValue("mgcp_endpoint",s_defaultEp);
s_mutex.lock();
s_wrappers.append(this);
// setupRTP(localip,rtcp);
s_mutex.unlock();
}
MGCPWrapper::~MGCPWrapper()
{
Debug(&splugin,DebugAll,"MGCPWrapper::~MGCPWrapper() '%s' [%p]",
name().c_str(),this);
s_mutex.lock();
s_wrappers.remove(this,false);
if (m_tr) {
m_tr->userData(0);
m_tr = 0;
}
s_mutex.unlock();
m_msg = 0;
clearConn();
}
bool MGCPWrapper::processEvent(MGCPTransaction* tr, MGCPMessage* mm)
{
Debug(&splugin,DebugAll,"MGCPWrapper::processEvent(%p,%p) [%p]",
tr,mm,this);
if (tr == m_tr) {
if (!mm || (tr->msgResponse())) {
tr->userData(0);
m_msg = mm;
m_tr = 0;
}
}
else if (mm) {
if (mm->name() == "NTFY") {
String* event = mm->params.getParam("o");
if (event) {
Debug(&splugin,DebugInfo,"Event '%s' [%p]",event->c_str(),this);
}
tr->setResponse(200);
return true;
}
}
return false;
}
bool MGCPWrapper::rtpMessage(Message& msg)
{
if (!s_endpoint)
return false;
const char* cmd = "MDCX";
if (m_connId.null())
cmd = "CRCX";
if (msg.getBoolValue("terminate")) {
if (m_connId.null())
return true;
cmd = "DLCX";
}
MGCPEpInfo* ep = s_endpoint->find(m_connEp);
if (!ep)
return false;
RefPointer<MGCPMessage> mm = new MGCPMessage(s_engine,cmd,ep->toString());
addParams(mm);
if (m_connId.null()) {
copyRename(mm->params,"x-transport",msg,"transport");
copyRename(mm->params,"x-mediatype",msg,"media");
}
copyRename(mm->params,"x-localip",msg,"localip");
copyRename(mm->params,"x-localport",msg,"localport");
copyRename(mm->params,"x-remoteip",msg,"remoteip");
copyRename(mm->params,"x-remoteport",msg,"remoteport");
copyRename(mm->params,"x-payload",msg,"payload");
copyRename(mm->params,"x-evpayload",msg,"evpayload");
copyRename(mm->params,"x-format",msg,"format");
copyRename(mm->params,"x-direction",msg,"direction");
copyRename(mm->params,"x-ssrc",msg,"ssrc");
copyRename(mm->params,"x-drillhole",msg,"drillhole");
copyRename(mm->params,"x-autoaddr",msg,"autoaddr");
copyRename(mm->params,"x-anyssrc",msg,"anyssrc");
mm = sendSync(mm,ep->address);
if (!mm)
return false;
if (m_connId.null())
m_connId = mm->params.getParam("i");
if (m_connId.null())
return false;
copyRename(msg,"localip",mm->params,"x-localip");
copyRename(msg,"localport",mm->params,"x-localport");
msg.setParam("rtpid",id());
return true;
}
void MGCPWrapper::clearConn()
{
if (m_connId.null() || !s_endpoint)
return;
MGCPEpInfo* ep = s_endpoint->find(m_connEp);
if (!ep)
return;
MGCPMessage* mm = new MGCPMessage(s_engine,"DLCX",ep->toString());
addParams(mm);
s_engine->sendCommand(mm,ep->address);
}
void MGCPWrapper::addParams(MGCPMessage* mm)
{
if (!mm)
return;
if (m_connId)
mm->params.addParam("I",m_connId);
if (m_master) {
String callId;
callId.hexify((void*)m_master.c_str(),m_master.length(),0,true);
mm->params.addParam("C",callId);
}
}
RefPointer<MGCPMessage> MGCPWrapper::sendSync(MGCPMessage* mm, const SocketAddr& address)
{
while (m_msg) {
if (Thread::check(false))
return 0;
Thread::msleep(10);
}
MGCPTransaction* tr = s_engine->sendCommand(mm,address);
tr->userData(static_cast<GenObject*>(this));
m_tr = tr;
while (m_tr == tr)
Thread::msleep(10);
RefPointer<MGCPMessage> tmp = m_msg;
m_msg = 0;
Debug(&splugin,DebugStub,"MGCPWrapper::sendSync() returning %p [%p]",(void*)tmp,this);
return tmp;
}
MGCPWrapper* MGCPWrapper::find(const CallEndpoint* conn, const String& media)
{
if (media.null() || !conn)
return 0;
Lock lock(s_mutex);
ObjList* l = &s_wrappers;
for (; l; l=l->next()) {
const MGCPWrapper *p = static_cast<const MGCPWrapper *>(l->get());
if (p && (p->getCall() == conn) && (p->name() == media))
return const_cast<MGCPWrapper *>(p);
}
return 0;
}
MGCPWrapper* MGCPWrapper::find(const String& id)
{
if (id.null())
return 0;
Lock lock(s_mutex);
ObjList* l = &s_wrappers;
for (; l; l=l->next()) {
const MGCPWrapper *p = static_cast<const MGCPWrapper *>(l->get());
if (p && (p->id() == id))
return const_cast<MGCPWrapper *>(p);
}
return 0;
}
MGCPWrapper* MGCPWrapper::findNotify(const String& id)
{
if (id.null())
return 0;
Lock lock(s_mutex);
ObjList* l = &s_wrappers;
for (; l; l=l->next()) {
const MGCPWrapper *p = static_cast<const MGCPWrapper *>(l->get());
if (p && (p->ntfyId() == id))
return const_cast<MGCPWrapper *>(p);
}
return 0;
}
bool MGCPWrapper::sendDTMF(const String& tones)
{
Debug(&splugin,DebugStub,"MGCPWrapper::sendDTMF('%s') [%p]",
tones.c_str(),this);
MGCPEpInfo* ep = s_endpoint->find(m_connEp);
if (!ep)
return false;
MGCPMessage* mm = new MGCPMessage(s_engine,"NTFY",ep->toString());
addParams(mm);
String tmp;
for (unsigned int i = 0; i < tones.length(); i++) {
if (tmp)
tmp << ",";
tmp << "D/" << tones.at(i);
}
mm->params.setParam("O",tmp);
return s_engine->sendCommand(mm,ep->address) != 0;
}
void MGCPWrapper::gotDTMF(char tone)
{
Debug(&splugin,DebugInfo,"MGCPWrapper::gotDTMF('%c') [%p]",tone,this);
if (m_master.null())
return;
char buf[2];
buf[0] = tone;
buf[1] = 0;
Message *m = new Message("chan.masquerade");
m->addParam("id",m_master);
m->addParam("message","chan.dtmf");
m->addParam("text",buf);
Engine::enqueue(m);
}
bool MGCPWrapper::nativeConnect(DataEndpoint* peer)
{
MGCPWrapper* other = YOBJECT(MGCPWrapper,peer);
if (!other)
return false;
// check if the other connection is using same endpoint
if (other->connEp() != m_connEp)
return false;
if (other->connId().null()) {
Debug(&splugin,DebugWarn,"Not bridging to uninitialized %p [%p]",other,this);
return false;
}
Debug(&splugin,DebugStub,"Native bridging to %p [%p]",other,this);
MGCPEpInfo* ep = s_endpoint->find(m_connEp);
if (!ep)
return false;
MGCPMessage* mm = new MGCPMessage(s_engine,"MDCX",ep->toString());
addParams(mm);
mm->params.setParam("Z2",other->connId());
return s_engine->sendCommand(mm,ep->address) != 0;
}
bool RtpHandler::received(Message& msg)
{
// refuse calls from a MGCP-GW
if (!msg.getBoolValue("mgcp_allowed",true))
return false;
String trans = msg.getValue("transport");
if (trans && !trans.startsWith("RTP/"))
return false;
Debug(&splugin,DebugAll,"RTP message received");
MGCPWrapper* w = 0;
const char* media = msg.getValue("media","audio");
CallEndpoint *ch = static_cast<CallEndpoint*>(msg.userData());
if (ch) {
w = MGCPWrapper::find(ch,media);
if (w)
Debug(&splugin,DebugAll,"Wrapper %p found by CallEndpoint",w);
}
if (!w) {
w = MGCPWrapper::find(msg.getValue("rtpid"));
if (w)
Debug(&splugin,DebugAll,"Wrapper %p found by ID",w);
}
if (!(ch || w)) {
Debug(&splugin,DebugWarn,"Neither call channel nor MGCP wrapper found!");
return false;
}
if (w)
return w->rtpMessage(msg);
if (ch)
ch->clearEndpoint(media);
w = new MGCPWrapper(ch,media,msg);
if (!w->rtpMessage(msg))
return false;
if (ch && ch->getPeer())
w->connect(ch->getPeer()->getEndpoint(media));
return true;
}
bool DTMFHandler::received(Message& msg)
{
String targetid(msg.getValue("targetid"));
if (targetid.null())
return false;
String text(msg.getValue("text"));
if (text.null())
return false;
MGCPWrapper* wrap = MGCPWrapper::find(targetid);
return wrap && wrap->sendDTMF(text);
}
MGCPPlugin::MGCPPlugin()
: Module("mgcpca","misc")
{
Output("Loaded module MGCP-CA");
}
MGCPPlugin::~MGCPPlugin()
{
Output("Unloading module MGCP-CA");
s_wrappers.clear();
delete s_engine;
}
void MGCPPlugin::statusParams(String& str)
{
s_mutex.lock();
str.append("chans=",",") << s_wrappers.count();
s_mutex.unlock();
}
void MGCPPlugin::statusDetail(String& str)
{
s_mutex.lock();
ObjList* l = s_wrappers.skipNull();
for (; l; l=l->skipNext()) {
MGCPWrapper* w = static_cast<MGCPWrapper*>(l->get());
str.append(w->id(),",") << "=" << w->callId();
}
s_mutex.unlock();
}
void MGCPPlugin::initialize()
{
Output("Initializing module MGCP Call Agent");
Configuration cfg(Engine::configFile("mgcpca"));
setup();
NamedList* sect = cfg.getSection("engine");
if (s_engine && sect)
s_engine->initialize(*sect);
while (!s_engine) {
if (!(sect && sect->getBoolValue("enabled",true)))
break;
s_engine = new YMGCPEngine(sect);
s_engine->debugChain(this);
s_endpoint = new MGCPEndpoint(
s_engine,
cfg.getValue("endpoint","user","yate"),
cfg.getValue("endpoint","host",s_engine->address().host()),
cfg.getIntValue("endpoint","port")
);
int n = cfg.sections();
for (int i = 0; i < n; i++) {
sect = cfg.getSection(i);
if (!sect)
continue;
String name(*sect);
if (name.startSkip("gw") && name) {
MGCPEpInfo* ep = s_endpoint->append(
sect->getValue("user",name),
sect->getValue("host"),
sect->getIntValue("port",0)
);
if (ep) {
if (s_defaultEp.null() || sect->getBoolValue("default"))
s_defaultEp = ep->toString();
}
else
Debug(this,DebugWarn,"Could not set endpoint for gateway '%s'",
name.c_str());
}
}
Debug(this,DebugNote,"Default remote endpoint: '%s'",s_defaultEp.c_str());
Engine::install(new RtpHandler(cfg.getIntValue("general","priority",80)));
Engine::install(new DTMFHandler);
}
}
}; // anonymous namespace
/* vi: set ts=8 sw=4 sts=4 noet: */

566
modules/server/mgcpgw.cpp Normal file
View File

@ -0,0 +1,566 @@
/**
* mgcpgw.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Media Gateway Control Protocol - Gateway component
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2004-2006 Null Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <yatephone.h>
#include <yatemgcp.h>
#include <stdlib.h>
using namespace TelEngine;
namespace { // anonymous
class YMGCPEngine : public MGCPEngine
{
public:
inline YMGCPEngine(const NamedList* params)
: MGCPEngine(true,0,params)
{ }
virtual ~YMGCPEngine();
virtual bool processEvent(MGCPTransaction* trans, MGCPMessage* msg, void* data);
private:
bool createConn(MGCPTransaction* trans, MGCPMessage* msg);
};
class MGCPChan : public Channel
{
YCLASS(MGCPChan,Channel);
public:
enum IdType {
CallId,
ConnId,
NtfyId,
};
MGCPChan(const char* connId = 0);
virtual ~MGCPChan();
virtual void callAccept(Message& msg);
virtual bool msgTone(Message& msg, const char* tone);
const String& getId(IdType type) const;
bool processEvent(MGCPTransaction* tr, MGCPMessage* mm);
bool initialEvent(MGCPTransaction* tr, MGCPMessage* mm, const MGCPEndpointId& id);
void activate(bool standby);
protected:
void disconnected(bool final, const char* reason);
private:
void endTransaction(int code = 407, const NamedList* params = 0);
bool reqNotify(String& evt);
bool setSignal(String& req);
static void copyRtpParams(NamedList& dest, const NamedList& src);
MGCPTransaction* m_tr;
String m_connEp;
String m_callId;
String m_ntfyId;
String m_rtpId;
bool m_standby;
bool m_isRtp;
};
class MGCPPlugin : public Driver
{
public:
MGCPPlugin();
virtual ~MGCPPlugin();
virtual bool msgExecute(Message& msg, String& dest);
virtual void initialize();
RefPointer<MGCPChan> findConn(const String* id, MGCPChan::IdType type);
inline RefPointer<MGCPChan> findConn(const String& id, MGCPChan::IdType type)
{ return findConn(&id,type); }
void activate(bool standby);
};
class DummyCall : public CallEndpoint
{
public:
inline DummyCall()
: CallEndpoint("dummy")
{ }
};
static MGCPPlugin splugin;
static YMGCPEngine* s_engine = 0;
// warm standby mode
static bool s_standby = false;
// start time as UNIX time
String s_started;
// copy parameter (if present) with new name
bool copyRename(NamedList& dest, const char* dname, const NamedList& src, const String& sname)
{
if (!sname)
return false;
const NamedString* value = src.getParam(sname);
if (!value)
return false;
dest.addParam(dname,*value);
return true;
}
YMGCPEngine::~YMGCPEngine()
{
s_engine = 0;
}
// process all MGCP events, distribute them according to their type
bool YMGCPEngine::processEvent(MGCPTransaction* trans, MGCPMessage* msg, void* data)
{
RefPointer<MGCPChan> chan = YOBJECT(MGCPChan,static_cast<GenObject*>(data));
Debug(this,DebugAll,"YMGCPEngine::processEvent(%p,%p,%p) [%p]",
trans,msg,data,this);
if (!trans)
return false;
if (chan)
return chan->processEvent(trans,msg);
if (!msg)
return false;
if (!data && !trans->outgoing() && msg->isCommand()) {
if (msg->name() == "CRCX") {
// create connection
if (!createConn(trans,msg))
trans->setResponse(500); // unknown endpoint
return true;
}
if ((msg->name() == "DLCX") || // delete
(msg->name() == "MDCX") || // modify
(msg->name() == "AUCX")) { // audit
// connection must exist already
chan = splugin.findConn(msg->params.getParam("i"),MGCPChan::ConnId);
if (chan)
return chan->processEvent(trans,msg);
trans->setResponse(515); // no connection
return true;
}
if (msg->name() == "RQNT") {
// request notify
chan = splugin.findConn(msg->params.getParam("x"),MGCPChan::NtfyId);
if (chan)
return chan->processEvent(trans,msg);
}
if (msg->name() == "EPCF") {
// endpoint configuration
NamedList params("");
bool standby = msg->params.getBoolValue("x-standby",s_standby);
if (standby != s_standby) {
params << "Switching to " << (standby ? "standby" : "active") << " mode";
Debug(this,DebugNote,"%s",params.c_str());
s_standby = standby;
splugin.activate(standby);
}
params.addParam("x-standby",String::boolText(s_standby));
trans->setResponse(200,&params);
return true;
}
if (msg->name() == "AUEP") {
// audit endpoint
NamedList params("");
params.addParam("MD",String(s_engine->maxRecvPacket()));
params.addParam("x-standby",String::boolText(s_standby));
params.addParam("x-started",s_started);
trans->setResponse(200,&params);
return true;
}
Debug(this,DebugMild,"Unhandled '%s' from '%s'",
msg->name().c_str(),msg->endpointId().c_str());
}
return false;
}
// create a new connection
bool YMGCPEngine::createConn(MGCPTransaction* trans, MGCPMessage* msg)
{
String id = msg->endpointId();
const char* connId = msg->params.getValue("i");
DDebug(this,DebugInfo,"YMGCPEngine::createConn() id='%s' connId='%s'",id.c_str(),connId);
if (connId && splugin.findConn(connId,MGCPChan::ConnId)) {
trans->setResponse(539,"Connection exists");
return true;
}
MGCPChan* chan = new MGCPChan(connId);
return chan->initialEvent(trans,msg,id);
}
MGCPChan::MGCPChan(const char* connId)
: Channel(splugin),
m_tr(0), m_standby(s_standby), m_isRtp(false)
{
DDebug(this,DebugAll,"MGCPChan::MGCPChan('%s') [%p]",connId,this);
status("created");
if (connId) {
if (!m_standby)
Debug(this,DebugMild,"Using provided connection ID in active mode! [%p]",this);
m_address = connId;
}
else {
if (m_standby)
Debug(this,DebugMild,"Allocating connection ID in standby mode! [%p]",this);
long int r = ::random();
m_address.hexify(&r,sizeof(r),0,true);
}
}
MGCPChan::~MGCPChan()
{
DDebug(this,DebugAll,"MGCPChan::~MGCPChan() [%p]",this);
endTransaction();
}
void MGCPChan::disconnected(bool final, const char* reason)
{
if (final || Engine::exiting())
return;
DummyCall* dummy = new DummyCall;
connect(dummy);
dummy->deref();
}
const String& MGCPChan::getId(IdType type) const
{
switch (type) {
case CallId:
return m_callId;
case ConnId:
return address();
case NtfyId:
return m_ntfyId;
default:
return String::empty();
}
}
void MGCPChan::activate(bool standby)
{
if (standby == m_standby)
return;
Debug(this,DebugCall,"Switching to %s mode. [%p]",standby ? "standby" : "active",this);
m_standby = standby;
}
void MGCPChan::endTransaction(int code, const NamedList* params)
{
MGCPTransaction* tr = m_tr;
m_tr = 0;
if (!tr)
return;
Debug(this,DebugInfo,"Finishing transaction %p with code %d [%p]",tr,code,this);
tr->userData(0);
tr->setResponse(code,params);
}
// method called for each event requesting notification
bool MGCPChan::reqNotify(String& evt)
{
Debug(this,DebugStub,"MGCPChan::reqNotify('%s') [%p]",evt.c_str(),this);
return false;
}
// method called for each signal request
bool MGCPChan::setSignal(String& req)
{
Debug(this,DebugStub,"MGCPChan::setSignal('%s') [%p]",req.c_str(),this);
return false;
}
void MGCPChan::callAccept(Message& msg)
{
NamedList params("");
params.addParam("I",address());
params.addParam("x-standby",String::boolText(m_standby));
endTransaction(200,&params);
}
bool MGCPChan::msgTone(Message& msg, const char* tone)
{
if (null(tone))
return false;
MGCPEndpoint* ep = s_engine->findEp(m_connEp);
if (!ep)
return false;
MGCPEpInfo* epi = ep->peer();
if (!epi)
return false;
MGCPMessage* mm = new MGCPMessage(s_engine,"NTFY",epi->toString());
String tmp;
while (char c = *tone++) {
if (tmp)
tmp << ",";
tmp << "D/" << c;
}
mm->params.setParam("O",tmp);
return s_engine->sendCommand(mm,epi->address) != 0;
}
bool MGCPChan::processEvent(MGCPTransaction* tr, MGCPMessage* mm)
{
Debug(this,DebugInfo,"MGCPChan::processEvent(%p,%p) [%p]",tr,mm,this);
if (!mm) {
if (m_tr == tr) {
Debug(this,DebugInfo,"Clearing transaction %p [%p]",tr,this);
m_tr = 0;
tr->userData(0);
}
return true;
}
if (!(m_tr || tr->userData())) {
Debug(this,DebugInfo,"Acquiring transaction %p [%p]",tr,this);
m_tr = tr;
tr->userData(static_cast<GenObject*>(this));
}
NamedList params("");
params.addParam("I",address());
params.addParam("x-standby",String::boolText(m_standby));
if (mm->name() == "DLCX") {
disconnect();
status("deleted");
clearEndpoint();
m_address.clear();
tr->setResponse(250,&params);
return true;
}
if (mm->name() == "MDCX") {
NamedString* param = mm->params.getParam("z2");
if (param) {
// native connect requested
RefPointer<MGCPChan> chan2 = splugin.findConn(*param,MGCPChan::ConnId);
if (!chan2) {
tr->setResponse(515); // no connection
return true;
}
if (!connect(chan2,mm->params.getValue("x-reason","bridged"))) {
tr->setResponse(400); // unspecified error
return true;
}
}
param = mm->params.getParam("x");
if (param)
m_ntfyId = *param;
if (m_isRtp) {
Message m("chan.rtp");
m.addParam("mgcp_allowed",String::boolText(false));
copyRtpParams(m,mm->params);
if (m_rtpId)
m.setParam("rtpid",m_rtpId);
m.userData(this);
if (Engine::dispatch(m)) {
copyRename(params,"x-localip",m,"localip");
copyRename(params,"x-localport",m,"localport");
m_rtpId = m.getValue("rtpid",m_rtpId);
}
}
tr->setResponse(200,&params);
return true;
}
if (mm->name() == "AUCX") {
tr->setResponse(200,&params);
return true;
}
if (mm->name() == "RQNT") {
bool ok = true;
// what we are requested to notify back
NamedString* req = mm->params.getParam("r");
if (req) {
ObjList* lst = req->split(',');
for (ObjList* item = lst->skipNull(); item; item = item->skipNext())
ok = reqNotify(*static_cast<String*>(item->get())) && ok;
delete lst;
}
// what we must signal now
req = mm->params.getParam("s");
if (req) {
ObjList* lst = req->split(',');
for (ObjList* item = lst->skipNull(); item; item = item->skipNext())
ok = setSignal(*static_cast<String*>(item->get())) && ok;
delete lst;
}
tr->setResponse(ok ? 200 : 538,&params);
return true;
}
return false;
}
bool MGCPChan::initialEvent(MGCPTransaction* tr, MGCPMessage* mm, const MGCPEndpointId& id)
{
Debug(this,DebugInfo,"MGCPChan::initialEvent(%p,%p,'%s') [%p]",
tr,mm,id.id().c_str(),this);
m_connEp = id.id();
m_callId = mm->params.getValue("c");
m_ntfyId = mm->params.getValue("x");
if (id.user() == "gigi")
m_isRtp = true;
Message* m = message(m_isRtp ? "chan.rtp" : "call.route");
m->addParam("mgcp_allowed",String::boolText(false));
copyRtpParams(*m,mm->params);
if (m_isRtp) {
m->userData(this);
bool ok = Engine::dispatch(m);
if (!ok) {
delete m;
deref();
return false;
}
NamedList params("");
params.addParam("I",address());
params.addParam("x-standby",String::boolText(m_standby));
copyRename(params,"x-localip",*m,"localip");
copyRename(params,"x-localport",*m,"localport");
m_rtpId = m->getValue("rtpid");
delete m;
tr->setResponse(200,&params);
DummyCall* dummy = new DummyCall;
connect(dummy);
dummy->deref();
deref();
return true;
}
m_tr = tr;
tr->userData(static_cast<GenObject*>(this));
m->addParam("called",id.id());
if (startRouter(m)) {
tr->sendProvisional();
return true;
}
return false;
}
void MGCPChan::copyRtpParams(NamedList& dest, const NamedList& src)
{
copyRename(dest,"transport",src,"x-transport");
copyRename(dest,"media",src,"x-media");
copyRename(dest,"localip",src,"x-localip");
copyRename(dest,"localport",src,"x-localport");
copyRename(dest,"remoteip",src,"x-remoteip");
copyRename(dest,"remoteport",src,"x-remoteport");
copyRename(dest,"payload",src,"x-payload");
copyRename(dest,"evpayload",src,"x-evpayload");
copyRename(dest,"format",src,"x-format");
copyRename(dest,"direction",src,"x-direction");
copyRename(dest,"ssrc",src,"x-ssrc");
copyRename(dest,"drillhole",src,"x-drillhole");
copyRename(dest,"autoaddr",src,"x-autoaddr");
copyRename(dest,"anyssrc",src,"x-anyssrc");
}
MGCPPlugin::MGCPPlugin()
: Driver("mgcpgw","misc")
{
Output("Loaded module MGCP-GW");
}
MGCPPlugin::~MGCPPlugin()
{
Output("Unloading module MGCP-GW");
delete s_engine;
}
bool MGCPPlugin::msgExecute(Message& msg, String& dest)
{
Debug(this,DebugWarn,"Received execute request for gateway '%s'",dest.c_str());
return false;
}
RefPointer<MGCPChan> MGCPPlugin::findConn(const String* id, MGCPChan::IdType type)
{
if (!id || id->null())
return 0;
Lock lock(this);
for (ObjList* l = channels().skipNull(); l; l = l->skipNext()) {
MGCPChan* c = static_cast<MGCPChan*>(l->get());
if (c->getId(type) == *id)
return c;
}
return 0;
}
void MGCPPlugin::activate(bool standby)
{
lock();
ListIterator iter(channels());
while (GenObject* obj = iter.get()) {
RefPointer<MGCPChan> chan = static_cast<MGCPChan*>(obj);
if (chan) {
unlock();
chan->activate(standby);
lock();
}
}
unlock();
}
void MGCPPlugin::initialize()
{
Output("Initializing module MGCP Gateway");
Configuration cfg(Engine::configFile("mgcpgw"));
setup();
NamedList* sect = cfg.getSection("engine");
if (s_engine && sect)
s_engine->initialize(*sect);
while (!s_engine) {
if (!(sect && sect->getBoolValue("enabled",true)))
break;
s_started = Time::secNow();
s_standby = cfg.getBoolValue("general","standby",false);
s_engine = new YMGCPEngine(sect);
s_engine->debugChain(this);
int n = cfg.sections();
for (int i = 0; i < n; i++) {
sect = cfg.getSection(i);
if (!sect)
continue;
String name(*sect);
if (name.startSkip("ep") && name) {
MGCPEndpoint* ep = new MGCPEndpoint(
s_engine,
sect->getValue("local_user",name),
sect->getValue("local_host",s_engine->address().host()),
sect->getIntValue("local_port")
);
MGCPEpInfo* ca = ep->append(0,
sect->getValue("remote_host"),
sect->getIntValue("remote_port",0)
);
if (ca) {
if (sect->getBoolValue("announce",true)) {
MGCPMessage* mm = new MGCPMessage(s_engine,"RSIP",ep->toString());
mm->params.addParam("RM","restart");
mm->params.addParam("x-standby",String::boolText(s_standby));
mm->params.addParam("x-started",s_started);
s_engine->sendCommand(mm,ca->address);
}
}
else
Debug(this,DebugWarn,"Could not set remote endpoint for '%s'",
name.c_str());
}
}
}
}
}; // anonymous namespace
/* vi: set ts=8 sw=4 sts=4 noet: */

View File

@ -0,0 +1,352 @@
/**
* mrcpspeech.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Detector and synthesizer for voice and tones using a MRCP v2 server
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2004-2006 Null Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <yatephone.h>
using namespace TelEngine;
namespace { // anonymous
class MrcpConnection : public CallEndpoint
{
public:
inline MrcpConnection(const char* id, const char* original)
: CallEndpoint(id),
m_original(original), m_socket(0)
{ }
virtual ~MrcpConnection();
bool init(Message& msg, const char* target);
bool answered(Message& msg);
private:
String m_original;
Socket* m_socket;
};
class MrcpConsumer : public DataConsumer
{
YCLASS(MrcpConsumer,DataConsumer)
public:
MrcpConsumer(const String& id, const char* target, const char* format = 0);
virtual ~MrcpConsumer();
virtual bool setFormat(const DataFormat& format);
virtual void Consume(const DataBlock& data, unsigned long tStamp);
bool init(Message& msg);
private:
void cleanup();
DataSource* m_source;
MrcpConnection* m_chan;
String m_id;
String m_target;
};
class AttachHandler : public MessageHandler
{
public:
AttachHandler() : MessageHandler("chan.attach") { }
virtual bool received(Message& msg);
};
class RecordHandler : public MessageHandler
{
public:
RecordHandler() : MessageHandler("chan.record") { }
virtual bool received(Message& msg);
};
class MrcpRtpHandler : public MessageHandler
{
public:
MrcpRtpHandler() : MessageHandler("chan.rtp",150) { }
virtual bool received(Message& msg);
};
class MrcpModule : public Module
{
public:
MrcpModule();
virtual ~MrcpModule();
virtual void initialize();
virtual bool received(Message& msg, int id);
virtual void statusParams(String& str);
private:
bool m_first;
};
static Mutex s_mutex;
static ObjList s_conns;
static int s_count = 0;
static int s_total = 0;
static MrcpModule plugin;
MrcpConsumer::MrcpConsumer(const String& id, const char* target, const char* format)
: DataConsumer(format),
m_source(0), m_chan(0), m_id(id)
{
s_mutex.lock();
s_count++;
s_mutex.unlock();
if (target) {
m_target = target;
m_target >> "mrcp/";
m_target = "sip/" + m_target;
}
Debug(&plugin,DebugAll,"MrcpConsumer::MrcpConsumer('%s','%s','%s') [%p]",
id.c_str(),target,format,this);
}
MrcpConsumer::~MrcpConsumer()
{
Debug(&plugin,DebugAll,"MrcpConsumer::~MrcpConsumer '%s' [%p]",
m_id.c_str(),this);
s_mutex.lock();
s_count--;
s_mutex.unlock();
cleanup();
}
bool MrcpConsumer::init(Message& msg)
{
if (m_chan)
return false;
String id("mrcp/");
s_mutex.lock();
s_total++;
id << s_total;
s_mutex.unlock();
m_source = new DataSource(m_format);
m_chan = new MrcpConnection(id,m_id);
m_chan->setSource(m_source);
if (m_chan->init(msg,m_target))
return true;
Debug(&plugin,DebugWarn,"Failed to start connection '%s' for '%s' [%p]",
id.c_str(),m_id.c_str(),this);
cleanup();
return false;
}
void MrcpConsumer::cleanup()
{
Debug(&plugin,DebugAll,"MrcpConsumer::cleanup() '%s' s=%p c=%p [%p]",
m_id.c_str(),m_source,m_chan,this);
if (m_source) {
m_source->deref();
m_source = 0;
}
if (m_chan) {
m_chan->disconnect();
m_chan->deref();
m_chan = 0;
}
}
bool MrcpConsumer::setFormat(const DataFormat& format)
{
Debug(&plugin,DebugAll,"MrcpConsumer::setFormat('%s') '%s' s=%p c=%p [%p]",
format.c_str(),m_id.c_str(),m_source,m_chan,this);
return m_source && m_source->setFormat(format);
}
void MrcpConsumer::Consume(const DataBlock& data, unsigned long timeDelta)
{
if (m_source)
m_source->Forward(data,timeDelta);
}
MrcpConnection::~MrcpConnection()
{
s_mutex.lock();
s_conns.remove(this,false);
s_mutex.unlock();
if (m_socket) {
m_socket->terminate();
delete m_socket;
m_socket = 0;
}
}
bool MrcpConnection::init(Message& msg, const char* target)
{
if (!target)
return false;
s_mutex.lock();
if (!s_conns.find(this))
s_conns.append(this);
s_mutex.unlock();
Message m("call.execute");
m.addParam("id",id());
m.addParam("callto",target);
m.copyParam(msg,"caller");
m.copyParam(msg,"called");
m.addParam("media",String::boolText(true));
m.addParam("media_application",String::boolText(true));
m.addParam("transport_application","TCP/MRCPv2");
m.addParam("formats_application","1"); // defined by the standard
m.userData(this);
return Engine::dispatch(m);
}
bool MrcpConnection::answered(Message& msg)
{
Debug(&plugin,DebugAll,"MrcpConnection::answered() '%s' [%p]",
id().c_str(),this);
int port = msg.getIntValue("rtp_port_application");
if (port > 0) {
return true;
}
return false;
}
// Attach a tone detector on "chan.attach" as consumer or sniffer
bool AttachHandler::received(Message& msg)
{
String cons(msg.getValue("consumer"));
if (!cons.startsWith("mrcp/"))
cons.clear();
String snif(msg.getValue("sniffer"));
if (!snif.startsWith("mrcp/"))
snif.clear();
if (cons.null() && snif.null())
return false;
CallEndpoint* ch = static_cast<CallEndpoint *>(msg.userObject("CallEndpoint"));
if (ch) {
if (cons) {
MrcpConsumer* c = new MrcpConsumer(ch->id(),cons,msg.getValue("format","slin"));
if (c->init(msg))
ch->setConsumer(c);
c->deref();
}
if (snif) {
DataEndpoint* de = ch->setEndpoint();
// try to reinit sniffer if one already exists
MrcpConsumer* c = static_cast<MrcpConsumer*>(de->getSniffer(snif));
if (c)
c->init(msg);
else {
c = new MrcpConsumer(ch->id(),snif,msg.getValue("format","slin"));
if (c->init(msg))
de->addSniffer(c);
c->deref();
}
}
return msg.getBoolValue("single");
}
else
Debug(&plugin,DebugWarn,"Attach request with no call endpoint!");
return false;
}
// Attach a tone detector on "chan.record" - needs just a CallEndpoint
bool RecordHandler::received(Message& msg)
{
String src(msg.getValue("call"));
String id(msg.getValue("id"));
if (!src.startsWith("mrcp/"))
return false;
DataEndpoint* de = static_cast<DataEndpoint *>(msg.userObject("DataEndpoint"));
CallEndpoint* ch = static_cast<CallEndpoint *>(msg.userObject("CallEndpoint"));
if (ch) {
id = ch->id();
if (!de)
de = ch->setEndpoint();
}
if (de) {
MrcpConsumer* c = new MrcpConsumer(id,src,msg.getValue("format","slin"));
if (c->init(msg))
de->setCallRecord(c);
c->deref();
return true;
}
else
Debug(&plugin,DebugWarn,"Record request with no call endpoint!");
return false;
}
bool MrcpRtpHandler::received(Message& msg)
{
String trans = msg.getValue("transport");
trans.toUpper();
bool tls = false;
if (trans == "TCP/TLS/MRCPV2")
tls = true;
else if (trans != "TCP/MRCPV2")
return false;
Debug(&plugin,DebugAll,"RTP message received, TLS: %s",String::boolText(tls));
return true;
}
MrcpModule::MrcpModule()
: Module("mrcp","misc"), m_first(true)
{
Output("Loaded module MRCP");
}
MrcpModule::~MrcpModule()
{
Output("Unloading module MRCP");
}
bool MrcpModule::received(Message& msg, int id)
{
if (id == Answered) {
const String* cid = msg.getParam("targetid");
if (!cid)
cid = msg.getParam("peerid");
if (!(cid && cid->startsWith("mrcp/")))
return false;
s_mutex.lock();
RefPointer<MrcpConnection> conn = static_cast<MrcpConnection*>(s_conns[*cid]);
s_mutex.unlock();
return conn && conn->answered(msg);
}
return Module::received(msg,id);
}
void MrcpModule::statusParams(String& str)
{
str.append("count=",",") << s_count;
str.append("total=",",") << s_total;
}
void MrcpModule::initialize()
{
Output("Initializing module MrcpSpeech");
setup();
if (m_first) {
m_first = false;
Engine::install(new AttachHandler);
Engine::install(new RecordHandler);
Engine::install(new MrcpRtpHandler);
installRelay(Answered);
}
}
}; // anonymous namespace
/* vi: set ts=8 sw=4 sts=4 noet: */

View File

@ -1,5 +1,5 @@
Makefile
YateLocal.mak
YateLocal*
core*
*.o
*.a

View File

@ -6,11 +6,11 @@ SED := sed
DEFS :=
INCLUDES := -I@top_srcdir@
CFLAGS := -O0 @MODULE_CPPFLAGS@ @INLINE_FLAGS@
LDFLAGS:= -L.. -lyate
LDFLAGS:= -L../.. -lyate
MODFLAGS:= @MODULE_LDFLAGS@
MODSTRIP:= @MODULE_SYMBOLS@
MKDEPS := ../config.status
MKDEPS := @top_builddir@/config.status
PROGS = randcall.yate
LIBS =
OBJS =
@ -43,7 +43,7 @@ clean:
$(COMPILE) -c $<
Makefile: @srcdir@/Makefile.in $(MKDEPS)
cd .. && ./config.status
cd @top_builddir@ && ./config.status
lib%.so: %.o
$(LINK) -shared -o $@ $^

View File

@ -1,698 +0,0 @@
/**
* wpchan.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Wanpipe PRI cards telephony driver
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2004-2006 Null Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <modules/libypri.h>
#ifdef _WINDOWS
#error This module is not for Windows
#else
extern "C" {
#define INVALID_HANDLE_VALUE (-1)
#define __LINUX__
#include <linux/if_wanpipe.h>
#include <linux/if.h>
#include <linux/wanpipe_defines.h>
#include <linux/wanpipe_cfg.h>
#include <linux/wanpipe.h>
#include <linux/sdla_aft_te1.h>
#ifdef HAVE_WANPIPE_HWEC
#include <wanec_iface.h>
#endif
};
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
using namespace TelEngine;
namespace { // anonymous
class WpChan;
class WpData;
class WpDriver;
class WpSpan : public PriSpan, public Thread
{
friend class WpData;
friend class WpDriver;
public:
virtual ~WpSpan();
virtual void run();
inline int overRead() const
{ return m_overRead; }
unsigned long bChanMap() const;
private:
WpSpan(struct pri *_pri, PriDriver* driver, int span, int first, int chans, int dchan, Configuration& cfg, const String& sect, HANDLE fd);
HANDLE m_fd;
WpData *m_data;
int m_overRead;
};
class WpSource : public PriSource
{
public:
WpSource(WpChan *owner, const char* format, unsigned int bufsize);
~WpSource();
void put(unsigned char val);
private:
unsigned int m_bufpos;
};
class WpConsumer : public PriConsumer, public Fifo
{
public:
WpConsumer(WpChan *owner, const char* format, unsigned int bufsize);
~WpConsumer();
virtual void Consume(const DataBlock &data, unsigned long tStamp);
private:
DataErrors m_overruns;
};
class WpChan : public PriChan
{
friend class WpSource;
friend class WpConsumer;
friend class WpData;
public:
WpChan(const PriSpan *parent, int chan, unsigned int bufsize);
virtual ~WpChan();
virtual bool openData(const char* format, int echoTaps);
private:
WpSource* m_wp_s;
WpConsumer* m_wp_c;
};
class WpData : public Thread
{
public:
WpData(WpSpan* span, const char* card, const char* device, Configuration& cfg, const String& sect);
~WpData();
virtual void run();
private:
bool decodeEvent(const api_rx_hdr_t* ev);
WpSpan* m_span;
HANDLE m_fd;
unsigned char* m_buffer;
WpChan **m_chans;
int m_samples;
bool m_swap;
unsigned char m_rdError;
unsigned char m_wrError;
unsigned char m_oobError;
};
class WpDriver : public PriDriver
{
friend class PriSpan;
friend class WpHandler;
public:
WpDriver();
virtual ~WpDriver();
virtual void initialize();
virtual PriSpan* createSpan(PriDriver* driver, int span, int first, int chans, Configuration& cfg, const String& sect);
virtual PriChan* createChan(const PriSpan* span, int chan, unsigned int bufsize);
};
INIT_PLUGIN(WpDriver);
#define WP_HEADER 16
#define MAX_DATA_ERRORS 250
static int wp_recv(HANDLE fd, void *buf, int buflen, int flags = 0)
{
int r = ::recv(fd,buf,buflen,flags);
return r;
}
static int wp_send(HANDLE fd, void *buf, int buflen, int flags = 0)
{
int w = ::send(fd,buf,buflen,flags);
return w;
}
static int wp_read(struct pri *pri, void *buf, int buflen)
{
buflen -= 2;
int sz = buflen+WP_HEADER;
char *tmp = (char*)::calloc(sz,1);
XDebug("wp_read",DebugAll,"pre buf=%p len=%d tmp=%p sz=%d",buf,buflen,tmp,sz);
int r = wp_recv((HANDLE)::pri_fd(pri),tmp,sz,MSG_NOSIGNAL);
XDebug("wp_read",DebugAll,"post r=%d",r);
if (r > 0) {
r -= WP_HEADER;
if ((r > 0) && (r <= buflen)) {
WpSpan* span = (WpSpan*)::pri_get_userdata(pri);
if (span)
r -= span->overRead();
DDebug("wp_read",DebugAll,"Transferring %d for %p",r,pri);
::memcpy(buf,tmp+WP_HEADER,r);
r += 2;
}
}
::free(tmp);
return r;
}
static int wp_write(struct pri *pri, void *buf, int buflen)
{
buflen -= 2;
int sz = buflen+WP_HEADER;
char *tmp = (char*)::calloc(sz,1);
::memcpy(tmp+WP_HEADER,buf,buflen);
XDebug("wp_write",DebugAll,"pre buf=%p len=%d tmp=%p sz=%d",buf,buflen,tmp,sz);
int w = wp_send((HANDLE)::pri_fd(pri),tmp,sz,0);
XDebug("wp_write",DebugAll,"post w=%d",w);
if (w > 0) {
w -= WP_HEADER;
DDebug("wp_write",DebugAll,"Transferred %d for %p",w,pri);
w += 2;
}
::free(tmp);
return w;
}
static bool wp_select(HANDLE fd,int samp,bool* errp = 0)
{
fd_set rdfds;
fd_set errfds;
FD_ZERO(&rdfds);
FD_SET(fd,&rdfds);
FD_ZERO(&errfds);
FD_SET(fd,&errfds);
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = samp*125;
int sel = ::select(fd+1, &rdfds, NULL, errp ? &errfds : NULL, &tv);
if (sel < 0)
Debug(DebugWarn,"Wanpipe select failed on %d: error %d: %s",
fd,errno,::strerror(errno));
if (errp)
*errp = FD_ISSET(fd,&errfds);
return FD_ISSET(fd,&rdfds);
}
#ifdef HAVE_WANPIPE_HWEC
static bool wp_hwec_ioctl(wan_ec_api_t* ecapi)
{
if (!ecapi)
return false;
int fd = -1;
for (int i = 0; i < 5; i++) {
fd = open(WANEC_DEV_DIR WANEC_DEV_NAME, O_RDONLY);
if (fd >= 0)
break;
Thread::msleep(200);
}
if (fd < 0)
return false;
ecapi->err = WAN_EC_API_RC_OK;
if (::ioctl(fd,ecapi->cmd,ecapi)) {
// preserve errno while we close the handle
int err = errno;
::close(fd);
errno = err;
return false;
}
::close(fd);
return true;
}
// Configure the DTMF detection of the DSP
static bool wp_dtmf_config(const char* card, const char* device, bool enable, unsigned long chanmap)
{
wan_ec_api_t ecapi;
::memset(&ecapi,0,sizeof(ecapi));
::strncpy((char*)ecapi.devname,card,sizeof(ecapi.devname));
//::strncpy((char*)ecapi.if_name,device,sizeof(ecapi.if_name));
ecapi.channel_map = chanmap;
if (enable) {
ecapi.cmd = WAN_EC_CMD_DTMF_ENABLE;
ecapi.verbose = WAN_EC_VERBOSE_EXTRA1;
// event on start of tone, before echo canceller
ecapi.u_dtmf_config.type = WAN_EC_TONE_PRESENT;
ecapi.u_dtmf_config.port = WAN_EC_CHANNEL_PORT_SOUT;
}
else
ecapi.cmd = WAN_EC_CMD_DTMF_DISABLE;
return wp_hwec_ioctl(&ecapi);
}
#else
static bool wp_dtmf_config(const char* card, const char* device, bool enable, unsigned long chanmap)
{
return false;
}
#endif // HAVE_WANPIPE_HWEC
// Enable/disable DTMF events
static bool wp_dtmfs(HANDLE fd, bool detect)
{
#ifdef HAVE_WANPIPE_HWEC
api_tx_hdr_t api_tx_hdr;
::memset(&api_tx_hdr,0,sizeof(api_tx_hdr_t));
api_tx_hdr.u.event.type = WP_API_EVENT_DTMF;
api_tx_hdr.u.event.mode = detect ? WP_API_EVENT_ENABLE : WP_API_EVENT_DISABLE;
return (::ioctl(fd,SIOC_WANPIPE_API,&api_tx_hdr) >= 0);
#else
// pretend enabling fails, disabling succeeds
if (!detect)
return true;
errno = ENOSYS;
return false;
#endif
}
void wp_close(HANDLE fd)
{
if (fd == INVALID_HANDLE_VALUE)
return;
::close(fd);
}
static HANDLE wp_open(const char* card, const char* device)
{
DDebug(DebugAll,"wp_open('%s','%s')",card,device);
if (null(card) || null(device))
return INVALID_HANDLE_VALUE;
HANDLE fd = ::socket(AF_WANPIPE, SOCK_RAW, 0);
if (fd == INVALID_HANDLE_VALUE) {
Debug(DebugGoOn,"Wanpipe failed to create socket: error %d: %s",
errno,::strerror(errno));
return fd;
}
// Bind to the card/interface
struct wan_sockaddr_ll sa;
memset(&sa,0,sizeof(struct wan_sockaddr_ll));
::strncpy((char*)sa.sll_device,device,sizeof(sa.sll_device));
::strncpy((char*)sa.sll_card,card,sizeof(sa.sll_card));
sa.sll_protocol = htons(PVC_PROT);
sa.sll_family=AF_WANPIPE;
if (::bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
Debug(DebugGoOn,"Wanpipe failed to bind %d: error %d: %s",
fd,errno,::strerror(errno));
wp_close(fd);
fd = INVALID_HANDLE_VALUE;
}
return fd;
}
static struct pri* wp_create(const char* card, const char* device, int nettype, int swtype)
{
DDebug(DebugAll,"wp_create('%s','%s',%d,%d)",card,device,nettype,swtype);
HANDLE fd = wp_open(card,device);
if (fd == INVALID_HANDLE_VALUE)
return 0;
struct pri* p = ::pri_new_cb((int)fd, nettype, swtype, wp_read, wp_write, 0);
if (!p)
wp_close(fd);
return p;
}
WpSpan::WpSpan(struct pri *_pri, PriDriver* driver, int span, int first, int chans, int dchan, Configuration& cfg, const String& sect, HANDLE fd)
: PriSpan(_pri,driver,span,first,chans,dchan,cfg,sect), Thread("WpSpan"),
m_fd(fd), m_data(0), m_overRead(0)
{
Debug(&__plugin,DebugAll,"WpSpan::WpSpan() [%p]",this);
m_overRead = cfg.getIntValue(sect,"overread",cfg.getIntValue("general","overread",0));
}
WpSpan::~WpSpan()
{
Debug(&__plugin,DebugAll,"WpSpan::~WpSpan() [%p]",this);
m_ok = false;
delete m_data;
wp_close(m_fd);
m_fd = INVALID_HANDLE_VALUE;
}
void WpSpan::run()
{
Debug(&__plugin,DebugAll,"WpSpan::run() [%p]",this);
for (;;) {
bool rd = wp_select(m_fd,5); // 5 bytes per smallest q921 frame
Thread::check();
runEvent(!rd);
}
}
unsigned long WpSpan::bChanMap() const
{
unsigned long res = 0;
for (int i = 1; i <= 31; i++)
if (validChan(i))
res |= ((unsigned long)1 << i);
return res;
}
WpSource::WpSource(WpChan *owner, const char* format, unsigned int bufsize)
: PriSource(owner,format,bufsize),
m_bufpos(0)
{
Debug(m_owner,DebugAll,"WpSource::WpSource(%p) [%p]",owner,this);
static_cast<WpChan*>(m_owner)->m_wp_s = this;
}
WpSource::~WpSource()
{
Debug(m_owner,DebugAll,"WpSource::~WpSource() [%p]",this);
static_cast<WpChan*>(m_owner)->m_wp_s = 0;
}
void WpSource::put(unsigned char val)
{
((char*)m_buffer.data())[m_bufpos] = val;
if (++m_bufpos >= m_buffer.length()) {
m_bufpos = 0;
Forward(m_buffer);
}
}
WpConsumer::WpConsumer(WpChan *owner, const char* format, unsigned int bufsize)
: PriConsumer(owner,format,bufsize), Fifo(2*bufsize)
{
Debug(m_owner,DebugAll,"WpConsumer::WpConsumer(%p) [%p]",owner,this);
static_cast<WpChan*>(m_owner)->m_wp_c = this;
}
WpConsumer::~WpConsumer()
{
Debug(m_owner,DebugAll,"WpConsumer::~WpConsumer() [%p]",this);
static_cast<WpChan*>(m_owner)->m_wp_c = 0;
if (m_overruns.events())
Debug(m_owner,DebugMild,"Consumer had %u overruns (%lu bytes)",
m_overruns.events(),m_overruns.bytes());
}
void WpConsumer::Consume(const DataBlock &data, unsigned long tStamp)
{
unsigned int err = put((const unsigned char*)data.data(),data.length());
if (err)
m_overruns.update(err);
}
static Thread::Priority cfgPriority(Configuration& cfg, const String& sect)
{
String tmp(cfg.getValue(sect,"thread"));
if (tmp.null())
tmp = cfg.getValue("general","thread");
return Thread::priority(tmp);
}
WpData::WpData(WpSpan* span, const char* card, const char* device, Configuration& cfg, const String& sect)
: Thread("WpData",cfgPriority(cfg,sect)), m_span(span), m_fd(INVALID_HANDLE_VALUE),
m_buffer(0), m_chans(0), m_samples(50), m_swap(true),
m_rdError(0), m_wrError(0), m_oobError(0)
{
Debug(&__plugin,DebugAll,"WpData::WpData(%p,'%s','%s') [%p]",
span,card,device,this);
HANDLE fd = wp_open(card,device);
if (fd != INVALID_HANDLE_VALUE) {
m_fd = fd;
m_span->m_data = this;
bool detect = m_span->detect();
if (!(wp_dtmf_config(card,device,detect,m_span->bChanMap()) && wp_dtmfs(fd,detect))) {
int err = errno;
Debug(&__plugin,detect ? DebugWarn : DebugMild,
"Failed to %s DTMF detection on span %d: %s (%d)",
detect ? "enable" : "disable",m_span->span(),strerror(err),err);
}
}
if (m_span->chans() == 24)
// for T1 we typically have 23 B channels so we adjust the number
// of samples to get multiple of 32 bit and also reduce overhead
m_samples = 64;
m_samples = cfg.getIntValue("general","samples",m_samples);
m_samples = cfg.getIntValue(sect,"samples",m_samples);
m_swap = cfg.getBoolValue("general","bitswap",m_swap);
m_swap = cfg.getBoolValue(sect,"bitswap",m_swap);
}
WpData::~WpData()
{
Debug(&__plugin,DebugAll,"WpData::~WpData() [%p]",this);
m_span->m_data = 0;
wp_close(m_fd);
m_fd = INVALID_HANDLE_VALUE;
if (m_buffer)
::free(m_buffer);
if (m_chans)
delete[] m_chans;
}
void WpData::run()
{
Debug(&__plugin,DebugAll,"WpData::run() [%p]",this);
int bchans = m_span->bchans();
int buflen = m_samples*bchans;
int sz = buflen+WP_HEADER;
m_buffer = (unsigned char*)::malloc(sz);
// Build a compacted list of allocated B channels
m_chans = new WpChan* [bchans];
int b = 0;
for (int n = 0; n < bchans; n++) {
while (!m_span->m_chans[b])
b++;
m_chans[n] = static_cast<WpChan*>(m_span->m_chans[b++]);
DDebug(&__plugin,DebugInfo,"wpdata ch[%d]=%d (%p)",n,m_chans[n]->chan(),m_chans[n]);
}
while (m_span && (m_fd >= 0)) {
Thread::check();
api_rx_hdr_t* ev = (api_rx_hdr_t*)m_buffer;
bool oob = false;
bool rd = wp_select(m_fd,m_samples,&oob);
if (oob) {
XDebug("wpdata_recv_oob",DebugAll,"pre buf=%p len=%d sz=%d",m_buffer,buflen,sz);
int r = wp_recv(m_fd,m_buffer,sz,MSG_OOB);
XDebug("wpdata_recv_oob",DebugAll,"post r=%d",r);
if (r < 0) {
if (!m_oobError) {
#ifdef SIOC_WANPIPE_SOCK_STATE
r = ::ioctl(m_fd,SIOC_WANPIPE_SOCK_STATE,0);
Debug(&__plugin,DebugNote,"Socket for span %d is %s",
m_span->span(),
(r == 0) ? "connected" : ((r == 1) ? "disconnected" : "connecting"));
#else
Debug(&__plugin,DebugNote,"Failed to read OOB on span %d",
m_span->span());
#endif
}
if (m_oobError < MAX_DATA_ERRORS)
m_oobError++;
}
else {
m_oobError = 0;
if (r >= WP_HEADER)
decodeEvent(ev);
}
}
if (rd) {
ev->error_flag = 0;
XDebug("wpdata_recv",DebugAll,"pre buf=%p len=%d sz=%d",m_buffer,buflen,sz);
int r = wp_recv(m_fd,m_buffer,sz,0/*MSG_NOSIGNAL*/);
XDebug("wpdata_recv",DebugAll,"post r=%d",r);
r -= WP_HEADER;
if (ev->error_flag) {
if (!m_rdError)
Debug(&__plugin,DebugWarn,"Read data error 0x%02X on span %d [%p]",
ev->error_flag,m_span->span(),this);
if (m_rdError < MAX_DATA_ERRORS)
m_rdError++;
}
else
m_rdError = 0;
// check if a DTMF was received with the data
if (r >= 0)
decodeEvent(ev);
// We should have read N bytes for each B channel
if ((r > 0) && ((r % bchans) == 0)) {
r /= bchans;
const unsigned char* dat = m_buffer + WP_HEADER;
m_span->lock();
for (int n = r; n > 0; n--)
for (b = 0; b < bchans; b++) {
WpSource *s = m_chans[b]->m_wp_s;
if (s)
s->put(m_swap ? PriDriver::bitswap(*dat) : *dat);
dat++;
}
m_span->unlock();
}
int wr = m_samples;
::memset(m_buffer,0,WP_HEADER);
unsigned char* dat = m_buffer + WP_HEADER;
m_span->lock();
for (int n = wr; n > 0; n--) {
for (b = 0; b < bchans; b++) {
WpConsumer *c = m_chans[b]->m_wp_c;
unsigned char d = c ? c->get() : 0xff;
*dat++ = m_swap ? PriDriver::bitswap(d) : d;
}
}
m_span->unlock();
wr = (wr * bchans) + WP_HEADER;
XDebug("wpdata_send",DebugAll,"pre buf=%p len=%d sz=%d",m_buffer,wr,sz);
int w = wp_send(m_fd,m_buffer,wr,MSG_DONTWAIT);
XDebug("wpdata_send",DebugAll,"post w=%d",w);
if (w != wr) {
if (!m_wrError)
Debug(&__plugin,DebugWarn,"Wrote %d data bytes instead of %d on span %d [%p]",
w,wr,m_span->span(),this);
if (m_wrError < MAX_DATA_ERRORS)
m_wrError++;
}
else
m_wrError = 0;
}
}
}
bool WpData::decodeEvent(const api_rx_hdr_t* ev)
{
#ifdef WAN_EC_TONE_PRESENT
switch (ev->event_type) {
case WP_API_EVENT_NONE:
return false;
case WP_API_EVENT_DTMF:
if (ev->hdr_u.wp_api_event.u_event.dtmf.type == WAN_EC_TONE_PRESENT) {
String tone((char)ev->hdr_u.wp_api_event.u_event.dtmf.digit);
tone.toUpper();
int chan = ev->hdr_u.wp_api_event.channel;
PriChan* c = m_span->getChan(chan);
if (c)
c->gotDigits(tone);
else
Debug(&__plugin,DebugMild,"Detected DTMF '%s' for invalid channel %d on span %d",
tone.c_str(),chan,m_span->span());
}
break;
default:
Debug(&__plugin,DebugMild,"Unhandled event %u on span %d",
ev->event_type,m_span->span());
break;
}
return true;
#else
return false;
#endif
}
WpChan::WpChan(const PriSpan *parent, int chan, unsigned int bufsize)
: PriChan(parent,chan,bufsize), m_wp_s(0), m_wp_c(0)
{
}
WpChan::~WpChan()
{
closeData();
}
bool WpChan::openData(const char* format, int echoTaps)
{
if (echoTaps)
Debug(DebugWarn,"Echo cancellation requested but not available in wanpipe");
m_span->lock();
setSource(new WpSource(this,format,m_bufsize));
getSource()->deref();
setConsumer(new WpConsumer(this,format,m_bufsize));
getConsumer()->deref();
m_span->unlock();
return true;
}
PriSpan* WpDriver::createSpan(PriDriver* driver, int span, int first, int chans, Configuration& cfg, const String& sect)
{
Debug(this,DebugAll,"WpDriver::createSpan(%p,%d,%d,%d) [%p]",driver,span,first,chans,this);
int netType = -1;
int swType = -1;
int dchan = -1;
netParams(cfg,sect,chans,&netType,&swType,&dchan);
String card;
card << "wanpipe" << span;
card = cfg.getValue(sect,"card",card);
String dev;
dev << "w" << span << "g1";
pri* p = wp_create(card,cfg.getValue(sect,"dgroup",dev),netType,swType);
if (!p)
return 0;
WpSpan *ps = new WpSpan(p,driver,span,first,chans,dchan,cfg,sect,(HANDLE)::pri_fd(p));
ps->startup();
dev.clear();
dev << "w" << span << "g2";
WpData* dat = new WpData(ps,card,cfg.getValue(sect,"bgroup",dev),cfg,sect);
dat->startup();
return ps;
}
PriChan* WpDriver::createChan(const PriSpan* span, int chan, unsigned int bufsize)
{
Debug(this,DebugAll,"WpDriver::createChan(%p,%d,%u) [%p]",span,chan,bufsize,this);
return new WpChan(span,chan,bufsize);
}
WpDriver::WpDriver()
: PriDriver("wp")
{
Output("Loaded module Wanpipe");
}
WpDriver::~WpDriver()
{
Output("Unloading module Wanpipe");
}
void WpDriver::initialize()
{
Output("Initializing module Wanpipe");
init("wpchan");
}
}; // anonymous namespace
#endif /* _WINDOWS */
/* vi: set ts=8 sw=4 sts=4 noet: */

View File

@ -1,646 +0,0 @@
/**
* wpchanw.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Wanpipe PRI cards telephony driver for Windows
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2004-2006 Null Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <modules/libypri.h>
#ifndef _WINDOWS
#error This module is only for Windows
#else
extern "C" {
#define MSG_NOSIGNAL 0
#define MSG_DONTWAIT 0
#include <winioctl.h>
#define IOCTL_WRITE 1
#define IOCTL_READ 2
#define IOCTL_MGMT 3
#define IoctlWriteCommand \
CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_WRITE, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)
#define IoctlReadCommand \
CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_READ, METHOD_IN_DIRECT, FILE_ANY_ACCESS)
};
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
using namespace TelEngine;
namespace { // anonymous
class WpChan;
class WpData;
class WpReader;
class WpWriter;
class WpDriver;
class WpSpan : public PriSpan, public Thread
{
friend class WpData;
friend class WpReader;
friend class WpWriter;
friend class WpDriver;
public:
virtual ~WpSpan();
virtual void run();
virtual void cleanup();
int dataRead(void *buf, int buflen);
int dataWrite(void *buf, int buflen);
private:
WpSpan(struct pri *_pri, PriDriver* driver, int span, int first, int chans, int dchan, Configuration& cfg, const String& sect);
WpData* m_data;
WpReader* m_reader;
WpWriter* m_writer;
DataBlock m_rdata;
ObjList m_wdata;
};
class WpSource : public PriSource
{
public:
WpSource(WpChan *owner, const char* format, unsigned int bufsize);
~WpSource();
void put(unsigned char val);
private:
unsigned int m_bufpos;
};
class WpConsumer : public PriConsumer, public Fifo
{
public:
WpConsumer(WpChan *owner, const char* format, unsigned int bufsize);
~WpConsumer();
virtual void Consume(const DataBlock &data, unsigned long tStamp);
private:
DataErrors m_overruns;
};
class WpChan : public PriChan
{
friend class WpSource;
friend class WpConsumer;
friend class WpData;
public:
WpChan(const PriSpan *parent, int chan, unsigned int bufsize);
virtual ~WpChan();
virtual bool openData(const char* format, int echoTaps);
private:
WpSource* m_wp_s;
WpConsumer* m_wp_c;
};
class WpData : public Thread
{
public:
WpData(WpSpan* span, const char* card, const char* device, Configuration& cfg, const String& sect);
~WpData();
virtual void run();
private:
WpSpan* m_span;
HANDLE m_fd;
WpChan **m_chans;
bool m_swap;
};
class WpReader : public Thread
{
public:
WpReader(WpSpan* span, const char* card, const char* device);
~WpReader();
virtual void run();
private:
WpSpan* m_span;
HANDLE m_fd;
};
class WpWriter : public Thread
{
public:
WpWriter(WpSpan* span, const char* card, const char* device);
~WpWriter();
virtual void run();
private:
WpSpan* m_span;
HANDLE m_fd;
};
class WpDriver : public PriDriver
{
friend class PriSpan;
friend class WpHandler;
public:
WpDriver();
virtual ~WpDriver();
virtual void initialize();
virtual bool received(Message &msg, int id);
virtual PriSpan* createSpan(PriDriver* driver, int span, int first, int chans, Configuration& cfg, const String& sect);
virtual PriChan* createChan(const PriSpan* span, int chan, unsigned int bufsize);
};
INIT_PLUGIN(WpDriver);
#define WP_HEADER 21
#define WP_BUFFER 8188 // maximum length of data = 8K - 4
static Thread::Priority cfgPriority(Configuration& cfg, const String& sect)
{
String tmp(cfg.getValue(sect,"thread"));
if (tmp.null())
tmp = cfg.getValue("general","thread");
return Thread::priority(tmp);
}
static void dump_buffer(const void* buf, int len)
{
String s;
const unsigned char* p = (const unsigned char*)buf;
for (int i=0; i < len; i++) {
char tmp[4];
sprintf(tmp," %02x",p[i]);
//Debug(DebugAll,"%d",i);
s += tmp;
}
Output("[%d@%p]%s",len,buf,s.c_str());
}
static int wp_recv(HANDLE fd, void *buf, int buflen, int flags = 0)
{
int r = 0;
if (!DeviceIoControl(fd,IoctlReadCommand,0,0,buf,buflen,(LPDWORD)&r,0)) {
r = 0;
Output("recv (%d,%p,%d) last err=%x",fd,buf,buflen,GetLastError());
}
return r;
}
static int wp_send(HANDLE fd, void *buf, int buflen, int flags = 0)
{
int w = 0;
if (!DeviceIoControl(fd,IoctlWriteCommand,buf,buflen,buf,buflen,(LPDWORD)&w,0)) {
w = 0;
Output("send (%d,%p,%d) last err=%x",fd,buf,buflen,GetLastError());
}
return w;
}
static int wp_read(struct pri *pri, void *buf, int buflen)
{
WpSpan* span = (WpSpan*)::pri_get_userdata(pri);
return span ? span->dataRead(buf,buflen) : 0;
}
int WpSpan::dataRead(void *buf, int buflen)
{
Lock mylock(this);
if (m_rdata.data() && buf && (int)m_rdata.length() <= buflen) {
buflen = m_rdata.length();
::memcpy(buf,m_rdata.data(),buflen);
m_rdata.clear();
DDebug(&__plugin,DebugAll,"WpSpan dequeued %d bytes block [%p]",buflen,this);
return buflen+2;
}
return 0;
}
static int wp_write(struct pri *pri, void *buf, int buflen)
{
WpSpan* span = (WpSpan*)::pri_get_userdata(pri);
return span ? span->dataWrite(buf,buflen) : 0;
}
int WpSpan::dataWrite(void *buf, int buflen)
{
Lock mylock(this);
if (buf && (buflen > 2) && (m_wdata.length() < 5)) {
buflen -= 2;
DataBlock* block = new DataBlock(buf,buflen);
m_wdata.append(block);
DDebug(&__plugin,DebugAll,"WpSpan queued %d bytes block, total blocks %d [%p]",block->length(),m_wdata.count(),this);
return buflen+2;
}
return 0;
}
void wp_close(HANDLE fd)
{
if (fd == INVALID_HANDLE_VALUE)
return;
::CloseHandle(fd);
}
static HANDLE wp_open(const char* card, const char* device)
{
DDebug(DebugAll,"wp_open('%s','%s')",card,device);
if (null(card) || null(device))
return INVALID_HANDLE_VALUE;
String devname("\\\\.\\");
devname << card << "_" << device;
HANDLE fd = ::CreateFile(
devname,
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,
0,
OPEN_EXISTING,
FILE_FLAG_NO_BUFFERING|FILE_FLAG_WRITE_THROUGH,
0);
if (fd == INVALID_HANDLE_VALUE) {
Debug(DebugGoOn,"Wanpipe failed to open device '%s': error %d: %s",
devname.c_str(),errno,::strerror(errno));
return fd;
}
return fd;
}
WpReader::WpReader(WpSpan* span, const char* card, const char* device)
: Thread("WpReader"), m_span(span), m_fd(INVALID_HANDLE_VALUE)
{
DDebug(&__plugin,DebugAll,"WpReader::WpReader(%p) [%p]",span,this);
m_fd = wp_open(card,device);
m_span->m_reader = this;
}
WpReader::~WpReader()
{
DDebug(&__plugin,DebugAll,"WpReader::~WpReader() [%p]",this);
if (m_span)
m_span->m_reader = 0;
HANDLE tmp = m_fd;
m_fd = INVALID_HANDLE_VALUE;
wp_close(tmp);
}
void WpReader::run()
{
while (m_span && m_span->m_reader && (m_fd != INVALID_HANDLE_VALUE)) {
Thread::msleep(1,true);
Lock mylock(m_span);
if (m_span->m_rdata.data())
continue;
mylock.drop();
unsigned char buf[WP_HEADER+WP_BUFFER];
int r = wp_recv(m_fd,buf,sizeof(buf)) - WP_HEADER;
XDebug(&__plugin,DebugAll,"WpReader read returned %d [%p]",r,this);
if (r <= 0)
continue;
Thread::check();
m_span->lock();
m_span->m_rdata.assign(buf+WP_HEADER,r);
DDebug(&__plugin,DebugAll,"WpReader queued %d bytes block [%p]",r,this);
m_span->unlock();
}
}
WpWriter::WpWriter(WpSpan* span, const char* card, const char* device)
: Thread("WpWriter"), m_span(span), m_fd(INVALID_HANDLE_VALUE)
{
DDebug(&__plugin,DebugAll,"WpWriter::WpWriter(%p) [%p]",span,this);
m_fd = wp_open(card,device);
m_span->m_writer = this;
}
WpWriter::~WpWriter()
{
DDebug(&__plugin,DebugAll,"WpWriter::~WpWriter() [%p]",this);
if (m_span)
m_span->m_writer = 0;
HANDLE tmp = m_fd;
m_fd = INVALID_HANDLE_VALUE;
wp_close(tmp);
}
void WpWriter::run()
{
while (m_span && m_span->m_writer && (m_fd != INVALID_HANDLE_VALUE)) {
Thread::msleep(1,true);
m_span->lock();
DataBlock *block = static_cast<DataBlock*>(m_span->m_wdata.remove(false));
m_span->unlock();
if (!block)
continue;
DDebug(&__plugin,DebugAll,"WpWriter dequeued %d bytes block [%p]",block->length(),this);
// this is really stupid - have to send a huge buffer, or else
// Error : Tx system buffer length not equal sizeof(TX_DATA_STRUCT)!
unsigned char buf[WP_HEADER+WP_BUFFER];
int len = block->length();
::memcpy(buf+WP_HEADER,block->data(),len);
block->destruct();
buf[0] = 11;
buf[1] = len & 0xff;
buf[2] = len >> 8;
wp_send(m_fd,buf,sizeof(buf));
}
}
WpSpan::WpSpan(struct pri *_pri, PriDriver* driver, int span, int first, int chans, int dchan, Configuration& cfg, const String& sect)
: PriSpan(_pri,driver,span,first,chans,dchan,cfg,sect), Thread("WpSpan"),
m_data(0), m_reader(0), m_writer(0)
{
Debug(&__plugin,DebugAll,"WpSpan::WpSpan() [%p]",this);
}
WpSpan::~WpSpan()
{
Debug(&__plugin,DebugAll,"WpSpan::~WpSpan() [%p]",this);
m_ok = false;
}
void WpSpan::cleanup()
{
Debug(&__plugin,DebugAll,"WpSpan::cleanup() [%p]",this);
m_ok = false;
if (m_data)
m_data->cancel();
if (m_reader)
m_reader->cancel();
if (m_writer)
m_writer->cancel();
Debug(&__plugin,DebugAll,"WpSpan waiting for cleanups [%p]",this);
Thread::msleep(20);
while (m_data || m_reader || m_writer)
Thread::msleep(1);
Debug(&__plugin,DebugAll,"WpSpan cleanups complete [%p]",this);
}
void WpSpan::run()
{
Debug(&__plugin,DebugAll,"WpSpan::run() [%p]",this);
while (m_data && m_reader && m_writer) {
Thread::msleep(1,true);
lock();
runEvent(m_rdata.null());
unlock();
}
}
WpSource::WpSource(WpChan *owner, const char* format, unsigned int bufsize)
: PriSource(owner,format,bufsize),
m_bufpos(0)
{
Debug(m_owner,DebugAll,"WpSource::WpSource(%p) [%p]",owner,this);
static_cast<WpChan*>(m_owner)->m_wp_s = this;
}
WpSource::~WpSource()
{
Debug(m_owner,DebugAll,"WpSource::~WpSource() [%p]",this);
static_cast<WpChan*>(m_owner)->m_wp_s = 0;
}
void WpSource::put(unsigned char val)
{
((char*)m_buffer.data())[m_bufpos] = val;
if (++m_bufpos >= m_buffer.length()) {
m_bufpos = 0;
Forward(m_buffer);
}
}
WpConsumer::WpConsumer(WpChan *owner, const char* format, unsigned int bufsize)
: PriConsumer(owner,format,bufsize), Fifo(2*bufsize)
{
Debug(m_owner,DebugAll,"WpConsumer::WpConsumer(%p) [%p]",owner,this);
static_cast<WpChan*>(m_owner)->m_wp_c = this;
}
WpConsumer::~WpConsumer()
{
Debug(m_owner,DebugAll,"WpConsumer::~WpConsumer() [%p]",this);
static_cast<WpChan*>(m_owner)->m_wp_c = 0;
if (m_overruns.events())
Debug(m_owner,DebugMild,"Consumer had %u overruns (%lu bytes)",
m_overruns.events(),m_overruns.bytes());
}
void WpConsumer::Consume(const DataBlock &data, unsigned long tStamp)
{
unsigned int err = put((const unsigned char*)data.data(),data.length());
if (err)
m_overruns.update(err);
}
WpData::WpData(WpSpan* span, const char* card, const char* device, Configuration& cfg, const String& sect)
: Thread("WpData",cfgPriority(cfg,sect)), m_span(span), m_fd(INVALID_HANDLE_VALUE), m_chans(0), m_swap(true)
{
DDebug(&__plugin,DebugAll,"WpData::WpData(%p,'%s','%s') [%p]",span,card,device,this);
HANDLE fd = wp_open(card,device);
if (fd != INVALID_HANDLE_VALUE) {
m_fd = fd;
m_span->m_data = this;
}
m_swap = cfg.getBoolValue("general","bitswap",m_swap);
m_swap = cfg.getBoolValue(sect,"bitswap",m_swap);
}
WpData::~WpData()
{
DDebug(&__plugin,DebugAll,"WpData::~WpData() [%p]",this);
if (m_span)
m_span->m_data = 0;
wp_close(m_fd);
m_fd = INVALID_HANDLE_VALUE;
if (m_chans)
delete[] m_chans;
}
void WpData::run()
{
DDebug(&__plugin,DebugAll,"WpData::run() [%p]",this);
unsigned char buffer[WP_HEADER+WP_BUFFER];
int bchans = m_span->bchans();
// Build a compacted list of allocated B channels
m_chans = new WpChan* [bchans];
int b = 0;
for (int n = 0; n < bchans; n++) {
while (!m_span->m_chans[b])
b++;
m_chans[n] = static_cast<WpChan*>(m_span->m_chans[b++]);
DDebug(&__plugin,DebugInfo,"wpdata ch[%d]=%d (%p)",n,m_chans[n]->chan(),m_chans[n]);
}
int rok = 0, rerr = 0;
int wok = 0, werr = 0;
while (m_span && m_span->m_data && (m_fd != INVALID_HANDLE_VALUE)) {
Thread::check();
int samp = 0;
int r = wp_recv(m_fd,buffer,sizeof(buffer),0/*MSG_NOSIGNAL*/);
XDebug(&__plugin,DebugAll,"WpData recv r=%d",r);
r -= WP_HEADER;
// We should have read N bytes for each B channel
if (r > 0) {
samp = r / bchans;
if ((r % bchans) == 0) {
const unsigned char* dat = buffer + WP_HEADER;
m_span->lock();
int p1 = -1;
int p2 = -1;
for (int n = samp; n > 0; n--) {
for (b = 0; b < bchans; b++) {
WpSource *s = m_chans[b]->m_wp_s;
if (s)
s->put(m_swap ? PriDriver::bitswap(*dat) : *dat);
dat++;
}
}
m_span->unlock();
++rok;
}
else
Debug(DebugWarn,"WpData read %d (ok/bad %d/%d)",r,rok,++rerr);
}
if (samp) {
::memset(buffer,0,WP_HEADER);
unsigned char* dat = buffer + WP_HEADER;
m_span->lock();
for (int n = samp; n > 0; n--) {
for (b = 0; b < bchans; b++) {
WpConsumer *c = m_chans[b]->m_wp_c;
unsigned char d = c ? c->get() : 0xff;
*dat++ = m_swap ? PriDriver::bitswap(d) : d;
}
}
m_span->unlock();
int w = samp * bchans;
dat = buffer;
buffer[0] = 11;
buffer[1] = w & 0xff;
buffer[2] = w >> 8;
w = wp_send(m_fd,buffer,sizeof(buffer),MSG_DONTWAIT);
if (w != sizeof(buffer))
Debug(DebugWarn,"WpData wrote %d (ok/bad %d/%d)",w,wok,++werr);
else
++wok;
XDebug(&__plugin,DebugAll,"WpData send w=%d",w);
}
}
}
WpChan::WpChan(const PriSpan *parent, int chan, unsigned int bufsize)
: PriChan(parent,chan,bufsize), m_wp_s(0), m_wp_c(0)
{
}
WpChan::~WpChan()
{
closeData();
}
bool WpChan::openData(const char* format, int echoTaps)
{
Debug(this,DebugAll,"WpChan::openData(%s,%d) [%p]",format,echoTaps,this);
if (echoTaps)
Debug(DebugWarn,"Echo cancellation requested but not available in wanpipe");
m_span->lock();
setSource(new WpSource(this,format,m_bufsize));
getSource()->deref();
setConsumer(new WpConsumer(this,format,m_bufsize));
getConsumer()->deref();
m_span->unlock();
return true;
}
PriSpan* WpDriver::createSpan(PriDriver* driver, int span, int first, int chans, Configuration& cfg, const String& sect)
{
Debug(this,DebugAll,"WpDriver::createSpan(%p,%d,%d,%d) [%p]",driver,span,first,chans,this);
int netType = -1;
int swType = -1;
int dchan = -1;
netParams(cfg,sect,chans,&netType,&swType,&dchan);
String card;
card << "WANPIPE" << span;
card = cfg.getValue(sect,"card",card);
String dev;
dev = cfg.getValue(sect,"dgroup","IF0");
pri* p = ::pri_new_cb((int)INVALID_HANDLE_VALUE, netType, swType, wp_read, wp_write, 0);
if (!p)
return 0;
WpSpan *ps = new WpSpan(p,driver,span,first,chans,dchan,cfg,sect);
WpWriter* wr = new WpWriter(ps,card,dev);
WpReader* rd = new WpReader(ps,card,dev);
dev = cfg.getValue(sect,"bgroup","IF1");
WpData* dat = new WpData(ps,card,dev,cfg,sect);
wr->startup();
rd->startup();
dat->startup();
ps->startup();
return ps;
}
PriChan* WpDriver::createChan(const PriSpan* span, int chan, unsigned int bufsize)
{
Debug(this,DebugAll,"WpDriver::createChan(%p,%d,%u) [%p]",span,chan,bufsize,this);
return new WpChan(span,chan,bufsize);
}
WpDriver::WpDriver()
: PriDriver("wp")
{
Output("Loaded module Wanpipe");
}
WpDriver::~WpDriver()
{
Output("Unloading module Wanpipe");
}
void WpDriver::initialize()
{
Output("Initializing module Wanpipe");
init("wpchan");
installRelay(Halt,110);
}
bool WpDriver::received(Message &msg, int id)
{
bool ok = PriDriver::received(msg,id);
if (id == Halt) {
Debug(this,DebugAll,"WpDriver clearing all spans [%p]",this);
lock();
const ObjList *l = &m_spans;
for (; l; l=l->next()) {
WpSpan *s = static_cast<WpSpan*>(l->get());
if (s)
s->cancel();
}
unlock();
Debug(this,DebugAll,"WpDriver waiting for spans to exit [%p]",this);
while (m_spans.get())
Thread::msleep(10);
}
return ok;
}
}; // anonymous namespace
#endif /* _WINDOWS */
/* vi: set ts=8 sw=4 sts=4 noet: */

View File

@ -1,515 +0,0 @@
/**
* zapchan.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Zapata telephony driver
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2004-2006 Null Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <modules/libypri.h>
#ifdef _WINDOWS
#error This module is not for Windows
#else
extern "C" {
#ifdef NEW_ZAPTEL_LOCATION
#define __LINUX__
#include <zaptel/zaptel.h>
#else
#include <linux/zaptel.h>
#endif
};
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#ifndef _WINDOWS
#include <sys/ioctl.h>
#include <fcntl.h>
#endif
#ifndef ZT_EVENT_DTMFDIGIT
#ifdef ZT_EVENT_DTMFDOWN
#define ZT_EVENT_DTMFDIGIT ZT_EVENT_DTMFDOWN
#else
#define ZT_EVENT_DTMFDIGIT 0
#endif
#endif
#ifndef ZT_EVENT_PULSEDIGIT
#define ZT_EVENT_PULSEDIGIT 0
#endif
using namespace TelEngine;
namespace { // anonymous
/* Zaptel formats */
static TokenDict dict_str2ztlaw[] = {
{ "slin", -1 },
{ "default", ZT_LAW_DEFAULT },
{ "mulaw", ZT_LAW_MULAW },
{ "alaw", ZT_LAW_ALAW },
{ 0, -2 }
};
class ZapChan;
class ZapSpan : public PriSpan, public Thread
{
friend class ZapDriver;
public:
virtual ~ZapSpan();
virtual void run();
inline int train() const
{ return m_train; }
private:
ZapSpan(struct pri *_pri, PriDriver* driver, int span, int first, int chans, int dchan, Configuration& cfg, const String& sect, int fd);
int m_fd;
int m_train;
};
class ZapSource : public PriSource, public Thread
{
public:
ZapSource(ZapChan *owner, const char* format, unsigned int bufsize);
~ZapSource();
virtual void run();
private:
DataBlock m_data;
};
class ZapConsumer : public PriConsumer
{
public:
ZapConsumer(ZapChan *owner, const char* format, unsigned int bufsize);
~ZapConsumer();
virtual void Consume(const DataBlock &data, unsigned long tStamp);
private:
unsigned int m_bufsize;
DataErrors m_overruns;
};
class ZapChan : public PriChan
{
friend class ZapSource;
friend class ZapConsumer;
public:
ZapChan(const PriSpan *parent, int chan, unsigned int bufsize);
virtual ~ZapChan();
virtual bool openData(const char* format, int echoTaps);
virtual void closeData();
inline int fd() const
{ return m_fd; }
inline int law() const
{ return m_law; }
protected:
virtual void dataChanged();
private:
int m_fd;
int m_law;
bool m_train;
};
class ZapDriver : public PriDriver
{
friend class PriSpan;
friend class ZapHandler;
public:
ZapDriver();
virtual ~ZapDriver();
virtual void initialize();
virtual PriSpan* createSpan(PriDriver* driver, int span, int first, int chans, Configuration& cfg, const String& sect);
virtual PriChan* createChan(const PriSpan* span, int chan, unsigned int bufsize);
};
INIT_PLUGIN(ZapDriver);
static int zt_get_event(int fd)
{
/* Avoid the silly zt_getevent which ignores a bunch of events */
int j = 0;
if (::ioctl(fd, ZT_GETEVENT, &j) == -1)
return -1;
return j;
}
static int zt_open_dchan(int channo, int bsize = 1024, int nbufs = 16)
{
DDebug(&__plugin,DebugInfo,"Opening zap d-channel %d with %d x %d buffers",channo,nbufs,bsize);
int fd = ::open("/dev/zap/channel", O_RDWR, 0600);
if (fd < 0) {
Debug(&__plugin,DebugGoOn,"Failed to open device: error %d: %s",errno,::strerror(errno));
return -1;
}
if (::ioctl(fd,ZT_SPECIFY,&channo) == -1) {
Debug(&__plugin,DebugGoOn,"Failed to specify chan %d: error %d: %s",channo,errno,::strerror(errno));
::close(fd);
return -1;
}
ZT_PARAMS par;
if (::ioctl(fd, ZT_GET_PARAMS, &par) == -1) {
Debug(&__plugin,DebugGoOn,"Failed to get params of chan %d: error %d: %s",channo,errno,::strerror(errno));
::close(fd);
return -1;
}
if (par.sigtype != ZT_SIG_HDLCFCS) {
Debug(&__plugin,DebugGoOn,"Channel %d is not in HDLC/FCS mode",channo);
::close(fd);
return -1;
}
ZT_BUFFERINFO bi;
bi.txbufpolicy = ZT_POLICY_IMMEDIATE;
bi.rxbufpolicy = ZT_POLICY_IMMEDIATE;
bi.numbufs = nbufs;
bi.bufsize = bsize;
if (::ioctl(fd, ZT_SET_BUFINFO, &bi) == -1)
Debug(&__plugin,DebugWarn,"Could not set buffering on %d: error %d: %s",channo,errno,::strerror(errno));
return fd;
}
static int zt_open_bchan(int channo, bool subchan, unsigned int blksize)
{
DDebug(&__plugin,DebugInfo,"Opening zap b-channel %d with block size=%d",channo,blksize);
int fd = ::open(subchan ? "/dev/zap/pseudo" : "/dev/zap/channel",O_RDWR|O_NONBLOCK);
if (fd < 0) {
Debug(&__plugin,DebugGoOn,"Failed to open device: error %d: %s",errno,::strerror(errno));
return -1;
}
if (channo) {
if (::ioctl(fd, subchan ? ZT_CHANNO : ZT_SPECIFY, &channo)) {
Debug(&__plugin,DebugGoOn,"Failed to specify chan %d: error %d: %s",channo,errno,::strerror(errno));
::close(fd);
return -1;
}
}
if (blksize) {
if (::ioctl(fd, ZT_SET_BLOCKSIZE, &blksize) == -1) {
Debug(&__plugin,DebugGoOn,"Failed to set block size %d: error %d: %s",blksize,errno,::strerror(errno));
::close(fd);
return -1;
}
}
return fd;
}
static bool zt_set_law(int fd, int law)
{
if (law < 0) {
int linear = 1;
if (::ioctl(fd, ZT_SETLINEAR, &linear) != -1)
return true;
}
else
if (::ioctl(fd, ZT_SETLAW, &law) != -1)
return true;
DDebug(&__plugin,DebugInfo,"Failed to set law %d: error %d: %s",law,errno,::strerror(errno));
return false;
}
static bool zt_echo_cancel(int fd, int taps)
{
int tmp = 1;
if (taps && (::ioctl(fd, ZT_AUDIOMODE, &tmp) < 0)) {
Debug(&__plugin,DebugNote,"Failed to set audio mode: error %d: %s",errno,::strerror(errno));
return false;
}
if (::ioctl(fd, ZT_ECHOCANCEL, &taps) < 0) {
Debug(&__plugin,DebugMild,"Failed to set %d echo cancellation taps: error %d: %s",taps,errno,::strerror(errno));
return false;
}
return true;
}
static bool zt_echo_train(int fd, int train = 400)
{
if ((train > 0) && (::ioctl(fd, ZT_ECHOTRAIN, &train) < 0)) {
Debug(&__plugin,DebugMild,"Failed to start echo trainig for %d ms: error %d: %s",train,errno,::strerror(errno));
return false;
}
return true;
}
static bool zt_dtmf_detect(int fd, bool detect)
{
#ifdef ZT_TONEDETECT
int tdetect = detect ? ZT_TONEDETECT_ON | ZT_TONEDETECT_MUTE : 0;
if (::ioctl(fd, ZT_TONEDETECT, &tdetect) >= 0)
return true;
#else
// pretend enabling fails, disabling succeeds
if (!detect)
return true;
errno = ENOSYS;
#endif
return false;
}
ZapSpan::ZapSpan(struct pri *_pri, PriDriver* driver, int span, int first, int chans, int dchan, Configuration& cfg, const String& sect, int fd)
: PriSpan(_pri,driver,span,first,chans,dchan,cfg,sect), Thread("ZapSpan"),
m_fd(fd), m_train(0)
{
Debug(m_driver,DebugAll,"ZapSpan::ZapSpan() [%p]",this);
m_train = cfg.getIntValue(sect,"echotrain",cfg.getIntValue("general","echotrain",400));
}
ZapSpan::~ZapSpan()
{
Debug(m_driver,DebugAll,"ZapSpan::~ZapSpan() [%p]",this);
m_ok = false;
::close(m_fd);
m_fd = -1;
}
void ZapSpan::run()
{
Debug(m_driver,DebugAll,"ZapSpan::run() [%p]",this);
fd_set rdfds;
fd_set errfds;
for (;;) {
FD_ZERO(&rdfds);
FD_SET(m_fd, &rdfds);
FD_ZERO(&errfds);
FD_SET(m_fd, &errfds);
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 100;
int sel = ::select(m_fd+1, &rdfds, NULL, &errfds, &tv);
Thread::check();
if (!sel)
runEvent(true);
else if (sel > 0) {
if (FD_ISSET(m_fd, &errfds)) {
int zev = zt_get_event(m_fd);
if (zev)
Debug(DebugInfo,"Zapata event %d on span %d",zev,span());
}
if (FD_ISSET(m_fd, &rdfds))
runEvent(false);
}
else if (errno != EINTR)
Debug("ZapSpan",DebugGoOn,"select() error %d: %s",
errno,::strerror(errno));
}
}
ZapSource::ZapSource(ZapChan *owner, const char* format, unsigned int bufsize)
: PriSource(owner,format,bufsize), Thread("ZapSource")
{
Debug(m_owner,DebugAll,"ZapSource::ZapSource(%p) [%p]",owner,this);
}
ZapSource::~ZapSource()
{
Debug(m_owner,DebugAll,"ZapSource::~ZapSource() [%p]",this);
}
void ZapSource::run()
{
int rd = 0;
for (;;) {
Thread::yield(true);
int fd = static_cast<ZapChan*>(m_owner)->fd();
if (fd != -1) {
rd = ::read(fd,m_buffer.data(),m_buffer.length());
XDebug(m_owner,DebugAll,"ZapSource read %d bytes [%p]",rd,this);
if (rd > 0)
Forward(m_buffer);
else if (rd < 0) {
if ((errno != EAGAIN) && (errno != EINTR)) {
int zev = zt_get_event(fd);
if (zev) {
Debug(m_owner,DebugInfo,"ZapSource event %d [%p]",zev,this);
// driver-decoded digit arrived
if (zev & (ZT_EVENT_DTMFDIGIT | ZT_EVENT_PULSEDIGIT)) {
char buf[2];
buf[0] = zev & 0xff;
buf[1] = '\0';
m_owner->gotDigits(buf);
}
}
else
break;
}
}
}
else
break;
}
Debug(m_owner,DebugWarn,"ZapSource at EOF (read %d) [%p]",rd,this);
// TODO: find a better way of dealing with this abnormal condition
for (;;)
Thread::yield(true);
}
ZapConsumer::ZapConsumer(ZapChan *owner, const char* format, unsigned int bufsize)
: PriConsumer(owner,format,bufsize), m_bufsize(bufsize)
{
Debug(m_owner,DebugAll,"ZapConsumer::ZapConsumer(%p) [%p]",owner,this);
}
ZapConsumer::~ZapConsumer()
{
Debug(m_owner,DebugAll,"ZapConsumer::~ZapConsumer() [%p]",this);
if (m_overruns.events())
Debug(m_owner,DebugMild,"Consumer had %u overruns (%lu bytes)",
m_overruns.events(),m_overruns.bytes());
}
void ZapConsumer::Consume(const DataBlock &data, unsigned long tStamp)
{
int fd = static_cast<ZapChan*>(m_owner)->fd();
XDebug(DebugAll,"ZapConsumer fd=%d datalen=%u",fd,data.length());
if ((fd != -1) && !data.null()) {
if (m_buffer.length()+data.length() <= m_bufsize*4)
m_buffer += data;
else {
m_overruns.update(data.length());
DDebug(m_owner,DebugAll,"ZapConsumer skipped %u bytes, buffer is full",data.length());
}
if (m_buffer.null())
return;
if (m_buffer.length() >= m_bufsize) {
int wr = ::write(fd,m_buffer.data(),m_bufsize);
if (wr < 0) {
if ((errno != EAGAIN) && (errno != EINTR))
Debug(DebugGoOn,"ZapConsumer write error %d: %s",
errno,::strerror(errno));
}
else {
if ((unsigned)wr != m_bufsize)
Debug(m_owner,DebugInfo,"ZapConsumer short write, %d of %u bytes",wr,m_bufsize);
m_buffer.cut(-wr);
}
}
}
}
ZapChan::ZapChan(const PriSpan *parent, int chan, unsigned int bufsize)
: PriChan(parent,chan,bufsize), m_fd(-1), m_law(-1), m_train(false)
{
}
ZapChan::~ZapChan()
{
closeData();
}
bool ZapChan::openData(const char* format, int echoTaps)
{
m_fd = zt_open_bchan(m_abschan,false,m_bufsize);
if (m_fd == -1)
return false;
int defLaw = ZT_LAW_ALAW;
if (m_span->chans() == 24)
defLaw = ZT_LAW_MULAW;
defLaw = lookup(format,dict_str2ztlaw,defLaw);
if (zt_set_law(m_fd,defLaw)) {
m_law = defLaw;
format = lookup(m_law,dict_str2ztlaw,"unknown");
Debug(this,DebugInfo,"Opened Zap channel %d, law is: %s",m_abschan,format);
}
if (zt_echo_cancel(m_fd,echoTaps) && echoTaps)
m_train = true;
if (!zt_dtmf_detect(m_fd,m_detect)) {
Debug(this,m_detect ? DebugFail : DebugMild,
"Failed to %s DTMF detection: %s (%d)",
m_detect ? "enable" : "disable",::strerror(errno),errno);
}
ZapSource* src = new ZapSource(this,format,m_bufsize);
setSource(src);
src->startup();
src->deref();
setConsumer(new ZapConsumer(this,format,m_bufsize));
getConsumer()->deref();
return true;
}
void ZapChan::dataChanged()
{
if (m_train && zt_echo_train(m_fd,static_cast<ZapSpan*>(span())->train()))
Debug(this,DebugCall,"Started echo canceller training [%p]",this);
}
void ZapChan::closeData()
{
PriChan::closeData();
m_train = false;
if (m_fd != -1) {
::close(m_fd);
m_fd = -1;
}
}
PriSpan* ZapDriver::createSpan(PriDriver* driver, int span, int first, int chans, Configuration& cfg, const String& sect)
{
Debug(this,DebugAll,"ZapDriver::createSpan(%p,%d,%d,%d) [%p]",driver,span,first,chans,this);
int netType = -1;
int swType = -1;
int dchan = -1;
netParams(cfg,sect,chans,&netType,&swType,&dchan);
if (dchan < 0)
return 0;
int fd = zt_open_dchan(dchan+first-1);
if (fd < 0)
return 0;
#if defined(PRI_NEW_SET_API) && defined(BRI_NETWORK_PTMP)
// Klaus-Peter Junghanns broke this one too
pri* p = ::pri_new(fd,netType,swType,span);
#else
pri* p = ::pri_new(fd,netType,swType);
#endif
if (!p)
return 0;
ZapSpan *zs = new ZapSpan(p,driver,span,first,chans,dchan,cfg,sect,fd);
zs->startup();
return zs;
}
PriChan* ZapDriver::createChan(const PriSpan* span, int chan, unsigned int bufsize)
{
Debug(this,DebugAll,"ZapDriver::createChan(%p,%d,%u) [%p]",span,chan,bufsize,this);
return new ZapChan(span,chan,bufsize);
}
ZapDriver::ZapDriver()
: PriDriver("zap")
{
Output("Loaded module Zapchan");
}
ZapDriver::~ZapDriver()
{
Output("Unloading module Zapchan");
}
void ZapDriver::initialize()
{
Output("Initializing module Zapchan");
init("zapchan");
}
}; // anonymous namespace
#endif /* _WINDOWS */
/* vi: set ts=8 sw=4 sts=4 noet: */

8
packing/.cvsignore Normal file
View File

@ -0,0 +1,8 @@
Makefile
YateLocal.mak
core*
*.o
*.a
*.orig
*~
.*.swp

8
packing/rpm/.cvsignore Normal file
View File

@ -0,0 +1,8 @@
Makefile
YateLocal.mak
core*
*.o
*.a
*.orig
*~
.*.swp

View File

@ -1,7 +1,7 @@
# Have to rotate the log file before it reaches 2GB in size
/var/log/yate {
size=1000M
size=100M
rotate 5
missingok
notifempty

6
run.in
View File

@ -5,6 +5,7 @@
yate="./yate"
set_conf="-c ./conf.d"
set_mods="-m ./modules"
set_share="-e ./share"
if [ "$1" = "--executable" ]; then
shift
yate="$1"
@ -38,7 +39,10 @@ for opt in $@; do
-m)
set_mods=
;;
-e)
set_share=
;;
esac
done
LD_LIBRARY_PATH=.@H323_RUN@:$LD_LIBRARY_PATH exec $yate $set_conf $set_mods "$@"
LD_LIBRARY_PATH=.@H323_RUN@:$LD_LIBRARY_PATH exec $yate $set_conf $set_mods $set_share "$@"

7
share/.cvsignore Normal file
View File

@ -0,0 +1,7 @@
Makefile
YateLocal*
.xvpics
core*
*.orig
*~
.*.swp

29
share/Makefile.in Normal file
View File

@ -0,0 +1,29 @@
# Makefile
# This file holds the make rules for the Telephony Engine modules
# override DESTDIR at install time to prefix the install directory
DESTDIR :=
SUBDIRS := help skins scripts
prefix = @prefix@
exec_prefix = @exec_prefix@
datarootdir = @datarootdir@
datadir = @datadir@
shrdir = $(datadir)/yate
# include optional local make rules
-include YateLocal.mak
PHONY: all clean install uninstall
all clean install uninstall:
$(if $(SUBDIRS),\
@for i in $(SUBDIRS) ; do \
if test -f ./$$i/Makefile ; then \
$(MAKE) -C ./$$i $@ || exit 1;\
fi; \
done \
)
Makefile: @srcdir@/Makefile.in @top_builddir@/config.status
cd @top_builddir@ && ./config.status

View File

@ -1,4 +1,5 @@
Makefile
YateLocal*
.xvpics
core*
*.orig

View File

@ -1,20 +1,15 @@
# Makefile
# This file holds the make rules for the Telephony Engine modules
# This file holds the make rules for Yate client help files
# override DESTDIR at install time to prefix the install directory
DESTDIR :=
# override DEBUG at compile time to enable full debug or remove it all
DEBUG :=
SUBDIRS := gtk2
MKDEPS := ../../config.status
basedir = @libdir@/yate
prefix = @prefix@
exec_prefix = @exec_prefix@
moddir = $(basedir)/modules
helpdir = $(moddir)/help
datarootdir = @datarootdir@
datadir = @datadir@
shrdir = $(datadir)/yate
helpdir = $(shrdir)/help
.PHONY: all clean install uninstall
all clean:
@ -29,8 +24,7 @@ install:
uninstall:
@-rm "$(DESTDIR)$(helpdir)/"*.yhlp
@-rmdir "$(DESTDIR)$(helpdir)"
@-rmdir "$(DESTDIR)$(moddir)"
@-rmdir "$(DESTDIR)$(basedir)"
@-rmdir "$(DESTDIR)$(shrdir)"
Makefile: @srcdir@/Makefile.in $(MKDEPS)
cd ../.. && ./config.status
Makefile: @srcdir@/Makefile.in @top_builddir@/config.status
cd @top_builddir@ && ./config.status

View File

@ -1,5 +1,6 @@
Makefile
YateLocal.mak
YateLocal*
.xvpics
core*
*.orig
*~

View File

@ -1,5 +1,5 @@
# Makefile
# This file holds the make rules for the Telephony Engine script files
# This file holds the make rules for Yate script files
# override DESTDIR at install time to prefix the install directory
DESTDIR :=
@ -7,10 +7,12 @@ DESTDIR :=
SCRIPTS := leavemail.php voicemail.php route.php
SCRLIBS := libyate.php libyatechan.php libvoicemail.php libyate.py Yate.pm
basedir = @libdir@/yate
prefix = @prefix@
exec_prefix = @exec_prefix@
scrdir = $(basedir)/scripts
datarootdir = @datarootdir@
datadir = @datadir@
shrdir = $(datadir)/yate
scrdir = $(shrdir)/scripts
# include optional local make rules
-include YateLocal.mak
@ -31,7 +33,7 @@ uninstall:
rm "$(DESTDIR)$(scrdir)/$$i" ; \
done;
@-rmdir "$(DESTDIR)$(scrdir)"
@-rmdir "$(DESTDIR)$(basedir)"
@-rmdir "$(DESTDIR)$(shrdir)"
Makefile: @srcdir@/Makefile.in ../config.status
cd .. && ./config.status
Makefile: @srcdir@/Makefile.in @top_builddir@/config.status
cd @top_builddir@ && ./config.status

View File

@ -1,4 +1,5 @@
Makefile
YateLocal*
.xvpics
core*
*.orig

View File

@ -1,20 +1,15 @@
# Makefile
# This file holds the make rules for the Telephony Engine modules
# This file holds the make rules for Yate client skins
# override DESTDIR at install time to prefix the install directory
DESTDIR :=
# override DEBUG at compile time to enable full debug or remove it all
DEBUG :=
SUBDIRS := gtk2
MKDEPS := ../../config.status
basedir = @libdir@/yate
prefix = @prefix@
exec_prefix = @exec_prefix@
moddir = $(basedir)/modules
skindir = $(moddir)/skin
datarootdir = @datarootdir@
datadir = @datadir@
shrdir = $(datadir)/yate
skindir = $(shrdir)/skins
.PHONY: all clean install uninstall
all clean:
@ -36,8 +31,7 @@ uninstall:
rmdir "$(DESTDIR)$(skindir)/$$i" ; \
done;
@-rmdir "$(DESTDIR)$(skindir)"
@-rmdir "$(DESTDIR)$(moddir)"
@-rmdir "$(DESTDIR)$(basedir)"
@-rmdir "$(DESTDIR)$(shrdir)"
Makefile: @srcdir@/Makefile.in $(MKDEPS)
cd ../.. && ./config.status
Makefile: @srcdir@/Makefile.in @top_builddir@/config.status
cd @top_builddir@ && ./config.status

View File

@ -2,7 +2,8 @@
ustr='Usage: yate-config [--cflags] [--includes] [--c-all]
[--ldflags] [--libs] [--ld-all] [--ld-nostrip] [--ld-strip]
[--config] [--modules] [--scripts] [--skins]
[--config] [--modules] [--share]
[--helpdir] [--scripts] [--skins]
[--version]'
if [ "$#" = 0 ]; then
echo "$ustr"
@ -10,6 +11,8 @@ if [ "$#" = 0 ]; then
fi
prefix="@prefix@"
exec_prefix="@exec_prefix@"
datarootdir="@datarootdir@"
shrdir="@datadir@/yate"
moddir="@libdir@/yate"
confdir="@sysconfdir@/yate"
s1="@MODULE_CPPFLAGS@"
@ -50,14 +53,20 @@ while [ "$#" != 0 ]; do
--config)
echo "$confdir"
;;
--skins)
echo "$moddir/modules/skin"
;;
--modules)
echo "$moddir/modules"
;;
--share)
echo "$shrdir"
;;
--helpdir)
echo "$shrdir/help"
;;
--skins)
echo "$shrdir/skins"
;;
--scripts)
echo "$moddir/scripts"
echo "$shrdir/scripts"
;;
*)
echo "I didn't understand: $1" >&2

View File

@ -752,6 +752,13 @@ public:
*/
static bool Register(const Plugin* plugin, bool reg = true);
/**
* Get the application's shared directory path
* @return The base path for shared files and directories
*/
inline static String& sharedPath()
{ return s_shrpath; }
/**
* Get the filename for a specific configuration
* @param name Name of the configuration requested
@ -958,6 +965,7 @@ private:
ObjList m_libs;
MessageDispatcher m_dispatcher;
static Engine* s_self;
static String s_shrpath;
static String s_cfgpath;
static String s_cfgsuffix;
static String s_modpath;