Imported in new CVS

git-svn-id: http://yate.null.ro/svn/yate/trunk@2 acf43c95-373e-0410-b603-e72c3f656dc1
This commit is contained in:
paulc 2004-05-22 00:05:20 +00:00
parent e0f55ab398
commit 628070ee2e
71 changed files with 12923 additions and 0 deletions

19
.cvsignore Normal file
View File

@ -0,0 +1,19 @@
*.cache
Makefile
configure
config.*
yatepaths.h
run
yate.spec
yate
yate-config
yate.pc
core*
yate.core*
*.o
*.a
*.so
*.yate
*.orig
*~
.*.swp

339
COPYING Normal file
View File

@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
675 Mass Ave, Cambridge, MA 02139, USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) 19yy <name of author>
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., 675 Mass Ave, Cambridge, MA 02139, USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) 19yy name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.

39
ChangeLog Normal file
View File

@ -0,0 +1,39 @@
Sat May 15 2004 Paul Chitescu <paulc-devel@null.ro>
- Added pkgconfig support
- Improved detection of Postgress' include file path
- Support for detecting libraries required for SIP
- Better detection of OpenH323 versions
Sat May 15 2004 Diana Cionoiu <diana@diana.null.ro>
- Added SIP channel and registration module
Wed Apr 28 2004 Paul Chitescu <paulc-devel@null.ro>
- Version 0.8.1
- Fixed data law selection on Zaptel
- Preventing vi swap and backup files from polluting the tarballs
Mon Apr 26 2004 Paul Chitescu <paulc-devel@null.ro>
- Fixed a fatal 16->8 bit conversion bug in DataBlock::convert()
- Added valgrind support to the run script
Sat Apr 11 2004 Paul Chitescu <paulc-devel@null.ro>
- Moved hash capabilities to the String class so HString was removed
- String encoding and decoding methods for messages
- Slightly more useful RManager
Sun Apr 04 2004 Paul Chitescu <paulc-devel@null.ro>
- Added an yate-config script
Sat Apr 03 2004 Paul Chitescu <paulc-devel@null.ro>
- Turned some redundant strings from makefiles into variables
- Applied some patches submitted by Cristian Andrei Calin <kman@tfm.ro> :
- Patches to allow compiling under NetBSD and FreeBSD
- Patch to add -fPIC which supposedly fixes the dlclose() bug in *BSD
Fri Apr 02 2004 Paul Chitescu <paulc-devel@null.ro>
- Imported into new CVS
Mon Mar 29 2004 Paul Chitescu <paulc-devel@null.ro>
- Version 0.8.0
- Redesigned the build system so it works trough autoconf
- Added a RPM specfile - tested on RedHat 7.1

209
Makefile.in Normal file
View File

@ -0,0 +1,209 @@
# Makefile
# This file holds the make rules for the Telephony Engine
# 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 :=
CC := g++ -Wall
SED := sed
DEFS :=
LIBAUX:= -ldl -lpthread
INCLUDES := -I@srcdir@
CFLAGS := -O2 @MODULE_CFLAGS@ -finline -Winline
LDFLAGS:=
PROGS:= yate
SLIBS:= libyate.so
INCS := telengine.h telephony.h yateversn.h
LIBS :=
MAN8 := yate.8
DOCS := README COPYING ChangeLog
ENGOBJS := TelEngine.o String.o DataBlock.o ObjList.o \
NamedList.o Configuration.o \
Message.o Mutex.o Thread.o \
Plugin.o Engine.o
OBJS := main.o
LIBOBJS := $(ENGOBJS) $(TELOBJS)
CLEANS = $(PROGS) $(SLIBS) $(LIBS) $(OBJS) $(LIBOBJS) core
COMPILE = $(CC) $(DEFS) $(DEBUG) $(INCLUDES) $(CFLAGS)
LINK = $(CC) $(LDFLAGS)
prefix = @prefix@
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
.PHONY: all
all: engine modules
.PHONY: clean
clean:
-rm $(CLEANS) 2>/dev/null
$(MAKE) -C ./modules $@
$(MAKE) -C ./test $@
.PHONY: engine
engine: tables yatepaths.h $(LIBS) $(SLIBS) $(PROGS)
.PHONY: apidocs
apidocs: check-topdir
kdoc -d docs/api/ $(INCS)
.PHONY: strip
strip: all
-strip --strip-debug --discard-locals $(PROGS) $(SLIBS)
.PHONY: sex
sex: strip
@echo 'Stripped for you!'
.PHONY: love
love:
@echo 'Not war?'
.PHONY: run
run: all
-./run
.PHONY: gdb-run
gdb-run: all
-./run --gdb
.PHONY: gdb-core
gdb-core: all
-./run --core
.PHONY: test
test: engine
$(MAKE) -C ./test all
.PHONY: modules
modules: engine
$(MAKE) -C ./modules all
tables: @srcdir@/tables/all.h
@srcdir@/tables/all.h:
$(MAKE) -C @srcdir@/tables -f Makefile.tables all
yatepaths.h:
@echo -en '#define MOD_PATH "$(DESTDIR)$(moddir)"\n#define CFG_PATH "$(DESTDIR)$(confdir)"\n' > $@
.PHONY: everything
everything: all test
.PHONY: install
install: all
@mkdir -p "$(DESTDIR)$(libdir)/" && \
install $(SLIBS) "$(DESTDIR)$(libdir)/" && ldconfig
@mkdir -p "$(DESTDIR)$(bindir)/" && \
install $(PROGS) yate-config "$(DESTDIR)$(bindir)/"
@mkdir -p "$(DESTDIR)$(libdir)/pkgconfig/" && \
install yate.pc "$(DESTDIR)$(libdir)/pkgconfig/"
@mkdir -p "$(DESTDIR)$(incdir)/" && \
for i in $(INCS) ; do \
install -m 0644 @srcdir@/$$i "$(DESTDIR)$(incdir)/" ; \
done
@mkdir -p "$(DESTDIR)$(mandir)/man8/" && \
for i in $(MAN8) ; do \
install -m 0644 @srcdir@/$$i "$(DESTDIR)$(mandir)/man8/" ; \
done
@mkdir -p "$(DESTDIR)$(docdir)/api/" && \
for i in $(DOCS) ; do \
install -m 0644 @srcdir@/$$i "$(DESTDIR)$(docdir)/" ; \
done ;
install -m 0644 @srcdir@/docs/*.html "$(DESTDIR)$(docdir)/" && \
install -m 0644 @srcdir@/docs/api/*.html "$(DESTDIR)$(docdir)/api/"
$(MAKE) -C ./modules $@
$(MAKE) -C ./conf.d $@
.PHONY: uninstall
uninstall:
@-for i in $(SLIBS) ; do \
rm "$(DESTDIR)$(libdir)/$$i" ; \
done; \
ldconfig
@-for i in $(PROGS) yate-config ; do \
rm "$(DESTDIR)$(bindir)/$$i" ; \
done
@-rm "$(DESTDIR)$(libdir)/pkgconfig/yate.pc" && \
rmdir $(DESTDIR)$(libdir)/pkgconfig
@-for i in $(INCS) ; do \
rm "$(DESTDIR)$(incdir)/$$i" ; \
done; \
rmdir "$(DESTDIR)$(incdir)"
@-for i in $(MAN8) ; do \
rm "$(DESTDIR)$(mandir)/man8/$$i" ; \
done
@rm -rf "$(DESTDIR)$(docdir)/"
$(MAKE) -C ./modules $@
$(MAKE) -C ./conf.d $@
.PHONY: snapshot tarball
snapshot tarball: check-topdir clean tables apidocs
@if [ $@ = snapshot ]; then ver="`date '+CVS-%Y%m%d'`"; else ver="@PACKAGE_VERSION@"; fi ; \
wd=`pwd|sed 's,^.*/,,'`; \
mkdir -p tarballs; cd ..; \
echo $$wd/tar-exclude >$$wd/tar-exclude; \
find $$wd -name Makefile >>$$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 CVS >>$$wd/tar-exclude; \
find $$wd -name .cvsignore >>$$wd/tar-exclude; \
else \
echo "$$wd/yate.spec" >>$$wd/tar-exclude; \
fi ; \
tar czf $$wd/tarballs/$$wd-$$ver.tar.gz \
--exclude $$wd/tarballs \
--exclude $$wd/config.status \
--exclude $$wd/config.log \
--exclude $$wd/run \
--exclude $$wd/yate-config \
--exclude $$wd/yate.pc \
--exclude $$wd/yatepaths.h \
-X $$wd/tar-exclude \
$$wd; \
rm $$wd/tar-exclude
.PHONY: check-topdir
check-topdir:
@test -f configure || (echo "Must make this target in the top source directory"; exit 1)
%.o: @srcdir@/%.cpp @srcdir@/telengine.h
$(COMPILE) -c $<
@srcdir@/configure: @srcdir@/configure.in
cd @srcdir@ && autoconf
config.status: @srcdir@/configure
./config.status --recheck
Makefile: @srcdir@/Makefile.in config.status
./config.status
yate: $(OBJS) libyate.so $(LIBS)
$(LINK) -o $@ $^
libyate.so: $(ENGOBJS) $(LIBS)
$(LINK) -shared -o $@ $^ $(LIBAUX)
.PHONY: help
help:
@echo -e 'Usual make targets:\n\
all engine modules test everything apidocs\n\
install uninstall\n\
snapshot tarball'

72
README Normal file
View File

@ -0,0 +1,72 @@
YATE - Yet Another Telephony Engine
-----------------------------------
The YATE project aims to be a fully featured software PBX.
It was created to alow developers and users to have more functionality and
scalability. To reach this goal YATE is built from two kinds of components:
1. The main engine - telengine.
2. Modules - routing modules
- drivers
- script language bindings
- billing modules
Its license is GPL with exceptions (in case of linking with proprietary
software). We have chosen this license to help the growth of this project.
Building YATE Software
----------------------
YATE have been tested only on Linux and is in alpha stage yet, so is very
possibile to find bugs (even if we have tried by design to minimize the
chance of introducing bugs). Please report them at bugs@voip.null.ro
1. Building the engine
You have just to run 'make' in the main directory, this will not build any
modules or test cases.
2. Building the modules.
Run 'make modules' in the main directory or 'make' in the modules directory.
3. Building the test modules.
Run 'make test' in the main directory or 'make' in the test directory.
After you have create the test modules use 'mktestlinks' in the modules
directory to make links from test modules into modules directory.
4. Building the classes API documentation
Run 'make apidocs' in the main directory. You will need to have kdoc installed.
Alternatively you can just 'make everything' in the main directory which will
build them all.
Running YATE
------------
You can run YATE directly from the build directory - just use 'run' script
from the main directory.
You can also install YATE - then you can run it from anywhere.
On the command line you can use '-v' to increase the verbosity level. If in
doubt run "run --help" (or "yate --help" if installed) to get a list of
possible options. There is also a manual page - "man yate" and read.
While running the following signals and keys are trapped and used:
- SIGTERM and SIGINT (Ctrl-C) will cleanly stop the engine
- SIGHUP and SIGQUIT (Ctrl-\) will reinitialize the modules
Configuring YATE
----------------
Some samples for the configuraton files can be found in the conf.d directory.
Note that you must rename them without the .sample extension or create symlinks
to them.

6
conf.d/.cvsignore Normal file
View File

@ -0,0 +1,6 @@
Makefile
core*
*.conf
*.orig
*~
.*.swp

32
conf.d/Makefile.in Normal file
View File

@ -0,0 +1,32 @@
# Makefile
# This file holds the make rules for the Telephony Engine config files
# override DESTDIR at install time to prefix the install directory
DESTDIR :=
confdir = @sysconfdir@/yate
.PHONY: all
all:
# Install .sample files only if we don't have other versions
.PHONY: install
install:
@mkdir -p "$(DESTDIR)$(confdir)/" && \
lst="`ls -1 @srcdir@/*.conf @srcdir@/*.sample @srcdir@/*.sql | sed 's/\.sample//g; s/[^ ]*\*\.[^ ]*//g' | sort | uniq`" ; \
for s in $$lst; do \
d="$(DESTDIR)$(confdir)/`echo $$s | sed 's,.*/,,'`" ; \
if [ -f "$$d" ]; then \
echo "Not overwriting existing $$d" ; \
else \
test -f "$$s" || s="$$s.sample" ; \
install -m 0644 "$$s" "$$d" ; \
fi ; \
done
.PHONY: uninstall
uninstall:
@-rmdir "$(DESTDIR)$(confdir)" || echo "Remove conf files by hand if you want so"
Makefile: @srcdir@/Makefile.in ../config.status
cd .. && ./config.status

View File

@ -0,0 +1,3 @@
[general]
file=/tmp/cdr.tsv
tabs=true

View File

@ -0,0 +1,82 @@
[general]
; This section sets global variables of the implementation
; debug: int: OpenH323 debug level
;debug=0
; vendor: string: Vendor name
;vendor=Null Team
; product: string: Product name
;product=YATE
; major: int: Major version number
;major=0
; minor: int: Minor version number
;minor=1
; build: int: Build number
;build=1
; status: keyword: Code status: alpha, beta or release
;status=alpha
[codecs]
; This section allows to individually enable or disable the codecs
; g711u: bool: Companded-only G711 mu-law
;g711u=enable
; g711a: bool: Companded-only G711 a-law
;g711a=enable
; gsm0610: bool: European GSM 06.10
;gsm0610=enable
; speexnarrow: bool: Speex narrow
;speexnarrow=enable
; lpc10: bool: LPC 10
;lpc10=enable
[ep]
; Control the endpoint operation of the module
; ep: bool: True if you want to activate the h323 endpoint
ep = true
; alias: string: The alias used by h323 module to connect to gatekeeper
alias = yate
; ident: string: Sets the hostname part of the outgoing e.164 (numeric) aliases
ident = yate
; gkclient: bool: If h323 module endpoint should register to a gatekeeper
gkclient = false
; password: string: Password for h.235 authentification
;password = 1234
; gkip: ipaddress: Set the reported ip aaddress of the gatekeeper
;gkip = 10.10.10.10
; gkname: string: Set the name of the gatekeeper
;gkname = gigi
; How the gatekeeper is located
; If gkclient is true the endpoint will try first to find the gatekeeper by
; using gkip; then, if gkip is unset or is not corect, will try gkname and
; then will try to brodcast in the network.
[incoming]
; This section sets defaults for the incoming H.323 calls
; context: string: Input context
context=default
; called: string: Default number to call
called=8989989

9
conf.d/pgsql.conf.sample Normal file
View File

@ -0,0 +1,9 @@
[general]
tcp = yes
host = localhost
port = 5432
database = yate
user = yate
password =
socket =
priority = 50

64
conf.d/pgsql.sql Normal file
View File

@ -0,0 +1,64 @@
DROP TABLE route;
DROP TABLE preroute;
DROP TABLE cdr;
DROP SEQUENCE route_routeid_seq;
DROP SEQUENCE preroute_prerouteid_seq;
DROP SEQUENCE cdr_cdrid_seq;
CREATE TABLE route (
routeid bigserial,
context varchar(15),
prefix varchar(50),
tehno varchar(20),
data varchar(100)
);
CREATE TABLE preroute (
prerouteid bigserial,
tehno varchar(20),
channel varchar(20),
caller varchar(30),
called varchar(30),
context varchar(15)
);
CREATE TABLE cdr (
cdrid bigserial,
time TIMESTAMP with time zone NOT NULL DEFAULT now(),
channel varchar(20),
caller varchar(30),
called varchar(30),
billtime INTEGER NOT NULL default '0',
ringtime INTEGER NOT NULL default '0',
duration INTEGER NOT NULL default '0',
status varchar(15)
);
INSERT INTO route (context,prefix,tehno,data) VALUES ('paul','1','SIP','jen');
INSERT INTO route (context,prefix,tehno,data) VALUES ('bell','112','SIP','bell');
INSERT INTO route (context,prefix,tehno,data) VALUES ('gigi','4021','ZAP','1');
INSERT INTO route (context,prefix,tehno,data) VALUES ('whatever','40238','ZAP','g1');
INSERT INTO route (context,prefix,tehno,data) VALUES ('default','1','SIP','jen');
INSERT INTO route (context,prefix,tehno,data) VALUES ('default','2','SIP','bell');
INSERT INTO route (context,prefix,tehno,data) VALUES ('default','3','ZAP','g1');
INSERT INTO route (context,prefix,tehno,data) VALUES ('default','4','ZAP','g2');
INSERT INTO route (context,prefix,tehno,data) VALUES ('default','11','ZAP','g11');
INSERT INTO route (context,prefix,tehno,data) VALUES ('default','12','ZAP','g12');
INSERT INTO route (context,prefix,tehno,data) VALUES ('default','13','ZAP','g13');
INSERT INTO route (context,prefix,tehno,data) VALUES ('default','14','ZAP','g14');
INSERT INTO route (context,prefix,tehno,data) VALUES ('default','15','ZAP','g15');
INSERT INTO route (context,prefix,tehno,data) VALUES ('default','16','ZAP','g16');
INSERT INTO route (context,prefix,tehno,data) VALUES ('default','17','ZAP','g17');
INSERT INTO route (context,prefix,tehno,data) VALUES ('default','18','ZAP','g18');
INSERT INTO route (context,prefix,tehno,data) VALUES ('default','19','ZAP','g19');
INSERT INTO preroute (tehno,channel,caller,called,context) VALUES ('Zap','1','5556','','default');
INSERT INTO preroute (tehno,channel,caller,called,context) VALUES ('Zap','1','1','','default');
INSERT INTO preroute (tehno,channel,caller,called,context) VALUES ('Zap','1','2','','bell');
INSERT INTO preroute (tehno,channel,caller,called,context) VALUES ('Zap','1','12','','gigi');
INSERT INTO cdr (channel,caller,called,billtime,ringtime,duration) VALUES ('Zap','1','12','34','10','45');

View File

@ -0,0 +1,28 @@
[priorities]
preroute=80
route=80
[contexts]
sip:.*=sip
iax:.*=iax
^1=from-us
.*=default
[default]
^1\(.*\)$=us-number-1-\1
^40=romania
^800=green
^\(.\)\(..\)\(...\)$=6digit/\1-\2-\3
^.....$=5digit
^....$=4digit
.*=defaultroute/\0
[sip]
.*:\(.*\)@.*=\1
[iax]
.*:\(.*\)@.*=\1
[from-us]
^1=local
.*=defaultroute

22
conf.d/register.sql Normal file
View File

@ -0,0 +1,22 @@
DROP TABLE register;
DROP TABLE routepaid;
DROP SEQUENCE register_registerid_seq;
DROP SEQUENCE routepaid_routepaidid_seq;
CREATE TABLE register (
registerid bigserial,
username varchar(20),
password varchar(20),
e164 varchar(30),
credit varchar(20),
context varchar(15)
);
CREATE TABLE routepaid (
routepaidid bigserial,
context varchar(15),
prefix varchar(50),
tehno varchar(20),
data varchar(100),
price INTEGER NOT NULL default '0'
);

View File

@ -0,0 +1,11 @@
[general]
; This section controls the behaviour of the Remote Manager
; port: int: TCP Port to listen on, 0 to disable module
;port=5038
; addr: ipaddress: IP address to bind to
;addr=127.0.0.1
; header: string: Header string to display on connect
;header=YATE (http://YATE.null.ro) ready.

3
conf.d/test1.conf.sample Normal file
View File

@ -0,0 +1,3 @@
[general]
noisy=0
threads=0

View File

@ -0,0 +1,26 @@
; Warning: all strings are case sensitive
;
; supported keywords (you can always specify numbers directly):
;
; swtype= unknown,ni2,dms100,lucent5e,at&t4ess,euroisdn_e1,euroisdn_t1,ni1
; dialplan= unknown,international,national,local,private
; numplan= unknown,e164,x121,f69,national,private,reserved
; numtype= unknown,international,national,net_specific,subscriber,abbreviated,
; reserved
; presentation= allow_user_not_screened,allow_user_passed,allow_user_failed,
; allow_network,prohibit_user_not_screened,prohibit_user_passed,
; prohibit_user_failed,prohibit_network,not_available
[general]
;buflen=480
;restart=0
;dumpevents=no
[span 1]
first=1
chans=31
dchan=16
isnet=yes
;swtype=unknown
;dialplan=unknown
;presentation=allow_user_not_screened

263
configure.in Normal file
View File

@ -0,0 +1,263 @@
# Process this file with autoconf to produce a configure script.
AC_INIT(YATE, 0.8.1)
AC_CONFIG_SRCDIR([README])
# Checks for programs.
AC_PROG_CXX
AC_PROG_CC
AC_PROG_AWK
# Checks for header files.
AC_HEADER_DIRENT
AC_HEADER_STDC
AC_CHECK_HEADERS([fcntl.h arpa/inet.h netdb.h netinet/in.h sys/ioctl.h sys/socket.h sys/time.h], , [AC_MSG_ERROR([This header file is required.])])
# Checks for typedefs, structures, and compiler characteristics.
AC_C_CONST
AC_C_INLINE
AC_HEADER_TIME
# Checks for library functions.
AC_FUNC_MALLOC
AC_TYPE_SIGNAL
AC_CHECK_FUNCS([gettimeofday inet_ntoa memmove regcomp strerror], , [AC_MSG_ERROR([This function is required.])])
AC_CACHE_SAVE
# Checks for required libraries.
AC_CHECK_LIB([dl], [dlopen], , [AC_MSG_ERROR([This library is required.])])
AC_CHECK_LIB([pthread], [pthread_create], , [AC_MSG_ERROR([This library is required.])])
# Checks for optional libraries.
HAVE_PGSQL=no
PGSQL_INC=""
AC_ARG_WITH(libpq,AC_HELP_STRING([--with-libpq=DIR],[use Postgress SQL from DIR (default /usr)]),[ac_cv_use_libpq=$withval],[ac_cv_use_libpq=/usr])
if [[ "x$ac_cv_use_libpq" != "xno" ]]; then
AC_MSG_CHECKING([for Postgress SQL in $ac_cv_use_libpq])
incpq="$ac_cv_use_libpq/include"
libpq="$ac_cv_use_libpq/lib/libpq.so"
if [[ ! -f "$incpq/libpq-fe.h" ]]; then
incpq="$incpq/pgsql"
fi
if [[ -f "$incpq/libpq-fe.h" -a -f "$libpq" ]]; then
HAVE_PGSQL=yes
PGSQL_INC="-I$incpq"
fi
AC_MSG_RESULT([$HAVE_PGSQL])
fi
AC_SUBST(HAVE_PGSQL)
AC_SUBST(PGSQL_INC)
HAVE_PRI=no
AC_ARG_WITH(libpri,AC_HELP_STRING([--with-libpri],[use ISDN PRI if available (default)]),[ac_cv_use_libpri=$withval],[ac_cv_use_libpri=yes])
if [[ "x$ac_cv_use_libpri" != "xno" ]]; then
AC_CHECK_HEADER(libpri.h, , [ac_cv_use_libpri=no])
fi
if [[ "x$ac_cv_use_libpri" != "xno" ]]; then
AC_CHECK_LIB([pri], [pri_new], [HAVE_PRI=yes])
fi
AC_SUBST(HAVE_PRI)
HAVE_GSM=no
AC_ARG_WITH(libgsm,AC_HELP_STRING([--with-libgsm],[use GSM codec if available (default)]),[ac_cv_use_libgsm=$withval],[ac_cv_use_libgsm=yes])
if [[ "x$ac_cv_use_libgsm" != "xno" ]]; then
AC_CHECK_HEADER(gsm.h, , [ac_cv_use_libgsm=no])
fi
if [[ "x$ac_cv_use_libgsm" != "xno" ]]; then
AC_CHECK_LIB([gsm], [gsm_create], [HAVE_GSM=yes])
fi
AC_SUBST(HAVE_GSM)
HAVE_PWLIB=no
PWLIB_INC=""
PWLIB_LIB=""
PWLIB_RUN=""
AC_ARG_WITH(pwlib,AC_HELP_STRING([--with-pwlib=DIR],[use Pwlib from DIR (default /usr)]),[ac_cv_use_pwlib=$withval],[ac_cv_use_pwlib=/usr])
if [[ "x$ac_cv_use_pwlib" != "xno" ]]; then
AC_MSG_CHECKING([for Pwlib in $ac_cv_use_pwlib])
verpw=`ptlib-config --version 2>/dev/null`
# try first installed directory
incpw="$ac_cv_use_pwlib/include/ptlib.h"
libpw="$ac_cv_use_pwlib/lib/libpt.so"
PWLIB_INC="-I$ac_cv_use_pwlib/include/ptlib"
if [[ "$verpw" '<' "1.6.0" ]]; then
PWLIB_INC="-I$ac_cv_use_pwlib/include/ptlib/unix/ptlib -I$ac_cv_use_pwlib/include/ptlib/unix $PWLIB_INC"
fi
if [[ -f "$incpw" -a -f "$libpw" ]]; then
HAVE_PWLIB=installed
PWLIB_LIB="-L$ac_cv_use_pwlib/lib -lpt"
else
# try source directory style
libpw=`echo "$ac_cv_use_pwlib/lib/"libpt*r.so`
if [[ -f "$incpw" -a -f "$libpw" ]]; then
HAVE_PWLIB=sources
PWLIB_LIB="-L$ac_cv_use_pwlib/lib -l`echo "$libpw"|sed 's,^.*/lib,,; s,\.so$,,'`"
PWLIB_RUN=":$ac_cv_use_pwlib/lib"
fi
fi
AC_MSG_RESULT([$HAVE_PWLIB $verpw])
fi
HAVE_H323=no
H323_INC=""
H323_LIB=""
H323_RUN=""
AC_ARG_WITH(openh323,AC_HELP_STRING([--with-openh323=DIR],[use OpenH323 from DIR (default /usr)]),[ac_cv_use_openh323=$withval],[ac_cv_use_openh323=/usr])
if [[ "x$HAVE_PWLIB" != "xno" -a "x$ac_cv_use_openh323" != "xno" ]]; then
AC_MSG_CHECKING([for OpenH323 in $ac_cv_use_openh323])
# try first installed directory
inc323="$ac_cv_use_openh323/include/openh323/h323.h"
lib323="$ac_cv_use_openh323/lib/libopenh323.so"
if [[ -f "$inc323" -a -f "$lib323" ]]; then
HAVE_H323=installed
H323_INC="-I$ac_cv_use_openh323/include/openh323"
H323_LIB="-L$ac_cv_use_openh323/lib -lopenh323"
else
# try source directory style
inc323="$ac_cv_use_openh323/include/h323.h"
lib323=`echo "$ac_cv_use_openh323/lib/"libh323*r.so`
if [[ -f "$inc323" -a -f "$lib323" ]]; then
HAVE_H323=sources
H323_INC="-I$ac_cv_use_openh323/include"
H323_LIB="-L$ac_cv_use_openh323/lib -l`echo "$lib323"|sed 's,^.*/lib,,; s,\.so$,,'`"
H323_RUN=":$ac_cv_use_openh323/lib"
fi
fi
AC_MSG_RESULT([$HAVE_H323])
fi
if [[ "x$HAVE_H323" != "xno" ]]; then
H323_INC="$PWLIB_INC $H323_INC"
H323_LIB="$PWLIB_LIB $H323_LIB"
H323_RUN="$PWLIB_RUN$H323_RUN"
fi
AC_SUBST(HAVE_H323)
AC_SUBST(H323_INC)
AC_SUBST(H323_LIB)
AC_SUBST(H323_RUN)
HAVE_ORTP=no
ORTP_INC=""
ORTP_LIB=""
AC_ARG_WITH(libortp,AC_HELP_STRING([--with-libortp=DIR],[use oRTP from DIR (default /usr)]),[ac_cv_use_libortp=$withval],[ac_cv_use_libortp=/usr])
if [[ "x$ac_cv_use_libortp" != "xno" ]]; then
AC_MSG_CHECKING([for oRTP in $ac_cv_use_libortp])
incor="$ac_cv_use_libortp/include/ortp"
libor="$ac_cv_use_libortp/lib/libortp.so"
if [[ -f "$incor/ortp.h" -a -f "$libor" ]]; then
HAVE_ORTP=yes
ORTP_INC="-I$incor"
ORTP_LIB="-L$ac_cv_use_libortp/lib -lortp"
fi
AC_MSG_RESULT([$HAVE_ORTP])
fi
AC_SUBST(HAVE_ORTP)
AC_SUBST(ORTP_INC)
AC_SUBST(ORTP_LIB)
HAVE_EXOSIP=no
EXOSIP_INC=""
EXOSIP_LIB=""
AC_ARG_WITH(libexosip,AC_HELP_STRING([--with-libexosip=DIR],[use eXosip from DIR (default /usr)]),[ac_cv_use_libexosip=$withval],[ac_cv_use_libexosip=/usr])
if [[ "x$ac_cv_use_libexosip" != "xno" ]]; then
AC_MSG_CHECKING([for eXosip in $ac_cv_use_libexosip])
incexo="$ac_cv_use_libexosip/include/eXosip"
libexo="$ac_cv_use_libexosip/lib/libeXosip.so"
if [[ -f "$incexo/eXosip.h" -a -f "$libexo" ]]; then
HAVE_EXOSIP=yes
EXOSIP_INC="-I$incexo"
EXOSIP_LIB="-L$ac_cv_use_libexosip/lib -leXosip"
fi
AC_MSG_RESULT([$HAVE_EXOSIP])
fi
AC_SUBST(HAVE_EXOSIP)
AC_SUBST(EXOSIP_INC)
AC_SUBST(EXOSIP_LIB)
HAVE_GLIB2=no
GLIB2_INC=""
GLIB2_LIB=""
AC_ARG_WITH(libglib2,AC_HELP_STRING([--with-libglib2=DIR],[use Glib 2.0 from DIR (default /usr)]),[ac_cv_use_libglib2=$withval],[ac_cv_use_libglib2=yes])
if [[ "x$ac_cv_use_libglib2" = "xyes" ]]; then
AC_MSG_CHECKING([for Glib 2.0 using pkg-config])
verg2=`pkg-config --modversion glib-2.0 2>/dev/null`
incg2=`pkg-config --cflags-only-I glib-2.0 2>/dev/null`
libg2=`pkg-config --libs glib-2.0 2>/dev/null`
if [[ "x$incg2" != "x" -a "x$libg2" != "x" ]]; then
HAVE_GLIB2=yes
GLIB2_INC="$incg2"
GLIB2_LIB="$libg2"
ac_cv_use_libglib2="no"
else
ac_cv_use_libglib2="/usr"
verg2="no"
fi
AC_MSG_RESULT([$verg2])
fi
if [[ "x$ac_cv_use_libglib2" != "xno" ]]; then
AC_MSG_CHECKING([for Glib 2.0 in $ac_cv_use_libglib2])
incg2="$ac_cv_use_libglib2"
libg2="$ac_cv_use_libglib2/lib"
if [[ -f "$incg2/include/glib-2.0/glib.h" -a -f "$libg2/libglib-2.0.so" ]]; then
HAVE_GLIB2=yes
GLIB2_INC="-I$incg2/include/glib-2.0 -I$libg2/glib-2.0/include"
GLIB2_LIB="-L$libg2 -lglib-2.0"
fi
AC_MSG_RESULT([$HAVE_GLIB2])
fi
AC_SUBST(HAVE_GLIB2)
AC_SUBST(GLIB2_INC)
AC_SUBST(GLIB2_LIB)
HAVE_IAX2=no
IAX2_INC=""
IAX2_LIB=""
AC_ARG_WITH(libIAX2,AC_HELP_STRING([--with-libiax2=DIR],[use IAX 2 from DIR (default /usr)]),[ac_cv_use_libiax2=$withval],[ac_cv_use_libiax2=yes])
if [[ "x$ac_cv_use_libiax2" = "xyes" ]]; then
AC_MSG_CHECKING([for IAX 2 using iax-config])
veri2=`iax-config --version 2>/dev/null`
inci2=`iax-config --cflags 2>/dev/null`
libi2=`iax-config --libs 2>/dev/null`
if [[ "x$inci2" != "x" -a "x$libi2" != "x" ]]; then
HAVE_IAX2=yes
IAX2_INC="$inci2"
IAX2_LIB="$libi2"
ac_cv_use_libiax2="no"
else
ac_cv_use_libiax2="/usr"
verg2="no"
fi
AC_MSG_RESULT([$veri2])
fi
if [[ "x$ac_cv_use_libiax2" != "xno" ]]; then
AC_MSG_CHECKING([for IAX 2 in $ac_cv_use_libiax2])
inci2="$ac_cv_use_libiax2"
libi2="$ac_cv_use_libiax2/lib"
if [[ -f "$inci2/include/iax/iax2.h" -a -f "$libi2/libiax.so" ]]; then
HAVE_IAX2=yes
IAX2_INC="-I$inci2/include/iax"
IAX2_LIB="-L$libi2 -liax"
fi
AC_MSG_RESULT([$HAVE_IAX2])
fi
AC_SUBST(HAVE_IAX2)
AC_SUBST(IAX2_INC)
AC_SUBST(IAX2_LIB)
MODULE_CFLAGS="-fno-exceptions -fno-check-new -frtti -fPIC"
MODULE_LDFLAGS="-export-dynamic -shared -Wl,--retain-symbols-file,/dev/null"
AC_SUBST(MODULE_CFLAGS)
AC_SUBST(MODULE_LDFLAGS)
AC_CONFIG_FILES([yate.spec
yate.pc
Makefile
modules/Makefile
conf.d/Makefile
test/Makefile])
AC_CONFIG_FILES([yate-config],[chmod +x yate-config])
AC_CONFIG_FILES([run],[chmod +x run])
AC_OUTPUT

4
docs/.cvsignore Normal file
View File

@ -0,0 +1,4 @@
core*
*.orig
*~
.*.swp

5
docs/api/.cvsignore Normal file
View File

@ -0,0 +1,5 @@
core*
*.html
*.orig
*~
.*.swp

18
docs/dataflow.html Normal file
View File

@ -0,0 +1,18 @@
<html>
<head>
<title>YATE - Data flows</title>
</head>
<body>
<h1 align="center">YATE</h1>
<p align="center">Data flows</p>
<h2>Data blocks</h2>
<p></p>
<h2>Data nodes</h2>
<h2>Data endpoints</h2>
<h2>Links</h2>
<a href="index.html">Overview</a>.<br />
The <a href="api/TelEngine__DataBlock.html">DataBlock</a> class.<br />
The <a href="api/TelEngine__DataNode.html">DataNode</a> class.<br />
The <a href="api/TelEngine__DataEndpoint.html">DataEndpoint</a> class.<br />
</body>
</html>

119
docs/extmodule.html Normal file
View File

@ -0,0 +1,119 @@
<html>
<head>
<title>YATE - External module</title>
</head>
<body>
The work on this document is still in progress.
Please do not use it as reference, the specifications are likely to change.
<h1>External module command flow</h1>
<h2>File handles used</h2>
The external user application or script comunicates with the module trough
several file descriptors:<br />
0 (stdin) - Carries commands and notifications from engine to application<br />
1 (stdout) - Carries commands and answers from application to the engine<br />
2 (stderr) - Has the usual meaning of reporting errors and is directed to the engine's stderr or logfile<br />
3 (optional) - Transports audio data from the engine to the application<br />
4 (optional) - Transports audio data from the application to the engine<br />
File descriptors 3 and 4 are open only for audio capable applications.<br />
<h2>Format of commands and notifications</h2>
In the following description the _ (underscore) character is used to denote
the TAB character (\t, ^I, decimal 9) used as element separator.<br />
Words enclosed in &lt;angle brackets&gt; must be replaced with the proper value.<br />
Elements in [square brackets] are optional. An ellipsis ... denotes optional
repetition of the last element.<br />
Every command is sent on its own newline (\n, ^J, decimal 10) delimited line.<br />
Any value that contains special characters (ASCII code lower than 32) MUST have
them converted to %&lt;hexcode&gt; where &lt;hexcode&gt; is the 2 character hexadecimal
representation of that ASCII code. The % character itself MUST be converted to
its %25 representation. Characters with codes higher than 32 (except %) SHOULD
not be escaped but may be so. An %-escaped code may be received instead of an
unescaped character anywhere except in the initial keyword or the delimiting
TAB characters. Anywhere in the line except the initial keyword a % character
not followed by 2 hexadecimal digits is an error.<br />
<p><b>Keyword: %%=error</b><br />
%%=error_&lt;original&gt;<br />
The engine sends this notification as answer to a syntactically incorrect line
it received from the application.<br />
&lt;original&gt; - the original line exactly as received (not escaped or something)<br />
</p>
<p><b>Keyword: %%=init</b><br />
%%=init<br />
This command is sent to the other party requesting it to (re)initialize.<br />
</p>
<p><b>Keyword: %%=halt</b><br />
%%=halt[_&lt;exitcode&gt;]<br />
Command from application to the engine asking it to terminate. An optional
exit code may be provided.<br />
&lt;exitcode&gt; - positive numeric engine exit code<br />
</p>
<p><b>Keyword: %%=output</b><br />
%%=output_&lt;message&gt;<br />
Asks the engine to display a message in its console output.<br />
&lt;message&gt; - message line to display as-is<br />
</p>
<p><b>Keyword: %%=debug</b><br />
%%=debug_[&lt;facility&gt;]_&lt;level&gt;_&lt;message&gt;<br />
Asks the engine to emit a debugging message to the console output.<br />
&lt;facility&gt; - optional facility text to display<br />
&lt;level&gt; - numeric debug level (0-9) at which to output<br />
&lt;message&gt; - debug message to display<br />
</p>
<p><b>Keyword: %%&gt;message</b><br />
%%&gt;message_&lt;id&gt;_&lt;name&gt;_&lt;retvalue&gt;[_&lt;key&gt;=&lt;value&gt;...]<br />
This is sent by a party (engine or application) to ask the other to run a
message trough its handlers. The local message must be held until an answer is
received.<br />
&lt;id&gt; - obscure unique message ID string generated by the sender<br />
&lt;name&gt; - name of the message<br />
&lt;retvalue&gt; - default textual return value of the message<br />
&lt;key&gt;=&lt;value&gt; - enumeration of the key-value pairs of the message<br />
</p>
<p><b>Keyword: %%&lt;message</b><br />
%%&lt;message_&lt;id&gt;_&lt;processed&gt;_[&lt;name&gt;]_&lt;retvalue&gt;[_&lt;key&gt;=&lt;value&gt;...]<br />
One of this answer is required for every received message (%%&gt;message). When a
party gets a line in this format it should paste the provided values back into
the message and let it continue.<br />
&lt;id&gt; - same message ID string received trough %%&gt;message<br />
&lt;processed&gt; - boolean (&quot;true&quot; or &quot;false&quot;) indication if the message has been
processed or it should be passed to the next handler<br />
&lt;name&gt; - new name of the message, if empty keep unchanged<br />
&lt;retvalue&gt; - new textual return value of the message<br />
&lt;key&gt;=&lt;value&gt; - new key-value pairs to set in the message; delete the key if
the value is an empty string<br />
</p>
<p><b>Keyword: %%&gt;install</b><br />
%%&gt;install_&lt;name&gt;[_&lt;priority&gt;]<br />
Always from the application to the engine, requests the installing of a message
handler<br />
&lt;name&gt; - name of the messages for that a handler should be installed<br />
&lt;priority&gt; - priority, use default if missing<br />
</p>
<p><b>Keyword: %%&lt;install</b><br />
%%&lt;install_&lt;name&gt;_&lt;priority&gt;<br />
Confirmation from engine to the application that the handler has been installed
properly or not.<br />
&lt;name&gt; - name of the messages asked to handle<br />
&lt;priority&gt; - priority of the installed handler, -1 if it failed<br />
</p>
</body>
</html>

23
docs/index.html Normal file
View File

@ -0,0 +1,23 @@
<html>
<head>
<title>YATE - Yet Another Telephony Engine</title>
</head>
<body>
<h1 align="center">YATE</h1>
<p align="center">Yet Another Telephony Engine</p>
<h2>Purpose</h2>
<p>YATE is a telephony engine designed to implement PBX and IVR solutions for
small to large scale projects.</p>
<h2>Architecture</h2>
<p>YATE is based mainly on messages and data flows. Messages are used for
signalling and control while the data flows carry the voice or other media
data.</p>
<h2>API Documentation</h2>
<a href="api/index.html">Class list</a> also available in
<a href="api/index-long.html">annotated format</a>.<br />
<a href="api/hier.html">Class hierarcy</a><br />
<a href="messages.html">Messages</a><br />
<a href="dataflow.html">Data flows</a><br />
<!-- <a href="extmodule.html">External module</a><br /> -->
</body>
</html>

16
docs/messages.html Normal file
View File

@ -0,0 +1,16 @@
<html>
<head>
<title>YATE - Standard and typical messages</title>
</head>
<body>
<h1 align="center">YATE</h1>
<p align="center">Standard and typical messages</p>
<h2>Message format</h2>
<p>Messages in YATE</p>
<h2>Standard messages</h2>
<h2>Other messages</h2>
<h2>Links</h2>
<a href="index.html">Overview</a>.<br />
The <a href="api/TelEngine__Message.html">Message</a> class.<br />
</body>
</html>

204
engine/Configuration.cpp Normal file
View File

@ -0,0 +1,204 @@
/**
* Configuration.cpp
* This file is part of the YATE Project http://YATE.null.ro
*/
#include "telengine.h"
#include <stdio.h>
#include <string.h>
using namespace TelEngine;
Configuration::Configuration()
{
}
Configuration::Configuration(const char *filename)
: String(filename)
{
load();
}
ObjList *Configuration::getSectHolder(const String &sect) const
{
if (sect.null())
return 0;
ObjList *l = const_cast<ObjList *>(&m_sections);
for (;l;l=l->next()) {
NamedList *n = static_cast<NamedList *>(l->get());
if (n && *n == sect)
return l;
}
return 0;
}
ObjList *Configuration::makeSectHolder(const String &sect)
{
if (sect.null())
return 0;
ObjList *l = getSectHolder(sect);
if (!l)
l = m_sections.append(new NamedList(sect));
return l;
}
NamedList *Configuration::getSection(unsigned int index) const
{
const ObjList *l = m_sections[index];
return l ? static_cast<NamedList *>(l->get()) : 0;
}
NamedList *Configuration::getSection(const String &sect) const
{
ObjList *l = getSectHolder(sect);
return l ? static_cast<NamedList *>(l->get()) : 0;
}
NamedString *Configuration::getKey(const String &sect, const String &key) const
{
NamedList *l = getSection(sect);
return l ? l->getParam(key) : 0;
}
const char *Configuration::getValue(const String &sect, const String &key, const char *defvalue) const
{
const NamedString *s = getKey(sect,key);
return s ? s->c_str() : defvalue;
}
int Configuration::getIntValue(const String &sect, const String &key, int defvalue) const
{
const NamedString *s = getKey(sect,key);
return s ? s->toInteger(defvalue) : defvalue;
}
int Configuration::getIntValue(const String &sect, const String &key, const TokenDict *tokens, int defvalue) const
{
const NamedString *s = getKey(sect,key);
return s ? s->toInteger(tokens,defvalue) : defvalue;
}
bool Configuration::getBoolValue(const String &sect, const String &key, bool defvalue) const
{
const NamedString *s = getKey(sect,key);
return s ? s->toBoolean(defvalue) : defvalue;
}
void Configuration::clearSection(const char *sect)
{
if (sect) {
ObjList *l = getSectHolder(sect);
if (l)
l->remove();
}
else
m_sections.clear();
}
void Configuration::clearKey(const String &sect, const String &key)
{
NamedList *l = getSection(sect);
if (l)
l->clearParam(key);
}
void Configuration::setValue(const String &sect, const char *key, const char *value)
{
#ifdef DEBUG
Debug(DebugInfo,"Configuration::setValue(\"%s\",\"%s\",\"%s\")",sect.c_str(),key,value);
#endif
ObjList *l = makeSectHolder(sect);
if (!l)
return;
NamedList *n = static_cast<NamedList *>(l->get());
if (n)
n->setParam(key,value);
}
void Configuration::setValue(const String &sect, const char *key, int value)
{
char buf[32];
::sprintf(buf,"%d",value);
setValue(sect,key,buf);
}
void Configuration::setValue(const String &sect, const char *key, bool value)
{
setValue(sect,key,value ? "true" : "false");
}
bool Configuration::load()
{
m_sections.clear();
if (null())
return false;
FILE *f = ::fopen(c_str(),"r");
if (f) {
String sect;
for (;;) {
char buf[1024];
if (!::fgets(buf,sizeof(buf),f))
break;
char *pc = ::strchr(buf,'\r');
if (pc)
*pc = 0;
pc = ::strchr(buf,'\n');
if (pc)
*pc = 0;
pc = buf;
while (*pc == ' ' || *pc == '\t')
pc++;
switch (*pc) {
case 0:
case ';':
continue;
}
String s(pc);
if (s[0] == '[') {
int r = s.find(']');
if (r > 0) {
sect = s.substr(1,r-1);
makeSectHolder(sect);
}
continue;
}
int q = s.find('=');
if (q > 0)
setValue(sect,s.substr(0,q).trimBlanks(),s.substr(q+1).trimBlanks());
}
::fclose(f);
return true;
}
return false;
}
bool Configuration::save() const
{
if (null())
return false;
FILE *f = ::fopen(c_str(),"w");
if (f) {
ObjList *ol = const_cast<ObjList *>(&m_sections);
for (;ol;ol=ol->next()) {
NamedList *nl = static_cast<NamedList *>(ol->get());
if (!nl)
continue;
::fprintf(f,"[%s]\n",nl->c_str());
unsigned int n = nl->length();
for (unsigned int i = 0; i < n; i++) {
NamedString *ns = nl->getParam(i);
if (ns) {
const char *v = ns->c_str();
if (!v)
v = "";
::fprintf(f,"%s=%s\n",ns->name().c_str(),v);
}
}
}
::fclose(f);
return true;
}
return false;
}

543
engine/DataBlock.cpp Normal file
View File

@ -0,0 +1,543 @@
/**
* DataBlock.cpp
* This file is part of the YATE Project http://YATE.null.ro
*/
#include "telephony.h"
#include <string.h>
#include <stdlib.h>
namespace TelEngine{
extern "C" {
#include "tables/all.h"
}
class ThreadedSourcePrivate : public Thread
{
public:
ThreadedSourcePrivate(ThreadedSource *source, const char *name)
: Thread(name), m_source(source) { }
protected:
virtual void run()
{ m_source->run(); }
virtual void cleanup()
{ m_source->m_thread = 0; m_source->cleanup(); }
private:
ThreadedSource *m_source;
};
class SimpleTranslator : public DataTranslator
{
public:
SimpleTranslator(const String &sFormat, const String &dFormat)
: DataTranslator(sFormat,dFormat) { }
virtual void Consume(const DataBlock &data)
{
ref();
if (getTransSource()) {
DataBlock oblock;
if (oblock.convert(data, m_format, getTransSource()->getFormat()))
getTransSource()->Forward(oblock);
}
deref();
}
};
};
using namespace TelEngine;
DataBlock::DataBlock()
: m_data(0), m_length(0)
{
}
DataBlock::DataBlock(const DataBlock &value)
: m_data(0), m_length(0)
{
assign(value.data(),value.length());
}
DataBlock::DataBlock(void *value, unsigned int len, bool copyData)
: m_data(0), m_length(0)
{
assign(value,len,copyData);
}
DataBlock::~DataBlock()
{
clear();
}
void DataBlock::clear(bool deleteData)
{
m_length = 0;
if (m_data) {
void *data = m_data;
m_data = 0;
if (deleteData)
::free(data);
}
}
DataBlock& DataBlock::assign(void *value, unsigned int len, bool copyData)
{
if ((value != m_data) || (len != m_length)) {
void *odata = m_data;
m_length = 0;
m_data = 0;
if (len) {
if (copyData) {
void *data = ::malloc(len);
if (value)
::memcpy(data,value,len);
else
::memset(data,0,len);
m_data = data;
}
else
m_data = value;
if (m_data)
m_length = len;
}
if (odata && (odata != m_data))
::free(odata);
}
return *this;
}
void DataBlock::truncate(unsigned int len)
{
if (!len)
clear();
else if (len < m_length)
assign(m_data,len);
}
void DataBlock::cut(int len)
{
if (!len)
return;
int ofs = 0;
if (len < 0)
ofs = len = -len;
if ((unsigned)len >= m_length) {
clear();
return;
}
assign(ofs+(char *)m_data,m_length - len);
}
DataBlock& DataBlock::operator=(const DataBlock &value)
{
assign(value.data(),value.length());
return *this;
}
void DataBlock::append(const DataBlock &value)
{
if (m_length) {
if (value.length()) {
unsigned int len = m_length+value.length();
void *data = ::malloc(len);
::memcpy(data,m_data,m_length);
::memcpy(m_length+(char*)data,value.data(),value.length());
assign(data,len,false);
}
}
else
assign(value.data(),value.length());
}
void DataBlock::insert(const DataBlock &value)
{
unsigned int vl = value.length();
if (m_length) {
if (vl) {
unsigned int len = m_length+vl;
void *data = ::malloc(len);
::memcpy(data,value.data(),vl);
::memcpy(vl+(char*)data,m_data,m_length);
assign(data,len,false);
}
}
else
assign(value.data(),vl);
}
bool DataBlock::convert(const DataBlock &src, const String &sFormat,
const String &dFormat, unsigned maxlen)
{
if (sFormat == dFormat) {
operator=(src);
return true;
}
unsigned sl = 0, dl = 0;
void *ctable = 0;
if (sFormat == "slin") {
sl = 2;
dl = 1;
if (dFormat == "alaw")
ctable = s2a;
else if (dFormat == "mulaw")
ctable = s2u;
}
else if (sFormat == "alaw") {
sl = 1;
if (dFormat == "mulaw") {
dl = 1;
ctable = a2u;
}
else if (dFormat == "slin") {
dl = 2;
ctable = a2s;
}
}
else if (sFormat == "mulaw") {
sl = 1;
if (dFormat == "alaw") {
dl = 1;
ctable = u2a;
}
else if (dFormat == "slin") {
dl = 2;
ctable = u2s;
}
}
clear();
if (!ctable)
return false;
unsigned len = src.length();
if (maxlen && (maxlen < len))
len = maxlen;
len /= sl;
if (!len)
return true;
assign(0,len*dl);
if ((sl == 1) && (dl == 1)) {
unsigned char *s = (unsigned char *) src.data();
unsigned char *d = (unsigned char *) data();
unsigned char *c = (unsigned char *) ctable;
while (len--)
*d++ = c[*s++];
}
else if ((sl == 1) && (dl == 2)) {
unsigned char *s = (unsigned char *) src.data();
unsigned short *d = (unsigned short *) data();
unsigned short *c = (unsigned short *) ctable;
while (len--)
*d++ = c[*s++];
}
else if ((sl == 2) && (dl == 1)) {
unsigned short *s = (unsigned short *) src.data();
unsigned char *d = (unsigned char *) data();
unsigned char *c = (unsigned char *) ctable;
while (len--)
*d++ = c[*s++];
}
return true;
}
void DataSource::Forward(const DataBlock &data)
{
Lock lock(m_mutex);
ref();
ObjList *l = &m_consumers;
for (; l; l=l->next()) {
DataConsumer *c = static_cast<DataConsumer *>(l->get());
if (c)
c->Consume(data);
}
deref();
}
bool DataSource::attach(DataConsumer *consumer)
{
#ifdef DEBUG
Debug(DebugInfo,"DataSource [%p] attaching consumer [%p]",this,consumer);
#endif
Lock lock(m_mutex);
consumer->ref();
if (consumer->getConnSource())
consumer->getConnSource()->detach(consumer);
m_consumers.append(consumer);
consumer->setSource(this);
return true;
}
bool DataSource::detach(DataConsumer *consumer)
{
#ifdef DEBUG
Debug(DebugInfo,"DataSource [%p] detaching consumer [%p]",this,consumer);
#endif
Lock lock(m_mutex);
DataConsumer *temp = static_cast<DataConsumer *>(m_consumers.remove(consumer,false));
if (temp) {
temp->setSource(0);
temp->deref();
return true;
}
return false;
}
DataEndpoint::~DataEndpoint()
{
disconnect();
setSource();
setConsumer();
}
bool DataEndpoint::connect(DataEndpoint *peer)
{
Debug(DebugInfo,"DataEndpoint peer address is [%p]",peer);
if (!peer) {
disconnect();
return false;
}
if (peer == m_peer)
return true;
ref();
disconnect();
peer->ref();
peer->disconnect();
bool native = (name() == peer->name()) && nativeConnect(peer);
if (!native) {
DataSource *s = getSource();
DataConsumer *c = peer->getConsumer();
if (s && c)
DataTranslator::attachChain(s,c);
s = peer->getSource();
c = getConsumer();
if (s && c)
DataTranslator::attachChain(s,c);
}
m_peer = peer;
peer->setPeer(this);
connected();
return true;
}
void DataEndpoint::disconnect()
{
if (!m_peer)
return;
DataSource *s = getSource();
DataConsumer *c = m_peer->getConsumer();
if (s && c)
DataTranslator::detachChain(s,c);
s = m_peer->getSource();
c = getConsumer();
if (s && c)
DataTranslator::detachChain(s,c);
DataEndpoint *temp = m_peer;
m_peer = 0;
temp->setPeer(0);
temp->deref();
disconnected();
deref();
}
void DataEndpoint::setPeer(DataEndpoint *peer)
{
m_peer = peer;
if (m_peer)
connected();
else
disconnected();
}
void DataEndpoint::setSource(DataSource *source)
{
if (source == m_source)
return;
DataConsumer *consumer = m_peer ? m_peer->getConsumer() : 0;
DataSource *temp = m_source;
if (source) {
source->ref();
if (consumer)
DataTranslator::attachChain(source,consumer);
}
m_source = source;
if (temp) {
if (consumer)
DataTranslator::detachChain(temp,consumer);
temp->deref();
}
}
void DataEndpoint::setConsumer(DataConsumer *consumer)
{
if (consumer == m_consumer)
return;
DataSource *source = m_peer ? m_peer->getSource() : 0;
DataConsumer *temp = m_consumer;
if (consumer) {
consumer->ref();
if (source)
DataTranslator::attachChain(source,consumer);
}
m_consumer = consumer;
if (temp) {
if (source)
DataTranslator::detachChain(source,temp);
temp->deref();
}
}
ThreadedSource::~ThreadedSource()
{
if (m_thread)
delete m_thread;
}
void ThreadedSource::start(const char *name)
{
if (!m_thread)
m_thread = new ThreadedSourcePrivate(this,name);
}
void ThreadedSource::cleanup()
{
}
DataTranslator::DataTranslator(const char *sFormat, const char *dFormat)
: DataConsumer(sFormat)
{
m_tsource = new DataSource(dFormat);
m_tsource->setTranslator(this);
}
DataTranslator::DataTranslator(const char *sFormat, DataSource *source)
: DataConsumer(sFormat), m_tsource(source)
{
m_tsource->setTranslator(this);
}
DataTranslator::~DataTranslator()
{
DataSource *temp = m_tsource;
m_tsource = 0;
if (temp) {
temp->setTranslator(0);
temp->deref();
}
}
Mutex DataTranslator::s_mutex;
ObjList DataTranslator::s_factories;
void DataTranslator::install(TranslatorFactory *factory)
{
s_mutex.lock();
s_factories.append(factory);
s_mutex.unlock();
}
void DataTranslator::uninstall(TranslatorFactory *factory)
{
s_mutex.lock();
s_factories.remove(factory,false);
s_mutex.unlock();
}
DataTranslator *DataTranslator::create(const String &sFormat, const String &dFormat)
{
if (sFormat == dFormat) {
Debug(DebugInfo,"Not creating identity DataTranslator for \"%s\"",sFormat.c_str());
return 0;
}
DataTranslator *trans = 0;
s_mutex.lock();
ObjList *l = &s_factories;
for (; l; l=l->next()) {
TranslatorFactory *f = static_cast<TranslatorFactory *>(l->get());
if (f) {
trans = f->create(sFormat,dFormat);
if (trans)
break;
}
}
s_mutex.unlock();
if (!trans) {
DataBlock empty,probe;
if (probe.convert(empty,sFormat,dFormat))
trans = new SimpleTranslator(sFormat,dFormat);
}
if (trans)
Debug(DebugAll,"Created DataTranslator [%p] for \"%s\" -> \"%s\"",
trans,sFormat.c_str(),dFormat.c_str());
else
Debug(DebugWarn,"No DataTranslator created for \"%s\" -> \"%s\"",
sFormat.c_str(),dFormat.c_str());
return trans;
}
bool DataTranslator::attachChain(DataSource *source, DataConsumer *consumer)
{
if (!source || !consumer)
return false;
bool retv = false;
if (source->getFormat() == consumer->getFormat()) {
source->attach(consumer);
retv = true;
}
else {
// TODO: try to create a chain of translators, recurse if we have to
DataTranslator *trans = create(source->getFormat(),consumer->getFormat());
if (trans) {
trans->getTransSource()->attach(consumer);
source->attach(trans);
retv = true;
}
}
#ifndef NDEBUG
Debug(DebugAll,"DataTranslator::attachChain [%p] \"%s\" -> [%p] \"%s\" %s",
source,source->getFormat().c_str(),consumer,consumer->getFormat().c_str(),
retv ? "succeeded" : "failed");
#endif
return retv;
}
bool DataTranslator::detachChain(DataSource *source, DataConsumer *consumer)
{
Debugger debug(DebugAll,"DataTranslator::detachChain","(%p,%p)",source,consumer);
if (!source || !consumer)
return false;
if (source->detach(consumer))
return true;
DataSource *tsource = consumer->getConnSource();
if (tsource) {
DataTranslator *trans = tsource->getTranslator();
if (trans && detachChain(source,trans)) {
trans->deref();
return true;
}
}
Debug(DebugWarn,"DataTranslator failed to detach chain [%p] -> [%p]",source,consumer);
return false;
}

532
engine/Engine.cpp Normal file
View File

@ -0,0 +1,532 @@
/**
* Engine.cpp
* This file is part of the YATE Project http://YATE.null.ro
*/
#include "telengine.h"
#include "yatepaths.h"
#include <dirent.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <dlfcn.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
namespace TelEngine {
class EnginePrivate : public Thread
{
public:
EnginePrivate()
: Thread("EnginePrivate")
{ count++; }
~EnginePrivate()
{ count--; }
virtual void run();
static int count;
};
};
using namespace TelEngine;
#ifndef MOD_PATH
#define MOD_PATH "./modules"
#endif
#ifndef CFG_PATH
#define CFG_PATH "./conf.d"
#endif
#define DLL_SUFFIX ".yate"
#define CFG_SUFFIX ".conf"
static unsigned long long s_nextinit = 0;
static bool s_makeworker = true;
static bool s_keepclosing = false;
static void sighandler(int signal)
{
switch (signal) {
case SIGHUP:
case SIGQUIT:
if (s_nextinit <= Time::now())
Engine::init();
s_nextinit = Time::now() + 2000000;
break;
case SIGINT:
case SIGTERM:
Engine::halt(0);
break;
}
}
String Engine::s_cfgpath(CFG_PATH);
String Engine::s_cfgsuffix(CFG_SUFFIX);
String Engine::s_modpath(MOD_PATH);
String Engine::s_modsuffix(DLL_SUFFIX);
Engine *Engine::s_self = 0;
int Engine::s_haltcode = -1;
bool Engine::s_init = false;
bool Engine::s_dynplugin = false;
int Engine::s_maxworkers = 10;
int EnginePrivate::count = 0;
ObjList plugins;
class SLib : public GenObject
{
public:
virtual ~SLib();
static SLib *load(const char *file);
private:
SLib(void *handle, const char *file);
const char *m_file;
void *m_handle;
};
SLib::SLib(void *handle, const char *file)
: m_handle(handle)
{
#ifdef DEBUG
Debug(DebugAll,"SLib::SLib(%p,\"%s\") [%p]",handle,file,this);
#endif
}
SLib::~SLib()
{
#ifdef DEBUG
Debugger debug("SLib::~SLib()"," [%p]",this);
#endif
int err = dlclose(m_handle);
if (err)
Debug(DebugGoOn,"Error %d on dlclose(%p)",err,m_handle);
else if (s_keepclosing) {
int tries;
for (tries=0; tries<10; tries++)
if (dlclose(m_handle))
break;
if (tries)
Debug(DebugGoOn,"Made %d attempts to dlclose(%p)",tries,m_handle);
}
}
SLib *SLib::load(const char *file)
{
#ifdef DEBUG
Debugger debug("SLib::load","(\"%s\")",file);
#endif
void *handle = ::dlopen(file,RTLD_NOW);
if (handle)
return new SLib(handle,file);
Debug(DebugWarn,dlerror());
return 0;
}
class EngineStatusHandler : public MessageHandler
{
public:
EngineStatusHandler() : MessageHandler("status",0) { }
virtual bool received(Message &msg);
};
bool EngineStatusHandler::received(Message &msg)
{
const char *sel = msg.getValue("module");
if (sel && ::strcmp(sel,"engine"))
return false;
msg.retValue() << "engine";
msg.retValue() << ",plugins=" << plugins.count();
msg.retValue() << ",workers=" << EnginePrivate::count;
msg.retValue() << "\n";
return false;
}
void EnginePrivate::run()
{
for (;;) {
s_makeworker = false;
Engine::self()->m_dispatcher.dequeue();
yield();
}
}
Engine::Engine()
{
#ifdef DEBUG
Debugger debug("Engine::Engine()"," [%p]",this);
#endif
}
Engine::~Engine()
{
#ifdef DEBUG
Debugger debug("Engine::~Engine()"," [%p]",this);
#endif
assert(this == s_self);
m_dispatcher.clear();
plugins.clear();
s_self = 0;
}
int Engine::run()
{
Debug(DebugInfo,"Engine::run()");
install(new EngineStatusHandler);
loadPlugins();
Debug(DebugInfo,"plugins.count() = %d",plugins.count());
initPlugins();
::signal(SIGINT,sighandler);
::signal(SIGTERM,sighandler);
Debug(DebugInfo,"Engine entering main loop");
dispatch("engine.start");
unsigned long corr = 0;
::signal(SIGHUP,sighandler);
::signal(SIGQUIT,sighandler);
while (s_haltcode == -1) {
if (s_init) {
s_init = false;
initPlugins();
}
// Create worker thread if we didn't hear about any of them in a while
if (s_makeworker && (EnginePrivate::count < s_maxworkers)) {
Debug(DebugInfo,"Creating new message dispatching thread");
new EnginePrivate;
}
s_makeworker = true;
// Attempt to sleep until the next full second
unsigned long t = (Time::now() + corr) % 1000000;
::usleep(1000000 - t);
Message m("engine.timer");
m.addParam("time",String((int)m.msgTime().sec()));
// Try to fine tune the ticker
t = m.msgTime().usec() % 1000000;
if (t > 500000)
corr -= (1000000-t)/10;
else
corr += t/10;
dispatch(&m);
}
Debug(DebugInfo,"Engine exiting with code %d",s_haltcode);
dispatch("engine.halt");
m_dispatcher.dequeue();
Thread::killall();
m_dispatcher.dequeue();
::signal(SIGINT,SIG_DFL);
::signal(SIGTERM,SIG_DFL);
::signal(SIGHUP,SIG_DFL);
::signal(SIGQUIT,SIG_DFL);
delete this;
Debug(DebugInfo,"Exiting with %d locked mutexes",Mutex::locks());
return s_haltcode;
}
Engine *Engine::self()
{
if (!s_self)
s_self = new Engine;
return s_self;
}
bool Engine::Register(const Plugin *plugin, bool reg)
{
#ifdef DEBUG
Debug(DebugInfo,"Engine::Register(%p,%d)",plugin,reg);
#endif
ObjList *p = plugins.find(plugin);
if (reg) {
if (p)
return false;
p = plugins.append(plugin);
p->setDelete(s_dynplugin);
}
else if (p)
p->remove(false);
return true;
}
bool Engine::loadPlugin(const char *file)
{
s_dynplugin = false;
SLib *lib = SLib::load(file);
s_dynplugin = true;
if (lib) {
m_libs.append(lib);
return true;
}
return false;
}
void Engine::loadPlugins()
{
#ifdef DEBUG
Debugger debug("Engine::loadPlugins()");
#endif
Configuration cfg(configFile("yate"));
bool defload = cfg.getBoolValue("general","modload",true);
const char *name = cfg.getValue("general","modpath");
if (name)
s_modpath = name;
NamedList *l = cfg.getSection("preload");
if (l) {
unsigned int len = l->length();
for (unsigned int i=0; i<len; i++) {
NamedString *n = l->getParam(i);
if (n && n->toBoolean())
loadPlugin(n->name());
}
}
DIR *dir = ::opendir(s_modpath);
if (!dir) {
Debug(DebugFail,"Engine::loadPlugins() failed opendir()");
return;
}
struct dirent *entry;
while ((entry = ::readdir(dir)) != 0) {
#ifdef DEBUG
Debug(DebugInfo,"Found dir entry %s",entry->d_name);
#endif
int n = ::strlen(entry->d_name) - s_modsuffix.length();
if ((n > 0) && !::strcmp(entry->d_name+n,s_modsuffix)) {
if (cfg.getBoolValue("modules",entry->d_name,defload))
loadPlugin(s_modpath+"/"+entry->d_name);
}
}
::closedir(dir);
l = cfg.getSection("postload");
if (l) {
unsigned int len = l->length();
for (unsigned int i=0; i<len; i++) {
NamedString *n = l->getParam(i);
if (n && n->toBoolean())
loadPlugin(n->name());
}
}
}
void Engine::initPlugins()
{
#ifdef DEBUG
Debugger debug("Engine::initPlugins()");
#else
Debug(DebugInfo,"Engine::initPlugins()");
#endif
dispatch("engine.init");
ObjList *l = &plugins;
for (; l; l = l->next()) {
Plugin *p = static_cast<Plugin *>(l->get());
if (p)
p->initialize();
}
}
void Engine::halt(unsigned int code)
{
s_haltcode = code;
}
void Engine::init()
{
s_init = true;
}
bool Engine::install(MessageHandler *handler)
{
return s_self ? s_self->m_dispatcher.install(handler) : false;
}
bool Engine::uninstall(MessageHandler *handler)
{
return s_self ? s_self->m_dispatcher.uninstall(handler) : false;
}
bool Engine::enqueue(Message *msg)
{
return (msg && s_self) ? s_self->m_dispatcher.enqueue(msg) : false;
}
bool Engine::dispatch(Message *msg)
{
return (msg && s_self) ? s_self->m_dispatcher.dispatch(*msg) : false;
}
bool Engine::dispatch(Message &msg)
{
return s_self ? s_self->m_dispatcher.dispatch(msg) : false;
}
bool Engine::dispatch(const char *name)
{
if (!(s_self && name && *name))
return false;
Message msg(name);
return s_self->m_dispatcher.dispatch(msg);
}
static void usage(FILE *f)
{
::fprintf(f,
"Usage: yate [options]\n"
" -h Help message (this one)\n"
" -v Verbose debugging (you can use more than once)\n"
" -q Quieter debugging (you can use more than once)\n"
" -d Daemonify, suppress output unless logged\n"
" -l filename Log to file\n"
" -c pathname Path to conf files directory (" CFG_PATH ")\n"
" -m pathname Path to modules directory (" MOD_PATH ")\n"
#ifndef NDEBUG
" -D[options] Special debugging options\n"
" c Call dlclose() until it gets an error\n"
" i Reinitialize after 1st initialization\n"
" x Exit immediately after initialization\n"
" w Delay creation of 1st worker thread\n"
#endif
);
}
static void badopt(char chr, const char *opt)
{
if (chr)
::fprintf(stderr,"Invalid character '%c' in option '%s'\n",chr,opt);
else
::fprintf(stderr,"Invalid option '%s'\n",opt);
usage(stderr);
}
static void noarg(const char *opt)
{
::fprintf(stderr,"Missing parameter to option '%s'\n",opt);
usage(stderr);
}
int Engine::main(int argc, const char **argv, const char **environ)
{
bool daemonic = false;
int debug_level = debugLevel();
const char *logfile = 0;
int i;
bool inopt = true;
for (i=1;i<argc;i++) {
const char *pc = argv[i];
if (inopt && (pc[0] == '-') && pc[1]) {
while (pc && *++pc) {
switch (*pc) {
case '-':
if (!*++pc) {
inopt=false;
pc=0;
continue;
}
if (!::strcmp(pc,"help")) {
usage(stdout);
return 0;
}
badopt(0,argv[i]);
return EINVAL;
break;
case 'h':
usage(stdout);
return 0;
case 'v':
debug_level++;
break;
case 'q':
debug_level--;
break;
case 'd':
daemonic = true;
break;
case 'l':
if (i+1 >= argc) {
noarg(argv[i]);
return ENOENT;
}
pc = 0;
logfile=argv[++i];
break;
case 'c':
if (i+1 >= argc) {
noarg(argv[i]);
return ENOENT;
}
pc = 0;
s_cfgpath=argv[++i];
break;
case 'm':
if (i+1 >= argc) {
noarg(argv[i]);
return ENOENT;
}
pc = 0;
s_modpath=argv[++i];
break;
#ifndef NDEBUG
case 'D':
while (*++pc) {
switch (*pc) {
case 'c':
s_keepclosing = true;
break;
case 'i':
s_init = true;
break;
case 'x':
s_haltcode++;
break;
case 'w':
s_makeworker = false;
break;
default:
badopt(*pc,argv[i]);
return EINVAL;
}
}
pc = 0;
break;
#endif
default:
badopt(*pc,argv[i]);
return EINVAL;
}
}
}
else {
::fprintf(stderr,"Invalid non-option '%s'\n",pc);
usage(stderr);
return EINVAL;
}
}
if (daemonic) {
Debugger::enableOutput(false);
if (::daemon(1,0) == -1) {
int err = errno;
::fprintf(stderr,"Daemonification failed: %s (%d)\n",::strerror(err),err);
return err;
}
}
if (logfile) {
int fd = ::open(logfile,O_WRONLY|O_CREAT|O_APPEND,0640);
if (fd >= 0) {
// Redirect stdout and stderr to the new file
::fflush(stdout);
::dup2(fd,1);
::fflush(stderr);
::dup2(fd,2);
::close(fd);
Debugger::enableOutput(true);
}
}
debugLevel(debug_level);
return self()->run();
}

269
engine/Message.cpp Normal file
View File

@ -0,0 +1,269 @@
/**
* Message.cpp
* This file is part of the YATE Project http://YATE.null.ro
*/
#include "telengine.h"
#include <string.h>
using namespace TelEngine;
Message::Message(const char *name, const char *retval)
: NamedList(name), m_return(retval), m_data(0)
{
#ifdef DEBUG
Debug(DebugAll,"Message::Message(\"%s\",\"%s\") [%p]",name,retval,this);
#endif
}
String Message::encode(const char *id) const
{
String s("%%>message:");
s << String::msgEscape(id,':') << ":" << (unsigned int)m_time.sec() << ":";
commonEncode(s);
return s;
}
String Message::encode(bool received, const char *id) const
{
String s("%%<message:");
s << String::msgEscape(id,':') << ":" << received << ":";
commonEncode(s);
return s;
}
int Message::decode(const char *str, String &id)
{
String s("%%>message:");
if (!str || ::strncmp(str,s.c_str(),s.length()))
return -1;
// locate the SEP after id
const char *sep = ::strchr(str+s.length(),':');
if (!sep)
return s.length();
// locate the SEP after time
const char *sep2 = ::strchr(sep+1,':');
if (!sep2)
return sep-str;
id.assign(str+s.length(),(sep-str)-s.length()-1);
int err = -1;
id = id.msgUnescape(&err,':');
if (err >= 0)
return err+s.length();
String t(sep+1,sep2-sep-1);
unsigned int tm;
t >> tm;
if (!t.null())
return sep-str;
m_time=1000000ULL*tm;
return commonDecode(str,sep2-str);
}
int Message::decode(const char *str, bool &received, const char *id)
{
String s("%%<message:");
s << id << ":";
if (!str || ::strncmp(str,s.c_str(),s.length()))
return -1;
// locate the SEP after received
const char *sep = ::strchr(str+s.length(),':');
if (!sep)
return s.length();
String rcvd(str+s.length(),(sep-str)-s.length()-1);
rcvd >> received;
if (!rcvd.null())
return s.length();
return commonDecode(str,sep-str);
}
void Message::commonEncode(String &str) const
{
str << msgEscape(':') << ":" << m_return.msgEscape(':');
unsigned n = length();
for (unsigned i = 0; i < n; i++) {
NamedString *s = getParam(i);
if (s)
str << ":" << s->name().msgEscape(':') << "=" << s->msgEscape(':');
}
}
int Message::commonDecode(const char *str, int offs)
{
str += offs;
// locate SEP after name
const char *sep = ::strchr(str,':');
if (!sep)
return offs;
String chunk(str,sep-str);
int err = -1;
chunk = chunk.msgUnescape(&err,':');
if (err >= 0)
return offs+err;
if (!chunk.null())
*this = chunk;
offs += (sep-str+1);
str = sep+1;
// locate SEP or EOL after retval
sep = ::strchr(str,':');
if (sep)
chunk.assign(str,sep-str);
else
chunk.assign(str);
chunk = chunk.msgUnescape(&err,':');
if (err >= 0)
return offs+err;
m_return = chunk;
// find and assign name=value pairs
while (sep) {
offs += (sep-str+1);
str = sep+1;
sep = ::strchr(str,':');
if (sep)
chunk.assign(str,sep-str);
else
chunk.assign(str);
if (chunk.null())
continue;
chunk = chunk.msgUnescape(&err,':');
if (err >= 0)
return offs+err;
int pos = chunk.find('=');
switch (pos) {
case -1:
clearParam(chunk);
break;
case 0:
return offs+err;
default:
setParam(chunk.substr(0,pos),chunk.substr(pos+1));
}
}
return -2;
}
MessageHandler::MessageHandler(const char *name, unsigned priority)
: String(name), m_priority(priority), m_dispatcher(0)
{
#ifdef DEBUG
Debug(DebugAll,"MessageHandler::MessageHandler(\"%s\",%u) [%p]",name,priority,this);
#endif
}
MessageHandler::~MessageHandler()
{
#ifdef DEBUG
Debug(DebugAll,"MessageHandler::~MessageHandler() [%p]",this);
#endif
if (m_dispatcher)
m_dispatcher->uninstall(this);
}
MessageDispatcher::MessageDispatcher()
: m_hook(0)
{
#ifdef DEBUG
Debug(DebugAll,"MessageDispatcher::MessageDispatcher() [%p]",this);
#endif
}
MessageDispatcher::~MessageDispatcher()
{
#ifdef DEBUG
Debug(DebugAll,"MessageDispatcher::~MessageDispatcher() [%p]",this);
#endif
m_handlers.clear();
}
bool MessageDispatcher::install(MessageHandler *handler)
{
#ifdef DEBUG
Debug(DebugAll,"MessageDispatcher::install(%p)",handler);
#endif
if (!handler)
return false;
ObjList *l = m_handlers.find(handler);
if (l)
return false;
unsigned p = handler->priority();
int pos = 0;
for (l=&m_handlers; l; l=l->next(),pos++) {
MessageHandler *h = static_cast<MessageHandler *>(l->get());
if (h && (h->priority() > p))
break;
}
if (l) {
#ifdef DEBUG
Debug(DebugAll,"Inserting handler [%p] on place #%d",handler,pos);
#endif
l->insert(handler);
}
else {
#ifdef DEBUG
Debug(DebugAll,"Appending handler [%p] on place #%d",handler,pos);
#endif
m_handlers.append(handler);
}
handler->m_dispatcher = this;
if (handler->null())
Debug(DebugInfo,"Registered broadcast message handler %p",handler);
return true;
}
bool MessageDispatcher::uninstall(MessageHandler *handler)
{
#ifdef DEBUG
Debug(DebugAll,"MessageDispatcher::uninstall(%p)",handler);
#endif
handler = static_cast<MessageHandler *>(m_handlers.remove(handler,false));
if (handler)
handler->m_dispatcher = 0;
return (handler != 0);
}
bool MessageDispatcher::dispatch(Message &msg)
{
#ifdef DEBUG
Debugger debug("MessageDispatcher::dispatch","(%p) (\"%s\")",&msg,msg.c_str());
#endif
bool retv = false;
ObjList *l = &m_handlers;
for (; l; l=l->next()) {
MessageHandler *h = static_cast<MessageHandler *>(l->get());
if (h && (h->null() || *h == msg) && h->received(msg)) {
retv = true;
break;
}
}
msg.dispatched(retv);
if (m_hook)
(*m_hook)(msg,retv);
return retv;
}
bool MessageDispatcher::enqueue(Message *msg)
{
if (!msg || m_messages.find(msg))
return false;
m_mutex.lock();
m_messages.append(msg);
m_mutex.unlock();
return true;
}
bool MessageDispatcher::dequeueOne()
{
m_mutex.lock();
Message *msg = static_cast<Message *>(m_messages.remove(false));
m_mutex.unlock();
if (!msg)
return false;
dispatch(*msg);
msg->destruct();
return true;
}
void MessageDispatcher::dequeue()
{
while (dequeueOne())
;
}

139
engine/Mutex.cpp Normal file
View File

@ -0,0 +1,139 @@
/**
* Mutex.cpp
* This file is part of the YATE Project http://YATE.null.ro
*/
#include "telengine.h"
#include <unistd.h>
#include <pthread.h>
namespace TelEngine {
class MutexPrivate {
public:
MutexPrivate();
~MutexPrivate();
inline void ref()
{ ++m_refcount; }
inline void deref()
{ if (!--m_refcount) delete this; }
bool lock(long long int maxwait);
void unlock();
static int m_count;
static int m_locks;
private:
pthread_mutex_t m_mutex;
int m_refcount;
};
};
using namespace TelEngine;
int MutexPrivate::m_count = 0;
int MutexPrivate::m_locks = 0;
// WARNING!!!
// No debug messages are allowed in mutexes since the debug output itself
// is serialized using a mutex!
MutexPrivate::MutexPrivate()
: m_refcount(1)
{
m_count++;
::pthread_mutex_init(&m_mutex,0);
}
MutexPrivate::~MutexPrivate()
{
m_count--;
::pthread_mutex_destroy(&m_mutex);
}
bool MutexPrivate::lock(long long int maxwait)
{
bool rval = false;
ref();
if (maxwait < 0)
rval = !::pthread_mutex_lock(&m_mutex);
else if (!maxwait)
rval = !::pthread_mutex_trylock(&m_mutex);
else {
unsigned long long t = Time::now() + maxwait;
do {
rval = !::pthread_mutex_trylock(&m_mutex);
if (rval)
break;
::usleep(1);
} while (t > Time::now());
}
if (rval)
m_locks++;
else
deref();
return rval;
}
void MutexPrivate::unlock()
{
::pthread_mutex_unlock(&m_mutex);
m_locks--;
deref();
}
Mutex::Mutex()
: m_private(0)
{
m_private = new MutexPrivate;
}
Mutex::Mutex(const Mutex &original)
: m_private(original.privDataCopy())
{
}
Mutex::~Mutex()
{
MutexPrivate *priv = m_private;
m_private = 0;
if (priv)
priv->deref();
}
Mutex& Mutex::operator=(const Mutex &original)
{
MutexPrivate *priv = m_private;
m_private = original.privDataCopy();
if (priv)
priv->deref();
return *this;
}
MutexPrivate *Mutex::privDataCopy() const
{
if (m_private)
m_private->ref();
return m_private;
}
bool Mutex::lock(long long int maxwait)
{
return m_private ? m_private->lock(maxwait) : false;
}
void Mutex::unlock()
{
if (m_private)
m_private->unlock();
}
int Mutex::count()
{
return MutexPrivate::m_count;
}
int Mutex::locks()
{
return MutexPrivate::m_locks;
}

109
engine/NamedList.cpp Normal file
View File

@ -0,0 +1,109 @@
/**
* NamedList.cpp
* This file is part of the YATE Project http://YATE.null.ro
*/
#include "telengine.h"
using namespace TelEngine;
NamedList::NamedList(const char *name)
: String(name)
{
}
NamedList &NamedList::addParam(NamedString *param)
{
#ifdef DEBUG
Debug(DebugInfo,"NamedList::addParam(%p) [\"%s\",\"%s\"]",
param,param->name().c_str(),param->c_str());
#endif
m_params.append(param);
return *this;
}
NamedList &NamedList::addParam(const char *name, const char *value)
{
#ifdef DEBUG
Debug(DebugInfo,"NamedList::addParam(\"%s\",\"%s\")",name,value);
#endif
m_params.append(new NamedString(name, value));
return *this;
}
NamedList &NamedList::setParam(NamedString *param)
{
#ifdef DEBUG
Debug(DebugInfo,"NamedList::setParam(%p) [\"%s\",\"%s\"]",
param,param->name().c_str(),param->c_str());
#endif
NamedString *s = getParam(param->name());
if (s) {
*s = param->c_str();
param->destruct();
}
else
m_params.append(param);
return *this;
}
NamedList &NamedList::setParam(const char *name, const char *value)
{
#ifdef DEBUG
Debug(DebugInfo,"NamedList::setParam(\"%s\",\"%s\")",name,value);
#endif
NamedString *s = getParam(name);
if (s)
*s = value;
else
m_params.append(new NamedString(name, value));
return *this;
}
NamedList &NamedList::clearParam(const String &name)
{
#ifdef DEBUG
Debug(DebugInfo,"NamedList::clearParam(\"%s\")",name.c_str());
#endif
ObjList *p = &m_params;
while (p) {
NamedString *s = static_cast<NamedString *>(p->get());
if (s && (s->name() == name))
p->remove();
else
p = p->next();
}
return *this;
}
NamedString *NamedList::getParam(const String &name) const
{
#ifdef DEBUG
Debug(DebugInfo,"NamedList::getParam(\"%s\")",name.c_str());
#endif
const ObjList *p = &m_params;
for (;p;p=p->next()) {
NamedString *s = static_cast<NamedString *>(p->get());
if (s && (s->name() == name))
return s;
}
return 0;
}
NamedString *NamedList::getParam(unsigned int index) const
{
#ifdef DEBUG
Debug(DebugInfo,"NamedList::getParam(%u)",index);
#endif
const ObjList *p = m_params[index];
return p ? static_cast<NamedString *>(p->get()) : 0;
}
const char *NamedList::getValue(const String &name, const char *defvalue) const
{
#ifdef DEBUG
Debug(DebugInfo,"NamedList::getValue(\"%s\",\"%s\")",name.c_str(),defvalue);
#endif
const NamedString *s = getParam(name);
return s ? s->c_str() : defvalue;
}

177
engine/ObjList.cpp Normal file
View File

@ -0,0 +1,177 @@
/**
* ObjList.cpp
* This file is part of the YATE Project http://YATE.null.ro
*/
#include "telengine.h"
using namespace TelEngine;
ObjList::ObjList()
: m_next(0), m_obj(0), m_delete(true)
{
#ifdef DEBUG
Debug(DebugAll,"ObjList::ObjList() [%p]",this);
#endif
}
ObjList::~ObjList()
{
#ifdef DEBUG
Debugger debug("ObjList::~ObjList()"," [%p]",this);
#endif
if (m_obj) {
GenObject *tmp = m_obj;
m_obj = 0;
if (m_delete) {
#ifdef DEBUG
Debug(DebugInfo,"ObjList::~ObjList() deleting %p",tmp);
#endif
tmp->destruct();
}
}
if (m_next)
m_next->destruct();
}
unsigned int ObjList::length() const
{
unsigned int c = 0;
const ObjList *n = this;
while (n) {
c++;
n = n->next();
}
return c;
}
unsigned int ObjList::count() const
{
unsigned int c = 0;
const ObjList *n = this;
while (n) {
if (n->get())
c++;
n = n->next();
}
return c;
}
ObjList *ObjList::last() const
{
const ObjList *n = this;
while (n->next())
n = n->next();
return const_cast<ObjList *>(n);
}
ObjList *ObjList::operator[](int index) const
{
if (index < 0)
return 0;
ObjList *obj = const_cast<ObjList *>(this);
for (;obj;obj=obj->next(),index--)
if (!index) break;
return obj;
}
ObjList *ObjList::find(const GenObject *obj) const
{
#ifdef DEBUG
Debugger debug("ObjList::find","(%p) [%p]",obj,this);
#endif
const ObjList *n = this;
while (n && (n->get() != obj))
n = n->next();
#ifdef DEBUG
Debug(DebugInfo,"ObjList::find returning %p",n);
#endif
return const_cast<ObjList *>(n);
}
GenObject *ObjList::set(const GenObject *obj, bool delold)
{
if (m_obj == obj)
return 0;
GenObject *tmp = m_obj;
m_obj = const_cast<GenObject *>(obj);
if (delold && tmp) {
tmp->destruct();
return 0;
}
return tmp;
}
ObjList *ObjList::insert(const GenObject *obj)
{
#ifdef DEBUG
Debugger debug("ObjList::insert","(%p) [%p]",obj,this);
#endif
if (m_obj) {
ObjList *n = new ObjList();
n->set(m_obj);
set(obj,false);
n->m_next = m_next;
m_next = n;
}
else
m_obj = const_cast<GenObject *>(obj);
return this;
}
ObjList *ObjList::append(const GenObject *obj)
{
#ifdef DEBUG
Debugger debug("ObjList::append","(%p) [%p]",obj,this);
#endif
ObjList *n = last();
if (n->get()) {
n->m_next = new ObjList();
n = n->m_next;
}
n->set(obj);
return n;
}
GenObject *ObjList::remove(bool delobj)
{
GenObject *tmp = m_obj;
if (m_next) {
ObjList *n = m_next;
m_obj = n->get();
m_next = n->next();
n->m_obj = 0;
n->m_next = 0;
n->destruct();
}
else
m_obj = 0;
if (delobj && tmp) {
#ifdef DEBUG
Debug(DebugInfo,"ObjList::remove() deleting %p",tmp);
#endif
tmp->destruct();
tmp = 0;
}
return tmp;
}
GenObject *ObjList::remove(GenObject *obj, bool delobj)
{
ObjList *n = find(obj);
return n ? n->remove(delobj) : 0;
}
void ObjList::clear()
{
#ifdef DEBUG
Debugger debug("ObjList::clear()"," [%p]",this);
#endif
ObjList *n = m_next;
m_next = 0;
remove(m_delete);
if (n)
n->destruct();
}

26
engine/Plugin.cpp Normal file
View File

@ -0,0 +1,26 @@
/**
* Plugin.cpp
* This file is part of the YATE Project http://YATE.null.ro
*/
#include "telengine.h"
using namespace TelEngine;
Plugin::Plugin()
{
Debug(DebugAll,"Plugin::Plugin() [%p]",this);
Engine::Register(this);
}
Plugin::Plugin(const char *name)
{
Debug(DebugAll,"Plugin::Plugin(\"%s\") [%p]",name,this);
Engine::Register(this);
}
Plugin::~Plugin()
{
Debugger debug("Plugin::~Plugin()"," [%p]",this);
Engine::Register(this,false);
}

774
engine/String.cpp Normal file
View File

@ -0,0 +1,774 @@
/**
* String.cpp
* This file is part of the YATE Project http://YATE.null.ro
*/
#include "telengine.h"
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <regex.h>
namespace TelEngine {
String operator+(const String &s1, const String &s2)
{
String s(s1.c_str());
s += s2.c_str();
return s;
}
String operator+(const String &s1, const char *s2)
{
String s(s1.c_str());
s += s2;
return s;
}
String operator+(const char *s1, const String &s2)
{
String s(s1);
s += s2;
return s;
}
int lookup(const char *str, const TokenDict *tokens, int defvalue, int base)
{
if (!str)
return defvalue;
if (tokens) {
for (; tokens->token; tokens++)
if (!::strcmp(str,tokens->token))
return tokens->value;
}
char *eptr = 0;
long int val= ::strtol(str,&eptr,base);
if (!eptr || *eptr)
return defvalue;
return val;
}
const char *lookup(int value, const TokenDict *tokens, const char *defvalue)
{
if (tokens) {
for (; tokens->token; tokens++)
if (value == tokens->value)
return tokens->token;
}
return defvalue;
}
#define MAX_MATCH 9
class StringMatchPrivate
{
public:
StringMatchPrivate();
void fixup();
void clear();
int count;
regmatch_t rmatch[MAX_MATCH+1];
};
};
using namespace TelEngine;
static bool isWordBreak(char c, bool nullOk = false)
{
return (c == ' ' || c == '\t' || c == '\n' || (nullOk && !c));
}
StringMatchPrivate::StringMatchPrivate()
{
#ifdef DEBUG
Debug(DebugAll,"StringMatchPrivate::StringMatchPrivate() [%p]",this);
#endif
clear();
}
void StringMatchPrivate::clear()
{
count = 0;
for (int i = 0; i <= MAX_MATCH; i++) {
rmatch[i].rm_so = -1;
rmatch[i].rm_eo = 0;
}
}
void StringMatchPrivate::fixup()
{
count = 0;
rmatch[0].rm_so = rmatch[1].rm_so;
rmatch[0].rm_eo = 0;
int i, c = 0;
for (i = 1; i <= MAX_MATCH; i++) {
if (rmatch[i].rm_so != -1) {
rmatch[0].rm_eo = rmatch[i].rm_eo - rmatch[0].rm_so;
rmatch[i].rm_eo -= rmatch[i].rm_so;
c = i;
}
}
// Cope with the regexp stupidity.
if (c > 1) {
for (i = 0; i < c; i++)
rmatch[i] = rmatch[i+1];
rmatch[c].rm_so = -1;
c--;
}
count = c;
}
String::String()
: m_string(0), m_length(0), m_hash(0), m_matches(0)
{
#ifdef DEBUG
Debug(DebugAll,"String::String() [%p]",this);
#endif
}
String::String(const char *value, int len)
: m_string(0), m_length(0), m_hash(0), m_matches(0)
{
#ifdef DEBUG
Debug(DebugAll,"String::String(\"%s\",%d) [%p]",value,len,this);
#endif
assign(value,len);
}
String::String(const String &value)
: m_string(0), m_length(0), m_hash(0), m_matches(0)
{
#ifdef DEBUG
Debug(DebugAll,"String::String(%p) [%p]",&value,this);
#endif
if (!value.null()) {
m_string = ::strdup(value.c_str());
changed();
}
}
String::String(char value, unsigned int repeat)
: m_string(0), m_length(0), m_hash(0), m_matches(0)
{
#ifdef DEBUG
Debug(DebugAll,"String::String('%c',%d) [%p]",value,repeat,this);
#endif
if (value && repeat) {
m_string = (char *) ::malloc(repeat+1);
::memset(m_string,value,repeat);
m_string[repeat] = 0;
changed();
}
}
String::String(int value)
: m_string(0), m_length(0), m_hash(0), m_matches(0)
{
#ifdef DEBUG
Debug(DebugAll,"String::String(%d) [%p]",value,this);
#endif
char buf[64];
::sprintf(buf,"%d",value);
m_string = ::strdup(buf);
changed();
}
String::String(unsigned int value)
: m_string(0), m_length(0), m_hash(0), m_matches(0)
{
#ifdef DEBUG
Debug(DebugAll,"String::String(%u) [%p]",value,this);
#endif
char buf[64];
::sprintf(buf,"%u",value);
m_string = ::strdup(buf);
changed();
}
String::String(bool value)
: m_string(0), m_length(0), m_hash(0), m_matches(0)
{
#ifdef DEBUG
Debug(DebugAll,"String::String(%u) [%p]",value,this);
#endif
m_string = ::strdup(value ? "true" : "false");
changed();
}
String::~String()
{
#ifdef DEBUG
Debug(DebugAll,"String::~String() [%p] (\"%s\")",this,m_string);
#endif
if (m_matches) {
StringMatchPrivate *odata = m_matches;
m_matches = 0;
delete odata;
}
if (m_string) {
char *odata = m_string;
m_length = 0;
m_string = 0;
::free(odata);
}
}
String& String::assign(const char *value, int len)
{
if (len && value && *value) {
if (len < 0)
len = ::strlen(value);
if (value != m_string || len != (int)m_length) {
char *data = (char *) ::malloc(len+1);
::memcpy(data,value,len);
data[len] = 0;
char *odata = m_string;
m_string = data;
changed();
if (odata)
::free(odata);
}
}
else
clear();
return *this;
}
void String::changed()
{
clearMatches();
m_hash = 0;
m_length = m_string ? ::strlen(m_string) : 0;
}
void String::clear()
{
if (m_string) {
char *odata = m_string;
m_string = 0;
changed();
::free(odata);
}
}
char String::at(int index) const
{
if ((index < 0) || ((unsigned)index >= m_length) || !m_string)
return 0;
return m_string[index];
}
String String::substr(int offs, int len) const
{
if (offs < 0) {
offs += m_length;
if (offs < 0)
offs = 0;
}
if ((unsigned int)offs >= m_length)
return String();
return String(c_str()+offs,len);
}
int String::toInteger(int defvalue, int base) const
{
if (!m_string)
return defvalue;
char *eptr = 0;
long int val= ::strtol(m_string,&eptr,base);
if (!eptr || *eptr)
return defvalue;
return val;
}
int String::toInteger(const TokenDict *tokens, int defvalue, int base) const
{
if (!m_string)
return defvalue;
if (tokens) {
for (; tokens->token; tokens++)
if (operator==(tokens->token))
return tokens->value;
}
return toInteger(defvalue,base);
}
static const char *str_false[] = { "false", "no", "off", "disable", 0 };
static const char *str_true[] = { "true", "yes", "on", "enable", 0 };
bool String::toBoolean(bool defvalue) const
{
if (!m_string)
return defvalue;
const char **test;
for (test=str_false; *test; test++)
if (!::strcmp(m_string,*test))
return false;
for (test=str_true; *test; test++)
if (!::strcmp(m_string,*test))
return true;
return defvalue;
}
String& String::trimBlanks()
{
if (m_string) {
const char *s = m_string;
while (*s == ' ' || *s == '\t')
s++;
const char *e = s;
for (const char *p = e; *p; p++)
if (*p != ' ' && *p != '\t')
e = p+1;
assign(s,e-s);
}
return *this;
}
String& String::operator=(const char *value)
{
if (value != c_str()) {
char *tmp = m_string;
m_string = value ? ::strdup(value) : 0;
changed();
if (tmp)
::free(tmp);
}
return *this;
}
String& String::operator+=(const char *value)
{
if (value && *value) {
if (m_string) {
char *tmp1 = m_string;
char *tmp2 = (char *) ::malloc(::strlen(value)+length()+1);
::strcpy(tmp2,m_string);
::strcat(tmp2,value);
m_string = tmp2;
::free(tmp1);
}
else
m_string = ::strdup(value);
changed();
}
return *this;
}
String& String::operator=(char value)
{
char buf[2] = {value,0};
return operator=(buf);
}
String& String::operator=(int value)
{
char buf[64];
::sprintf(buf,"%d",value);
return operator=(buf);
}
String& String::operator=(unsigned int value)
{
char buf[64];
::sprintf(buf,"%u",value);
return operator=(buf);
}
String& String::operator+=(char value)
{
char buf[2] = {value,0};
return operator+=(buf);
}
String& String::operator+=(int value)
{
char buf[64];
::sprintf(buf,"%d",value);
return operator+=(buf);
}
String& String::operator+=(unsigned int value)
{
char buf[64];
::sprintf(buf,"%u",value);
return operator+=(buf);
}
String& String::operator>>(const char *skip)
{
if (m_string && skip && *skip) {
const char *loc = ::strstr(m_string,skip);
if (loc)
assign(loc+::strlen(skip));
}
return *this;
}
String& String::operator>>(char &store)
{
if (m_string) {
store = m_string[0];
assign(m_string+1);
}
return *this;
}
String& String::operator>>(int &store)
{
if (m_string) {
char *end = 0;
long int l = ::strtol(m_string,&end,0);
if (end && (m_string != end)) {
store = l;
assign(end);
}
}
return *this;
}
String& String::operator>>(unsigned int &store)
{
if (m_string) {
char *end = 0;
unsigned long int l = ::strtoul(m_string,&end,0);
if (end && (m_string != end)) {
store = l;
assign(end);
}
}
return *this;
}
String& String::operator>>(bool &store)
{
if (m_string) {
const char *s = m_string;
while (*s == ' ' || *s == '\t')
s++;
const char **test;
for (test=str_false; *test; test++) {
int l = ::strlen(*test);
if (!::strncmp(s,*test,l) && isWordBreak(s[l],true)) {
store = false;
assign(s+l);
return *this;
}
}
for (test=str_true; *test; test++) {
int l = ::strlen(*test);
if (!::strncmp(s,*test,l) && isWordBreak(s[l],true)) {
store = true;
assign(s+l);
return *this;
}
}
}
return *this;
}
bool String::operator==(const char *value) const
{
if (!m_string)
return !(value && *value);
return value && !::strcmp(m_string,value);
}
bool String::operator!=(const char *value) const
{
if (!m_string)
return value && *value;
return (!value) || ::strcmp(m_string,value);
}
bool String::operator==(const String &value) const
{
if (hash() != value.hash())
return false;
return operator==(value.c_str());
}
bool String::operator!=(const String &value) const
{
if (hash() != value.hash())
return true;
return operator!=(value.c_str());
}
int String::find(char what, unsigned int offs) const
{
if (!m_string || (offs > m_length))
return -1;
const char *s = ::strchr(m_string+offs,what);
return s ? s-m_string : -1;
}
int String::find(const char *what, unsigned int offs) const
{
if (!(m_string && what && *what) || (offs > m_length))
return -1;
const char *s = ::strstr(m_string+offs,what);
return s ? s-m_string : -1;
}
int String::rfind(char what) const
{
if (!m_string)
return -1;
const char *s = ::strrchr(m_string,what);
return s ? s-m_string : -1;
}
bool String::startsWith(const char *what, bool wordBreak) const
{
if (!(m_string && what && *what))
return false;
unsigned int l = ::strlen(what);
if (m_length < l)
return false;
else if (wordBreak && (m_length > l) && !isWordBreak(m_string[l]))
return false;
return (::strncmp(m_string,what,l) == 0);
}
bool String::endsWith(const char *what, bool wordBreak) const
{
if (!(m_string && what && *what))
return false;
unsigned int l = ::strlen(what);
if (m_length < l)
return false;
else if (wordBreak && (m_length > l) && !isWordBreak(m_string[m_length-l-1]))
return false;
return (::strncmp(m_string+m_length-l,what,l) == 0);
}
bool String::matches(Regexp &rexp)
{
if (m_matches)
clearMatches();
else
m_matches = new StringMatchPrivate;
if (rexp.matches(c_str(),m_matches)) {
m_matches->fixup();
return true;
}
return false;
}
int String::matchOffset(int index) const
{
if ((!m_matches) || (index < 0) || (index > m_matches->count))
return -1;
return m_matches->rmatch[index].rm_so;
}
int String::matchLength(int index) const
{
if ((!m_matches) || (index < 0) || (index > m_matches->count))
return 0;
return m_matches->rmatch[index].rm_eo;
}
int String::matchCount() const
{
if (!m_matches)
return 0;
return m_matches->count;
}
String String::replaceMatches(const String &templ) const
{
String s;
int pos, ofs = 0;
for (;;) {
pos = templ.find('\\',ofs);
if (pos < 0) {
s += templ.substr(ofs);
break;
}
s += templ.substr(ofs,pos-ofs);
pos++;
char c = templ[pos];
if (c == '\\') {
pos++;
s += "\\";
}
else if ('0' <= c && c <= '9') {
pos++;
s += matchString(c - '0');
}
ofs = pos;
}
return s;
}
void String::clearMatches()
{
if (m_matches)
m_matches->clear();
}
String String::msgEscape(const char *str, char extraEsc)
{
if (!str)
str = "";
String s;
char c;
while ((c=*str++)) {
if (c < ' ' || c == extraEsc) {
c += '@';
s += '%';
}
else if (c == '%')
s += c;
s += c;
}
return s;
}
String String::msgUnescape(const char *str, int *errptr, char extraEsc)
{
if (!str)
str = "";
if (extraEsc)
extraEsc += '@';
const char *pos = str;
String s;
char c;
while ((c=*pos++)) {
if (c < ' ') {
if (errptr)
*errptr = (pos-str);
return s;
}
else if (c == '%') {
c=*pos++;
if ((c > '@' && c <= '_') || c == extraEsc)
c -= '@';
else if (c != '%') {
if (errptr)
*errptr = (pos-str);
return s;
}
}
s += c;
}
if (errptr)
*errptr = -1;
return s;
}
unsigned int String::hash() const
{
if (!m_hash)
m_hash = hash(m_string);
return m_hash;
}
unsigned int String::hash(const char *value)
{
if (!value)
return 0;
unsigned int h = 0;
while (unsigned char c = (unsigned char) *value++)
h = (h << 1) + c;
return h;
}
Regexp::Regexp()
: m_regexp(0)
{
#ifdef DEBUG
Debug(DebugAll,"Regexp::Regexp() [%p]",this);
#endif
}
Regexp::Regexp(const char *value)
: String(value), m_regexp(0)
{
#ifdef DEBUG
Debug(DebugAll,"Regexp::Regexp(\"%s\") [%p]",value,this);
#endif
}
Regexp::Regexp(const Regexp &value)
: String(value.c_str()), m_regexp(0)
{
#ifdef DEBUG
Debug(DebugAll,"Regexp::Regexp(%p) [%p]",&value,this);
#endif
}
Regexp::~Regexp()
{
cleanup();
}
bool Regexp::matches(const char *value, StringMatchPrivate *matches)
{
#ifdef DEBUG
Debug(DebugInfo,"Regexp::matches(\"%s\",%p)",value,matches);
#endif
if (!value)
return false;
if (!compile())
return false;
int mm = matches ? MAX_MATCH : 0;
regmatch_t *mt = matches ? (matches->rmatch)+1 : 0;
return !::regexec((regex_t *)m_regexp,value,mm,mt,0);
}
bool Regexp::matches(const char *value)
{
return matches(value,0);
}
void Regexp::changed()
{
cleanup();
String::changed();
}
bool Regexp::compile()
{
#ifdef DEBUG
Debug(DebugInfo,"Regexp::compile()");
#endif
if (c_str() && !m_regexp) {
regex_t *data = (regex_t *) ::malloc(sizeof(regex_t));
if (::regcomp(data,c_str(),0)) {
Debug(DebugWarn,"Regexp::compile() \"%s\" failed",c_str());
::regfree(data);
::free(data);
}
else
m_regexp = (void *)data;
}
return (m_regexp != 0);
}
void Regexp::cleanup()
{
#ifdef DEBUG
Debug(DebugInfo,"Regexp::cleanup()");
#endif
if (m_regexp) {
regex_t *data = (regex_t *)m_regexp;
m_regexp = 0;
::regfree(data);
::free(data);
}
}
NamedString::NamedString(const char *name, const char *value)
: String(value), m_name(name)
{
#ifdef DEBUG
Debug(DebugAll,"NamedString::NamedString(\"%s\",\"%s\") [%p]",name,value,this);
#endif
}

233
engine/TelEngine.cpp Normal file
View File

@ -0,0 +1,233 @@
/**
* TelEngine.cpp
* This file is part of the YATE Project http://YATE.null.ro
*/
#include "telengine.h"
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include <sys/time.h>
namespace TelEngine {
#define DebugMin DebugFail
#define DebugMax DebugAll
static int s_debug = DebugWarn;
static int s_indent = 0;
static bool s_debugging = true;
static void dbg_stderr_func(const char *buf)
{
::fwrite(buf,1,::strlen(buf),stderr);
::fflush(stderr);
}
static void (*s_output)(const char *) = dbg_stderr_func;
static void (*s_intout)(const char *) = 0;
static Mutex out_mux;
static void common_output(char *buf)
{
int n = ::strlen(buf);
if (n && (buf[n-1] == '\n'))
n--;
buf[n] = '\n';
buf[n+1] = '\0';
// serialize the output strings
out_mux.lock();
if (s_output)
s_output(buf);
if (s_intout)
s_intout(buf);
out_mux.unlock();
}
static void dbg_output(const char *prefix, const char *format, va_list ap)
{
if (!(s_output || s_intout))
return;
char buf[1024];
unsigned int n = s_indent*2;
if (n >= sizeof(buf))
n = sizeof(buf)-1;
::memset(buf,' ',n);
buf[n] = 0;
unsigned int l = sizeof(buf)-n-2;
if (prefix)
::strncpy(buf+n,prefix,l);
n = ::strlen(buf);
l = sizeof(buf)-n-2;
if (format) {
::vsnprintf(buf+n,l,format,ap);
}
common_output(buf);
}
void Output(const char *format, ...)
{
char buf[1024];
if (!((s_output || s_intout) && format && *format))
return;
va_list va;
va_start(va,format);
::vsnprintf(buf,sizeof(buf)-2,format,va);
va_end(va);
common_output(buf);
}
bool Debug(int level, const char *format, ...)
{
if (level <= s_debug) {
if (!s_debugging)
return true;
if (!format)
format = "";
char buf[32];
::sprintf(buf,"<%d> ",level);
va_list va;
va_start(va,format);
dbg_output(buf,format,va);
va_end(va);
return true;
}
return false;
}
bool Debug(const char *facility, int level, const char *format, ...)
{
if (level <= s_debug) {
if (!s_debugging)
return true;
if (!format)
format = "";
char buf[64];
::snprintf(buf,sizeof(buf),"<%s:%d> ",facility,level);
va_list va;
va_start(va,format);
dbg_output(buf,format,va);
va_end(va);
return true;
}
return false;
}
int debugLevel()
{
return s_debug;
}
int debugLevel(int level)
{
if (level < DebugMin)
level = DebugMin;
if (level > DebugMax)
level = DebugMax;
return (s_debug = level);
}
bool debugAt(int level)
{
return (s_debugging && (level <= s_debug));
}
Debugger::Debugger(const char *name, const char *format, ...)
: m_name(name)
{
if (s_debugging && m_name && (s_debug >= DebugAll)) {
char buf[64];
::snprintf(buf,sizeof(buf),">>> %s",m_name);
va_list va;
va_start(va,format);
dbg_output(buf,format,va);
va_end(va);
s_indent++;
}
else
m_name = 0;
}
Debugger::Debugger(int level, const char *name, const char *format, ...)
: m_name(name)
{
if (s_debugging && m_name && (s_debug >= level)) {
char buf[64];
::snprintf(buf,sizeof(buf),">>> %s",m_name);
va_list va;
va_start(va,format);
dbg_output(buf,format,va);
va_end(va);
s_indent++;
}
else
m_name = 0;
}
Debugger::~Debugger()
{
if (m_name) {
s_indent--;
if (s_debugging) {
char buf[64];
::snprintf(buf,sizeof(buf),"<<< %s",m_name);
char *format = 0;
va_list va = 0;
dbg_output(buf,format,va);
}
}
}
void Debugger::setOutput(void (*outFunc)(const char *))
{
s_output = outFunc ? outFunc : dbg_stderr_func;
}
void Debugger::setIntOut(void (*outFunc)(const char *))
{
s_intout = outFunc;
}
void Debugger::enableOutput(bool enable)
{
s_debugging = enable;
}
unsigned long long Time::now()
{
struct timeval tv;
return ::gettimeofday(&tv,0) ? 0 : fromTimeval(&tv);
}
unsigned long long Time::fromTimeval(struct timeval *tv)
{
unsigned long long rval = 0;
if (tv) {
// Please keep it this way or the compiler may b0rk
rval = tv->tv_sec;
rval *= 1000000;
rval += tv->tv_usec;
}
return rval;
}
void Time::toTimeval(struct timeval *tv, unsigned long long usec)
{
if (tv) {
tv->tv_usec = usec % 1000000;
tv->tv_sec = usec / 1000000;
}
}
RefObject::~RefObject()
{
#ifndef NDEBUG
if (m_refcount)
Debug(DebugMild,"RefObject [%p] destroyed with count=%d",this,m_refcount);
#endif
}
};

330
engine/Thread.cpp Normal file
View File

@ -0,0 +1,330 @@
/**
* Thread.cpp
* This file is part of the YATE Project http://YATE.null.ro
*/
#include "telengine.h"
#include <unistd.h>
#include <pthread.h>
namespace TelEngine {
class ThreadPrivate : public GenObject {
friend class Thread;
public:
ThreadPrivate(Thread *t,const char *name);
~ThreadPrivate();
void run();
bool cancel();
void cleanup();
void destroy();
void pubdestroy();
static ThreadPrivate *create(Thread *t,const char *name);
static void killall();
static Thread *current();
Thread *m_thread;
pthread_t thread;
bool m_running;
bool m_updest;
const char *m_name;
private:
static void *startFunc(void *arg);
static void cleanupFunc(void *arg);
static void destroyFunc(void *arg);
static void keyAllocFunc();
};
};
using namespace TelEngine;
static pthread_key_t current_key;
static pthread_once_t current_key_once = PTHREAD_ONCE_INIT;
ObjList threads;
Mutex tmutex;
ThreadPrivate *ThreadPrivate::create(Thread *t,const char *name)
{
ThreadPrivate *p = new ThreadPrivate(t,name);
int e = ::pthread_create(&p->thread,0,startFunc,p);
if (e) {
Debug(DebugFail,"Error %d while creating pthread in '%s' [%p]",e,name,p);
p->m_thread = 0;
p->destroy();
return 0;
}
p->m_running = true;
return p;
}
ThreadPrivate::ThreadPrivate(Thread *t,const char *name)
: m_thread(t), m_running(false), m_updest(true), m_name(name)
{
#ifdef DEBUG
Debugger debug("ThreadPrivate::ThreadPrivate","(%p,\"%s\") [%p]",t,name,this);
#endif
Lock lock(tmutex);
threads.append(this);
}
ThreadPrivate::~ThreadPrivate()
{
#ifdef DEBUG
Debugger debug("ThreadPrivate::~ThreadPrivate()"," '%s' [%p]",m_name,this);
#endif
m_running = false;
tmutex.lock();
threads.remove(this,false);
if (m_thread && m_updest) {
Thread *t = m_thread;
m_thread = 0;
delete t;
}
tmutex.unlock();
}
void ThreadPrivate::destroy()
{
#ifdef DEBUG
Debug(DebugAll,"ThreadPrivate::destroy() '%s' [%p]",m_name,this);
#endif
cleanup();
delete this;
}
void ThreadPrivate::pubdestroy()
{
#ifdef DEBUG
Debug(DebugAll,"ThreadPrivate::pubdestroy() '%s' [%p]",m_name,this);
#endif
m_updest = false;
if (!cancel()) {
cleanup();
m_thread = 0;
}
}
void ThreadPrivate::run()
{
#ifdef DEBUG
Debug(DebugAll,"ThreadPrivate::run() '%s' [%p]",m_name,this);
#endif
::pthread_once(&current_key_once,keyAllocFunc);
::pthread_setspecific(current_key,this);
pthread_cleanup_push(cleanupFunc,this);
::pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,0);
while (!m_running)
::usleep(10);
m_thread->run();
pthread_cleanup_pop(1);
}
bool ThreadPrivate::cancel()
{
#ifdef DEBUG
Debug(DebugAll,"ThreadPrivate::cancel() '%s' [%p]",m_name,this);
#endif
bool ret = true;
if (m_running) {
ret = !::pthread_cancel(thread);
if (ret) {
m_running = false;
::usleep(10);
}
}
return ret;
}
void ThreadPrivate::cleanup()
{
#ifdef DEBUG
Debug(DebugAll,"ThreadPrivate::cleanup() '%s' [%p]",m_name,this);
#endif
if (m_thread && m_thread->m_private) {
m_thread->m_private = 0;
m_thread->cleanup();
}
}
Thread *ThreadPrivate::current()
{
ThreadPrivate *t = reinterpret_cast<ThreadPrivate *>(::pthread_getspecific(current_key));
return t ? t->m_thread : 0;
}
void ThreadPrivate::killall()
{
Debugger debug("ThreadPrivate::killall()");
ThreadPrivate *t;
bool sledgehammer = false;
int c = 1;
tmutex.lock();
ObjList *l = &threads;
while (l && (t = static_cast<ThreadPrivate *>(l->get())) != 0)
{
Debug(DebugInfo,"Trying to kill ThreadPrivate '%s' [%p], attempt %d",t->m_name,t,c);
tmutex.unlock();
bool ok = t->cancel();
tmutex.lock();
if (ok)
::usleep(10);
if (t != l->get())
c = 1;
else {
if (ok) {
Debug(DebugGoOn,"Could not kill %p but seems OK to delete it (pthread bug?)",t);
tmutex.unlock();
t->destroy();
tmutex.lock();
continue;
}
::usleep(10);
if (++c >= 10) {
Debug(DebugFail,"Could not kill %p, will use sledgehammer later.",t);
sledgehammer = true;
t->m_thread = 0;
l = l->next();
c = 1;
}
}
}
tmutex.unlock();
// last solution - a REALLY BIG tool!
// usually too big since many libraries have threads of their own...
if (sledgehammer) {
#ifdef __linux__
Debug(DebugFail,"Brutally killing remaining threads!");
::pthread_kill_other_threads_np();
#else
Debug(DebugFail,"Aargh! I cannot kill remaining threads on this platform!");
#endif
}
}
void ThreadPrivate::destroyFunc(void *arg)
{
#ifdef DEBUG
Debugger debug("ThreadPrivate::destroyFunc","(%p)",arg);
#endif
ThreadPrivate *t = reinterpret_cast<ThreadPrivate *>(arg);
if (t)
t->destroy();
}
void ThreadPrivate::cleanupFunc(void *arg)
{
#ifdef DEBUG
Debug(DebugAll,"ThreadPrivate::cleanupFunc(%p)",arg);
#endif
ThreadPrivate *t = reinterpret_cast<ThreadPrivate *>(arg);
if (t)
t->cleanup();
}
void ThreadPrivate::keyAllocFunc()
{
#ifdef DEBUG
Debug(DebugAll,"ThreadPrivate::keyAllocFunc()");
#endif
if (::pthread_key_create(&current_key,destroyFunc))
Debug(DebugGoOn,"Failed to create current thread key!");
}
void *ThreadPrivate::startFunc(void *arg)
{
#ifdef DEBUG
Debug(DebugAll,"ThreadPrivate::startFunc(%p)",arg);
#endif
ThreadPrivate *t = reinterpret_cast<ThreadPrivate *>(arg);
t->run();
return 0;
}
Thread::Thread(const char *name)
: m_private(0)
{
#ifdef DEBUG
Debugger debug("Thread::Thread","(\"%s\") [%p]",name,this);
#endif
m_private = ThreadPrivate::create(this,name);
}
Thread::~Thread()
{
#ifdef DEBUG
Debug(DebugAll,"Thread::~Thread() [%p]",this);
#endif
if (m_private)
m_private->pubdestroy();
}
bool Thread::error() const
{
return !m_private;
}
bool Thread::running() const
{
return m_private ? m_private->m_running : false;
}
Thread *Thread::current()
{
return ThreadPrivate::current();
}
int Thread::count()
{
Lock lock(tmutex);
return threads.count();
}
void Thread::cleanup()
{
#ifdef DEBUG
Debug(DebugAll,"Thread::cleanup() [%p]",this);
#endif
}
void Thread::killall()
{
if (!ThreadPrivate::current())
ThreadPrivate::killall();
}
void Thread::exit()
{
#ifdef DEBUG
Debug(DebugAll,"Thread::exit()");
#endif
::pthread_exit(0);
}
void Thread::cancel()
{
#ifdef DEBUG
Debug(DebugAll,"Thread::cancel() [%p]",this);
#endif
if (m_private)
m_private->cancel();
}
void Thread::yield()
{
::usleep(1);
}
int Thread::fork()
{
// pid_t __fork(void);
return ::fork();
}
void Thread::preExec()
{
#ifdef __linux__
::pthread_kill_other_threads_np();
#endif
}

11
main.cpp Normal file
View File

@ -0,0 +1,11 @@
/**
* main.cpp
* This file is part of the YATE Project http://YATE.null.ro
*/
#include "telengine.h"
extern "C" int main(int argc, const char **argv, const char **environ)
{
return TelEngine::Engine::main(argc,argv,environ);
}

9
modules/.cvsignore Normal file
View File

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

123
modules/Makefile.in Normal file
View File

@ -0,0 +1,123 @@
# Makefile
# This file holds the make rules for the Telephony Engine modules
# 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 :=
CC := g++ -Wall
SED := sed
DEFS :=
INCLUDES := -I@top_srcdir@
CFLAGS := -O2 @MODULE_CFLAGS@
LDFLAGS:= -L.. -lyate
MODFLAGS:= @MODULE_LDFLAGS@
INCFILES := @top_srcdir@/telengine.h @top_srcdir@/telephony.h @top_srcdir@/yateversn.h
SUBDIRS :=
PROGS := cdrbuild.yate cdrfile.yate \
regexroute.yate \
tonegen.yate wavefile.yate \
rmanager.yate extmodule.yate
LIBS :=
ifneq (@HAVE_PGSQL@,no)
PROGS := $(PROGS) pgsqlroute.yate cdrpgsql.yate register.yate
endif
ifneq (@HAVE_PRI@,no)
PROGS := $(PROGS) zapchan.yate
endif
ifneq (@HAVE_H323@,no)
PROGS := $(PROGS) h323chan.yate
endif
ifeq (@HAVE_EXOSIP@_@HAVE_ORTP@_@HAVE_GLIB2@,yes_yes_yes)
PROGS := $(PROGS) sipchan.yate
endif
ifneq (@HAVE_IAX2@,no)
PROGS := $(PROGS) iaxchan.yate
endif
ifneq (@HAVE_GSM@,no)
PROGS := $(PROGS) gsmcodec.yate
endif
LOCALFLAGS =
LOCALLIBS =
COMPILE = $(CC) $(DEFS) $(DEBUG) $(INCLUDES) $(CFLAGS)
LINK = $(CC) $(LDFLAGS)
MODLINK = $(CC) $(MODFLAGS) $(LDFLAGS)
MODCOMP = $(COMPILE) $(MODFLAGS) $(LDFLAGS)
prefix = @prefix@
exec_prefix = @exec_prefix@
moddir = @libdir@/yate
.PHONY: all
all: do-all $(LIBS) $(PROGS)
.PHONY: strip
strip: all do-strip
strip --strip-debug --discard-locals $(PROGS)
.PHONY: clean
clean: do-clean
@echo rm $(PROGS) $(LIBS) *.o core
@rm $(PROGS) $(LIBS) *.o core 2>/dev/null; true
.PHONY: install
install: all do-install
@mkdir -p "$(DESTDIR)$(moddir)/" && \
install $(PROGS) "$(DESTDIR)$(moddir)/"
.PHONY: uninstall
uninstall: do-uninstall
@-for i in $(PROGS) ; do \
rm "$(DESTDIR)$(moddir)/$$i" ; \
done; \
rmdir "$(DESTDIR)$(moddir)"
%.o: @srcdir@/%.cpp $(INCFILES)
$(COMPILE) -c $<
%.o: @srcdir@/%.c
$(COMPILE) -c $<
do-all do-strip do-clean do-install do-uninstall:
$(if $(SUBDIRS),\
@target=`echo $@ | $(SED) -e 's/^do-//'`; \
for i in $(SUBDIRS) ; do \
if test -f ./$$i/Makefile ; then \
$(MAKE) -C ./$$i $${target} || exit 1;\
fi; \
done \
)
Makefile: @srcdir@/Makefile.in ../config.status
cd .. && ./config.status
lib%.so: %.o
$(LINK) -shared -o $@ $^
%.yate: @srcdir@/%.cpp $(INCFILES)
$(MODCOMP) -o $@ $(LOCALFLAGS) $< $(LOCALLIBS)
# Take special care of the modules that depend on optional libs
zapchan.yate: LOCALFLAGS = -lpri
h323chan.yate: LOCALFLAGS = -DPHAS_TEMPLATES -D_REENTRANT -DP_HAS_SEMAPHORES @H323_INC@ @H323_LIB@
pgsqlroute.yate cdrpgsql.yate register.yate: LOCALFLAGS = @PGSQL_INC@ -lpq
sipchan.yate: LOCALFLAGS = @EXOSIP_INC@ @ORTP_INC@ @GLIB2_INC@ @EXOSIP_LIB@ @ORTP_LIB@ @GLIB2_LIB@
iaxchan.yate: LOCALFLAGS = @IAX2_INC@ @IAX2_LIB@
gsmcodec.yate: LOCALLIBS = -lgsm

228
modules/cdrbuild.cpp Normal file
View File

@ -0,0 +1,228 @@
/**
* cdrbuild.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Cdr builder
*/
#include <telengine.h>
#include <telephony.h>
#include <string.h>
using namespace TelEngine;
enum {
CdrRing,
CdrCall,
CdrRinging,
CdrAnswer,
CdrHangup,
CdrDrop,
EngHalt
};
class CdrHandler : public MessageHandler
{
public:
CdrHandler(const char *name, int type)
: MessageHandler(name), m_type(type) { }
virtual bool received(Message &msg);
private:
int m_type;
};
class StatusHandler : public MessageHandler
{
public:
StatusHandler() : MessageHandler("status") { }
virtual bool received(Message &msg);
};
class CdrBuilder : public String
{
public:
CdrBuilder(const char *name, const char *caller, const char *called);
virtual ~CdrBuilder();
void update(int type, unsigned long long val);
inline void setStatus(const char *status)
{ m_status = status; }
String getStatus() const;
static CdrBuilder *find(String &id);
private:
inline static int sec(unsigned long long usec)
{ return (usec + 500000) / 1000000; }
unsigned long long
m_ring,
m_call,
m_ringing,
m_answer,
m_hangup;
String m_caller;
String m_called;
String m_status;
};
static ObjList cdrs;
CdrBuilder::CdrBuilder(const char *name, const char *caller, const char *called)
: String(name), m_caller(caller), m_called(called), m_status("unknown")
{
m_ring = m_call = m_ringing = m_answer = m_hangup = 0;
}
CdrBuilder::~CdrBuilder()
{
if (!m_hangup)
m_hangup = Time::now();
if (!m_ring)
m_ring = m_call;
if (!m_call)
m_call = m_ring;
if (!m_ringing)
m_ringing = m_call;
if (!m_answer)
m_answer = m_hangup;
Message *m = new Message("cdr");
m->addParam("time",String(sec(m_ring)));
m->addParam("chan",c_str());
m->addParam("caller",m_caller);
m->addParam("called",m_called);
m->addParam("duration",String(sec(m_hangup - m_ring)));
m->addParam("billtime",String(sec(m_hangup - m_answer)));
m->addParam("ringtime",String(sec(m_answer - m_ringing)));
m->addParam("status",m_status);
Engine::enqueue(m);
}
String CdrBuilder::getStatus() const
{
String s(m_status);
s << "/" << m_caller << "/" << m_called;
return s;
}
void CdrBuilder::update(int type, unsigned long long val)
{
switch (type) {
case CdrRing:
m_ring = val;
break;
case CdrCall:
m_call = val;
break;
case CdrRinging:
if (!m_ringing)
m_ringing = val;
break;
case CdrAnswer:
m_answer = val;
break;
case CdrHangup:
m_hangup = val;
break;
}
}
CdrBuilder *CdrBuilder::find(String &id)
{
ObjList *l = &cdrs;
for (; l; l=l->next()) {
CdrBuilder *b = static_cast<CdrBuilder *>(l->get());
if (b && (*b == id))
return b;
}
return 0;
}
bool CdrHandler::received(Message &msg)
{
static Mutex mutex;
Lock lock(mutex);
if (m_type == EngHalt) {
cdrs.clear();
return false;
}
String id(msg.getValue("id"));
if (id.null()) {
id = msg.getValue("driver");
id += "/";
id += msg.getValue("span");
id += "/";
id += msg.getValue("channel");
}
CdrBuilder *b = CdrBuilder::find(id);
if (!b && ((m_type == CdrRing) || (m_type == CdrCall))) {
b = new CdrBuilder(id,msg.getValue("caller"),msg.getValue("called"));
cdrs.append(b);
}
if (b) {
const char *s = msg.getValue("status");
if (s)
b->setStatus(s);
b->update(m_type,msg.msgTime().usec());
if (m_type == CdrHangup) {
cdrs.remove(b);
return false;
}
}
else
Debug("CdrBuilder",DebugGoOn,"Got message '%s' for untracked id '%s'",
msg.c_str(),id.c_str());
return false;
};
bool StatusHandler::received(Message &msg)
{
const char *sel = msg.getValue("module");
if (sel && ::strcmp(sel,"cdrbuild"))
return false;
String st("cdrbuild,type=cdr,cdrs=");
st << cdrs.count() << ",[LIST]";
ObjList *l = &cdrs;
for (; l; l=l->next()) {
CdrBuilder *b = static_cast<CdrBuilder *>(l->get());
if (b) {
st << "," << *b << "=" << b->getStatus();
}
}
msg.retValue() << st << "\n";
return false;
}
class CdrBuildPlugin : public Plugin
{
public:
CdrBuildPlugin();
virtual void initialize();
private:
bool m_first;
};
CdrBuildPlugin::CdrBuildPlugin()
: m_first(true)
{
Output("Loaded module CdrBuild");
}
void CdrBuildPlugin::initialize()
{
Output("Initializing module CdrBuild");
if (m_first) {
m_first = false;
Engine::install(new CdrHandler("ring",CdrRing));
Engine::install(new CdrHandler("call",CdrCall));
Engine::install(new CdrHandler("ringing",CdrRinging));
Engine::install(new CdrHandler("answer",CdrAnswer));
Engine::install(new CdrHandler("hangup",CdrHangup));
Engine::install(new CdrHandler("dropcdr",CdrDrop));
Engine::install(new CdrHandler("engine.halt",EngHalt));
Engine::install(new StatusHandler);
}
}
INIT_PLUGIN(CdrBuildPlugin);
/* vi: set ts=8 sw=4 sts=4 noet: */

98
modules/cdrfile.cpp Normal file
View File

@ -0,0 +1,98 @@
/**
* cdrfile.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Write the CDR to a text file
*/
#include <telengine.h>
#include <stdio.h>
using namespace TelEngine;
class CdrFileHandler : public MessageHandler
{
public:
CdrFileHandler(const char *name)
: MessageHandler(name), m_tabs(0), m_file(0) { }
virtual ~CdrFileHandler();
virtual bool received(Message &msg);
void init(const char *fname, bool tabsep);
private:
bool m_tabs;
FILE *m_file;
Mutex m_lock;
};
CdrFileHandler::~CdrFileHandler()
{
Lock lock(m_lock);
if (m_file) {
::fclose(m_file);
m_file = 0;
}
}
void CdrFileHandler::init(const char *fname, bool tabsep)
{
Lock lock(m_lock);
if (m_file)
::fclose(m_file);
m_tabs = tabsep;
m_file = fname ? ::fopen(fname,"a") : 0;
}
bool CdrFileHandler::received(Message &msg)
{
Lock lock(m_lock);
if (m_file) {
const char *format = m_tabs
? "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n"
: "%s,\"%s\",\"%s\",\"%s\",%s,%s,%s,\"%s\"\n";
::fprintf(m_file,format,
c_safe(msg.getValue("time")),
c_safe(msg.getValue("chan")),
c_safe(msg.getValue("caller")),
c_safe(msg.getValue("called")),
c_safe(msg.getValue("billtime")),
c_safe(msg.getValue("ringtime")),
c_safe(msg.getValue("duration")),
c_safe(msg.getValue("status"))
);
::fflush(m_file);
}
return false;
};
class CdrFilePlugin : public Plugin
{
public:
CdrFilePlugin();
virtual void initialize();
private:
CdrFileHandler *m_handler;
};
CdrFilePlugin::CdrFilePlugin()
: m_handler(0)
{
Output("Loaded module CdrFile");
}
void CdrFilePlugin::initialize()
{
Output("Initializing module CdrFile");
Configuration cfg(Engine::configFile("cdrfile"));
const char *file = cfg.getValue("general","file");
if (file && !m_handler) {
m_handler = new CdrFileHandler("cdr");
Engine::install(m_handler);
}
if (m_handler)
m_handler->init(file,cfg.getBoolValue("general","tabs"));
}
INIT_PLUGIN(CdrFilePlugin);
/* vi: set ts=8 sw=4 sts=4 noet: */

129
modules/cdrpgsql.cpp Normal file
View File

@ -0,0 +1,129 @@
/**
* cdrpgsql.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Write the CDR to a PostgreSQL database
*/
#include <telengine.h>
#include <stdio.h>
#include <libpq-fe.h>
using namespace TelEngine;
static PGconn *conn=0;
Mutex dbmutex;
class CdrPgsqlHandler : public MessageHandler
{
public:
CdrPgsqlHandler(const char *name)
: MessageHandler(name) { }
virtual bool received(Message &msg);
private:
};
bool CdrPgsqlHandler::received(Message &msg)
{
// const char *calltime = c_safe(msg.getValue("time"));
const char *channel = c_safe(msg.getValue("channel"));
const char *called = c_safe(msg.getValue("called"));
const char *caller = c_safe(msg.getValue("caller"));
const char *billtime = c_safe(msg.getValue("billtime"));
const char *ringtime = c_safe(msg.getValue("ringtime"));
const char *duration = c_safe(msg.getValue("duration"));
const char *status = c_safe(msg.getValue("status"));
Lock lock(dbmutex);
if (!conn)
return false;
char buffer[2048];
snprintf(buffer,sizeof(buffer),"INSERT INTO cdr"
" (channel,caller,called,billtime,ringtime,duration,status)"
" VALUES ('%s','%s','%s','%s','%s','%s','%s')",
channel,caller,called,billtime,ringtime,duration,status);
PGresult *respgsql = PQexec(conn,buffer);
if (!respgsql || PQresultStatus(respgsql) != PGRES_COMMAND_OK)
Debug(DebugFail,"Failed to insert in database: %s",
PQerrorMessage(conn));
return false;
};
class StatusHandler : public MessageHandler
{
public:
StatusHandler(const char *name, unsigned prio = 1)
: MessageHandler(name,prio) { }
virtual bool received(Message &msg);
};
bool StatusHandler::received(Message &msg)
{
// msg.addParam("mod","cdrpgsql");
msg.retValue() << "CdrPgsql,conn=" << (conn != 0) <<"\n";
return false;
}
class CdrPgsqlPlugin : public Plugin
{
public:
CdrPgsqlPlugin();
~CdrPgsqlPlugin();
virtual void initialize();
private:
CdrPgsqlHandler *m_handler;
};
CdrPgsqlPlugin::CdrPgsqlPlugin()
: m_handler(0)
{
Output("Loaded module CdrFile");
}
CdrPgsqlPlugin::~CdrPgsqlPlugin()
{
if (conn) {
PQfinish(conn);
conn = 0;
}
}
void CdrPgsqlPlugin::initialize()
{
char *pgoptions=NULL,
*pgtty=NULL;
Output("Initializing module Cdr for PostgreSQL");
Configuration cfg(Engine::configFile("cdrpgsql"));
const char *pghost = c_safe(cfg.getValue("general","host","localhost"));
const char *pgport = c_safe(cfg.getValue("general","port","5432"));
const char *dbName = c_safe(cfg.getValue("general","database","yate"));
const char *dbUser = c_safe(cfg.getValue("general","user","postgres"));
const char *dbPass = c_safe(cfg.getValue("general","password"));
Lock lock(dbmutex);
if (conn)
PQfinish(conn);
conn = PQsetdbLogin(pghost,pgport,pgoptions,pgtty,dbName,dbUser,dbPass);
if (PQstatus(conn) == CONNECTION_BAD) {
Debug(DebugFail, "Connection to database '%s' failed.", dbName);
Debug(DebugFail, "%s", PQerrorMessage(conn));
PQfinish(conn);
conn = 0;
return;
}
if (!m_handler) {
Output("Installing Cdr for PostgreSQL handler");
m_handler = new CdrPgsqlHandler("cdr");
Engine::install(m_handler);
Engine::install(new StatusHandler("status"));
}
}
INIT_PLUGIN(CdrPgsqlPlugin);
/* vi: set ts=8 sw=4 sts=4 noet: */

481
modules/extmodule.cpp Normal file
View File

@ -0,0 +1,481 @@
/**
* extmodule.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Some parts of this code have been stolen shamelessly from app_agi.
* I think that AGI is great idea.
*
* External module handler
*/
#include <telengine.h>
#include <telephony.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
using namespace TelEngine;
static Configuration s_cfg;
class ExtModReceiver;
class ExtModSource : public ThreadedSource
{
public:
ExtModSource(int fd);
~ExtModSource();
virtual void run();
private:
int m_fd;
unsigned m_brate;
unsigned m_total;
};
class ExtModConsumer : public DataConsumer
{
public:
ExtModConsumer(int fd);
~ExtModConsumer();
virtual void Consume(const DataBlock &data);
private:
int m_fd;
unsigned m_total;
};
class ExtModChan : public DataEndpoint
{
public:
enum {
DataNone = 0,
DataRead,
DataWrite,
DataBoth
};
ExtModChan(const char *file, int type);
~ExtModChan();
virtual void disconnected();
private:
ExtModReceiver *m_recv;
};
class MsgHolder : public GenObject
{
public:
MsgHolder(Message &msg);
Message &m_msg;
bool m_ret;
String m_id;
bool decode(const char *s);
};
class ExtModReceiver : public MessageReceiver
{
public:
ExtModReceiver(const char *script, const char *args,
int ain = -1, int aout = -1, ExtModChan *chan = 0);
~ExtModReceiver();
virtual bool received(Message &msg, int id);
void processLine(const char *line);
void outputLine(const char *line);
bool create(const char *script, const char *args);
void run();
private:
pid_t m_pid;
int m_in, m_out, m_ain, m_aout;
ExtModChan *m_chan;
String m_script, m_args;
ObjList m_waiting;
};
class ExtThread : public Thread
{
public:
ExtThread(ExtModReceiver *receiver) : Thread("ExtModule"), m_receiver(receiver)
{ }
virtual void run()
{ m_receiver->run(); }
private:
ExtModReceiver *m_receiver;
};
class ExtModHandler : public MessageHandler
{
public:
ExtModHandler(const char *name) : MessageHandler(name) { }
virtual bool received(Message &msg);
};
class ExtModulePlugin : public Plugin
{
public:
ExtModulePlugin();
~ExtModulePlugin();
virtual void initialize();
private:
ExtModHandler *m_handler;
};
ExtModSource::ExtModSource(int fd)
: m_fd(fd), m_brate(16000), m_total(0)
{
Debug(DebugAll,"ExtModSource::ExtModSource(%d) [%p]",fd,this);
if (m_fd >= 0)
start("ExtModSource");
}
ExtModSource::~ExtModSource()
{
Debug(DebugAll,"ExtModSource::~ExtModSource() [%p] total=%u",this,m_total);
if (m_fd >= 0) {
::close(m_fd);
m_fd = -1;
}
}
void ExtModSource::run()
{
DataBlock data(0,480);
int r = 0;
unsigned long long tpos = Time::now();
do {
r = ::read(m_fd,data.data(),data.length());
if (r < 0) {
if (errno == EINTR) {
r = 1;
continue;
}
break;
}
if (r < (int)data.length())
data.assign(data.data(),r);
long long dly = tpos - Time::now();
if (dly > 0) {
#ifdef DEBUG
Debug("ExtModSource",DebugAll,"Sleeping for %lld usec",dly);
#endif
::usleep((unsigned long)dly);
}
Forward(data);
m_total += r;
tpos += (r*1000000ULL/m_brate);
} while (r > 0);
Debug(DebugAll,"ExtModSource [%p] end of data total=%u",this,m_total);
}
ExtModConsumer::ExtModConsumer(int fd)
: m_fd(fd), m_total(0)
{
Debug(DebugAll,"ExtModConsumer::ExtModConsumer(%d) [%p]",fd,this);
}
ExtModConsumer::~ExtModConsumer()
{
Debug(DebugAll,"ExtModConsumer::~ExtModConsumer() [%p] total=%u",this,m_total);
if (m_fd >= 0) {
::close(m_fd);
m_fd = -1;
}
}
void ExtModConsumer::Consume(const DataBlock &data)
{
if ((m_fd >= 0) && !data.null()) {
::write(m_fd,data.data(),data.length());
m_total += data.length();
}
}
ExtModChan::ExtModChan(const char *file, int type)
: DataEndpoint("ExtModule"), m_recv(0)
{
Debug(DebugAll,"ExtModChan::ExtModChan(%d) [%p]",type,this);
int wfifo[2] = { -1, -1 };
int rfifo[2] = { -1, -1 };
switch (type) {
case DataWrite:
case DataBoth:
::pipe(wfifo);
setConsumer(new ExtModConsumer(wfifo[1]));
getConsumer()->deref();
}
switch (type) {
case DataRead:
case DataBoth:
::pipe(rfifo);
setSource(new ExtModSource(rfifo[0]));
getSource()->deref();
}
m_recv = new ExtModReceiver(file,"",wfifo[0],rfifo[1],this);
}
ExtModChan::~ExtModChan()
{
Debug(DebugAll,"ExtModChan::~ExtModChan() [%p]",this);
}
void ExtModChan::disconnected()
{
Debugger debug("ExtModChan::disconnected()"," [%p]",this);
// destruct();
}
MsgHolder::MsgHolder(Message &msg)
: m_msg(msg), m_ret(false)
{
m_id = (int)this;
}
bool MsgHolder::decode(const char *s)
{
return (m_msg.decode(s,m_ret,m_id) == -2);
}
ExtModReceiver::ExtModReceiver(const char *script, const char *args, int ain, int aout, ExtModChan *chan)
: m_pid(-1), m_in(-1), m_out(-1), m_ain(ain), m_aout(aout),
m_chan(chan), m_script(script), m_args(args)
{
Debug(DebugAll,"ExtModReceiver::ExtModReceiver(\"%s\",\"%s\") [%p]",script,args,this);
new ExtThread(this);
}
ExtModReceiver::~ExtModReceiver()
{
Debug(DebugAll,"ExtModReceiver::~ExtModReceiver() [%p] pid=%d",this,m_pid);
/* Give the external script a chance to die gracefully */
::kill(m_pid,SIGTERM);
::close(m_in);
::close(m_out);
if (m_chan)
m_chan->disconnect();
int w = ::waitpid(m_pid, 0, WNOHANG);
if (w == 0)
Debug(DebugWarn, "Process %d has not exited yet?",m_pid);
else if (w < 0)
Debug(DebugWarn, "Failed waitpid on %d: %s",m_pid,strerror(errno));
}
bool ExtModReceiver::received(Message &msg, int id)
{
MsgHolder h(msg);
m_waiting.append(&h);
Debug(DebugAll,"ExtMod [%p] queued message '%s' [%p]",this,msg.c_str(),&msg);
outputLine(msg.encode(h.m_id));
while (m_waiting.find(&h))
Thread::yield();
Debug(DebugAll,"ExtMod [%p] message '%s' [%p] returning %s",this,msg.c_str(),&msg, h.m_ret ? "true" : "false");
return h.m_ret;
}
bool ExtModReceiver::create(const char *script, const char *args)
{
String tmp(script);
int pid;
int ext2yate[2];
int yate2ext[2];
int x;
if (script[0] != '/') {
tmp = s_cfg.getValue("general","scripts_dir","scripts/") + tmp;
}
script = tmp.c_str();
if (::pipe(ext2yate)) {
Debug(DebugWarn, "Unable to create ext->yate pipe: %s",strerror(errno));
return false;
}
if (pipe(yate2ext)) {
Debug(DebugWarn, "unable to create yate->ext pipe: %s", strerror(errno));
::close(ext2yate[0]);
::close(ext2yate[1]);
return false;
}
pid = Thread::fork();
if (pid < 0) {
Debug(DebugWarn, "Failed to fork(): %s", strerror(errno));
return false;
}
if (!pid) {
/* Terminate all other threads if needed */
Thread::preExec();
/* Redirect stdin and out */
::dup2(yate2ext[0], STDIN_FILENO);
::dup2(ext2yate[1], STDOUT_FILENO);
/* Set audio in/out handlers */
if (m_ain != -1)
::dup2(m_ain, STDERR_FILENO+1);
else
::close(STDERR_FILENO+1);
if (m_aout != -1)
::dup2(m_aout, STDERR_FILENO+2);
else
::close(STDERR_FILENO+2);
/* Close everything but stdin/out/error/audio */
for (x=STDERR_FILENO+3;x<1024;x++)
::close(x);
/* Execute script */
::fprintf(stderr, "Execing '%s' '%s'\n", script, args);
::execl(script, script, args, (char *)NULL);
::fprintf(stderr, "Failed to execute '%s': %s\n", script, strerror(errno));
::exit(1);
}
Debug(DebugInfo,"Launched External Script %s", script);
m_in = ext2yate[0];
m_out = yate2ext[1];
/* close what we're not using in the parent */
close(ext2yate[1]);
close(yate2ext[0]);
m_pid = pid;
return true;
}
void ExtModReceiver::run()
{
if (!create(m_script.safe(),m_args.safe())) {
destruct();
return;
}
char buffer[1024];
int posinbuf = 0;
for (;;) {
int readsize = ::read(m_in,buffer+posinbuf,sizeof(buffer)-posinbuf-1);
if (!readsize) {
destruct();
return;
}
else if (readsize < 0) {
Debug("ExtModule",DebugWarn,"Read error %d on %d",errno,m_in);
destruct();
return;
}
int totalsize = readsize + posinbuf;
buffer[totalsize]=0;
for (;;) {
char *eoline = ::strchr(buffer,'\n');
if (!eoline && ((int)::strlen(buffer) < totalsize))
eoline=buffer+::strlen(buffer);
if (!eoline)
break;
*eoline=0;
if (buffer[0])
processLine(buffer);
totalsize -= eoline-buffer+1;
::memmove(buffer,eoline+1,totalsize+1);
}
posinbuf = totalsize;
}
}
void ExtModReceiver::outputLine(const char *line)
{
Debug("ExtModReceiver",DebugInfo,"outputLine '%s'", line);
::write(m_out,line,::strlen(line));
char nl = '\n';
::write(m_out,&nl,sizeof(nl));
}
void ExtModReceiver::processLine(const char *line)
{
Debug("ExtModReceiver",DebugInfo,"processLine '%s'", line);
ObjList *p = &m_waiting;
for (; p; p=p->next()) {
MsgHolder *msg = static_cast<MsgHolder *>(p->get());
if (msg && msg->decode(line)) {
Debug("ExtModReceiver",DebugInfo,"Matched message");
p->remove(false);
return;
}
}
Message m("");
String id;
if (m.decode(line,id) == -2) {
Debug("ExtModReceiver",DebugInfo,"Created message [%p]",this);
outputLine(m.encode(Engine::dispatch(m),id));
Debug("ExtModReceiver",DebugInfo,"Dispatched message [%p]",this);
return;
}
Debug("ExtModReceiver",DebugWarn,"Error: '%s'", line);
}
bool ExtModHandler::received(Message &msg)
{
String dest(msg.getValue("callto"));
if (dest.null())
return false;
Regexp r("^external/\\([^/]*\\)/\\(.*\\)$");
if (!dest.matches(r))
return false;
DataEndpoint *dd = static_cast<DataEndpoint *>(msg.userData());
String t = dest.matchString(1);
int typ = 0;
if (t == "none")
typ = ExtModChan::DataNone;
else if (t == "read")
typ = ExtModChan::DataRead;
else if (t == "write")
typ = ExtModChan::DataWrite;
else if (t == "both")
typ = ExtModChan::DataBoth;
else {
Debug(DebugFail,"Invalid ExtModule method '%s', use 'none', 'read', 'write' or 'both'",
t.c_str());
return false;
}
if (typ != ExtModChan::DataNone && !dd) {
Debug(DebugFail,"ExtMod '%s' call found but no data channel!",t.c_str());
return false;
}
ExtModChan *em = new ExtModChan(dest.matchString(2).c_str(),typ);
if (!em) {
Debug(DebugFail,"Failed to create ExtMod for '%s'",dest.matchString(2).c_str());
return false;
}
if (dd && dd->connect(em))
em->deref();
return true;
}
ExtModulePlugin::ExtModulePlugin()
: m_handler(0)
{
Output("Loaded module ExtModule");
}
ExtModulePlugin::~ExtModulePlugin()
{
Output("Unloading module ExtModule");
}
void ExtModulePlugin::initialize()
{
Output("Initializing module ExtModule");
s_cfg = Engine::configFile("extmodule");
s_cfg.load();
if (!m_handler) {
m_handler = new ExtModHandler("call");
Engine::install(m_handler);
NamedList *list = s_cfg.getSection("scripts");
if (list)
{
unsigned int len = list->length();
for (unsigned int i=0; i<len; i++) {
NamedString *n = list->getParam(i);
if (n) {
new ExtModReceiver(n->name(),*n);
}
}
}
}
}
INIT_PLUGIN(ExtModulePlugin);
/* vi: set ts=8 sw=4 sts=4 noet: */

124
modules/gsmcodec.cpp Normal file
View File

@ -0,0 +1,124 @@
/**
* gsmcodec.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* GSM 6.10 codec using libgsm
*/
#include <telengine.h>
#include <telephony.h>
extern "C" {
#include <gsm.h>
typedef gsm_signal gsm_block[160];
}
using namespace TelEngine;
int count = 0;
class GsmPlugin : public Plugin, public TranslatorFactory
{
public:
GsmPlugin();
~GsmPlugin();
virtual void initialize() { }
virtual DataTranslator *create(const String &sFormat, const String &dFormat);
};
class GsmCodec : public DataTranslator
{
public:
GsmCodec(const char *sFormat, const char *dFormat, bool encoding);
~GsmCodec();
virtual void Consume(const DataBlock &data);
private:
bool m_encoding;
gsm m_gsm;
DataBlock m_data;
};
GsmCodec::GsmCodec(const char *sFormat, const char *dFormat, bool encoding)
: DataTranslator(sFormat,dFormat), m_encoding(encoding), m_gsm(0)
{
Debug(DebugAll,"GsmCodec::GsmCodec(\"%s\",\"%s\",%scoding) [%p]",
sFormat,dFormat, m_encoding ? "en" : "de",this);
count++;
m_gsm = ::gsm_create();
}
GsmCodec::~GsmCodec()
{
Debug(DebugAll,"GsmCodec::~GsmCodec() [%p]",this);
count--;
if (m_gsm) {
gsm temp = m_gsm;
m_gsm = 0;
::gsm_destroy(temp);
}
}
void GsmCodec::Consume(const DataBlock &data)
{
if (!(m_gsm && getTransSource()))
return;
ref();
m_data += data;
DataBlock outdata;
int frames,consumed;
if (m_encoding) {
frames = m_data.length() / sizeof(gsm_block);
consumed = frames * sizeof(gsm_block);
if (frames) {
outdata.assign(0,frames*sizeof(gsm_frame));
for (int i=0; i<frames; i++)
::gsm_encode(m_gsm,
(gsm_signal*)(((gsm_block *)m_data.data())+i),
(gsm_byte*)(((gsm_frame *)outdata.data())+i));
}
}
else {
frames = m_data.length() / sizeof(gsm_frame);
consumed = frames * sizeof(gsm_frame);
if (frames) {
outdata.assign(0,frames*sizeof(gsm_block));
for (int i=0; i<frames; i++)
::gsm_decode(m_gsm,
(gsm_byte*)(((gsm_frame *)m_data.data())+i),
(gsm_signal*)(((gsm_block *)outdata.data())+i));
}
}
#ifdef DEBUG
Debug("GsmCodec",DebugAll,"%scoding %d frames of %d input bytes (consumed %d) in %d output bytes",
m_encoding ? "en" : "de",frames,m_data.length(),consumed,outdata.length());
#endif
if (frames) {
m_data.cut(-consumed);
getTransSource()->Forward(outdata);
}
deref();
}
GsmPlugin::GsmPlugin()
{
Output("Loaded module GSM - based on libgsm-%d.%d.%d",GSM_MAJOR,GSM_MINOR,GSM_PATCHLEVEL);
}
GsmPlugin::~GsmPlugin()
{
Output("Unloading module GSM with %d codecs still in use",count);
}
DataTranslator *GsmPlugin::create(const String &sFormat, const String &dFormat)
{
if (sFormat == "slin" && dFormat == "gsm")
return new GsmCodec(sFormat,dFormat,true);
else if (sFormat == "gsm" && dFormat == "slin")
return new GsmCodec(sFormat,dFormat,false);
else return 0;
}
INIT_PLUGIN(GsmPlugin);
/* vi: set ts=8 sw=4 sts=4 noet: */

944
modules/h323chan.cpp Normal file
View File

@ -0,0 +1,944 @@
/**
* h323chan.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* As a special exception to the GNU General Public License, permission is
* granted for additional uses of the text contained in this release of Yate
* as noted here.
* This exception is that permission is hereby granted to link Yate with the
* OpenH323 and PWLIB runtime libraries to produce an executable image.
*
* H.323 channel
*/
#include <ptlib.h>
#include <h323.h>
#include <h323pdu.h>
#include <ptclib/delaychan.h>
#include <gkserver.h>
/* Guess if codecs are dynamically loaded or linked in */
#if (OPENH323_MAJOR <= 1)
#if (OPENH323_MINOR < 13)
#define OLD_STYLE_CODECS 1
#endif
#endif
#include <telengine.h>
#include <telephony.h>
#include <yateversn.h>
#include <string.h>
using namespace TelEngine;
static Configuration s_cfg;
static ObjList translate;
static TokenDict dict_str2code[] = {
{ "alpha" , PProcess::AlphaCode },
{ "beta" , PProcess::BetaCode },
{ "release" , PProcess::ReleaseCode },
{ 0 , 0 },
};
class H323Process : public PProcess
{
PCLASSINFO(H323Process, PProcess)
H323Process()
: PProcess(
s_cfg.getValue("general","vendor","Null Team"),
s_cfg.getValue("general","product","YATE"),
(unsigned short)s_cfg.getIntValue("general","major",YATE_MAJOR),
(unsigned short)s_cfg.getIntValue("general","minor",YATE_MINOR),
(PProcess::CodeStatus)s_cfg.getIntValue("general","status",dict_str2code,PProcess::AlphaCode),
(unsigned short)s_cfg.getIntValue("general","build",YATE_BUILD)
)
{ Resume(); }
public:
void Main()
{ }
};
class YateH323EndPoint;
class YateGatekeeperServer;
class YateGatekeeperCall : public H323GatekeeperCall
{
PCLASSINFO(YateGatekeeperCall, H323GatekeeperCall);
public:
YateGatekeeperCall(
YateGatekeeperServer & server,
const OpalGloballyUniqueID & callIdentifier, /// Unique call identifier
Direction direction
);
virtual H323GatekeeperRequest::Response OnAdmission(
H323GatekeeperARQ & request
);
};
class TranslateObj : public GenObject
{
public:
H225_TransportAddress_ipAddress ip;
PString alias;
String e164;
};
class YateGatekeeperServer : public H323GatekeeperServer
{
PCLASSINFO(YateGatekeeperServer, H323GatekeeperServer);
public:
YateGatekeeperServer(YateH323EndPoint & ep);
BOOL Init();
H323GatekeeperRequest::Response OnRegistration(
H323GatekeeperRRQ & request);
H323GatekeeperRequest::Response OnUnregistration(
H323GatekeeperURQ & request );
H323GatekeeperCall * CreateCall(const OpalGloballyUniqueID & id,H323GatekeeperCall::Direction dir);
BOOL TranslateAliasAddressToSignalAddress(const H225_AliasAddress & alias,H323TransportAddress & address);
TranslateObj * findAlias(const PString & alias);
virtual BOOL GetUsersPassword(const PString & alias,PString & password) const;
private:
YateH323EndPoint & endpoint;
};
class YateH323AudioSource : public DataSource, public PIndirectChannel
{
PCLASSINFO(YateH323AudioSource, PIndirectChannel)
public:
YateH323AudioSource() { }
// ~YateH323AudioSource() { Debug(DebugAll,"h.323 source [%p] deleted",this); }
virtual BOOL Close();
virtual BOOL IsOpen() const;
virtual BOOL Write(const void *buf, PINDEX len);
private:
PAdaptiveDelay writeDelay;
};
class YateH323AudioConsumer : public DataConsumer, public PIndirectChannel
{
PCLASSINFO(YateH323AudioConsumer, PIndirectChannel)
public:
YateH323AudioConsumer() : m_exit(false) { }
// ~YateH323AudioConsumer() { Debug(DebugAll,"h.323 consumer [%p] deleted",this); }
virtual BOOL Close();
virtual BOOL IsOpen() const;
virtual BOOL Read(void *buf, PINDEX len);
virtual void Consume(const DataBlock &data);
private:
DataBlock m_buffer;
bool m_exit;
Mutex m_mutex;
};
class YateH323EndPoint : public H323EndPoint
{
PCLASSINFO(YateH323EndPoint, H323EndPoint)
public:
YateH323EndPoint();
~YateH323EndPoint();
virtual H323Connection *CreateConnection(unsigned callReference, void *userData,
H323Transport *transport, H323SignalPDU *setupPDU);
bool Init(void);
YateGatekeeperServer *gkServer;
};
class YateH323Connection : public H323Connection, public DataEndpoint
{
PCLASSINFO(YateH323Connection, H323Connection)
public:
YateH323Connection(YateH323EndPoint &endpoint, unsigned callReference, void *userdata);
~YateH323Connection();
virtual H323Connection::AnswerCallResponse OnAnswerCall(const PString &caller,
const H323SignalPDU &signalPDU, H323SignalPDU &connectPDU);
virtual void OnEstablished();
virtual void OnCleared();
virtual BOOL OnAlerting(const H323SignalPDU &alertingPDU, const PString &user);
virtual void OnUserInputTone(char tone, unsigned duration, unsigned logicalChannel, unsigned rtpTimestamp);
virtual void OnUserInputString(const PString &value);
virtual BOOL OpenAudioChannel(BOOL isEncoding, unsigned bufferSize,
H323AudioCodec &codec);
virtual void disconnected();
inline const String &id() const
{ return m_id; }
private:
String m_id;
};
class H323Handler : public MessageHandler
{
public:
H323Handler(const char *name) : MessageHandler(name) { }
virtual bool received(Message &msg);
};
class H323Dropper : public MessageHandler
{
public:
H323Dropper(const char *name) : MessageHandler(name) { }
virtual bool received(Message &msg);
};
class H323Stopper : public MessageHandler
{
public:
H323Stopper(const char *name) : MessageHandler(name) { }
virtual bool received(Message &msg);
};
class H323MsgThread : public Thread
{
public:
H323MsgThread(Message *msg, YateH323Connection *conn)
: Thread("H323MsgThread"), m_msg(msg), m_conn(conn) { }
virtual void run();
private:
Message *m_msg;
YateH323Connection *m_conn;
};
class StatusHandler : public MessageHandler
{
public:
StatusHandler() : MessageHandler("status") { }
virtual bool received(Message &msg);
};
class H323Plugin : public Plugin
{
public:
H323Plugin();
virtual ~H323Plugin();
virtual void initialize();
void cleanup();
YateH323Connection *findConnectionLock(const char *id);
inline YateH323EndPoint *ep()
{ return m_endpoint; }
inline ObjList &calls()
{ return m_calls; }
private:
bool m_first;
ObjList m_calls;
YateH323EndPoint *m_endpoint;
static H323Process *m_process;
};
H323Process *H323Plugin::m_process = 0;
static H323Plugin hplugin;
void H323MsgThread::run()
{
Engine::dispatch(m_msg);
*m_msg = "preroute";
Engine::dispatch(m_msg);
*m_msg = "route";
if (Engine::dispatch(m_msg) && !m_msg->retValue().null()) {
*m_msg = "call";
m_msg->addParam("callto",m_msg->retValue());
m_msg->retValue() = 0;
m_msg->userData(static_cast<DataEndpoint *>(m_conn));
if (Engine::dispatch(m_msg)) {
Debug(DebugInfo,"Routing H.323 [%p] call to '%s'",m_conn,m_msg->getValue("callto"));
m_conn->deref();
m_conn->AnsweringCall(H323Connection::AnswerCallNow);
}
else {
Debug(DebugInfo,"Rejecting unconnected H.323 [%p] call",m_conn);
m_conn->AnsweringCall(H323Connection::AnswerCallDenied);
}
}
else {
Debug(DebugInfo,"Rejecting unrouted H.323 [%p] call",m_conn);
m_conn->AnsweringCall(H323Connection::AnswerCallDenied);
}
delete m_msg;
}
YateGatekeeperServer::YateGatekeeperServer(YateH323EndPoint & ep)
: H323GatekeeperServer(ep),
endpoint(ep)
{
Debug(DebugAll,"YateGatekeeperServer::YateGatekeeperServer() [%p]",this);
}
BOOL YateGatekeeperServer::Init ()
{
SetGatekeeperIdentifier("YATE gatekeeper");
H323TransportAddressArray interfaces;
const char *addr = 0;
int i;
for (i = 1; (addr = s_cfg.getValue("gk",("interface"+String(i)).c_str())); i++){
if (!AddListener(new H323GatekeeperListener(endpoint, *this,s_cfg.getValue("gk","name","YateGatekeeper"),new H323TransportUDP(endpoint,PIPSocket::Address(addr),s_cfg.getIntValue("gk","port",1719),0))))
Debug(DebugFail,"I can't start the listener for address: %s",addr);
}
Debug(DebugInfo,"i = %d",i);
return TRUE;
}
YateH323EndPoint::YateH323EndPoint()
: gkServer(0)
{
Debug(DebugAll,"YateH323EndPoint::YateH323EndPoint() [%p]",this);
}
YateH323EndPoint::~YateH323EndPoint()
{
Debug(DebugAll,"YateH323EndPoint::~YateH323EndPoint() [%p]",this);
RemoveListener(0);
ClearAllCalls(H323Connection::EndedByLocalUser, true);
if (gkServer)
delete gkServer;
}
H323Connection *YateH323EndPoint::CreateConnection(unsigned callReference,
void *userData, H323Transport *transport, H323SignalPDU *setupPDU)
{
return new YateH323Connection(*this,callReference,userData);
}
bool YateH323EndPoint::Init(void)
{
if (s_cfg.getBoolValue("codecs","g711u",true))
#ifdef OLD_STYLE_CODECS
SetCapability(0,0,new H323_G711Capability(H323_G711Capability::muLaw));
#else
AddAllCapabilities(0, 0, "G.711-u*{sw}");
#endif
if (s_cfg.getBoolValue("codecs","g711a",true))
#ifdef OLD_STYLE_CODECS
SetCapability(0,0,new H323_G711Capability(H323_G711Capability::ALaw));
#else
AddAllCapabilities(0, 0, "G.711-A*{sw}");
#endif
if (s_cfg.getBoolValue("codecs","gsm0610",true)) {
#ifdef OLD_STYLE_CODECS
H323_GSM0610Capability *gsmCap = new H323_GSM0610Capability;
SetCapability(0, 0, gsmCap);
gsmCap->SetTxFramesInPacket(4);
#else
AddAllCapabilities(0, 0, "GSM*{sw}");
#endif
}
if (s_cfg.getBoolValue("codecs","speexnarrow",true)) {
#ifdef OLD_STYLE_CODECS
SpeexNarrow3AudioCapability *speex3Cap = new SpeexNarrow3AudioCapability();
SetCapability(0, 0, speex3Cap);
speex3Cap->SetTxFramesInPacket(5);
#else
AddAllCapabilities(0, 0, "Speex*{sw}");
#endif
}
if (s_cfg.getBoolValue("codecs","lpc10",true))
#ifdef OLD_STYLE_CODECS
SetCapability(0, 0, new H323_LPC10Capability(*this));
#else
AddAllCapabilities(0, 0, "LPC*{sw}");
#endif
AddAllUserInputCapabilities(0,1);
PIPSocket::Address addr = INADDR_ANY;
int port = s_cfg.getIntValue("ep","port",1720);
if (s_cfg.getBoolValue("ep","ep",true)) {
H323ListenerTCP *listener = new H323ListenerTCP(*this,addr,port);
if (!(listener && StartListener(listener))) {
Debug(DebugFail,"Unable to start H323 Listener at port %d",port);
if (listener)
delete listener;
return false;
}
const char *ali = s_cfg.getValue("ep","alias","yate");
SetLocalUserName(ali);
if (s_cfg.getBoolValue("ep","gkclient",false)){
const char *p = s_cfg.getValue("ep","password");
if (p) {
SetGatekeeperPassword(p);
Debug(DebugInfo,"Enabling H.235 security access to gatekeeper %s",p);
}
const char *d = s_cfg.getValue("ep","gkip");
const char *a = s_cfg.getValue("ep","gkname");
if (d) {
PString gkName = d;
H323TransportUDP * rasChannel = new H323TransportUDP(*this);
if (SetGatekeeper(gkName, rasChannel))
Debug(DebugInfo,"Connect to gatekeeper ip = %s",d);
else {
Debug(DebugFail,"Unable to connect to gatekeeper ip = %s",d);
if (listener)
listener->Close();
}
} else if (a) {
PString gkIdentifier = a;
if (LocateGatekeeper(gkIdentifier))
Debug(DebugInfo,"Connect to gatekeeper name = %s",a);
else {
Debug(DebugFail,"Unable to connect to gatekeeper name = %s",a);
if (listener)
listener->Close();
}
} else {
if (DiscoverGatekeeper(new H323TransportUDP(*this)))
Debug(DebugInfo,"Find a gatekeeper");
else {
Debug(DebugFail,"Unable to connect to any gatekeeper");
if (listener)
listener->Close();
return false;
}
}
}
}
/* if (s_cfg.getBoolValue("gk","server",true))
{
gkServer = new YateGatekeeperServer(*this);
gkServer->Init();
}
*/
// bool useGk = s_cfg.getBoolean("general","use_gatekeeper");
return true;
}
YateH323Connection::YateH323Connection(YateH323EndPoint &endpoint,
unsigned callReference, void *userdata)
: H323Connection(endpoint,callReference), DataEndpoint("h323")
{
Debug(DebugAll,"YateH323Connection::YateH323Connection(%p,%u,%p) [%p]",
&endpoint,callReference,userdata,this);
m_id = "h323/";
m_id << callReference;
setSource(new YateH323AudioSource);
getSource()->deref();
setConsumer(new YateH323AudioConsumer);
getConsumer()->deref();
DataEndpoint *dd = static_cast<DataEndpoint *>(userdata);
if (dd && connect(dd))
deref();
hplugin.calls().append(this)->setDelete(false);
}
YateH323Connection::~YateH323Connection()
{
Debug(DebugAll,"YateH323Connection::~YateH323Connection() [%p]",this);
hplugin.calls().remove(this,false);
CloseAllLogicalChannels(true);
CloseAllLogicalChannels(false);
}
H323Connection::AnswerCallResponse YateH323Connection::OnAnswerCall(const PString &caller,
const H323SignalPDU &setupPDU, H323SignalPDU &connectPDU)
{
Debug(DebugInfo,"YateH323Connection::OnAnswerCall caller='%s'",(const char *)caller);
Message *m = new Message("ring");
m->addParam("driver","h323");
m->addParam("id",m_id);
const char *s = s_cfg.getValue("incoming","context");
if (s)
m->addParam("context",s);
m->addParam("callername",caller);
s = GetRemotePartyNumber();
Debug(DebugInfo,"GetRemotePartyNumber()='%s'",s);
m->addParam("caller",s ? s : (const char *)("h323/"+caller));
const H225_Setup_UUIE &setup = setupPDU.m_h323_uu_pdu.m_h323_message_body;
const H225_ArrayOf_AliasAddress &adr = setup.m_destinationAddress;
s = adr.GetSize() ? (const char *)H323GetAliasAddressString(adr[0]) : 0;
if (!(s && *s))
s = s_cfg.getValue("incoming","called");
if (s)
m->addParam("called",s);
#if 0
s = GetRemotePartyAddress();
Debug(DebugInfo,"GetRemotePartyAddress()='%s'",s);
if (s)
m->addParam("calledname",s);
#endif
new H323MsgThread(m,this);
return H323Connection::AnswerCallPending;
}
void YateH323Connection::OnEstablished()
{
Debug(DebugInfo,"YateH323Connection::OnEstablished");
if (!HadAnsweredCall())
return;
Message *m = new Message("answer");
m->addParam("driver","h323");
m->addParam("id",m_id);
m->addParam("status","answered");
Engine::enqueue(m);
}
void YateH323Connection::OnCleared()
{
Debug(DebugInfo,"YateH323Connection::OnCleared");
bool ans = HadAnsweredCall();
disconnect();
if (!ans)
return;
Message *m = new Message("hangup");
m->addParam("driver","h323");
m->addParam("id",m_id);
Engine::enqueue(m);
}
BOOL YateH323Connection::OnAlerting(const H323SignalPDU &alertingPDU, const PString &user)
{
Debug(DebugInfo,"YateH323Connection::OnAlerting '%s'",(const char *)user);
Message *m = new Message("ringing");
m->addParam("driver","h323");
m->addParam("id",m_id);
Engine::enqueue(m);
return true;
}
void YateH323Connection::OnUserInputTone(char tone, unsigned duration, unsigned logicalChannel, unsigned rtpTimestamp)
{
Debug(DebugInfo,"YateH323Connection::OnUserInputTone '%c' duration=%u",tone,duration);
}
void YateH323Connection::OnUserInputString(const PString &value)
{
Debug(DebugInfo,"YateH323Connection::OnUserInputString '%s'",(const char *)value);
}
BOOL YateH323Connection::OpenAudioChannel(BOOL isEncoding, unsigned bufferSize,
H323AudioCodec &codec)
{
if (isEncoding) {
// data going TO h.323
if (getConsumer())
return codec.AttachChannel(static_cast<YateH323AudioConsumer *>(getConsumer()),false);
}
else {
// data coming FROM h.323
if (getSource())
return codec.AttachChannel(static_cast<YateH323AudioSource *>(getSource()),false);
}
return false;
}
void YateH323Connection::disconnected()
{
Debugger debug("YateH323Connection::disconnected()");
// we must bypass the normal Yate refcounted destruction as OpenH323 will destroy the object
ref();
if (getSource())
static_cast<YateH323AudioSource *>(getSource())->Close();
if (getConsumer())
static_cast<YateH323AudioConsumer *>(getConsumer())->Close();
ClearCall();
}
BOOL YateH323AudioConsumer::Close()
{
m_exit = true;
return true;
}
BOOL YateH323AudioConsumer::IsOpen() const
{
return !m_exit;
}
void YateH323AudioConsumer::Consume(const DataBlock &data)
{
Lock lock(m_mutex);
if ((m_buffer.length() + data.length()) <= (480*5))
m_buffer += data;
#ifdef DEBUG
else
Debug("YateH323AudioConsumer",DebugAll,"Skipped %u bytes, buffer is full",data.length());
#endif
}
BOOL YateH323AudioConsumer::Read(void *buf, PINDEX len)
{
for (;;) {
Lock lock(m_mutex);
if (len >= (int)m_buffer.length()) {
ref();
Thread::yield();
if (deref() || m_exit || Engine::exiting())
return false;
continue;
}
if (len > 0) {
::memcpy(buf,m_buffer.data(),len);
m_buffer.assign(len+(char *)m_buffer.data(),m_buffer.length()-len);
#ifdef DEBUG
Debug("YateH323AudioConsumer",DebugAll,"Pulled %d bytes from buffer",len);
#endif
break;
}
else
len = 0;
}
lastReadCount = len;
return (len != 0);
}
BOOL YateH323AudioSource::Close()
{
DataSource::clear();
return true;
}
BOOL YateH323AudioSource::IsOpen() const
{
return true;
}
BOOL YateH323AudioSource::Write(const void *buf, PINDEX len)
{
DataBlock data((void *)buf,len,false);
Forward(data);
data.clear(false);
lastWriteCount = len;
writeDelay.Delay(len/16);
return true;
}
TranslateObj * YateGatekeeperServer::findAlias(const PString &alias)
{
ObjList *p = &translate;
for (; p; p=p->next()) {
TranslateObj *t =
static_cast<TranslateObj *>(p->get());
if (t && t->alias == alias)
return t;
}
return 0;
}
BOOL YateGatekeeperServer::GetUsersPassword(const PString & alias,PString & password) const
{
Message *m = new Message("auth");
m->addParam("username",alias);
Engine::dispatch(m);
if (m->retValue() != NULL)
{
password = m->retValue();
return true;
} else
{
return false;
}
}
H323GatekeeperCall * YateGatekeeperServer::CreateCall(const OpalGloballyUniqueID & id,
H323GatekeeperCall::Direction dir)
{
return new YateGatekeeperCall(*this, id, dir);
}
H323GatekeeperRequest::Response YateGatekeeperServer::OnRegistration(H323GatekeeperRRQ & request)
{
// PString request_s = request.GetGatekeeperIdentifier();
// request.rrq.HasOptionalField(H225_RegistrationRequest::e_endpointIdentifier);
PString alias;
PString ips;
PString r ;
for (int j = 0; j < request.rrq.m_terminalAlias.GetSize(); j++) {
alias = H323GetAliasAddressString(request.rrq.m_terminalAlias[j]);
r = H323GetAliasAddressE164(request.rrq.m_terminalAlias[j]);
// PString c = request.GetEndpointIdentifier();
Debug(DebugInfo,"marimea matrici este %d : ",request.rrq.m_callSignalAddress.GetSize());
// H225_TransportAddress_ipAddress ip=request.rrq.m_callSignalAddress[0];
for (int k=0; k<request.rrq.m_callSignalAddress.GetSize();k++)
{
H225_TransportAddress_ipAddress ip=request.rrq.m_callSignalAddress[k];
ips = String(ip.m_ip[0]) + "." + String(ip.m_ip[1]) + "." + String(ip.m_ip[2]) + "." + String(ip.m_ip[3]) + ":" + String((int)ip.m_port) ;
Debug(DebugInfo,"Stringul initial este %s",(const char *)ips);
}
// Debug(DebugInfo,"ip %d.%d.%d.%d:%u",ip.m_ip[0], ip.m_ip[1], ip.m_ip[2], ip.m_ip[3],ip.m_port.GetValue());
// Debug(DebugInfo,"end point user id %s and e164 %s",(const char *)s,(const char *)r); //(const char *)request.m_endpointIdentifier); //request_s.GetLength());
H225_TransportAddress_ipAddress ip=request.rrq.m_callSignalAddress[0];
Message *m = new Message("regist");
m->addParam("username",alias);
m->addParam("techno","h323");
m->addParam("data",ips);
Engine::dispatch(m);
Debug(DebugInfo,"prefix boo registering %s",m->retValue().c_str());
TranslateObj *t = new TranslateObj;
t->ip = ip;
t->alias = alias;
t->e164 = m->retValue();
translate.append(t);
}
/* for (int i = 0; i < request.rrq.m_callSignalAddress.GetSize(); i++) {
Debug(DebugInfo,"end point identifier %d",request.rrq.m_callSignalAddress[i]); //(const char *)request.m_endpointIdentifier); //request_s.GetLength());
}*/
return H323GatekeeperServer::OnRegistration(request);
}
H323GatekeeperRequest::Response YateGatekeeperServer::OnUnregistration(H323GatekeeperURQ & request )
{
/* for (int j = 0; j < request.urq.m_terminalAlias.GetSize(); j++) {
PString s = H323GetAliasAddressString(request.urq.m_terminalAlias[j]);
}*/
// PString s = H323GetAliasAddressString(request.urq.m_callSignalAddress[0]);
PString s = H323GetAliasAddressString(request.urq.m_endpointAlias[0]);
TranslateObj * c = findAlias(s);
Message *m = new Message("unregist");
m->addParam("prefix",c->e164);
Debug(DebugInfo,"prefixh323 %s",c->e164.c_str());
Engine::dispatch(m);
//Debug(DebugInfo,"aliasul descarcat este %s",(const char *)c->alias);
translate.remove(c);
return H323GatekeeperServer::OnUnregistration(request);
}
BOOL YateGatekeeperServer::TranslateAliasAddressToSignalAddress(const H225_AliasAddress & alias,H323TransportAddress & address)
{
PString aliasString = H323GetAliasAddressString(alias);
// TranslateObj *f = new TranslateObj;
//f->alias = aliasString;
//translate.find()
TranslateObj * c = findAlias(aliasString);
// c->alias = aliasString;
//Debug(DebugInfo,"ip-ul corespondent este %d.%d.%d.%d:%u",c->ip.m_ip[0],c->ip.m_ip[1],c->ip.m_ip[2],c->ip.m_ip[3],c->ip.m_port.GetValue());
if (c)
{
Debug(DebugInfo,"alias-ul este %s si cel gasit este %s",(const char *)aliasString,(const char *)c->alias);
// Debug(DebugInfo,"ip-ul corespondent este %d.%d.%d.%d:%u",c->ip.m_ip[0],c->ip.m_ip[1],c->ip.m_ip[2],c->ip.m_ip[3],c->ip.m_port.GetValue());
String s = "ip$" + String(c->ip.m_ip[0]) + "." + String(c->ip.m_ip[1]) + "." + String(c->ip.m_ip[2]) + "." + String(c->ip.m_ip[3]) + ":" + String((int)c->ip.m_port) ;
Debug(DebugInfo,"Stringul este %s",(const char *)s);
/* H323TransportAddress aliasAsTransport = c->aliasaddres;
PIPSocket::Address ip;
WORD port = H323EndPoint::DefaultTcpPort;
if (!aliasAsTransport.GetIpAndPort(ip, port)) {
Debug(DebugInfo,"RAS\tCould not translate %s as host name.",(const char *)aliasString);
return FALSE;
}*/
// H225_TransportAddress ceva = c->aliasaddres;
// address = H323TransportAddress(ceva);
//Debug(DebugInfo,"RAS\tTranslating alias %s to %s, host name",(const char *)aliasString,(const char *)address);
// return TRUE;
address = s.c_str();
return TRUE;
//if (H323GatekeeperServer::TranslateAliasAddressToSignalAddress(alias, address))
}
// if (H323GatekeeperServer::TranslateAliasAddressToSignalAddress(alias, address))
// return TRUE;
return FALSE;
}
YateGatekeeperCall::YateGatekeeperCall(YateGatekeeperServer & gk,
const OpalGloballyUniqueID & id,
Direction dir)
: H323GatekeeperCall(gk, id, dir)
{
}
H323GatekeeperRequest::Response YateGatekeeperCall::OnAdmission(H323GatekeeperARQ & info)
{
/* for (int i = 0; i < info.arq.m_srcInfo.GetSize(); i++) {
PString alias = H323GetAliasAddressString(info.arq.m_srcInfo[i]);
PString d = H323GetAliasAddressString(info.arq.m_destinationInfo[0]);
Debug(DebugInfo,"aliasul in m_srcInfo %s si m_destinationInfo %s",(const char *)alias,(const char *)d);
}
return H323GatekeeperCall::OnAdmission(info);*/
#ifdef TEST_TOKEN
info.acf.IncludeOptionalField(H225_AdmissionConfirm::e_tokens);
info.acf.m_tokens.SetSize(1);
info.acf.m_tokens[0].m_tokenOID = "1.2.36.76840296.1";
info.acf.m_tokens[0].IncludeOptionalField(H235_ClearToken::e_nonStandard);
info.acf.m_tokens[0].m_nonStandard.m_nonStandardIdentifier = "1.2.36.76840296.1.1";
info.acf.m_tokens[0].m_nonStandard.m_data = "SnfYt0jUuZ4lVQv8umRYaH2JltXDRW6IuYcnASVU";
#endif
#ifdef TEST_SLOW_ARQ
if (info.IsFastResponseRequired()) {
if (YateH323GatekeeperCall::OnAdmission(info) == H323GatekeeperRequest::Reject)
return H323GatekeeperRequest::Reject;
return H323GatekeeperRequest::InProgress(5000); // 5 seconds maximum
}
PTimeInterval delay = 500+PRandom::Number()%3500; // Take from 0.5 to 4 seconds
PTRACE(3, "RAS\tTest ARQ delay " << delay);
PThread::Sleep(delay);
return H323GatekeeperRequest::Confirm;
#else
return H323GatekeeperCall::OnAdmission(info);
#endif
}
bool H323Handler::received(Message &msg)
{
String dest(msg.getValue("callto"));
if (dest.null())
return false;
Regexp r("^h323/\\(.*\\)$");
if (!dest.matches(r))
return false;
if (!msg.userData()) {
Debug(DebugFail,"H.323 call found but no data channel!");
return false;
}
Debug(DebugInfo,"Found call to H.323 target='%s'",
dest.matchString(1).c_str());
PString p;
H323Connection *conn = hplugin.ep()->MakeCallLocked(dest.matchString(1).c_str(),p,msg.userData());
if (conn) {
String caller(msg.getValue("caller"));
if (caller.null())
caller = msg.getValue("callername");
else
caller << " [" << s_cfg.getValue("ep","ident","yate") << "]";
if (!caller.null()) {
Debug(DebugInfo,"Setting H.323 caller name to '%s'",caller.c_str());
conn->SetLocalPartyName(caller.c_str());
}
conn->Unlock();
return true;
}
return false;
};
bool H323Dropper::received(Message &msg)
{
String id(msg.getValue("id"));
if (id.null()) {
Debug("H323Dropper",DebugInfo,"Dropping all calls");
ObjList *l = &hplugin.calls();
for (; l; l=l->next()) {
YateH323Connection *c = static_cast<YateH323Connection *>(l->get());
if (c && c->Lock()) {
c->ClearCall(H323Connection::EndedByGatekeeper);
c->Unlock();
}
}
return false;
}
if (!id.startsWith("h323"))
return false;
YateH323Connection *conn = hplugin.findConnectionLock(id);
if (conn) {
Debug("H323Dropper",DebugInfo,"Dropping call '%s' [%p]",conn->id().c_str(),conn);
conn->ClearCall(H323Connection::EndedByGatekeeper);
conn->Unlock();
return true;
}
Debug("H323Dropper",DebugInfo,"Could not find call '%s'",id.c_str());
return false;
};
bool StatusHandler::received(Message &msg)
{
const char *sel = msg.getValue("module");
if (sel && ::strcmp(sel,"h323chan") && ::strcmp(sel,"varchans"))
return false;
String st("h323chan,type=varchans");
st << ",chans=" << hplugin.calls().count() << ",[LIST]";
ObjList *l = &hplugin.calls();
for (; l; l=l->next()) {
YateH323Connection *c = static_cast<YateH323Connection *>(l->get());
if (c && c->Lock()) {
// HACK: we assume transport$address/callref format
String s((const char *)c->GetCallToken());
st << "," << c->id() << "=" << s.substr(0,s.rfind('/'));
c->Unlock();
}
}
msg.retValue() << st << "\n";
return false;
}
bool H323Stopper::received(Message &msg)
{
hplugin.cleanup();
return false;
};
H323Plugin::H323Plugin()
: m_first(true), m_endpoint(0)
{
Output("Loaded module H.323");
}
void H323Plugin::cleanup()
{
if (m_endpoint) {
delete m_endpoint;
m_endpoint = 0;
PSyncPoint terminationSync;
terminationSync.Signal();
Output("Waiting for OpenH323 to die");
terminationSync.Wait();
}
m_calls.clear();
}
H323Plugin::~H323Plugin()
{
cleanup();
if (m_process) {
delete m_process;
m_process = 0;
}
}
YateH323Connection *H323Plugin::findConnectionLock(const char *id)
{
ObjList *l = &m_calls;
for (; l; l=l->next()) {
YateH323Connection *c = static_cast<YateH323Connection *>(l->get());
if (c && c->Lock()) {
if (c->id() == id)
return c;
c->Unlock();
}
}
return 0;
}
void H323Plugin::initialize()
{
Output("Initializing module H.323");
s_cfg = Engine::configFile("h323chan");
s_cfg.load();
if (!m_process)
m_process = new H323Process;
int dbg=s_cfg.getIntValue("general","debug");
if (dbg)
PTrace::Initialise(dbg,0,PTrace::Blocks | PTrace::Timestamp
| PTrace::Thread | PTrace::FileAndLine);
if (!m_endpoint) {
m_endpoint = new YateH323EndPoint;
m_endpoint->Init();
}
if (m_first) {
m_first = false;
Engine::install(new H323Handler("call"));
Engine::install(new H323Dropper("drop"));
Engine::install(new H323Stopper("engine.halt"));
Engine::install(new StatusHandler);
}
}
/* vi: set ts=8 sw=4 sts=4 noet: */

8
modules/mktestlinks.sh Normal file
View File

@ -0,0 +1,8 @@
#!/bin/sh
tests="randcall msgsniff"
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

58
modules/msgsniff.cpp Normal file
View File

@ -0,0 +1,58 @@
/*
test.c
This file holds the entry point of the Telephony Engine
*/
#include <telengine.h>
#include <unistd.h>
using namespace TelEngine;
class MsgSniff : public Plugin
{
public:
MsgSniff();
virtual void initialize();
private:
bool m_first;
};
class SniffHandler : public MessageHandler
{
public:
SniffHandler() : MessageHandler(0,0) { }
virtual bool received(Message &msg);
};
bool SniffHandler::received(Message &msg)
{
Output("Sniffed message '%s' time=%llu thread=%p",
msg.c_str(),msg.msgTime().usec(),Thread::current());
unsigned n = msg.length();
for (unsigned i = 0; i < n; i++) {
NamedString *s = msg.getParam(i);
if (s)
Output(" param['%s']='%s'",s->name().c_str(),s->c_str());
}
return false;
};
MsgSniff::MsgSniff()
: m_first(true)
{
Output("Loaded module MsgSniffer");
}
void MsgSniff::initialize()
{
Output("Initializing module MsgSniffer");
if (m_first) {
m_first = false;
Engine::install(new SniffHandler);
}
}
INIT_PLUGIN(MsgSniff);
/* vi: set ts=8 sw=4 sts=4 noet: */

216
modules/pgsqlroute.cpp Normal file
View File

@ -0,0 +1,216 @@
/**
* pgsqlroute.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Postgres SQL based routing
*/
#include <telengine.h>
#include <telephony.h>
#include <libpq-fe.h>
#include <string.h>
using namespace TelEngine;
static PGconn *conn=0;
static Mutex dbmutex;
static unsigned s_route_rq = 0;
static unsigned s_route_err = 0;
static unsigned s_route_yes = 0;
static unsigned s_route_no = 0;
class RouteHandler : public MessageHandler
{
public:
RouteHandler(const char *name, unsigned prio = 1)
: MessageHandler(name,prio) { }
virtual bool received(Message &msg);
};
bool RouteHandler::received(Message &msg)
{
char buffer[2048];
unsigned long long tmr = Time::now();
String called(msg.getValue("called"));
if (called.null())
return false;
Lock lock(dbmutex);
if (!conn)
return false;
s_route_rq++;
const char *context = c_safe(msg.getValue("context","default"));
snprintf(buffer,sizeof(buffer),"SELECT tehno,data,length (prefix) as lll"
" from route where prefix= substring('%s',1,length(prefix))"
" and context='%s' order by lll desc LIMIT 1",called.c_str(),context);
PGresult *respgsql = PQexec(conn,buffer);
if (!respgsql || PQresultStatus(respgsql) != PGRES_TUPLES_OK)
{
Debug(DebugFail,"Failed to query from database: %s",
PQerrorMessage(conn));
s_route_err++;
return false;
}
if (PQntuples(respgsql) == 0) {
Debug(DebugFail,"No route.");
s_route_no++;
return false;
}
msg.retValue() = String(PQgetvalue(respgsql,0,0))+"/" + String(PQgetvalue(respgsql,0,1));
Debug(DebugInfo,"Routing call to '%s' in context '%s' using '%s' tehnology and data in %llu usec",
called.c_str(),context,msg.retValue().c_str(),Time::now()-tmr);
s_route_yes++;
return true;
};
class PrerouteHandler : public MessageHandler
{
public:
PrerouteHandler(const char *name, unsigned prio = 1)
: MessageHandler(name,prio) { }
virtual bool received(Message &msg);
};
bool PrerouteHandler::received(Message &msg)
{
char buffer[2048];
// char select_called[200];
// char select_channel[200];
unsigned long long tmr = Time::now();
// return immediately if there is already a context
if (msg.getValue("context"))
return false;
String caller(msg.getValue("caller"));
if (caller.null())
return false;
Lock lock(dbmutex);
if (!conn)
return false;
String called(msg.getValue("called"));
if (!caller.null())
// snprintf(select_called,sizeof(select_called),"and called='%s'",called.c_str());
snprintf(buffer,sizeof(buffer),"SELECT context,length (caller) as lll from preroute where caller= substring('%s',1,length(caller)) order by lll desc limit 1;",caller.c_str());
PGresult *respgsql = PQexec(conn,buffer);
if (!respgsql || PQresultStatus(respgsql) != PGRES_TUPLES_OK)
{
Debug(DebugFail,"Failed to query from database: %s",
PQerrorMessage(conn));
return false;
}
if (PQntuples(respgsql) == 0) {
Debug(DebugFail,"No preroute.");
return false;
}
msg.addParam("context",PQgetvalue(respgsql,0,0));
Debug(DebugInfo,"Classifying caller '%s' in context '%s' in %llu usec",
caller.c_str(),msg.getValue("context"),Time::now()-tmr);
return true;
#if 0
NamedList *l = s_cfg.getSection("contexts");
if (l) {
unsigned int len = l->length();
for (unsigned int i=0; i<len; i++) {
NamedString *n = l->getParam(i);
if (n) {
Regexp r(n->name());
if (s.matches(r)) {
msg.addParam("context",s.replaceMatches(*n));
Debug(DebugInfo,"Classifying caller '%s' in context '%s' by rule #%u '%s' in %llu usec",
s.c_str(),msg.getValue("context"),i+1,r.c_str(),Time::now()-tmr);
return true;
}
}
}
}
Debug(DebugInfo,"Could not classify call from '%s', wasted %llu usec",
s.c_str(),Time::now()-tmr);
return false;
#endif
};
class StatusHandler : public MessageHandler
{
public:
StatusHandler(const char *name, unsigned prio = 1)
: MessageHandler(name,prio) { }
virtual bool received(Message &msg);
};
bool StatusHandler::received(Message &msg)
{
const char *sel = msg.getValue("module");
if (sel && ::strcmp(sel,"pgsqlroute"))
return false;
msg.retValue() << "PgSQLroute,conn=" << (conn != 0);
msg.retValue() << ",total=" << s_route_rq << ",errors=" << s_route_err;
msg.retValue() << ",routed=" << s_route_yes << ",noroute=" << s_route_no;
msg.retValue() << "\n";
return false;
}
class PGSQLRoutePlugin : public Plugin
{
public:
PGSQLRoutePlugin();
~PGSQLRoutePlugin();
virtual void initialize();
private:
bool m_first;
};
PGSQLRoutePlugin::PGSQLRoutePlugin()
: m_first(true)
{
Output("Loaded module PGSQLRoute");
}
PGSQLRoutePlugin::~PGSQLRoutePlugin()
{
if (conn) {
PQfinish(conn);
conn = 0;
}
}
void PGSQLRoutePlugin::initialize()
{
char *pgoptions=NULL,
*pgtty=NULL;
Output("Initializing module PGSQLRoute");
Configuration cfg(Engine::configFile("pgsqlroute"));
const char *pghost = c_safe(cfg.getValue("general","host","localhost"));
const char *pgport = c_safe(cfg.getValue("general","port","5432"));
const char *dbName = c_safe(cfg.getValue("general","database","yate"));
const char *dbUser = c_safe(cfg.getValue("general","user","postgres"));
const char *dbPass = c_safe(cfg.getValue("general","password"));
Lock lock(dbmutex);
if (conn)
PQfinish(conn);
conn = PQsetdbLogin(pghost,pgport,pgoptions,pgtty,dbName,dbUser,dbPass);
if (PQstatus(conn) == CONNECTION_BAD) {
Debug(DebugFail, "Connection to database '%s' failed.", dbName);
Debug(DebugFail, "%s", PQerrorMessage(conn));
PQfinish(conn);
conn = 0;
return;
}
// don't bother to install handlers until we are connected
if (m_first && conn) {
m_first = false;
unsigned prio = cfg.getIntValue("general","priority",100);
Engine::install(new PrerouteHandler("preroute",prio));
Engine::install(new RouteHandler("route",prio));
Engine::install(new StatusHandler("status"));
}
}
INIT_PLUGIN(PGSQLRoutePlugin);
/* vi: set ts=8 sw=4 sts=4 noet: */

139
modules/regexroute.cpp Normal file
View File

@ -0,0 +1,139 @@
/**
* regexroute.cpp
*
* This file is part of the YATE Project http://YATE.null.ro
* Regexp based routing
*/
#include <telengine.h>
#include <telephony.h>
#include <string.h>
using namespace TelEngine;
static Configuration s_cfg;
class RouteHandler : public MessageHandler
{
public:
RouteHandler(int prio)
: MessageHandler("route",prio) { }
virtual bool received(Message &msg);
};
bool RouteHandler::received(Message &msg)
{
unsigned long long tmr = Time::now();
String s(msg.getValue("called"));
if (s.null())
return false;
const char *context = msg.getValue("context","default");
NamedList *l = s_cfg.getSection(context);
if (l) {
unsigned int len = l->length();
for (unsigned int i=0; i<len; i++) {
NamedString *n = l->getParam(i);
if (n) {
Regexp r(n->name());
if (s.matches(r)) {
msg.retValue() = s.replaceMatches(*n);
Debug(DebugInfo,"Routing call to '%s' in context '%s' via `%s' by rule #%u '%s' in %llu usec",
s.c_str(),context,msg.retValue().c_str(),i+1,r.c_str(),Time::now()-tmr);
return true;
}
}
}
}
Debug(DebugInfo,"Could not route call to '%s' in context '%s', wasted %llu usec",
s.c_str(),context,Time::now()-tmr);
return false;
};
class PrerouteHandler : public MessageHandler
{
public:
PrerouteHandler(int prio)
: MessageHandler("preroute",prio) { }
virtual bool received(Message &msg);
};
bool PrerouteHandler::received(Message &msg)
{
unsigned long long tmr = Time::now();
// return immediately if there is already a context
if (msg.getValue("context"))
return false;
// String s(msg.getValue("caller"));
String s(msg.getValue("driver")); s+="/";
s+=msg.getValue("span"); s+="/";
s+=msg.getValue("channel"); s+="/";
s+=msg.getValue("caller");
if (s.null())
return false;
NamedList *l = s_cfg.getSection("contexts");
if (l) {
unsigned int len = l->length();
for (unsigned int i=0; i<len; i++) {
NamedString *n = l->getParam(i);
if (n) {
Regexp r(n->name());
if (s.matches(r)) {
msg.addParam("context",s.replaceMatches(*n));
Debug(DebugInfo,"Classifying caller '%s' in context '%s' by rule #%u '%s' in %llu usec",
s.c_str(),msg.getValue("context"),i+1,r.c_str(),Time::now()-tmr);
return true;
}
}
}
}
Debug(DebugInfo,"Could not classify call from '%s', wasted %llu usec",
s.c_str(),Time::now()-tmr);
return false;
};
class RegexRoutePlugin : public Plugin
{
public:
RegexRoutePlugin();
virtual void initialize();
private:
MessageHandler *m_preroute, *m_route;
};
RegexRoutePlugin::RegexRoutePlugin()
: m_preroute(0), m_route(0)
{
Output("Loaded module RegexRoute");
}
void RegexRoutePlugin::initialize()
{
Output("Initializing module RegexRoute");
s_cfg = Engine::configFile("regexroute");
s_cfg.load();
if (m_preroute) {
delete m_preroute;
m_preroute = 0;
}
if (m_route) {
delete m_route;
m_route = 0;
}
unsigned priority = s_cfg.getIntValue("priorities","preroute",100);
if (priority) {
m_preroute = new PrerouteHandler(priority);
Engine::install(m_preroute);
}
priority = s_cfg.getIntValue("priorities","route",100);
if (priority) {
m_route = new RouteHandler(priority);
Engine::install(m_route);
}
}
INIT_PLUGIN(RegexRoutePlugin);
/* vi: set ts=8 sw=4 sts=4 noet: */

279
modules/register.cpp Normal file
View File

@ -0,0 +1,279 @@
/* register.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Ask for a registration from this module.
*/
#include <telengine.h>
#include <stdio.h>
#include <libpq-fe.h>
using namespace TelEngine;
static PGconn *conn=0;
Mutex dbmutex;
static unsigned s_route_rq = 0;
static unsigned s_route_err = 0;
static unsigned s_route_yes = 0;
static unsigned s_route_no = 0;
class AuthHandler : public MessageHandler
{
public:
AuthHandler(const char *name)
: MessageHandler(name) { }
virtual bool received(Message &msg);
};
class RegistHandler : public MessageHandler
{
public:
RegistHandler(const char *name)
: MessageHandler(name) { }
virtual bool received(Message &msg);
};
class UnRegistHandler : public MessageHandler
{
public:
UnRegistHandler(const char *name)
: MessageHandler(name) { }
virtual bool received(Message &msg);
};
class RouteHandler : public MessageHandler
{
public:
RouteHandler(const char *name)
: MessageHandler(name) { }
virtual bool received(Message &msg);
};
class StatusHandler : public MessageHandler
{
public:
StatusHandler(const char *name, unsigned prio = 1)
: MessageHandler(name,prio) { }
virtual bool received(Message &msg);
};
class RegistThread : public Thread
{
public:
RegistThread();
~RegistThread();
void run(void);
};
class RegistPlugin : public Plugin
{
public:
RegistPlugin();
~RegistPlugin();
virtual void initialize();
private:
AuthHandler *m_authhandler;
RegistHandler *m_registhandler;
UnRegistHandler *m_unregisthandler;
RouteHandler *m_routehandler;
StatusHandler *m_statushandler;
};
bool AuthHandler::received(Message &msg)
{
// const char *calltime = c_safe(msg.getValue("time"));
const char *username = c_safe(msg.getValue("username"));
Lock lock(dbmutex);
if (!conn)
return false;
char buffer[2048];
snprintf(buffer,sizeof(buffer),"SELECT password FROM register WHERE username='%s'",username);
PGresult *respgsql = PQexec(conn,buffer);
if (!respgsql || PQresultStatus(respgsql) != PGRES_TUPLES_OK)
{
Debug(DebugFail,"Failed to query from database: %s",
PQerrorMessage(conn));
return false;
}
if (PQntuples(respgsql) == 0) {
Debug(DebugFail,"No user.");
return false;
}
msg.retValue() << PQgetvalue(respgsql,0,0);
return true;
};
bool RegistHandler::received(Message &msg)
{
// const char *calltime = c_safe(msg.getValue("time"));
const char *username = c_safe(msg.getValue("username"));
const char *techno = c_safe(msg.getValue("techno"));
const char *data = c_safe(msg.getValue("data"));
Lock lock(dbmutex);
if (!conn)
return false;
char buffer[2048];
snprintf(buffer,sizeof(buffer),"SELECT credit,price,e164,context FROM register WHERE username='%s'",username);
PGresult *respgsql = PQexec(conn,buffer);
if (!respgsql || PQresultStatus(respgsql) != PGRES_TUPLES_OK)
{
Debug(DebugFail,"Failed to query from database: %s",
PQerrorMessage(conn));
return false;
}
if (PQntuples(respgsql) == 0) {
Debug(DebugFail,"No credit.");
return false;
}
const char *credit = PQgetvalue(respgsql,0,0);
const char *price = PQgetvalue(respgsql,0,1);
const char *prefix = PQgetvalue(respgsql,0,2);
const char *context = PQgetvalue(respgsql,0,3);
snprintf(buffer,sizeof(buffer),"INSERT INTO routepaid (context,prefix,tehno,data,price) VALUES ('%s','%s','%s','%s',%s);",context,prefix,techno,data,price);
PGresult *respgsql1 = PQexec(conn,buffer);
if (!respgsql1 || PQresultStatus(respgsql1) != PGRES_COMMAND_OK)
Debug(DebugFail,"Failed to insert in database: %s",
PQerrorMessage(conn));
msg.retValue() = prefix;
Debug(DebugInfo,"prefix in register este %s",prefix);
return true;
};
bool UnRegistHandler::received(Message &msg)
{
const char *prefix = c_safe(msg.getValue("prefix"));
Debug(DebugInfo,"prefix=%s",prefix);
Lock lock(dbmutex);
if (!conn)
return false;
char buffer[2048];
snprintf(buffer,sizeof(buffer),"DELETE from routepaid WHERE prefix='%s'",prefix);
PGresult *respgsql = PQexec(conn,buffer);
if (!respgsql || PQresultStatus(respgsql) != PGRES_TUPLES_OK)
{
Debug(DebugFail,"Failed to query from database: %s",
PQerrorMessage(conn));
return false;
}
if (PQntuples(respgsql) == 0) {
Debug(DebugFail,"No user.");
return false;
}
return true;
};
bool RouteHandler::received(Message &msg)
{
char buffer[2048];
unsigned long long tmr = Time::now();
String called(msg.getValue("called"));
if (called.null())
return false;
Lock lock(dbmutex);
if (!conn)
return false;
s_route_rq++;
const char *context = c_safe(msg.getValue("context","default"));
snprintf(buffer,sizeof(buffer),"SELECT tehno,data,length (prefix) as lll,price"
" from routepaid where prefix= substring('%s',1,length(prefix))"
" and context='%s' order by lll desc LIMIT 1",called.c_str(),context);
PGresult *respgsql = PQexec(conn,buffer);
if (!respgsql || PQresultStatus(respgsql) != PGRES_TUPLES_OK)
{
Debug(DebugFail,"Failed to query from database: %s",
PQerrorMessage(conn));
s_route_err++;
return false;
}
if (PQntuples(respgsql) == 0) {
Debug(DebugFail,"No route.");
s_route_no++;
return false;
}
msg.retValue() = String(PQgetvalue(respgsql,0,0))+"/" + String(PQgetvalue(respgsql,0,1));
Debug(DebugInfo,"Routing call to '%s' in context '%s' using '%s' tehnology and data in %llu usec",
called.c_str(),context,msg.retValue().c_str(),Time::now()-tmr);
s_route_yes++;
return true;
};
bool StatusHandler::received(Message &msg)
{
msg.retValue() << "Register,conn=" << (conn != 0) <<"\n";
return false;
}
RegistPlugin::RegistPlugin()
: m_authhandler(0),m_registhandler(0),m_routehandler(0),m_statushandler(0)
{
Output("Loaded module Registration");
}
RegistPlugin::~RegistPlugin()
{
if (conn) {
PQfinish(conn);
conn = 0;
}
}
void RegistPlugin::initialize()
{
char *pgoptions=NULL,
*pgtty=NULL;
Output("Initializing module Register for PostgreSQL");
Configuration cfg(Engine::configFile("register"));
const char *pghost = c_safe(cfg.getValue("general","host","localhost"));
const char *pgport = c_safe(cfg.getValue("general","port","5432"));
const char *dbName = c_safe(cfg.getValue("general","database","yate"));
const char *dbUser = c_safe(cfg.getValue("general","user","postgres"));
const char *dbPass = c_safe(cfg.getValue("general","password"));
Lock lock(dbmutex);
if (conn)
PQfinish(conn);
conn = PQsetdbLogin(pghost,pgport,pgoptions,pgtty,dbName,dbUser,dbPass);
if (PQstatus(conn) == CONNECTION_BAD) {
Debug(DebugFail, "Connection to database '%s' failed.", dbName);
Debug(DebugFail, "%s", PQerrorMessage(conn));
PQfinish(conn);
conn = 0;
return;
}
if (!m_registhandler) {
Output("Installing Registering handler");
Engine::install(new RegistHandler("regist"));
}
if (!m_unregisthandler) {
Output("Installing UnRegistering handler");
Engine::install(new UnRegistHandler("unregist"));
}
if (!m_authhandler) {
Output("Installing Authentification handler");
Engine::install(new AuthHandler("auth"));
}
if (!m_routehandler) {
Output("Installing Route handler");
Engine::install(new RouteHandler("route"));
}
if (!m_statushandler) {
Output("Installing Status handler");
Engine::install(new StatusHandler("status"));
}
}
INIT_PLUGIN(RegistPlugin);
/* vi: set ts=8 sw=4 sts=4 noet: */

457
modules/rmanager.cpp Normal file
View File

@ -0,0 +1,457 @@
/**
* rmanager.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* This module gets the messages from YATE out so anyone can use an
* administrating interface.
*/
#include <telengine.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
using namespace TelEngine;
static const char s_helpmsg[] =
"Available commands:\n"
" debug [level|on|off]\n"
" machine [on|off]\n"
" status [module]\n"
" drop {chan|*|all}\n"
" call chan target\n"
" reload\n"
" quit\n"
" stop [exitcode]\n";
static Configuration s_cfg;
/* I need this here because i'm gonna use it in both classes */
int sock = -1;
//we gonna create here the list with all the new connections.
static ObjList connectionlist;
class RManagerThread : public Thread
{
public:
RManagerThread() : Thread("RManager Listener") { }
virtual void run();
private:
};
class Connection : public GenObject, public Thread
{
public:
Connection(int sock);
~Connection();
virtual void run();
void processLine(const char *line);
void write(const char *str, int len = -1);
void writeDebug(const char *str);
void write(Message &msg,bool received);
inline void write(const String &s)
{ write(s.safe(),s.length()); }
static Connection *checkCreate(int sock);
private:
int m_socket;
bool m_debug;
bool m_machine;
};
class RManager : public Plugin
{
public:
RManager();
~RManager();
virtual void initialize();
private:
bool m_first;
};
static void dbg_remote_func(const char *buf)
{
ObjList *p = &connectionlist;
for (; p; p=p->next()) {
Connection *con = static_cast<Connection *>(p->get());
if (con)
con->writeDebug(buf);
}
}
void RManagerThread::run()
{
for (;;)
{
struct sockaddr_in sin;
int sinlen = sizeof(sin);
int as = ::accept(sock, (struct sockaddr *)&sin, (socklen_t *)&sinlen);
if (as < 0) {
Debug("RManager",DebugWarn, "Accept error: %s\n", strerror(errno));
continue;
} else {
if (Connection::checkCreate(as))
Debug("RManager",DebugInfo,"Connection established from %s:%u",inet_ntoa(sin.sin_addr),ntohs(sin.sin_port));
else {
Debug("RManager",DebugWarn,"Connection rejected for %s:%u",inet_ntoa(sin.sin_addr),ntohs(sin.sin_port));
::close(as);
}
}
}
}
Connection *Connection::checkCreate(int sock)
{
// should check IP address here
return new Connection(sock);
}
Connection::Connection(int sock)
: Thread("RManager Connection"), m_socket(sock), m_debug(false), m_machine(false)
{
const char *hdr = s_cfg.getValue("general","header","YATE (http://YATE.null.ro) ready.");
if (hdr) {
write(hdr);
write("\n");
}
connectionlist.append(this);
}
Connection::~Connection()
{
m_debug = false;
connectionlist.remove(this,false);
::close(m_socket);
}
void Connection::run()
{
if (::fcntl(m_socket,F_SETFL,O_NONBLOCK)) {
Debug("RManager",DebugFail, "Failed to set tcp socket to nonblocking mode: %s\n", strerror(errno));
return;
}
// For the sake of responsiveness try to turn off the tcp assembly timer
int arg = 1;
if (::setsockopt(m_socket, SOL_SOCKET, TCP_NODELAY, (char *)&arg, sizeof(arg) ) < 0)
Debug("RManager",DebugWarn, "Failed to set tcp socket to TCP_NODELAY mode: %s\n", strerror(errno));
struct timeval timer;
char buffer[300];
int posinbuf = 0;
while (posinbuf < (int)sizeof(buffer)-1) {
timer.tv_sec = 0;
timer.tv_usec = 30000;
fd_set readfd;
FD_ZERO(&readfd);
FD_SET(m_socket, &readfd);
fd_set errorfd;
FD_ZERO(&errorfd);
FD_SET(m_socket, &errorfd);
int c = ::select(m_socket+1,&readfd,0,&errorfd,&timer);
// Debug(DebugInfo,"trec pasul 1");
if (c > 0) {
if (FD_ISSET(m_socket,&errorfd)) {
Debug("RManager",DebugInfo,"Socket exception condition on %d",m_socket);
return;
}
int readsize = ::read(m_socket,buffer+posinbuf,sizeof(buffer)-posinbuf-1);
if (!readsize) {
Debug("RManager",DebugInfo,"Socket condition EOF on %d",m_socket);
return;
}
else if (readsize > 0) {
int totalsize = readsize + posinbuf;
buffer[totalsize]=0;
#ifdef DEBUG
Debug("RManager",DebugInfo,"read=%d pos=%d buffer='%s'",readsize,posinbuf,buffer);
#endif
for (;;) {
// Try to accomodate various telnet modes
char *eoline = ::strchr(buffer,'\r');
if (!eoline)
eoline = ::strchr(buffer,'\n');
if (!eoline && ((int)::strlen(buffer) < totalsize))
eoline=buffer+::strlen(buffer);
if (!eoline)
break;
*eoline=0;
if (buffer[0])
processLine(buffer);
totalsize -= eoline-buffer+1;
::memmove(buffer,eoline+1,totalsize+1);
}
posinbuf = totalsize;
}
else if ((readsize < 0) && (errno != EINTR) && (errno != EAGAIN)) {
Debug("RManager",DebugWarn,"Socket read error %d on %d",errno,m_socket);
return;
}
}
else if ((c < 0) && (errno != EINTR)) {
Debug("RManager",DebugWarn,"socket select error %d on %d",errno,m_socket);
return;
}
}
}
static bool startSkip(String &s, const char *keyword)
{
if (s.startsWith(keyword,true)) {
s >> keyword;
s.trimBlanks();
return true;
}
return false;
}
void Connection::processLine(const char *line)
{
#ifdef DEBUG
Debug("RManager",DebugInfo,"processLine = %s",line);
#endif
String str(line);
str.trimBlanks();
if (str.null())
return;
if (startSkip(str,"status"))
{
Message m("status");
if (!str.null()) {
m.addParam("module",str);
str = ":" + str;
}
Engine::dispatch(m);
str = "%%+status" + str + "\n";
str << m.retValue() << "%%-status\n";
write(str);
}
else if (startSkip(str,"drop"))
{
if (str.null()) {
write(m_machine ? "%%=drop:fail=noarg\n" : "You must specify what connection to drop!\n");
return;
}
Message m("drop");
bool all = false;
if (str == "*" || str == "all") {
all = true;
str = "all calls";
}
else
m.addParam("id",str);
if (Engine::dispatch(m))
str = (m_machine ? "%%=drop:success:" : "Dropped ") + str + "\n";
else if (all)
str = (m_machine ? "%%=drop:unknown:" : "Tried to drop ") + str + "\n";
else
str = (m_machine ? "%%=drop:fail:" : "Could not drop ") + str + "\n";
write(str);
}
else if (startSkip(str,"call"))
{
int pos = str.find(' ');
if (pos <= 0) {
write(m_machine ? "%%=call:fail=noarg\n" : "You must specify source and target!\n");
return;
}
Message m("call");
m.addParam("callto",str.substr(0,pos));
m.addParam("target",str.substr(pos+1));
if (Engine::dispatch(m))
str = (m_machine ? "%%=call:success:" : "Called ") + str + "\n";
else
str = (m_machine ? "%%=call:fail:" : "Could not call ") + str + "\n";
write(str);
}
else if (startSkip(str,"debug"))
{
if (startSkip(str,"level")) {
int dbg = debugLevel();
str >> dbg;
dbg = debugLevel(dbg);
}
else
str >> m_debug;
if (m_machine) {
str = "%%=debug:level=";
str << debugLevel() << ":local=" << m_debug << "\n";
}
else {
str = "Debug level: ";
str << debugLevel() << " local: " << (m_debug ? "on\n" : "off\n");
}
write(str);
}
else if (startSkip(str,"machine"))
{
str >> m_machine;
str = "Machine mode: ";
str += (m_machine ? "on\n" : "off\n");
write(str);
}
else if (startSkip(str,"reload"))
{
write(m_machine ? "%%=reload\n" : "Reinitializing...\n");
Engine::init();
}
else if (startSkip(str,"quit"))
{
write(m_machine ? "%%=quit\n" : "Goodbye!\n");
cancel();
}
else if (startSkip(str,"stop"))
{
unsigned code = 0;
str >> code;
write(m_machine ? "%%=shutdown\n" : "Engine shutting down - bye!\n");
Engine::halt(code);
}
else if (startSkip(str,"help") || startSkip(str,"?"))
{
Message m("help");
if (!str.null())
{
m.addParam("command",str);
if (Engine::dispatch(m))
write(m.retValue());
else
write("No help for '"+str+"'\n");
}
else
{
m.retValue() = s_helpmsg;
Engine::dispatch(m);
write(m.retValue());
}
}
else
{
Message m("command");
m.addParam("line",str);
if (Engine::dispatch(m))
write(m.retValue());
else
write((m_machine ? "%%=syntax:" : "Cannot understand: ") + str + "\n");
}
}
void Connection::write(Message &msg,bool received)
{
if (!m_machine)
return;
String s = msg.encode(received,"");
s << "\n";
write(s.c_str());
}
void Connection::writeDebug(const char *str)
{
if (m_debug && str && *str)
write(str,::strlen(str));
}
void Connection::write(const char *str, int len)
{
if (len < 0)
len = ::strlen(str);
if (int written = ::write(m_socket,str,len) != len) {
Debug("RManager",DebugInfo,"Socket %d wrote only %d out of %d bytes",m_socket,written,len);
// Destroy the thread, will kill the connection
cancel();
}
}
static void postHook(Message &msg, bool received)
{
ObjList *p = &connectionlist;
for (; p; p=p->next()) {
Connection *con = static_cast<Connection *>(p->get());
if (con)
con->write(msg,received);
}
};
RManager::RManager()
: m_first(true)
{
Output("Loaded module RManager");
Debugger::setIntOut(dbg_remote_func);
}
RManager::~RManager()
{
Output("Unloading module RManager");
if (sock != -1) {
::close(sock);
sock = -1;
}
Engine::self()->setHook();
Debugger::setIntOut(0);
}
void RManager::initialize()
{
Output("Initializing module RManager");
s_cfg = Engine::configFile("rmanager");
s_cfg.load();
if (sock >= 0)
return;
/* configuration */
int port = s_cfg.getIntValue("general","port",5038);
const char *host = c_safe(s_cfg.getValue("general","addr","127.0.0.1"));
if (!(port && *host))
return;
/* starting the socket */
struct sockaddr_in bindaddr;
sock = socket(AF_INET, SOCK_STREAM, 0);
bindaddr.sin_family = AF_INET;
bindaddr.sin_addr.s_addr = inet_addr(host);
bindaddr.sin_port = htons(port);
if (sock < 0) {
Debug("RManager",DebugFail,"Unable to create the listening socket: %s",strerror(errno));
return;
}
const int reuseFlag = 1;
::setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,(const char*)&reuseFlag,sizeof reuseFlag);
if (::bind(sock, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) < 0) {
Debug("RManager",DebugFail,"Failed to bind to %s:%u : %s",inet_ntoa(bindaddr.sin_addr),ntohs(bindaddr.sin_port),strerror(errno));
::close(sock);
sock = -1;
return;
}
if (listen(sock, 2)) {
Debug("RManager",DebugFail,"Unable to listen on socket: %s\n", strerror(errno));
::close(sock);
sock = -1;
return;
}
// don't bother to install handlers until we are listening
if (m_first) {
m_first = false;
Engine::self()->setHook(postHook);
new RManagerThread;
}
}
INIT_PLUGIN(RManager);
/* vi: set ts=8 sw=4 sts=4 noet: */

299
modules/tonegen.cpp Normal file
View File

@ -0,0 +1,299 @@
/**
* tonegen.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Tones generator
*/
#include <telengine.h>
#include <telephony.h>
#include <unistd.h>
#include <string.h>
using namespace TelEngine;
static ObjList tones;
static ObjList chans;
typedef struct {
int nsamples;
const short *data;
} Tone;
class ToneSource : public ThreadedSource
{
public:
~ToneSource();
virtual void run();
inline const String &name()
{ return m_name; }
static ToneSource *getTone(const String &tone);
private:
ToneSource(const String &tone);
static Tone *getBlock(const String &tone);
String m_name;
Tone *m_tone;
unsigned m_brate;
unsigned m_total;
unsigned long long m_time;
};
class ToneChan : public DataEndpoint
{
public:
ToneChan(const String &tone);
~ToneChan();
virtual void disconnected();
};
class ToneHandler : public MessageHandler
{
public:
ToneHandler(const char *name) : MessageHandler(name) { }
virtual bool received(Message &msg);
};
class StatusHandler : public MessageHandler
{
public:
StatusHandler() : MessageHandler("status") { }
virtual bool received(Message &msg);
};
class ToneGenPlugin : public Plugin
{
public:
ToneGenPlugin();
~ToneGenPlugin();
virtual void initialize();
private:
ToneHandler *m_handler;
};
// 421.052Hz (19 samples @ 8kHz) sine wave, pretty close to standard 425Hz
static short tone421hz[] = {
19,
3246,6142,8371,9694,9965,9157,7357,4759,1645,
-1645,-4759,-7357,-9157,-9965,-9694,-8371,-6142,-3246,
0 };
static short get_sample(const short *data, int index)
{
return data ? data[1+(index % data[0])] : 0;
}
static short get_sample(const Tone *data, int index)
{
const Tone *d = data;
while (index >= d->nsamples) {
index -= d->nsamples;
d++;
if (!d->nsamples)
d = data;
}
return get_sample(d->data,index);
}
static int get_length(const Tone *data)
{
int len = 0;
for (; data->nsamples; data++)
len += data->nsamples;
return len;
}
static Tone t_dial[] = { { 8000, tone421hz }, { 0, 0 } };
static Tone t_busy[] = { { 4000, tone421hz }, { 4000, 0 }, { 0, 0 } };
static Tone t_specdial[] = { { 7600, tone421hz }, { 400, 0 }, { 0, 0 } };
static Tone t_ring[] = { { 8000, tone421hz }, { 32000, 0 }, { 0, 0 } };
ToneSource::ToneSource(const String &tone)
: m_name(tone), m_tone(0), m_brate(16000), m_total(0), m_time(0)
{
Debug(DebugAll,"ToneSource::ToneSource(\"%s\") [%p]",tone.c_str(),this);
m_tone = getBlock(tone);
tones.append(this);
if (m_tone)
start("ToneSource");
}
ToneSource::~ToneSource()
{
Debug(DebugAll,"ToneSource::~ToneSource() [%p] total=%u",this,m_total);
if (m_time) {
m_time = Time::now() - m_time;
m_time = (m_total*1000000ULL + m_time/2) / m_time;
Debug(DebugAll,"ToneSource rate=%llu b/s",m_time);
}
tones.remove(this,false);
}
Tone *ToneSource::getBlock(const String &tone)
{
if (tone == "dial" || tone == "dt")
return t_dial;
else if (tone == "busy" || tone == "bs")
return t_busy;
else if (tone == "ring" || tone == "rt")
return t_ring;
else if (tone == "specdial" || tone == "sd")
return t_specdial;
Debug(DebugWarn,"No waveform is defined for tone '%s'",tone.c_str());
return 0;
}
ToneSource *ToneSource::getTone(const String &tone)
{
ObjList *l = &tones;
for (; l; l = l->next()) {
ToneSource *t = static_cast<ToneSource *>(l->get());
if (t && (t->name() == tone)) {
t->ref();
return t;
}
}
return new ToneSource(tone);
}
void ToneSource::run()
{
Debug(DebugAll,"ToneSource::run() [%p]",this);
unsigned long long tpos = Time::now();
m_time = tpos;
DataBlock data(0,480);
int pos = 0;
while (m_tone) {
short *d = (short *) data.data();
for (unsigned int i = data.length()/2; i--; pos++)
*d++ = get_sample(m_tone,pos);
pos = pos % get_length(m_tone);
long long dly = tpos - Time::now();
if (dly > 0) {
#ifdef DEBUG
Debug("ToneSource",DebugAll,"Sleeping for %lld usec",dly);
#endif
::usleep((unsigned long)dly);
}
Forward(data);
m_total += data.length();
tpos += (data.length()*1000000ULL/m_brate);
};
m_time = Time::now() - m_time;
m_time = (m_total*1000000ULL + m_time/2) / m_time;
Debug(DebugAll,"ToneSource [%p] end, total=%u (%llu b/s)",this,m_total,m_time);
m_time = 0;
}
ToneChan::ToneChan(const String &tone)
: DataEndpoint("tone")
{
Debug(DebugAll,"ToneChan::ToneChan(\"%s\") [%p]",tone.c_str(),this);
chans.append(this);
ToneSource *t = ToneSource::getTone(tone);
if (t) {
setSource(t);
t->deref();
}
}
ToneChan::~ToneChan()
{
Debug(DebugAll,"ToneChan::~ToneChan() [%p]",this);
chans.remove(this,false);
}
void ToneChan::disconnected()
{
Debugger debug("ToneChan::disconnected()"," [%p]",this);
destruct();
}
bool ToneHandler::received(Message &msg)
{
String dest(msg.getValue("callto"));
if (dest.null())
return false;
Regexp r("^tone/\\(.*\\)$");
if (!dest.matches(r))
return false;
String tone = dest.matchString(1);
DataEndpoint *dd = static_cast<DataEndpoint *>(msg.userData());
if (dd)
dd->connect(new ToneChan(tone));
else {
const char *targ = msg.getValue("target");
if (!targ) {
Debug(DebugWarn,"Tone outgoing call with no target!");
return false;
}
Message m("preroute");
m.addParam("id",dest);
m.addParam("caller",dest);
m.addParam("called",targ);
Engine::dispatch(m);
m = "route";
if (Engine::dispatch(m)) {
m = "call";
m.addParam("callto",m.retValue());
m.retValue() = 0;
ToneChan *tc = new ToneChan(dest.matchString(1).c_str());
m.userData(tc);
if (Engine::dispatch(m))
return true;
Debug(DebugFail,"Tone outgoing call not accepted!");
delete tc;
}
else
Debug(DebugWarn,"Tone outgoing call but no route!");
return false;
}
return true;
}
bool StatusHandler::received(Message &msg)
{
const char *sel = msg.getValue("module");
if (sel && ::strcmp(sel,"tonegen"))
return false;
msg.retValue() << "tonegen,tones=" << tones.count() << "\n";
return false;
}
ToneGenPlugin::ToneGenPlugin()
: m_handler(0)
{
Output("Loaded module ToneGen");
}
ToneGenPlugin::~ToneGenPlugin()
{
Output("Unloading module ToneGen");
ObjList *l = &chans;
while (l) {
ToneChan *t = static_cast<ToneChan *>(l->get());
if (t)
t->disconnect();
if (l->get() == t)
l = l->next();
}
chans.clear();
tones.clear();
}
void ToneGenPlugin::initialize()
{
Output("Initializing module ToneGen");
if (!m_handler) {
m_handler = new ToneHandler("call");
Engine::install(m_handler);
Engine::install(new StatusHandler);
}
}
INIT_PLUGIN(ToneGenPlugin);
/* vi: set ts=8 sw=4 sts=4 noet: */

226
modules/wavefile.cpp Normal file
View File

@ -0,0 +1,226 @@
/**
* wavefile.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Wave file driver (record+playback)
*/
#include <telengine.h>
#include <telephony.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
using namespace TelEngine;
class WaveChan;
class WaveSource : public ThreadedSource
{
public:
WaveSource(const char *file, WaveChan *chan);
~WaveSource();
virtual void run();
virtual void cleanup();
private:
WaveChan *m_chan;
int m_fd;
unsigned m_brate;
unsigned m_total;
};
class WaveConsumer : public DataConsumer
{
public:
WaveConsumer(const char *file);
~WaveConsumer();
virtual void Consume(const DataBlock &data);
private:
int m_fd;
unsigned m_total;
};
class WaveChan : public DataEndpoint
{
public:
WaveChan(const char *file, bool record);
~WaveChan();
virtual void disconnected();
};
class WaveHandler : public MessageHandler
{
public:
WaveHandler(const char *name) : MessageHandler(name) { }
virtual bool received(Message &msg);
};
class WaveFilePlugin : public Plugin
{
public:
WaveFilePlugin();
virtual void initialize();
private:
WaveHandler *m_handler;
};
WaveSource::WaveSource(const char *file, WaveChan *chan)
: m_chan(chan), m_fd(-1), m_brate(16000), m_total(0)
{
Debug(DebugAll,"WaveSource::WaveSource(\"%s\") [%p]",file,this);
m_fd = ::open(file,O_RDONLY|O_NOCTTY);
if (m_fd >= 0)
start("WaveSource");
else
Debug(DebugFail,"Opening '%s': error %d: %s",
file, errno, ::strerror(errno));
}
WaveSource::~WaveSource()
{
Debug(DebugAll,"WaveSource::~WaveSource() [%p] total=%u",this,m_total);
if (m_fd >= 0) {
::close(m_fd);
m_fd = -1;
}
}
void WaveSource::run()
{
DataBlock data(0,480);
int r = 0;
unsigned long long tpos = Time::now();
do {
r = ::read(m_fd,data.data(),data.length());
if (r < 0) {
if (errno == EINTR) {
r = 1;
continue;
}
break;
}
if (r < (int)data.length())
data.assign(data.data(),r);
long long dly = tpos - Time::now();
if (dly > 0) {
#ifdef DEBUG
Debug("WaveSource",DebugAll,"Sleeping for %lld usec",dly);
#endif
::usleep((unsigned long)dly);
}
Forward(data);
m_total += r;
tpos += (r*1000000ULL/m_brate);
} while (r > 0);
Debug(DebugAll,"WaveSource [%p] end of data",this);
}
void WaveSource::cleanup()
{
Debug(DebugAll,"WaveSource [%p] cleanup, total=%u",this,m_total);
m_chan->disconnect();
}
WaveConsumer::WaveConsumer(const char *file)
: m_fd(-1), m_total(0)
{
Debug(DebugAll,"WaveConsumer::WaveConsumer(\"%s\") [%p]",file,this);
m_fd = ::creat(file,S_IRUSR|S_IWUSR);
if (m_fd < 0)
Debug(DebugFail,"Creating '%s': error %d: %s",
file, errno, ::strerror(errno));
}
WaveConsumer::~WaveConsumer()
{
Debug(DebugAll,"WaveConsumer::~WaveConsumer() [%p] total=%u",this,m_total);
if (m_fd >= 0) {
::close(m_fd);
m_fd = -1;
}
}
void WaveConsumer::Consume(const DataBlock &data)
{
if ((m_fd >= 0) && !data.null()) {
::write(m_fd,data.data(),data.length());
m_total += data.length();
}
}
WaveChan::WaveChan(const char *file, bool record)
: DataEndpoint("wavefile")
{
Debug(DebugAll,"WaveChan::WaveChan(%s) [%p]",(record ? "record" : "play"),this);
if (record) {
setConsumer(new WaveConsumer(file));
getConsumer()->deref();
}
else {
setSource(new WaveSource(file,this));
getSource()->deref();
}
}
WaveChan::~WaveChan()
{
Debug(DebugAll,"WaveChan::~WaveChan() [%p]",this);
}
void WaveChan::disconnected()
{
Debugger debug("WaveChan::disconnected()"," [%p]",this);
destruct();
}
bool WaveHandler::received(Message &msg)
{
String dest(msg.getValue("callto"));
if (dest.null())
return false;
Regexp r("^wave/\\([^/]*\\)/\\(.*\\)$");
if (!dest.matches(r))
return false;
if (!msg.userData()) {
Debug(DebugFail,"Wave call found but no data channel!");
return false;
}
DataEndpoint *dd = static_cast<DataEndpoint *>(msg.userData());
if (dest.matchString(1) == "record") {
Debug(DebugInfo,"Record to wave file '%s'",dest.matchString(2).c_str());
dd->connect(new WaveChan(dest.matchString(2).c_str(),true));
return true;
}
else if (dest.matchString(1) == "play") {
Debug(DebugInfo,"Play from wave file '%s'",dest.matchString(2).c_str());
dd->connect(new WaveChan(dest.matchString(2).c_str(),false));
return true;
}
Debug(DebugFail,"Invalid wavefile method '%s', use 'record' or 'play'",
dest.matchString(1).c_str());
return false;
}
WaveFilePlugin::WaveFilePlugin()
: m_handler(0)
{
Output("Loaded module WaveFile");
}
void WaveFilePlugin::initialize()
{
Output("Initializing module WaveFile");
if (!m_handler) {
m_handler = new WaveHandler("call");
Engine::install(m_handler);
}
}
INIT_PLUGIN(WaveFilePlugin);
/* vi: set ts=8 sw=4 sts=4 noet: */

1219
modules/zapchan.cpp Normal file

File diff suppressed because it is too large Load Diff

33
run.in Normal file
View File

@ -0,0 +1,33 @@
#! /bin/sh
yate="./yate"
set_conf="-c ./conf.d"
set_mods="-m ./modules"
if [ "$1" = "--gdb" ]; then
shift
yate="gdb --args $yate"
fi
if [ "$1" = "--core" ]; then
shift
yate="gdb $yate core*"
set_conf=""
set_mods=""
fi
if [ "$1" = "--valgrind" ]; then
shift
yate="valgrind $1 $yate"
shift
fi
for opt in $@; do
case "$opt" in
-c)
set_conf=
;;
-m)
set_mods=
;;
esac
done
LD_LIBRARY_PATH=.@H323_RUN@:$LD_LIBRARY_PATH exec $yate $set_conf $set_mods "$@"

4
scripts/.cvsignore Normal file
View File

@ -0,0 +1,4 @@
core*
*.orig
*~
.*.swp

8
tables/.cvsignore Normal file
View File

@ -0,0 +1,8 @@
core*
*.h
*.raw
?2?
gen
*.orig
*~
.*.swp

25
tables/Makefile.tables Normal file
View File

@ -0,0 +1,25 @@
# Makefile
# This file holds the make rules for the Telephony Engine
all: all.h
all.h: ./gen.c ./gen.sh ./gen.awk
./gen.sh "."
.PHONY: clean
clean:
rm -f *.raw ?2? gen core*
.PHONY: mrproper
mrproper: clean
rm -f *.h
.PHONY: strip
strip: all
install:
uninstall:
Makefile: ./Makefile.in ../config.status
cd .. && ./config.status

10
tables/gen.awk Normal file
View File

@ -0,0 +1,10 @@
BEGIN {
for (i=0;i<=255;i++)
printf("%c",i) >"08b.raw";
for (j=0;j<=255;j++)
for (i=0;i<=255;i++)
printf("%c%c",i,j) >"16b.raw";
exit;
}

31
tables/gen.c Normal file
View File

@ -0,0 +1,31 @@
#include <stdio.h>
int main(int argc, const char **argv)
{
int n = 0;
if (argv[1][0] == 'b') {
unsigned char c;
printf("static unsigned char %s[] = {",argv[2]);
while (fread(&c,1,1,stdin)) {
if (n)
printf(",");
if (((n++) % 16) == 0)
printf("\n");
printf(" 0x%02X",c);
}
printf("\n};\n");
}
else {
unsigned short s;
printf("static unsigned short %s[] = {",argv[2]);
while (fread(&s,2,1,stdin)) {
if (n)
printf(",");
if (((n++) % 8) == 0)
printf("\n");
printf(" 0x%04X",s);
}
printf("\n};\n");
}
return 0;
}

28
tables/gen.sh Executable file
View File

@ -0,0 +1,28 @@
#! /bin/sh
awk -f "$1/gen.awk"
sc="sox -r 8000 -c 1 -t raw"
$sc -w -s 16b.raw -b -U -t raw s2u
$sc -w -s 16b.raw -b -A -t raw s2a
$sc -b -U 08b.raw -w -s -t raw u2s
$sc -b -A 08b.raw -w -s -t raw a2s
$sc -b -U 08b.raw -b -A -t raw u2a
$sc -b -A 08b.raw -b -U -t raw a2u
gcc -o gen "$1/gen.c"
for i in ?2?; do
case "$i" in
*2s)
./gen w "$i" <"$i" >"$i.h"
;;
*)
./gen b "$i" <"$i" >"$i.h"
;;
esac
echo "#include \"tables/$i.h\""
done >all.h
rm *.raw ?2? gen

5
tarballs/.cvsignore Normal file
View File

@ -0,0 +1,5 @@
core*
*.tar.gz
*.orig
*~
.*.swp

9
test/.cvsignore Normal file
View File

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

50
test/Makefile.in Normal file
View File

@ -0,0 +1,50 @@
# Makefile
# This file holds the make rules for the Telephony Engine test cases
CC := g++ -Wall
SED := sed
DEFS :=
INCLUDES := -I@top_srcdir@
CFLAGS := -O0 @MODULE_CFLAGS@
LDFLAGS:= -L.. -lyate
MODFLAGS:= @MODULE_LDFLAGS@
PROGS = msgsniff.yate randcall.yate
LIBS =
OBJS =
LOCALFLAGS =
LOCALLIBS =
COMPILE = $(CC) $(DEFS) $(INCLUDES) $(CFLAGS)
LINK = $(CC) $(LDFLAGS)
MODLINK = $(CC) $(MODFLAGS) $(LDFLAGS)
MODCOMP = $(COMPILE) $(MODFLAGS) $(LDFLAGS)
prefix = @prefix@
exec_prefix = @exec_prefix@
.PHONY: all
all: $(LIBS) $(PROGS)
.PHONY: strip
strip: all
strip --strip-debug --discard-locals $(PROGS)
.PHONY: clean
clean:
@-rm $(PROGS) $(LIBS) $(OBJS) core 2>/dev/null
%.o: @srcdir@/%.cpp @top_srcdir@/telengine.h
$(COMPILE) -c $<
%.o: @srcdir@/%.c
$(COMPILE) -c $<
Makefile: @srcdir@/Makefile.in ../config.status
cd .. && ./config.status
lib%.so: %.o
$(LINK) -shared -o $@ $^
%.yate: @srcdir@/%.cpp $(INCFILES)
$(MODCOMP) -o $@ $(LOCALFLAGS) $< $(LOCALLIBS)

95
test/randcall.cpp Normal file
View File

@ -0,0 +1,95 @@
/*
test.c
This file holds the entry point of the Telephony Engine
*/
#include <telengine.h>
#include <unistd.h>
#include <stdlib.h>
using namespace TelEngine;
class RandThread : public Thread
{
public:
RandThread() : Thread("RandThread") { }
virtual void run();
};
class RandPlugin : public Plugin
{
public:
RandPlugin();
virtual void initialize();
RandThread *m_thread;
};
class TestHandler : public MessageHandler
{
public:
TestHandler(const char *name) : MessageHandler(name) { }
virtual bool received(Message &msg);
};
void RandThread::run()
{
for (;;) {
::usleep(::random() % 5000000);
String id("random/"+String((int)::random() %1000));
Message *m = new Message("preroute");
m->addParam("id",id);
m->addParam("caller",String((int)(::random() % 1000000)));
m->addParam("called",String((int)(::random() % 1000000)));
Engine::dispatch(m);
*m = "route";
bool routed = Engine::dispatch(m);
Debug(DebugMild,"Routed %ssuccessfully in %llu usec",(routed ? "" : "un"),
Time::now()-m->msgTime().usec());
if (routed) {
m->addParam("callto",m->retValue());
m->retValue() = "";
*m = "call";
m->msgTime() = Time::now();
if (Engine::dispatch(m)) {
::usleep(::random() % 5000000);
if ((::random() % 100) < 33) {
*m = "answered";
m->msgTime() = Time::now();
m->addParam("status","answered");
Engine::dispatch(m);
::usleep(::random() % 10000000);
}
else if ((::random() % 100) < 50)
*m = "busy";
else
*m = "no answer";
}
else {
Debug(DebugMild,"Noone processed call to '%s'",m->getValue("callto"));
m->addParam("status","rejected");
}
*m = "hangup";
m->msgTime() = Time::now();
Engine::dispatch(m);
}
delete m;
}
}
RandPlugin::RandPlugin()
: m_thread(0)
{
Output("Loaded random call generator");
}
void RandPlugin::initialize()
{
Output("Initializing module RandPlugin");
if (!m_thread)
m_thread = new RandThread;
}
INIT_PLUGIN(RandPlugin);
/* vi: set ts=8 sw=4 sts=4 noet: */

46
test/test.cpp Normal file
View File

@ -0,0 +1,46 @@
/*
test.c
This file holds the entry point of the Telephony Engine
*/
#include <telengine.h>
using namespace TelEngine;
class TestPlugin : public Plugin
{
public:
TestPlugin();
virtual void initialize();
};
TestPlugin::TestPlugin()
{
Output("Hello, I am module TestPlugin");
}
void TestPlugin::initialize()
{
Output("Initializing module TestPlugin");
// Regexp r("\\([a-z]*\\)\\(.*\\)");
Regexp r("\\([a-z]\\+\\)\\([0-9]\\+\\)");
String s("123abc456xyz");
if (s.matches(r)) {
Output("Found %d matches of '%s' in '%s'",s.matchCount(),r.c_str(),s.c_str());
for (int i=0; i<=s.matchCount(); i++)
Output("match[%d]='%s' pos=%d len=%d",i,s.matchString(i).c_str(),s.matchOffset(i),s.matchLength(i));
String t("\\0-ABC-\\1-DEF-\\2-GHI-\\\\");
Output("Replacing matches in '%s' got '%s'",t.c_str(),s.replaceMatches(t).c_str());
}
r = "[a-z]\\+[0-9]\\+";
s.matches(r);
Output("Found %d matches of '%s' in '%s'",s.matchCount(),r.c_str(),s.c_str());
for (int i=0; i<=s.matchCount(); i++)
Output("match[%d]='%s' pos=%d len=%d",i,s.matchString(i).c_str(),s.matchOffset(i),s.matchLength(i));
}
INIT_PLUGIN(TestPlugin);
/*
* vim:ts=4:et:sw=4:ht=8
*/

95
test/test1.cpp Normal file
View File

@ -0,0 +1,95 @@
/*
test.c
This file holds the entry point of the Telephony Engine
*/
#include <telengine.h>
#include <unistd.h>
using namespace TelEngine;
static bool noisy = false;
class TestThread : public Thread
{
public:
virtual void run();
virtual void cleanup();
};
class TestPlugin1 : public Plugin
{
public:
TestPlugin1();
~TestPlugin1();
virtual void initialize();
private:
bool m_first;
};
class TestHandler : public MessageHandler
{
public:
TestHandler(const char *name) : MessageHandler(name) { }
virtual bool received(Message &msg);
};
void TestThread::run()
{
Debug(DebugInfo,"TestThread::run() [%p]",this);
for (;;) {
Engine::dispatch(Message("test.thread.direct"));
Engine::enqueue(new Message("test.thread.queued"));
::sleep(2);
}
}
void TestThread::cleanup()
{
Debug(DebugInfo,"TestThread::cleanup() [%p]",this);
Debug(DebugInfo,"Thread::current() = %p",Thread::current());
}
bool TestHandler::received(Message &msg)
{
if (noisy)
Output("Received message '%s' time=%llu thread=%p",
msg.c_str(),msg.msgTime().usec(),Thread::current());
return false;
};
TestPlugin1::TestPlugin1()
: m_first(true)
{
Output("Hello, I am module TestPlugin1");
}
TestPlugin1::~TestPlugin1()
{
Message msg("test1.exit","ok");
msg.addParam("foo","bar").addParam("x","y");
Engine::dispatch(&msg);
}
void TestPlugin1::initialize()
{
Output("Initializing module TestPlugin1");
Configuration *cfg = new Configuration(Engine::configFile("test1"));
noisy = cfg->getBoolValue("general","noisy");
int n = cfg->getIntValue("general","threads");
delete cfg;
Engine::install(new TestHandler("engine.halt"));
Engine::install(new TestHandler(""));
Engine::enqueue(new Message("test.queued1"));
Engine::enqueue(new Message("test.queued2"));
if (m_first) {
m_first = false;
for (int i=0; i<n; i++) {
::usleep(10000);
new TestThread;
}
}
}
INIT_PLUGIN(TestPlugin1);

55
yate-config.in Normal file
View File

@ -0,0 +1,55 @@
#! /bin/sh
ustr='Usage: yate-config [--cflags] [--includes] [--c-all]
[--ldflags] [--libs] [--ld-all]
[--config] [--modules] [--version]'
if [ "$#" = 0 ]; then
echo "$ustr"
exit 0
fi
prefix="@prefix@"
exec_prefix="@exec_prefix@"
moddir="@libdir@/yate"
confdir="@sysconfdir@/yate"
s1="@MODULE_CFLAGS@"
s2="-I@includedir@/yate"
s3="@MODULE_LDFLAGS@"
s4="-lyate"
while [ "$#" != 0 ]; do
case "$1" in
--version)
echo "@PACKAGE_VERSION@"
;;
--cflags)
echo "$s1"
;;
--includes)
echo "$s2"
;;
--c-all)
echo "$s1 $s2"
;;
--ldflags)
echo "$s3"
;;
--libss)
echo "$s4"
;;
--ld-all)
echo "$s3 $s4"
;;
--config)
echo "$confdir"
;;
--modules)
echo "$moddir"
;;
*)
echo "I didn't understand: $1" >&2
echo "$ustr" >&2
exit 1
;;
esac
shift
done

81
yate.8 Normal file
View File

@ -0,0 +1,81 @@
.\"
.\" YATE - Yet Another Telephony Engine
.\"
.\" 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
.\"
.\"
.TH YATE 8 "March 2004" "YATE" "Telephony Engine"
.SH NAME
\fByate\fP \- launch the YATE telephony engine
.SH SYNOPSIS
.B yate
.RI [options]
.SH DESCRIPTION
.B yate
is a telephony engine that supports PBX and IVR functions trough plugins.
.SH OPTIONS
.SS General
.TP
.B \-h, \-\-help
Display a short help message
.TP
.B \-v
Verbose debugging (you can use more than once)
.TP
.B \-q
Quieter debugging (you can use more than once)
.TP
.B \-d
Daemonify, suppress output unless logged
.TP
.B \-l filename
Log to file
.SS Debugging (may not be compiled in)
.TP
.B \-D[options]
Special debugging options
.TP
.B \-Dc
Call dlclose() until it gets an error
.TP
.B \-Di
Reinitialize after first initialization
.TP
.B \-Dx
Exit immediately after initialization
.TP
.B \-Dw
Delay for one secod the creation of the first worker thread
.SH SIGNALS
.TP
\- SIGTERM and SIGINT (Ctrl\-C) will cleanly stop the engine
.TP
\- SIGHUP and SIGQUIT (Ctrl\-\\) will reinitialize the modules
.SH BUGS
Under various
.B *BSD
implementations the
.I dlclose()
function is broken and may generate a segfault on exit.
.PP
Some
.B libpthread
implementations are broken and may fail to start the threads or fail to cleanup
when the thread terminates.
.SH AUTHOR
Paul Chitescu <paulc-devel@null.ro>
.SH SEE ALSO
.\" .BR libyate (3),
.BR bayonne (8)

13
yate.pc.in Normal file
View File

@ -0,0 +1,13 @@
prefix=@prefix@
exec_prefix=@exec_prefix@
libdir=@libdir@
includedir=@includedir@
yate=yate
yate-config=yate-config
Name: Yate
Description: Yet Another Telephony Engine
Version: @PACKAGE_VERSION@
Libs: -L${libdir} -lyate @MODULE_LDFLAGS@
Cflags: -I${includedir}/yate @MODULE_CFLAGS@

82
yate.spec.in Normal file
View File

@ -0,0 +1,82 @@
Summary: Yet Another Telephony Engine
Name: yate
Version: @PACKAGE_VERSION@
Release: 1
Copyright: GPL
Packager: Paul Chitescu <paulc-devel@null.ro>
Source: http://yate.null.ro/%{name}-%{version}.tar.gz
Group: Applications/Communications
BuildRoot: %{_tmppath}/%{name}-%{version}-root
URL: http://yate.null.ro/
BuildRequires: gcc-c++
Group: System Environment/Libraries
%define prefix /usr
%description
YATE is a telephony engine designed to implement PBX and IVR solutions
for small to large scale projects.
%files
%defattr(-, root, root)
%doc /usr/share/doc/yate-%{version}/README
%doc /usr/share/doc/yate-%{version}/COPYING
%doc /usr/share/doc/yate-%{version}/ChangeLog
%config /etc/yate/*
/usr/lib/lib*.so*
/usr/bin/yate
/usr/share/man/*
/usr/lib/yate/*
%post
ldconfig
%postun
ldconfig
%package devel
Summary: Development package for yate
Group: Development/Libraries
Requires: %{name} = %{version}
%description devel
The yate-devel package includes the libraries and header files for YATE
%files devel
%defattr(-, root, root)
%doc /usr/share/doc/yate-%{version}/*.html
%doc /usr/share/doc/yate-%{version}/api/*
/usr/include/*
/usr/bin/yate-config
/usr/lib/pkgconfig/yate.pc
%prep
%setup -q -n %{name}
%build
./configure --prefix=%{prefix} --sysconfdir=/etc --mandir=%{prefix}/share/man
make strip
%install
make install DESTDIR=%{buildroot}
%clean
# make clean
rm -rf %{buildroot}
%changelog
* Sat May 15 2004 Paul Chitescu <paulc-devel@null.ro>
- Added pkgconfig support
- Improved detection of Postgress' include file path
- Support for detecting libraries required for SIP
- Better detection of OpenH323 versions
* Sat May 15 2004 Diana Cionoiu <diana@diana.null.ro>
- Added SIP channel and registration module
* Sun Apr 04 2004 Paul Chitescu <paulc-devel@null.ro>
- Added yate-config to the devel package
* Mon Mar 29 2004 Paul Chitescu <paulc-devel@null.ro>
- Created specfile

1935
yatengine.h Normal file

File diff suppressed because it is too large Load Diff

522
yatephone.h Normal file
View File

@ -0,0 +1,522 @@
/**
* telephony.h
* This file is part of the YATE Project http://YATE.null.ro
*/
#ifndef __TELEPHONY_H
#define __TELEPHONY_H
#ifndef __cplusplus
#error C++ is required
#endif
#include <telengine.h>
/**
* Holds all Telephony Engine related classes.
*/
namespace TelEngine {
/**
* The DataBlock holds a data buffer with no specific formatting.
* @short A class that holds just a block of raw data
*/
class DataBlock : public GenObject
{
public:
/**
* Constructs an empty data block
*/
DataBlock();
/**
* Copy constructor
*/
DataBlock(const DataBlock &value);
/**
* Constructs an initialized data block
* @param value Data to assign, may be NULL to fill with zeros
* @param len Length of data, may be zero (then @ref value is ignored)
* @param copyData True to make a copy of the data, false to just insert the pointer
*/
DataBlock(void *value, unsigned int len, bool copyData = true);
/**
* Destroys the data, disposes the memory.
*/
virtual ~DataBlock();
/**
* Get a pointer to the stored data.
* @return A pointer to the data or NULL.
*/
inline void *data() const
{ return m_data; }
/**
* Checks if the block holds a NULL pointer.
* @return True if the block holds NULL, false otherwise.
*/
inline bool null() const
{ return !m_data; }
/**
* Get the length of the stored data.
* @return The length of the stored data, zero for NULL.
*/
inline unsigned int length() const
{ return m_length; }
/**
* Clear the data and optionally free the memory
* @param deleteData True to free the deta block, false to just forget it
*/
void clear(bool deleteData = true);
/**
* Assign data to the object
* @param value Data to assign, may be NULL to fill with zeros
* @param len Length of data, may be zero (then @ref value is ignored)
* @param copyData True to make a copy of the data, false to just insert the pointer
*/
DataBlock& assign(void *value, unsigned int len, bool copyData = true);
/**
* Append data to the current block
* @param value Data to append
*/
void append(const DataBlock &value);
/**
* Insert data before the current block
* @param value Data to insert
*/
void insert(const DataBlock &value);
/**
* Truncate the data block
* @param len The maximum length to keep
*/
void truncate(unsigned int len);
/**
* Cut off a number of bytes from the data block
* @param len Amount to cut, positive to cut from end, negative to cut from start of block
*/
void cut(int len);
/**
* Assignment operator.
*/
DataBlock& operator=(const DataBlock &value);
/**
* Appending operator.
*/
inline DataBlock& operator+=(const DataBlock &value)
{ append(value); return *this; }
/**
* Convert data from a different format
* @param src Source data block
* @param sFormat Name of the source format
* @param dFormat Name of the destination format
* @param maxlen Maximum amount to convert, 0 to use source
* @return True if converted successfully, false on failure
*/
bool convert(const DataBlock &src, const String &sFormat,
const String &dFormat, unsigned maxlen = 0);
private:
void *m_data;
unsigned int m_length;
};
/**
* A generic data handling object
*/
class DataNode : public RefObject
{
public:
/**
* Construct a DataNode
* @param format Name of the data format, default none
*/
DataNode(const char *format = 0)
: m_format(format) { }
/**
* Get the computing cost of converting the data to the format asked
* @param format Name of the format to check for
* @return -1 if unsupported, 0 for native format else cost in KIPS
*/
virtual int costFormat(const String &format)
{ return -1; }
/**
* Change the format used to transfer data
* @param format Name of the format to set for data
* @return True if the format changed successfully, false if not changed
*/
virtual bool setFormat(const String &format)
{ return false; }
/**
* Get the name of the format currently in use
* @return Name of the data format
*/
inline const String &getFormat() const
{ return m_format; }
protected:
/**
* The name of the data format the node is currently using
*/
String m_format;
};
/**
* A data consumer
*/
class DataConsumer : public DataNode
{
friend class DataSource;
public:
/**
* Consumer constructor
* @param format Name of the data format, default "slin" (Signed Linear)
*/
DataConsumer(const char *format = "slin")
: DataNode(format), m_source(0) { }
/**
* Consumes the data sent to it from a source
* @param data The raw data block to process; an empty block ends data
*/
virtual void Consume(const DataBlock &data) = 0;
/**
* Get the data source of this object if it's connected
* @return A pointer to the DataSource object or NULL
*/
DataSource *getConnSource() const
{ return m_source; }
/**
* Get the data source of a translator object
* @return A pointer to the DataSource object or NULL
*/
virtual DataSource *getTransSource() const
{ return 0; }
private:
inline void setSource(DataSource *source)
{ m_source = source; }
DataSource *m_source;
};
/**
* A data source
*/
class DataSource : public DataNode
{
friend class DataTranslator;
public:
/**
* Source constructor
* @param format Name of the data format, default "slin" (Signed Linear)
*/
DataSource(const char *format = "slin")
: DataNode(format), m_translator(0) { }
/**
* Forwards the data to its consumers
* @param data The raw data block to forward; an empty block ends data
*/
void Forward(const DataBlock &data);
/**
* Attach a data consumer
* @param consumer Data consumer to attach
* @return True on success, false on failure
*/
bool attach(DataConsumer *consumer);
/**
* Detach a data consumer
* @param consumer Data consumer to detach
* @return True on success, false on failure
*/
bool detach(DataConsumer *consumer);
/**
* Detach all data consumers
*/
inline void clear()
{ m_consumers.clear(); }
/**
* Get the master translator object if this source is part of a translator
* @return A pointer to the DataTranslator object or NULL
*/
DataTranslator *getTranslator() const
{ return m_translator; }
protected:
inline void setTranslator(DataTranslator *translator)
{ m_translator = translator; }
DataTranslator *m_translator;
ObjList m_consumers;
Mutex m_mutex;
};
/**
* A data source with a thread of its own
*/
class ThreadedSource : public DataSource
{
friend class ThreadedSourcePrivate;
public:
/**
* The destructor, stops the thread
*/
virtual ~ThreadedSource();
/**
* Start the worker thread
*/
void start(const char *name = "ThreadedSource");
protected:
/**
* Threaded Source constructor
* @param format Name of the data format, default "slin" (Signed Linear)
*/
ThreadedSource(const char *format = "slin")
: DataSource(format), m_thread(0) { }
/**
* The worker method. You have to reimplement it as you need
*/
virtual void run() = 0;
/**
* The cleanup after thread method
*/
virtual void cleanup();
private:
ThreadedSourcePrivate *m_thread;
};
/**
* The DataEndpoint holds an endpoint capable of performing unidirectional
* or bidirectional data transfers
* @short A data transfer endpoint capable of sending and/or receiving data
*/
class DataEndpoint : public RefObject
{
public:
/**
* Creates am empty data ednpoint
*/
inline DataEndpoint(const char *name = 0)
: m_name(name), m_source(0), m_consumer(0), m_peer(0) { }
/**
* Destroys the endpoint, source and consumer
*/
~DataEndpoint();
/**
* Connect the source and consumer of the endpoint to a peer
* @param peer Pointer to the peer data endpoint
* @return True if connected, false if incompatible source/consumer
*/
bool connect(DataEndpoint *peer);
/**
* Disconnect from the connected endpoint
*/
void disconnect();
/**
* Set the data source of this object
* @param source A pointer to the new source or NULL
*/
void setSource(DataSource *source = 0);
/**
* Get the data source of this object
* @return A pointer to the DataSource object or NULL
*/
DataSource *getSource() const
{ return m_source; }
/**
* Set the data consumer of this object
* @param consumer A pointer to the new consumer or NULL
*/
void setConsumer(DataConsumer *consumer = 0);
/**
* Get the data consumer of this object
* @return A pointer to the DataConsumer object or NULL
*/
DataConsumer *getConsumer() const
{ return m_consumer; }
/*
* Get a pointer to the peer endpoint
* @return A pointer to the peer endpoint or NULL
*/
inline DataEndpoint *getPeer() const
{ return m_peer; }
/**
* Get the name set in constructor
* @return A reference to the name as hashed string
*/
inline const String &name() const
{ return m_name; }
protected:
/**
* Connect notification method
*/
virtual void connected() { }
/**
* Disconnect notification method
*/
virtual void disconnected() { }
/**
* Attempt to connect the endpoint to a peer of the same type
* @param peer Pointer to the endpoint data driver
* @return True if connected, false if failed native connection
*/
virtual bool nativeConnect(DataEndpoint *peer)
{ return false; }
/*
* Set the peer endpoint pointer
* @param peer A pointer to the new peer or NULL
*/
void setPeer(DataEndpoint *peer);
private:
String m_name;
DataSource *m_source;
DataConsumer *m_consumer;
DataEndpoint *m_peer;
};
/**
* The DataTranslator holds a translator (codec) capable of unidirectional
* conversion of data from one type to another
* @short An unidirectional data translator (codec)
*/
class DataTranslator : public DataConsumer
{
friend class TranslatorFactory;
public:
/**
* Construct a data translator
* @param sFormat Name of the source format (data received from the consumer)
* @param dFormat Name of the destination format (data supplied to the source)
*/
DataTranslator(const char *sFormat, const char *dFormat);
/**
* Creates a data translator from an existing source
* does not increment the source's reference counter
* @param sFormat Name of the source format (data received from the consumer)
* @param source Optional pointer to a DataSource object
*/
DataTranslator(const char *sFormat, DataSource *source = 0);
/**
* Destroys the translator and its source
*/
~DataTranslator();
/**
* Get the data source of a translator object
* @return A pointer to the DataSource object or NULL
*/
virtual DataSource *getTransSource() const
{ return m_tsource; }
/**
* Creates a translator given the source and destination format names
* @param sFormat Name of the source format (data received from the consumer)
* @param dFormat Name of the destination format (data supplied to the source)
* @return A pointer to a DataTranslator object or NULL if no known codec exists
*/
static DataTranslator *create(const String &sFormat, const String &dFormat);
/**
* Attach a consumer to a source, possibly trough a chain of translators
* @param source Source to attach the chain to
* @param consumer Consumer where the chain ends
* @return True if successfull, false if no translator chain could be built
*/
static bool attachChain(DataSource *source, DataConsumer *consumer);
/**
* Detach a consumer from a source, possibly trough a chain of translators
* @param source Source to dettach the chain from
* @param consumer Consumer where the chain ends
* @return True if successfull, false if source and consumers were not attached
*/
static bool detachChain(DataSource *source, DataConsumer *consumer);
protected:
/**
* Install a Translator Factory in the list of known codecs
* @param factory A pointer to a TranslatorFactory instance
*/
static void install(TranslatorFactory *factory);
/**
* Remove a Translator Factory from the list of known codecs
* @param factory A pointer to a TranslatorFactory instance
*/
static void uninstall(TranslatorFactory *factory);
private:
DataTranslator(); // No default constructor please
DataSource *m_tsource;
static Mutex s_mutex;
static ObjList s_factories;
};
/**
* A factory for constructing data translators by format name
* conversion of data from one type to another
* @short An unidirectional data translator (codec)
*/
class TranslatorFactory : public GenObject
{
public:
TranslatorFactory()
{ DataTranslator::install(this); }
virtual ~TranslatorFactory()
{ DataTranslator::uninstall(this); }
/**
* Creates a translator given the source and destination format names
* @param sFormat Name of the source format (data received from the consumer)
* @param dFormat Name of the destination format (data supplied to the source)
* @return A pointer to a DataTranslator object or NULL
*/
virtual DataTranslator *create(const String &sFormat, const String &dFormat) = 0;
};
}; // namespace TelEngine
#endif /* __TELEPHONY_H */