Directory reorder #3
git-svn-id: http://yate.null.ro/svn/yate/trunk@1476 acf43c95-373e-0410-b603-e72c3f656dc1
This commit is contained in:
parent
dc7b6f93d0
commit
b42a894ddc
80
Makefile.in
80
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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
Makefile
|
||||
YateLocal.mak
|
||||
YateLocal*
|
||||
core*
|
||||
yate-*
|
||||
*.o
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
Makefile
|
||||
YateLocal.mak
|
||||
YateLocal*
|
||||
core*
|
||||
*.o
|
||||
*.a
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
#include <yatephone.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include "../contrib/gtk2/gtk2client.h"
|
||||
#include "gtk2/gtk2client.h"
|
||||
|
||||
using namespace TelEngine;
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 206 B |
Binary file not shown.
After Width: | Height: | Size: 313 B |
|
@ -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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
Makefile
|
||||
YateLocal.mak
|
||||
YateLocal*
|
||||
core*
|
||||
*.conf
|
||||
*.orig
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
39
configure.in
39
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<mysql.h>])
|
||||
AC_CHECK_DECLS([MYSQL_OPT_READ_TIMEOUT],[MYSQL_INC="$MYSQL_INC -DMYSQL_OPT_READ_TIMEOUT=MYSQL_OPT_READ_TIMEOUT"],,[#include<mysql.h>])
|
||||
AC_CHECK_DECLS([MYSQL_OPT_WRITE_TIMEOUT],[MYSQL_INC="$MYSQL_INC -DMYSQL_OPT_WRITE_TIMEOUT=MYSQL_OPT_WRITE_TIMEOUT"],,[#include<mysql.h>])
|
||||
CPPFLAGS="$save_CPPFLAGS"
|
||||
CPPFLAGS=$save_CPPFLAGS
|
||||
fi
|
||||
fi
|
||||
AC_SUBST(HAVE_MYSQL)
|
||||
|
@ -759,29 +759,32 @@ AC_SUBST(KDOC_BIN)
|
|||
|
||||
m4_sinclude(./YateLocal.ac)
|
||||
|
||||
AC_CONFIG_FILES([yate.spec
|
||||
AC_CONFIG_FILES([packing/rpm/yate.spec
|
||||
yate.pc
|
||||
yateversn.h
|
||||
yateiss.inc
|
||||
Makefile
|
||||
engine/Makefile
|
||||
modules/Makefile
|
||||
modules/skin/Makefile
|
||||
modules/help/Makefile
|
||||
modules/test/Makefile
|
||||
clients/Makefile
|
||||
scripts/Makefile
|
||||
conf.d/Makefile
|
||||
contrib/ilbc/Makefile
|
||||
contrib/ysip/Makefile
|
||||
contrib/yrtp/Makefile
|
||||
contrib/yiax/Makefile
|
||||
contrib/yxml/Makefile
|
||||
contrib/yjingle/Makefile
|
||||
contrib/ypbx/Makefile
|
||||
contrib/gtk2/Makefile
|
||||
test/Makefile])
|
||||
clients/gtk2/Makefile
|
||||
libs/ilbc/Makefile
|
||||
libs/ysip/Makefile
|
||||
libs/yrtp/Makefile
|
||||
libs/yiax/Makefile
|
||||
libs/yxml/Makefile
|
||||
libs/yjingle/Makefile
|
||||
libs/ymgcp/Makefile
|
||||
libs/ysig/Makefile
|
||||
libs/ypbx/Makefile
|
||||
share/Makefile
|
||||
share/scripts/Makefile
|
||||
share/skins/Makefile
|
||||
share/help/Makefile
|
||||
conf.d/Makefile])
|
||||
AC_CONFIG_FILES([yate-config],[chmod +x yate-config])
|
||||
AC_CONFIG_FILES([run],[chmod +x run])
|
||||
CONFIGURE_FILES=`echo "$ac_config_files config.status config.log" | sed 's/yate\.spec *//'`
|
||||
CONFIGURE_FILES=`echo "$ac_config_files config.status config.log" | sed 's,packing/yate\.spec *,,'`
|
||||
AC_SUBST(CONFIGURE_FILES)
|
||||
AC_OUTPUT
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
.\" Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
.\"
|
||||
.\"
|
||||
.TH YATE-CONFIG 8 "March 2004" "YATE" "Telephony Engine"
|
||||
.TH YATE-CONFIG 8 "September 2007" "YATE" "Telephony Engine"
|
||||
.SH NAME
|
||||
\fByate-config\fP \- retrieve metainformation about the YATE telephony engine
|
||||
.SH SYNOPSIS
|
||||
|
@ -44,6 +44,15 @@ Returns the configuration files directory
|
|||
.TP
|
||||
.B \-\-modules
|
||||
Returns the modules directory
|
||||
.TP
|
||||
.B \-\-share
|
||||
Returns the base shared directory
|
||||
.TP
|
||||
.B \-\-scripts
|
||||
Returns the scripts directory
|
||||
.TP
|
||||
.B \-\-skins
|
||||
Returns the base skins directory
|
||||
.SS Compiler flags
|
||||
.TP
|
||||
.B \-\-cflags
|
||||
|
@ -67,7 +76,7 @@ All linker options \- the above two concatenated
|
|||
.SH AUTHORS
|
||||
Paul Chitescu <paulc@voip.null.ro>
|
||||
.br
|
||||
Diana Cionoiu <diana@diana.null.ro>
|
||||
Diana Cionoiu <diana@voip.null.ro>
|
||||
.SH SEE ALSO
|
||||
.BR yate (8),
|
||||
.BR pkg-config (1)
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
.\" Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
.\"
|
||||
.\"
|
||||
.TH YATE 8 "September 2005" "YATE" "Telephony Engine"
|
||||
.TH YATE 8 "September 2007" "YATE" "Telephony Engine"
|
||||
.SH NAME
|
||||
\fByate\fP \- launch the YATE telephony engine
|
||||
.SH SYNOPSIS
|
||||
|
@ -65,6 +65,9 @@ Path to conf files directory, overrides compiled-in value
|
|||
.B \-m \fIpathname\fR
|
||||
Path to modules directory, overrides compiled-in value
|
||||
.TP
|
||||
.B \-e \fIpathname\fR
|
||||
Path to shared directory, overrides compiled-in value
|
||||
.TP
|
||||
.B \-x \fIrelpath\fR
|
||||
Relative path to extra modules directory (can be repeated)
|
||||
.TP
|
||||
|
@ -145,6 +148,6 @@ when the thread terminates.
|
|||
.SH AUTHORS
|
||||
Paul Chitescu <paulc@voip.null.ro>
|
||||
.br
|
||||
Diana Cionoiu <diana@diana.null.ro>
|
||||
Diana Cionoiu <diana@voip.null.ro>
|
||||
.SH SEE ALSO
|
||||
.BR yate-config (8)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
Makefile
|
||||
YateLocal.mak
|
||||
YateLocal*
|
||||
core*
|
||||
*.o
|
||||
*.a
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
extern "C" {
|
||||
#include "tables/all.h"
|
||||
#include "all.h"
|
||||
}
|
||||
|
||||
using namespace TelEngine;
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -20,6 +20,3 @@ strip: all
|
|||
install:
|
||||
|
||||
uninstall:
|
||||
|
||||
Makefile: ./Makefile.in ../config.status
|
||||
cd .. && ./config.status
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
Makefile
|
||||
YateLocal.mak
|
||||
YateLocal*
|
||||
core*
|
||||
*.o
|
||||
*.a
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
Makefile
|
||||
YateLocal.mak
|
||||
YateLocal*
|
||||
core*
|
||||
*.o
|
||||
*.a
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
Makefile
|
||||
YateLocal*
|
||||
core*
|
||||
*.o
|
||||
*.a
|
||||
*.orig
|
||||
*~
|
||||
.*.swp
|
|
@ -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 $@ $^
|
|
@ -0,0 +1,101 @@
|
|||
/**
|
||||
* endpoint.cpp
|
||||
* Yet Another MGCP Stack
|
||||
* This file is part of the YATE Project http://YATE.null.ro
|
||||
*
|
||||
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
||||
* Copyright (C) 2004-2006 Null Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <yatemgcp.h>
|
||||
|
||||
using namespace TelEngine;
|
||||
|
||||
/**
|
||||
* MGCPEndpoint
|
||||
*/
|
||||
// Construct the id. Append itself to the engine's list
|
||||
MGCPEndpoint::MGCPEndpoint(MGCPEngine* engine, const char* user,
|
||||
const char* host, int port)
|
||||
: MGCPEndpointId(user,host,port),
|
||||
m_engine(engine)
|
||||
{
|
||||
if (!m_engine) {
|
||||
Debug(DebugNote,"Can't construct endpoint without engine [%p]",this);
|
||||
return;
|
||||
}
|
||||
m_engine->attach(this);
|
||||
}
|
||||
|
||||
// Remove itself from engine's list
|
||||
MGCPEndpoint::~MGCPEndpoint()
|
||||
{
|
||||
if (m_engine)
|
||||
m_engine->detach(this);
|
||||
}
|
||||
|
||||
// Append info about a remote endpoint controlled by or controlling this endpoint.
|
||||
// If the engine owning this endpoint is an MGCP gateway, only 1 remote peer (Call Agent) is allowed
|
||||
MGCPEpInfo* MGCPEndpoint::append(const char* endpoint, const char* host, int port)
|
||||
{
|
||||
if (!m_engine || (m_engine->gateway() && m_remote.count() >= 1))
|
||||
return 0;
|
||||
|
||||
if (!endpoint)
|
||||
endpoint = user();
|
||||
if (!port)
|
||||
port = m_engine->defaultPort(!m_engine->gateway());
|
||||
MGCPEpInfo* ep = new MGCPEpInfo(endpoint,host,port);
|
||||
if (!ep->valid() || find(ep->id()))
|
||||
TelEngine::destruct(ep);
|
||||
else
|
||||
m_remote.append(ep);
|
||||
return ep;
|
||||
}
|
||||
|
||||
// Find the info object associated with a remote peer
|
||||
MGCPEpInfo* MGCPEndpoint::find(const char* epId)
|
||||
{
|
||||
Lock lock(m_mutex);
|
||||
ObjList* obj = m_remote.find(epId);
|
||||
return obj ? static_cast<MGCPEpInfo*>(obj->get()) : 0;
|
||||
}
|
||||
|
||||
// Find the info object associated with an unique remote peer
|
||||
MGCPEpInfo* MGCPEndpoint::peer()
|
||||
{
|
||||
return (m_remote.count() == 1) ? static_cast<MGCPEpInfo*>(m_remote.get()) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* MGCPEndpointId
|
||||
*/
|
||||
// Set this endpoint id. Convert it to lower case
|
||||
void MGCPEndpointId::set(const char* endpoint, const char* host, int port)
|
||||
{
|
||||
m_id = "";
|
||||
m_endpoint = endpoint;
|
||||
m_endpoint.toLower();
|
||||
m_host = host;
|
||||
m_host.toLower();
|
||||
m_port = port;
|
||||
m_id << m_endpoint << "@" << m_host;
|
||||
if (m_port)
|
||||
m_id << ":" << m_port;
|
||||
}
|
||||
|
||||
/* vi: set ts=8 sw=4 sts=4 noet: */
|
|
@ -0,0 +1,830 @@
|
|||
/**
|
||||
* engine.cpp
|
||||
* Yet Another MGCP Stack
|
||||
* This file is part of the YATE Project http://YATE.null.ro
|
||||
*
|
||||
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
||||
* Copyright (C) 2004-2006 Null Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <yatemgcp.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
namespace TelEngine {
|
||||
|
||||
// Engine process, receive or check timeouts
|
||||
class MGCPPrivateThread : public Thread, public GenObject
|
||||
{
|
||||
public:
|
||||
enum Action {
|
||||
Process = 1,
|
||||
Receive = 2,
|
||||
};
|
||||
// Create a thread to process or receive data for the engine
|
||||
MGCPPrivateThread(MGCPEngine* engine, bool process, Thread::Priority priority);
|
||||
virtual ~MGCPPrivateThread();
|
||||
virtual void run();
|
||||
private:
|
||||
MGCPEngine* m_engine;
|
||||
Action m_action;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
using namespace TelEngine;
|
||||
|
||||
#define MAX_TRANS_ID 999999999 // Maximum length for transaction identifier
|
||||
|
||||
// Some default values. Time values are given in miliseconds
|
||||
#define RECV_BUF_LEN 1500 // Receive buffer length
|
||||
#define TR_RETRANS_INTERVAL 250
|
||||
#define TR_RETRANS_INTERVAL_MIN 100
|
||||
#define TR_RETRANS_COUNT 3
|
||||
#define TR_RETRANS_COUNT_MIN 1
|
||||
#define TR_EXTRA_TIME 30000
|
||||
#define TR_EXTRA_TIME_MIN 10000
|
||||
|
||||
|
||||
/**
|
||||
* MGCPPrivateThread
|
||||
*/
|
||||
MGCPPrivateThread::MGCPPrivateThread(MGCPEngine* engine, bool process,
|
||||
Thread::Priority priority)
|
||||
: Thread(process?"MGCP private process":"MGCP private receive",priority),
|
||||
m_engine(engine),
|
||||
m_action(process?Process:Receive)
|
||||
{
|
||||
XDebug(m_engine,DebugInfo,"MGCPPrivateThread::MGCPPrivateThread() [%p]",this);
|
||||
if (m_engine)
|
||||
m_engine->appendThread(this);
|
||||
}
|
||||
|
||||
MGCPPrivateThread::~MGCPPrivateThread()
|
||||
{
|
||||
XDebug(m_engine,DebugInfo,"MGCPPrivateThread::~MGCPPrivateThread() [%p]",this);
|
||||
if (m_engine)
|
||||
m_engine->removeThread(this);
|
||||
}
|
||||
|
||||
void MGCPPrivateThread::run()
|
||||
{
|
||||
DDebug(m_engine,DebugInfo,"%s started [%p]",currentName(),this);
|
||||
if (!m_engine)
|
||||
return;
|
||||
switch (m_action) {
|
||||
case Process:
|
||||
m_engine->runProcess();
|
||||
break;
|
||||
case Receive:
|
||||
m_engine->runReceive();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* MGCPEngine
|
||||
*/
|
||||
MGCPEngine::MGCPEngine(bool gateway, const char* name, const NamedList* params)
|
||||
: Mutex(true),
|
||||
m_gateway(gateway),
|
||||
m_initialized(false),
|
||||
m_nextId(1),
|
||||
m_address(AF_INET),
|
||||
m_maxRecvPacket(RECV_BUF_LEN),
|
||||
m_recvBuf(0),
|
||||
m_allowUnkCmd(false),
|
||||
m_retransInterval(TR_RETRANS_INTERVAL * 1000),
|
||||
m_retransCount(TR_RETRANS_COUNT),
|
||||
m_extraTime(TR_EXTRA_TIME * 1000),
|
||||
m_parseParamToLower(true),
|
||||
m_provisional(true)
|
||||
{
|
||||
debugName((name && *name) ? name : (gateway ? "mgcp_gw" : "mgcp_ca"));
|
||||
|
||||
DDebug(this,DebugAll,"MGCPEngine::MGCPEngine(). Gateway: %s [%p]",
|
||||
String::boolText(gateway),this);
|
||||
// Add known commands
|
||||
for (int i = 0; mgcp_commands[i].token; i++)
|
||||
m_knownCommands.append(new String(mgcp_commands[i].token));
|
||||
// Init
|
||||
if (params)
|
||||
initialize(*params);
|
||||
}
|
||||
|
||||
MGCPEngine::~MGCPEngine()
|
||||
{
|
||||
cleanup(false);
|
||||
if (m_recvBuf)
|
||||
delete[] m_recvBuf;
|
||||
DDebug(this,DebugAll,"MGCPEngine::~MGCPEngine()");
|
||||
}
|
||||
|
||||
// Initialize this engine
|
||||
void MGCPEngine::initialize(const NamedList& params)
|
||||
{
|
||||
int level = params.getIntValue("debuglevel");
|
||||
if (level)
|
||||
debugLevel(level);
|
||||
|
||||
m_allowUnkCmd = params.getBoolValue("allow_unknown_cmd",false);
|
||||
int val = params.getIntValue("retrans_interval",TR_RETRANS_INTERVAL);
|
||||
m_retransInterval = 1000 * (val < TR_RETRANS_INTERVAL_MIN ? TR_RETRANS_INTERVAL_MIN : val);
|
||||
val = params.getIntValue("retrans_count",TR_RETRANS_COUNT);
|
||||
m_retransCount = (val < TR_RETRANS_COUNT_MIN ? TR_RETRANS_COUNT_MIN : val);
|
||||
val = params.getIntValue("extra_time_to_live",TR_EXTRA_TIME);
|
||||
m_extraTime = 1000 * (val < TR_EXTRA_TIME_MIN ? TR_EXTRA_TIME_MIN : val);
|
||||
|
||||
if (!m_initialized) {
|
||||
val = params.getIntValue("max_recv_packet",RECV_BUF_LEN);
|
||||
m_maxRecvPacket = val < RECV_BUF_LEN ? RECV_BUF_LEN : val;
|
||||
}
|
||||
|
||||
m_parseParamToLower = params.getBoolValue("lower_case_params",true);
|
||||
m_provisional = params.getBoolValue("send_provisional",true);
|
||||
|
||||
// Bind socket if not valid
|
||||
if (!m_socket.valid()) {
|
||||
m_address.host(params.getValue("localip"));
|
||||
int port = params.getIntValue("port",-1);
|
||||
m_address.port(port < 0 ? defaultPort(gateway()) : port);
|
||||
m_socket.create(AF_INET,SOCK_DGRAM);
|
||||
|
||||
int reqlen = params.getIntValue("buffer");
|
||||
if (reqlen > 0) {
|
||||
#ifdef SO_RCVBUF
|
||||
int buflen = reqlen;
|
||||
if ((unsigned int)buflen < maxRecvPacket())
|
||||
buflen = maxRecvPacket();
|
||||
if (buflen < 4096)
|
||||
buflen = 4096;
|
||||
if (m_socket.setOption(SOL_SOCKET,SO_RCVBUF,&buflen,sizeof(buflen))) {
|
||||
buflen = 0;
|
||||
socklen_t sz = sizeof(buflen);
|
||||
if (m_socket.getOption(SOL_SOCKET,SO_RCVBUF,&buflen,&sz))
|
||||
Debug(this,DebugAll,"UDP buffer size is %d (requested %d)",buflen,reqlen);
|
||||
else
|
||||
Debug(this,DebugWarn,"Could not get UDP buffer size (requested %d)",reqlen);
|
||||
}
|
||||
else
|
||||
Debug(this,DebugWarn,"Could not set UDP buffer size %d (%d: %s)",
|
||||
buflen,m_socket.error(),::strerror(m_socket.error()));
|
||||
#else
|
||||
Debug(this,DebugMild,"Can't set socket receive buffer: unsupported feature");
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!m_socket.bind(m_address)) {
|
||||
Debug(this,DebugWarn,"Failed to bind socket to %s:%d. Error: %d: %s",
|
||||
m_address.host().safe(),m_address.port(),
|
||||
m_socket.error(),::strerror(m_socket.error()));
|
||||
m_socket.terminate();
|
||||
}
|
||||
else
|
||||
m_socket.getSockName(m_address);
|
||||
m_socket.setBlocking(false);
|
||||
}
|
||||
|
||||
// Create private threads
|
||||
if (!m_initialized) {
|
||||
Thread::Priority prio = Thread::priority(params.getValue("thread_priority"));
|
||||
int c = params.getIntValue("private_receive_threads",1);
|
||||
for (int i = 0; i < c; i++)
|
||||
(new MGCPPrivateThread(this,false,prio))->startup();
|
||||
c = params.getIntValue("private_process_threads",1);
|
||||
for (int i = 0; i < c; i++)
|
||||
(new MGCPPrivateThread(this,true,prio))->startup();
|
||||
}
|
||||
|
||||
if (debugAt(DebugAll)) {
|
||||
String tmp;
|
||||
tmp << "\r\ntype: " << (gateway() ? "Gateway" : "Call Agent");
|
||||
tmp << "\r\nbind address: " << m_address.host() << ":" << m_address.port();
|
||||
tmp << "\r\nallow_unknown_cmd: " << String::boolText(m_allowUnkCmd);
|
||||
tmp << "\r\nretrans_interval: " << m_retransInterval;
|
||||
tmp << "\r\nretrans_count: " << m_retransCount;
|
||||
tmp << "\r\nlower_case_params: " << m_parseParamToLower;
|
||||
tmp << "\r\nmax_recv_packet: " << maxRecvPacket();
|
||||
tmp << "\r\nsend_provisional: " << provisional();
|
||||
Debug(this,DebugInfo,"%s:%s",m_initialized?"Reloaded":"Initialized",tmp.c_str());
|
||||
}
|
||||
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
// Add a command to the list of known commands
|
||||
void MGCPEngine::addCommand(const char* cmd)
|
||||
{
|
||||
String* tmp = new String(cmd);
|
||||
Lock lock(this);
|
||||
tmp->toUpper();
|
||||
if (tmp->length() == 4 && !knownCommand(*tmp)) {
|
||||
Debug(this,DebugInfo,"Adding extra command %s",tmp->c_str());
|
||||
m_knownCommands.append(tmp);
|
||||
}
|
||||
else
|
||||
TelEngine::destruct(tmp);
|
||||
}
|
||||
|
||||
// Append an endpoint to this engine if not already done
|
||||
void MGCPEngine::attach(MGCPEndpoint* ep)
|
||||
{
|
||||
if (!ep)
|
||||
return;
|
||||
Lock lock(this);
|
||||
if (!m_endpoints.find(ep)) {
|
||||
m_endpoints.append(ep);
|
||||
Debug(this,DebugInfo,"Attached endpoint '%s'",ep->id().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Remove an endpoint from this engine and, optionally, remove all its transactions
|
||||
void MGCPEngine::detach(MGCPEndpoint* ep, bool del, bool delTrans)
|
||||
{
|
||||
if (!ep)
|
||||
return;
|
||||
if (del)
|
||||
delTrans = true;
|
||||
Debug(this,DebugInfo,"Detaching endpoint '%s'",ep->id().c_str());
|
||||
|
||||
Lock lock(this);
|
||||
// Remove transactions
|
||||
if (delTrans) {
|
||||
ListIterator iter(m_transactions);
|
||||
for (GenObject* o; 0 != (o = iter.get());) {
|
||||
MGCPTransaction* tr = static_cast<MGCPTransaction*>(o);
|
||||
if (ep->id() == tr->ep())
|
||||
m_transactions.remove(tr,true);
|
||||
}
|
||||
}
|
||||
m_endpoints.remove(ep,del);
|
||||
}
|
||||
|
||||
// Find an endpoint by its pointer
|
||||
MGCPEndpoint* MGCPEngine::findEp(MGCPEndpoint* ep)
|
||||
{
|
||||
Lock lock(this);
|
||||
ObjList* o = m_endpoints.find(ep);
|
||||
return o ? static_cast<MGCPEndpoint*>(o->get()) : 0;
|
||||
}
|
||||
|
||||
// Find an endpoint by its id
|
||||
MGCPEndpoint* MGCPEngine::findEp(const char* epId)
|
||||
{
|
||||
Lock lock(this);
|
||||
ObjList* o = m_endpoints.find(epId);
|
||||
return o ? static_cast<MGCPEndpoint*>(o->get()) : 0;
|
||||
}
|
||||
|
||||
// find a transaction
|
||||
MGCPTransaction* MGCPEngine::findTrans(unsigned int id, bool outgoing)
|
||||
{
|
||||
Lock lock(this);
|
||||
for (ObjList* o = m_transactions.skipNull(); o; o = o->skipNext()) {
|
||||
MGCPTransaction* tr = static_cast<MGCPTransaction*>(o->get());
|
||||
if (outgoing == tr->outgoing() && id == tr->id())
|
||||
return tr;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Generate a new id for an outgoing transaction
|
||||
unsigned int MGCPEngine::getNextId()
|
||||
{
|
||||
Lock lock(this);
|
||||
if (m_nextId < MAX_TRANS_ID)
|
||||
return m_nextId++;
|
||||
m_nextId = 1;
|
||||
return MAX_TRANS_ID;
|
||||
}
|
||||
|
||||
// Send a command message. Create a transaction for it.
|
||||
// Fail if the message is not a valid one or isn't a valid command
|
||||
MGCPTransaction* MGCPEngine::sendCommand(MGCPMessage* cmd, const SocketAddr& addr)
|
||||
{
|
||||
if (!cmd)
|
||||
return 0;
|
||||
if (!(cmd->valid() && cmd->isCommand())) {
|
||||
Debug(this,DebugNote,"Can't initiate outgoing transaction for (%p) cmd=%s",
|
||||
cmd,cmd->name().c_str());
|
||||
TelEngine::destruct(cmd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Lock lock(this);
|
||||
return new MGCPTransaction(this,cmd,true,addr);
|
||||
}
|
||||
|
||||
// Read data from the socket. Parse and process the received message
|
||||
bool MGCPEngine::receive(unsigned char* buffer, SocketAddr& addr)
|
||||
{
|
||||
if (!m_socket.valid())
|
||||
return false;
|
||||
int len = maxRecvPacket();
|
||||
int rd = m_socket.recvFrom(buffer,len,addr);
|
||||
if (rd == Socket::socketError()) {
|
||||
if (!m_socket.canRetry())
|
||||
Debug(this,DebugWarn,"Socket read error: %d: %s",
|
||||
m_socket.error(),::strerror(m_socket.error()));
|
||||
return false;
|
||||
}
|
||||
if (rd > 0)
|
||||
len = rd;
|
||||
else
|
||||
return false;
|
||||
|
||||
ObjList msgs;
|
||||
if (!MGCPMessage::parse(this,msgs,buffer,len)) {
|
||||
ObjList* o = msgs.skipNull();
|
||||
MGCPMessage* msg = static_cast<MGCPMessage*>(o?o->get():0);
|
||||
if (msg && msg->valid() && !msg->isCommand()) {
|
||||
String tmp;
|
||||
msg->toString(tmp);
|
||||
sendData(tmp,addr);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!msgs.skipNull())
|
||||
return false;
|
||||
|
||||
Lock lock(this);
|
||||
if (debugAt(DebugInfo)) {
|
||||
String tmp((const char*)buffer,len);
|
||||
Debug(this,DebugInfo,
|
||||
"Received %u message(s) from %s:%d\r\n-----\r\n%s\r\n-----",
|
||||
msgs.count(),addr.host().c_str(),addr.port(),tmp.c_str());
|
||||
}
|
||||
|
||||
// Process received message(s)
|
||||
while (true) {
|
||||
MGCPMessage* msg = static_cast<MGCPMessage*>(msgs.remove(false));
|
||||
if (!msg)
|
||||
break;
|
||||
|
||||
// Command messages may contain ACK'd incoming transaction's responses
|
||||
// See RFC 3435: 3.2.2.19 and 3.5.1
|
||||
if (msg->isCommand()) {
|
||||
String s = msg->params.getValue("k");
|
||||
if (!(s || m_parseParamToLower))
|
||||
s = msg->params.getValue("K");
|
||||
if (s) {
|
||||
unsigned int len = 0;
|
||||
unsigned int* trList = decodeAck(s,len);
|
||||
// Build an ACK message for each of ACK'd transaction response
|
||||
if (trList) {
|
||||
for (unsigned int i = 0; i < len; i++) {
|
||||
MGCPTransaction* tr = findTrans(trList[i],false);
|
||||
if (tr)
|
||||
tr->processMessage(new MGCPMessage(tr,0));
|
||||
else
|
||||
DDebug(this,DebugNote,
|
||||
"Message %s carry ACK for unknown transaction %u",
|
||||
msg->name().c_str(),trList[i]);
|
||||
}
|
||||
delete trList;
|
||||
}
|
||||
else {
|
||||
DDebug(this,DebugNote,"Message %s has invalid k: '%s' parameter",
|
||||
msg->name().c_str(),s.c_str());
|
||||
MGCPTransaction* tr = findTrans(msg->transactionId(),false);
|
||||
if (!tr)
|
||||
tr = new MGCPTransaction(this,msg,false,addr);
|
||||
tr->setResponse(400,"Bad Transaction Ack");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Outgoing transaction id namespace is different then the incoming one
|
||||
// Check message:
|
||||
// Command or response ACK: Destination is an incoming transaction
|
||||
// Response: Destination is an outgoing transaction
|
||||
bool outgoing = !(msg->isCommand() || msg->isAck());
|
||||
MGCPTransaction* tr = findTrans(msg->transactionId(),outgoing);
|
||||
if (tr) {
|
||||
tr->processMessage(msg);
|
||||
continue;
|
||||
}
|
||||
// No transaction
|
||||
if (msg->isCommand()) {
|
||||
new MGCPTransaction(this,msg,false,addr);
|
||||
continue;
|
||||
}
|
||||
DDebug(this,DebugNote,"Received response %d for unknown transaction %u",
|
||||
msg->code(),msg->transactionId());
|
||||
TelEngine::destruct(msg);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Try to get an event from a transaction.
|
||||
// If the event contains an unknown command and this engine is not allowed
|
||||
// to process such commands, calls the @ref returnEvent() method, otherwise,
|
||||
// calls the @ref processEvent() method
|
||||
bool MGCPEngine::process(u_int64_t time)
|
||||
{
|
||||
MGCPEvent* event = getEvent(time);
|
||||
if (!event)
|
||||
return false;
|
||||
if (!processEvent(event))
|
||||
returnEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Repeatedly calls receive() until the calling thread terminates
|
||||
void MGCPEngine::runReceive()
|
||||
{
|
||||
SocketAddr addr(AF_INET);
|
||||
if (m_recvBuf)
|
||||
delete[] m_recvBuf;
|
||||
m_recvBuf = new unsigned char[maxRecvPacket()];
|
||||
|
||||
while (true)
|
||||
if (!receive(m_recvBuf,addr))
|
||||
Thread::msleep(2,true);
|
||||
else
|
||||
Thread::check(true);
|
||||
}
|
||||
|
||||
// Repeatedly calls process() until the calling thread terminates
|
||||
void MGCPEngine::runProcess()
|
||||
{
|
||||
while (true)
|
||||
if (!process())
|
||||
Thread::msleep(2,true);
|
||||
else
|
||||
Thread::check(true);
|
||||
}
|
||||
|
||||
// Try to get an event from a transaction
|
||||
MGCPEvent* MGCPEngine::getEvent(u_int64_t time)
|
||||
{
|
||||
lock();
|
||||
ListIterator iter(m_transactions);
|
||||
while (true) {
|
||||
if (Thread::check(false))
|
||||
break;
|
||||
MGCPTransaction* tr = static_cast<MGCPTransaction*>(iter.get());
|
||||
// End of iteration? NO: get a reference to the transaction
|
||||
if (!tr)
|
||||
break;
|
||||
RefPointer<MGCPTransaction> sref = tr;
|
||||
if (!sref)
|
||||
continue;
|
||||
// Get an event from the transaction
|
||||
unlock();
|
||||
MGCPEvent* event = sref->getEvent(time);
|
||||
// Remove the transaction if destroying
|
||||
if (event)
|
||||
return event;
|
||||
lock();
|
||||
}
|
||||
unlock();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Process an event generated by a transaction. Descendants must override this
|
||||
// method if they want to process events without breaking them apart
|
||||
bool MGCPEngine::processEvent(MGCPEvent* event)
|
||||
{
|
||||
DDebug(this,DebugAll,"MGCPEngine::processEvent(%p)",event);
|
||||
if (!event)
|
||||
return false;
|
||||
MGCPTransaction* trans = event->transaction();
|
||||
void* data = trans ? trans->userData() : 0;
|
||||
if (processEvent(trans,event->message(),data)) {
|
||||
// Get rid of the event, it was handled
|
||||
delete event;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Process an event generated by a transaction. Descendants must override this
|
||||
// method if they want to process events
|
||||
bool MGCPEngine::processEvent(MGCPTransaction* trans, MGCPMessage* msg, void* data)
|
||||
{
|
||||
Debug(this,DebugStub,"MGCPEngine::processEvent(%p,%p,%p)",trans,msg,data);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns an unprocessed event to this engine to be deleted.
|
||||
// Incoming transactions will be responded. Unknown commands will receive a
|
||||
// 504 Unknown Command response, the others will receive a 507 Unsupported Functionality one
|
||||
void MGCPEngine::returnEvent(MGCPEvent* event)
|
||||
{
|
||||
if (!event)
|
||||
return;
|
||||
DDebug(this,DebugInfo,"Event (%p) returned to the engine",event);
|
||||
MGCPTransaction* tr = event->transaction();
|
||||
const MGCPMessage* msg = event->message();
|
||||
if (tr && !tr->outgoing() && msg && msg->isCommand())
|
||||
tr->setResponse(knownCommand(msg->name()) ? 507 : 504);
|
||||
delete event;
|
||||
}
|
||||
|
||||
// Terminate all transactions. Cancel all private threads if any and
|
||||
// wait for them to terminate
|
||||
void MGCPEngine::cleanup(bool gracefully, const char* text)
|
||||
{
|
||||
DDebug(this,DebugAll,"Cleanup (gracefully=%s text=%s)",
|
||||
String::boolText(gracefully),text);
|
||||
|
||||
// Terminate transactions
|
||||
lock();
|
||||
if (gracefully)
|
||||
for (ObjList* o = m_transactions.skipNull(); o; o = o->skipNext()) {
|
||||
MGCPTransaction* tr = static_cast<MGCPTransaction*>(o->get());
|
||||
if (!tr->outgoing())
|
||||
tr->setResponse(400,text);
|
||||
}
|
||||
m_transactions.clear();
|
||||
unlock();
|
||||
|
||||
// Check if we have any private threads to wait
|
||||
if (!m_threads.skipNull())
|
||||
return;
|
||||
|
||||
// Terminate private threads
|
||||
XDebug(this,DebugAll,"Terminating %u private threads",m_threads.count());
|
||||
lock();
|
||||
ListIterator iter(m_threads);
|
||||
for (GenObject* o = 0; 0 != (o = iter.get());)
|
||||
static_cast<MGCPPrivateThread*>(o)->cancel(!gracefully);
|
||||
unlock();
|
||||
XDebug(this,DebugAll,"Waiting for private threads to terminate");
|
||||
while (m_threads.skipNull())
|
||||
Thread::yield();
|
||||
XDebug(this,DebugAll,"Private threads terminated");
|
||||
}
|
||||
|
||||
// Write data to socket
|
||||
bool MGCPEngine::sendData(const String& msg, const SocketAddr& address)
|
||||
{
|
||||
if (debugAt(DebugInfo)) {
|
||||
SocketAddr local;
|
||||
m_socket.getSockName(local);
|
||||
Debug(this,DebugInfo,
|
||||
"Sending message from %s:%d to %s:%d\r\n-----\r\n%s\r\n-----",
|
||||
local.host().c_str(),local.port(),address.host().c_str(),address.port(),
|
||||
msg.c_str());
|
||||
}
|
||||
|
||||
int len = m_socket.sendTo(msg.c_str(),msg.length(),address);
|
||||
if (len != Socket::socketError())
|
||||
return true;
|
||||
if (!m_socket.canRetry())
|
||||
Debug(this,DebugWarn,"Socket write error: %d: %s",
|
||||
m_socket.error(),::strerror(m_socket.error()));
|
||||
else
|
||||
DDebug(this,DebugMild,"Socket temporary unavailable: %d: %s",
|
||||
m_socket.error(),::strerror(m_socket.error()));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Append a transaction to the list
|
||||
void MGCPEngine::appendTrans(MGCPTransaction* trans)
|
||||
{
|
||||
if (!trans)
|
||||
return;
|
||||
Lock lock(this);
|
||||
DDebug(this,DebugAll,"Added transaction (%p)",trans);
|
||||
m_transactions.append(trans);
|
||||
}
|
||||
|
||||
// Remove a transaction from the list
|
||||
void MGCPEngine::removeTrans(MGCPTransaction* trans, bool del)
|
||||
{
|
||||
if (!trans)
|
||||
return;
|
||||
Lock lock(this);
|
||||
DDebug(this,DebugAll,"Removed transaction (%p) del=%u",trans,del);
|
||||
m_transactions.remove(trans,del);
|
||||
}
|
||||
|
||||
// Append a private thread to the list
|
||||
void MGCPEngine::appendThread(MGCPPrivateThread* thread)
|
||||
{
|
||||
if (!thread)
|
||||
return;
|
||||
Lock lock(this);
|
||||
m_threads.append(thread);
|
||||
XDebug(this,DebugAll,"Added private thread (%p)",thread);
|
||||
}
|
||||
|
||||
// Remove private thread from the list without deleting it
|
||||
void MGCPEngine::removeThread(MGCPPrivateThread* thread)
|
||||
{
|
||||
if (!thread)
|
||||
return;
|
||||
Lock lock(this);
|
||||
m_threads.remove(thread,false);
|
||||
XDebug(this,DebugAll,"Removed private thread (%p)",thread);
|
||||
}
|
||||
|
||||
// Process ACK received with a message or response
|
||||
// Build an ACK message for each of responded incoming transaction
|
||||
unsigned int* MGCPEngine::decodeAck(const String& param, unsigned int& count)
|
||||
{
|
||||
ObjList* list = param.split(',',false);
|
||||
if (!list->count()) {
|
||||
TelEngine::destruct(list);
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned int maxArray = 0;
|
||||
unsigned int* array = 0;
|
||||
bool ok = true;
|
||||
int first, last;
|
||||
|
||||
for (ObjList* o = list->skipNull(); o; o = o->skipNext()) {
|
||||
String* s = static_cast<String*>(o->get());
|
||||
s->trimBlanks();
|
||||
// Get the interval (may be a single value)
|
||||
int sep = s->find('-');
|
||||
if (sep == -1)
|
||||
first = last = s->toInteger(-1);
|
||||
else {
|
||||
first = s->substr(0,sep).toInteger(-1);
|
||||
last = s->substr(sep + 1).toInteger(-2);
|
||||
}
|
||||
if (first < 0 || last < 0 || last < first) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
// Resize and copy array if not enough room
|
||||
unsigned int len = (unsigned int)(last - first + 1);
|
||||
if (count + len > maxArray) {
|
||||
maxArray = count + len;
|
||||
unsigned int* tmp = new unsigned int[maxArray];
|
||||
if (array) {
|
||||
::memcpy(tmp,array,sizeof(unsigned int) * count);
|
||||
delete[] array;
|
||||
}
|
||||
array = tmp;
|
||||
}
|
||||
// Add to array code list
|
||||
for (; first <= last; first++)
|
||||
array[count++] = first;
|
||||
}
|
||||
TelEngine::destruct(list);
|
||||
|
||||
if (ok && count)
|
||||
return array;
|
||||
count = 0;
|
||||
if (array)
|
||||
delete[] array;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* MGCPEvent
|
||||
*/
|
||||
// Constructs an event from a transaction
|
||||
MGCPEvent::MGCPEvent(MGCPTransaction* trans, MGCPMessage* msg)
|
||||
: m_transaction(0),
|
||||
m_message(0)
|
||||
{
|
||||
if (trans && trans->ref())
|
||||
m_transaction = trans;
|
||||
if (msg && msg->ref())
|
||||
m_message = msg;
|
||||
}
|
||||
|
||||
// Delete the message. Notify and deref the transaction
|
||||
MGCPEvent::~MGCPEvent()
|
||||
{
|
||||
if (m_transaction) {
|
||||
m_transaction->eventTerminated(this);
|
||||
m_transaction->deref();
|
||||
}
|
||||
TelEngine::destruct(m_message);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The list of known commands defined in RFC 3435
|
||||
*/
|
||||
TokenDict MGCPEngine::mgcp_commands[] = {
|
||||
{"EPCF", 1}, // CA --> GW EndpointConfiguration
|
||||
{"CRCX", 2}, // CA --> GW CreateConnection
|
||||
{"MDCX", 3}, // CA --> GW ModifyConnection
|
||||
{"DLCX", 4}, // CA <--> GW DeleteConnection
|
||||
{"RQNT", 5}, // CA --> GW NotificationRequest
|
||||
{"AUEP", 6}, // CA --> GW AuditEndpoint
|
||||
{"AUCX", 7}, // CA --> GW AuditConnection
|
||||
{"RSIP", 8}, // GW --> CA RestartInProgress
|
||||
{"NTFY", 9}, // GW --> CA Notify
|
||||
{"MESG", 10}, // GW --> CA Message
|
||||
{0,0}
|
||||
};
|
||||
|
||||
/**
|
||||
* The list of known responses defined in RFC 3435 2.4
|
||||
*/
|
||||
TokenDict MGCPEngine::mgcp_responses[] = {
|
||||
{"ACK", 0}, // Response Acknowledgement
|
||||
{"Trying", 100}, // The transaction is currently being executed
|
||||
{"Queued", 101}, // The transaction has been queued for execution
|
||||
{"OK", 200}, // The requested transaction was executed normally
|
||||
{"OK", 250}, // Used only to respond to DeleteConnection
|
||||
{"Unspecified", 400}, // The transaction could not be executed, due to some unspecified transient error
|
||||
{"Already Off Hook", 401}, // The phone is already off hook
|
||||
{"Already On Hook", 402}, // The phone is already on hook
|
||||
{"No Resources Now", 403}, // The transaction could not be executed, because the endpoint does
|
||||
// not have sufficient resources at this time
|
||||
{"Insufficient Bandwidth", 404},
|
||||
{"Endpoint Is Restarting", 405}, // The transaction could not be executed, because the endpoint is restarting
|
||||
{"Timeout", 406}, // The transaction did not complete in a reasonable period of time
|
||||
{"Aborted", 407}, // The transaction was aborted by some external action,
|
||||
// e.g., a ModifyConnection command aborted by a DeleteConnection command
|
||||
{"Overload", 409}, // The transaction could not be executed because of internal overload
|
||||
{"No Endpoint Available", 410}, // A valid "any of" wildcard was used, but there was no endpoint
|
||||
// available to satisfy the request
|
||||
{"Unknown Endpoint", 500},
|
||||
{"Endpoint Not Ready", 501}, // The endpoint is not ready. Includes out-of-service
|
||||
{"No Resources", 502}, // The endpoint doesn't have sufficient resources (permanent condition)
|
||||
{"Wildcard Too Complicated", 503}, // "All of" wildcard too complicated
|
||||
{"Unknown Command", 504}, // Unknown or unsupported command.
|
||||
{"Unsupported RemoteConnectionDescriptor", 505}, // This SHOULD be used when one or more mandatory parameters
|
||||
// or values in the RemoteConnectionDescriptor is not supported
|
||||
{"Unable To Satisfy LocalConnectionOptions And RemoteConnectionDescriptor", 506}, // LocalConnectionOptions and
|
||||
// RemoteConnectionDescriptor contain one or more mandatory parameters
|
||||
// or values that conflict with each other
|
||||
{"Unsupported Functionality", 507},
|
||||
{"Unknown Or Unsupported Quarantine Handling", 508},
|
||||
{"Bad RemoteConnectionDescriptor", 509}, // Syntax or semantic error in the RemoteConnectionDescriptor
|
||||
{"Protocol Error", 510}, // Unspecified protocol error was detected
|
||||
{"Unrecognized Extension", 511}, // Used for unsupported critical parameter extensions ("X+")
|
||||
{"Can't Detect Event", 512}, // The gateway is not equipped to detect one of the requested events
|
||||
{"Can't Generate Signal", 513}, // The gateway is not equipped to generate one of the requested signals
|
||||
{"Can't Send Announcement", 514}, // The gateway cannot send the specified announcement.
|
||||
{"No Connection", 515}, // The transaction refers to an incorrect connection-id
|
||||
{"Bad Call-id", 516}, // Unknown or incorrect call-id (connection-id not associated with this call-id)
|
||||
{"Unsupported Mode", 517}, // Unsupported or invalid mode
|
||||
{"Unsupported Package", 518},
|
||||
{"No Digit Map", 519}, // Endpoint does not have a digit map
|
||||
{"Endpoint Is Restarting", 520},
|
||||
{"Endpoint Redirected To Another Call Agent", 521}, // The associated redirection behavior is only well-defined
|
||||
// when this response is issued for a RestartInProgress command
|
||||
{"Unknown Event Or Signal", 522}, // The request referred to an event or signal that is not defined in
|
||||
// the relevant package (which could be the default package)
|
||||
{"Illegal Action", 523}, // Unknown action or illegal combination of actions
|
||||
{"Inconsistency In LocalConnectionOptions", 524},
|
||||
{"Unknown Extension In LocalConnectionOptions", 525}, // Used for unsupported mandatory vendor extensions ("x+")
|
||||
{"Insufficient Bandwidth", 526},
|
||||
{"Missing RemoteConnectionDescriptor", 527},
|
||||
{"Incompatible Protocol Version", 528},
|
||||
{"Internal Hardware Failure", 529},
|
||||
{"CAS Signaling Protocol Error", 530},
|
||||
{"Grouping Of Trunks Failure", 531}, // e.g., facility failure
|
||||
{"Unsupported LocalConnectionOptions", 532}, // Unsupported value(s) in LocalConnectionOptions.
|
||||
{"Response Too Large", 533},
|
||||
{"Codec Negotiation Failure", 534},
|
||||
{"Packetization Period Not Supported", 535},
|
||||
{"Unsupported RestartMethod", 536},
|
||||
{"Unsupported Digit Map Extension", 537},
|
||||
{"Event/Signal Parameter Error", 538}, // e.g., missing, erroneous, unsupported, unknown, etc.
|
||||
{"Unsupported Command Parameter", 539}, // Used when the parameter is neither a package or vendor extension parameter
|
||||
{"Per Endpoint Connection Limit Exceeded", 540},
|
||||
{"Unsupported LocalConnectionOptions", 541}, // Used when the LocalConnectionOptions is neither a package
|
||||
// nor a vendor extension LocalConnectionOptions.
|
||||
{0,0},
|
||||
};
|
||||
|
||||
/**
|
||||
* The list of known reason codes defined in RFC 3435 2.5
|
||||
* Reason codes are used by the gateway when deleting a connection to
|
||||
inform the Call Agent about the reason for deleting the connection.
|
||||
They may also be used in a RestartInProgress command to inform the
|
||||
Call Agent of the reason for the RestartInProgress
|
||||
*/
|
||||
TokenDict MGCPEngine::mgcp_reasons[] = {
|
||||
{"Normal", 0}, // Endpoint state is normal (only in response to audit requests)
|
||||
{"Endpoint Malfunctioning", 900},
|
||||
{"Endpoint Taken Out-Of-Service", 901},
|
||||
{"Loss Of Lower Layer Connectivity", 902},
|
||||
{"QoS Resource Reservation Was Lost", 903},
|
||||
{"Manual Intervention", 904},
|
||||
{"Facility failure", 905},
|
||||
{0,0},
|
||||
};
|
||||
|
||||
/* vi: set ts=8 sw=4 sts=4 noet: */
|
|
@ -0,0 +1,493 @@
|
|||
/**
|
||||
* message.cpp
|
||||
* Yet Another MGCP Stack
|
||||
* This file is part of the YATE Project http://YATE.null.ro
|
||||
*
|
||||
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
||||
* Copyright (C) 2004-2006 Null Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <yatemgcp.h>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace TelEngine;
|
||||
|
||||
#ifdef XDEBUG
|
||||
#define PARSER_DEBUG
|
||||
#endif
|
||||
|
||||
// Ensure response code string representation is 3 digit long
|
||||
inline void setCode(String& dest, unsigned int code)
|
||||
{
|
||||
char c[4];
|
||||
sprintf(c,"%03u",code);
|
||||
dest = c;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* MGCPMessage
|
||||
*/
|
||||
// Construct an outgoing command message
|
||||
MGCPMessage::MGCPMessage(MGCPEngine* engine, const char* name, const char* ep, const char* ver)
|
||||
: params(""),
|
||||
m_name(name),
|
||||
m_valid(false),
|
||||
m_code(-1),
|
||||
m_transaction(0),
|
||||
m_endpoint(ep),
|
||||
m_version(ver)
|
||||
{
|
||||
if (!(engine && (engine->allowUnkCmd() || engine->knownCommand(name)))) {
|
||||
Debug(engine,DebugNote,"MGCPMessage. Unknown cmd=%s [%p]",name,this);
|
||||
return;
|
||||
}
|
||||
// Command names MUST be 4 character long
|
||||
if (m_name.length() != 4) {
|
||||
Debug(engine,DebugNote,
|
||||
"MGCPMessage. Invalid command length cmd=%s len=%u [%p]",
|
||||
m_name.c_str(),m_name.length(),this);
|
||||
return;
|
||||
}
|
||||
|
||||
m_transaction = engine->getNextId();
|
||||
m_valid = true;
|
||||
DDebug(engine,DebugAll,"MGCPMessage. cmd=%s trans=%u ep=%s [%p]",
|
||||
name,m_transaction,ep,this);
|
||||
}
|
||||
|
||||
// Construct a response message
|
||||
MGCPMessage::MGCPMessage(MGCPTransaction* trans, unsigned int code, const char* comment)
|
||||
: params(""),
|
||||
m_valid(false),
|
||||
m_code(code),
|
||||
m_transaction(0),
|
||||
m_comment(comment)
|
||||
{
|
||||
if (!trans) {
|
||||
Debug(DebugNote,
|
||||
"MGCPMessage. Can't create response without transaction [%p]",this);
|
||||
return;
|
||||
}
|
||||
if (code > 999) {
|
||||
Debug(trans->engine(),DebugNote,
|
||||
"MGCPMessage. Invalid response code=%u [%p]",code,this);
|
||||
return;
|
||||
}
|
||||
setCode(m_name,code);
|
||||
m_transaction = trans->id();
|
||||
if (!m_comment.length())
|
||||
m_comment = lookup(code,MGCPEngine::mgcp_responses);
|
||||
m_valid = true;
|
||||
DDebug(trans->engine(),DebugAll,"MGCPMessage code=%d trans=%u comment=%s [%p]",
|
||||
code,m_transaction,m_comment.c_str(),this);
|
||||
}
|
||||
|
||||
// Constructor. Used by the parser to construct an incoming message
|
||||
MGCPMessage::MGCPMessage(MGCPEngine* engine, const char* name, int code,
|
||||
unsigned int transId, const char* epId, const char* ver)
|
||||
: params(""),
|
||||
m_valid(true),
|
||||
m_code(code),
|
||||
m_transaction(transId),
|
||||
m_endpoint(epId),
|
||||
m_version(ver)
|
||||
{
|
||||
if (code < 0)
|
||||
m_name = name;
|
||||
else {
|
||||
setCode(m_name,code);
|
||||
m_comment = name;
|
||||
if (!m_comment.length())
|
||||
m_comment = lookup(code,MGCPEngine::mgcp_responses);
|
||||
}
|
||||
DDebug(engine,DebugAll,
|
||||
"Incoming MGCPMessage %s=%s trans=%u ep=%s ver=%s comment=%s [%p]",
|
||||
isCommand()?"cmd":"rsp",this->name().c_str(),
|
||||
transId,epId,ver,m_comment.safe(),this);
|
||||
}
|
||||
|
||||
MGCPMessage::~MGCPMessage()
|
||||
{
|
||||
DDebug(DebugAll,"MGCPMessage::~MGCPMessage [%p]",this);
|
||||
}
|
||||
|
||||
// Convert this message to a string representation
|
||||
void MGCPMessage::toString(String& dest) const
|
||||
{
|
||||
// Construct first line
|
||||
dest << name() << " " << transactionId();
|
||||
if (isCommand())
|
||||
dest << " " << endpointId() << " " << m_version;
|
||||
else if (m_comment)
|
||||
dest << " " << m_comment;
|
||||
dest << "\r\n";
|
||||
|
||||
// Append message parameters
|
||||
unsigned int n = params.count();
|
||||
for (unsigned int i = 0; i < n; i++) {
|
||||
NamedString* ns = params.getParam(i);
|
||||
if (!ns)
|
||||
continue;
|
||||
dest << ns->name() << ": " << *ns << "\r\n";
|
||||
}
|
||||
|
||||
// Append SDP(s)
|
||||
for (ObjList* obj = sdp.skipNull(); obj; obj = obj->skipNext()) {
|
||||
String s;
|
||||
MimeSdpBody* tmp = static_cast<MimeSdpBody*>(obj->get());
|
||||
for (ObjList* o = tmp->lines().skipNull(); o; o = o->skipNext()) {
|
||||
NamedString* ns = static_cast<NamedString*>(o->get());
|
||||
if (*ns)
|
||||
s << ns->name() << "=" << *ns << "\r\n";
|
||||
}
|
||||
if (s)
|
||||
dest << "\r\n" << s;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a character is an end-of-line one
|
||||
static inline bool isEoln(char c)
|
||||
{
|
||||
return c == '\r' || c == '\n';
|
||||
}
|
||||
|
||||
// Check if a character is blank: space or tab
|
||||
static inline bool isBlank(char c)
|
||||
{
|
||||
return c == ' ' || c == '\t';
|
||||
}
|
||||
|
||||
// Skip blank characters. The buffer is assumed to be valid
|
||||
// Return false if the end of line was reached
|
||||
static inline bool skipBlanks(const char*& buffer, unsigned int& len)
|
||||
{
|
||||
for (; len && isBlank(*buffer); buffer++, len--)
|
||||
;
|
||||
return len;
|
||||
}
|
||||
|
||||
// Get a line from a buffer until the first valid end-of-line or end of buffer was reached,
|
||||
// starting with current index in buffer
|
||||
// Set the current index to the first character after the end-of-line or at the end of the buffer
|
||||
// The buffer is assumed to be valid
|
||||
// Return the line and length or 0 if an invalid end-of-line was found
|
||||
// RFC 3435 3.1: end-of-line may be CR/LF or LF
|
||||
static inline const char* getLine(const unsigned char* buffer, unsigned int len,
|
||||
unsigned int& crt, unsigned int& count, bool skip = true)
|
||||
{
|
||||
count = 0;
|
||||
const char* line = (const char*)buffer + crt;
|
||||
while (crt < len && !isEoln(buffer[crt])) {
|
||||
crt++;
|
||||
count++;
|
||||
}
|
||||
if (skip)
|
||||
skipBlanks(line,count);
|
||||
|
||||
// Check end of buffer or end-of-line
|
||||
if (crt == len)
|
||||
return line;
|
||||
if (buffer[crt] == '\r' && (++crt) == len)
|
||||
return 0;
|
||||
return buffer[crt++] == '\n' ? line : 0;
|
||||
}
|
||||
|
||||
// Parse a received buffer according to RFC 3435
|
||||
// See Appendix A for the grammar
|
||||
bool MGCPMessage::parse(MGCPEngine* engine, ObjList& dest,
|
||||
const unsigned char* buffer, unsigned int len, const char* sdpType)
|
||||
{
|
||||
if (!buffer)
|
||||
return false;
|
||||
|
||||
#ifdef PARSER_DEBUG
|
||||
String t((const char*)buffer,len);
|
||||
Debug(engine,DebugAll,"Parse received buffer\r\n%s",t.c_str());
|
||||
#endif
|
||||
|
||||
int errorCode = 510; // Protocol error
|
||||
unsigned int trans = 0;
|
||||
String error;
|
||||
unsigned int crt = 0;
|
||||
|
||||
while (crt < len && !error) {
|
||||
unsigned int count = 0;
|
||||
const char* line = 0;
|
||||
|
||||
// Skip empty lines before a message line and skip trailing blanks on the message line
|
||||
while (crt < len) {
|
||||
line = getLine(buffer,len,crt,count);
|
||||
if (!line) {
|
||||
error = "Invalid end-of-line";
|
||||
break;
|
||||
}
|
||||
// Exit loop if the line is not empty
|
||||
if (count)
|
||||
break;
|
||||
}
|
||||
if (!count || error)
|
||||
break;
|
||||
|
||||
// *** Decode the message line
|
||||
MGCPMessage* msg = decodeMessage(line,count,trans,error,engine);
|
||||
if (!msg)
|
||||
break;
|
||||
dest.append(msg);
|
||||
|
||||
#ifdef PARSER_DEBUG
|
||||
String m((const char*)line,count);
|
||||
Debug(engine,DebugAll,"Decoded message: %s",m.c_str());
|
||||
#endif
|
||||
|
||||
// *** Decode parameters
|
||||
if (decodeParams(buffer,len,crt,msg,error,engine))
|
||||
continue;
|
||||
if (error) {
|
||||
if (msg->isCommand())
|
||||
trans = msg->transactionId();
|
||||
break;
|
||||
}
|
||||
if (crt >= len)
|
||||
break;
|
||||
|
||||
// *** Decode SDP
|
||||
// Decode SDPs until the end of buffer or
|
||||
// a line containing a dot (message separator in a piggybacked block)
|
||||
// SDPs are separated by an empty line
|
||||
int empty = 0;
|
||||
while (empty < 2) {
|
||||
// Skip until an empty line, a line containing a dot or end of buffer
|
||||
unsigned int start = crt;
|
||||
unsigned int sdpLen = 0;
|
||||
while (true) {
|
||||
line = getLine(buffer,len,crt,count);
|
||||
if (!line) {
|
||||
error = "Invalid end-of-line";
|
||||
break;
|
||||
}
|
||||
if (!count || (count == 1 && *line == '.')) {
|
||||
if (!count)
|
||||
empty++;
|
||||
else
|
||||
empty = 3;
|
||||
break;
|
||||
}
|
||||
empty = 0;
|
||||
sdpLen = crt - start;
|
||||
}
|
||||
if (error)
|
||||
break;
|
||||
if (sdpLen)
|
||||
msg->sdp.append(new MimeSdpBody(sdpType,(const char*)buffer+start,sdpLen));
|
||||
}
|
||||
|
||||
// Found 2 empty lines: skip until end of buffer or line containing '.' or non empty line
|
||||
if (empty == 2) {
|
||||
unsigned int start = crt;
|
||||
while (true) {
|
||||
line = getLine(buffer,len,crt,count);
|
||||
if (!line) {
|
||||
error = "Invalid end-of-line";
|
||||
break;
|
||||
}
|
||||
if (!count) {
|
||||
if (crt == len)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
// Fallback with current index if found non empty line which doesn't start with '.'
|
||||
if (*line != '.')
|
||||
crt = start;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!error)
|
||||
return true;
|
||||
|
||||
dest.clear();
|
||||
if (trans && trans <= 999999999)
|
||||
dest.append(new MGCPMessage(engine,0,errorCode,trans,0,0));
|
||||
Debug(engine,DebugNote,"Parser error: %s",error.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decode the message line
|
||||
// Command: verb transaction endpoint proto_name proto_version ...
|
||||
// Response: code transaction comment ...
|
||||
MGCPMessage* MGCPMessage::decodeMessage(const char* line, unsigned int len, unsigned int& trans,
|
||||
String& error, MGCPEngine* engine)
|
||||
{
|
||||
String name, ver;
|
||||
int code = -1;
|
||||
unsigned int trID = 0;
|
||||
MGCPEndpointId id;
|
||||
|
||||
#ifdef PARSER_DEBUG
|
||||
String msgLine(line,len);
|
||||
Debug(engine,DebugAll,"Parse message line (len=%u): %s",
|
||||
msgLine.length(),msgLine.c_str());
|
||||
#endif
|
||||
|
||||
for (unsigned int item = 1; true; item++) {
|
||||
if (item == 6) {
|
||||
#ifdef DEBUG
|
||||
if (len) {
|
||||
String rest(line,len);
|
||||
Debug(engine,DebugAll,"Unparsed data on message line: '%s'",rest.c_str());
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
// Get current item
|
||||
if (!skipBlanks(line,len)) {
|
||||
error = "Unexpected end of line";
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned int itemLen = 0;
|
||||
// Response: the 3rd item is the comment
|
||||
if (item == 3 && code != -1)
|
||||
itemLen = len;
|
||||
else
|
||||
for (; itemLen < len && !isBlank(line[itemLen]); itemLen++)
|
||||
;
|
||||
String tmp(line,itemLen);
|
||||
len -= itemLen;
|
||||
line += itemLen;
|
||||
|
||||
switch (item) {
|
||||
// 1st item: verb (command or notification) or response code
|
||||
// Verbs must be 4-character long. Responses must be numbers in the interval [0..999]
|
||||
case 1:
|
||||
if (tmp.length() == 3) {
|
||||
code = tmp.toInteger(-1,10);
|
||||
if (code < 0 || code > 999)
|
||||
error << "Invalid response code " << tmp;
|
||||
}
|
||||
else if (tmp.length() == 4)
|
||||
name = tmp.toUpper();
|
||||
else
|
||||
error << "Invalid first item '" << tmp << "' length " << tmp.length();
|
||||
break;
|
||||
|
||||
// 2nd item: the transaction id
|
||||
// Restriction: must be a number in the interval [1..999999999]
|
||||
case 2:
|
||||
trID = tmp.toInteger(-1,10);
|
||||
if (!trID || trID > 999999999)
|
||||
error << "Invalid transaction id '" << tmp << "'";
|
||||
// Set trans for command messages so they can be responded on error
|
||||
else if (code == -1)
|
||||
trans = trID;
|
||||
break;
|
||||
|
||||
// 3rd item: endpoint id (code is -1) or response comment (code != -1)
|
||||
case 3:
|
||||
if (code != -1)
|
||||
name = tmp;
|
||||
else {
|
||||
id.set(tmp);
|
||||
if (!id.valid())
|
||||
error << "Invalid endpoint id '" << tmp << "'";
|
||||
}
|
||||
break;
|
||||
|
||||
// 4th item: protocol name if this is a verb (command)
|
||||
case 4:
|
||||
if (tmp.toUpper() == "MGCP")
|
||||
ver = "MGCP 1.0";
|
||||
else
|
||||
error << "Invalid protocol '" << tmp << "'";
|
||||
break;
|
||||
|
||||
// 5th item: protocol version if this is a verb (command)
|
||||
case 5:
|
||||
if (tmp != "1.0")
|
||||
error << "Invalid protocol version '" << tmp << "'";
|
||||
break;
|
||||
}
|
||||
if (error)
|
||||
return 0;
|
||||
// Stop parse the rest if this is a response
|
||||
if (item == 3 && code != -1)
|
||||
break;
|
||||
}
|
||||
// Check known commands
|
||||
if (code == -1 &&
|
||||
!(engine && (engine->allowUnkCmd() || engine->knownCommand(name)))) {
|
||||
error << "Unknown cmd '" << name << "'";
|
||||
return 0;
|
||||
}
|
||||
return new MGCPMessage(engine,name,code,trID,id.id(),ver);
|
||||
}
|
||||
|
||||
// Decode message parameters. Return true if found a line containing a dot
|
||||
// Decode parameters until the end of buffer, empty line or
|
||||
// a line containing a dot (message separator in a piggybacked block)
|
||||
// Parameters names and values are separated by a ':' character
|
||||
// See RFC 3435 3.1
|
||||
bool MGCPMessage::decodeParams(const unsigned char* buffer, unsigned int len,
|
||||
unsigned int& crt, MGCPMessage* msg, String& error, MGCPEngine* engine)
|
||||
{
|
||||
while (crt < len) {
|
||||
unsigned int count = 0;
|
||||
const char* line = getLine(buffer,len,crt,count);
|
||||
if (!line) {
|
||||
error = "Invalid end-of-line";
|
||||
break;
|
||||
}
|
||||
|
||||
// Terminate if the line is empty or is a message separator
|
||||
if (!count)
|
||||
break;
|
||||
if (count == 1 && *line == '.')
|
||||
return true;
|
||||
|
||||
#ifdef PARSER_DEBUG
|
||||
String paramLine(line,count);
|
||||
Debug(engine,DebugAll,"Parse parameter line(len=%u): %s",count,paramLine.c_str());
|
||||
#endif
|
||||
|
||||
// Decode parameter
|
||||
int pos = -1;
|
||||
for (int i = 0; i < (int)count; i++)
|
||||
if (line[i] == ':')
|
||||
pos = i;
|
||||
if (pos == -1) {
|
||||
error = "Parameter separator is missing";
|
||||
break;
|
||||
}
|
||||
String param(line,pos);
|
||||
param.trimBlanks();
|
||||
if (!param) {
|
||||
error = "Parameter name is empty";
|
||||
break;
|
||||
}
|
||||
String value(line+pos+1,count-pos-1);
|
||||
value.trimBlanks();
|
||||
if (engine && engine->parseParamToLower())
|
||||
msg->params.addParam(param.toLower(),value);
|
||||
else
|
||||
msg->params.addParam(param,value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* vi: set ts=8 sw=4 sts=4 noet: */
|
|
@ -0,0 +1,476 @@
|
|||
/**
|
||||
* transaction.cpp
|
||||
* Yet Another MGCP Stack
|
||||
* This file is part of the YATE Project http://YATE.null.ro
|
||||
*
|
||||
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
||||
* Copyright (C) 2004-2006 Null Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <yatemgcp.h>
|
||||
|
||||
using namespace TelEngine;
|
||||
|
||||
// Construct a transaction from its first message
|
||||
MGCPTransaction::MGCPTransaction(MGCPEngine* engine, MGCPMessage* msg, bool outgoing,
|
||||
const SocketAddr& address)
|
||||
: Mutex(true),
|
||||
m_state(Invalid),
|
||||
m_outgoing(outgoing),
|
||||
m_address(address),
|
||||
m_engine(engine),
|
||||
m_cmd(msg),
|
||||
m_provisional(0),
|
||||
m_response(0),
|
||||
m_ack(0),
|
||||
m_lastEvent(0),
|
||||
m_nextRetrans(0),
|
||||
m_crtRetransInterval(0),
|
||||
m_retransCount(0),
|
||||
m_timeout(false),
|
||||
m_private(0)
|
||||
{
|
||||
if (m_engine)
|
||||
m_engine->appendTrans(this);
|
||||
else {
|
||||
Debug(engine,DebugNote,"Can't create MGCP transaction without engine");
|
||||
return;
|
||||
}
|
||||
if (!(msg && msg->isCommand())) {
|
||||
Debug(engine,DebugNote,"Can't create MGCP transaction from response");
|
||||
return;
|
||||
}
|
||||
|
||||
m_id = msg->transactionId();
|
||||
m_endpoint = m_cmd->endpointId();
|
||||
m_debug << "Transaction(" << (int)outgoing << "," << m_id << ")";
|
||||
|
||||
DDebug(m_engine,DebugAll,"%s. cmd=%s ep=%s addr=%s:%d [%p]",
|
||||
m_debug.c_str(),m_cmd->name().c_str(),m_cmd->endpointId().c_str(),
|
||||
m_address.host().c_str(),m_address.port(),this);
|
||||
|
||||
// Outgoing: send the message
|
||||
if (outgoing) {
|
||||
send(m_cmd);
|
||||
initTimeout(Time(),false);
|
||||
}
|
||||
else
|
||||
changeState(Initiated);
|
||||
}
|
||||
|
||||
MGCPTransaction::~MGCPTransaction()
|
||||
{
|
||||
DDebug(m_engine,DebugAll,"%s. Destroyed [%p]",m_debug.c_str(),this);
|
||||
}
|
||||
|
||||
// Get an event from this transaction. Check timeouts
|
||||
MGCPEvent* MGCPTransaction::getEvent(u_int64_t time)
|
||||
{
|
||||
Lock lock(this);
|
||||
if (m_lastEvent)
|
||||
return 0;
|
||||
|
||||
switch (state()) {
|
||||
case Initiated:
|
||||
// Outgoing: Check if received any kind of response
|
||||
// Ignore a provisional response if we received a final one
|
||||
// Stop timer if received a final response
|
||||
// Incoming: Process the received command
|
||||
if (outgoing()) {
|
||||
m_lastEvent = checkResponse(time);
|
||||
if (!m_lastEvent && m_provisional) {
|
||||
m_lastEvent = new MGCPEvent(this,m_provisional);
|
||||
changeState(Trying);
|
||||
}
|
||||
}
|
||||
else {
|
||||
initTimeout(time,true);
|
||||
m_lastEvent = new MGCPEvent(this,m_cmd);
|
||||
if (m_engine && m_engine->provisional()) {
|
||||
if (!m_provisional)
|
||||
m_provisional = new MGCPMessage(this,100);
|
||||
send(m_provisional);
|
||||
}
|
||||
else
|
||||
changeState(Trying);
|
||||
}
|
||||
break;
|
||||
case Trying:
|
||||
// Outgoing: Check if received any response. If so, send a response ACK
|
||||
// Incoming: Do nothing. Wait for the user to send a final response
|
||||
if (outgoing())
|
||||
m_lastEvent = checkResponse(time);
|
||||
break;
|
||||
case Responded:
|
||||
// Outgoing: Change state to Ack. Should never be in this state
|
||||
// Incoming: Check if we received a response ACK. Stop timer if received it
|
||||
if (outgoing())
|
||||
changeState(Ack);
|
||||
else {
|
||||
if (!m_ack)
|
||||
break;
|
||||
m_lastEvent = new MGCPEvent(this,m_ack);
|
||||
m_nextRetrans = time + m_engine->extraTime();
|
||||
changeState(Ack);
|
||||
}
|
||||
break;
|
||||
case Ack:
|
||||
// Just check timeouts
|
||||
break;
|
||||
case Invalid:
|
||||
m_lastEvent = terminate();
|
||||
break;
|
||||
case Destroying:
|
||||
break;
|
||||
}
|
||||
// Check timeouts
|
||||
if (!m_lastEvent)
|
||||
m_lastEvent = checkTimeout(time);
|
||||
|
||||
#ifdef DEBUG
|
||||
if (m_lastEvent) {
|
||||
const MGCPMessage* m = m_lastEvent->message();
|
||||
String s = m ? m->name() : String("");
|
||||
DDebug(m_engine,DebugAll,"%s. Generating event (%p) state=%u msg=%s [%p]",
|
||||
m_debug.c_str(),m_lastEvent,state(),s.c_str(),this);
|
||||
}
|
||||
#endif
|
||||
|
||||
return m_lastEvent;
|
||||
}
|
||||
|
||||
// Explicitely transmit a provisional code
|
||||
bool MGCPTransaction::sendProvisional(int code, const char* comment)
|
||||
{
|
||||
if (outgoing() || m_provisional || (state() >= Responded) || (code < 100) || (code > 199))
|
||||
return false;
|
||||
m_provisional = new MGCPMessage(this,code,comment);
|
||||
send(m_provisional);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Transmits a final response message if this is an incoming transaction
|
||||
bool MGCPTransaction::setResponse(MGCPMessage* msg)
|
||||
{
|
||||
Lock lock(this);
|
||||
|
||||
// Check state, message, transaction direction. Also check if we already have a response
|
||||
bool msgValid = (msg && msg->code() >= 200 || !msg->isCommand());
|
||||
bool stateValid = (state() >= Initiated || state() <= Ack);
|
||||
if (m_response || outgoing() || !msgValid || !stateValid) {
|
||||
TelEngine::destruct(msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
DDebug(m_engine,DebugAll,"%s. Set response %s in state %u [%p]",
|
||||
m_debug.c_str(),msg->name().c_str(),state(),this);
|
||||
|
||||
m_response = msg;
|
||||
// Force response ACK request
|
||||
m_response->params.setParam("K","");
|
||||
// Send and init timeout
|
||||
send(m_response);
|
||||
initTimeout(Time(),false);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Transmits a final response message if this is an incoming transaction
|
||||
bool MGCPTransaction::setResponse(int code, const NamedList* params, MimeSdpBody* sdp1,
|
||||
MimeSdpBody* sdp2)
|
||||
{
|
||||
if (m_response || outgoing()) {
|
||||
TelEngine::destruct(sdp1);
|
||||
TelEngine::destruct(sdp2);
|
||||
return false;
|
||||
}
|
||||
const char* comment = 0;
|
||||
if (params)
|
||||
comment = params->c_str();
|
||||
MGCPMessage* msg = new MGCPMessage(this,code,comment);
|
||||
if (params) {
|
||||
unsigned int n = params->length();
|
||||
for (unsigned int i = 0; i < n; i++) {
|
||||
const NamedString* p = params->getParam(i);
|
||||
if (p)
|
||||
msg->params.addParam(p->name(),*p);
|
||||
}
|
||||
}
|
||||
if (sdp1) {
|
||||
msg->sdp.append(sdp1);
|
||||
if (sdp2)
|
||||
msg->sdp.append(sdp2);
|
||||
}
|
||||
else
|
||||
TelEngine::destruct(sdp2);
|
||||
return setResponse(msg);
|
||||
}
|
||||
|
||||
// Gracefully terminate this transaction. Release memory
|
||||
void MGCPTransaction::destroyed()
|
||||
{
|
||||
lock();
|
||||
if (state() != Destroying) {
|
||||
if (!outgoing() && !m_response)
|
||||
setResponse(400);
|
||||
changeState(Destroying);
|
||||
}
|
||||
if (m_engine)
|
||||
m_engine->removeTrans(this,false);
|
||||
TelEngine::destruct(m_cmd);
|
||||
TelEngine::destruct(m_provisional);
|
||||
TelEngine::destruct(m_response);
|
||||
TelEngine::destruct(m_ack);
|
||||
unlock();
|
||||
RefObject::destroyed();
|
||||
}
|
||||
|
||||
// Consume (process) a received message, other then the initiating one
|
||||
void MGCPTransaction::processMessage(MGCPMessage* msg)
|
||||
{
|
||||
if (!msg)
|
||||
return;
|
||||
Lock lock(this);
|
||||
if (state() < Initiated || state() > Ack) {
|
||||
bool cmd = msg->isCommand();
|
||||
Debug(m_engine,DebugInfo,"%s. Can't process %s %s in state %u [%p]",
|
||||
m_debug.c_str(),msg->name().c_str(),cmd ? "command":"response",
|
||||
state(),this);
|
||||
TelEngine::destruct(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// Process commands
|
||||
if (msg->isCommand()) {
|
||||
// Commands can be received only by incoming transactions
|
||||
// Check for retransmission
|
||||
if (outgoing() || msg->name() != m_cmd->name()) {
|
||||
Debug(m_engine,DebugNote,"%s. Can't accept %s [%p]",
|
||||
m_debug.c_str(),msg->name().c_str(),this);
|
||||
TelEngine::destruct(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// Retransmit the last response
|
||||
DDebug(m_engine,DebugAll,
|
||||
"%s. Received command retransmission in state %u [%p]",
|
||||
m_debug.c_str(),state(),this);
|
||||
if (state() == Trying)
|
||||
send(m_provisional);
|
||||
else if (state() == Responded)
|
||||
send(m_response);
|
||||
// If state is Initiated, wait for getEvent to process the received command
|
||||
// Send nothing if we received the ACK to our final response
|
||||
TelEngine::destruct(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// Process responses
|
||||
if (msg->isResponse()) {
|
||||
// Responses can be received only by outgoing transactions
|
||||
if (!outgoing()) {
|
||||
Debug(m_engine,DebugNote,
|
||||
"%s. Can't accept response %d [%p]",
|
||||
m_debug.c_str(),msg->code(),this);
|
||||
TelEngine::destruct(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check response
|
||||
// Send ACK for final response tretransmissions
|
||||
// Don't accept different final responses
|
||||
// Don't accept provisional responses after final responses
|
||||
// Don't accept different provisional responses
|
||||
bool ok = true;
|
||||
if (msg->code() >= 200) {
|
||||
bool retrans = false;
|
||||
ok = !m_response;
|
||||
if (ok)
|
||||
m_response = msg;
|
||||
else if (m_response->code() == msg->code()) {
|
||||
retrans = true;
|
||||
send(m_ack);
|
||||
}
|
||||
Debug(m_engine,(ok || retrans) ? DebugAll : DebugNote,
|
||||
"%s. Received %sresponse %d [%p]",m_debug.c_str(),
|
||||
ok?"":(retrans?"retransmission for ":"different "),msg->code(),this);
|
||||
}
|
||||
else {
|
||||
ok = (!m_response && !m_provisional);
|
||||
if (ok)
|
||||
m_provisional = msg;
|
||||
Debug(m_engine,(ok || m_response)? DebugAll : DebugNote,
|
||||
"%s. Received %sprovisional response %d [%p]",m_debug.c_str(),
|
||||
ok?"":(m_response?"late ":"different "),msg->code(),this);
|
||||
}
|
||||
|
||||
if (!ok)
|
||||
TelEngine::destruct(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// Process response ACK
|
||||
if (msg->isAck()) {
|
||||
// Responses can be received only by outgoing transactions
|
||||
if (outgoing()) {
|
||||
Debug(m_engine,DebugNote,"%s. Can't accept response ACK [%p]",
|
||||
m_debug.c_str(),this);
|
||||
TelEngine::destruct(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep the ACK if not already received one
|
||||
if (state() == Responded && !m_ack) {
|
||||
m_ack = msg;
|
||||
return;
|
||||
}
|
||||
|
||||
Debug(m_engine,DebugNote,
|
||||
"%s. Ignoring response ACK in state %u [%p]",
|
||||
m_debug.c_str(),state(),this);
|
||||
TelEngine::destruct(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// !!! Unknown message type
|
||||
TelEngine::destruct(msg);
|
||||
}
|
||||
|
||||
// Check timeouts. Manage retransmissions
|
||||
MGCPEvent* MGCPTransaction::checkTimeout(u_int64_t time)
|
||||
{
|
||||
if (!m_nextRetrans || time < m_nextRetrans)
|
||||
return 0;
|
||||
|
||||
// Terminate transaction if we have nothing to retransmit:
|
||||
// Outgoing: Initiated: retransmit command. Trying: adjust timeout
|
||||
// Incoming: Responded: retransmit response
|
||||
while (m_retransCount) {
|
||||
if ((outgoing() && state() != Initiated && state() != Trying) ||
|
||||
(!outgoing() && state() != Responded))
|
||||
break;
|
||||
|
||||
MGCPMessage* m = 0;
|
||||
if (state() == Initiated)
|
||||
m = m_cmd;
|
||||
else if (state() == Trying)
|
||||
;
|
||||
else
|
||||
m = m_response;
|
||||
|
||||
m_crtRetransInterval *= 2;
|
||||
m_retransCount--;
|
||||
m_nextRetrans = time + m_crtRetransInterval;
|
||||
|
||||
if (m) {
|
||||
send(m);
|
||||
Debug(m_engine,DebugInfo,"%s. Retransmitted %s remaining=%u [%p]",
|
||||
m_debug.c_str(),m->name().c_str(),m_retransCount,this);
|
||||
}
|
||||
else
|
||||
Debug(m_engine,DebugAll,"%s. Adjusted timeout remaining=%u [%p]",
|
||||
m_debug.c_str(),m_retransCount,this);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
m_timeout = (state() == Initiated || state() == Trying);
|
||||
return terminate();
|
||||
}
|
||||
|
||||
// Event termination notification
|
||||
void MGCPTransaction::eventTerminated(MGCPEvent* event)
|
||||
{
|
||||
if (event != m_lastEvent)
|
||||
return;
|
||||
DDebug(m_engine,DebugAll,"%s. Event (%p) terminated [%p]",m_debug.c_str(),event,this);
|
||||
m_lastEvent = 0;
|
||||
}
|
||||
|
||||
// Change transaction's state if the new state is a valid one
|
||||
void MGCPTransaction::changeState(State newState)
|
||||
{
|
||||
if (newState <= m_state)
|
||||
return;
|
||||
DDebug(m_engine,DebugInfo,"%s. Changing state from %u to %u [%p]",
|
||||
m_debug.c_str(),m_state,newState,this);
|
||||
m_state = newState;
|
||||
}
|
||||
|
||||
// (Re)send one the initial, provisional or final response. Change transaction's state
|
||||
void MGCPTransaction::send(MGCPMessage* msg)
|
||||
{
|
||||
if (!(msg && m_engine))
|
||||
return;
|
||||
if (msg == m_cmd)
|
||||
changeState(Initiated);
|
||||
else if (msg == m_provisional)
|
||||
changeState(Trying);
|
||||
else if (msg == m_response)
|
||||
changeState(Responded);
|
||||
else if (msg == m_ack)
|
||||
changeState(Ack);
|
||||
else
|
||||
return;
|
||||
String tmp;
|
||||
msg->toString(tmp);
|
||||
m_engine->sendData(tmp,m_address);
|
||||
}
|
||||
|
||||
// Check if received any final response. Create an event. Init timeout.
|
||||
// Send a response ACK if requested by the response
|
||||
MGCPEvent* MGCPTransaction::checkResponse(u_int64_t time)
|
||||
{
|
||||
if (!m_response)
|
||||
return 0;
|
||||
if (m_response->params.getParam("k") || m_response->params.getParam("K")) {
|
||||
m_ack = new MGCPMessage(this,0);
|
||||
send(m_ack);
|
||||
}
|
||||
initTimeout(time,true);
|
||||
changeState(Responded);
|
||||
return new MGCPEvent(this,m_response);
|
||||
}
|
||||
|
||||
// Init timeout for retransmission or transaction termination
|
||||
void MGCPTransaction::initTimeout(u_int64_t time, bool extra)
|
||||
{
|
||||
if (!extra) {
|
||||
m_crtRetransInterval = m_engine->retransInterval();
|
||||
m_retransCount = m_engine->retransCount();
|
||||
}
|
||||
else {
|
||||
m_crtRetransInterval = m_engine->extraTime();
|
||||
m_retransCount = 0;
|
||||
}
|
||||
m_nextRetrans = time + m_crtRetransInterval;
|
||||
}
|
||||
|
||||
// Remove from engine. Create event. Deref the transaction
|
||||
MGCPEvent* MGCPTransaction::terminate()
|
||||
{
|
||||
if (m_engine)
|
||||
m_engine->removeTrans(this,false);
|
||||
if (m_timeout)
|
||||
Debug(m_engine,DebugNote,"%s. Timeout in state %u [%p]",m_debug.c_str(),state(),this);
|
||||
else
|
||||
DDebug(m_engine,DebugAll,"%s. Terminated in state %u [%p]",m_debug.c_str(),state(),this);
|
||||
MGCPEvent* event = new MGCPEvent(this);
|
||||
deref();
|
||||
return event;
|
||||
}
|
||||
|
||||
/* vi: set ts=8 sw=4 sts=4 noet: */
|
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,5 @@
|
|||
Makefile
|
||||
YateLocal.mak
|
||||
YateLocal*
|
||||
core*
|
||||
*.o
|
||||
*.a
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
Makefile
|
||||
YateLocal.mak
|
||||
YateLocal*
|
||||
core*
|
||||
*.o
|
||||
*.a
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
Makefile
|
||||
YateLocal.mak
|
||||
YateLocal*
|
||||
core*
|
||||
yate-*
|
||||
*.o
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
Makefile
|
||||
YateLocal.mak
|
||||
YateLocal*
|
||||
core*
|
||||
*.o
|
||||
*.a
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
Makefile
|
||||
YateLocal.mak
|
||||
YateLocal*
|
||||
core*
|
||||
*.o
|
||||
*.a
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
Makefile
|
||||
YateLocal.mak
|
||||
YateLocal*
|
||||
core*
|
||||
*.o
|
||||
*.a
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
Makefile
|
||||
core*
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
*.yate
|
||||
*.orig
|
||||
*~
|
||||
.*.swp
|
|
@ -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;
|
||||
|
|
1371
modules/libypri.cpp
1371
modules/libypri.cpp
File diff suppressed because it is too large
Load Diff
|
@ -1,260 +0,0 @@
|
|||
/**
|
||||
* libypri.h
|
||||
* This file is part of the YATE Project http://YATE.null.ro
|
||||
*
|
||||
* Common C++ base classes for PRI cards telephony drivers
|
||||
*
|
||||
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
||||
* Copyright (C) 2004-2006 Null Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <yatephone.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libpri.h>
|
||||
}
|
||||
|
||||
namespace TelEngine {
|
||||
|
||||
class Fifo
|
||||
{
|
||||
public:
|
||||
Fifo(int buflen = 0);
|
||||
~Fifo();
|
||||
void clear();
|
||||
bool put(unsigned char value);
|
||||
unsigned int put(const unsigned char* buf, unsigned int length);
|
||||
unsigned char get();
|
||||
private:
|
||||
int m_buflen;
|
||||
int m_head;
|
||||
int m_tail;
|
||||
unsigned char* m_buffer;
|
||||
};
|
||||
|
||||
class DataErrors
|
||||
{
|
||||
public:
|
||||
inline DataErrors()
|
||||
: m_events(0), m_bytes(0)
|
||||
{ }
|
||||
inline void update(unsigned int nbytes)
|
||||
{ m_events++; m_bytes += nbytes; }
|
||||
inline void clear()
|
||||
{ m_events = 0; m_bytes = 0; }
|
||||
inline unsigned int events() const
|
||||
{ return m_events; }
|
||||
inline unsigned long bytes() const
|
||||
{ return m_bytes; }
|
||||
private:
|
||||
unsigned int m_events;
|
||||
unsigned long m_bytes;
|
||||
};
|
||||
|
||||
class PriChan;
|
||||
class PriDriver;
|
||||
|
||||
class PriSpan : public GenObject, public Mutex
|
||||
{
|
||||
public:
|
||||
virtual ~PriSpan();
|
||||
inline struct pri *pri()
|
||||
{ return m_pri; }
|
||||
inline PriDriver* driver() const
|
||||
{ return m_driver; }
|
||||
inline int span() const
|
||||
{ return m_span; }
|
||||
inline bool belongs(int chan) const
|
||||
{ return (chan >= m_offs) && (chan < m_offs+m_nchans); }
|
||||
inline int chan1() const
|
||||
{ return m_offs; }
|
||||
inline int chans() const
|
||||
{ return m_nchans; }
|
||||
inline int bchans() const
|
||||
{ return m_bchans; }
|
||||
inline int dplan() const
|
||||
{ return m_dplan; }
|
||||
inline int pres() const
|
||||
{ return m_pres; }
|
||||
inline unsigned int overlapped() const
|
||||
{ return m_overlapped; }
|
||||
inline bool inband() const
|
||||
{ return m_inband; }
|
||||
inline bool detect() const
|
||||
{ return m_detect; }
|
||||
inline bool outOfOrder() const
|
||||
{ return !m_ok; }
|
||||
inline int buflen() const
|
||||
{ return m_buflen; }
|
||||
inline int layer1() const
|
||||
{ return m_layer1; }
|
||||
int findEmptyChan(int first = 0, int last = 65535) const;
|
||||
PriChan *getChan(int chan) const;
|
||||
void idle();
|
||||
|
||||
protected:
|
||||
PriSpan(struct pri *_pri, PriDriver* driver, int span, int first, int chans, int dchan, Configuration& cfg, const String& sect);
|
||||
void runEvent(bool idleRun);
|
||||
void handleEvent(pri_event &ev);
|
||||
bool validChan(int chan) const;
|
||||
void restartChan(int chan, bool outgoing, bool force = false);
|
||||
void ringChan(int chan, pri_event_ring &ev);
|
||||
void infoChan(int chan, pri_event_ring &ev);
|
||||
void digitsChan(int chan, const char* digits);
|
||||
void hangupChan(int chan,pri_event_hangup &ev);
|
||||
void ackChan(int chan);
|
||||
void answerChan(int chan);
|
||||
void proceedingChan(int chan);
|
||||
void ringingChan(int chan);
|
||||
PriDriver* m_driver;
|
||||
int m_span;
|
||||
int m_offs;
|
||||
int m_nchans;
|
||||
int m_bchans;
|
||||
int m_dplan;
|
||||
int m_pres;
|
||||
int m_buflen;
|
||||
int m_layer1;
|
||||
bool m_inband;
|
||||
bool m_detect;
|
||||
unsigned int m_overlapped;
|
||||
String m_callednumber;
|
||||
struct pri *m_pri;
|
||||
u_int64_t m_restart;
|
||||
u_int64_t m_restartPeriod;
|
||||
bool m_dumpEvents;
|
||||
PriChan **m_chans;
|
||||
bool m_ok;
|
||||
};
|
||||
|
||||
class PriSource : public DataSource
|
||||
{
|
||||
public:
|
||||
PriSource(PriChan *owner, const char* format, unsigned int bufsize);
|
||||
virtual ~PriSource();
|
||||
|
||||
protected:
|
||||
PriChan *m_owner;
|
||||
DataBlock m_buffer;
|
||||
};
|
||||
|
||||
class PriConsumer : public DataConsumer
|
||||
{
|
||||
public:
|
||||
PriConsumer(PriChan *owner, const char* format, unsigned int bufsize);
|
||||
virtual ~PriConsumer();
|
||||
|
||||
protected:
|
||||
PriChan *m_owner;
|
||||
DataBlock m_buffer;
|
||||
};
|
||||
|
||||
class PriChan : public Channel
|
||||
{
|
||||
friend class PriSource;
|
||||
friend class PriConsumer;
|
||||
public:
|
||||
virtual ~PriChan();
|
||||
virtual void disconnected(bool final, const char *reason);
|
||||
virtual bool nativeConnect(DataEndpoint *peer);
|
||||
virtual bool msgProgress(Message& msg);
|
||||
virtual bool msgRinging(Message& msg);
|
||||
virtual bool msgAnswered(Message& msg);
|
||||
virtual bool msgTone(Message& msg, const char* tone);
|
||||
virtual bool msgText(Message& msg, const char* text);
|
||||
virtual bool msgDrop(Message& msg, const char* reason);
|
||||
virtual void callAccept(Message& msg);
|
||||
virtual void callRejected(const char* error, const char* reason = 0, const Message* msg = 0);
|
||||
inline PriSpan *span() const
|
||||
{ return m_span; }
|
||||
inline int chan() const
|
||||
{ return m_chan; }
|
||||
inline int absChan() const
|
||||
{ return m_abschan; }
|
||||
inline bool inUse() const
|
||||
{ return (m_ring || m_call); }
|
||||
inline bool inband() const
|
||||
{ return m_inband; }
|
||||
inline bool detect() const
|
||||
{ return m_detect; }
|
||||
void ring(pri_event_ring &ev);
|
||||
void hangup(int cause = 0);
|
||||
void sendDigit(char digit);
|
||||
void gotDigits(const char *digits, bool overlapped = false);
|
||||
bool call(Message &msg, const char *called = 0);
|
||||
bool answer();
|
||||
void answered();
|
||||
bool progress();
|
||||
bool ringing();
|
||||
void idle();
|
||||
void restart(bool outgoing = false);
|
||||
virtual bool openData(const char* format, int echoTaps = 0) = 0;
|
||||
virtual void closeData();
|
||||
virtual void goneUp();
|
||||
inline void setTimeout(u_int64_t tout)
|
||||
{ m_timeout = tout ? Time::now()+tout : 0; }
|
||||
inline void extTimeout(u_int64_t tout)
|
||||
{ if (m_timeout) setTimeout(tout); }
|
||||
const char *chanStatus() const;
|
||||
bool isISDN() const
|
||||
{ return m_isdn; }
|
||||
protected:
|
||||
PriChan(const PriSpan *parent, int chan, unsigned int bufsize);
|
||||
virtual void dataChanged();
|
||||
PriSpan *m_span;
|
||||
int m_chan;
|
||||
bool m_ring;
|
||||
u_int64_t m_timeout;
|
||||
q931_call* m_call;
|
||||
unsigned int m_bufsize;
|
||||
int m_abschan;
|
||||
bool m_isdn;
|
||||
bool m_inband;
|
||||
bool m_detect;
|
||||
};
|
||||
|
||||
class PriDriver : public Driver
|
||||
{
|
||||
friend class PriSpan;
|
||||
public:
|
||||
virtual ~PriDriver();
|
||||
virtual bool isBusy() const;
|
||||
virtual void dropAll();
|
||||
virtual bool msgExecute(Message& msg, String& dest);
|
||||
virtual void init(const char* configName);
|
||||
virtual PriSpan* createSpan(PriDriver* driver, int span, int first, int chans, Configuration& cfg, const String& sect) = 0;
|
||||
virtual PriChan* createChan(const PriSpan* span, int chan, unsigned int bufsize) = 0;
|
||||
static void netParams(Configuration& cfg, const String& sect, int chans, int* netType, int* swType, int* dChan);
|
||||
PriSpan* findSpan(int chan);
|
||||
PriChan* findFree(int first = -1, int last = -1);
|
||||
PriChan* findFree(const String& group);
|
||||
static inline u_int8_t bitswap(u_int8_t v)
|
||||
{ return s_bitswap[v]; }
|
||||
protected:
|
||||
PriDriver(const char* name);
|
||||
void statusModule(String& str);
|
||||
void statusParams(String& str);
|
||||
ObjList m_spans;
|
||||
ObjList m_groups;
|
||||
private:
|
||||
static u_int8_t s_bitswap[256];
|
||||
static bool s_init;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/* vi: set ts=8 sw=4 sts=4 noet: */
|
|
@ -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
|
|
@ -0,0 +1,9 @@
|
|||
Makefile
|
||||
core*
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
*.yate
|
||||
*.orig
|
||||
*~
|
||||
.*.swp
|
|
@ -0,0 +1,320 @@
|
|||
/**
|
||||
* heartbeat.cpp
|
||||
* This file is part of the YATE Project http://YATE.null.ro
|
||||
*
|
||||
* Linux-HA compatible heartbeat module
|
||||
*
|
||||
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
||||
* Copyright (C) 2004-2006 Null Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#include <yatengine.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#define AUTH_BLOCKSIZE 64
|
||||
|
||||
// This code is copied from plugins/HBauth/crc.c
|
||||
static u_int32_t const s_crctab[256] =
|
||||
{
|
||||
0x0,
|
||||
0x04C11DB7, 0x09823B6E, 0x0D4326D9, 0x130476DC, 0x17C56B6B,
|
||||
0x1A864DB2, 0x1E475005, 0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6,
|
||||
0x2B4BCB61, 0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD,
|
||||
0x4C11DB70, 0x48D0C6C7, 0x4593E01E, 0x4152FDA9, 0x5F15ADAC,
|
||||
0x5BD4B01B, 0x569796C2, 0x52568B75, 0x6A1936C8, 0x6ED82B7F,
|
||||
0x639B0DA6, 0x675A1011, 0x791D4014, 0x7DDC5DA3, 0x709F7B7A,
|
||||
0x745E66CD, 0x9823B6E0, 0x9CE2AB57, 0x91A18D8E, 0x95609039,
|
||||
0x8B27C03C, 0x8FE6DD8B, 0x82A5FB52, 0x8664E6E5, 0xBE2B5B58,
|
||||
0xBAEA46EF, 0xB7A96036, 0xB3687D81, 0xAD2F2D84, 0xA9EE3033,
|
||||
0xA4AD16EA, 0xA06C0B5D, 0xD4326D90, 0xD0F37027, 0xDDB056FE,
|
||||
0xD9714B49, 0xC7361B4C, 0xC3F706FB, 0xCEB42022, 0xCA753D95,
|
||||
0xF23A8028, 0xF6FB9D9F, 0xFBB8BB46, 0xFF79A6F1, 0xE13EF6F4,
|
||||
0xE5FFEB43, 0xE8BCCD9A, 0xEC7DD02D, 0x34867077, 0x30476DC0,
|
||||
0x3D044B19, 0x39C556AE, 0x278206AB, 0x23431B1C, 0x2E003DC5,
|
||||
0x2AC12072, 0x128E9DCF, 0x164F8078, 0x1B0CA6A1, 0x1FCDBB16,
|
||||
0x018AEB13, 0x054BF6A4, 0x0808D07D, 0x0CC9CDCA, 0x7897AB07,
|
||||
0x7C56B6B0, 0x71159069, 0x75D48DDE, 0x6B93DDDB, 0x6F52C06C,
|
||||
0x6211E6B5, 0x66D0FB02, 0x5E9F46BF, 0x5A5E5B08, 0x571D7DD1,
|
||||
0x53DC6066, 0x4D9B3063, 0x495A2DD4, 0x44190B0D, 0x40D816BA,
|
||||
0xACA5C697, 0xA864DB20, 0xA527FDF9, 0xA1E6E04E, 0xBFA1B04B,
|
||||
0xBB60ADFC, 0xB6238B25, 0xB2E29692, 0x8AAD2B2F, 0x8E6C3698,
|
||||
0x832F1041, 0x87EE0DF6, 0x99A95DF3, 0x9D684044, 0x902B669D,
|
||||
0x94EA7B2A, 0xE0B41DE7, 0xE4750050, 0xE9362689, 0xEDF73B3E,
|
||||
0xF3B06B3B, 0xF771768C, 0xFA325055, 0xFEF34DE2, 0xC6BCF05F,
|
||||
0xC27DEDE8, 0xCF3ECB31, 0xCBFFD686, 0xD5B88683, 0xD1799B34,
|
||||
0xDC3ABDED, 0xD8FBA05A, 0x690CE0EE, 0x6DCDFD59, 0x608EDB80,
|
||||
0x644FC637, 0x7A089632, 0x7EC98B85, 0x738AAD5C, 0x774BB0EB,
|
||||
0x4F040D56, 0x4BC510E1, 0x46863638, 0x42472B8F, 0x5C007B8A,
|
||||
0x58C1663D, 0x558240E4, 0x51435D53, 0x251D3B9E, 0x21DC2629,
|
||||
0x2C9F00F0, 0x285E1D47, 0x36194D42, 0x32D850F5, 0x3F9B762C,
|
||||
0x3B5A6B9B, 0x0315D626, 0x07D4CB91, 0x0A97ED48, 0x0E56F0FF,
|
||||
0x1011A0FA, 0x14D0BD4D, 0x19939B94, 0x1D528623, 0xF12F560E,
|
||||
0xF5EE4BB9, 0xF8AD6D60, 0xFC6C70D7, 0xE22B20D2, 0xE6EA3D65,
|
||||
0xEBA91BBC, 0xEF68060B, 0xD727BBB6, 0xD3E6A601, 0xDEA580D8,
|
||||
0xDA649D6F, 0xC423CD6A, 0xC0E2D0DD, 0xCDA1F604, 0xC960EBB3,
|
||||
0xBD3E8D7E, 0xB9FF90C9, 0xB4BCB610, 0xB07DABA7, 0xAE3AFBA2,
|
||||
0xAAFBE615, 0xA7B8C0CC, 0xA379DD7B, 0x9B3660C6, 0x9FF77D71,
|
||||
0x92B45BA8, 0x9675461F, 0x8832161A, 0x8CF30BAD, 0x81B02D74,
|
||||
0x857130C3, 0x5D8A9099, 0x594B8D2E, 0x5408ABF7, 0x50C9B640,
|
||||
0x4E8EE645, 0x4A4FFBF2, 0x470CDD2B, 0x43CDC09C, 0x7B827D21,
|
||||
0x7F436096, 0x7200464F, 0x76C15BF8, 0x68860BFD, 0x6C47164A,
|
||||
0x61043093, 0x65C52D24, 0x119B4BE9, 0x155A565E, 0x18197087,
|
||||
0x1CD86D30, 0x029F3D35, 0x065E2082, 0x0B1D065B, 0x0FDC1BEC,
|
||||
0x3793A651, 0x3352BBE6, 0x3E119D3F, 0x3AD08088, 0x2497D08D,
|
||||
0x2056CD3A, 0x2D15EBE3, 0x29D4F654, 0xC5A92679, 0xC1683BCE,
|
||||
0xCC2B1D17, 0xC8EA00A0, 0xD6AD50A5, 0xD26C4D12, 0xDF2F6BCB,
|
||||
0xDBEE767C, 0xE3A1CBC1, 0xE760D676, 0xEA23F0AF, 0xEEE2ED18,
|
||||
0xF0A5BD1D, 0xF464A0AA, 0xF9278673, 0xFDE69BC4, 0x89B8FD09,
|
||||
0x8D79E0BE, 0x803AC667, 0x84FBDBD0, 0x9ABC8BD5, 0x9E7D9662,
|
||||
0x933EB0BB, 0x97FFAD0C, 0xAFB010B1, 0xAB710D06, 0xA6322BDF,
|
||||
0xA2F33668, 0xBCB4666D, 0xB8757BDA, 0xB5365D03, 0xB1F740B4
|
||||
};
|
||||
|
||||
static u_int32_t crc(const char* buf, unsigned int len)
|
||||
{
|
||||
u_int32_t crc = 0;
|
||||
while (len--)
|
||||
crc = (crc << 8) ^ s_crctab[((crc >> 24) ^ *(buf++)) & 0xFF];
|
||||
return ~crc;
|
||||
}
|
||||
|
||||
|
||||
using namespace TelEngine;
|
||||
namespace { // anonymous
|
||||
|
||||
class TimerHandler : public MessageHandler
|
||||
{
|
||||
public:
|
||||
TimerHandler(unsigned int prio)
|
||||
: MessageHandler("engine.timer",prio)
|
||||
{ }
|
||||
virtual bool received(Message &msg);
|
||||
};
|
||||
|
||||
class HaltHandler : public MessageHandler
|
||||
{
|
||||
public:
|
||||
HaltHandler(unsigned int prio)
|
||||
: MessageHandler("engine.halt",prio)
|
||||
{ }
|
||||
virtual bool received(Message &msg);
|
||||
};
|
||||
|
||||
class HBeatPlugin : public Plugin
|
||||
{
|
||||
public:
|
||||
enum AuthType {
|
||||
AuthNone = 0,
|
||||
AuthCRC,
|
||||
AuthMD5,
|
||||
AuthSHA1,
|
||||
};
|
||||
HBeatPlugin();
|
||||
virtual ~HBeatPlugin();
|
||||
virtual void initialize();
|
||||
void sendHeartbeat(const Time& tStamp, bool goDown);
|
||||
private:
|
||||
Mutex m_mutex;
|
||||
Socket m_socket;
|
||||
String m_node;
|
||||
String m_authKey;
|
||||
int m_seq;
|
||||
int m_ttl;
|
||||
int m_authIdx;
|
||||
AuthType m_authType;
|
||||
};
|
||||
|
||||
static HBeatPlugin splugin;
|
||||
|
||||
|
||||
void HBeatPlugin::sendHeartbeat(const Time& tStamp, bool goDown)
|
||||
{
|
||||
m_mutex.lock();
|
||||
if (m_socket.valid()) {
|
||||
char hex[16];
|
||||
String buf;
|
||||
buf << "t=status\n";
|
||||
if (goDown)
|
||||
buf << "st=dead\n";
|
||||
else if (m_seq)
|
||||
buf << "st=active\n";
|
||||
else
|
||||
buf << "st=up\n";
|
||||
buf << "src=" << m_node << "\n";
|
||||
::snprintf(hex,sizeof(hex),"%x",++m_seq);
|
||||
buf << "seq=" << hex << "\n";
|
||||
::snprintf(hex,sizeof(hex),"%x",Engine::runId());
|
||||
buf << "hg=" << hex << "\n";
|
||||
::snprintf(hex,sizeof(hex),"%x",tStamp.sec());
|
||||
buf << "ts=" << hex << "\n";
|
||||
buf << "ld=n/a\n";
|
||||
buf << "ttl=" << m_ttl << "\n";
|
||||
if (m_authIdx > 0) {
|
||||
DataBlock key((void*)m_authKey.c_str(),m_authKey.length());
|
||||
switch (m_authType) {
|
||||
case AuthMD5:
|
||||
if (key.length() > AUTH_BLOCKSIZE) {
|
||||
MD5 hash(key);
|
||||
key.assign((void*)hash.rawDigest(),hash.rawLength());
|
||||
}
|
||||
break;
|
||||
case AuthSHA1:
|
||||
if (key.length() > AUTH_BLOCKSIZE) {
|
||||
SHA1 hash(key);
|
||||
key.assign((void*)hash.rawDigest(),hash.rawLength());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
String tmp = "none";
|
||||
const char* pkey = (const char*)key.data();
|
||||
switch (m_authType) {
|
||||
case AuthMD5:
|
||||
{
|
||||
unsigned char kbuf[AUTH_BLOCKSIZE];
|
||||
unsigned int i;
|
||||
for (i = 0; i < AUTH_BLOCKSIZE; i++)
|
||||
kbuf[i] = 0x36 ^ (i < key.length() ? pkey[i] : 0);
|
||||
MD5 hash1(kbuf,AUTH_BLOCKSIZE);
|
||||
hash1.update(buf);
|
||||
for (i = 0; i < AUTH_BLOCKSIZE; i++)
|
||||
kbuf[i] = 0x5c ^ (i < key.length() ? pkey[i] : 0);
|
||||
MD5 hash2(kbuf,AUTH_BLOCKSIZE);
|
||||
hash2.update((void*)hash1.rawDigest(),hash1.rawLength());
|
||||
tmp = hash2.hexDigest();
|
||||
}
|
||||
break;
|
||||
case AuthSHA1:
|
||||
{
|
||||
// code duplication as we don't have a hash factory
|
||||
unsigned char kbuf[AUTH_BLOCKSIZE];
|
||||
unsigned int i;
|
||||
for (i = 0; i < AUTH_BLOCKSIZE; i++)
|
||||
kbuf[i] = 0x36 ^ (i < key.length() ? pkey[i] : 0);
|
||||
SHA1 hash1(kbuf,AUTH_BLOCKSIZE);
|
||||
hash1.update(buf);
|
||||
for (i = 0; i < AUTH_BLOCKSIZE; i++)
|
||||
kbuf[i] = 0x5c ^ (i < key.length() ? pkey[i] : 0);
|
||||
SHA1 hash2(kbuf,AUTH_BLOCKSIZE);
|
||||
hash2.update((void*)hash1.rawDigest(),hash1.rawLength());
|
||||
tmp = hash2.hexDigest();
|
||||
}
|
||||
break;
|
||||
case AuthCRC:
|
||||
::snprintf(hex,sizeof(hex),"%x",crc(buf.c_str(),buf.length()));
|
||||
tmp = hex;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
buf << "auth=" << m_authIdx << " " << tmp << "\n";
|
||||
}
|
||||
buf = ">>>\n" + buf;
|
||||
buf << "<<<\n";
|
||||
// send the string including the terminating zero
|
||||
if (!m_socket.send(buf.c_str(),buf.length()+1))
|
||||
Debug("heartbeat",DebugWarn,"Could not send Heartbeat packet, error: %d",m_socket.error());
|
||||
if (goDown)
|
||||
m_socket.terminate();
|
||||
}
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
|
||||
bool TimerHandler::received(Message &msg)
|
||||
{
|
||||
splugin.sendHeartbeat(msg.msgTime(),Engine::exiting());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool HaltHandler::received(Message &msg)
|
||||
{
|
||||
splugin.sendHeartbeat(msg.msgTime(),true);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
HBeatPlugin::HBeatPlugin()
|
||||
: Plugin("heartbeat"),
|
||||
m_seq(0), m_ttl(2), m_authIdx(0), m_authType(AuthNone)
|
||||
{
|
||||
Output("Loaded module Heartbeat");
|
||||
}
|
||||
|
||||
HBeatPlugin::~HBeatPlugin()
|
||||
{
|
||||
Output("Unloading module Heartbeat");
|
||||
sendHeartbeat(Time(),true);
|
||||
}
|
||||
|
||||
void HBeatPlugin::initialize()
|
||||
{
|
||||
Configuration cfg(Engine::configFile("heartbeat"));
|
||||
Lock lock(m_mutex);
|
||||
m_authIdx = cfg.getIntValue("authentication","index");
|
||||
m_authKey = cfg.getValue("authentication","key");
|
||||
NamedString* auth = cfg.getKey("authentication","method");
|
||||
if (auth) {
|
||||
if (*auth == "crc")
|
||||
m_authType = AuthCRC;
|
||||
else if (*auth == "md5")
|
||||
m_authType = AuthMD5;
|
||||
else if (*auth == "sha1")
|
||||
m_authType = AuthSHA1;
|
||||
else
|
||||
m_authIdx = 0;
|
||||
}
|
||||
else
|
||||
m_authIdx = 0;
|
||||
if (!m_socket.valid()) {
|
||||
if (!cfg.getBoolValue("general","enabled",true))
|
||||
return;
|
||||
m_node = cfg.getValue("general","node");
|
||||
if (m_node.null())
|
||||
return;
|
||||
SocketAddr addr(AF_INET);
|
||||
if (!addr.host(cfg.getValue("general","host")))
|
||||
return;
|
||||
addr.port(cfg.getIntValue("general","port",694));
|
||||
if (!m_socket.create(AF_INET,SOCK_DGRAM,IPPROTO_UDP))
|
||||
return;
|
||||
Output("Initializing module Heartbeat, node '%s' to %s:%d",
|
||||
m_node.c_str(),addr.host().c_str(),addr.port());
|
||||
#ifdef SO_BROADCAST
|
||||
int opt = 1;
|
||||
if (cfg.getBoolValue("general","broadcast",true) && !m_socket.setOption(SOL_SOCKET,SO_BROADCAST,&opt,sizeof(opt)))
|
||||
Debug("heartbeat",DebugMild,"Could not enable broadcast on socket, error: %d",m_socket.error());
|
||||
#endif
|
||||
if (!(m_socket.connect(addr) && m_socket.setBlocking())) {
|
||||
Debug("heartbeat",DebugWarn,"Could not set up socket, error: %d",m_socket.error());
|
||||
m_socket.terminate();
|
||||
return;
|
||||
}
|
||||
m_ttl = cfg.getIntValue("general","ttl",2);
|
||||
if (m_ttl < 1)
|
||||
m_ttl = 1;
|
||||
Engine::install(new TimerHandler(cfg.getIntValue("priorities","engine.timer",150)));
|
||||
Engine::install(new HaltHandler(cfg.getIntValue("priorities","engine.halt",50)));
|
||||
}
|
||||
}
|
||||
|
||||
}; // anonymous namespace
|
||||
|
||||
/* vi: set ts=8 sw=4 sts=4 noet: */
|
|
@ -0,0 +1,550 @@
|
|||
/**
|
||||
* mgcpca.cpp
|
||||
* This file is part of the YATE Project http://YATE.null.ro
|
||||
*
|
||||
* Media Gateway Control Protocol - Call Agent - also remote data helper
|
||||
* for other protocols
|
||||
*
|
||||
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
||||
* Copyright (C) 2004-2006 Null Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#include <yatephone.h>
|
||||
#include <yatemgcp.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
using namespace TelEngine;
|
||||
namespace { // anonymous
|
||||
|
||||
class YMGCPEngine : public MGCPEngine
|
||||
{
|
||||
public:
|
||||
inline YMGCPEngine(const NamedList* params)
|
||||
: MGCPEngine(false,0,params)
|
||||
{ }
|
||||
virtual ~YMGCPEngine();
|
||||
virtual bool processEvent(MGCPTransaction* trans, MGCPMessage* msg, void* data);
|
||||
};
|
||||
|
||||
class MGCPWrapper : public DataEndpoint
|
||||
{
|
||||
YCLASS(MGCPWrapper,DataEndpoint)
|
||||
public:
|
||||
MGCPWrapper(CallEndpoint* conn, const char* media, Message& msg);
|
||||
~MGCPWrapper();
|
||||
bool sendDTMF(const String& tones);
|
||||
void gotDTMF(char tone);
|
||||
inline const String& id() const
|
||||
{ return m_id; }
|
||||
inline const String& ntfyId() const
|
||||
{ return m_notify; }
|
||||
inline const String& callId() const
|
||||
{ return m_master; }
|
||||
inline const String& connEp() const
|
||||
{ return m_connEp; }
|
||||
inline const String& connId() const
|
||||
{ return m_connId; }
|
||||
inline bool isAudio() const
|
||||
{ return m_audio; }
|
||||
static MGCPWrapper* find(const CallEndpoint* conn, const String& media);
|
||||
static MGCPWrapper* find(const String& id);
|
||||
static MGCPWrapper* findNotify(const String& id);
|
||||
bool processEvent(MGCPTransaction* tr, MGCPMessage* mm);
|
||||
bool rtpMessage(Message& msg);
|
||||
RefPointer<MGCPMessage> sendSync(MGCPMessage* mm, const SocketAddr& address);
|
||||
void clearConn();
|
||||
protected:
|
||||
virtual bool nativeConnect(DataEndpoint* peer);
|
||||
private:
|
||||
void addParams(MGCPMessage* mm);
|
||||
MGCPTransaction* m_tr;
|
||||
RefPointer<MGCPMessage> m_msg;
|
||||
String m_connId;
|
||||
String m_connEp;
|
||||
String m_id;
|
||||
String m_notify;
|
||||
String m_master;
|
||||
bool m_audio;
|
||||
};
|
||||
|
||||
class RtpHandler : public MessageHandler
|
||||
{
|
||||
public:
|
||||
RtpHandler(unsigned int prio) : MessageHandler("chan.rtp",prio) { }
|
||||
virtual bool received(Message &msg);
|
||||
};
|
||||
|
||||
class DTMFHandler : public MessageHandler
|
||||
{
|
||||
public:
|
||||
DTMFHandler() : MessageHandler("chan.dtmf",150) { }
|
||||
virtual bool received(Message &msg);
|
||||
};
|
||||
|
||||
class MGCPPlugin : public Module
|
||||
{
|
||||
public:
|
||||
MGCPPlugin();
|
||||
virtual ~MGCPPlugin();
|
||||
virtual void initialize();
|
||||
virtual void statusParams(String& str);
|
||||
virtual void statusDetail(String& str);
|
||||
};
|
||||
|
||||
static YMGCPEngine* s_engine = 0;
|
||||
static MGCPEndpoint* s_endpoint = 0;
|
||||
static String s_defaultEp;
|
||||
|
||||
static MGCPPlugin splugin;
|
||||
static ObjList s_wrappers;
|
||||
static Mutex s_mutex;
|
||||
|
||||
|
||||
// copy parameter (if present) with new name
|
||||
bool copyRename(NamedList& dest, const char* dname, const NamedList& src, const String& sname)
|
||||
{
|
||||
if (!sname)
|
||||
return false;
|
||||
const NamedString* value = src.getParam(sname);
|
||||
if (!value)
|
||||
return false;
|
||||
dest.addParam(dname,*value);
|
||||
return true;
|
||||
}
|
||||
|
||||
YMGCPEngine::~YMGCPEngine()
|
||||
{
|
||||
s_engine = 0;
|
||||
s_endpoint = 0;
|
||||
}
|
||||
|
||||
bool YMGCPEngine::processEvent(MGCPTransaction* trans, MGCPMessage* msg, void* data)
|
||||
{
|
||||
MGCPWrapper* wrap = YOBJECT(MGCPWrapper,static_cast<GenObject*>(data));
|
||||
Debug(this,DebugAll,"YMGCPEngine::processEvent(%p,%p,%p) wrap=%p [%p]",
|
||||
trans,msg,data,wrap,this);
|
||||
if (!trans)
|
||||
return false;
|
||||
if (wrap)
|
||||
return wrap->processEvent(trans,msg);
|
||||
if (!msg)
|
||||
return false;
|
||||
if (!data && !trans->outgoing() && msg->isCommand()) {
|
||||
if (msg->name() == "NTFY") {
|
||||
Debug(this,DebugStub,"NTFY from '%s'",msg->endpointId().c_str());
|
||||
wrap = MGCPWrapper::findNotify(msg->params.getValue("x"));
|
||||
if (wrap)
|
||||
return wrap->processEvent(trans,msg);
|
||||
trans->setResponse(515,"Unknown notification-id");
|
||||
return true;
|
||||
}
|
||||
if (msg->name() == "RSIP") {
|
||||
Debug(this,DebugStub,"RSIP from '%s'",msg->endpointId().c_str());
|
||||
trans->setResponse(200);
|
||||
return true;
|
||||
}
|
||||
Debug(this,DebugMild,"Unhandled '%s' from '%s'",
|
||||
msg->name().c_str(),msg->endpointId().c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
MGCPWrapper::MGCPWrapper(CallEndpoint* conn, const char* media, Message& msg)
|
||||
: DataEndpoint(conn,media),
|
||||
m_tr(0)
|
||||
{
|
||||
Debug(&splugin,DebugAll,"MGCPWrapper::MGCPWrapper(%p,'%s') [%p]",
|
||||
conn,media,this);
|
||||
m_id = "mgcp/";
|
||||
m_id << (unsigned int)::random();
|
||||
if (conn)
|
||||
m_master = conn->id();
|
||||
m_master = msg.getValue("id",(conn ? conn->id().c_str() : (const char*)0));
|
||||
m_audio = (name() == "audio");
|
||||
m_connEp = msg.getValue("mgcp_endpoint",s_defaultEp);
|
||||
s_mutex.lock();
|
||||
s_wrappers.append(this);
|
||||
// setupRTP(localip,rtcp);
|
||||
s_mutex.unlock();
|
||||
}
|
||||
|
||||
MGCPWrapper::~MGCPWrapper()
|
||||
{
|
||||
Debug(&splugin,DebugAll,"MGCPWrapper::~MGCPWrapper() '%s' [%p]",
|
||||
name().c_str(),this);
|
||||
s_mutex.lock();
|
||||
s_wrappers.remove(this,false);
|
||||
if (m_tr) {
|
||||
m_tr->userData(0);
|
||||
m_tr = 0;
|
||||
}
|
||||
s_mutex.unlock();
|
||||
m_msg = 0;
|
||||
clearConn();
|
||||
}
|
||||
|
||||
bool MGCPWrapper::processEvent(MGCPTransaction* tr, MGCPMessage* mm)
|
||||
{
|
||||
Debug(&splugin,DebugAll,"MGCPWrapper::processEvent(%p,%p) [%p]",
|
||||
tr,mm,this);
|
||||
if (tr == m_tr) {
|
||||
if (!mm || (tr->msgResponse())) {
|
||||
tr->userData(0);
|
||||
m_msg = mm;
|
||||
m_tr = 0;
|
||||
}
|
||||
}
|
||||
else if (mm) {
|
||||
if (mm->name() == "NTFY") {
|
||||
String* event = mm->params.getParam("o");
|
||||
if (event) {
|
||||
Debug(&splugin,DebugInfo,"Event '%s' [%p]",event->c_str(),this);
|
||||
}
|
||||
tr->setResponse(200);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MGCPWrapper::rtpMessage(Message& msg)
|
||||
{
|
||||
if (!s_endpoint)
|
||||
return false;
|
||||
const char* cmd = "MDCX";
|
||||
if (m_connId.null())
|
||||
cmd = "CRCX";
|
||||
if (msg.getBoolValue("terminate")) {
|
||||
if (m_connId.null())
|
||||
return true;
|
||||
cmd = "DLCX";
|
||||
}
|
||||
MGCPEpInfo* ep = s_endpoint->find(m_connEp);
|
||||
if (!ep)
|
||||
return false;
|
||||
RefPointer<MGCPMessage> mm = new MGCPMessage(s_engine,cmd,ep->toString());
|
||||
addParams(mm);
|
||||
if (m_connId.null()) {
|
||||
copyRename(mm->params,"x-transport",msg,"transport");
|
||||
copyRename(mm->params,"x-mediatype",msg,"media");
|
||||
}
|
||||
copyRename(mm->params,"x-localip",msg,"localip");
|
||||
copyRename(mm->params,"x-localport",msg,"localport");
|
||||
copyRename(mm->params,"x-remoteip",msg,"remoteip");
|
||||
copyRename(mm->params,"x-remoteport",msg,"remoteport");
|
||||
copyRename(mm->params,"x-payload",msg,"payload");
|
||||
copyRename(mm->params,"x-evpayload",msg,"evpayload");
|
||||
copyRename(mm->params,"x-format",msg,"format");
|
||||
copyRename(mm->params,"x-direction",msg,"direction");
|
||||
copyRename(mm->params,"x-ssrc",msg,"ssrc");
|
||||
copyRename(mm->params,"x-drillhole",msg,"drillhole");
|
||||
copyRename(mm->params,"x-autoaddr",msg,"autoaddr");
|
||||
copyRename(mm->params,"x-anyssrc",msg,"anyssrc");
|
||||
mm = sendSync(mm,ep->address);
|
||||
if (!mm)
|
||||
return false;
|
||||
if (m_connId.null())
|
||||
m_connId = mm->params.getParam("i");
|
||||
if (m_connId.null())
|
||||
return false;
|
||||
copyRename(msg,"localip",mm->params,"x-localip");
|
||||
copyRename(msg,"localport",mm->params,"x-localport");
|
||||
msg.setParam("rtpid",id());
|
||||
return true;
|
||||
}
|
||||
|
||||
void MGCPWrapper::clearConn()
|
||||
{
|
||||
if (m_connId.null() || !s_endpoint)
|
||||
return;
|
||||
MGCPEpInfo* ep = s_endpoint->find(m_connEp);
|
||||
if (!ep)
|
||||
return;
|
||||
MGCPMessage* mm = new MGCPMessage(s_engine,"DLCX",ep->toString());
|
||||
addParams(mm);
|
||||
s_engine->sendCommand(mm,ep->address);
|
||||
}
|
||||
|
||||
void MGCPWrapper::addParams(MGCPMessage* mm)
|
||||
{
|
||||
if (!mm)
|
||||
return;
|
||||
if (m_connId)
|
||||
mm->params.addParam("I",m_connId);
|
||||
if (m_master) {
|
||||
String callId;
|
||||
callId.hexify((void*)m_master.c_str(),m_master.length(),0,true);
|
||||
mm->params.addParam("C",callId);
|
||||
}
|
||||
}
|
||||
|
||||
RefPointer<MGCPMessage> MGCPWrapper::sendSync(MGCPMessage* mm, const SocketAddr& address)
|
||||
{
|
||||
while (m_msg) {
|
||||
if (Thread::check(false))
|
||||
return 0;
|
||||
Thread::msleep(10);
|
||||
}
|
||||
MGCPTransaction* tr = s_engine->sendCommand(mm,address);
|
||||
tr->userData(static_cast<GenObject*>(this));
|
||||
m_tr = tr;
|
||||
while (m_tr == tr)
|
||||
Thread::msleep(10);
|
||||
RefPointer<MGCPMessage> tmp = m_msg;
|
||||
m_msg = 0;
|
||||
Debug(&splugin,DebugStub,"MGCPWrapper::sendSync() returning %p [%p]",(void*)tmp,this);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
MGCPWrapper* MGCPWrapper::find(const CallEndpoint* conn, const String& media)
|
||||
{
|
||||
if (media.null() || !conn)
|
||||
return 0;
|
||||
Lock lock(s_mutex);
|
||||
ObjList* l = &s_wrappers;
|
||||
for (; l; l=l->next()) {
|
||||
const MGCPWrapper *p = static_cast<const MGCPWrapper *>(l->get());
|
||||
if (p && (p->getCall() == conn) && (p->name() == media))
|
||||
return const_cast<MGCPWrapper *>(p);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
MGCPWrapper* MGCPWrapper::find(const String& id)
|
||||
{
|
||||
if (id.null())
|
||||
return 0;
|
||||
Lock lock(s_mutex);
|
||||
ObjList* l = &s_wrappers;
|
||||
for (; l; l=l->next()) {
|
||||
const MGCPWrapper *p = static_cast<const MGCPWrapper *>(l->get());
|
||||
if (p && (p->id() == id))
|
||||
return const_cast<MGCPWrapper *>(p);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
MGCPWrapper* MGCPWrapper::findNotify(const String& id)
|
||||
{
|
||||
if (id.null())
|
||||
return 0;
|
||||
Lock lock(s_mutex);
|
||||
ObjList* l = &s_wrappers;
|
||||
for (; l; l=l->next()) {
|
||||
const MGCPWrapper *p = static_cast<const MGCPWrapper *>(l->get());
|
||||
if (p && (p->ntfyId() == id))
|
||||
return const_cast<MGCPWrapper *>(p);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool MGCPWrapper::sendDTMF(const String& tones)
|
||||
{
|
||||
Debug(&splugin,DebugStub,"MGCPWrapper::sendDTMF('%s') [%p]",
|
||||
tones.c_str(),this);
|
||||
MGCPEpInfo* ep = s_endpoint->find(m_connEp);
|
||||
if (!ep)
|
||||
return false;
|
||||
MGCPMessage* mm = new MGCPMessage(s_engine,"NTFY",ep->toString());
|
||||
addParams(mm);
|
||||
String tmp;
|
||||
for (unsigned int i = 0; i < tones.length(); i++) {
|
||||
if (tmp)
|
||||
tmp << ",";
|
||||
tmp << "D/" << tones.at(i);
|
||||
}
|
||||
mm->params.setParam("O",tmp);
|
||||
return s_engine->sendCommand(mm,ep->address) != 0;
|
||||
}
|
||||
|
||||
void MGCPWrapper::gotDTMF(char tone)
|
||||
{
|
||||
Debug(&splugin,DebugInfo,"MGCPWrapper::gotDTMF('%c') [%p]",tone,this);
|
||||
if (m_master.null())
|
||||
return;
|
||||
char buf[2];
|
||||
buf[0] = tone;
|
||||
buf[1] = 0;
|
||||
Message *m = new Message("chan.masquerade");
|
||||
m->addParam("id",m_master);
|
||||
m->addParam("message","chan.dtmf");
|
||||
m->addParam("text",buf);
|
||||
Engine::enqueue(m);
|
||||
}
|
||||
|
||||
bool MGCPWrapper::nativeConnect(DataEndpoint* peer)
|
||||
{
|
||||
MGCPWrapper* other = YOBJECT(MGCPWrapper,peer);
|
||||
if (!other)
|
||||
return false;
|
||||
// check if the other connection is using same endpoint
|
||||
if (other->connEp() != m_connEp)
|
||||
return false;
|
||||
if (other->connId().null()) {
|
||||
Debug(&splugin,DebugWarn,"Not bridging to uninitialized %p [%p]",other,this);
|
||||
return false;
|
||||
}
|
||||
Debug(&splugin,DebugStub,"Native bridging to %p [%p]",other,this);
|
||||
MGCPEpInfo* ep = s_endpoint->find(m_connEp);
|
||||
if (!ep)
|
||||
return false;
|
||||
MGCPMessage* mm = new MGCPMessage(s_engine,"MDCX",ep->toString());
|
||||
addParams(mm);
|
||||
mm->params.setParam("Z2",other->connId());
|
||||
return s_engine->sendCommand(mm,ep->address) != 0;
|
||||
}
|
||||
|
||||
|
||||
bool RtpHandler::received(Message& msg)
|
||||
{
|
||||
// refuse calls from a MGCP-GW
|
||||
if (!msg.getBoolValue("mgcp_allowed",true))
|
||||
return false;
|
||||
String trans = msg.getValue("transport");
|
||||
if (trans && !trans.startsWith("RTP/"))
|
||||
return false;
|
||||
Debug(&splugin,DebugAll,"RTP message received");
|
||||
|
||||
MGCPWrapper* w = 0;
|
||||
const char* media = msg.getValue("media","audio");
|
||||
CallEndpoint *ch = static_cast<CallEndpoint*>(msg.userData());
|
||||
if (ch) {
|
||||
w = MGCPWrapper::find(ch,media);
|
||||
if (w)
|
||||
Debug(&splugin,DebugAll,"Wrapper %p found by CallEndpoint",w);
|
||||
}
|
||||
if (!w) {
|
||||
w = MGCPWrapper::find(msg.getValue("rtpid"));
|
||||
if (w)
|
||||
Debug(&splugin,DebugAll,"Wrapper %p found by ID",w);
|
||||
}
|
||||
if (!(ch || w)) {
|
||||
Debug(&splugin,DebugWarn,"Neither call channel nor MGCP wrapper found!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (w)
|
||||
return w->rtpMessage(msg);
|
||||
|
||||
if (ch)
|
||||
ch->clearEndpoint(media);
|
||||
w = new MGCPWrapper(ch,media,msg);
|
||||
if (!w->rtpMessage(msg))
|
||||
return false;
|
||||
if (ch && ch->getPeer())
|
||||
w->connect(ch->getPeer()->getEndpoint(media));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool DTMFHandler::received(Message& msg)
|
||||
{
|
||||
String targetid(msg.getValue("targetid"));
|
||||
if (targetid.null())
|
||||
return false;
|
||||
String text(msg.getValue("text"));
|
||||
if (text.null())
|
||||
return false;
|
||||
MGCPWrapper* wrap = MGCPWrapper::find(targetid);
|
||||
return wrap && wrap->sendDTMF(text);
|
||||
}
|
||||
|
||||
|
||||
MGCPPlugin::MGCPPlugin()
|
||||
: Module("mgcpca","misc")
|
||||
{
|
||||
Output("Loaded module MGCP-CA");
|
||||
}
|
||||
|
||||
MGCPPlugin::~MGCPPlugin()
|
||||
{
|
||||
Output("Unloading module MGCP-CA");
|
||||
s_wrappers.clear();
|
||||
delete s_engine;
|
||||
}
|
||||
|
||||
void MGCPPlugin::statusParams(String& str)
|
||||
{
|
||||
s_mutex.lock();
|
||||
str.append("chans=",",") << s_wrappers.count();
|
||||
s_mutex.unlock();
|
||||
}
|
||||
|
||||
void MGCPPlugin::statusDetail(String& str)
|
||||
{
|
||||
s_mutex.lock();
|
||||
ObjList* l = s_wrappers.skipNull();
|
||||
for (; l; l=l->skipNext()) {
|
||||
MGCPWrapper* w = static_cast<MGCPWrapper*>(l->get());
|
||||
str.append(w->id(),",") << "=" << w->callId();
|
||||
}
|
||||
s_mutex.unlock();
|
||||
}
|
||||
|
||||
void MGCPPlugin::initialize()
|
||||
{
|
||||
Output("Initializing module MGCP Call Agent");
|
||||
Configuration cfg(Engine::configFile("mgcpca"));
|
||||
setup();
|
||||
NamedList* sect = cfg.getSection("engine");
|
||||
if (s_engine && sect)
|
||||
s_engine->initialize(*sect);
|
||||
while (!s_engine) {
|
||||
if (!(sect && sect->getBoolValue("enabled",true)))
|
||||
break;
|
||||
s_engine = new YMGCPEngine(sect);
|
||||
s_engine->debugChain(this);
|
||||
s_endpoint = new MGCPEndpoint(
|
||||
s_engine,
|
||||
cfg.getValue("endpoint","user","yate"),
|
||||
cfg.getValue("endpoint","host",s_engine->address().host()),
|
||||
cfg.getIntValue("endpoint","port")
|
||||
);
|
||||
int n = cfg.sections();
|
||||
for (int i = 0; i < n; i++) {
|
||||
sect = cfg.getSection(i);
|
||||
if (!sect)
|
||||
continue;
|
||||
String name(*sect);
|
||||
if (name.startSkip("gw") && name) {
|
||||
MGCPEpInfo* ep = s_endpoint->append(
|
||||
sect->getValue("user",name),
|
||||
sect->getValue("host"),
|
||||
sect->getIntValue("port",0)
|
||||
);
|
||||
if (ep) {
|
||||
if (s_defaultEp.null() || sect->getBoolValue("default"))
|
||||
s_defaultEp = ep->toString();
|
||||
}
|
||||
else
|
||||
Debug(this,DebugWarn,"Could not set endpoint for gateway '%s'",
|
||||
name.c_str());
|
||||
}
|
||||
}
|
||||
Debug(this,DebugNote,"Default remote endpoint: '%s'",s_defaultEp.c_str());
|
||||
Engine::install(new RtpHandler(cfg.getIntValue("general","priority",80)));
|
||||
Engine::install(new DTMFHandler);
|
||||
}
|
||||
}
|
||||
|
||||
}; // anonymous namespace
|
||||
|
||||
/* vi: set ts=8 sw=4 sts=4 noet: */
|
|
@ -0,0 +1,566 @@
|
|||
/**
|
||||
* mgcpgw.cpp
|
||||
* This file is part of the YATE Project http://YATE.null.ro
|
||||
*
|
||||
* Media Gateway Control Protocol - Gateway component
|
||||
*
|
||||
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
||||
* Copyright (C) 2004-2006 Null Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#include <yatephone.h>
|
||||
#include <yatemgcp.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
using namespace TelEngine;
|
||||
namespace { // anonymous
|
||||
|
||||
class YMGCPEngine : public MGCPEngine
|
||||
{
|
||||
public:
|
||||
inline YMGCPEngine(const NamedList* params)
|
||||
: MGCPEngine(true,0,params)
|
||||
{ }
|
||||
virtual ~YMGCPEngine();
|
||||
virtual bool processEvent(MGCPTransaction* trans, MGCPMessage* msg, void* data);
|
||||
private:
|
||||
bool createConn(MGCPTransaction* trans, MGCPMessage* msg);
|
||||
};
|
||||
|
||||
class MGCPChan : public Channel
|
||||
{
|
||||
YCLASS(MGCPChan,Channel);
|
||||
public:
|
||||
enum IdType {
|
||||
CallId,
|
||||
ConnId,
|
||||
NtfyId,
|
||||
};
|
||||
MGCPChan(const char* connId = 0);
|
||||
virtual ~MGCPChan();
|
||||
virtual void callAccept(Message& msg);
|
||||
virtual bool msgTone(Message& msg, const char* tone);
|
||||
const String& getId(IdType type) const;
|
||||
bool processEvent(MGCPTransaction* tr, MGCPMessage* mm);
|
||||
bool initialEvent(MGCPTransaction* tr, MGCPMessage* mm, const MGCPEndpointId& id);
|
||||
void activate(bool standby);
|
||||
protected:
|
||||
void disconnected(bool final, const char* reason);
|
||||
private:
|
||||
void endTransaction(int code = 407, const NamedList* params = 0);
|
||||
bool reqNotify(String& evt);
|
||||
bool setSignal(String& req);
|
||||
static void copyRtpParams(NamedList& dest, const NamedList& src);
|
||||
MGCPTransaction* m_tr;
|
||||
String m_connEp;
|
||||
String m_callId;
|
||||
String m_ntfyId;
|
||||
String m_rtpId;
|
||||
bool m_standby;
|
||||
bool m_isRtp;
|
||||
};
|
||||
|
||||
class MGCPPlugin : public Driver
|
||||
{
|
||||
public:
|
||||
MGCPPlugin();
|
||||
virtual ~MGCPPlugin();
|
||||
virtual bool msgExecute(Message& msg, String& dest);
|
||||
virtual void initialize();
|
||||
RefPointer<MGCPChan> findConn(const String* id, MGCPChan::IdType type);
|
||||
inline RefPointer<MGCPChan> findConn(const String& id, MGCPChan::IdType type)
|
||||
{ return findConn(&id,type); }
|
||||
void activate(bool standby);
|
||||
};
|
||||
|
||||
class DummyCall : public CallEndpoint
|
||||
{
|
||||
public:
|
||||
inline DummyCall()
|
||||
: CallEndpoint("dummy")
|
||||
{ }
|
||||
};
|
||||
|
||||
static MGCPPlugin splugin;
|
||||
|
||||
static YMGCPEngine* s_engine = 0;
|
||||
|
||||
// warm standby mode
|
||||
static bool s_standby = false;
|
||||
|
||||
// start time as UNIX time
|
||||
String s_started;
|
||||
|
||||
// copy parameter (if present) with new name
|
||||
bool copyRename(NamedList& dest, const char* dname, const NamedList& src, const String& sname)
|
||||
{
|
||||
if (!sname)
|
||||
return false;
|
||||
const NamedString* value = src.getParam(sname);
|
||||
if (!value)
|
||||
return false;
|
||||
dest.addParam(dname,*value);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
YMGCPEngine::~YMGCPEngine()
|
||||
{
|
||||
s_engine = 0;
|
||||
}
|
||||
|
||||
// process all MGCP events, distribute them according to their type
|
||||
bool YMGCPEngine::processEvent(MGCPTransaction* trans, MGCPMessage* msg, void* data)
|
||||
{
|
||||
RefPointer<MGCPChan> chan = YOBJECT(MGCPChan,static_cast<GenObject*>(data));
|
||||
Debug(this,DebugAll,"YMGCPEngine::processEvent(%p,%p,%p) [%p]",
|
||||
trans,msg,data,this);
|
||||
if (!trans)
|
||||
return false;
|
||||
if (chan)
|
||||
return chan->processEvent(trans,msg);
|
||||
if (!msg)
|
||||
return false;
|
||||
if (!data && !trans->outgoing() && msg->isCommand()) {
|
||||
if (msg->name() == "CRCX") {
|
||||
// create connection
|
||||
if (!createConn(trans,msg))
|
||||
trans->setResponse(500); // unknown endpoint
|
||||
return true;
|
||||
}
|
||||
if ((msg->name() == "DLCX") || // delete
|
||||
(msg->name() == "MDCX") || // modify
|
||||
(msg->name() == "AUCX")) { // audit
|
||||
// connection must exist already
|
||||
chan = splugin.findConn(msg->params.getParam("i"),MGCPChan::ConnId);
|
||||
if (chan)
|
||||
return chan->processEvent(trans,msg);
|
||||
trans->setResponse(515); // no connection
|
||||
return true;
|
||||
}
|
||||
if (msg->name() == "RQNT") {
|
||||
// request notify
|
||||
chan = splugin.findConn(msg->params.getParam("x"),MGCPChan::NtfyId);
|
||||
if (chan)
|
||||
return chan->processEvent(trans,msg);
|
||||
}
|
||||
if (msg->name() == "EPCF") {
|
||||
// endpoint configuration
|
||||
NamedList params("");
|
||||
bool standby = msg->params.getBoolValue("x-standby",s_standby);
|
||||
if (standby != s_standby) {
|
||||
params << "Switching to " << (standby ? "standby" : "active") << " mode";
|
||||
Debug(this,DebugNote,"%s",params.c_str());
|
||||
s_standby = standby;
|
||||
splugin.activate(standby);
|
||||
}
|
||||
params.addParam("x-standby",String::boolText(s_standby));
|
||||
trans->setResponse(200,¶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<GenObject*>(this));
|
||||
}
|
||||
NamedList params("");
|
||||
params.addParam("I",address());
|
||||
params.addParam("x-standby",String::boolText(m_standby));
|
||||
if (mm->name() == "DLCX") {
|
||||
disconnect();
|
||||
status("deleted");
|
||||
clearEndpoint();
|
||||
m_address.clear();
|
||||
tr->setResponse(250,¶ms);
|
||||
return true;
|
||||
}
|
||||
if (mm->name() == "MDCX") {
|
||||
NamedString* param = mm->params.getParam("z2");
|
||||
if (param) {
|
||||
// native connect requested
|
||||
RefPointer<MGCPChan> chan2 = splugin.findConn(*param,MGCPChan::ConnId);
|
||||
if (!chan2) {
|
||||
tr->setResponse(515); // no connection
|
||||
return true;
|
||||
}
|
||||
if (!connect(chan2,mm->params.getValue("x-reason","bridged"))) {
|
||||
tr->setResponse(400); // unspecified error
|
||||
return true;
|
||||
}
|
||||
}
|
||||
param = mm->params.getParam("x");
|
||||
if (param)
|
||||
m_ntfyId = *param;
|
||||
if (m_isRtp) {
|
||||
Message m("chan.rtp");
|
||||
m.addParam("mgcp_allowed",String::boolText(false));
|
||||
copyRtpParams(m,mm->params);
|
||||
if (m_rtpId)
|
||||
m.setParam("rtpid",m_rtpId);
|
||||
m.userData(this);
|
||||
if (Engine::dispatch(m)) {
|
||||
copyRename(params,"x-localip",m,"localip");
|
||||
copyRename(params,"x-localport",m,"localport");
|
||||
m_rtpId = m.getValue("rtpid",m_rtpId);
|
||||
}
|
||||
}
|
||||
tr->setResponse(200,¶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<String*>(item->get())) && ok;
|
||||
delete lst;
|
||||
}
|
||||
// what we must signal now
|
||||
req = mm->params.getParam("s");
|
||||
if (req) {
|
||||
ObjList* lst = req->split(',');
|
||||
for (ObjList* item = lst->skipNull(); item; item = item->skipNext())
|
||||
ok = setSignal(*static_cast<String*>(item->get())) && ok;
|
||||
delete lst;
|
||||
}
|
||||
tr->setResponse(ok ? 200 : 538,¶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<GenObject*>(this));
|
||||
m->addParam("called",id.id());
|
||||
if (startRouter(m)) {
|
||||
tr->sendProvisional();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MGCPChan::copyRtpParams(NamedList& dest, const NamedList& src)
|
||||
{
|
||||
copyRename(dest,"transport",src,"x-transport");
|
||||
copyRename(dest,"media",src,"x-media");
|
||||
copyRename(dest,"localip",src,"x-localip");
|
||||
copyRename(dest,"localport",src,"x-localport");
|
||||
copyRename(dest,"remoteip",src,"x-remoteip");
|
||||
copyRename(dest,"remoteport",src,"x-remoteport");
|
||||
copyRename(dest,"payload",src,"x-payload");
|
||||
copyRename(dest,"evpayload",src,"x-evpayload");
|
||||
copyRename(dest,"format",src,"x-format");
|
||||
copyRename(dest,"direction",src,"x-direction");
|
||||
copyRename(dest,"ssrc",src,"x-ssrc");
|
||||
copyRename(dest,"drillhole",src,"x-drillhole");
|
||||
copyRename(dest,"autoaddr",src,"x-autoaddr");
|
||||
copyRename(dest,"anyssrc",src,"x-anyssrc");
|
||||
}
|
||||
|
||||
MGCPPlugin::MGCPPlugin()
|
||||
: Driver("mgcpgw","misc")
|
||||
{
|
||||
Output("Loaded module MGCP-GW");
|
||||
}
|
||||
|
||||
MGCPPlugin::~MGCPPlugin()
|
||||
{
|
||||
Output("Unloading module MGCP-GW");
|
||||
delete s_engine;
|
||||
}
|
||||
|
||||
bool MGCPPlugin::msgExecute(Message& msg, String& dest)
|
||||
{
|
||||
Debug(this,DebugWarn,"Received execute request for gateway '%s'",dest.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
RefPointer<MGCPChan> MGCPPlugin::findConn(const String* id, MGCPChan::IdType type)
|
||||
{
|
||||
if (!id || id->null())
|
||||
return 0;
|
||||
Lock lock(this);
|
||||
for (ObjList* l = channels().skipNull(); l; l = l->skipNext()) {
|
||||
MGCPChan* c = static_cast<MGCPChan*>(l->get());
|
||||
if (c->getId(type) == *id)
|
||||
return c;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MGCPPlugin::activate(bool standby)
|
||||
{
|
||||
lock();
|
||||
ListIterator iter(channels());
|
||||
while (GenObject* obj = iter.get()) {
|
||||
RefPointer<MGCPChan> chan = static_cast<MGCPChan*>(obj);
|
||||
if (chan) {
|
||||
unlock();
|
||||
chan->activate(standby);
|
||||
lock();
|
||||
}
|
||||
}
|
||||
unlock();
|
||||
}
|
||||
|
||||
void MGCPPlugin::initialize()
|
||||
{
|
||||
Output("Initializing module MGCP Gateway");
|
||||
Configuration cfg(Engine::configFile("mgcpgw"));
|
||||
setup();
|
||||
NamedList* sect = cfg.getSection("engine");
|
||||
if (s_engine && sect)
|
||||
s_engine->initialize(*sect);
|
||||
while (!s_engine) {
|
||||
if (!(sect && sect->getBoolValue("enabled",true)))
|
||||
break;
|
||||
s_started = Time::secNow();
|
||||
s_standby = cfg.getBoolValue("general","standby",false);
|
||||
s_engine = new YMGCPEngine(sect);
|
||||
s_engine->debugChain(this);
|
||||
int n = cfg.sections();
|
||||
for (int i = 0; i < n; i++) {
|
||||
sect = cfg.getSection(i);
|
||||
if (!sect)
|
||||
continue;
|
||||
String name(*sect);
|
||||
if (name.startSkip("ep") && name) {
|
||||
MGCPEndpoint* ep = new MGCPEndpoint(
|
||||
s_engine,
|
||||
sect->getValue("local_user",name),
|
||||
sect->getValue("local_host",s_engine->address().host()),
|
||||
sect->getIntValue("local_port")
|
||||
);
|
||||
MGCPEpInfo* ca = ep->append(0,
|
||||
sect->getValue("remote_host"),
|
||||
sect->getIntValue("remote_port",0)
|
||||
);
|
||||
if (ca) {
|
||||
if (sect->getBoolValue("announce",true)) {
|
||||
MGCPMessage* mm = new MGCPMessage(s_engine,"RSIP",ep->toString());
|
||||
mm->params.addParam("RM","restart");
|
||||
mm->params.addParam("x-standby",String::boolText(s_standby));
|
||||
mm->params.addParam("x-started",s_started);
|
||||
s_engine->sendCommand(mm,ca->address);
|
||||
}
|
||||
}
|
||||
else
|
||||
Debug(this,DebugWarn,"Could not set remote endpoint for '%s'",
|
||||
name.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}; // anonymous namespace
|
||||
|
||||
/* vi: set ts=8 sw=4 sts=4 noet: */
|
|
@ -0,0 +1,352 @@
|
|||
/**
|
||||
* mrcpspeech.cpp
|
||||
* This file is part of the YATE Project http://YATE.null.ro
|
||||
*
|
||||
* Detector and synthesizer for voice and tones using a MRCP v2 server
|
||||
*
|
||||
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
||||
* Copyright (C) 2004-2006 Null Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <yatephone.h>
|
||||
|
||||
using namespace TelEngine;
|
||||
|
||||
namespace { // anonymous
|
||||
|
||||
class MrcpConnection : public CallEndpoint
|
||||
{
|
||||
public:
|
||||
inline MrcpConnection(const char* id, const char* original)
|
||||
: CallEndpoint(id),
|
||||
m_original(original), m_socket(0)
|
||||
{ }
|
||||
virtual ~MrcpConnection();
|
||||
bool init(Message& msg, const char* target);
|
||||
bool answered(Message& msg);
|
||||
private:
|
||||
String m_original;
|
||||
Socket* m_socket;
|
||||
};
|
||||
|
||||
class MrcpConsumer : public DataConsumer
|
||||
{
|
||||
YCLASS(MrcpConsumer,DataConsumer)
|
||||
public:
|
||||
MrcpConsumer(const String& id, const char* target, const char* format = 0);
|
||||
virtual ~MrcpConsumer();
|
||||
virtual bool setFormat(const DataFormat& format);
|
||||
virtual void Consume(const DataBlock& data, unsigned long tStamp);
|
||||
bool init(Message& msg);
|
||||
private:
|
||||
void cleanup();
|
||||
DataSource* m_source;
|
||||
MrcpConnection* m_chan;
|
||||
String m_id;
|
||||
String m_target;
|
||||
};
|
||||
|
||||
class AttachHandler : public MessageHandler
|
||||
{
|
||||
public:
|
||||
AttachHandler() : MessageHandler("chan.attach") { }
|
||||
virtual bool received(Message& msg);
|
||||
};
|
||||
|
||||
class RecordHandler : public MessageHandler
|
||||
{
|
||||
public:
|
||||
RecordHandler() : MessageHandler("chan.record") { }
|
||||
virtual bool received(Message& msg);
|
||||
};
|
||||
|
||||
class MrcpRtpHandler : public MessageHandler
|
||||
{
|
||||
public:
|
||||
MrcpRtpHandler() : MessageHandler("chan.rtp",150) { }
|
||||
virtual bool received(Message& msg);
|
||||
};
|
||||
|
||||
class MrcpModule : public Module
|
||||
{
|
||||
public:
|
||||
MrcpModule();
|
||||
virtual ~MrcpModule();
|
||||
virtual void initialize();
|
||||
virtual bool received(Message& msg, int id);
|
||||
virtual void statusParams(String& str);
|
||||
private:
|
||||
bool m_first;
|
||||
};
|
||||
|
||||
static Mutex s_mutex;
|
||||
static ObjList s_conns;
|
||||
static int s_count = 0;
|
||||
static int s_total = 0;
|
||||
|
||||
static MrcpModule plugin;
|
||||
|
||||
MrcpConsumer::MrcpConsumer(const String& id, const char* target, const char* format)
|
||||
: DataConsumer(format),
|
||||
m_source(0), m_chan(0), m_id(id)
|
||||
{
|
||||
s_mutex.lock();
|
||||
s_count++;
|
||||
s_mutex.unlock();
|
||||
if (target) {
|
||||
m_target = target;
|
||||
m_target >> "mrcp/";
|
||||
m_target = "sip/" + m_target;
|
||||
}
|
||||
Debug(&plugin,DebugAll,"MrcpConsumer::MrcpConsumer('%s','%s','%s') [%p]",
|
||||
id.c_str(),target,format,this);
|
||||
}
|
||||
|
||||
MrcpConsumer::~MrcpConsumer()
|
||||
{
|
||||
Debug(&plugin,DebugAll,"MrcpConsumer::~MrcpConsumer '%s' [%p]",
|
||||
m_id.c_str(),this);
|
||||
s_mutex.lock();
|
||||
s_count--;
|
||||
s_mutex.unlock();
|
||||
cleanup();
|
||||
}
|
||||
|
||||
bool MrcpConsumer::init(Message& msg)
|
||||
{
|
||||
if (m_chan)
|
||||
return false;
|
||||
String id("mrcp/");
|
||||
s_mutex.lock();
|
||||
s_total++;
|
||||
id << s_total;
|
||||
s_mutex.unlock();
|
||||
m_source = new DataSource(m_format);
|
||||
m_chan = new MrcpConnection(id,m_id);
|
||||
m_chan->setSource(m_source);
|
||||
if (m_chan->init(msg,m_target))
|
||||
return true;
|
||||
Debug(&plugin,DebugWarn,"Failed to start connection '%s' for '%s' [%p]",
|
||||
id.c_str(),m_id.c_str(),this);
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
void MrcpConsumer::cleanup()
|
||||
{
|
||||
Debug(&plugin,DebugAll,"MrcpConsumer::cleanup() '%s' s=%p c=%p [%p]",
|
||||
m_id.c_str(),m_source,m_chan,this);
|
||||
if (m_source) {
|
||||
m_source->deref();
|
||||
m_source = 0;
|
||||
}
|
||||
if (m_chan) {
|
||||
m_chan->disconnect();
|
||||
m_chan->deref();
|
||||
m_chan = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool MrcpConsumer::setFormat(const DataFormat& format)
|
||||
{
|
||||
Debug(&plugin,DebugAll,"MrcpConsumer::setFormat('%s') '%s' s=%p c=%p [%p]",
|
||||
format.c_str(),m_id.c_str(),m_source,m_chan,this);
|
||||
return m_source && m_source->setFormat(format);
|
||||
}
|
||||
|
||||
void MrcpConsumer::Consume(const DataBlock& data, unsigned long timeDelta)
|
||||
{
|
||||
if (m_source)
|
||||
m_source->Forward(data,timeDelta);
|
||||
}
|
||||
|
||||
|
||||
MrcpConnection::~MrcpConnection()
|
||||
{
|
||||
s_mutex.lock();
|
||||
s_conns.remove(this,false);
|
||||
s_mutex.unlock();
|
||||
if (m_socket) {
|
||||
m_socket->terminate();
|
||||
delete m_socket;
|
||||
m_socket = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool MrcpConnection::init(Message& msg, const char* target)
|
||||
{
|
||||
if (!target)
|
||||
return false;
|
||||
s_mutex.lock();
|
||||
if (!s_conns.find(this))
|
||||
s_conns.append(this);
|
||||
s_mutex.unlock();
|
||||
Message m("call.execute");
|
||||
m.addParam("id",id());
|
||||
m.addParam("callto",target);
|
||||
m.copyParam(msg,"caller");
|
||||
m.copyParam(msg,"called");
|
||||
m.addParam("media",String::boolText(true));
|
||||
m.addParam("media_application",String::boolText(true));
|
||||
m.addParam("transport_application","TCP/MRCPv2");
|
||||
m.addParam("formats_application","1"); // defined by the standard
|
||||
m.userData(this);
|
||||
return Engine::dispatch(m);
|
||||
}
|
||||
|
||||
bool MrcpConnection::answered(Message& msg)
|
||||
{
|
||||
Debug(&plugin,DebugAll,"MrcpConnection::answered() '%s' [%p]",
|
||||
id().c_str(),this);
|
||||
int port = msg.getIntValue("rtp_port_application");
|
||||
if (port > 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Attach a tone detector on "chan.attach" as consumer or sniffer
|
||||
bool AttachHandler::received(Message& msg)
|
||||
{
|
||||
String cons(msg.getValue("consumer"));
|
||||
if (!cons.startsWith("mrcp/"))
|
||||
cons.clear();
|
||||
String snif(msg.getValue("sniffer"));
|
||||
if (!snif.startsWith("mrcp/"))
|
||||
snif.clear();
|
||||
if (cons.null() && snif.null())
|
||||
return false;
|
||||
CallEndpoint* ch = static_cast<CallEndpoint *>(msg.userObject("CallEndpoint"));
|
||||
if (ch) {
|
||||
if (cons) {
|
||||
MrcpConsumer* c = new MrcpConsumer(ch->id(),cons,msg.getValue("format","slin"));
|
||||
if (c->init(msg))
|
||||
ch->setConsumer(c);
|
||||
c->deref();
|
||||
}
|
||||
if (snif) {
|
||||
DataEndpoint* de = ch->setEndpoint();
|
||||
// try to reinit sniffer if one already exists
|
||||
MrcpConsumer* c = static_cast<MrcpConsumer*>(de->getSniffer(snif));
|
||||
if (c)
|
||||
c->init(msg);
|
||||
else {
|
||||
c = new MrcpConsumer(ch->id(),snif,msg.getValue("format","slin"));
|
||||
if (c->init(msg))
|
||||
de->addSniffer(c);
|
||||
c->deref();
|
||||
}
|
||||
}
|
||||
return msg.getBoolValue("single");
|
||||
}
|
||||
else
|
||||
Debug(&plugin,DebugWarn,"Attach request with no call endpoint!");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Attach a tone detector on "chan.record" - needs just a CallEndpoint
|
||||
bool RecordHandler::received(Message& msg)
|
||||
{
|
||||
String src(msg.getValue("call"));
|
||||
String id(msg.getValue("id"));
|
||||
if (!src.startsWith("mrcp/"))
|
||||
return false;
|
||||
DataEndpoint* de = static_cast<DataEndpoint *>(msg.userObject("DataEndpoint"));
|
||||
CallEndpoint* ch = static_cast<CallEndpoint *>(msg.userObject("CallEndpoint"));
|
||||
if (ch) {
|
||||
id = ch->id();
|
||||
if (!de)
|
||||
de = ch->setEndpoint();
|
||||
}
|
||||
if (de) {
|
||||
MrcpConsumer* c = new MrcpConsumer(id,src,msg.getValue("format","slin"));
|
||||
if (c->init(msg))
|
||||
de->setCallRecord(c);
|
||||
c->deref();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
Debug(&plugin,DebugWarn,"Record request with no call endpoint!");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool MrcpRtpHandler::received(Message& msg)
|
||||
{
|
||||
String trans = msg.getValue("transport");
|
||||
trans.toUpper();
|
||||
bool tls = false;
|
||||
if (trans == "TCP/TLS/MRCPV2")
|
||||
tls = true;
|
||||
else if (trans != "TCP/MRCPV2")
|
||||
return false;
|
||||
Debug(&plugin,DebugAll,"RTP message received, TLS: %s",String::boolText(tls));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
MrcpModule::MrcpModule()
|
||||
: Module("mrcp","misc"), m_first(true)
|
||||
{
|
||||
Output("Loaded module MRCP");
|
||||
}
|
||||
|
||||
MrcpModule::~MrcpModule()
|
||||
{
|
||||
Output("Unloading module MRCP");
|
||||
}
|
||||
|
||||
bool MrcpModule::received(Message& msg, int id)
|
||||
{
|
||||
if (id == Answered) {
|
||||
const String* cid = msg.getParam("targetid");
|
||||
if (!cid)
|
||||
cid = msg.getParam("peerid");
|
||||
if (!(cid && cid->startsWith("mrcp/")))
|
||||
return false;
|
||||
s_mutex.lock();
|
||||
RefPointer<MrcpConnection> conn = static_cast<MrcpConnection*>(s_conns[*cid]);
|
||||
s_mutex.unlock();
|
||||
return conn && conn->answered(msg);
|
||||
}
|
||||
return Module::received(msg,id);
|
||||
}
|
||||
|
||||
void MrcpModule::statusParams(String& str)
|
||||
{
|
||||
str.append("count=",",") << s_count;
|
||||
str.append("total=",",") << s_total;
|
||||
}
|
||||
|
||||
void MrcpModule::initialize()
|
||||
{
|
||||
Output("Initializing module MrcpSpeech");
|
||||
setup();
|
||||
if (m_first) {
|
||||
m_first = false;
|
||||
Engine::install(new AttachHandler);
|
||||
Engine::install(new RecordHandler);
|
||||
Engine::install(new MrcpRtpHandler);
|
||||
installRelay(Answered);
|
||||
}
|
||||
}
|
||||
|
||||
}; // anonymous namespace
|
||||
|
||||
/* vi: set ts=8 sw=4 sts=4 noet: */
|
|
@ -1,5 +1,5 @@
|
|||
Makefile
|
||||
YateLocal.mak
|
||||
YateLocal*
|
||||
core*
|
||||
*.o
|
||||
*.a
|
||||
|
|
|
@ -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 $@ $^
|
||||
|
|
|
@ -1,698 +0,0 @@
|
|||
/**
|
||||
* wpchan.cpp
|
||||
* This file is part of the YATE Project http://YATE.null.ro
|
||||
*
|
||||
* Wanpipe PRI cards telephony driver
|
||||
*
|
||||
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
||||
* Copyright (C) 2004-2006 Null Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <modules/libypri.h>
|
||||
|
||||
#ifdef _WINDOWS
|
||||
#error This module is not for Windows
|
||||
#else
|
||||
|
||||
extern "C" {
|
||||
|
||||
#define INVALID_HANDLE_VALUE (-1)
|
||||
#define __LINUX__
|
||||
#include <linux/if_wanpipe.h>
|
||||
#include <linux/if.h>
|
||||
#include <linux/wanpipe_defines.h>
|
||||
#include <linux/wanpipe_cfg.h>
|
||||
#include <linux/wanpipe.h>
|
||||
#include <linux/sdla_aft_te1.h>
|
||||
|
||||
#ifdef HAVE_WANPIPE_HWEC
|
||||
#include <wanec_iface.h>
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
using namespace TelEngine;
|
||||
namespace { // anonymous
|
||||
|
||||
class WpChan;
|
||||
class WpData;
|
||||
class WpDriver;
|
||||
|
||||
class WpSpan : public PriSpan, public Thread
|
||||
{
|
||||
friend class WpData;
|
||||
friend class WpDriver;
|
||||
public:
|
||||
virtual ~WpSpan();
|
||||
virtual void run();
|
||||
inline int overRead() const
|
||||
{ return m_overRead; }
|
||||
unsigned long bChanMap() const;
|
||||
|
||||
private:
|
||||
WpSpan(struct pri *_pri, PriDriver* driver, int span, int first, int chans, int dchan, Configuration& cfg, const String& sect, HANDLE fd);
|
||||
HANDLE m_fd;
|
||||
WpData *m_data;
|
||||
int m_overRead;
|
||||
};
|
||||
|
||||
class WpSource : public PriSource
|
||||
{
|
||||
public:
|
||||
WpSource(WpChan *owner, const char* format, unsigned int bufsize);
|
||||
~WpSource();
|
||||
void put(unsigned char val);
|
||||
|
||||
private:
|
||||
unsigned int m_bufpos;
|
||||
};
|
||||
|
||||
class WpConsumer : public PriConsumer, public Fifo
|
||||
{
|
||||
public:
|
||||
WpConsumer(WpChan *owner, const char* format, unsigned int bufsize);
|
||||
~WpConsumer();
|
||||
|
||||
virtual void Consume(const DataBlock &data, unsigned long tStamp);
|
||||
private:
|
||||
DataErrors m_overruns;
|
||||
};
|
||||
|
||||
class WpChan : public PriChan
|
||||
{
|
||||
friend class WpSource;
|
||||
friend class WpConsumer;
|
||||
friend class WpData;
|
||||
public:
|
||||
WpChan(const PriSpan *parent, int chan, unsigned int bufsize);
|
||||
virtual ~WpChan();
|
||||
virtual bool openData(const char* format, int echoTaps);
|
||||
|
||||
private:
|
||||
WpSource* m_wp_s;
|
||||
WpConsumer* m_wp_c;
|
||||
};
|
||||
|
||||
class WpData : public Thread
|
||||
{
|
||||
public:
|
||||
WpData(WpSpan* span, const char* card, const char* device, Configuration& cfg, const String& sect);
|
||||
~WpData();
|
||||
virtual void run();
|
||||
private:
|
||||
bool decodeEvent(const api_rx_hdr_t* ev);
|
||||
WpSpan* m_span;
|
||||
HANDLE m_fd;
|
||||
unsigned char* m_buffer;
|
||||
WpChan **m_chans;
|
||||
int m_samples;
|
||||
bool m_swap;
|
||||
unsigned char m_rdError;
|
||||
unsigned char m_wrError;
|
||||
unsigned char m_oobError;
|
||||
};
|
||||
|
||||
class WpDriver : public PriDriver
|
||||
{
|
||||
friend class PriSpan;
|
||||
friend class WpHandler;
|
||||
public:
|
||||
WpDriver();
|
||||
virtual ~WpDriver();
|
||||
virtual void initialize();
|
||||
virtual PriSpan* createSpan(PriDriver* driver, int span, int first, int chans, Configuration& cfg, const String& sect);
|
||||
virtual PriChan* createChan(const PriSpan* span, int chan, unsigned int bufsize);
|
||||
};
|
||||
|
||||
INIT_PLUGIN(WpDriver);
|
||||
|
||||
#define WP_HEADER 16
|
||||
#define MAX_DATA_ERRORS 250
|
||||
|
||||
static int wp_recv(HANDLE fd, void *buf, int buflen, int flags = 0)
|
||||
{
|
||||
int r = ::recv(fd,buf,buflen,flags);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int wp_send(HANDLE fd, void *buf, int buflen, int flags = 0)
|
||||
{
|
||||
int w = ::send(fd,buf,buflen,flags);
|
||||
return w;
|
||||
}
|
||||
|
||||
static int wp_read(struct pri *pri, void *buf, int buflen)
|
||||
{
|
||||
buflen -= 2;
|
||||
int sz = buflen+WP_HEADER;
|
||||
char *tmp = (char*)::calloc(sz,1);
|
||||
XDebug("wp_read",DebugAll,"pre buf=%p len=%d tmp=%p sz=%d",buf,buflen,tmp,sz);
|
||||
int r = wp_recv((HANDLE)::pri_fd(pri),tmp,sz,MSG_NOSIGNAL);
|
||||
XDebug("wp_read",DebugAll,"post r=%d",r);
|
||||
if (r > 0) {
|
||||
r -= WP_HEADER;
|
||||
if ((r > 0) && (r <= buflen)) {
|
||||
WpSpan* span = (WpSpan*)::pri_get_userdata(pri);
|
||||
if (span)
|
||||
r -= span->overRead();
|
||||
DDebug("wp_read",DebugAll,"Transferring %d for %p",r,pri);
|
||||
::memcpy(buf,tmp+WP_HEADER,r);
|
||||
r += 2;
|
||||
}
|
||||
}
|
||||
::free(tmp);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int wp_write(struct pri *pri, void *buf, int buflen)
|
||||
{
|
||||
buflen -= 2;
|
||||
int sz = buflen+WP_HEADER;
|
||||
char *tmp = (char*)::calloc(sz,1);
|
||||
::memcpy(tmp+WP_HEADER,buf,buflen);
|
||||
XDebug("wp_write",DebugAll,"pre buf=%p len=%d tmp=%p sz=%d",buf,buflen,tmp,sz);
|
||||
int w = wp_send((HANDLE)::pri_fd(pri),tmp,sz,0);
|
||||
XDebug("wp_write",DebugAll,"post w=%d",w);
|
||||
if (w > 0) {
|
||||
w -= WP_HEADER;
|
||||
DDebug("wp_write",DebugAll,"Transferred %d for %p",w,pri);
|
||||
w += 2;
|
||||
}
|
||||
::free(tmp);
|
||||
return w;
|
||||
}
|
||||
|
||||
static bool wp_select(HANDLE fd,int samp,bool* errp = 0)
|
||||
{
|
||||
fd_set rdfds;
|
||||
fd_set errfds;
|
||||
FD_ZERO(&rdfds);
|
||||
FD_SET(fd,&rdfds);
|
||||
FD_ZERO(&errfds);
|
||||
FD_SET(fd,&errfds);
|
||||
struct timeval tv;
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = samp*125;
|
||||
int sel = ::select(fd+1, &rdfds, NULL, errp ? &errfds : NULL, &tv);
|
||||
if (sel < 0)
|
||||
Debug(DebugWarn,"Wanpipe select failed on %d: error %d: %s",
|
||||
fd,errno,::strerror(errno));
|
||||
if (errp)
|
||||
*errp = FD_ISSET(fd,&errfds);
|
||||
return FD_ISSET(fd,&rdfds);
|
||||
}
|
||||
|
||||
#ifdef HAVE_WANPIPE_HWEC
|
||||
|
||||
static bool wp_hwec_ioctl(wan_ec_api_t* ecapi)
|
||||
{
|
||||
if (!ecapi)
|
||||
return false;
|
||||
int fd = -1;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
fd = open(WANEC_DEV_DIR WANEC_DEV_NAME, O_RDONLY);
|
||||
if (fd >= 0)
|
||||
break;
|
||||
Thread::msleep(200);
|
||||
}
|
||||
if (fd < 0)
|
||||
return false;
|
||||
ecapi->err = WAN_EC_API_RC_OK;
|
||||
if (::ioctl(fd,ecapi->cmd,ecapi)) {
|
||||
// preserve errno while we close the handle
|
||||
int err = errno;
|
||||
::close(fd);
|
||||
errno = err;
|
||||
return false;
|
||||
}
|
||||
::close(fd);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Configure the DTMF detection of the DSP
|
||||
static bool wp_dtmf_config(const char* card, const char* device, bool enable, unsigned long chanmap)
|
||||
{
|
||||
wan_ec_api_t ecapi;
|
||||
::memset(&ecapi,0,sizeof(ecapi));
|
||||
::strncpy((char*)ecapi.devname,card,sizeof(ecapi.devname));
|
||||
//::strncpy((char*)ecapi.if_name,device,sizeof(ecapi.if_name));
|
||||
ecapi.channel_map = chanmap;
|
||||
if (enable) {
|
||||
ecapi.cmd = WAN_EC_CMD_DTMF_ENABLE;
|
||||
ecapi.verbose = WAN_EC_VERBOSE_EXTRA1;
|
||||
// event on start of tone, before echo canceller
|
||||
ecapi.u_dtmf_config.type = WAN_EC_TONE_PRESENT;
|
||||
ecapi.u_dtmf_config.port = WAN_EC_CHANNEL_PORT_SOUT;
|
||||
}
|
||||
else
|
||||
ecapi.cmd = WAN_EC_CMD_DTMF_DISABLE;
|
||||
return wp_hwec_ioctl(&ecapi);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static bool wp_dtmf_config(const char* card, const char* device, bool enable, unsigned long chanmap)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif // HAVE_WANPIPE_HWEC
|
||||
|
||||
// Enable/disable DTMF events
|
||||
static bool wp_dtmfs(HANDLE fd, bool detect)
|
||||
{
|
||||
#ifdef HAVE_WANPIPE_HWEC
|
||||
api_tx_hdr_t api_tx_hdr;
|
||||
::memset(&api_tx_hdr,0,sizeof(api_tx_hdr_t));
|
||||
api_tx_hdr.u.event.type = WP_API_EVENT_DTMF;
|
||||
api_tx_hdr.u.event.mode = detect ? WP_API_EVENT_ENABLE : WP_API_EVENT_DISABLE;
|
||||
return (::ioctl(fd,SIOC_WANPIPE_API,&api_tx_hdr) >= 0);
|
||||
#else
|
||||
// pretend enabling fails, disabling succeeds
|
||||
if (!detect)
|
||||
return true;
|
||||
errno = ENOSYS;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void wp_close(HANDLE fd)
|
||||
{
|
||||
if (fd == INVALID_HANDLE_VALUE)
|
||||
return;
|
||||
::close(fd);
|
||||
}
|
||||
|
||||
static HANDLE wp_open(const char* card, const char* device)
|
||||
{
|
||||
DDebug(DebugAll,"wp_open('%s','%s')",card,device);
|
||||
if (null(card) || null(device))
|
||||
return INVALID_HANDLE_VALUE;
|
||||
HANDLE fd = ::socket(AF_WANPIPE, SOCK_RAW, 0);
|
||||
if (fd == INVALID_HANDLE_VALUE) {
|
||||
Debug(DebugGoOn,"Wanpipe failed to create socket: error %d: %s",
|
||||
errno,::strerror(errno));
|
||||
return fd;
|
||||
}
|
||||
// Bind to the card/interface
|
||||
struct wan_sockaddr_ll sa;
|
||||
memset(&sa,0,sizeof(struct wan_sockaddr_ll));
|
||||
::strncpy((char*)sa.sll_device,device,sizeof(sa.sll_device));
|
||||
::strncpy((char*)sa.sll_card,card,sizeof(sa.sll_card));
|
||||
sa.sll_protocol = htons(PVC_PROT);
|
||||
sa.sll_family=AF_WANPIPE;
|
||||
if (::bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
|
||||
Debug(DebugGoOn,"Wanpipe failed to bind %d: error %d: %s",
|
||||
fd,errno,::strerror(errno));
|
||||
wp_close(fd);
|
||||
fd = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
static struct pri* wp_create(const char* card, const char* device, int nettype, int swtype)
|
||||
{
|
||||
DDebug(DebugAll,"wp_create('%s','%s',%d,%d)",card,device,nettype,swtype);
|
||||
HANDLE fd = wp_open(card,device);
|
||||
if (fd == INVALID_HANDLE_VALUE)
|
||||
return 0;
|
||||
struct pri* p = ::pri_new_cb((int)fd, nettype, swtype, wp_read, wp_write, 0);
|
||||
if (!p)
|
||||
wp_close(fd);
|
||||
return p;
|
||||
}
|
||||
|
||||
WpSpan::WpSpan(struct pri *_pri, PriDriver* driver, int span, int first, int chans, int dchan, Configuration& cfg, const String& sect, HANDLE fd)
|
||||
: PriSpan(_pri,driver,span,first,chans,dchan,cfg,sect), Thread("WpSpan"),
|
||||
m_fd(fd), m_data(0), m_overRead(0)
|
||||
{
|
||||
Debug(&__plugin,DebugAll,"WpSpan::WpSpan() [%p]",this);
|
||||
m_overRead = cfg.getIntValue(sect,"overread",cfg.getIntValue("general","overread",0));
|
||||
}
|
||||
|
||||
WpSpan::~WpSpan()
|
||||
{
|
||||
Debug(&__plugin,DebugAll,"WpSpan::~WpSpan() [%p]",this);
|
||||
m_ok = false;
|
||||
delete m_data;
|
||||
wp_close(m_fd);
|
||||
m_fd = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
void WpSpan::run()
|
||||
{
|
||||
Debug(&__plugin,DebugAll,"WpSpan::run() [%p]",this);
|
||||
for (;;) {
|
||||
bool rd = wp_select(m_fd,5); // 5 bytes per smallest q921 frame
|
||||
Thread::check();
|
||||
runEvent(!rd);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long WpSpan::bChanMap() const
|
||||
{
|
||||
unsigned long res = 0;
|
||||
for (int i = 1; i <= 31; i++)
|
||||
if (validChan(i))
|
||||
res |= ((unsigned long)1 << i);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
WpSource::WpSource(WpChan *owner, const char* format, unsigned int bufsize)
|
||||
: PriSource(owner,format,bufsize),
|
||||
m_bufpos(0)
|
||||
{
|
||||
Debug(m_owner,DebugAll,"WpSource::WpSource(%p) [%p]",owner,this);
|
||||
static_cast<WpChan*>(m_owner)->m_wp_s = this;
|
||||
}
|
||||
|
||||
WpSource::~WpSource()
|
||||
{
|
||||
Debug(m_owner,DebugAll,"WpSource::~WpSource() [%p]",this);
|
||||
static_cast<WpChan*>(m_owner)->m_wp_s = 0;
|
||||
}
|
||||
|
||||
void WpSource::put(unsigned char val)
|
||||
{
|
||||
((char*)m_buffer.data())[m_bufpos] = val;
|
||||
if (++m_bufpos >= m_buffer.length()) {
|
||||
m_bufpos = 0;
|
||||
Forward(m_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
WpConsumer::WpConsumer(WpChan *owner, const char* format, unsigned int bufsize)
|
||||
: PriConsumer(owner,format,bufsize), Fifo(2*bufsize)
|
||||
{
|
||||
Debug(m_owner,DebugAll,"WpConsumer::WpConsumer(%p) [%p]",owner,this);
|
||||
static_cast<WpChan*>(m_owner)->m_wp_c = this;
|
||||
}
|
||||
|
||||
WpConsumer::~WpConsumer()
|
||||
{
|
||||
Debug(m_owner,DebugAll,"WpConsumer::~WpConsumer() [%p]",this);
|
||||
static_cast<WpChan*>(m_owner)->m_wp_c = 0;
|
||||
if (m_overruns.events())
|
||||
Debug(m_owner,DebugMild,"Consumer had %u overruns (%lu bytes)",
|
||||
m_overruns.events(),m_overruns.bytes());
|
||||
}
|
||||
|
||||
void WpConsumer::Consume(const DataBlock &data, unsigned long tStamp)
|
||||
{
|
||||
unsigned int err = put((const unsigned char*)data.data(),data.length());
|
||||
if (err)
|
||||
m_overruns.update(err);
|
||||
}
|
||||
|
||||
|
||||
static Thread::Priority cfgPriority(Configuration& cfg, const String& sect)
|
||||
{
|
||||
String tmp(cfg.getValue(sect,"thread"));
|
||||
if (tmp.null())
|
||||
tmp = cfg.getValue("general","thread");
|
||||
return Thread::priority(tmp);
|
||||
}
|
||||
|
||||
|
||||
WpData::WpData(WpSpan* span, const char* card, const char* device, Configuration& cfg, const String& sect)
|
||||
: Thread("WpData",cfgPriority(cfg,sect)), m_span(span), m_fd(INVALID_HANDLE_VALUE),
|
||||
m_buffer(0), m_chans(0), m_samples(50), m_swap(true),
|
||||
m_rdError(0), m_wrError(0), m_oobError(0)
|
||||
{
|
||||
Debug(&__plugin,DebugAll,"WpData::WpData(%p,'%s','%s') [%p]",
|
||||
span,card,device,this);
|
||||
HANDLE fd = wp_open(card,device);
|
||||
if (fd != INVALID_HANDLE_VALUE) {
|
||||
m_fd = fd;
|
||||
m_span->m_data = this;
|
||||
bool detect = m_span->detect();
|
||||
if (!(wp_dtmf_config(card,device,detect,m_span->bChanMap()) && wp_dtmfs(fd,detect))) {
|
||||
int err = errno;
|
||||
Debug(&__plugin,detect ? DebugWarn : DebugMild,
|
||||
"Failed to %s DTMF detection on span %d: %s (%d)",
|
||||
detect ? "enable" : "disable",m_span->span(),strerror(err),err);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_span->chans() == 24)
|
||||
// for T1 we typically have 23 B channels so we adjust the number
|
||||
// of samples to get multiple of 32 bit and also reduce overhead
|
||||
m_samples = 64;
|
||||
|
||||
m_samples = cfg.getIntValue("general","samples",m_samples);
|
||||
m_samples = cfg.getIntValue(sect,"samples",m_samples);
|
||||
m_swap = cfg.getBoolValue("general","bitswap",m_swap);
|
||||
m_swap = cfg.getBoolValue(sect,"bitswap",m_swap);
|
||||
}
|
||||
|
||||
WpData::~WpData()
|
||||
{
|
||||
Debug(&__plugin,DebugAll,"WpData::~WpData() [%p]",this);
|
||||
m_span->m_data = 0;
|
||||
wp_close(m_fd);
|
||||
m_fd = INVALID_HANDLE_VALUE;
|
||||
if (m_buffer)
|
||||
::free(m_buffer);
|
||||
if (m_chans)
|
||||
delete[] m_chans;
|
||||
}
|
||||
|
||||
void WpData::run()
|
||||
{
|
||||
Debug(&__plugin,DebugAll,"WpData::run() [%p]",this);
|
||||
int bchans = m_span->bchans();
|
||||
int buflen = m_samples*bchans;
|
||||
int sz = buflen+WP_HEADER;
|
||||
m_buffer = (unsigned char*)::malloc(sz);
|
||||
// Build a compacted list of allocated B channels
|
||||
m_chans = new WpChan* [bchans];
|
||||
int b = 0;
|
||||
for (int n = 0; n < bchans; n++) {
|
||||
while (!m_span->m_chans[b])
|
||||
b++;
|
||||
m_chans[n] = static_cast<WpChan*>(m_span->m_chans[b++]);
|
||||
DDebug(&__plugin,DebugInfo,"wpdata ch[%d]=%d (%p)",n,m_chans[n]->chan(),m_chans[n]);
|
||||
}
|
||||
while (m_span && (m_fd >= 0)) {
|
||||
Thread::check();
|
||||
api_rx_hdr_t* ev = (api_rx_hdr_t*)m_buffer;
|
||||
bool oob = false;
|
||||
bool rd = wp_select(m_fd,m_samples,&oob);
|
||||
if (oob) {
|
||||
XDebug("wpdata_recv_oob",DebugAll,"pre buf=%p len=%d sz=%d",m_buffer,buflen,sz);
|
||||
int r = wp_recv(m_fd,m_buffer,sz,MSG_OOB);
|
||||
XDebug("wpdata_recv_oob",DebugAll,"post r=%d",r);
|
||||
if (r < 0) {
|
||||
if (!m_oobError) {
|
||||
#ifdef SIOC_WANPIPE_SOCK_STATE
|
||||
r = ::ioctl(m_fd,SIOC_WANPIPE_SOCK_STATE,0);
|
||||
Debug(&__plugin,DebugNote,"Socket for span %d is %s",
|
||||
m_span->span(),
|
||||
(r == 0) ? "connected" : ((r == 1) ? "disconnected" : "connecting"));
|
||||
#else
|
||||
Debug(&__plugin,DebugNote,"Failed to read OOB on span %d",
|
||||
m_span->span());
|
||||
#endif
|
||||
}
|
||||
if (m_oobError < MAX_DATA_ERRORS)
|
||||
m_oobError++;
|
||||
}
|
||||
else {
|
||||
m_oobError = 0;
|
||||
if (r >= WP_HEADER)
|
||||
decodeEvent(ev);
|
||||
}
|
||||
}
|
||||
|
||||
if (rd) {
|
||||
ev->error_flag = 0;
|
||||
XDebug("wpdata_recv",DebugAll,"pre buf=%p len=%d sz=%d",m_buffer,buflen,sz);
|
||||
int r = wp_recv(m_fd,m_buffer,sz,0/*MSG_NOSIGNAL*/);
|
||||
XDebug("wpdata_recv",DebugAll,"post r=%d",r);
|
||||
r -= WP_HEADER;
|
||||
if (ev->error_flag) {
|
||||
if (!m_rdError)
|
||||
Debug(&__plugin,DebugWarn,"Read data error 0x%02X on span %d [%p]",
|
||||
ev->error_flag,m_span->span(),this);
|
||||
if (m_rdError < MAX_DATA_ERRORS)
|
||||
m_rdError++;
|
||||
}
|
||||
else
|
||||
m_rdError = 0;
|
||||
|
||||
// check if a DTMF was received with the data
|
||||
if (r >= 0)
|
||||
decodeEvent(ev);
|
||||
|
||||
// We should have read N bytes for each B channel
|
||||
if ((r > 0) && ((r % bchans) == 0)) {
|
||||
r /= bchans;
|
||||
const unsigned char* dat = m_buffer + WP_HEADER;
|
||||
m_span->lock();
|
||||
for (int n = r; n > 0; n--)
|
||||
for (b = 0; b < bchans; b++) {
|
||||
WpSource *s = m_chans[b]->m_wp_s;
|
||||
if (s)
|
||||
s->put(m_swap ? PriDriver::bitswap(*dat) : *dat);
|
||||
dat++;
|
||||
}
|
||||
m_span->unlock();
|
||||
}
|
||||
int wr = m_samples;
|
||||
::memset(m_buffer,0,WP_HEADER);
|
||||
unsigned char* dat = m_buffer + WP_HEADER;
|
||||
m_span->lock();
|
||||
for (int n = wr; n > 0; n--) {
|
||||
for (b = 0; b < bchans; b++) {
|
||||
WpConsumer *c = m_chans[b]->m_wp_c;
|
||||
unsigned char d = c ? c->get() : 0xff;
|
||||
*dat++ = m_swap ? PriDriver::bitswap(d) : d;
|
||||
}
|
||||
}
|
||||
m_span->unlock();
|
||||
wr = (wr * bchans) + WP_HEADER;
|
||||
XDebug("wpdata_send",DebugAll,"pre buf=%p len=%d sz=%d",m_buffer,wr,sz);
|
||||
int w = wp_send(m_fd,m_buffer,wr,MSG_DONTWAIT);
|
||||
XDebug("wpdata_send",DebugAll,"post w=%d",w);
|
||||
if (w != wr) {
|
||||
if (!m_wrError)
|
||||
Debug(&__plugin,DebugWarn,"Wrote %d data bytes instead of %d on span %d [%p]",
|
||||
w,wr,m_span->span(),this);
|
||||
if (m_wrError < MAX_DATA_ERRORS)
|
||||
m_wrError++;
|
||||
}
|
||||
else
|
||||
m_wrError = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool WpData::decodeEvent(const api_rx_hdr_t* ev)
|
||||
{
|
||||
#ifdef WAN_EC_TONE_PRESENT
|
||||
switch (ev->event_type) {
|
||||
case WP_API_EVENT_NONE:
|
||||
return false;
|
||||
case WP_API_EVENT_DTMF:
|
||||
if (ev->hdr_u.wp_api_event.u_event.dtmf.type == WAN_EC_TONE_PRESENT) {
|
||||
String tone((char)ev->hdr_u.wp_api_event.u_event.dtmf.digit);
|
||||
tone.toUpper();
|
||||
int chan = ev->hdr_u.wp_api_event.channel;
|
||||
PriChan* c = m_span->getChan(chan);
|
||||
if (c)
|
||||
c->gotDigits(tone);
|
||||
else
|
||||
Debug(&__plugin,DebugMild,"Detected DTMF '%s' for invalid channel %d on span %d",
|
||||
tone.c_str(),chan,m_span->span());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Debug(&__plugin,DebugMild,"Unhandled event %u on span %d",
|
||||
ev->event_type,m_span->span());
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
WpChan::WpChan(const PriSpan *parent, int chan, unsigned int bufsize)
|
||||
: PriChan(parent,chan,bufsize), m_wp_s(0), m_wp_c(0)
|
||||
{
|
||||
}
|
||||
|
||||
WpChan::~WpChan()
|
||||
{
|
||||
closeData();
|
||||
}
|
||||
|
||||
bool WpChan::openData(const char* format, int echoTaps)
|
||||
{
|
||||
if (echoTaps)
|
||||
Debug(DebugWarn,"Echo cancellation requested but not available in wanpipe");
|
||||
m_span->lock();
|
||||
setSource(new WpSource(this,format,m_bufsize));
|
||||
getSource()->deref();
|
||||
setConsumer(new WpConsumer(this,format,m_bufsize));
|
||||
getConsumer()->deref();
|
||||
m_span->unlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
PriSpan* WpDriver::createSpan(PriDriver* driver, int span, int first, int chans, Configuration& cfg, const String& sect)
|
||||
{
|
||||
Debug(this,DebugAll,"WpDriver::createSpan(%p,%d,%d,%d) [%p]",driver,span,first,chans,this);
|
||||
int netType = -1;
|
||||
int swType = -1;
|
||||
int dchan = -1;
|
||||
netParams(cfg,sect,chans,&netType,&swType,&dchan);
|
||||
String card;
|
||||
card << "wanpipe" << span;
|
||||
card = cfg.getValue(sect,"card",card);
|
||||
String dev;
|
||||
dev << "w" << span << "g1";
|
||||
pri* p = wp_create(card,cfg.getValue(sect,"dgroup",dev),netType,swType);
|
||||
if (!p)
|
||||
return 0;
|
||||
WpSpan *ps = new WpSpan(p,driver,span,first,chans,dchan,cfg,sect,(HANDLE)::pri_fd(p));
|
||||
ps->startup();
|
||||
dev.clear();
|
||||
dev << "w" << span << "g2";
|
||||
WpData* dat = new WpData(ps,card,cfg.getValue(sect,"bgroup",dev),cfg,sect);
|
||||
dat->startup();
|
||||
return ps;
|
||||
}
|
||||
|
||||
PriChan* WpDriver::createChan(const PriSpan* span, int chan, unsigned int bufsize)
|
||||
{
|
||||
Debug(this,DebugAll,"WpDriver::createChan(%p,%d,%u) [%p]",span,chan,bufsize,this);
|
||||
return new WpChan(span,chan,bufsize);
|
||||
}
|
||||
|
||||
WpDriver::WpDriver()
|
||||
: PriDriver("wp")
|
||||
{
|
||||
Output("Loaded module Wanpipe");
|
||||
}
|
||||
|
||||
WpDriver::~WpDriver()
|
||||
{
|
||||
Output("Unloading module Wanpipe");
|
||||
}
|
||||
|
||||
void WpDriver::initialize()
|
||||
{
|
||||
Output("Initializing module Wanpipe");
|
||||
init("wpchan");
|
||||
}
|
||||
|
||||
}; // anonymous namespace
|
||||
|
||||
#endif /* _WINDOWS */
|
||||
|
||||
/* vi: set ts=8 sw=4 sts=4 noet: */
|
|
@ -1,646 +0,0 @@
|
|||
/**
|
||||
* wpchanw.cpp
|
||||
* This file is part of the YATE Project http://YATE.null.ro
|
||||
*
|
||||
* Wanpipe PRI cards telephony driver for Windows
|
||||
*
|
||||
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
||||
* Copyright (C) 2004-2006 Null Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <modules/libypri.h>
|
||||
|
||||
#ifndef _WINDOWS
|
||||
#error This module is only for Windows
|
||||
#else
|
||||
|
||||
extern "C" {
|
||||
|
||||
#define MSG_NOSIGNAL 0
|
||||
#define MSG_DONTWAIT 0
|
||||
#include <winioctl.h>
|
||||
#define IOCTL_WRITE 1
|
||||
#define IOCTL_READ 2
|
||||
#define IOCTL_MGMT 3
|
||||
#define IoctlWriteCommand \
|
||||
CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_WRITE, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)
|
||||
#define IoctlReadCommand \
|
||||
CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_READ, METHOD_IN_DIRECT, FILE_ANY_ACCESS)
|
||||
|
||||
};
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
using namespace TelEngine;
|
||||
namespace { // anonymous
|
||||
|
||||
class WpChan;
|
||||
class WpData;
|
||||
class WpReader;
|
||||
class WpWriter;
|
||||
class WpDriver;
|
||||
|
||||
class WpSpan : public PriSpan, public Thread
|
||||
{
|
||||
friend class WpData;
|
||||
friend class WpReader;
|
||||
friend class WpWriter;
|
||||
friend class WpDriver;
|
||||
public:
|
||||
virtual ~WpSpan();
|
||||
virtual void run();
|
||||
virtual void cleanup();
|
||||
int dataRead(void *buf, int buflen);
|
||||
int dataWrite(void *buf, int buflen);
|
||||
|
||||
private:
|
||||
WpSpan(struct pri *_pri, PriDriver* driver, int span, int first, int chans, int dchan, Configuration& cfg, const String& sect);
|
||||
WpData* m_data;
|
||||
WpReader* m_reader;
|
||||
WpWriter* m_writer;
|
||||
DataBlock m_rdata;
|
||||
ObjList m_wdata;
|
||||
};
|
||||
|
||||
class WpSource : public PriSource
|
||||
{
|
||||
public:
|
||||
WpSource(WpChan *owner, const char* format, unsigned int bufsize);
|
||||
~WpSource();
|
||||
void put(unsigned char val);
|
||||
|
||||
private:
|
||||
unsigned int m_bufpos;
|
||||
};
|
||||
|
||||
class WpConsumer : public PriConsumer, public Fifo
|
||||
{
|
||||
public:
|
||||
WpConsumer(WpChan *owner, const char* format, unsigned int bufsize);
|
||||
~WpConsumer();
|
||||
|
||||
virtual void Consume(const DataBlock &data, unsigned long tStamp);
|
||||
private:
|
||||
DataErrors m_overruns;
|
||||
};
|
||||
|
||||
class WpChan : public PriChan
|
||||
{
|
||||
friend class WpSource;
|
||||
friend class WpConsumer;
|
||||
friend class WpData;
|
||||
public:
|
||||
WpChan(const PriSpan *parent, int chan, unsigned int bufsize);
|
||||
virtual ~WpChan();
|
||||
virtual bool openData(const char* format, int echoTaps);
|
||||
|
||||
private:
|
||||
WpSource* m_wp_s;
|
||||
WpConsumer* m_wp_c;
|
||||
};
|
||||
|
||||
class WpData : public Thread
|
||||
{
|
||||
public:
|
||||
WpData(WpSpan* span, const char* card, const char* device, Configuration& cfg, const String& sect);
|
||||
~WpData();
|
||||
virtual void run();
|
||||
private:
|
||||
WpSpan* m_span;
|
||||
HANDLE m_fd;
|
||||
WpChan **m_chans;
|
||||
bool m_swap;
|
||||
};
|
||||
|
||||
class WpReader : public Thread
|
||||
{
|
||||
public:
|
||||
WpReader(WpSpan* span, const char* card, const char* device);
|
||||
~WpReader();
|
||||
virtual void run();
|
||||
private:
|
||||
WpSpan* m_span;
|
||||
HANDLE m_fd;
|
||||
};
|
||||
|
||||
class WpWriter : public Thread
|
||||
{
|
||||
public:
|
||||
WpWriter(WpSpan* span, const char* card, const char* device);
|
||||
~WpWriter();
|
||||
virtual void run();
|
||||
private:
|
||||
WpSpan* m_span;
|
||||
HANDLE m_fd;
|
||||
};
|
||||
|
||||
class WpDriver : public PriDriver
|
||||
{
|
||||
friend class PriSpan;
|
||||
friend class WpHandler;
|
||||
public:
|
||||
WpDriver();
|
||||
virtual ~WpDriver();
|
||||
virtual void initialize();
|
||||
virtual bool received(Message &msg, int id);
|
||||
virtual PriSpan* createSpan(PriDriver* driver, int span, int first, int chans, Configuration& cfg, const String& sect);
|
||||
virtual PriChan* createChan(const PriSpan* span, int chan, unsigned int bufsize);
|
||||
};
|
||||
|
||||
INIT_PLUGIN(WpDriver);
|
||||
|
||||
#define WP_HEADER 21
|
||||
#define WP_BUFFER 8188 // maximum length of data = 8K - 4
|
||||
|
||||
|
||||
static Thread::Priority cfgPriority(Configuration& cfg, const String& sect)
|
||||
{
|
||||
String tmp(cfg.getValue(sect,"thread"));
|
||||
if (tmp.null())
|
||||
tmp = cfg.getValue("general","thread");
|
||||
return Thread::priority(tmp);
|
||||
}
|
||||
|
||||
static void dump_buffer(const void* buf, int len)
|
||||
{
|
||||
String s;
|
||||
const unsigned char* p = (const unsigned char*)buf;
|
||||
for (int i=0; i < len; i++) {
|
||||
char tmp[4];
|
||||
sprintf(tmp," %02x",p[i]);
|
||||
//Debug(DebugAll,"%d",i);
|
||||
s += tmp;
|
||||
}
|
||||
Output("[%d@%p]%s",len,buf,s.c_str());
|
||||
}
|
||||
|
||||
static int wp_recv(HANDLE fd, void *buf, int buflen, int flags = 0)
|
||||
{
|
||||
int r = 0;
|
||||
if (!DeviceIoControl(fd,IoctlReadCommand,0,0,buf,buflen,(LPDWORD)&r,0)) {
|
||||
r = 0;
|
||||
Output("recv (%d,%p,%d) last err=%x",fd,buf,buflen,GetLastError());
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static int wp_send(HANDLE fd, void *buf, int buflen, int flags = 0)
|
||||
{
|
||||
int w = 0;
|
||||
if (!DeviceIoControl(fd,IoctlWriteCommand,buf,buflen,buf,buflen,(LPDWORD)&w,0)) {
|
||||
w = 0;
|
||||
Output("send (%d,%p,%d) last err=%x",fd,buf,buflen,GetLastError());
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
static int wp_read(struct pri *pri, void *buf, int buflen)
|
||||
{
|
||||
WpSpan* span = (WpSpan*)::pri_get_userdata(pri);
|
||||
return span ? span->dataRead(buf,buflen) : 0;
|
||||
}
|
||||
|
||||
int WpSpan::dataRead(void *buf, int buflen)
|
||||
{
|
||||
Lock mylock(this);
|
||||
if (m_rdata.data() && buf && (int)m_rdata.length() <= buflen) {
|
||||
buflen = m_rdata.length();
|
||||
::memcpy(buf,m_rdata.data(),buflen);
|
||||
m_rdata.clear();
|
||||
DDebug(&__plugin,DebugAll,"WpSpan dequeued %d bytes block [%p]",buflen,this);
|
||||
return buflen+2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wp_write(struct pri *pri, void *buf, int buflen)
|
||||
{
|
||||
WpSpan* span = (WpSpan*)::pri_get_userdata(pri);
|
||||
return span ? span->dataWrite(buf,buflen) : 0;
|
||||
}
|
||||
|
||||
int WpSpan::dataWrite(void *buf, int buflen)
|
||||
{
|
||||
Lock mylock(this);
|
||||
if (buf && (buflen > 2) && (m_wdata.length() < 5)) {
|
||||
buflen -= 2;
|
||||
DataBlock* block = new DataBlock(buf,buflen);
|
||||
m_wdata.append(block);
|
||||
DDebug(&__plugin,DebugAll,"WpSpan queued %d bytes block, total blocks %d [%p]",block->length(),m_wdata.count(),this);
|
||||
return buflen+2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void wp_close(HANDLE fd)
|
||||
{
|
||||
if (fd == INVALID_HANDLE_VALUE)
|
||||
return;
|
||||
::CloseHandle(fd);
|
||||
}
|
||||
|
||||
static HANDLE wp_open(const char* card, const char* device)
|
||||
{
|
||||
DDebug(DebugAll,"wp_open('%s','%s')",card,device);
|
||||
if (null(card) || null(device))
|
||||
return INVALID_HANDLE_VALUE;
|
||||
String devname("\\\\.\\");
|
||||
devname << card << "_" << device;
|
||||
HANDLE fd = ::CreateFile(
|
||||
devname,
|
||||
GENERIC_READ|GENERIC_WRITE,
|
||||
FILE_SHARE_READ|FILE_SHARE_WRITE,
|
||||
0,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_NO_BUFFERING|FILE_FLAG_WRITE_THROUGH,
|
||||
0);
|
||||
if (fd == INVALID_HANDLE_VALUE) {
|
||||
Debug(DebugGoOn,"Wanpipe failed to open device '%s': error %d: %s",
|
||||
devname.c_str(),errno,::strerror(errno));
|
||||
return fd;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
WpReader::WpReader(WpSpan* span, const char* card, const char* device)
|
||||
: Thread("WpReader"), m_span(span), m_fd(INVALID_HANDLE_VALUE)
|
||||
{
|
||||
DDebug(&__plugin,DebugAll,"WpReader::WpReader(%p) [%p]",span,this);
|
||||
m_fd = wp_open(card,device);
|
||||
m_span->m_reader = this;
|
||||
}
|
||||
|
||||
WpReader::~WpReader()
|
||||
{
|
||||
DDebug(&__plugin,DebugAll,"WpReader::~WpReader() [%p]",this);
|
||||
if (m_span)
|
||||
m_span->m_reader = 0;
|
||||
HANDLE tmp = m_fd;
|
||||
m_fd = INVALID_HANDLE_VALUE;
|
||||
wp_close(tmp);
|
||||
}
|
||||
|
||||
void WpReader::run()
|
||||
{
|
||||
while (m_span && m_span->m_reader && (m_fd != INVALID_HANDLE_VALUE)) {
|
||||
Thread::msleep(1,true);
|
||||
Lock mylock(m_span);
|
||||
if (m_span->m_rdata.data())
|
||||
continue;
|
||||
mylock.drop();
|
||||
unsigned char buf[WP_HEADER+WP_BUFFER];
|
||||
int r = wp_recv(m_fd,buf,sizeof(buf)) - WP_HEADER;
|
||||
XDebug(&__plugin,DebugAll,"WpReader read returned %d [%p]",r,this);
|
||||
if (r <= 0)
|
||||
continue;
|
||||
Thread::check();
|
||||
m_span->lock();
|
||||
m_span->m_rdata.assign(buf+WP_HEADER,r);
|
||||
DDebug(&__plugin,DebugAll,"WpReader queued %d bytes block [%p]",r,this);
|
||||
m_span->unlock();
|
||||
}
|
||||
}
|
||||
|
||||
WpWriter::WpWriter(WpSpan* span, const char* card, const char* device)
|
||||
: Thread("WpWriter"), m_span(span), m_fd(INVALID_HANDLE_VALUE)
|
||||
{
|
||||
DDebug(&__plugin,DebugAll,"WpWriter::WpWriter(%p) [%p]",span,this);
|
||||
m_fd = wp_open(card,device);
|
||||
m_span->m_writer = this;
|
||||
}
|
||||
|
||||
WpWriter::~WpWriter()
|
||||
{
|
||||
DDebug(&__plugin,DebugAll,"WpWriter::~WpWriter() [%p]",this);
|
||||
if (m_span)
|
||||
m_span->m_writer = 0;
|
||||
HANDLE tmp = m_fd;
|
||||
m_fd = INVALID_HANDLE_VALUE;
|
||||
wp_close(tmp);
|
||||
}
|
||||
|
||||
void WpWriter::run()
|
||||
{
|
||||
while (m_span && m_span->m_writer && (m_fd != INVALID_HANDLE_VALUE)) {
|
||||
Thread::msleep(1,true);
|
||||
m_span->lock();
|
||||
DataBlock *block = static_cast<DataBlock*>(m_span->m_wdata.remove(false));
|
||||
m_span->unlock();
|
||||
if (!block)
|
||||
continue;
|
||||
DDebug(&__plugin,DebugAll,"WpWriter dequeued %d bytes block [%p]",block->length(),this);
|
||||
// this is really stupid - have to send a huge buffer, or else
|
||||
// Error : Tx system buffer length not equal sizeof(TX_DATA_STRUCT)!
|
||||
unsigned char buf[WP_HEADER+WP_BUFFER];
|
||||
int len = block->length();
|
||||
::memcpy(buf+WP_HEADER,block->data(),len);
|
||||
block->destruct();
|
||||
buf[0] = 11;
|
||||
buf[1] = len & 0xff;
|
||||
buf[2] = len >> 8;
|
||||
wp_send(m_fd,buf,sizeof(buf));
|
||||
}
|
||||
}
|
||||
|
||||
WpSpan::WpSpan(struct pri *_pri, PriDriver* driver, int span, int first, int chans, int dchan, Configuration& cfg, const String& sect)
|
||||
: PriSpan(_pri,driver,span,first,chans,dchan,cfg,sect), Thread("WpSpan"),
|
||||
m_data(0), m_reader(0), m_writer(0)
|
||||
{
|
||||
Debug(&__plugin,DebugAll,"WpSpan::WpSpan() [%p]",this);
|
||||
}
|
||||
|
||||
WpSpan::~WpSpan()
|
||||
{
|
||||
Debug(&__plugin,DebugAll,"WpSpan::~WpSpan() [%p]",this);
|
||||
m_ok = false;
|
||||
}
|
||||
|
||||
void WpSpan::cleanup()
|
||||
{
|
||||
Debug(&__plugin,DebugAll,"WpSpan::cleanup() [%p]",this);
|
||||
m_ok = false;
|
||||
if (m_data)
|
||||
m_data->cancel();
|
||||
if (m_reader)
|
||||
m_reader->cancel();
|
||||
if (m_writer)
|
||||
m_writer->cancel();
|
||||
Debug(&__plugin,DebugAll,"WpSpan waiting for cleanups [%p]",this);
|
||||
Thread::msleep(20);
|
||||
while (m_data || m_reader || m_writer)
|
||||
Thread::msleep(1);
|
||||
Debug(&__plugin,DebugAll,"WpSpan cleanups complete [%p]",this);
|
||||
}
|
||||
|
||||
void WpSpan::run()
|
||||
{
|
||||
Debug(&__plugin,DebugAll,"WpSpan::run() [%p]",this);
|
||||
while (m_data && m_reader && m_writer) {
|
||||
Thread::msleep(1,true);
|
||||
lock();
|
||||
runEvent(m_rdata.null());
|
||||
unlock();
|
||||
}
|
||||
}
|
||||
|
||||
WpSource::WpSource(WpChan *owner, const char* format, unsigned int bufsize)
|
||||
: PriSource(owner,format,bufsize),
|
||||
m_bufpos(0)
|
||||
{
|
||||
Debug(m_owner,DebugAll,"WpSource::WpSource(%p) [%p]",owner,this);
|
||||
static_cast<WpChan*>(m_owner)->m_wp_s = this;
|
||||
}
|
||||
|
||||
WpSource::~WpSource()
|
||||
{
|
||||
Debug(m_owner,DebugAll,"WpSource::~WpSource() [%p]",this);
|
||||
static_cast<WpChan*>(m_owner)->m_wp_s = 0;
|
||||
}
|
||||
|
||||
void WpSource::put(unsigned char val)
|
||||
{
|
||||
((char*)m_buffer.data())[m_bufpos] = val;
|
||||
if (++m_bufpos >= m_buffer.length()) {
|
||||
m_bufpos = 0;
|
||||
Forward(m_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
WpConsumer::WpConsumer(WpChan *owner, const char* format, unsigned int bufsize)
|
||||
: PriConsumer(owner,format,bufsize), Fifo(2*bufsize)
|
||||
{
|
||||
Debug(m_owner,DebugAll,"WpConsumer::WpConsumer(%p) [%p]",owner,this);
|
||||
static_cast<WpChan*>(m_owner)->m_wp_c = this;
|
||||
}
|
||||
|
||||
WpConsumer::~WpConsumer()
|
||||
{
|
||||
Debug(m_owner,DebugAll,"WpConsumer::~WpConsumer() [%p]",this);
|
||||
static_cast<WpChan*>(m_owner)->m_wp_c = 0;
|
||||
if (m_overruns.events())
|
||||
Debug(m_owner,DebugMild,"Consumer had %u overruns (%lu bytes)",
|
||||
m_overruns.events(),m_overruns.bytes());
|
||||
}
|
||||
|
||||
void WpConsumer::Consume(const DataBlock &data, unsigned long tStamp)
|
||||
{
|
||||
unsigned int err = put((const unsigned char*)data.data(),data.length());
|
||||
if (err)
|
||||
m_overruns.update(err);
|
||||
}
|
||||
|
||||
|
||||
|
||||
WpData::WpData(WpSpan* span, const char* card, const char* device, Configuration& cfg, const String& sect)
|
||||
: Thread("WpData",cfgPriority(cfg,sect)), m_span(span), m_fd(INVALID_HANDLE_VALUE), m_chans(0), m_swap(true)
|
||||
{
|
||||
DDebug(&__plugin,DebugAll,"WpData::WpData(%p,'%s','%s') [%p]",span,card,device,this);
|
||||
HANDLE fd = wp_open(card,device);
|
||||
if (fd != INVALID_HANDLE_VALUE) {
|
||||
m_fd = fd;
|
||||
m_span->m_data = this;
|
||||
}
|
||||
m_swap = cfg.getBoolValue("general","bitswap",m_swap);
|
||||
m_swap = cfg.getBoolValue(sect,"bitswap",m_swap);
|
||||
}
|
||||
|
||||
WpData::~WpData()
|
||||
{
|
||||
DDebug(&__plugin,DebugAll,"WpData::~WpData() [%p]",this);
|
||||
if (m_span)
|
||||
m_span->m_data = 0;
|
||||
wp_close(m_fd);
|
||||
m_fd = INVALID_HANDLE_VALUE;
|
||||
if (m_chans)
|
||||
delete[] m_chans;
|
||||
}
|
||||
|
||||
void WpData::run()
|
||||
{
|
||||
DDebug(&__plugin,DebugAll,"WpData::run() [%p]",this);
|
||||
unsigned char buffer[WP_HEADER+WP_BUFFER];
|
||||
int bchans = m_span->bchans();
|
||||
// Build a compacted list of allocated B channels
|
||||
m_chans = new WpChan* [bchans];
|
||||
int b = 0;
|
||||
for (int n = 0; n < bchans; n++) {
|
||||
while (!m_span->m_chans[b])
|
||||
b++;
|
||||
m_chans[n] = static_cast<WpChan*>(m_span->m_chans[b++]);
|
||||
DDebug(&__plugin,DebugInfo,"wpdata ch[%d]=%d (%p)",n,m_chans[n]->chan(),m_chans[n]);
|
||||
}
|
||||
int rok = 0, rerr = 0;
|
||||
int wok = 0, werr = 0;
|
||||
while (m_span && m_span->m_data && (m_fd != INVALID_HANDLE_VALUE)) {
|
||||
Thread::check();
|
||||
int samp = 0;
|
||||
int r = wp_recv(m_fd,buffer,sizeof(buffer),0/*MSG_NOSIGNAL*/);
|
||||
XDebug(&__plugin,DebugAll,"WpData recv r=%d",r);
|
||||
r -= WP_HEADER;
|
||||
// We should have read N bytes for each B channel
|
||||
if (r > 0) {
|
||||
samp = r / bchans;
|
||||
if ((r % bchans) == 0) {
|
||||
const unsigned char* dat = buffer + WP_HEADER;
|
||||
m_span->lock();
|
||||
int p1 = -1;
|
||||
int p2 = -1;
|
||||
for (int n = samp; n > 0; n--) {
|
||||
for (b = 0; b < bchans; b++) {
|
||||
WpSource *s = m_chans[b]->m_wp_s;
|
||||
if (s)
|
||||
s->put(m_swap ? PriDriver::bitswap(*dat) : *dat);
|
||||
dat++;
|
||||
}
|
||||
}
|
||||
m_span->unlock();
|
||||
++rok;
|
||||
}
|
||||
else
|
||||
Debug(DebugWarn,"WpData read %d (ok/bad %d/%d)",r,rok,++rerr);
|
||||
}
|
||||
if (samp) {
|
||||
::memset(buffer,0,WP_HEADER);
|
||||
unsigned char* dat = buffer + WP_HEADER;
|
||||
m_span->lock();
|
||||
for (int n = samp; n > 0; n--) {
|
||||
for (b = 0; b < bchans; b++) {
|
||||
WpConsumer *c = m_chans[b]->m_wp_c;
|
||||
unsigned char d = c ? c->get() : 0xff;
|
||||
*dat++ = m_swap ? PriDriver::bitswap(d) : d;
|
||||
}
|
||||
}
|
||||
m_span->unlock();
|
||||
int w = samp * bchans;
|
||||
dat = buffer;
|
||||
buffer[0] = 11;
|
||||
buffer[1] = w & 0xff;
|
||||
buffer[2] = w >> 8;
|
||||
w = wp_send(m_fd,buffer,sizeof(buffer),MSG_DONTWAIT);
|
||||
if (w != sizeof(buffer))
|
||||
Debug(DebugWarn,"WpData wrote %d (ok/bad %d/%d)",w,wok,++werr);
|
||||
else
|
||||
++wok;
|
||||
XDebug(&__plugin,DebugAll,"WpData send w=%d",w);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WpChan::WpChan(const PriSpan *parent, int chan, unsigned int bufsize)
|
||||
: PriChan(parent,chan,bufsize), m_wp_s(0), m_wp_c(0)
|
||||
{
|
||||
}
|
||||
|
||||
WpChan::~WpChan()
|
||||
{
|
||||
closeData();
|
||||
}
|
||||
|
||||
bool WpChan::openData(const char* format, int echoTaps)
|
||||
{
|
||||
Debug(this,DebugAll,"WpChan::openData(%s,%d) [%p]",format,echoTaps,this);
|
||||
if (echoTaps)
|
||||
Debug(DebugWarn,"Echo cancellation requested but not available in wanpipe");
|
||||
m_span->lock();
|
||||
setSource(new WpSource(this,format,m_bufsize));
|
||||
getSource()->deref();
|
||||
setConsumer(new WpConsumer(this,format,m_bufsize));
|
||||
getConsumer()->deref();
|
||||
m_span->unlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
PriSpan* WpDriver::createSpan(PriDriver* driver, int span, int first, int chans, Configuration& cfg, const String& sect)
|
||||
{
|
||||
Debug(this,DebugAll,"WpDriver::createSpan(%p,%d,%d,%d) [%p]",driver,span,first,chans,this);
|
||||
int netType = -1;
|
||||
int swType = -1;
|
||||
int dchan = -1;
|
||||
netParams(cfg,sect,chans,&netType,&swType,&dchan);
|
||||
String card;
|
||||
card << "WANPIPE" << span;
|
||||
card = cfg.getValue(sect,"card",card);
|
||||
String dev;
|
||||
dev = cfg.getValue(sect,"dgroup","IF0");
|
||||
pri* p = ::pri_new_cb((int)INVALID_HANDLE_VALUE, netType, swType, wp_read, wp_write, 0);
|
||||
if (!p)
|
||||
return 0;
|
||||
WpSpan *ps = new WpSpan(p,driver,span,first,chans,dchan,cfg,sect);
|
||||
WpWriter* wr = new WpWriter(ps,card,dev);
|
||||
WpReader* rd = new WpReader(ps,card,dev);
|
||||
dev = cfg.getValue(sect,"bgroup","IF1");
|
||||
WpData* dat = new WpData(ps,card,dev,cfg,sect);
|
||||
wr->startup();
|
||||
rd->startup();
|
||||
dat->startup();
|
||||
ps->startup();
|
||||
return ps;
|
||||
}
|
||||
|
||||
PriChan* WpDriver::createChan(const PriSpan* span, int chan, unsigned int bufsize)
|
||||
{
|
||||
Debug(this,DebugAll,"WpDriver::createChan(%p,%d,%u) [%p]",span,chan,bufsize,this);
|
||||
return new WpChan(span,chan,bufsize);
|
||||
}
|
||||
|
||||
WpDriver::WpDriver()
|
||||
: PriDriver("wp")
|
||||
{
|
||||
Output("Loaded module Wanpipe");
|
||||
}
|
||||
|
||||
WpDriver::~WpDriver()
|
||||
{
|
||||
Output("Unloading module Wanpipe");
|
||||
}
|
||||
|
||||
void WpDriver::initialize()
|
||||
{
|
||||
Output("Initializing module Wanpipe");
|
||||
init("wpchan");
|
||||
installRelay(Halt,110);
|
||||
}
|
||||
|
||||
bool WpDriver::received(Message &msg, int id)
|
||||
{
|
||||
bool ok = PriDriver::received(msg,id);
|
||||
if (id == Halt) {
|
||||
Debug(this,DebugAll,"WpDriver clearing all spans [%p]",this);
|
||||
lock();
|
||||
const ObjList *l = &m_spans;
|
||||
for (; l; l=l->next()) {
|
||||
WpSpan *s = static_cast<WpSpan*>(l->get());
|
||||
if (s)
|
||||
s->cancel();
|
||||
}
|
||||
unlock();
|
||||
Debug(this,DebugAll,"WpDriver waiting for spans to exit [%p]",this);
|
||||
while (m_spans.get())
|
||||
Thread::msleep(10);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
}; // anonymous namespace
|
||||
|
||||
#endif /* _WINDOWS */
|
||||
|
||||
/* vi: set ts=8 sw=4 sts=4 noet: */
|
|
@ -1,515 +0,0 @@
|
|||
/**
|
||||
* zapchan.cpp
|
||||
* This file is part of the YATE Project http://YATE.null.ro
|
||||
*
|
||||
* Zapata telephony driver
|
||||
*
|
||||
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
||||
* Copyright (C) 2004-2006 Null Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <modules/libypri.h>
|
||||
|
||||
#ifdef _WINDOWS
|
||||
#error This module is not for Windows
|
||||
#else
|
||||
|
||||
extern "C" {
|
||||
#ifdef NEW_ZAPTEL_LOCATION
|
||||
#define __LINUX__
|
||||
#include <zaptel/zaptel.h>
|
||||
#else
|
||||
#include <linux/zaptel.h>
|
||||
#endif
|
||||
};
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifndef _WINDOWS
|
||||
#include <sys/ioctl.h>
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
||||
#ifndef ZT_EVENT_DTMFDIGIT
|
||||
#ifdef ZT_EVENT_DTMFDOWN
|
||||
#define ZT_EVENT_DTMFDIGIT ZT_EVENT_DTMFDOWN
|
||||
#else
|
||||
#define ZT_EVENT_DTMFDIGIT 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef ZT_EVENT_PULSEDIGIT
|
||||
#define ZT_EVENT_PULSEDIGIT 0
|
||||
#endif
|
||||
|
||||
using namespace TelEngine;
|
||||
namespace { // anonymous
|
||||
|
||||
/* Zaptel formats */
|
||||
static TokenDict dict_str2ztlaw[] = {
|
||||
{ "slin", -1 },
|
||||
{ "default", ZT_LAW_DEFAULT },
|
||||
{ "mulaw", ZT_LAW_MULAW },
|
||||
{ "alaw", ZT_LAW_ALAW },
|
||||
{ 0, -2 }
|
||||
};
|
||||
|
||||
class ZapChan;
|
||||
|
||||
class ZapSpan : public PriSpan, public Thread
|
||||
{
|
||||
friend class ZapDriver;
|
||||
public:
|
||||
virtual ~ZapSpan();
|
||||
virtual void run();
|
||||
inline int train() const
|
||||
{ return m_train; }
|
||||
|
||||
private:
|
||||
ZapSpan(struct pri *_pri, PriDriver* driver, int span, int first, int chans, int dchan, Configuration& cfg, const String& sect, int fd);
|
||||
int m_fd;
|
||||
int m_train;
|
||||
};
|
||||
|
||||
class ZapSource : public PriSource, public Thread
|
||||
{
|
||||
public:
|
||||
ZapSource(ZapChan *owner, const char* format, unsigned int bufsize);
|
||||
~ZapSource();
|
||||
virtual void run();
|
||||
private:
|
||||
DataBlock m_data;
|
||||
};
|
||||
|
||||
class ZapConsumer : public PriConsumer
|
||||
{
|
||||
public:
|
||||
ZapConsumer(ZapChan *owner, const char* format, unsigned int bufsize);
|
||||
~ZapConsumer();
|
||||
virtual void Consume(const DataBlock &data, unsigned long tStamp);
|
||||
private:
|
||||
unsigned int m_bufsize;
|
||||
DataErrors m_overruns;
|
||||
};
|
||||
|
||||
class ZapChan : public PriChan
|
||||
{
|
||||
friend class ZapSource;
|
||||
friend class ZapConsumer;
|
||||
public:
|
||||
ZapChan(const PriSpan *parent, int chan, unsigned int bufsize);
|
||||
virtual ~ZapChan();
|
||||
virtual bool openData(const char* format, int echoTaps);
|
||||
virtual void closeData();
|
||||
inline int fd() const
|
||||
{ return m_fd; }
|
||||
inline int law() const
|
||||
{ return m_law; }
|
||||
protected:
|
||||
virtual void dataChanged();
|
||||
private:
|
||||
int m_fd;
|
||||
int m_law;
|
||||
bool m_train;
|
||||
};
|
||||
|
||||
class ZapDriver : public PriDriver
|
||||
{
|
||||
friend class PriSpan;
|
||||
friend class ZapHandler;
|
||||
public:
|
||||
ZapDriver();
|
||||
virtual ~ZapDriver();
|
||||
virtual void initialize();
|
||||
virtual PriSpan* createSpan(PriDriver* driver, int span, int first, int chans, Configuration& cfg, const String& sect);
|
||||
virtual PriChan* createChan(const PriSpan* span, int chan, unsigned int bufsize);
|
||||
};
|
||||
|
||||
INIT_PLUGIN(ZapDriver);
|
||||
|
||||
static int zt_get_event(int fd)
|
||||
{
|
||||
/* Avoid the silly zt_getevent which ignores a bunch of events */
|
||||
int j = 0;
|
||||
if (::ioctl(fd, ZT_GETEVENT, &j) == -1)
|
||||
return -1;
|
||||
return j;
|
||||
}
|
||||
|
||||
static int zt_open_dchan(int channo, int bsize = 1024, int nbufs = 16)
|
||||
{
|
||||
DDebug(&__plugin,DebugInfo,"Opening zap d-channel %d with %d x %d buffers",channo,nbufs,bsize);
|
||||
int fd = ::open("/dev/zap/channel", O_RDWR, 0600);
|
||||
if (fd < 0) {
|
||||
Debug(&__plugin,DebugGoOn,"Failed to open device: error %d: %s",errno,::strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if (::ioctl(fd,ZT_SPECIFY,&channo) == -1) {
|
||||
Debug(&__plugin,DebugGoOn,"Failed to specify chan %d: error %d: %s",channo,errno,::strerror(errno));
|
||||
::close(fd);
|
||||
return -1;
|
||||
}
|
||||
ZT_PARAMS par;
|
||||
if (::ioctl(fd, ZT_GET_PARAMS, &par) == -1) {
|
||||
Debug(&__plugin,DebugGoOn,"Failed to get params of chan %d: error %d: %s",channo,errno,::strerror(errno));
|
||||
::close(fd);
|
||||
return -1;
|
||||
}
|
||||
if (par.sigtype != ZT_SIG_HDLCFCS) {
|
||||
Debug(&__plugin,DebugGoOn,"Channel %d is not in HDLC/FCS mode",channo);
|
||||
::close(fd);
|
||||
return -1;
|
||||
}
|
||||
ZT_BUFFERINFO bi;
|
||||
bi.txbufpolicy = ZT_POLICY_IMMEDIATE;
|
||||
bi.rxbufpolicy = ZT_POLICY_IMMEDIATE;
|
||||
bi.numbufs = nbufs;
|
||||
bi.bufsize = bsize;
|
||||
if (::ioctl(fd, ZT_SET_BUFINFO, &bi) == -1)
|
||||
Debug(&__plugin,DebugWarn,"Could not set buffering on %d: error %d: %s",channo,errno,::strerror(errno));
|
||||
return fd;
|
||||
}
|
||||
|
||||
static int zt_open_bchan(int channo, bool subchan, unsigned int blksize)
|
||||
{
|
||||
DDebug(&__plugin,DebugInfo,"Opening zap b-channel %d with block size=%d",channo,blksize);
|
||||
int fd = ::open(subchan ? "/dev/zap/pseudo" : "/dev/zap/channel",O_RDWR|O_NONBLOCK);
|
||||
if (fd < 0) {
|
||||
Debug(&__plugin,DebugGoOn,"Failed to open device: error %d: %s",errno,::strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if (channo) {
|
||||
if (::ioctl(fd, subchan ? ZT_CHANNO : ZT_SPECIFY, &channo)) {
|
||||
Debug(&__plugin,DebugGoOn,"Failed to specify chan %d: error %d: %s",channo,errno,::strerror(errno));
|
||||
::close(fd);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (blksize) {
|
||||
if (::ioctl(fd, ZT_SET_BLOCKSIZE, &blksize) == -1) {
|
||||
Debug(&__plugin,DebugGoOn,"Failed to set block size %d: error %d: %s",blksize,errno,::strerror(errno));
|
||||
::close(fd);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
static bool zt_set_law(int fd, int law)
|
||||
{
|
||||
if (law < 0) {
|
||||
int linear = 1;
|
||||
if (::ioctl(fd, ZT_SETLINEAR, &linear) != -1)
|
||||
return true;
|
||||
}
|
||||
else
|
||||
if (::ioctl(fd, ZT_SETLAW, &law) != -1)
|
||||
return true;
|
||||
DDebug(&__plugin,DebugInfo,"Failed to set law %d: error %d: %s",law,errno,::strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool zt_echo_cancel(int fd, int taps)
|
||||
{
|
||||
int tmp = 1;
|
||||
if (taps && (::ioctl(fd, ZT_AUDIOMODE, &tmp) < 0)) {
|
||||
Debug(&__plugin,DebugNote,"Failed to set audio mode: error %d: %s",errno,::strerror(errno));
|
||||
return false;
|
||||
}
|
||||
if (::ioctl(fd, ZT_ECHOCANCEL, &taps) < 0) {
|
||||
Debug(&__plugin,DebugMild,"Failed to set %d echo cancellation taps: error %d: %s",taps,errno,::strerror(errno));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool zt_echo_train(int fd, int train = 400)
|
||||
{
|
||||
if ((train > 0) && (::ioctl(fd, ZT_ECHOTRAIN, &train) < 0)) {
|
||||
Debug(&__plugin,DebugMild,"Failed to start echo trainig for %d ms: error %d: %s",train,errno,::strerror(errno));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool zt_dtmf_detect(int fd, bool detect)
|
||||
{
|
||||
#ifdef ZT_TONEDETECT
|
||||
int tdetect = detect ? ZT_TONEDETECT_ON | ZT_TONEDETECT_MUTE : 0;
|
||||
if (::ioctl(fd, ZT_TONEDETECT, &tdetect) >= 0)
|
||||
return true;
|
||||
#else
|
||||
// pretend enabling fails, disabling succeeds
|
||||
if (!detect)
|
||||
return true;
|
||||
errno = ENOSYS;
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
ZapSpan::ZapSpan(struct pri *_pri, PriDriver* driver, int span, int first, int chans, int dchan, Configuration& cfg, const String& sect, int fd)
|
||||
: PriSpan(_pri,driver,span,first,chans,dchan,cfg,sect), Thread("ZapSpan"),
|
||||
m_fd(fd), m_train(0)
|
||||
{
|
||||
Debug(m_driver,DebugAll,"ZapSpan::ZapSpan() [%p]",this);
|
||||
m_train = cfg.getIntValue(sect,"echotrain",cfg.getIntValue("general","echotrain",400));
|
||||
}
|
||||
|
||||
ZapSpan::~ZapSpan()
|
||||
{
|
||||
Debug(m_driver,DebugAll,"ZapSpan::~ZapSpan() [%p]",this);
|
||||
m_ok = false;
|
||||
::close(m_fd);
|
||||
m_fd = -1;
|
||||
}
|
||||
|
||||
void ZapSpan::run()
|
||||
{
|
||||
Debug(m_driver,DebugAll,"ZapSpan::run() [%p]",this);
|
||||
fd_set rdfds;
|
||||
fd_set errfds;
|
||||
for (;;) {
|
||||
FD_ZERO(&rdfds);
|
||||
FD_SET(m_fd, &rdfds);
|
||||
FD_ZERO(&errfds);
|
||||
FD_SET(m_fd, &errfds);
|
||||
struct timeval tv;
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 100;
|
||||
int sel = ::select(m_fd+1, &rdfds, NULL, &errfds, &tv);
|
||||
Thread::check();
|
||||
if (!sel)
|
||||
runEvent(true);
|
||||
else if (sel > 0) {
|
||||
if (FD_ISSET(m_fd, &errfds)) {
|
||||
int zev = zt_get_event(m_fd);
|
||||
if (zev)
|
||||
Debug(DebugInfo,"Zapata event %d on span %d",zev,span());
|
||||
}
|
||||
if (FD_ISSET(m_fd, &rdfds))
|
||||
runEvent(false);
|
||||
}
|
||||
else if (errno != EINTR)
|
||||
Debug("ZapSpan",DebugGoOn,"select() error %d: %s",
|
||||
errno,::strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
ZapSource::ZapSource(ZapChan *owner, const char* format, unsigned int bufsize)
|
||||
: PriSource(owner,format,bufsize), Thread("ZapSource")
|
||||
{
|
||||
Debug(m_owner,DebugAll,"ZapSource::ZapSource(%p) [%p]",owner,this);
|
||||
}
|
||||
|
||||
ZapSource::~ZapSource()
|
||||
{
|
||||
Debug(m_owner,DebugAll,"ZapSource::~ZapSource() [%p]",this);
|
||||
}
|
||||
|
||||
void ZapSource::run()
|
||||
{
|
||||
int rd = 0;
|
||||
for (;;) {
|
||||
Thread::yield(true);
|
||||
int fd = static_cast<ZapChan*>(m_owner)->fd();
|
||||
if (fd != -1) {
|
||||
rd = ::read(fd,m_buffer.data(),m_buffer.length());
|
||||
XDebug(m_owner,DebugAll,"ZapSource read %d bytes [%p]",rd,this);
|
||||
if (rd > 0)
|
||||
Forward(m_buffer);
|
||||
else if (rd < 0) {
|
||||
if ((errno != EAGAIN) && (errno != EINTR)) {
|
||||
int zev = zt_get_event(fd);
|
||||
if (zev) {
|
||||
Debug(m_owner,DebugInfo,"ZapSource event %d [%p]",zev,this);
|
||||
// driver-decoded digit arrived
|
||||
if (zev & (ZT_EVENT_DTMFDIGIT | ZT_EVENT_PULSEDIGIT)) {
|
||||
char buf[2];
|
||||
buf[0] = zev & 0xff;
|
||||
buf[1] = '\0';
|
||||
m_owner->gotDigits(buf);
|
||||
}
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
Debug(m_owner,DebugWarn,"ZapSource at EOF (read %d) [%p]",rd,this);
|
||||
// TODO: find a better way of dealing with this abnormal condition
|
||||
for (;;)
|
||||
Thread::yield(true);
|
||||
}
|
||||
|
||||
ZapConsumer::ZapConsumer(ZapChan *owner, const char* format, unsigned int bufsize)
|
||||
: PriConsumer(owner,format,bufsize), m_bufsize(bufsize)
|
||||
{
|
||||
Debug(m_owner,DebugAll,"ZapConsumer::ZapConsumer(%p) [%p]",owner,this);
|
||||
}
|
||||
|
||||
ZapConsumer::~ZapConsumer()
|
||||
{
|
||||
Debug(m_owner,DebugAll,"ZapConsumer::~ZapConsumer() [%p]",this);
|
||||
if (m_overruns.events())
|
||||
Debug(m_owner,DebugMild,"Consumer had %u overruns (%lu bytes)",
|
||||
m_overruns.events(),m_overruns.bytes());
|
||||
}
|
||||
|
||||
void ZapConsumer::Consume(const DataBlock &data, unsigned long tStamp)
|
||||
{
|
||||
int fd = static_cast<ZapChan*>(m_owner)->fd();
|
||||
XDebug(DebugAll,"ZapConsumer fd=%d datalen=%u",fd,data.length());
|
||||
if ((fd != -1) && !data.null()) {
|
||||
if (m_buffer.length()+data.length() <= m_bufsize*4)
|
||||
m_buffer += data;
|
||||
else {
|
||||
m_overruns.update(data.length());
|
||||
DDebug(m_owner,DebugAll,"ZapConsumer skipped %u bytes, buffer is full",data.length());
|
||||
}
|
||||
if (m_buffer.null())
|
||||
return;
|
||||
if (m_buffer.length() >= m_bufsize) {
|
||||
int wr = ::write(fd,m_buffer.data(),m_bufsize);
|
||||
if (wr < 0) {
|
||||
if ((errno != EAGAIN) && (errno != EINTR))
|
||||
Debug(DebugGoOn,"ZapConsumer write error %d: %s",
|
||||
errno,::strerror(errno));
|
||||
}
|
||||
else {
|
||||
if ((unsigned)wr != m_bufsize)
|
||||
Debug(m_owner,DebugInfo,"ZapConsumer short write, %d of %u bytes",wr,m_bufsize);
|
||||
m_buffer.cut(-wr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ZapChan::ZapChan(const PriSpan *parent, int chan, unsigned int bufsize)
|
||||
: PriChan(parent,chan,bufsize), m_fd(-1), m_law(-1), m_train(false)
|
||||
{
|
||||
}
|
||||
|
||||
ZapChan::~ZapChan()
|
||||
{
|
||||
closeData();
|
||||
}
|
||||
|
||||
bool ZapChan::openData(const char* format, int echoTaps)
|
||||
{
|
||||
m_fd = zt_open_bchan(m_abschan,false,m_bufsize);
|
||||
if (m_fd == -1)
|
||||
return false;
|
||||
int defLaw = ZT_LAW_ALAW;
|
||||
if (m_span->chans() == 24)
|
||||
defLaw = ZT_LAW_MULAW;
|
||||
defLaw = lookup(format,dict_str2ztlaw,defLaw);
|
||||
if (zt_set_law(m_fd,defLaw)) {
|
||||
m_law = defLaw;
|
||||
format = lookup(m_law,dict_str2ztlaw,"unknown");
|
||||
Debug(this,DebugInfo,"Opened Zap channel %d, law is: %s",m_abschan,format);
|
||||
}
|
||||
if (zt_echo_cancel(m_fd,echoTaps) && echoTaps)
|
||||
m_train = true;
|
||||
if (!zt_dtmf_detect(m_fd,m_detect)) {
|
||||
Debug(this,m_detect ? DebugFail : DebugMild,
|
||||
"Failed to %s DTMF detection: %s (%d)",
|
||||
m_detect ? "enable" : "disable",::strerror(errno),errno);
|
||||
}
|
||||
ZapSource* src = new ZapSource(this,format,m_bufsize);
|
||||
setSource(src);
|
||||
src->startup();
|
||||
src->deref();
|
||||
setConsumer(new ZapConsumer(this,format,m_bufsize));
|
||||
getConsumer()->deref();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ZapChan::dataChanged()
|
||||
{
|
||||
if (m_train && zt_echo_train(m_fd,static_cast<ZapSpan*>(span())->train()))
|
||||
Debug(this,DebugCall,"Started echo canceller training [%p]",this);
|
||||
}
|
||||
|
||||
void ZapChan::closeData()
|
||||
{
|
||||
PriChan::closeData();
|
||||
m_train = false;
|
||||
if (m_fd != -1) {
|
||||
::close(m_fd);
|
||||
m_fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
PriSpan* ZapDriver::createSpan(PriDriver* driver, int span, int first, int chans, Configuration& cfg, const String& sect)
|
||||
{
|
||||
Debug(this,DebugAll,"ZapDriver::createSpan(%p,%d,%d,%d) [%p]",driver,span,first,chans,this);
|
||||
int netType = -1;
|
||||
int swType = -1;
|
||||
int dchan = -1;
|
||||
netParams(cfg,sect,chans,&netType,&swType,&dchan);
|
||||
if (dchan < 0)
|
||||
return 0;
|
||||
int fd = zt_open_dchan(dchan+first-1);
|
||||
if (fd < 0)
|
||||
return 0;
|
||||
#if defined(PRI_NEW_SET_API) && defined(BRI_NETWORK_PTMP)
|
||||
// Klaus-Peter Junghanns broke this one too
|
||||
pri* p = ::pri_new(fd,netType,swType,span);
|
||||
#else
|
||||
pri* p = ::pri_new(fd,netType,swType);
|
||||
#endif
|
||||
if (!p)
|
||||
return 0;
|
||||
ZapSpan *zs = new ZapSpan(p,driver,span,first,chans,dchan,cfg,sect,fd);
|
||||
zs->startup();
|
||||
return zs;
|
||||
}
|
||||
|
||||
PriChan* ZapDriver::createChan(const PriSpan* span, int chan, unsigned int bufsize)
|
||||
{
|
||||
Debug(this,DebugAll,"ZapDriver::createChan(%p,%d,%u) [%p]",span,chan,bufsize,this);
|
||||
return new ZapChan(span,chan,bufsize);
|
||||
}
|
||||
|
||||
ZapDriver::ZapDriver()
|
||||
: PriDriver("zap")
|
||||
{
|
||||
Output("Loaded module Zapchan");
|
||||
}
|
||||
|
||||
ZapDriver::~ZapDriver()
|
||||
{
|
||||
Output("Unloading module Zapchan");
|
||||
}
|
||||
|
||||
void ZapDriver::initialize()
|
||||
{
|
||||
Output("Initializing module Zapchan");
|
||||
init("zapchan");
|
||||
}
|
||||
|
||||
}; // anonymous namespace
|
||||
|
||||
#endif /* _WINDOWS */
|
||||
|
||||
/* vi: set ts=8 sw=4 sts=4 noet: */
|
|
@ -0,0 +1,8 @@
|
|||
Makefile
|
||||
YateLocal.mak
|
||||
core*
|
||||
*.o
|
||||
*.a
|
||||
*.orig
|
||||
*~
|
||||
.*.swp
|
|
@ -0,0 +1,8 @@
|
|||
Makefile
|
||||
YateLocal.mak
|
||||
core*
|
||||
*.o
|
||||
*.a
|
||||
*.orig
|
||||
*~
|
||||
.*.swp
|
|
@ -1,7 +1,7 @@
|
|||
# Have to rotate the log file before it reaches 2GB in size
|
||||
|
||||
/var/log/yate {
|
||||
size=1000M
|
||||
size=100M
|
||||
rotate 5
|
||||
missingok
|
||||
notifempty
|
||||
|
|
6
run.in
6
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 "$@"
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
Makefile
|
||||
YateLocal*
|
||||
.xvpics
|
||||
core*
|
||||
*.orig
|
||||
*~
|
||||
.*.swp
|
|
@ -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
|
|
@ -1,4 +1,5 @@
|
|||
Makefile
|
||||
YateLocal*
|
||||
.xvpics
|
||||
core*
|
||||
*.orig
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
Makefile
|
||||
YateLocal.mak
|
||||
YateLocal*
|
||||
.xvpics
|
||||
core*
|
||||
*.orig
|
||||
*~
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
Makefile
|
||||
YateLocal*
|
||||
.xvpics
|
||||
core*
|
||||
*.orig
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue