diff --git a/Makefile.in b/Makefile.in index 2e11f020..559e2e02 100644 --- a/Makefile.in +++ b/Makefile.in @@ -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' diff --git a/autogen.sh b/autogen.sh index e962111d..e324997a 100755 --- a/autogen.sh +++ b/autogen.sh @@ -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 diff --git a/clients/.cvsignore b/clients/.cvsignore index d56d87c7..64cffece 100644 --- a/clients/.cvsignore +++ b/clients/.cvsignore @@ -1,5 +1,5 @@ Makefile -YateLocal.mak +YateLocal* core* yate-* *.o diff --git a/clients/Makefile.in b/clients/Makefile.in index c4ca9eb4..96c1d496 100644 --- a/clients/Makefile.in +++ b/clients/Makefile.in @@ -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 diff --git a/clients/gtk2/.cvsignore b/clients/gtk2/.cvsignore index 6e6f6e26..efbd82da 100644 --- a/clients/gtk2/.cvsignore +++ b/clients/gtk2/.cvsignore @@ -1,5 +1,5 @@ Makefile -YateLocal.mak +YateLocal* core* *.o *.a diff --git a/clients/gtk2/Makefile.in b/clients/gtk2/Makefile.in index e992a7b3..5fbaeeec 100644 --- a/clients/gtk2/Makefile.in +++ b/clients/gtk2/Makefile.in @@ -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 diff --git a/clients/gtk2/gtk2client.cpp b/clients/gtk2/gtk2client.cpp index dcdb9941..e0e7069a 100644 --- a/clients/gtk2/gtk2client.cpp +++ b/clients/gtk2/gtk2client.cpp @@ -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")); diff --git a/clients/main-gtk2.cpp b/clients/main-gtk2.cpp index 6a096418..85d95fdc 100644 --- a/clients/main-gtk2.cpp +++ b/clients/main-gtk2.cpp @@ -24,7 +24,7 @@ #include #include -#include "../contrib/gtk2/gtk2client.h" +#include "gtk2/gtk2client.h" using namespace TelEngine; diff --git a/clients/null_team-16.png b/clients/null_team-16.png new file mode 100644 index 00000000..00bd3c33 Binary files /dev/null and b/clients/null_team-16.png differ diff --git a/clients/null_team-32.png b/clients/null_team-32.png new file mode 100644 index 00000000..f90e3366 Binary files /dev/null and b/clients/null_team-32.png differ diff --git a/clients/run-gtk2 b/clients/run-gtk2 index b514f03f..48a84db1 100755 --- a/clients/run-gtk2 +++ b/clients/run-gtk2 @@ -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 diff --git a/conf.d/.cvsignore b/conf.d/.cvsignore index 9bac8cb5..a5f9f2b8 100644 --- a/conf.d/.cvsignore +++ b/conf.d/.cvsignore @@ -1,5 +1,5 @@ Makefile -YateLocal.mak +YateLocal* core* *.conf *.orig diff --git a/conf.d/Makefile.in b/conf.d/Makefile.in index c35e5684..c17dd69a 100644 --- a/conf.d/Makefile.in +++ b/conf.d/Makefile.in @@ -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 diff --git a/conf.d/extmodule.conf.sample b/conf.d/extmodule.conf.sample index f97b282a..453c98c3 100644 --- a/conf.d/extmodule.conf.sample +++ b/conf.d/extmodule.conf.sample @@ -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 diff --git a/conf.d/yate-gtk2.conf.default b/conf.d/yate-gtk2.conf.default index 9f13dfcd..866c4cca 100644 --- a/conf.d/yate-gtk2.conf.default +++ b/conf.d/yate-gtk2.conf.default @@ -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 diff --git a/configure.in b/configure.in index 8ef916b6..dda2edec 100644 --- a/configure.in +++ b/configure.in @@ -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]) AC_CHECK_DECLS([MYSQL_OPT_READ_TIMEOUT],[MYSQL_INC="$MYSQL_INC -DMYSQL_OPT_READ_TIMEOUT=MYSQL_OPT_READ_TIMEOUT"],,[#include]) AC_CHECK_DECLS([MYSQL_OPT_WRITE_TIMEOUT],[MYSQL_INC="$MYSQL_INC -DMYSQL_OPT_WRITE_TIMEOUT=MYSQL_OPT_WRITE_TIMEOUT"],,[#include]) - 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 diff --git a/docs/Doxyfile b/docs/Doxyfile index 4c2b40bf..dedf3987 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -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 diff --git a/docs/man/yate-config.8 b/docs/man/yate-config.8 index 5caeec42..9c1e8975 100644 --- a/docs/man/yate-config.8 +++ b/docs/man/yate-config.8 @@ -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 .br -Diana Cionoiu +Diana Cionoiu .SH SEE ALSO .BR yate (8), .BR pkg-config (1) diff --git a/docs/man/yate.8 b/docs/man/yate.8 index 6dacca72..4e444b8f 100644 --- a/docs/man/yate.8 +++ b/docs/man/yate.8 @@ -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 .br -Diana Cionoiu +Diana Cionoiu .SH SEE ALSO .BR yate-config (8) diff --git a/engine/.cvsignore b/engine/.cvsignore index 6e6f6e26..efbd82da 100644 --- a/engine/.cvsignore +++ b/engine/.cvsignore @@ -1,5 +1,5 @@ Makefile -YateLocal.mak +YateLocal* core* *.o *.a diff --git a/engine/DataBlock.cpp b/engine/DataBlock.cpp index d9e9df4b..37071f0c 100644 --- a/engine/DataBlock.cpp +++ b/engine/DataBlock.cpp @@ -26,7 +26,7 @@ #include extern "C" { -#include "tables/all.h" +#include "all.h" } using namespace TelEngine; diff --git a/engine/Engine.cpp b/engine/Engine.cpp index dd63be61..6b6521a5 100644 --- a/engine/Engine.cpp +++ b/engine/Engine.cpp @@ -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]); diff --git a/engine/Makefile.in b/engine/Makefile.in index c2e33865..ef30013f 100644 --- a/engine/Makefile.in +++ b/engine/Makefile.in @@ -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) diff --git a/engine/tables/Makefile.tables b/engine/tables/Makefile.tables index 4c4daa00..f2078006 100644 --- a/engine/tables/Makefile.tables +++ b/engine/tables/Makefile.tables @@ -20,6 +20,3 @@ strip: all install: uninstall: - -Makefile: ./Makefile.in ../config.status - cd .. && ./config.status diff --git a/engine/tables/gen.sh b/engine/tables/gen.sh index b95e6a54..67f0d9cd 100755 --- a/engine/tables/gen.sh +++ b/engine/tables/gen.sh @@ -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 diff --git a/libs/yiax/.cvsignore b/libs/yiax/.cvsignore index 6e6f6e26..efbd82da 100644 --- a/libs/yiax/.cvsignore +++ b/libs/yiax/.cvsignore @@ -1,5 +1,5 @@ Makefile -YateLocal.mak +YateLocal* core* *.o *.a diff --git a/libs/yjingle/.cvsignore b/libs/yjingle/.cvsignore index 6e6f6e26..efbd82da 100644 --- a/libs/yjingle/.cvsignore +++ b/libs/yjingle/.cvsignore @@ -1,5 +1,5 @@ Makefile -YateLocal.mak +YateLocal* core* *.o *.a diff --git a/libs/ymgcp/.cvsignore b/libs/ymgcp/.cvsignore new file mode 100644 index 00000000..efbd82da --- /dev/null +++ b/libs/ymgcp/.cvsignore @@ -0,0 +1,8 @@ +Makefile +YateLocal* +core* +*.o +*.a +*.orig +*~ +.*.swp diff --git a/libs/ymgcp/Makefile.in b/libs/ymgcp/Makefile.in new file mode 100644 index 00000000..fd8c5c47 --- /dev/null +++ b/libs/ymgcp/Makefile.in @@ -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 $@ $^ diff --git a/libs/ymgcp/endpoint.cpp b/libs/ymgcp/endpoint.cpp new file mode 100644 index 00000000..4b1e0ae4 --- /dev/null +++ b/libs/ymgcp/endpoint.cpp @@ -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 + +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(obj->get()) : 0; +} + +// Find the info object associated with an unique remote peer +MGCPEpInfo* MGCPEndpoint::peer() +{ + return (m_remote.count() == 1) ? static_cast(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: */ diff --git a/libs/ymgcp/engine.cpp b/libs/ymgcp/engine.cpp new file mode 100644 index 00000000..c19b4b78 --- /dev/null +++ b/libs/ymgcp/engine.cpp @@ -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 + +#include + +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(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(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(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(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(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(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(iter.get()); + // End of iteration? NO: get a reference to the transaction + if (!tr) + break; + RefPointer 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(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(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(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: */ diff --git a/libs/ymgcp/message.cpp b/libs/ymgcp/message.cpp new file mode 100644 index 00000000..9c4bf577 --- /dev/null +++ b/libs/ymgcp/message.cpp @@ -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 +#include + +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(obj->get()); + for (ObjList* o = tmp->lines().skipNull(); o; o = o->skipNext()) { + NamedString* ns = static_cast(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: */ diff --git a/libs/ymgcp/transaction.cpp b/libs/ymgcp/transaction.cpp new file mode 100644 index 00000000..d882cf5c --- /dev/null +++ b/libs/ymgcp/transaction.cpp @@ -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 + +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: */ diff --git a/libs/ymgcp/yatemgcp.h b/libs/ymgcp/yatemgcp.h new file mode 100644 index 00000000..97b9a0c2 --- /dev/null +++ b/libs/ymgcp/yatemgcp.h @@ -0,0 +1,1057 @@ +/** + * yatemgcp.h + * 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. + */ + +#ifndef __YATEMGCP_H +#define __YATEMGCP_H + +#include +#include + +#ifdef _WINDOWS + +#ifdef LIBYMGCP_EXPORTS +#define YMGCP_API __declspec(dllexport) +#else +#ifndef LIBYMGCP_STATIC +#define YMGCP_API __declspec(dllimport) +#endif +#endif + +#endif /* _WINDOWS */ + +#ifndef YMGCP_API +#define YMGCP_API +#endif + +/** + * Holds all Telephony Engine related classes. + */ +namespace TelEngine { + +class MGCPMessage; +class MGCPTransaction; +class MGCPEpInfo; +class MGCPEndpoint; +class MGCPEvent; +class MGCPEngine; + +/** + * This class holds an MGCP message, either command or response, along with + * its parameters. The + * @short An MGCP command or response + */ +class YMGCP_API MGCPMessage : public RefObject +{ + friend class MGCPTransaction; +public: + /** + * Constructor. Construct an outgoing command message. + * A transaction id will be requested from the endpoint's engine. + * The message will be invalidated if failed to get a transaction id or the + * command name is unknown + * @param engine The engine sending this message + * @param name Command name + * @param ep The id of the endpoint issuing this command + * @param ver The protocol version to use + */ + MGCPMessage(MGCPEngine* engine, const char* name, const char* ep, const char* ver = "MGCP 1.0"); + + /** + * Constructor. Construct an outgoing response message + * The message will be invalidated if failed to get a transaction id or the + * code is greater then 999 + * @param trans The transaction to respond + * @param code The response code ranging from 0 to 999 + * @param comment Optional response comment + */ + MGCPMessage(MGCPTransaction* trans, unsigned int code, const char* comment = 0); + + /** + * Destructor + */ + virtual ~MGCPMessage(); + + /** + * Check if this is a valid message + * @return True if this is a valid message + */ + inline bool valid() const + { return m_valid; } + + /** + * Get the command name or response code text representation of this message + * @return The command name or response text representation of this message + */ + inline const String& name() const + { return m_name; } + + /** + * Get the response code if this is a response message + * @return The response code contained in this message + */ + inline int code() const + { return m_code; } + + /** + * Get the protocol version of a command message + * @return The protocol version of this message + */ + inline const String& version() const + { return m_version; } + + /** + * Get the comment from a response message + * @return The comment of this message + */ + inline const String& comment() const + { return m_comment; } + + /** + * Check if this is a command (code is a negative value) + * @return True if this message is a command + */ + inline bool isCommand() const + { return code() < 0; } + + /** + * Check if this is a response message (code is greater then or equal to 100) + * @return True if this message is a response + */ + inline bool isResponse() const + { return 100 <= code(); } + + /** + * Check if this message is a response ACK (code is between 0 and 99, including the margins) + * @return True if this message is a response ACK + */ + inline bool isAck() const + { return 0 <= code() && code() <= 99; } + + /** + * Get the message's transaction id + * @return The message's transaction id + */ + inline unsigned int transactionId() const + { return m_transaction; } + + /** + * Get the message's endpoint id if this is a command + * @return The message's endpoint id if this is a command + */ + inline const String& endpointId() const + { return m_endpoint; } + + /** + * Convert this message to a string representation to be sent to the remote + * endpoint or printed to output + * @param dest Destination string + */ + void toString(String& dest) const; + + /** + * Parse a received buffer according to RFC 3435. Command and protocol names are converted to upper case. + * The enpoint id is converted to lower case. Message parameter names are converted to lower case if + * the engine's flag is set. Message parameter values and SDP(s) are stored unchanged + * @param engine The receiving engine + * @param dest The list of received messages + * @param buffer The buffer to parse + * @param len The buffer length + * @param sdpType The MIME SDP content type if the message contains any SDP body + * @return False on failure, true on success. If failed, the destination + * list may contain a response message to be sent + */ + static bool parse(MGCPEngine* engine, ObjList& dest, + const unsigned char* buffer, unsigned int len, + const char* sdpType = "application/sdp"); + + /** + * Keep the message parameters + */ + NamedList params; + + /** + * Keep the SDP(s) carried by this message as MimeSdpBody object(s) + */ + ObjList sdp; + +protected: + /** + * Constructor. Used by the parser to construct an incoming message + * @param engine The engine receiving this message + * @param name Command name or response comment + * @param code The response code in the range 0 to 999 or -1 if the received + * message is a command + * @param transId The id of the transaction owning this message + * @param epId The id of the endpoint issuing this command + * @param ver The protocol version + */ + MGCPMessage(MGCPEngine* engine, const char* name, int code, + unsigned int transId, const char* epId, const char* ver); + +private: + MGCPMessage() : params("") {} // Avoid using default constructor + // Decode the message line + static MGCPMessage* decodeMessage(const char* line, unsigned int len, unsigned int& trans, + String& error, MGCPEngine* engine); + // Decode message parameters. Return true if found a line containing a dot + static bool decodeParams(const unsigned char* buffer, unsigned int len, + unsigned int& crt, MGCPMessage* msg, String& error, MGCPEngine* engine); + + String m_name; // Command or string representation of response code + bool m_valid; // False if this message is invalid + int m_code; // Response code or -1 if this is a command + unsigned int m_transaction; // The id of the transaction this message belongs to + String m_endpoint; // The id of the endpoint issuing this message + String m_version; // The protocol version + String m_comment; // The comment attached to a response message +}; + +/** + * This class implements an MGCP transaction + * @short An MGCP transaction + */ +class YMGCP_API MGCPTransaction : public RefObject, public Mutex +{ + friend class MGCPEngine; // Process a received message + friend class MGCPEvent; // Access to event termination notification +public: + /** + * Transaction state enumeration + */ + enum State { + Invalid = 0, // This is an invalid transaction (constructor failed) + Initiated = 1, // An initial command message was sent/received + Trying = 2, // Sent or received a provisional response to the initial message + Responded = 3, // Sent or received a final response to the initial message + Ack = 4, // Response was ack'd + Destroying = 5, // Waiting to be removed from the engine + }; + + /** + * Constructor. Construct a transaction from its first message + * @param engine The engine owning this transaction + * @param msg The command creating this transaction + * @param outgoing The direction of this transaction + * @param address Remote enpoint's address + */ + MGCPTransaction(MGCPEngine* engine, MGCPMessage* msg, bool outgoing, + const SocketAddr& address); + + /** + * Destructor + */ + virtual ~MGCPTransaction(); + + /** + * Get the current transaction's state + * @return The transaction state as enumeration + */ + inline State state() const + { return m_state; } + + /** + * Get the id of this transaction + * @return The id of this transaction + */ + inline unsigned int id() const + { return m_id; } + + /** + * Get the direction of this transaction + * @return True if this is an outgoing transaction + */ + inline bool outgoing() const + { return m_outgoing; } + + /** + * Get the id of the endpoint owning this transaction + * @return The id of the endpoint owning this transaction + */ + inline const String& ep() const + { return m_endpoint; } + + /** + * Get the remote endpoint's IP address + * @return The remote endpoint's IP address + */ + const SocketAddr& addr() const + { return m_address; } + + /** + * Get the engine owning this transaction + * @return The engine owning this transaction + */ + inline MGCPEngine* engine() + { return m_engine; } + + /** + * Get the initial command message sent or received by this transaction + * @return The transaction's initial message + */ + inline const MGCPMessage* initial() const + { return m_cmd; } + + /** + * Get the provisional response message sent or received by this transaction + * @return The transaction's provisional response message + */ + inline const MGCPMessage* msgProvisional() const + { return m_provisional; } + + /** + * Get the final response message sent or received by this transaction + * @return The transaction's final response message + */ + inline const MGCPMessage* msgResponse() const + { return m_response; } + + /** + * Get the response aknowledgement message sent or received by this transaction + * @return The transaction's response aknowledgement message + */ + inline const MGCPMessage* msgAck() const + { return m_ack; } + + /** + * Check if this transaction timed out + * @return True if this transaction timed out + */ + inline bool timeout() const + { return m_timeout; } + + /** + * Get the private user data of this transaction + * @return The private user data of this transaction + */ + inline void* userData() const + { return m_private; } + + /** + * Set the private user data of this transaction + * @param data The new private user data of this transaction + */ + inline void userData(void* data) + { m_private = data; } + + /** + * Get an event from this transaction. Check timeouts + * @param time Current time in microseconds + * @return MGCPEvent pointer or 0 if none + */ + MGCPEvent* getEvent(u_int64_t time = Time()); + + /** + * Explicitely transmits a provisional code + * @param code Provisional response code to send, must be in range 100-199 + * @param comment Optional response comment text + * @return True if the provisional response was sent + */ + bool sendProvisional(int code = 100, const char* comment = 0); + + /** + * Creates and transmits a final response (code must at least 200) message if + * this is an incoming transaction + * @param code Response code to send + * @param comment Optional response comment text + * @return True if the message was queued for transmission + */ + inline bool setResponse(int code, const char* comment = 0) + { return setResponse(new MGCPMessage(this,code,comment)); } + + /** + * Creates and transmits a final response (code must at least 200) message if + * this is an incoming transaction. + * The SDP(s) will be consumed (appended to the message or destroyed) + * @param code Response code to send + * @param params Parameters to set in response, name will be set as comment + * @param sdp1 Optional SDP to be added to the response + * @param sdp2 Optional second SDP to be added to the response if the first one is not 0 + * @return True if the message was queued for transmission + */ + bool setResponse(int code, const NamedList* params, MimeSdpBody* sdp1 = 0, + MimeSdpBody* sdp2 = 0); + + /** + * Transmits a final response (code must at least 200) + * message if this is an incoming transaction + * @param msg The message to transmit + * @return True if the message was queued for transmission + */ + bool setResponse(MGCPMessage* msg); + +protected: + /** + * Gracefully terminate this transaction. Release memory + */ + virtual void destroyed(); + + /** + * Consume (process) a received message, other then the initiating one + * @param msg The received message + */ + void processMessage(MGCPMessage* msg); + + /** + * Check timeouts. Manage retransmissions + * @param time Current time in milliseconds + * @return MGCPEvent pointer if timeout + */ + MGCPEvent* checkTimeout(u_int64_t time); + + /** + * Event termination notification + * @param event The notifier + */ + void eventTerminated(MGCPEvent* event); + + /** + * Change transaction's state if the new state is a valid one + * @param newState The new state of this transaction + */ + void changeState(State newState); + + /** + * Set and send the provisional response (codes between 100 and 199) + * @param code The response code + */ + void setProvisional(int code = 100); + + /** + * (Re)send one the initial, provisional or final response. Change transaction's state + * @param msg The message to send + */ + void send(MGCPMessage* msg); + +private: + MGCPTransaction() {} // Avoid using default constructor + // Check if received any final response. Create an event. Init timeout. + // Send a response ACK if requested by the response + MGCPEvent* checkResponse(u_int64_t time); + // Init timeout for retransmission or transaction termination + void initTimeout(u_int64_t time, bool extra); + // Remove from engine. Create event. Deref the transaction + MGCPEvent* terminate(); + + State m_state; // Current state + unsigned int m_id; // Transaction id + bool m_outgoing; // Transaction direction + SocketAddr m_address; // Remote andpoint's address + MGCPEngine* m_engine; // The engine owning this transaction + MGCPMessage* m_cmd; // The command that created this transaction + MGCPMessage* m_provisional; // The provisional response to the command that created this transaction + MGCPMessage* m_response; // The response to the command that created this transaction + MGCPMessage* m_ack; // The response aknowledgement message sent or received + MGCPEvent* m_lastEvent; // The last generated event + String m_endpoint; // The endpoint owning this transaction + u_int64_t m_nextRetrans; // Retransission or destroy time + unsigned int m_crtRetransInterval; // Current retransmission interval + unsigned int m_retransCount; // Remainig number of retransmissions + bool m_timeout; // Transaction timeout flag + void* m_private; // Data used by this transaction's user + String m_debug; // String used to identify the transaction in debug messages +}; + +/** + * This class holds an endpoint id in the form "endpoint@host:port" + * @short An endpoint id + */ +class YMGCP_API MGCPEndpointId +{ +public: + /** + * Constructor + */ + inline MGCPEndpointId() + : m_port(0) + {} + + /** + * Constructor. Construct this endpoint id from a string + * @param src The string to construct from + */ + inline MGCPEndpointId(String& src) + : m_port(0) + { set(src); } + + /** + * Constructor. Construct this endpoint id + * @param endpoint The user part of the endpoint's URI + * @param host The IP address of the endpoint's URI + * @param port The port used by the endpoint to receive data + */ + inline MGCPEndpointId(const char* endpoint, const char* host, int port) + : m_port(0) + { set(endpoint,host,port); } + + /** + * Get the full id of the endpoint + * @return The full id of the endpoint + */ + inline const String& id() const + { return m_id; } + + /** + * Get the user part of the endpoint URI + * @return The user part of the endpoint URI + */ + inline const String& user() const + { return m_endpoint; } + + /** + * Get the host part of the endpoint URI + * @return The host part of the endpoint URI + */ + inline const String& host() const + { return m_host; } + + /** + * Get the port used by this endpoint + * @return The port used by this endpoint + */ + inline int port() const + { return m_port; } + + /** + * Set the port used by this endpoint + * @param newPort The new port used by this endpoint + */ + inline void port(int newPort) + { set(m_endpoint,m_host,newPort); } + + /** + * Set this endpoint id. Convert it to lower case + * @param endpoint The user part of the endpoint's URI + * @param host The IP address of the endpoint's URI + * @param port The port used by the endpoint to receive data + */ + void set(const char* endpoint, const char* host, int port); + + /** + * Set this endpoint id. Convert it to lower case + * @param src The string to construct from + */ + inline void set(String& src) { + URI uri(src); + set(uri.getUser(),uri.getHost(),uri.getPort()); + } + + /** + * Check if this is a valid endpoint id as defined in RFC 3435 3.2.1.3. + * It is considerred valid if the user and host part lengths are between + * 1 and 255 and the port is not 0 + * @return True if this is a valid endpoint id + */ + inline bool valid() const { + return m_endpoint && m_endpoint.length() < 256 && + m_host && m_host.length() < 256; + } + +private: + String m_id; // The complete id + String m_endpoint; // The endpoint's name inside the host + String m_host; // Host of this endpoint + int m_port; // Port used by this endpoint +}; + +/** + * This class holds data about a remote endpoint (id and address) + * @short Remote endpoint info class + */ +class YMGCP_API MGCPEpInfo : public MGCPEndpointId, public GenObject +{ +public: + /** + * Constructor. Construct this endpoint info + * @param endpoint The endpoint part of the endpoint's id + * @param host The IP address of this endpoint + * @param port The port used to send data to this endpoint + */ + inline MGCPEpInfo(const char* endpoint, const char* host, int port) + : MGCPEndpointId(endpoint,host,port), address(AF_INET) { + address.host(host); + address.port(port); + } + + /** + * Get a string representation of this object + * @return The endpoint's id + */ + virtual const String& toString() const + { return id(); } + + /** + * The IP address and port of this endpoint + */ + SocketAddr address; +}; + +/** + * This class holds a local MGCP endpoint (either gateway or call agent) along + * with its remote peer(s). + * If the engine owning this endpoint is an MGCP gateway, only 1 remote peer (Call Agent) is allowed + * @short An MGCP endpoint + */ +class YMGCP_API MGCPEndpoint : public RefObject, public MGCPEndpointId +{ +public: + /** + * Constructor. Construct this endpoint. Append itself to the engine's list. + * The endpoint's id will be created from the received user and engine's address + * @param engine The engine owning this endpoint + * @param user The user part of the endpoint's id + * @param host The host part of the endpoint's id + * @param port The port part of the endpoint's id + */ + MGCPEndpoint(MGCPEngine* engine, const char* user, const char* host, int port); + + /** + * Destructor. Remove itself from engine's list + */ + virtual ~MGCPEndpoint(); + + /** + * Get a string representation of this endpoint + * @return A string representation of this endpoint + */ + virtual const String& toString() const + { return MGCPEndpointId::id(); } + + /** + * Get the engine owning this endpoint + * @return The engine owning this endpoint + */ + inline MGCPEngine* engine() + { return m_engine; } + + /** + * 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 + * @param endpoint The endpoint part of the remote endpoint's id + * @param host The IP address of the remote endpoint + * @param port The port used to send data to this endpoint. + * Set to 0 to set it to the default port defined by the protocol and the + * opposite of the engine's mode + * @return Valid MGCPEpInfo pointer or 0 if the data wasn't added + */ + MGCPEpInfo* append(const char* endpoint, const char* host, int port = 0); + + /** + * Clear the list or remote endpoints + */ + inline void clear() + { Lock lock(m_mutex); m_remote.clear(); } + + /** + * Find the info object associated with a remote peer + * @param epId The remote endpoint's id to find + * @return MGCPEpInfo pointer or 0 if not found + */ + MGCPEpInfo* find(const char* epId); + + /** + * Find the info object associated with an unique remote peer + * @return MGCPEpInfo pointer or 0 if not exactly one peer + */ + MGCPEpInfo* peer(); + +private: + MGCPEngine* m_engine; // The engine owning this endpoint + Mutex m_mutex; // Lock remote endpoint list + ObjList m_remote; // The remote endpoints +}; + +/** + * This class carries a copy of the message received by a transaction or a transaction state + * change notification (such as timeout or destroy) + * @short An MGCP event + */ +class YMGCP_API MGCPEvent +{ + friend class MGCPTransaction; +public: + /** + * Destructor. Delete the message. Notify and deref the transaction + */ + ~MGCPEvent(); + + /** + * Get the transaction that generated this event + * @return The transaction that generated this event + */ + inline MGCPTransaction* transaction() + { return m_transaction; } + + /** + * Get the message carried by this event + * @return The message carried by this event or 0 if none + */ + inline MGCPMessage* message() const + { return m_message; } + +protected: + /** + * Constructor. Constructs an event from a transaction + * @param trans The transaction that generated this event + * @param msg The message carried by this event, if any + */ + MGCPEvent(MGCPTransaction* trans, MGCPMessage* msg = 0); + +private: + MGCPTransaction* m_transaction; // The transaction that generated this event + MGCPMessage* m_message; // The message carried by this event, if any +}; + +class MGCPPrivateThread; + +/** + * The engine may keep gateway endpoints or call agents + * Keep the transaction list and manage it (create/delete/modify/timeout...) + * Keep a list with the endpoints it services + * Generate transaction numbers (IDs) + * Parse received messages, validate and send them to the appropriate transaction + * Send MGCP messages to remote addresses + * @short An MGCP engine + */ +class YMGCP_API MGCPEngine : public DebugEnabler, public Mutex +{ + friend class MGCPPrivateThread; + friend class MGCPTransaction; +public: + /** + * Constructor. Construct the engine and, optionally, initialize it + * @param gateway Engine's mode: true if this engine is an MGCP Gateway, + * false if it's a collection of Call Agents + * @param name Optional debug name for this engine + * @param params Optional parameters used to initialize this engine + */ + MGCPEngine(bool gateway, const char* name = 0, const NamedList* params = 0); + + /** + * Destructor. Clear all lists + */ + virtual ~MGCPEngine(); + + /** + * Check if this engine is an MGCP Gateway or a collection of Call Agents + * @return True if this engine is an MGCP Gateway, false if it's a + * collection of Call Agents + */ + inline bool gateway() const + { return m_gateway; } + + /** + * Get the IP address used by this engine to receive data + * @return The IP address used by this engine to receive data + */ + inline const SocketAddr& address() const + { return m_address; } + + /** + * Get the maximum length or received packets. This is the size of the buffer used by + * this engine to read data from the socket + * @return The maximum length or received packets + */ + inline unsigned int maxRecvPacket() const + { return m_maxRecvPacket; } + + /** + * Check if this engine is allowed to send/accept unknown commands + * @return True if this engine is allowed to send/accept unknown commands + */ + inline bool allowUnkCmd() const + { return m_allowUnkCmd; } + + /** + * Get the message retransmission interval + * @return The message retransmission interval + */ + inline unsigned int retransInterval() const + { return m_retransInterval; } + + /** + * Get the maximum number of retransmissions for a message + * @return The maximum number of retransmissions for a message + */ + inline unsigned int retransCount() const + { return m_retransCount; } + + /** + * Get the time to live after the transaction terminated gracefully + * @return The time to live after the transaction terminated gracefully + */ + inline u_int64_t extraTime() const + { return m_extraTime; } + + /** + * Check if the parser should convert received messages' parameters to lower case + * @return True if the parser should convert received messages' parameters to lower case + */ + inline bool parseParamToLower() const + { return m_parseParamToLower; } + + /** + * Check if incoming transactions would send provisional responses + * @return True if incoming transactions would send provisional responses + */ + inline bool provisional() const + { return m_provisional; } + + /** + * Initialize this engine + * @param params Engine's parameters + */ + virtual void initialize(const NamedList& params); + + /** + * Check if a command is known by this engine + * @param cmd The command name to check + * @return True if the given command is known by this engine + */ + inline bool knownCommand(const char* cmd) + { Lock lock(this); return m_knownCommands.find(cmd); } + + /** + * Add a command to the list of known commands + * @param cmd The command name to add + */ + void addCommand(const char* cmd); + + /** + * Append an endpoint to this engine if not already done + * @param ep The endpoint to append + */ + void attach(MGCPEndpoint* ep); + + /** + * Remove an endpoint from this engine and, optionally, remove all its transactions + * @param ep The endpoint to remove + * @param del True to delete it, false to just remove it from the list + * @param delTrans True to remove all its transactions. + * Forced to true if the endpoint is deleted + */ + void detach(MGCPEndpoint* ep, bool del = false, bool delTrans = false); + + /** + * Find an endpoint by its pointer + * @param ep The endpoint to find + * @return MGCPEndpoint pointer or 0 if not found + */ + MGCPEndpoint* findEp(MGCPEndpoint* ep); + + /** + * Find an endpoint by its id + * @param epId The endpoint's id to find + * @return MGCPEndpoint pointer or 0 if not found + */ + MGCPEndpoint* findEp(const char* epId); + + /** + * Find a transaction by its id + * @param id The id of the transaction to find + * @param outgoing The transaction direction. True for outgoing, false for incoming + * @return MGCPTransaction pointer or 0 if not found + */ + MGCPTransaction* findTrans(unsigned int id, bool outgoing); + + /** + * Generate a new id for an outgoing transaction + * @return An id for an outgoing transaction + */ + unsigned int getNextId(); + + /** + * Send a command message. Create a transaction for it. + * The method will fail if the message is not a valid one or isn't a valid command + * @param cmd The message containig the command + * @param address The destination IP address + * @return MGCPTransaction pointer or 0 if failed to create a transaction + */ + MGCPTransaction* sendCommand(MGCPMessage* cmd, const SocketAddr& address); + + /** + * Read data from the socket. Parse and process the received message + * @param buffer Buffer used for read operation. The buffer must be large enough + * to keep the maximum packet length returned by @ref maxRecvPacket() + * @param addr The sender's address if received any data + * @return True if received any data (a message was successfully parsed) + */ + bool receive(unsigned char* buffer, SocketAddr& addr); + + /** + * 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 + * @param time Current time in microseconds + * @return True if an event was processed + */ + bool process(u_int64_t time = Time()); + + /** + * Repeatedly calls @ref receive() until the calling thread terminates + */ + void runReceive(); + + /** + * Repeatedly calls @ref process() until the calling thread terminates + */ + void runProcess(); + + /** + * Try to get an event from a transaction + * @param time Current time in microseconds + * @return MGCPEvent pointer or 0 if none + */ + MGCPEvent* getEvent(u_int64_t time = Time()); + + /** + * Process an event generated by a transaction. Descendants must override this + * method if they want to process events. By default it calls the version + * of processEvent that accepts separate parameters of event + * @param event The event to process + * @return True if the event was processed. If the event carry a received + * command and it's not processed the transaction will receive an 'unknown command' response + */ + virtual bool processEvent(MGCPEvent* event); + + /** + * Process an event generated by a transaction. Descendants must override this + * method if they want to process events + * @param trans Pointer to the transaction that generated the event + * @param msg MGCP message of the event, may be NULL + * @param data User data that is stored in transaction, may be NULL + * @return True if the event was processed. If the event carry a received + * command and it's not processed the transaction will receive an 'unknown command' response + */ + virtual bool processEvent(MGCPTransaction* trans, MGCPMessage* msg, void* data); + + /** + * 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 + * @param event The event to return + */ + void returnEvent(MGCPEvent* event); + + /** + * Terminate all transactions. Cancel all private threads if any and + * wait for them to terminate + * @param gracefully If true, all incoming transaction will be responded and private + * threads will be gently cancelled. If false, all transactions will be deleted and + * threads will be cancelled the hard way + * @param text Optional text to be sent with the response code of the incoming + * transactions on gracefully cleanup + */ + void cleanup(bool gracefully = true, const char* text = "Shutdown"); + + /** + * Get the default port defined by the protocol + * @param gateway True to get the default Gateway port, + * false to get the default port for the Call Agent + * @return The default port defined by the protocol + */ + static inline int defaultPort(bool gateway) + { return gateway ? 2427 : 2727; } + + /** + * The list of commands defined in RFC 3435 + */ + static TokenDict mgcp_commands[]; + + /** + * The list of known responses defined in RFC 3435 2.4 + */ + static TokenDict mgcp_responses[]; + + /** + * 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 + */ + static TokenDict mgcp_reasons[]; + +protected: + /** + * Send a string buffer through the socket + * @param msg The buffer to send + * @param address The destination IP address + * @return False if the operation failed + */ + bool sendData(const String& msg, const SocketAddr& address); + + /** + * Append a transaction to the list + * @param trans The transaction to append + */ + void appendTrans(MGCPTransaction* trans); + + /** + * Remove a transaction from the list + * @param trans The transaction to remove + * @param del True to delete it, false to just remove it from list + */ + void removeTrans(MGCPTransaction* trans, bool del); + + /** + * The list of endpoints attached to this engine + */ + ObjList m_endpoints; + + /** + * The transaction list + */ + ObjList m_transactions; + +private: + // Append a private thread to the list + void appendThread(MGCPPrivateThread* thread); + // Remove private thread from the list without deleting it + void removeThread(MGCPPrivateThread* thread); + // Process ACK received with a message or response + // Return a list of ack'd transactions or 0 if the parameter is incorrect + unsigned int* decodeAck(const String& param, unsigned int & count); + + bool m_gateway; // True if this engine is an MGCP gateway, false if call agent + bool m_initialized; // True if the engine was already initialized + unsigned int m_nextId; // Next outgoing transaction id + Socket m_socket; // The socket used to send/receive data + SocketAddr m_address; // The IP address used by this engine + unsigned int m_maxRecvPacket; // The maximum length or received packets + unsigned char* m_recvBuf; // Receiving buffer + bool m_allowUnkCmd; // Allow this engine to send/accept unknown commands + unsigned int m_retransInterval; // Message retransmission interval + unsigned int m_retransCount; // Maximum number of retransmissions for a message + u_int64_t m_extraTime; // Time to live after the transaction terminated gracefully + bool m_parseParamToLower; // Convert received messages' params to lower case + bool m_provisional; // Send provisional responses flag + ObjList m_knownCommands; // The list of known commands + ObjList m_threads; +}; + +} + +#endif /* __YATEMGCP_H */ + +/* vi: set ts=8 sw=4 sts=4 noet: */ diff --git a/libs/ypbx/.cvsignore b/libs/ypbx/.cvsignore index 6e6f6e26..efbd82da 100644 --- a/libs/ypbx/.cvsignore +++ b/libs/ypbx/.cvsignore @@ -1,5 +1,5 @@ Makefile -YateLocal.mak +YateLocal* core* *.o *.a diff --git a/libs/yrtp/.cvsignore b/libs/yrtp/.cvsignore index 6e6f6e26..efbd82da 100644 --- a/libs/yrtp/.cvsignore +++ b/libs/yrtp/.cvsignore @@ -1,5 +1,5 @@ Makefile -YateLocal.mak +YateLocal* core* *.o *.a diff --git a/libs/ysig/.cvsignore b/libs/ysig/.cvsignore index 47f3b97a..eff6dad0 100644 --- a/libs/ysig/.cvsignore +++ b/libs/ysig/.cvsignore @@ -1,5 +1,5 @@ Makefile -YateLocal.mak +YateLocal* core* yate-* *.o diff --git a/libs/ysip/.cvsignore b/libs/ysip/.cvsignore index 6e6f6e26..efbd82da 100644 --- a/libs/ysip/.cvsignore +++ b/libs/ysip/.cvsignore @@ -1,5 +1,5 @@ Makefile -YateLocal.mak +YateLocal* core* *.o *.a diff --git a/libs/yxml/.cvsignore b/libs/yxml/.cvsignore index 6e6f6e26..efbd82da 100644 --- a/libs/yxml/.cvsignore +++ b/libs/yxml/.cvsignore @@ -1,5 +1,5 @@ Makefile -YateLocal.mak +YateLocal* core* *.o *.a diff --git a/modules/.cvsignore b/modules/.cvsignore index f614520d..b9910d4c 100644 --- a/modules/.cvsignore +++ b/modules/.cvsignore @@ -1,5 +1,5 @@ Makefile -YateLocal.mak +YateLocal* core* *.o *.a diff --git a/modules/Makefile.in b/modules/Makefile.in index 176bd051..f465ef27 100644 --- a/modules/Makefile.in +++ b/modules/Makefile.in @@ -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 diff --git a/modules/client/.cvsignore b/modules/client/.cvsignore new file mode 100644 index 00000000..20aaabb7 --- /dev/null +++ b/modules/client/.cvsignore @@ -0,0 +1,9 @@ +Makefile +core* +*.o +*.a +*.so +*.yate +*.orig +*~ +.*.swp diff --git a/modules/extmodule.cpp b/modules/extmodule.cpp index 6406a9ab..bc99cda8 100644 --- a/modules/extmodule.cpp +++ b/modules/extmodule.cpp @@ -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; diff --git a/modules/libypri.cpp b/modules/libypri.cpp deleted file mode 100644 index c10bdad1..00000000 --- a/modules/libypri.cpp +++ /dev/null @@ -1,1371 +0,0 @@ -/** - * libypri.cpp - * 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 - -/* Individual bit groups in number presentation - Q931 octet 3a bits 7,6 */ -#define PRESENTATION_BIT_MASK 0x60 -#define PRESENTATION_ALLOWED 0x00 -#define PRESENTATION_RESTRICTED 0x20 -#define PRESENTATION_UNAVAILABLE 0x40 - -/* Individual bit groups in number screening - Q931 octet 3a bits 2,1 */ -#define SCREENING_BIT_MASK 0x03 -#define SCREENING_USER_NOT_SCREENED 0x00 -#define SCREENING_USER_PASSED 0x01 -#define SCREENING_USER_FAILED 0x02 -#define SCREENING_NETWORK_PROVIDED 0x03 - -extern "C" { -extern int q931_setup(struct pri *pri, q931_call *c, struct pri_sr *req); -}; - -#include -#include -#include -#include - - -using namespace TelEngine; - -// default buffer length: 20 ms -static int s_buflen = 160; - - -#ifdef PRI_NEW_SET_API - -#ifdef BRI_NETWORK_PTMP - -// bristuff patch changed the error reporting for no good reason - -static void pri_err_cb(char *s, int span) -{ - Debug("libpri",DebugWarn,"Span %d: %s",span,s); -} - -static void pri_msg_cb(char *s, int span) -{ - Debug("libpri",DebugInfo,"Span %d: %s",span,s); -} - -#else - -static void pri_err_cb(struct pri *pri, char *s) -{ - PriSpan* span = pri ? (PriSpan*)::pri_get_userdata(pri) : 0; - if (span) - Debug(span->driver(),DebugWarn,"Span %d: %s",span->span(),s); - else - Debug("libpri",DebugWarn,"%s",s); -} - -static void pri_msg_cb(struct pri *pri, char *s) -{ - PriSpan* span = pri ? (PriSpan*)::pri_get_userdata(pri) : 0; - if (span) - Debug(span->driver(),DebugInfo,"Span %d: %s",span->span(),s); - else - Debug("libpri",DebugInfo,"%s",s); -} - -#endif // BRI_NETWORK_PTMP - -#else - -static void pri_err_cb(char *s) -{ - Debug("libpri",DebugWarn,"%s",s); -} - -static void pri_msg_cb(char *s) -{ - Debug("libpri",DebugInfo,"%s",s); -} - -#endif // PRI_NEW_SET_API - -/* Switch types */ -static TokenDict dict_str2switch[] = { - { "unknown", PRI_SWITCH_UNKNOWN }, - { "ni2", PRI_SWITCH_NI2 }, - { "dms100", PRI_SWITCH_DMS100 }, - { "lucent5e", PRI_SWITCH_LUCENT5E }, - { "at&t4ess", PRI_SWITCH_ATT4ESS }, - { "euroisdn_e1", PRI_SWITCH_EUROISDN_E1 }, - { "euroisdn_t1", PRI_SWITCH_EUROISDN_T1 }, - { "ni1", PRI_SWITCH_NI1 }, - { 0, -1 } -}; - -static TokenDict dict_str2type[] = { - { "pri_net", PRI_NETWORK }, - { "pri_cpe", PRI_CPE }, -#ifdef BRI_NETWORK_PTMP - { "bri_net_ptmp", BRI_NETWORK_PTMP }, - { "bri_cpe_ptmp", BRI_CPE_PTMP }, - { "bri_net", BRI_NETWORK }, - { "bri_cpe", BRI_CPE }, -#endif - { 0, -1 } -}; - -#if 0 -/* Numbering plan identifier */ -static TokenDict dict_str2nplan[] = { - { "unknown", PRI_NPI_UNKNOWN }, - { "e164", PRI_NPI_E163_E164 }, - { "x121", PRI_NPI_X121 }, - { "f69", PRI_NPI_F69 }, - { "national", PRI_NPI_NATIONAL }, - { "private", PRI_NPI_PRIVATE }, - { "reserved", PRI_NPI_RESERVED }, - { 0, -1 } -}; - -/* Type of number */ -static TokenDict dict_str2ntype[] = { - { "unknown", PRI_TON_UNKNOWN }, - { "international", PRI_TON_INTERNATIONAL }, - { "national", PRI_TON_NATIONAL }, - { "net_specific", PRI_TON_NET_SPECIFIC }, - { "subscriber", PRI_TON_SUBSCRIBER }, - { "abbreviated", PRI_TON_ABBREVIATED }, - { "reserved", PRI_TON_RESERVED }, - { 0, -1 } -}; -#endif - -/* Dialing plan */ -static TokenDict dict_str2dplan[] = { - { "unknown", PRI_UNKNOWN }, - { "international", PRI_INTERNATIONAL_ISDN }, - { "national", PRI_NATIONAL_ISDN }, - { "local", PRI_LOCAL_ISDN }, - { "private", PRI_PRIVATE }, - { 0, -1 } -}; - -/* Presentation and screening */ -static TokenDict dict_str2pres[] = { - { "allow_user_not_screened", PRES_ALLOWED_USER_NUMBER_NOT_SCREENED }, - { "allow_user_passed", PRES_ALLOWED_USER_NUMBER_PASSED_SCREEN }, - { "allow_user_failed", PRES_ALLOWED_USER_NUMBER_FAILED_SCREEN }, - { "allow_network", PRES_ALLOWED_NETWORK_NUMBER }, - { "prohibit_user_not_screened", PRES_PROHIB_USER_NUMBER_NOT_SCREENED }, - { "prohibit_user_passed", PRES_PROHIB_USER_NUMBER_PASSED_SCREEN }, - { "prohibit_user_failed", PRES_PROHIB_USER_NUMBER_FAILED_SCREEN }, - { "prohibit_network", PRES_PROHIB_NETWORK_NUMBER }, - { "not_available", PRES_NUMBER_NOT_AVAILABLE }, - { 0, -1 } -}; - - -#ifdef PRI_NSF_NONE -#define YATE_NSF_DEFAULT PRI_NSF_NONE -#else -#define YATE_NSF_DEFAULT -1 -#endif -/* Network Specific Facilities (AT&T) */ -static TokenDict dict_str2nsf[] = { -#ifdef PRI_NSF_NONE - { "none", PRI_NSF_NONE }, - { "sid_preferred", PRI_NSF_SID_PREFERRED }, - { "ani_preferred", PRI_NSF_ANI_PREFERRED }, - { "sid_only", PRI_NSF_SID_ONLY }, - { "ani_only", PRI_NSF_ANI_ONLY }, - { "call_assoc_tsc", PRI_NSF_CALL_ASSOC_TSC }, - { "notif_catsc_clearing", PRI_NSF_NOTIF_CATSC_CLEARING }, - { "operator", PRI_NSF_OPERATOR }, - { "pcco", PRI_NSF_PCCO }, - { "sdn", PRI_NSF_SDN }, - { "toll_free_megacom", PRI_NSF_TOLL_FREE_MEGACOM }, - { "megacom", PRI_NSF_MEGACOM }, - { "accunet", PRI_NSF_ACCUNET }, - { "long_distance", PRI_NSF_LONG_DISTANCE_SERVICE }, - { "international_toll_free", PRI_NSF_INTERNATIONAL_TOLL_FREE }, - { "at&t_multiquest", PRI_NSF_ATT_MULTIQUEST }, - { "call_redirection", PRI_NSF_CALL_REDIRECTION_SERVICE }, -#endif - { 0, -1 } -}; - -static TokenDict dict_str2cause[] = { - { "noroute", PRI_CAUSE_NO_ROUTE_DESTINATION }, - { "noconn", PRI_CAUSE_REQUESTED_CHAN_UNAVAIL }, - { "busy", PRI_CAUSE_USER_BUSY }, - { "noanswer", PRI_CAUSE_NO_USER_RESPONSE }, - { "rejected", PRI_CAUSE_CALL_REJECTED }, - { "forbidden", PRI_CAUSE_OUTGOING_CALL_BARRED }, - { "forbidden", PRI_CAUSE_INCOMING_CALL_BARRED }, - { "offline", PRI_CAUSE_DESTINATION_OUT_OF_ORDER }, - { "unallocated", PRI_CAUSE_UNALLOCATED }, - { "moved", PRI_CAUSE_NUMBER_CHANGED }, - { "congestion", PRI_CAUSE_NORMAL_CIRCUIT_CONGESTION }, - { "congestion", PRI_CAUSE_SWITCH_CONGESTION }, - { "failure", PRI_CAUSE_DESTINATION_OUT_OF_ORDER }, - { 0, -1 } -}; - -/* Layer 1 formats */ -static TokenDict dict_str2law[] = { - { "mulaw", PRI_LAYER_1_ULAW }, - { "alaw", PRI_LAYER_1_ALAW }, - { "g721", PRI_LAYER_1_G721 }, - { 0, -1 } -}; - -/* Echo canceller taps */ -static TokenDict dict_numtaps[] = { - { "on", 1 }, - { "yes", 1 }, - { "true", 1 }, - { "enable", 1 }, - { 0, 0 } -}; - -class ChanGroup : public String -{ -public: - enum { - FirstAvail = 0, - RoundRobin = 1, - RandomChan = 2 - }; - ChanGroup(const String& name, const NamedList* sect, int last); - virtual ~ChanGroup() - { } - inline void getRange(int& first, int& last, int& used) const - { first = m_first; last = m_last; used = m_used; } - void setUsed(int used); -private: - int m_mode; - int m_first; - int m_last; - int m_used; -}; - -ChanGroup::ChanGroup(const String& name, const NamedList* sect, int last) - : String(name) -{ - static TokenDict dict_groupmode[] = { - { "first", FirstAvail }, - { "firstavail", FirstAvail }, - { "rotate", RoundRobin }, - { "roundrobin", RoundRobin }, - { "random", RandomChan }, - { 0, 0 } - }; - m_mode = sect->getIntValue("mode",dict_groupmode,RoundRobin); - m_first = sect->getIntValue("first",1); - m_last = sect->getIntValue("last",last); - setUsed(m_last); -} - -void ChanGroup::setUsed(int used) -{ - switch (m_mode) { - case FirstAvail: - m_used = m_last; - break; - case RandomChan: - m_used = m_first + (::random() % (m_last - m_first + 1)); - break; - default: - m_used = used; - } -} - -Fifo::Fifo(int buflen) - : m_buflen(buflen), m_head(0), m_tail(1) -{ - if (!m_buflen) - m_buflen = s_buflen; - m_buffer = (unsigned char*)::malloc(m_buflen); -} - -Fifo::~Fifo() -{ - if (m_buffer) - ::free(m_buffer); -} - -// make the fifo empty -void Fifo::clear() -{ - m_head = 0; - m_tail = 1; -} - -// put a byte in fifo, overwrite last byte if full -bool Fifo::put(unsigned char value) -{ - m_buffer[m_tail] = value; - bool full = (m_head == m_tail); - m_tail++; - if (m_tail >= m_buflen) - m_tail = 0; - if (full) - m_head = m_tail; - return full; -} - -unsigned int Fifo::put(const unsigned char* buf, unsigned int length) -{ - unsigned int errors = 0; - while (length--) - if (put(*buf++)) - errors++; - return errors; -} - -// get a byte from fifo, return last read if empty -unsigned char Fifo::get() -{ - unsigned char tmp = m_buffer[m_head]; - int nh = m_head+1; - if (nh >= m_buflen) - nh = 0; - if (nh != m_tail) - m_head = nh; - return tmp; -} - -PriSpan::PriSpan(struct pri *_pri, PriDriver* driver, int span, int first, int chans, int dchan, Configuration& cfg, const String& sect) - : Mutex(true), - m_driver(driver), m_span(span), m_offs(first), m_nchans(chans), m_bchans(0), - m_pri(_pri), m_restart(0), m_chans(0), m_ok(false) -{ - Debug(m_driver,DebugAll,"PriSpan::PriSpan() [%p]",this); - int buflength = cfg.getIntValue(sect,"buflen", s_buflen); - - m_inband = cfg.getBoolValue(sect,"dtmfinband",cfg.getBoolValue("general","dtmfinband")); - m_detect = cfg.getBoolValue(sect,"dtmfdetect",cfg.getBoolValue("general","dtmfdetect",m_inband)); - m_layer1 = cfg.getIntValue(sect,"format",dict_str2law,(chans == 24) ? PRI_LAYER_1_ULAW : PRI_LAYER_1_ALAW); - m_dplan = cfg.getIntValue(sect,"dialplan",dict_str2dplan,PRI_UNKNOWN); - m_pres = cfg.getIntValue(sect,"presentation",dict_str2pres,PRES_ALLOWED_USER_NUMBER_NOT_SCREENED); - m_restartPeriod = cfg.getIntValue(sect,"restart",cfg.getIntValue("general","restart")) * (u_int64_t)1000000; - m_dumpEvents = cfg.getBoolValue(sect,"dumpevents",cfg.getBoolValue("general","dumpevents")); - m_overlapped = cfg.getIntValue(sect,"overlapdial",cfg.getIntValue("general","overlapdial")); - if (m_overlapped < 0) - m_overlapped = 0; -#ifdef PRI_SET_OVERLAPDIAL - ::pri_set_overlapdial(m_pri, (m_overlapped > 0)); -#endif -#ifdef PRI_NSF_NONE - ::pri_set_nsf(m_pri,cfg.getIntValue(sect,"facilities",dict_str2nsf,YATE_NSF_DEFAULT)); -#endif - ::pri_set_debug(m_pri,cfg.getIntValue(sect,"debug")); - ::pri_set_userdata(m_pri, this); - - PriChan **ch = new PriChan* [chans]; - for (int i = 1; i <= chans; i++) { - if (i != dchan) { - ch[i-1] = m_driver->createChan(this,i,buflength); - m_bchans++; - } - else - ch[i-1] = 0; - } - - m_chans = ch; - m_restart = Time::now() + m_restartPeriod; - m_driver->m_spans.append(this); -} - -PriSpan::~PriSpan() -{ - Debug(m_driver,DebugAll,"PriSpan::~PriSpan() [%p]",this); - m_driver->m_spans.remove(this,false); - m_ok = false; - for (int i = 0; i < m_nchans; i++) { - PriChan *c = m_chans[i]; - m_chans[i] = 0; - if (c) { - c->hangup(PRI_CAUSE_NORMAL_UNSPECIFIED); - c->destruct(); - } - } - delete[] m_chans; -} - -void PriSpan::runEvent(bool idleRun) -{ - pri_event *ev = 0; - lock(); - if (idleRun) { - ev = ::pri_schedule_run(m_pri); - idle(); - } - else - ev = ::pri_check_event(m_pri); - if (ev) { - if (m_dumpEvents && debugAt(DebugAll)) - ::pri_dump_event(m_pri, ev); - handleEvent(*ev); - } - unlock(); -} - -void PriSpan::idle() -{ - if (!m_chans) - return; - if (m_restartPeriod && (Time::now() > m_restart)) { - m_restart = Time::now() + m_restartPeriod; - Debug(m_driver,DebugInfo,"Restarting idle channels on span %d",m_span); - for (int i=0; iidle(); -} - -void PriSpan::handleEvent(pri_event &ev) -{ - switch (ev.e) { - case PRI_EVENT_DCHAN_UP: - Debug(DebugMild,"D-channel up on span %d",m_span); - m_ok = true; - m_restart = Time::now() + 1000000; - { - for (int i=0; igoneUp(); - } - break; - case PRI_EVENT_DCHAN_DOWN: - Debug(DebugWarn,"D-channel down on span %d",m_span); - m_ok = false; - { - for (int i=0; ihangup(PRI_CAUSE_NETWORK_OUT_OF_ORDER); - } - break; - case PRI_EVENT_RESTART: - restartChan(ev.restart.channel,false,true); - break; - case PRI_EVENT_CONFIG_ERR: - Debug(DebugWarn,"Error on span %d: %s",m_span,ev.err.err); - break; - case PRI_EVENT_RING: - ringChan(ev.ring.channel,ev.ring); - break; - case PRI_EVENT_INFO_RECEIVED: - infoChan(ev.ring.channel,ev.ring); - break; - case PRI_EVENT_RINGING: - Debug(m_driver,DebugInfo,"Ringing our call on channel %d on span %d",ev.ringing.channel,m_span); - ringingChan(ev.proceeding.channel); - break; - case PRI_EVENT_HANGUP: - Debug(m_driver,DebugInfo,"Hangup detected on channel %d on span %d",ev.hangup.channel,m_span); - hangupChan(ev.hangup.channel,ev.hangup); - break; - case PRI_EVENT_ANSWER: - Debug(m_driver,DebugInfo,"Answered channel %d on span %d",ev.answer.channel,m_span); - answerChan(ev.setup_ack.channel); - break; - case PRI_EVENT_HANGUP_ACK: - Debug(m_driver,DebugInfo,"Hangup ACK on channel %d on span %d",ev.hangup.channel,m_span); - break; - case PRI_EVENT_RESTART_ACK: - Debug(m_driver,DebugInfo,"Restart ACK on channel %d on span %d",ev.restartack.channel,m_span); - break; - case PRI_EVENT_SETUP_ACK: - Debug(m_driver,DebugInfo,"Setup ACK on channel %d on span %d",ev.setup_ack.channel,m_span); - ackChan(ev.setup_ack.channel); - break; - case PRI_EVENT_HANGUP_REQ: - Debug(m_driver,DebugInfo,"Hangup REQ on channel %d on span %d",ev.hangup.channel,m_span); - hangupChan(ev.hangup.channel,ev.hangup); - break; - case PRI_EVENT_PROCEEDING: - Debug(m_driver,DebugInfo,"Call proceeding on channel %d on span %d",ev.proceeding.channel,m_span); - proceedingChan(ev.proceeding.channel); - break; -#ifdef PRI_EVENT_PROGRESS - case PRI_EVENT_PROGRESS: - Debug(m_driver,DebugInfo,"Call progressing on channel %d on span %d",ev.proceeding.channel,m_span); - proceedingChan(ev.proceeding.channel); - break; -#endif -#ifdef PRI_EVENT_KEYPAD_DIGIT - case PRI_EVENT_KEYPAD_DIGIT: - digitsChan(ev.digit.channel,ev.digit.digits); - break; -#endif - default: - Debug(m_driver,DebugInfo,"Unhandled PRI event %d",ev.e); - } -} - -bool PriSpan::validChan(int chan) const -{ - return (chan > 0) && (chan <= m_nchans) && m_chans && m_chans[chan-1]; -} - -int PriSpan::findEmptyChan(int first, int last) const -{ - if (!m_ok) - return -1; - first -= m_offs; - last -= m_offs; - if (first < 0) - first = 0; - if (last > m_nchans-1) - last = m_nchans-1; - for (int i=first; i<=last; i++) - if (m_chans[i] && !m_chans[i]->inUse()) - return i+1; - return -1; -} - -PriChan *PriSpan::getChan(int chan) const -{ - return validChan(chan) ? m_chans[chan-1] : 0; -} - -void PriSpan::restartChan(int chan, bool outgoing, bool force) -{ - if (chan < 0) { - Debug(DebugInfo,"Restart request on entire span %d",m_span); - return; - } - if (!validChan(chan)) { - Debug(DebugInfo,"Restart request on invalid channel %d on span %d",chan,m_span); - return; - } - if (force || !getChan(chan)->inUse()) { - Debug(m_driver,DebugAll,"Restarting B-channel %d on span %d",chan,m_span); - getChan(chan)->restart(outgoing); - } -} - -void PriSpan::ringChan(int chan, pri_event_ring &ev) -{ - if (chan == -1) - chan = findEmptyChan(); - if (!validChan(chan)) { - Debug(DebugInfo,"Ring on invalid channel %d on span %d",chan,m_span); - ::pri_hangup(pri(),ev.call,PRI_CAUSE_CHANNEL_UNACCEPTABLE); - ::pri_destroycall(pri(),ev.call); - return; - } - Debug(m_driver,DebugInfo,"Ring on channel %d on span %d",chan,m_span); - Debug(m_driver,DebugInfo,"caller='%s' callerno='%s' callingplan=%d", - ev.callingname,ev.callingnum,ev.callingplan); - Debug(m_driver,DebugInfo,"callednum='%s' redirectnum='%s' calledplan=%d", - ev.callednum,ev.redirectingnum,ev.calledplan); - Debug(m_driver,DebugInfo,"type=%d complete=%d format='%s'", - ev.ctype,ev.complete,lookup(ev.layer1,dict_str2law,"unknown")); - PriChan* c = getChan(chan); - if (c) - c->ring(ev); -} - -void PriSpan::infoChan(int chan, pri_event_ring &ev) -{ - if (!validChan(chan)) { - Debug(DebugInfo,"Info on invalid channel %d on span %d",chan,m_span); - return; - } - Debug(m_driver,DebugInfo,"info on channel %d on span %d",chan,m_span); - Debug(m_driver,DebugInfo,"caller='%s' callerno='%s' callingplan=%d", - ev.callingname,ev.callingnum,ev.callingplan); - Debug(m_driver,DebugInfo,"callednum='%s' redirectnum='%s' calledplan=%d", - ev.callednum,ev.redirectingnum,ev.calledplan); - getChan(chan)->gotDigits(ev.callednum,true); -} - -void PriSpan::digitsChan(int chan, const char* digits) -{ - if (!validChan(chan)) { - Debug(DebugInfo,"Digits on invalid channel %d on span %d",chan,m_span); - return; - } - getChan(chan)->gotDigits(digits,false); -} - -void PriSpan::hangupChan(int chan,pri_event_hangup &ev) -{ - if (!validChan(chan)) { - Debug(DebugInfo,"Hangup on invalid channel %d on span %d",chan,m_span); - return; - } - Debug(m_driver,DebugInfo,"Hanging up channel %d on span %d",chan,m_span); - getChan(chan)->hangup(ev.cause); -} - -void PriSpan::ackChan(int chan) -{ - if (!validChan(chan)) { - Debug(DebugInfo,"ACK on invalid channel %d on span %d",chan,m_span); - return; - } - Debug(m_driver,DebugInfo,"ACKnowledging channel %d on span %d",chan,m_span); - getChan(chan)->setTimeout(0); -} - -void PriSpan::answerChan(int chan) -{ - if (!validChan(chan)) { - Debug(DebugInfo,"ANSWER on invalid channel %d on span %d",chan,m_span); - return; - } - Debug(m_driver,DebugInfo,"ANSWERing channel %d on span %d",chan,m_span); - getChan(chan)->answered(); -} - -void PriSpan::proceedingChan(int chan) -{ - if (!validChan(chan)) { - Debug(DebugInfo,"Proceeding on invalid channel %d on span %d",chan,m_span); - return; - } - Debug(m_driver,DebugInfo,"Extending timeout on channel %d on span %d",chan,m_span); - getChan(chan)->extTimeout(120000000); - Engine::enqueue(getChan(chan)->message("call.progress")); -} - -void PriSpan::ringingChan(int chan) -{ - if (!validChan(chan)) { - Debug(DebugInfo,"Ringing on invalid channel %d on span %d",chan,m_span); - return; - } - Debug(m_driver,DebugInfo,"Extending timeout on channel %d on span %d",chan,m_span); - getChan(chan)->extTimeout(120000000); - Engine::enqueue(getChan(chan)->message("call.ringing")); -} - -PriSource::PriSource(PriChan *owner, const char* format, unsigned int bufsize) - : DataSource(format), - m_owner(owner), m_buffer(0,bufsize) -{ - Debug(m_owner,DebugAll,"PriSource::PriSource(%p,'%s',%u) [%p]",owner,format,bufsize,this); -} - -PriSource::~PriSource() -{ - Debug(m_owner,DebugAll,"PriSource::~PriSource() [%p]",this); -} - -PriConsumer::PriConsumer(PriChan *owner, const char* format, unsigned int bufsize) - : DataConsumer(format), - m_owner(owner), m_buffer(0,bufsize) -{ - Debug(m_owner,DebugAll,"PriConsumer::PriConsumer(%p,'%s',%u) [%p]",owner,format,bufsize,this); -} - -PriConsumer::~PriConsumer() -{ - Debug(m_owner,DebugAll,"PriConsumer::~PriConsumer() [%p]",this); -} - -PriChan::PriChan(const PriSpan *parent, int chan, unsigned int bufsize) - : Channel(parent->driver()), - m_span(const_cast(parent)), m_chan(chan), m_ring(false), - m_timeout(0), m_call(0), m_bufsize(bufsize) -{ - Debug(this,DebugAll,"PriChan::PriChan(%p,%d,%u) [%p]",parent,chan,bufsize,this); - // I hate counting from one... - m_abschan = m_chan+m_span->chan1()-1; - m_isdn = true; - m_address << m_span->span() << "/" << m_chan << "/" << m_abschan; - status(chanStatus()); -} - -PriChan::~PriChan() -{ - Debug(this,DebugAll,"PriChan::~PriChan() [%p] %d",this,m_chan); - hangup(PRI_CAUSE_NORMAL_UNSPECIFIED); -} - -void PriChan::disconnected(bool final, const char *reason) -{ - Debugger debug("PriChan::disconnected()", " '%s' [%p]",reason,this); - if (!final) { - Message* m = message("chan.disconnected"); - m_targetid.clear(); - m->addParam("span",String(m_span->span())); - m->addParam("channel",String(m_chan)); - m->addParam("reason",reason); - Engine::enqueue(m); - } - m_span->lock(); - hangup(PRI_CAUSE_NORMAL_CLEARING); - m_span->unlock(); -} - -bool PriChan::nativeConnect(DataEndpoint *peer) -{ - return false; -} - -const char *PriChan::chanStatus() const -{ - if (m_ring) - return "ringing"; - if (m_call) - return m_timeout ? "calling" : "connected"; - return m_span->outOfOrder() ? "alarm" : "idle"; -} - -void PriChan::idle() -{ - if (m_timeout && (Time::now() > m_timeout)) { - Debug(this,DebugWarn,"Timeout %s channel %s (%s)", - chanStatus(),id().c_str(),address().c_str()); - m_timeout = 0; - hangup(PRI_CAUSE_RECOVERY_ON_TIMER_EXPIRE); - } -} - -void PriChan::restart(bool outgoing) -{ - disconnect("restart"); - closeData(); - if (outgoing) - ::pri_reset(m_span->pri(),m_chan); -} - -void PriChan::closeData() -{ - m_span->lock(); - // remove all types of data nodes - clearEndpoint(); - m_span->unlock(); -} - -bool PriChan::progress() -{ - if (!m_call) { - Debug(this,DebugWarn,"Progress request on %s channel %s (%s)", - chanStatus(),id().c_str(),address().c_str()); - return false; - } - Debug(this,DebugInfo,"Progressing on %s (%s)",id().c_str(),address().c_str()); -#ifdef PRI_PROGRESS - ::pri_progress(m_span->pri(),m_call,m_chan,1); -#endif - return true; -} - -bool PriChan::ringing() -{ - if (!m_call) { - Debug(this,DebugWarn,"Ringing request on %s channel %s (%s)", - chanStatus(),id().c_str(),address().c_str()); - return false; - } - Debug(this,DebugInfo,"Answering on %s (%s)",id().c_str(),address().c_str()); - ::pri_acknowledge(m_span->pri(),m_call,m_chan,1); - return true; -} - -bool PriChan::answer() -{ - if (!m_ring) { - Debug(this,DebugWarn,"Answer request on %s channel %s (%s)", - chanStatus(),id().c_str(),address().c_str()); - return false; - } - m_ring = false; - m_timeout = 0; - status(chanStatus()); - Debug(this,DebugInfo,"Answering on %s (%s)",id().c_str(),address().c_str()); - ::pri_answer(m_span->pri(),m_call,m_chan,!m_isdn); - return true; -} - -void PriChan::goneUp() -{ - status(chanStatus()); -} - -void PriChan::hangup(int cause) -{ - if (!cause) - cause = PRI_CAUSE_INVALID_MSG_UNSPECIFIED; - const char *reason = pri_cause2str(cause); - if (inUse()) - Debug(this,DebugInfo,"Hanging up %s (%s) in state %s: %s (%d)", - id().c_str(),address().c_str(),chanStatus(),reason,cause); - timeout(0); - maxcall(0); - m_timeout = 0; - m_targetid.clear(); - disconnect(lookup(cause,dict_str2cause,reason)); - closeData(); - m_ring = false; - if (m_call) { - ::pri_hangup(m_span->pri(),m_call,cause); - ::pri_destroycall(m_span->pri(),m_call); - m_call = 0; - Message *m = message("chan.hangup"); - m->addParam("span",String(m_span->span())); - m->addParam("channel",String(m_chan)); - m->addParam("reason",pri_cause2str(cause)); - Engine::enqueue(m); - } - m_answered = false; - m_billid.clear(); - status(chanStatus()); -} - -void PriChan::answered() -{ - if (!m_call) { - Debug(this,DebugWarn,"Answer detected on %s channel %s (%s)", - chanStatus(),id().c_str(),address().c_str()); - return; - } - m_timeout = 0; - m_answered = true; - status(chanStatus()); - Debug(this,DebugInfo,"Remote answered on %s (%s)",id().c_str(),address().c_str()); - maxcall(0); - dataChanged(); - Message *m = message("call.answered"); - m->addParam("span",String(m_span->span())); - m->addParam("channel",String(m_chan)); - Engine::enqueue(m); -} - -void PriChan::gotDigits(const char *digits, bool overlapped) -{ - if (null(digits)) { - Debug(this,DebugMild,"Received empty digits string in mode %s channel %s (%s)", - (overlapped ? "overlapped" : "keypad"),id().c_str(),address().c_str()); - return; - } - Message *m = message("chan.dtmf"); - m->addParam("span",String(m_span->span())); - m->addParam("channel",String(m_chan)); - m->addParam("text",digits); - if (overlapped) - m->addParam("overlapped","yes"); - Engine::enqueue(m); -} - -void PriChan::sendDigit(char digit) -{ - if (!m_call) - return; -#ifdef PRI_KEYPAD_FACILITY_TX - if (isAnswered()) { - char buf[2]; - buf[0] = digit; - buf[1] = '\0'; - ::pri_keypad_facility(m_span->pri(),m_call,buf); - return; - } -#endif - ::pri_information(m_span->pri(),m_call,digit); -} - -bool PriChan::call(Message &msg, const char *called) -{ - if (m_span->outOfOrder()) { - Debug(this,DebugMild,"Span %d is out of order, failing call",m_span->span()); - msg.setParam("error","offline"); - return false; - } - if (!called) - called = msg.getValue("called"); - Debug(this,DebugInfo,"Calling '%s' on %s (%s)", - called,id().c_str(),address().c_str()); - int complete = 1; - if (m_span->overlapped()) { - // if asked explicitely or number is too short mark it as incomplete - if (msg.getBoolValue("overlapped",!called || (m_span->overlapped() < ::strlen(called)))) - complete = 0; - } - int layer1 = msg.getIntValue("format",dict_str2law,m_span->layer1()); - hangup(PRI_CAUSE_PRE_EMPTED); - setOutgoing(true); - CallEndpoint *ch = static_cast(msg.userData()); - if (ch) { - openData(lookup(layer1,dict_str2law),msg.getIntValue("cancelecho",dict_numtaps)); - if (connect(ch,msg.getValue("reason"))) { - msg.setParam("peerid",id()); - callConnect(msg); - } - m_targetid = msg.getValue("id"); - msg.setParam("targetid",id()); - } - else - msg.userData(this); - m_inband = msg.getBoolValue("dtmfinband",m_span->inband()); - m_detect = msg.getBoolValue("dtmfdetect",m_span->detect()); - char *caller = (char *)msg.getValue("caller"); - int callerplan = msg.getIntValue("callerplan",dict_str2dplan,m_span->dplan()); - char *callername = (char *)msg.getValue("callername"); - int callerpres = m_span->pres(); - String tmp = msg.getValue("screened"); - if (tmp.isBoolean()) - callerpres = (callerpres & ~SCREENING_BIT_MASK) | - (tmp.toBoolean() ? SCREENING_USER_PASSED : SCREENING_USER_NOT_SCREENED); - tmp = msg.getValue("privacy"); - if (tmp && tmp.toBoolean(true)) - callerpres = (callerpres & ~PRESENTATION_BIT_MASK) | PRESENTATION_RESTRICTED; - callerpres = msg.getIntValue("presentation",dict_str2pres,callerpres); - int calledplan = msg.getIntValue("calledplan",dict_str2dplan,m_span->dplan()); - Debug(this,DebugAll,"Caller='%s' name='%s' plan=%s pres=%s, Called plan=%s", - caller,callername,lookup(callerplan,dict_str2dplan), - lookup(callerpres,dict_str2pres),lookup(calledplan,dict_str2dplan)); - m_call =::pri_new_call(span()->pri()); -#ifdef PRI_DUMP_INFO - struct pri_sr *req = ::pri_sr_new(); - ::pri_sr_set_bearer(req,0/*transmode*/,layer1); - ::pri_sr_set_channel(req,m_chan,1/*exclusive*/,!m_isdn); - ::pri_sr_set_caller(req,caller,callername,callerplan,callerpres); - ::pri_sr_set_called(req,(char *)called,calledplan,complete); - ::q931_setup(span()->pri(),m_call,req); -#else - ::pri_call(m_span->pri(),m_call,0/*transmode*/,m_chan,1/*exclusive*/,!m_isdn, - caller,callerplan,callername,callerpres,(char *)called,calledplan,layer1 - ); -#endif - setTimeout(30000000); - status(chanStatus()); - setMaxcall(msg); - Message* m = message("chan.startup",msg); - m->setParam("caller",msg.getValue("caller")); - m->setParam("called",msg.getValue("called")); - m->setParam("billid",msg.getValue("billid")); - m->addParam("span",String(m_span->span())); - m->addParam("channel",String(m_chan)); - m->addParam("direction","outgoing"); - Engine::enqueue(m); - return true; -} - -void PriChan::ring(pri_event_ring &ev) -{ - q931_call *call = ev.call; - if (!call) { - hangup(PRI_CAUSE_WRONG_CALL_STATE); - return; - } - - setTimeout(180000000); - setOutgoing(false); - m_billid.clear(); - m_billid << Engine::runId() << "-" << allocId(); - m_call = call; - m_ring = true; - status(chanStatus()); -#ifdef PRI_PROCEEDING_FULL - ::pri_proceeding(m_span->pri(),m_call,m_chan,1); -#else - // we signal ringing without media if the library doesn't know any better - ::pri_acknowledge(m_span->pri(),m_call,m_chan,0); -#endif - Message* m = message("chan.startup"); - m->addParam("span",String(m_span->span())); - m->addParam("channel",String(m_chan)); - m->addParam("direction","incoming"); - Engine::enqueue(m); - - m_inband = m_span->inband(); - m_detect = m_span->detect(); - openData(lookup(ev.layer1,dict_str2law),0); - - m = message("call.preroute"); - m->addParam("span",String(m_span->span())); - m->addParam("channel",String(m_chan)); - if (m_span->overlapped() && !ev.complete && (::strlen(ev.callednum) < m_span->overlapped())) { - ::pri_need_more_info(m_span->pri(),m_call,m_chan,!isISDN()); - m->addParam("overlapped","yes"); - } - if (ev.callingnum[0]) - m->addParam("caller",ev.callingnum); - if (ev.callednum[0]) - m->addParam("called",ev.callednum); - switch (ev.callingpres & PRESENTATION_BIT_MASK) { - case PRESENTATION_RESTRICTED: - m->addParam("privacy",String::boolText(true)); - break; - case PRESENTATION_ALLOWED: - m->addParam("privacy",String::boolText(false)); - break; - } - switch (ev.callingpres & SCREENING_BIT_MASK) { - case SCREENING_USER_PASSED: - m->addParam("screened","yes"); - break; - case SCREENING_USER_NOT_SCREENED: - m->addParam("screened","no"); - break; - } - const char* dataLaw = "slin"; - switch (ev.layer1) { - case PRI_LAYER_1_ALAW: - dataLaw = "alaw"; - break; - case PRI_LAYER_1_ULAW: - dataLaw = "mulaw"; - break; - } - m->addParam("format",dataLaw); - if (!startRouter(m)) - hangup(PRI_CAUSE_SWITCH_CONGESTION); -} - -void PriChan::dataChanged() -{ -} - -void PriChan::callAccept(Message& msg) -{ - Debug(this,DebugAll,"PriChan::callAccept() [%p]",this); - extTimeout(180000000); - Channel::callAccept(msg); -} - -void PriChan::callRejected(const char* error, const char* reason, const Message* msg) -{ - if (m_span->overlapped() && error && msg && msg->getBoolValue("overlapped")) { - // call was using overlapped dialing, check for incomplete numbers - if (!::strcmp(error,"incomplete")) { - ::pri_need_more_info(m_span->pri(),m_call,m_chan,!isISDN()); - return; - } - } - int cause = lookup(error,dict_str2cause,PRI_CAUSE_NETWORK_OUT_OF_ORDER); - Channel::callRejected(error,reason,msg); - hangup(cause); -} - -bool PriChan::msgProgress(Message& msg) -{ - if (!progress()) - return true; - return Channel::msgProgress(msg); -} - -bool PriChan::msgRinging(Message& msg) -{ - if (!ringing()) - return true; - return Channel::msgRinging(msg); -} - -bool PriChan::msgAnswered(Message& msg) -{ - if (!answer()) - return true; - dataChanged(); - return Channel::msgAnswered(msg); -} - -bool PriChan::msgTone(Message& msg, const char* tone) -{ - if (null(tone)) - return false; - if (m_inband && dtmfInband(tone)) - return true; - // if we failed try to send as signalling anyway - while (*tone) - sendDigit(*tone++); - return true; -} - -bool PriChan::msgText(Message& msg, const char* text) -{ - return false; -} - -bool PriChan::msgDrop(Message& msg, const char* reason) -{ - if (inUse()) { - hangup(PRI_CAUSE_INTERWORKING); - return true; - } - return false; -} - - -bool PriDriver::msgExecute(Message& msg, String& dest) -{ - Regexp r("^\\([^/]*\\)/\\?\\(.*\\)$"); - if (!dest.matches(r)) - return false; - if (!msg.userData()) { - Debug(DebugWarn,"Pri call found but no data channel!"); - return false; - } - String chan = dest.matchString(1); - String num = dest.matchString(2); - DDebug(this,DebugInfo,"Found call to pri chan='%s' name='%s'", - chan.c_str(),num.c_str()); - PriChan *c = 0; - - r = "^\\([0-9]\\+\\)-\\([0-9]*\\)$"; - Lock lock(this); - if (chan.matches(r)) - c = findFree(chan.matchString(1).toInteger(), - chan.matchString(2).toInteger(65535)); - else if ((chan[0] < '0') || (chan[0] > '9')) - c = findFree(chan); - else - c = findFree(chan.toInteger(-1)); - - if (c) { - Debug(this,DebugInfo,"Will call '%s' on chan %s (%s)", - num.c_str(),c->id().c_str(),c->address().c_str()); - return c->call(msg,num); - } - else { - Debug(this,DebugMild,"Found no free channel '%s'",chan.c_str()); - msg.setParam("error","congestion"); - } - return false; -} - -void PriDriver::dropAll() -{ - Debug(this,DebugInfo,"Dropping all %s calls",name().c_str()); - lock(); - const ObjList *l = &m_spans; - for (; l; l=l->next()) { - PriSpan *s = static_cast(l->get()); - if (s) { - for (int n=1; n<=s->chans(); n++) { - PriChan *c = s->getChan(n); - if (c) - c->hangup(PRI_CAUSE_INTERWORKING); - } - } - } - unlock(); -} - -u_int8_t PriDriver::s_bitswap[256]; - -bool PriDriver::s_init = true; - - -PriDriver::PriDriver(const char* name) - : Driver(name,"fixchans") -{ - varchan(false); - if (s_init) { - s_init = false; - for (unsigned int c = 0; c <= 255; c++) { - u_int8_t v = 0; - for (int b = 0; b <= 7; b++) - if (c & (1 << b)) - v |= (0x80 >> b); - s_bitswap[c] = v; - } - ::pri_set_error(pri_err_cb); - ::pri_set_message(pri_msg_cb); - } -} - -PriDriver::~PriDriver() -{ -} - -PriSpan* PriDriver::findSpan(int chan) -{ - const ObjList *l = &m_spans; - for (; l; l=l->next()) { - PriSpan *s = static_cast(l->get()); - if (s && s->belongs(chan)) - return s; - } - return 0; -} - -PriChan* PriDriver::findFree(int first, int last) -{ - DDebug(this,DebugAll,"PriDriver::findFree(%d,%d)",first,last); - // see first if we have an exact request - if (first > 0 && last < 0) { - PriSpan *s = findSpan(first); - return s ? s->getChan(first - s->chan1() + 1) : 0; - } - if (last < 0) - last = 65535; - const ObjList *l = &m_spans; - for (; l; l=l->next()) { - PriSpan *s = static_cast(l->get()); - if (s) { - Debug(this,DebugAll,"Searching for free chan in span %d [%p]", - s->span(),s); - int c = s->findEmptyChan(first,last); - if (c > 0) - return s->getChan(c); - if (s->belongs(last)) - break; - } - } - return 0; -} - -PriChan* PriDriver::findFree(const String& group) -{ - ObjList* lst = m_groups.find(group); - if (!lst) - return 0; - ChanGroup* grp = static_cast(lst->get()); - if (!grp) - return 0; - int first = 0, last = 0, used = 0; - grp->getRange(first,last,used); - PriChan* c = (used < last) ? findFree(used+1,last) : 0; - if (!c) - c = (first <= used) ? findFree(first,used) : 0; - if (!c) - return 0; - grp->setUsed(c->absChan()); - return c; -} - -bool PriDriver::isBusy() const -{ - const ObjList *l = &m_spans; - for (; l; l=l->next()) { - PriSpan *s = static_cast(l->get()); - if (s) { - for (int n=1; n<=s->chans(); n++) { - PriChan *c = s->getChan(n); - if (c && c->inUse()) - return true; - } - } - } - return false; -} - -void PriDriver::statusModule(String& str) -{ - Driver::statusModule(str); - String sp; - const ObjList *l = &m_spans; - for (; l; l=l->next()) { - PriSpan *s = static_cast(l->get()); - if (s) - sp.append(String(s->chans()),"|"); - } - str.append("spans=",",") << m_spans.count(); - if (sp) - str.append("spanlen=",",") << sp; - str.append("groups=",",") << m_groups.count(); -} - -void PriDriver::statusParams(String& str) -{ - Driver::statusParams(str); - int i = 0; - int u = 0; - const ObjList *l = &m_spans; - for (; l; l=l->next()) { - PriSpan *s = static_cast(l->get()); - if (s && !s->outOfOrder()) { - for (int n=1; n<=s->chans(); n++) { - PriChan *c = s->getChan(n); - if (c) { - if (c->inUse()) - u++; - else - i++; - } - } - } - } - str.append("idle=",",") << i; - str.append("used=",",") << u; -} - -void PriDriver::netParams(Configuration& cfg, const String& sect, int chans, int* netType, int* swType, int* dChan) -{ - if (netType) - *netType = cfg.getIntValue(sect,"type",dict_str2type,PRI_NETWORK); - if (swType) - *swType = cfg.getIntValue(sect,"swtype",dict_str2switch,PRI_SWITCH_UNKNOWN); - if (dChan) { - int dchan = -1; - // guess where we may have a D channel - switch (chans) { - case 3: // BRI ISDN - dchan = 3; - break; - case 24: // T1 with CCS - dchan = 24; - break; - case 31: // EuroISDN - dchan = 16; - break; - } - *dChan = cfg.getIntValue(sect,"dchan", dchan); - } -} - -void PriDriver::init(const char* configName) -{ - Configuration cfg(Engine::configFile(configName)); - s_buflen = cfg.getIntValue("general","buflen",160); - if (!m_spans.count()) { - int chan1 = 1; - for (int span = 1;;span++) { - String sect("span "); - sect << span; - int num = cfg.getIntValue(sect,"chans",-1); - if (num < 0) - break; - if (num) { - chan1 = cfg.getIntValue(sect,"first",chan1); - if (cfg.getBoolValue(sect,"enabled",true)) - createSpan(this,span,chan1,num,cfg,sect); - chan1 += num; - } - } - if (m_spans.count()) { - Output("Created %d spans",m_spans.count()); - unsigned int n = cfg.sections(); - for (unsigned int i = 0; i < n; i++) { - const NamedList* sect = cfg.getSection(i); - if (!sect) - continue; - String s(*sect); - if (s.startSkip("group") && sect->getBoolValue("enabled",true)) - m_groups.append(new ChanGroup(s,sect,chan1-1)); - } - if (m_groups.count()) - Output("Created %d groups",m_groups.count()); - setup(); - } - else - Output("No spans created, module not activated"); - } -} - -/* vi: set ts=8 sw=4 sts=4 noet: */ diff --git a/modules/libypri.h b/modules/libypri.h deleted file mode 100644 index 76102e05..00000000 --- a/modules/libypri.h +++ /dev/null @@ -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 - -extern "C" { -#include -} - -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: */ diff --git a/modules/mktestlinks.sh b/modules/mktestlinks.sh deleted file mode 100644 index 6b9a3a10..00000000 --- a/modules/mktestlinks.sh +++ /dev/null @@ -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 diff --git a/modules/server/.cvsignore b/modules/server/.cvsignore new file mode 100644 index 00000000..20aaabb7 --- /dev/null +++ b/modules/server/.cvsignore @@ -0,0 +1,9 @@ +Makefile +core* +*.o +*.a +*.so +*.yate +*.orig +*~ +.*.swp diff --git a/modules/server/heartbeat.cpp b/modules/server/heartbeat.cpp new file mode 100644 index 00000000..f6112b09 --- /dev/null +++ b/modules/server/heartbeat.cpp @@ -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 + +#include + +#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: */ diff --git a/modules/server/mgcpca.cpp b/modules/server/mgcpca.cpp new file mode 100644 index 00000000..f798acb5 --- /dev/null +++ b/modules/server/mgcpca.cpp @@ -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 +#include + +#include + +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 sendSync(MGCPMessage* mm, const SocketAddr& address); + void clearConn(); +protected: + virtual bool nativeConnect(DataEndpoint* peer); +private: + void addParams(MGCPMessage* mm); + MGCPTransaction* m_tr; + RefPointer 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(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 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 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(this)); + m_tr = tr; + while (m_tr == tr) + Thread::msleep(10); + RefPointer 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(l->get()); + if (p && (p->getCall() == conn) && (p->name() == media)) + return const_cast(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(l->get()); + if (p && (p->id() == id)) + return const_cast(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(l->get()); + if (p && (p->ntfyId() == id)) + return const_cast(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(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(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: */ diff --git a/modules/server/mgcpgw.cpp b/modules/server/mgcpgw.cpp new file mode 100644 index 00000000..e2481f91 --- /dev/null +++ b/modules/server/mgcpgw.cpp @@ -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 +#include + +#include + +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 findConn(const String* id, MGCPChan::IdType type); + inline RefPointer 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 chan = YOBJECT(MGCPChan,static_cast(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,¶ms); + 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,¶ms); + 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,¶ms); +} + +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(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,¶ms); + return true; + } + if (mm->name() == "MDCX") { + NamedString* param = mm->params.getParam("z2"); + if (param) { + // native connect requested + RefPointer 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,¶ms); + return true; + } + if (mm->name() == "AUCX") { + tr->setResponse(200,¶ms); + 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(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(item->get())) && ok; + delete lst; + } + tr->setResponse(ok ? 200 : 538,¶ms); + 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,¶ms); + DummyCall* dummy = new DummyCall; + connect(dummy); + dummy->deref(); + deref(); + return true; + } + m_tr = tr; + tr->userData(static_cast(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 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(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 chan = static_cast(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: */ diff --git a/modules/server/mrcpspeech.cpp b/modules/server/mrcpspeech.cpp new file mode 100644 index 00000000..31e37a9f --- /dev/null +++ b/modules/server/mrcpspeech.cpp @@ -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 + +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(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(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(msg.userObject("DataEndpoint")); + CallEndpoint* ch = static_cast(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 conn = static_cast(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: */ diff --git a/modules/test/.cvsignore b/modules/test/.cvsignore index f614520d..b9910d4c 100644 --- a/modules/test/.cvsignore +++ b/modules/test/.cvsignore @@ -1,5 +1,5 @@ Makefile -YateLocal.mak +YateLocal* core* *.o *.a diff --git a/modules/test/Makefile.in b/modules/test/Makefile.in index d70cc749..b2e4fb5f 100644 --- a/modules/test/Makefile.in +++ b/modules/test/Makefile.in @@ -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 $@ $^ diff --git a/modules/wpchan.cpp b/modules/wpchan.cpp deleted file mode 100644 index 3a312ee1..00000000 --- a/modules/wpchan.cpp +++ /dev/null @@ -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 - -#ifdef _WINDOWS -#error This module is not for Windows -#else - -extern "C" { - -#define INVALID_HANDLE_VALUE (-1) -#define __LINUX__ -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_WANPIPE_HWEC -#include -#endif - -}; - -#include -#include -#include -#include - -#include -#include - -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(m_owner)->m_wp_s = this; -} - -WpSource::~WpSource() -{ - Debug(m_owner,DebugAll,"WpSource::~WpSource() [%p]",this); - static_cast(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(m_owner)->m_wp_c = this; -} - -WpConsumer::~WpConsumer() -{ - Debug(m_owner,DebugAll,"WpConsumer::~WpConsumer() [%p]",this); - static_cast(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(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: */ diff --git a/modules/wpchanw.cpp b/modules/wpchanw.cpp deleted file mode 100644 index e71eda52..00000000 --- a/modules/wpchanw.cpp +++ /dev/null @@ -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 - -#ifndef _WINDOWS -#error This module is only for Windows -#else - -extern "C" { - -#define MSG_NOSIGNAL 0 -#define MSG_DONTWAIT 0 -#include -#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 -#include -#include -#include - -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(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(m_owner)->m_wp_s = this; -} - -WpSource::~WpSource() -{ - Debug(m_owner,DebugAll,"WpSource::~WpSource() [%p]",this); - static_cast(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(m_owner)->m_wp_c = this; -} - -WpConsumer::~WpConsumer() -{ - Debug(m_owner,DebugAll,"WpConsumer::~WpConsumer() [%p]",this); - static_cast(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(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(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: */ diff --git a/modules/zapchan.cpp b/modules/zapchan.cpp deleted file mode 100644 index 2d35afb0..00000000 --- a/modules/zapchan.cpp +++ /dev/null @@ -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 - -#ifdef _WINDOWS -#error This module is not for Windows -#else - -extern "C" { -#ifdef NEW_ZAPTEL_LOCATION -#define __LINUX__ -#include -#else -#include -#endif -}; - -#include -#include -#include -#include - -#ifndef _WINDOWS -#include -#include -#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(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(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(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: */ diff --git a/packing/.cvsignore b/packing/.cvsignore new file mode 100644 index 00000000..6e6f6e26 --- /dev/null +++ b/packing/.cvsignore @@ -0,0 +1,8 @@ +Makefile +YateLocal.mak +core* +*.o +*.a +*.orig +*~ +.*.swp diff --git a/packing/rpm/.cvsignore b/packing/rpm/.cvsignore new file mode 100644 index 00000000..6e6f6e26 --- /dev/null +++ b/packing/rpm/.cvsignore @@ -0,0 +1,8 @@ +Makefile +YateLocal.mak +core* +*.o +*.a +*.orig +*~ +.*.swp diff --git a/packing/yate.logrotate b/packing/yate.logrotate index 4f1ad580..fa249cb4 100644 --- a/packing/yate.logrotate +++ b/packing/yate.logrotate @@ -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 diff --git a/run.in b/run.in index 7b807130..dc264535 100644 --- a/run.in +++ b/run.in @@ -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 "$@" diff --git a/share/.cvsignore b/share/.cvsignore new file mode 100644 index 00000000..fb80248c --- /dev/null +++ b/share/.cvsignore @@ -0,0 +1,7 @@ +Makefile +YateLocal* +.xvpics +core* +*.orig +*~ +.*.swp diff --git a/share/Makefile.in b/share/Makefile.in new file mode 100644 index 00000000..ed2f50fd --- /dev/null +++ b/share/Makefile.in @@ -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 diff --git a/share/help/.cvsignore b/share/help/.cvsignore index 6f7092ac..fb80248c 100644 --- a/share/help/.cvsignore +++ b/share/help/.cvsignore @@ -1,4 +1,5 @@ Makefile +YateLocal* .xvpics core* *.orig diff --git a/share/help/Makefile.in b/share/help/Makefile.in index 3df6d2ba..9ce09844 100644 --- a/share/help/Makefile.in +++ b/share/help/Makefile.in @@ -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 diff --git a/share/scripts/.cvsignore b/share/scripts/.cvsignore index be0f8d92..fb80248c 100644 --- a/share/scripts/.cvsignore +++ b/share/scripts/.cvsignore @@ -1,5 +1,6 @@ Makefile -YateLocal.mak +YateLocal* +.xvpics core* *.orig *~ diff --git a/share/scripts/Makefile.in b/share/scripts/Makefile.in index c658889d..2ac11979 100644 --- a/share/scripts/Makefile.in +++ b/share/scripts/Makefile.in @@ -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 diff --git a/share/skins/.cvsignore b/share/skins/.cvsignore index 6f7092ac..fb80248c 100644 --- a/share/skins/.cvsignore +++ b/share/skins/.cvsignore @@ -1,4 +1,5 @@ Makefile +YateLocal* .xvpics core* *.orig diff --git a/share/skins/Makefile.in b/share/skins/Makefile.in index 043f0d53..b14112e6 100644 --- a/share/skins/Makefile.in +++ b/share/skins/Makefile.in @@ -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 diff --git a/yate-config.in b/yate-config.in index bac33e57..e03b644c 100644 --- a/yate-config.in +++ b/yate-config.in @@ -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 diff --git a/yatengine.h b/yatengine.h index 09b7c424..4cd38d30 100644 --- a/yatengine.h +++ b/yatengine.h @@ -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;