From d668e85bf04e05d6ea9c8c81f908548b0486c9fb Mon Sep 17 00:00:00 2001 From: paulc Date: Sat, 22 May 2004 00:05:20 +0000 Subject: [PATCH] Imported in new CVS git-svn-id: http://voip.null.ro/svn/yate@2 acf43c95-373e-0410-b603-e72c3f656dc1 --- .cvsignore | 19 + COPYING | 339 ++++++ ChangeLog | 39 + Makefile.in | 209 ++++ README | 72 ++ conf.d/.cvsignore | 6 + conf.d/Makefile.in | 32 + conf.d/cdrfile.conf.sample | 3 + conf.d/h323chan.conf.sample | 82 ++ conf.d/pgsql.conf.sample | 9 + conf.d/pgsql.sql | 64 ++ conf.d/regexroute.conf.sample | 28 + conf.d/register.sql | 22 + conf.d/rmanager.conf.sample | 11 + conf.d/test1.conf.sample | 3 + conf.d/zapchan.conf.sample | 26 + configure.in | 263 +++++ docs/.cvsignore | 4 + docs/api/.cvsignore | 5 + docs/dataflow.html | 18 + docs/extmodule.html | 119 ++ docs/index.html | 23 + docs/messages.html | 16 + engine/Configuration.cpp | 204 ++++ engine/DataBlock.cpp | 543 +++++++++ engine/Engine.cpp | 532 +++++++++ engine/Message.cpp | 269 +++++ engine/Mutex.cpp | 139 +++ engine/NamedList.cpp | 109 ++ engine/ObjList.cpp | 177 +++ engine/Plugin.cpp | 26 + engine/String.cpp | 774 +++++++++++++ engine/TelEngine.cpp | 233 ++++ engine/Thread.cpp | 330 ++++++ main.cpp | 11 + modules/.cvsignore | 9 + modules/Makefile.in | 123 +++ modules/cdrbuild.cpp | 228 ++++ modules/cdrfile.cpp | 98 ++ modules/cdrpgsql.cpp | 129 +++ modules/extmodule.cpp | 481 ++++++++ modules/gsmcodec.cpp | 124 +++ modules/h323chan.cpp | 944 ++++++++++++++++ modules/mktestlinks.sh | 8 + modules/msgsniff.cpp | 58 + modules/pgsqlroute.cpp | 216 ++++ modules/regexroute.cpp | 139 +++ modules/register.cpp | 279 +++++ modules/rmanager.cpp | 457 ++++++++ modules/tonegen.cpp | 299 +++++ modules/wavefile.cpp | 226 ++++ modules/zapchan.cpp | 1219 +++++++++++++++++++++ run.in | 33 + scripts/.cvsignore | 4 + tables/.cvsignore | 8 + tables/Makefile.tables | 25 + tables/gen.awk | 10 + tables/gen.c | 31 + tables/gen.sh | 28 + tarballs/.cvsignore | 5 + test/.cvsignore | 9 + test/Makefile.in | 50 + test/randcall.cpp | 95 ++ test/test.cpp | 46 + test/test1.cpp | 95 ++ yate-config.in | 55 + yate.8 | 81 ++ yate.pc.in | 13 + yate.spec.in | 82 ++ yatengine.h | 1935 +++++++++++++++++++++++++++++++++ yatephone.h | 522 +++++++++ 71 files changed, 12923 insertions(+) create mode 100644 .cvsignore create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 Makefile.in create mode 100644 README create mode 100644 conf.d/.cvsignore create mode 100644 conf.d/Makefile.in create mode 100644 conf.d/cdrfile.conf.sample create mode 100644 conf.d/h323chan.conf.sample create mode 100644 conf.d/pgsql.conf.sample create mode 100644 conf.d/pgsql.sql create mode 100644 conf.d/regexroute.conf.sample create mode 100644 conf.d/register.sql create mode 100644 conf.d/rmanager.conf.sample create mode 100644 conf.d/test1.conf.sample create mode 100644 conf.d/zapchan.conf.sample create mode 100644 configure.in create mode 100644 docs/.cvsignore create mode 100644 docs/api/.cvsignore create mode 100644 docs/dataflow.html create mode 100644 docs/extmodule.html create mode 100644 docs/index.html create mode 100644 docs/messages.html create mode 100644 engine/Configuration.cpp create mode 100644 engine/DataBlock.cpp create mode 100644 engine/Engine.cpp create mode 100644 engine/Message.cpp create mode 100644 engine/Mutex.cpp create mode 100644 engine/NamedList.cpp create mode 100644 engine/ObjList.cpp create mode 100644 engine/Plugin.cpp create mode 100644 engine/String.cpp create mode 100644 engine/TelEngine.cpp create mode 100644 engine/Thread.cpp create mode 100644 main.cpp create mode 100644 modules/.cvsignore create mode 100644 modules/Makefile.in create mode 100644 modules/cdrbuild.cpp create mode 100644 modules/cdrfile.cpp create mode 100644 modules/cdrpgsql.cpp create mode 100644 modules/extmodule.cpp create mode 100644 modules/gsmcodec.cpp create mode 100644 modules/h323chan.cpp create mode 100644 modules/mktestlinks.sh create mode 100644 modules/msgsniff.cpp create mode 100644 modules/pgsqlroute.cpp create mode 100644 modules/regexroute.cpp create mode 100644 modules/register.cpp create mode 100644 modules/rmanager.cpp create mode 100644 modules/tonegen.cpp create mode 100644 modules/wavefile.cpp create mode 100644 modules/zapchan.cpp create mode 100644 run.in create mode 100644 scripts/.cvsignore create mode 100644 tables/.cvsignore create mode 100644 tables/Makefile.tables create mode 100644 tables/gen.awk create mode 100644 tables/gen.c create mode 100755 tables/gen.sh create mode 100644 tarballs/.cvsignore create mode 100644 test/.cvsignore create mode 100644 test/Makefile.in create mode 100644 test/randcall.cpp create mode 100644 test/test.cpp create mode 100644 test/test1.cpp create mode 100644 yate-config.in create mode 100644 yate.8 create mode 100644 yate.pc.in create mode 100644 yate.spec.in create mode 100644 yatengine.h create mode 100644 yatephone.h diff --git a/.cvsignore b/.cvsignore new file mode 100644 index 00000000..22e3019c --- /dev/null +++ b/.cvsignore @@ -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 diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..e77696ae --- /dev/null +++ b/COPYING @@ -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. + + + Copyright (C) 19yy + + 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. + + , 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. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 00000000..b54fb8d4 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,39 @@ +Sat May 15 2004 Paul Chitescu +- 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 +- Added SIP channel and registration module + +Wed Apr 28 2004 Paul Chitescu +- 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 +- Fixed a fatal 16->8 bit conversion bug in DataBlock::convert() +- Added valgrind support to the run script + +Sat Apr 11 2004 Paul Chitescu +- 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 +- Added an yate-config script + +Sat Apr 03 2004 Paul Chitescu +- Turned some redundant strings from makefiles into variables +- Applied some patches submitted by Cristian Andrei Calin : + - 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 +- Imported into new CVS + +Mon Mar 29 2004 Paul Chitescu +- Version 0.8.0 +- Redesigned the build system so it works trough autoconf +- Added a RPM specfile - tested on RedHat 7.1 diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 00000000..c37a9cda --- /dev/null +++ b/Makefile.in @@ -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' diff --git a/README b/README new file mode 100644 index 00000000..2b38670a --- /dev/null +++ b/README @@ -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. diff --git a/conf.d/.cvsignore b/conf.d/.cvsignore new file mode 100644 index 00000000..39967899 --- /dev/null +++ b/conf.d/.cvsignore @@ -0,0 +1,6 @@ +Makefile +core* +*.conf +*.orig +*~ +.*.swp diff --git a/conf.d/Makefile.in b/conf.d/Makefile.in new file mode 100644 index 00000000..935bf57f --- /dev/null +++ b/conf.d/Makefile.in @@ -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 diff --git a/conf.d/cdrfile.conf.sample b/conf.d/cdrfile.conf.sample new file mode 100644 index 00000000..0918bb8e --- /dev/null +++ b/conf.d/cdrfile.conf.sample @@ -0,0 +1,3 @@ +[general] +file=/tmp/cdr.tsv +tabs=true diff --git a/conf.d/h323chan.conf.sample b/conf.d/h323chan.conf.sample new file mode 100644 index 00000000..21567d78 --- /dev/null +++ b/conf.d/h323chan.conf.sample @@ -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 diff --git a/conf.d/pgsql.conf.sample b/conf.d/pgsql.conf.sample new file mode 100644 index 00000000..f559cff0 --- /dev/null +++ b/conf.d/pgsql.conf.sample @@ -0,0 +1,9 @@ +[general] +tcp = yes +host = localhost +port = 5432 +database = yate +user = yate +password = +socket = +priority = 50 diff --git a/conf.d/pgsql.sql b/conf.d/pgsql.sql new file mode 100644 index 00000000..512fa50a --- /dev/null +++ b/conf.d/pgsql.sql @@ -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'); + diff --git a/conf.d/regexroute.conf.sample b/conf.d/regexroute.conf.sample new file mode 100644 index 00000000..8178d71e --- /dev/null +++ b/conf.d/regexroute.conf.sample @@ -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 diff --git a/conf.d/register.sql b/conf.d/register.sql new file mode 100644 index 00000000..c4f10570 --- /dev/null +++ b/conf.d/register.sql @@ -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' +); + diff --git a/conf.d/rmanager.conf.sample b/conf.d/rmanager.conf.sample new file mode 100644 index 00000000..955b4624 --- /dev/null +++ b/conf.d/rmanager.conf.sample @@ -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. diff --git a/conf.d/test1.conf.sample b/conf.d/test1.conf.sample new file mode 100644 index 00000000..46c56bba --- /dev/null +++ b/conf.d/test1.conf.sample @@ -0,0 +1,3 @@ +[general] +noisy=0 +threads=0 diff --git a/conf.d/zapchan.conf.sample b/conf.d/zapchan.conf.sample new file mode 100644 index 00000000..61c86b6b --- /dev/null +++ b/conf.d/zapchan.conf.sample @@ -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 diff --git a/configure.in b/configure.in new file mode 100644 index 00000000..b77cfae9 --- /dev/null +++ b/configure.in @@ -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 diff --git a/docs/.cvsignore b/docs/.cvsignore new file mode 100644 index 00000000..3359fbab --- /dev/null +++ b/docs/.cvsignore @@ -0,0 +1,4 @@ +core* +*.orig +*~ +.*.swp diff --git a/docs/api/.cvsignore b/docs/api/.cvsignore new file mode 100644 index 00000000..6b7e947b --- /dev/null +++ b/docs/api/.cvsignore @@ -0,0 +1,5 @@ +core* +*.html +*.orig +*~ +.*.swp diff --git a/docs/dataflow.html b/docs/dataflow.html new file mode 100644 index 00000000..a63bd078 --- /dev/null +++ b/docs/dataflow.html @@ -0,0 +1,18 @@ + + +YATE - Data flows + + +

YATE

+

Data flows

+

Data blocks

+

+

Data nodes

+

Data endpoints

+

Links

+Overview.
+The DataBlock class.
+The DataNode class.
+The DataEndpoint class.
+ + diff --git a/docs/extmodule.html b/docs/extmodule.html new file mode 100644 index 00000000..0acf3061 --- /dev/null +++ b/docs/extmodule.html @@ -0,0 +1,119 @@ + + +YATE - External module + + +The work on this document is still in progress. +Please do not use it as reference, the specifications are likely to change. + +

External module command flow

+ +

File handles used

+The external user application or script comunicates with the module trough +several file descriptors:
+0 (stdin) - Carries commands and notifications from engine to application
+1 (stdout) - Carries commands and answers from application to the engine
+2 (stderr) - Has the usual meaning of reporting errors and is directed to the engine's stderr or logfile
+3 (optional) - Transports audio data from the engine to the application
+4 (optional) - Transports audio data from the application to the engine
+ +File descriptors 3 and 4 are open only for audio capable applications.
+ + +

Format of commands and notifications

+ +In the following description the _ (underscore) character is used to denote +the TAB character (\t, ^I, decimal 9) used as element separator.
+ +Words enclosed in <angle brackets> must be replaced with the proper value.
+ +Elements in [square brackets] are optional. An ellipsis ... denotes optional +repetition of the last element.
+ +Every command is sent on its own newline (\n, ^J, decimal 10) delimited line.
+ +Any value that contains special characters (ASCII code lower than 32) MUST have +them converted to %<hexcode> where <hexcode> 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.
+ +

Keyword: %%=error
+%%=error_<original>
+The engine sends this notification as answer to a syntactically incorrect line +it received from the application.
+<original> - the original line exactly as received (not escaped or something)
+

+ +

Keyword: %%=init
+%%=init
+This command is sent to the other party requesting it to (re)initialize.
+

+ +

Keyword: %%=halt
+%%=halt[_<exitcode>]
+Command from application to the engine asking it to terminate. An optional +exit code may be provided.
+<exitcode> - positive numeric engine exit code
+

+ +

Keyword: %%=output
+%%=output_<message>
+Asks the engine to display a message in its console output.
+<message> - message line to display as-is
+

+ +

Keyword: %%=debug
+%%=debug_[<facility>]_<level>_<message>
+Asks the engine to emit a debugging message to the console output.
+<facility> - optional facility text to display
+<level> - numeric debug level (0-9) at which to output
+<message> - debug message to display
+

+ +

Keyword: %%>message
+%%>message_<id>_<name>_<retvalue>[_<key>=<value>...]
+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.
+<id> - obscure unique message ID string generated by the sender
+<name> - name of the message
+<retvalue> - default textual return value of the message
+<key>=<value> - enumeration of the key-value pairs of the message
+

+ +

Keyword: %%<message
+%%<message_<id>_<processed>_[<name>]_<retvalue>[_<key>=<value>...]
+One of this answer is required for every received message (%%>message). When a +party gets a line in this format it should paste the provided values back into +the message and let it continue.
+<id> - same message ID string received trough %%>message
+<processed> - boolean ("true" or "false") indication if the message has been + processed or it should be passed to the next handler
+<name> - new name of the message, if empty keep unchanged
+<retvalue> - new textual return value of the message
+<key>=<value> - new key-value pairs to set in the message; delete the key if + the value is an empty string
+

+ +

Keyword: %%>install
+%%>install_<name>[_<priority>]
+Always from the application to the engine, requests the installing of a message +handler
+<name> - name of the messages for that a handler should be installed
+<priority> - priority, use default if missing
+

+ +

Keyword: %%<install
+%%<install_<name>_<priority>
+Confirmation from engine to the application that the handler has been installed +properly or not.
+<name> - name of the messages asked to handle
+<priority> - priority of the installed handler, -1 if it failed
+

+ + + diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 00000000..5845edcc --- /dev/null +++ b/docs/index.html @@ -0,0 +1,23 @@ + + +YATE - Yet Another Telephony Engine + + +

YATE

+

Yet Another Telephony Engine

+

Purpose

+

YATE is a telephony engine designed to implement PBX and IVR solutions for +small to large scale projects.

+

Architecture

+

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.

+

API Documentation

+Class list also available in +annotated format.
+Class hierarcy
+Messages
+Data flows
+ + + diff --git a/docs/messages.html b/docs/messages.html new file mode 100644 index 00000000..c3aaada6 --- /dev/null +++ b/docs/messages.html @@ -0,0 +1,16 @@ + + +YATE - Standard and typical messages + + +

YATE

+

Standard and typical messages

+

Message format

+

Messages in YATE

+

Standard messages

+

Other messages

+

Links

+Overview.
+The Message class.
+ + diff --git a/engine/Configuration.cpp b/engine/Configuration.cpp new file mode 100644 index 00000000..55071fd3 --- /dev/null +++ b/engine/Configuration.cpp @@ -0,0 +1,204 @@ +/** + * Configuration.cpp + * This file is part of the YATE Project http://YATE.null.ro + */ + +#include "telengine.h" + +#include +#include + +using namespace TelEngine; + +Configuration::Configuration() +{ +} + +Configuration::Configuration(const char *filename) + : String(filename) +{ + load(); +} + +ObjList *Configuration::getSectHolder(const String §) const +{ + if (sect.null()) + return 0; + ObjList *l = const_cast(&m_sections); + for (;l;l=l->next()) { + NamedList *n = static_cast(l->get()); + if (n && *n == sect) + return l; + } + return 0; +} + +ObjList *Configuration::makeSectHolder(const String §) +{ + 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(l->get()) : 0; +} + +NamedList *Configuration::getSection(const String §) const +{ + ObjList *l = getSectHolder(sect); + return l ? static_cast(l->get()) : 0; +} + +NamedString *Configuration::getKey(const String §, const String &key) const +{ + NamedList *l = getSection(sect); + return l ? l->getParam(key) : 0; +} + +const char *Configuration::getValue(const String §, const String &key, const char *defvalue) const +{ + const NamedString *s = getKey(sect,key); + return s ? s->c_str() : defvalue; +} + +int Configuration::getIntValue(const String §, const String &key, int defvalue) const +{ + const NamedString *s = getKey(sect,key); + return s ? s->toInteger(defvalue) : defvalue; +} + +int Configuration::getIntValue(const String §, 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 §, 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 §, const String &key) +{ + NamedList *l = getSection(sect); + if (l) + l->clearParam(key); +} + +void Configuration::setValue(const String §, 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(l->get()); + if (n) + n->setParam(key,value); +} + +void Configuration::setValue(const String §, const char *key, int value) +{ + char buf[32]; + ::sprintf(buf,"%d",value); + setValue(sect,key,buf); +} + +void Configuration::setValue(const String §, 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(&m_sections); + for (;ol;ol=ol->next()) { + NamedList *nl = static_cast(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; +} diff --git a/engine/DataBlock.cpp b/engine/DataBlock.cpp new file mode 100644 index 00000000..2bcb6684 --- /dev/null +++ b/engine/DataBlock.cpp @@ -0,0 +1,543 @@ +/** + * DataBlock.cpp + * This file is part of the YATE Project http://YATE.null.ro + */ + +#include "telephony.h" + +#include +#include + +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(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(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(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; +} diff --git a/engine/Engine.cpp b/engine/Engine.cpp new file mode 100644 index 00000000..a1cbee4e --- /dev/null +++ b/engine/Engine.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +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; igetParam(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; igetParam(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(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) { + 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(); +} diff --git a/engine/Message.cpp b/engine/Message.cpp new file mode 100644 index 00000000..d0bce00d --- /dev/null +++ b/engine/Message.cpp @@ -0,0 +1,269 @@ +/** + * Message.cpp + * This file is part of the YATE Project http://YATE.null.ro + */ + +#include "telengine.h" +#include + +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:"); + 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("%%> 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(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(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(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(m_messages.remove(false)); + m_mutex.unlock(); + if (!msg) + return false; + dispatch(*msg); + msg->destruct(); + return true; +} + +void MessageDispatcher::dequeue() +{ + while (dequeueOne()) + ; +} diff --git a/engine/Mutex.cpp b/engine/Mutex.cpp new file mode 100644 index 00000000..ee7a9da6 --- /dev/null +++ b/engine/Mutex.cpp @@ -0,0 +1,139 @@ +/** + * Mutex.cpp + * This file is part of the YATE Project http://YATE.null.ro + */ + +#include "telengine.h" + +#include +#include + +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; +} diff --git a/engine/NamedList.cpp b/engine/NamedList.cpp new file mode 100644 index 00000000..7a7d9b8f --- /dev/null +++ b/engine/NamedList.cpp @@ -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(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(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(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; +} diff --git a/engine/ObjList.cpp b/engine/ObjList.cpp new file mode 100644 index 00000000..bea2ea1b --- /dev/null +++ b/engine/ObjList.cpp @@ -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(n); +} + +ObjList *ObjList::operator[](int index) const +{ + if (index < 0) + return 0; + ObjList *obj = const_cast(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(n); +} + +GenObject *ObjList::set(const GenObject *obj, bool delold) +{ + if (m_obj == obj) + return 0; + GenObject *tmp = m_obj; + m_obj = const_cast(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(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(); +} diff --git a/engine/Plugin.cpp b/engine/Plugin.cpp new file mode 100644 index 00000000..2ae6e517 --- /dev/null +++ b/engine/Plugin.cpp @@ -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); +} diff --git a/engine/String.cpp b/engine/String.cpp new file mode 100644 index 00000000..cc9c35d7 --- /dev/null +++ b/engine/String.cpp @@ -0,0 +1,774 @@ +/** + * String.cpp + * This file is part of the YATE Project http://YATE.null.ro + */ + +#include "telengine.h" + +#include +#include +#include +#include +#include + +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 +} diff --git a/engine/TelEngine.cpp b/engine/TelEngine.cpp new file mode 100644 index 00000000..506cee8f --- /dev/null +++ b/engine/TelEngine.cpp @@ -0,0 +1,233 @@ +/** + * TelEngine.cpp + * This file is part of the YATE Project http://YATE.null.ro + */ + +#include "telengine.h" + +#include +#include +#include +#include +#include + +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 +} + +}; diff --git a/engine/Thread.cpp b/engine/Thread.cpp new file mode 100644 index 00000000..f68cbd54 --- /dev/null +++ b/engine/Thread.cpp @@ -0,0 +1,330 @@ +/** + * Thread.cpp + * This file is part of the YATE Project http://YATE.null.ro + */ + +#include "telengine.h" + +#include +#include + +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(¤t_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(::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(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(arg); + if (t) + t->destroy(); +} + +void ThreadPrivate::cleanupFunc(void *arg) +{ +#ifdef DEBUG + Debug(DebugAll,"ThreadPrivate::cleanupFunc(%p)",arg); +#endif + ThreadPrivate *t = reinterpret_cast(arg); + if (t) + t->cleanup(); +} + +void ThreadPrivate::keyAllocFunc() +{ +#ifdef DEBUG + Debug(DebugAll,"ThreadPrivate::keyAllocFunc()"); +#endif + if (::pthread_key_create(¤t_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(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 +} diff --git a/main.cpp b/main.cpp new file mode 100644 index 00000000..5554822c --- /dev/null +++ b/main.cpp @@ -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); +} diff --git a/modules/.cvsignore b/modules/.cvsignore new file mode 100644 index 00000000..20aaabb7 --- /dev/null +++ b/modules/.cvsignore @@ -0,0 +1,9 @@ +Makefile +core* +*.o +*.a +*.so +*.yate +*.orig +*~ +.*.swp diff --git a/modules/Makefile.in b/modules/Makefile.in new file mode 100644 index 00000000..280ddc06 --- /dev/null +++ b/modules/Makefile.in @@ -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 diff --git a/modules/cdrbuild.cpp b/modules/cdrbuild.cpp new file mode 100644 index 00000000..e1d720c3 --- /dev/null +++ b/modules/cdrbuild.cpp @@ -0,0 +1,228 @@ +/** + * cdrbuild.cpp + * This file is part of the YATE Project http://YATE.null.ro + * + * Cdr builder + */ + +#include +#include + +#include + +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(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(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: */ diff --git a/modules/cdrfile.cpp b/modules/cdrfile.cpp new file mode 100644 index 00000000..f73af855 --- /dev/null +++ b/modules/cdrfile.cpp @@ -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 + +#include + +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: */ diff --git a/modules/cdrpgsql.cpp b/modules/cdrpgsql.cpp new file mode 100644 index 00000000..2dad861b --- /dev/null +++ b/modules/cdrpgsql.cpp @@ -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 + +#include +#include + +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: */ diff --git a/modules/extmodule.cpp b/modules/extmodule.cpp new file mode 100644 index 00000000..21160fa7 --- /dev/null +++ b/modules/extmodule.cpp @@ -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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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(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(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; igetParam(i); + if (n) { + new ExtModReceiver(n->name(),*n); + } + } + } + } +} + +INIT_PLUGIN(ExtModulePlugin); + +/* vi: set ts=8 sw=4 sts=4 noet: */ diff --git a/modules/gsmcodec.cpp b/modules/gsmcodec.cpp new file mode 100644 index 00000000..876ac9e7 --- /dev/null +++ b/modules/gsmcodec.cpp @@ -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 +#include + +extern "C" { +#include + +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; iForward(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: */ diff --git a/modules/h323chan.cpp b/modules/h323chan.cpp new file mode 100644 index 00000000..4ff45aa7 --- /dev/null +++ b/modules/h323chan.cpp @@ -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 +#include +#include +#include +#include + + +/* 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 +#include +#include + +#include + +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(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(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(getConsumer()),false); + } + else { + // data coming FROM h.323 + if (getSource()) + return codec.AttachChannel(static_cast(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(getSource())->Close(); + if (getConsumer()) + static_cast(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(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; kaddParam("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(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(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(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: */ diff --git a/modules/mktestlinks.sh b/modules/mktestlinks.sh new file mode 100644 index 00000000..402a36e3 --- /dev/null +++ b/modules/mktestlinks.sh @@ -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 diff --git a/modules/msgsniff.cpp b/modules/msgsniff.cpp new file mode 100644 index 00000000..9504574f --- /dev/null +++ b/modules/msgsniff.cpp @@ -0,0 +1,58 @@ +/* + test.c + This file holds the entry point of the Telephony Engine +*/ + +#include + +#include + +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: */ diff --git a/modules/pgsqlroute.cpp b/modules/pgsqlroute.cpp new file mode 100644 index 00000000..d1ebf26d --- /dev/null +++ b/modules/pgsqlroute.cpp @@ -0,0 +1,216 @@ +/** + * pgsqlroute.cpp + * This file is part of the YATE Project http://YATE.null.ro + * + * Postgres SQL based routing +*/ + +#include +#include + +#include +#include + +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; igetParam(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: */ diff --git a/modules/regexroute.cpp b/modules/regexroute.cpp new file mode 100644 index 00000000..b11ff1e3 --- /dev/null +++ b/modules/regexroute.cpp @@ -0,0 +1,139 @@ +/** + * regexroute.cpp + * + * This file is part of the YATE Project http://YATE.null.ro + * Regexp based routing + */ + +#include +#include + +#include + +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; igetParam(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; igetParam(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: */ diff --git a/modules/register.cpp b/modules/register.cpp new file mode 100644 index 00000000..836e475d --- /dev/null +++ b/modules/register.cpp @@ -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 + +#include +#include + +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: */ diff --git a/modules/rmanager.cpp b/modules/rmanager.cpp new file mode 100644 index 00000000..f092a1e9 --- /dev/null +++ b/modules/rmanager.cpp @@ -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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + + +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(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(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: */ diff --git a/modules/tonegen.cpp b/modules/tonegen.cpp new file mode 100644 index 00000000..bbe58a83 --- /dev/null +++ b/modules/tonegen.cpp @@ -0,0 +1,299 @@ +/** + * tonegen.cpp + * This file is part of the YATE Project http://YATE.null.ro + * + * Tones generator + */ + +#include +#include + +#include +#include + +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(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(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(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: */ diff --git a/modules/wavefile.cpp b/modules/wavefile.cpp new file mode 100644 index 00000000..2d7bfbe4 --- /dev/null +++ b/modules/wavefile.cpp @@ -0,0 +1,226 @@ +/** + * wavefile.cpp + * This file is part of the YATE Project http://YATE.null.ro + * + * Wave file driver (record+playback) + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + + +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(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: */ diff --git a/modules/zapchan.cpp b/modules/zapchan.cpp new file mode 100644 index 00000000..f131e3ee --- /dev/null +++ b/modules/zapchan.cpp @@ -0,0 +1,1219 @@ +/** + * zapchan.cpp + * This file is part of the YATE Project http://YATE.null.ro + * + * Zapata telephony driver + */ + +extern "C" { +#include +#include +}; +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +using namespace TelEngine; + +#define SIG_EM ZT_SIG_EM +#define SIG_EMWINK (0x10000 | ZT_SIG_EM) +#define SIG_FEATD (0x20000 | ZT_SIG_EM) +#define SIG_FEATDMF (0x40000 | ZT_SIG_EM) +#define SIG_FEATB (0x80000 | ZT_SIG_EM) +#define SIG_FXSLS ZT_SIG_FXSLS +#define SIG_FXSGS ZT_SIG_FXSGS +#define SIG_FXSKS ZT_SIG_FXSKS +#define SIG_FXOLS ZT_SIG_FXOLS +#define SIG_FXOGS ZT_SIG_FXOGS +#define SIG_FXOKS ZT_SIG_FXOKS +#define SIG_PRI ZT_SIG_CLEAR +#define SIG_R2 ZT_SIG_CAS +#define SIG_SF ZT_SIG_SF +#define SIG_SFWINK (0x10000 | ZT_SIG_SF) +#define SIG_SF_FEATD (0x20000 | ZT_SIG_SF) +#define SIG_SF_FEATDMF (0x40000 | ZT_SIG_SF) +#define SIG_SF_FEATB (0x80000 | ZT_SIG_SF) + +static int s_buflen = 480; + +static int zt_get_event(int fd) +{ + /* Avoid the silly zt_getevent which ignores a bunch of events */ + int j = 0; + if (::ioctl(fd, ZT_GETEVENT, &j) == -1) + return -1; + return j; +} + +#if 0 +static int zt_wait_event(int fd) +{ + /* Avoid the silly zt_waitevent which ignores a bunch of events */ + int i = ZT_IOMUX_SIGEVENT, j = 0; + if (::ioctl(fd, ZT_IOMUX, &i) == -1) + return -1; + if (::ioctl(fd, ZT_GETEVENT, &j) == -1) + return -1; + return j; +} +#endif + +static int zt_open(int channo, bool subchan,unsigned int blksize) +{ + Debug("ZapChan",DebugInfo,"Open zap channel=%d with block size=%d",channo,blksize); + int fd = ::open(subchan ? "/dev/zap/pseudo" : "/dev/zap/channel",O_RDWR|O_NONBLOCK); + if (fd < 0) { + Debug("ZapChan",DebugFail,"Failed to open zap device: error %d: %s",errno,::strerror(errno)); + return -1; + } + if (channo) { + if (::ioctl(fd, subchan ? ZT_CHANNO : ZT_SPECIFY, &channo)) { + Debug("ZapChan",DebugFail,"Failed to specify chan %d: error %d: %s",channo,errno,::strerror(errno)); + ::close(fd); + return -1; + } + } + if (blksize) { + if (::ioctl(fd, ZT_SET_BLOCKSIZE, &blksize) == -1) { + Debug("ZapChan",DebugFail,"Failed to set block size %d: error %d: %s",blksize,errno,::strerror(errno)); + ::close(fd); + return -1; + } + } + return fd; +} + +static bool zt_set_law(int fd, int law) +{ + if (law < 0) { + int linear = 1; + if (::ioctl(fd, ZT_SETLINEAR, &linear) != -1) + return true; + } + if (::ioctl(fd, ZT_SETLAW, &law) != -1) + return true; + + Debug("ZapChan",DebugInfo,"Failed to set law %d: error %d: %s",law,errno,::strerror(errno)); + return false; +} + +static void zt_close(int fd) +{ + if (fd != -1) + ::close(fd); +} + +#if 0 + +static const char *pri_alarm(int alarm) +{ + if (!alarm) + return "No alarm"; + if (alarm & ZT_ALARM_RED) + return "Red Alarm"; + if (alarm & ZT_ALARM_YELLOW) + return "Yellow Alarm"; + if (alarm & ZT_ALARM_BLUE) + return "Blue Alarm"; + if (alarm & ZT_ALARM_RECOVER) + return "Recovering"; + if (alarm & ZT_ALARM_LOOPBACK) + return "Loopback"; + if (alarm & ZT_ALARM_NOTOPEN) + return "Not Open"; + return "Unknown status"; +} + +static const char *sig_names(int sig) +{ + switch (sig) { + case SIG_EM: + return "E & M Immediate"; + case SIG_EMWINK: + return "E & M Wink"; + case SIG_FEATD: + return "Feature Group D (DTMF)"; + case SIG_FEATDMF: + return "Feature Group D (MF)"; + case SIG_FEATB: + return "Feature Group B (MF)"; + case SIG_FXSLS: + return "FXS Loopstart"; + case SIG_FXSGS: + return "FXS Groundstart"; + case SIG_FXSKS: + return "FXS Kewlstart"; + case SIG_FXOLS: + return "FXO Loopstart"; + case SIG_FXOGS: + return "FXO Groundstart"; + case SIG_FXOKS: + return "FXO Kewlstart"; + case SIG_PRI: + return "PRI Signalling"; + case SIG_R2: + return "R2 Signalling"; + case SIG_SF: + return "SF (Tone) Signalling Immediate"; + case SIG_SFWINK: + return "SF (Tone) Signalling Wink"; + case SIG_SF_FEATD: + return "SF (Tone) Signalling with Feature Group D (DTMF)"; + case SIG_SF_FEATDMF: + return "SF (Tone) Signallong with Feature Group D (MF)"; + case SIG_SF_FEATB: + return "SF (Tone) Signalling with Feature Group B (MF)"; + case 0: + return "Pseudo Signalling"; + default: + static char buf[64]; + ::sprintf(buf,"Unknown signalling %d",sig); + return buf; + } +} + +#endif + +static void pri_err_cb(char *s) +{ + Debug("PRI",DebugWarn,s); +} + +static void pri_msg_cb(char *s) +{ + Debug("PRI",DebugInfo,s); +} + +static TokenDict dict_str2switch[] = { + { "unknown", PRI_SWITCH_UNKNOWN }, + { "ni2", PRI_SWITCH_NI2 }, + { "dms100", PRI_SWITCH_DMS100 }, + { "lucent5e", PRI_SWITCH_LUCENT5E }, + { "at&t4ess", PRI_SWITCH_ATT4ESS }, + { "euroisdn_e1", PRI_SWITCH_EUROISDN_E1 }, + { "euroisdn_t1", PRI_SWITCH_EUROISDN_T1 }, + { "ni1", PRI_SWITCH_NI1 }, + { 0, -1 } +}; + +static TokenDict dict_str2nplan[] = { + { "unknown", PRI_NPI_UNKNOWN }, + { "e164", PRI_NPI_E163_E164 }, + { "x121", PRI_NPI_X121 }, + { "f69", PRI_NPI_F69 }, + { "national", PRI_NPI_NATIONAL }, + { "private", PRI_NPI_PRIVATE }, + { "reserved", PRI_NPI_RESERVED }, + { 0, -1 } +}; + +static TokenDict dict_str2ntype[] = { + { "unknown", PRI_TON_UNKNOWN }, + { "international", PRI_TON_INTERNATIONAL }, + { "national", PRI_TON_NATIONAL }, + { "net_specific", PRI_TON_NET_SPECIFIC }, + { "subscriber", PRI_TON_SUBSCRIBER }, + { "abbreviated", PRI_TON_ABBREVIATED }, + { "reserved", PRI_TON_RESERVED }, + { 0, -1 } +}; + +static TokenDict dict_str2dplan[] = { + { "unknown", PRI_UNKNOWN }, + { "international", PRI_INTERNATIONAL_ISDN }, + { "national", PRI_NATIONAL_ISDN }, + { "local", PRI_LOCAL_ISDN }, + { "private", PRI_PRIVATE }, + { 0, -1 } +}; + +static TokenDict dict_str2pres[] = { + { "allow_user_not_screened", PRES_ALLOWED_USER_NUMBER_NOT_SCREENED }, + { "allow_user_passed", PRES_ALLOWED_USER_NUMBER_PASSED_SCREEN }, + { "allow_user_failed", PRES_ALLOWED_USER_NUMBER_FAILED_SCREEN }, + { "allow_network", PRES_ALLOWED_NETWORK_NUMBER }, + { "prohibit_user_not_screened", PRES_PROHIB_USER_NUMBER_NOT_SCREENED }, + { "prohibit_user_passed", PRES_PROHIB_USER_NUMBER_PASSED_SCREEN }, + { "prohibit_user_failed", PRES_PROHIB_USER_NUMBER_FAILED_SCREEN }, + { "prohibit_network", PRES_PROHIB_NETWORK_NUMBER }, + { "not_available", PRES_NUMBER_NOT_AVAILABLE }, + { 0, -1 } +}; + +static TokenDict dict_str2law[] = { + { "mulaw", PRI_LAYER_1_ULAW }, + { "alaw", PRI_LAYER_1_ALAW }, + { "g721", PRI_LAYER_1_G721 }, + { 0, -1 } +}; + +static TokenDict dict_str2ztlaw[] = { + { "slin", -1 }, + { "default", ZT_LAW_DEFAULT }, + { "mulaw", ZT_LAW_MULAW }, + { "alaw", ZT_LAW_ALAW }, + { 0, -2 } +}; + +class ZapChan; + +class PriSpan : public GenObject, public Thread +{ +public: + static PriSpan *create(int span, int chan1, int nChans, int dChan, bool isNet, int switchType, int dialPlan, int presentation); + virtual ~PriSpan(); + virtual void run(); + inline struct pri *pri() + { return m_pri; } + inline int span() const + { return m_span; } + inline bool belongs(int chan) const + { return (chan >= m_offs) && (chan < m_offs+m_nchans); } + inline int chan1() const + { return m_offs; } + inline int chans() const + { return m_nchans; } + inline int dplan() const + { return m_dplan; } + inline int pres() const + { return m_pres; } + int findEmptyChan(int first = 0, int last = 65535) const; + ZapChan *getChan(int chan) const; + void idle(); + static unsigned long long restartPeriod; + static bool dumpEvents; + +private: + PriSpan(struct pri *_pri, int span, int first, int chans, int dchan, int fd, int dplan, int pres); + static struct pri *makePri(int fd, int dchan, int nettype, int swtype); + void handleEvent(pri_event &ev); + bool validChan(int chan) const; + void restartChan(int chan, bool force = false); + void ringChan(int chan, pri_event_ring &ev); + void infoChan(int chan, pri_event_ring &ev); + void hangupChan(int chan,pri_event_hangup &ev); + void ackChan(int chan); + void proceedingChan(int chan); + int m_span; + int m_offs; + int m_nchans; + int m_fd; + int m_dplan; + int m_pres; + struct pri *m_pri; + unsigned long long m_restart; + ZapChan **m_chans; +}; + +class ZapChan : public DataEndpoint +{ +public: + ZapChan(PriSpan *parent, int chan, unsigned int bufsize); + virtual ~ZapChan(); + virtual void disconnected(); + virtual bool nativeConnect(DataEndpoint *peer); + inline PriSpan *span() const + { return m_span; } + inline int chan() const + { return m_chan; } + inline int absChan() const + { return m_abschan; } + inline bool inUse() const + { return (m_ring || m_call); } + void ring(q931_call *call = 0); + void hangup(int cause = PRI_CAUSE_NORMAL_CLEARING); + void sendDigit(char digit); + void call(Message &msg); + bool answer(); + void idle(); + void restart(); + bool open(int defLaw = -1); + inline void setTimeout(unsigned long long tout) + { m_timeout = tout ? Time::now()+tout : 0; } + const char *status() const; + inline int fd() const + { return m_fd; } + inline int law() const + { return m_law; } +private: + PriSpan *m_span; + int m_chan; + bool m_ring; + unsigned long long m_timeout; + q931_call *m_call; + unsigned int m_bufsize; + int m_abschan; + int m_fd; + int m_law; +}; + +class ZapSource : public ThreadedSource +{ +public: + ZapSource(ZapChan *owner,unsigned int bufsize) + : m_owner(owner), m_bufsize(bufsize) + { + Debug(DebugAll,"ZapSource::ZapSource(%p) [%p]",owner,this); + start("ZapSource"); + } + + ~ZapSource() + { Debug(DebugAll,"ZapSource::~ZapSource() [%p]",this); } + + virtual void run(); + +private: + ZapChan *m_owner; + unsigned int m_bufsize; +}; + +class ZapConsumer : public DataConsumer +{ +public: + ZapConsumer(ZapChan *owner,unsigned int bufsize) + : m_owner(owner), m_bufsize(bufsize) + { Debug(DebugAll,"ZapConsumer::ZapConsumer(%p) [%p]",owner,this); } + + ~ZapConsumer() + { Debug(DebugAll,"ZapConsumer::~ZapConsumer() [%p]",this); } + + virtual void Consume(const DataBlock &data); + +private: + ZapChan *m_owner; + unsigned int m_bufsize; + DataBlock m_buffer; +}; + +class ZapHandler : public MessageHandler +{ +public: + ZapHandler(const char *name) : MessageHandler(name) { } + virtual bool received(Message &msg); +}; + +class ZapDropper : public MessageHandler +{ +public: + ZapDropper(const char *name) : MessageHandler(name) { } + virtual bool received(Message &msg); +}; + +class StatusHandler : public MessageHandler +{ +public: + StatusHandler() : MessageHandler("status") { } + virtual bool received(Message &msg); +}; + +class ZaptelPlugin : public Plugin +{ + friend class PriSpan; + friend class ZapHandler; +public: + ZaptelPlugin(); + virtual ~ZaptelPlugin(); + virtual void initialize(); + PriSpan *findSpan(int chan); + ZapChan *findChan(int first = -1, int last = -1); + ObjList m_spans; +}; + +ZaptelPlugin zplugin; +unsigned long long PriSpan::restartPeriod = 0; +bool PriSpan::dumpEvents = false; + +PriSpan *PriSpan::create(int span, int chan1, int nChans, int dChan, bool isNet, int switchType, int dialPlan, int presentation) +{ + int fd = ::open("/dev/zap/channel", O_RDWR, 0600); + if (fd < 0) + return 0; + struct pri *p = makePri(fd, + (dChan >= 0) ? dChan+chan1-1 : -1, + (isNet ? PRI_NETWORK : PRI_CPE), + switchType); + if (!p) { + ::close(fd); + return 0; + } + return new PriSpan(p,span,chan1,nChans,dChan,fd,dialPlan,presentation); +} + +struct pri *PriSpan::makePri(int fd, int dchan, int nettype, int swtype) +{ + if (dchan >= 0) { + // Set up the D channel if we have one + if (::ioctl(fd,ZT_SPECIFY,&dchan) == -1) { + Debug("PriSpan",DebugFail,"Failed to open D-channel %d: error %d: %s", + dchan,errno,::strerror(errno)); + return 0; + } + ZT_PARAMS par; + if (::ioctl(fd, ZT_GET_PARAMS, &par) == -1) { + Debug("PriSpan",DebugFail,"Failed to get parameters of D-channel %d: error %d: %s", + dchan,errno,::strerror(errno)); + return 0; + } + if (par.sigtype != ZT_SIG_HDLCFCS) { + Debug("PriSpan",DebugFail,"D-channel %d is not in HDLC/FCS mode",dchan); + return 0; + } + ZT_BUFFERINFO bi; + bi.txbufpolicy = ZT_POLICY_IMMEDIATE; + bi.rxbufpolicy = ZT_POLICY_IMMEDIATE; + bi.numbufs = 16; + bi.bufsize = 1024; + if (::ioctl(fd, ZT_SET_BUFINFO, &bi) == -1) { + Debug("PriSpan",DebugFail,"Could not set buffering on D-channel %d",dchan); + return 0; + } + } + return ::pri_new(fd, nettype, swtype); +} + +PriSpan::PriSpan(struct pri *_pri, int span, int first, int chans, int dchan, int fd, int dplan, int pres) + : Thread("PriSpan"), m_span(span), m_offs(first), m_nchans(chans), + m_fd(fd), m_dplan(dplan), m_pres(pres), m_pri(_pri), + m_restart(0), m_chans(0) +{ + Debug(DebugAll,"PriSpan::PriSpan(%p,%d,%d,%d) [%p]",_pri,span,chans,fd,this); + ZapChan **ch = new (ZapChan *)[chans]; + for (int i = 1; i <= chans; i++) + ch[i-1] = (i == dchan) ? 0 : new ZapChan(this,i,s_buflen); + m_chans = ch; + zplugin.m_spans.append(this); +} + +PriSpan::~PriSpan() +{ + Debug(DebugAll,"PriSpan::~PriSpan() [%p]",this); + zplugin.m_spans.remove(this,false); + for (int i = 0; i hangup(); + c->destruct(); + } + } + delete m_chans; + ::close(m_fd); +} + +void PriSpan::run() +{ + Debug(DebugAll,"PriSpan::run() [%p]",this); + m_restart = Time::now() + restartPeriod; + fd_set rdfds; + fd_set errfds; + for (;;) { + FD_ZERO(&rdfds); + FD_SET(m_fd, &rdfds); + FD_ZERO(&errfds); + FD_SET(m_fd, &errfds); + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 100; + int sel = ::select(m_fd+1, &rdfds, NULL, &errfds, &tv); + pri_event *ev = 0; + if (!sel) { + ev = ::pri_schedule_run(m_pri); + idle(); + } + else if (sel > 0) + ev = ::pri_check_event(m_pri); + else if (errno != EINTR) + Debug("PriSpan",DebugFail,"select() error %d: %s", + errno,::strerror(errno)); + if (ev) { + if (dumpEvents && debugAt(DebugAll)) + ::pri_dump_event(m_pri, ev); + handleEvent(*ev); + } + else { + int zev = zt_get_event(m_fd); + if (zev) + Debug("PriSpan",DebugInfo,"Zapata event %d",zev); + } + } +} + +void PriSpan::idle() +{ + if (restartPeriod && (Time::now() > m_restart)) { + m_restart = Time::now() + restartPeriod; + Debug("PriSpan",DebugInfo,"Restarting idle channels on span %d",m_span); + for (int i=1; iidle(); +} + +void PriSpan::handleEvent(pri_event &ev) +{ + switch (ev.e) { + case PRI_EVENT_DCHAN_UP: + Debug(DebugInfo,"D-channel up on span %d",m_span); + break; + case PRI_EVENT_DCHAN_DOWN: + Debug(DebugWarn,"D-channel down on span %d",m_span); + break; + case PRI_EVENT_RESTART: + restartChan(ev.restart.channel,true); + break; + case PRI_EVENT_CONFIG_ERR: + Debug(DebugWarn,"Error on span %d: %s",m_span,ev.err.err); + break; + case PRI_EVENT_RING: + ringChan(ev.ring.channel,ev.ring); + break; + case PRI_EVENT_INFO_RECEIVED: + infoChan(ev.ring.channel,ev.ring); + break; + case PRI_EVENT_RINGING: + Debug(DebugInfo,"Ringing our call on channel %d on span %d",ev.ringing.channel,m_span); + break; + case PRI_EVENT_HANGUP: + Debug(DebugInfo,"Hangup detected on channel %d on span %d",ev.hangup.channel,m_span); + hangupChan(ev.hangup.channel,ev.hangup); + break; + case PRI_EVENT_ANSWER: + Debug(DebugInfo,"Answered channel %d on span %d",ev.answer.channel,m_span); + break; + case PRI_EVENT_HANGUP_ACK: + Debug(DebugInfo,"Hangup ACK on channel %d on span %d",ev.hangup.channel,m_span); + break; + case PRI_EVENT_RESTART_ACK: + Debug(DebugInfo,"Restart ACK on channel %d on span %d",ev.restartack.channel,m_span); + break; + case PRI_EVENT_SETUP_ACK: + Debug(DebugInfo,"Setup ACK on channel %d on span %d",ev.setup_ack.channel,m_span); + ackChan(ev.setup_ack.channel); + break; + case PRI_EVENT_HANGUP_REQ: + Debug(DebugInfo,"Hangup REQ on channel %d on span %d",ev.hangup.channel,m_span); + hangupChan(ev.hangup.channel,ev.hangup); + break; + case PRI_EVENT_PROCEEDING: + Debug(DebugInfo,"Call proceeding on channel %d on span %d",ev.proceeding.channel,m_span); + proceedingChan(ev.proceeding.channel); + break; + default: + Debug(DebugInfo,"Received PRI event %d",ev.e); + } +} + +bool PriSpan::validChan(int chan) const +{ + return (chan > 0) && (chan <= m_nchans) && m_chans && m_chans[chan-1]; +} + +int PriSpan::findEmptyChan(int first, int last) const +{ + first -= m_offs; + last -= m_offs; + if (first < 0) + first = 0; + if (last > m_nchans-1) + last = m_nchans-1; + for (int i=first; i<=last; i++) + if (m_chans[i] && !m_chans[i]->inUse()) + return i+1; + return -1; +} + +ZapChan *PriSpan::getChan(int chan) const +{ + return validChan(chan) ? m_chans[chan-1] : 0; +} + +void PriSpan::restartChan(int chan, bool force) +{ + if (chan < 0) { + Debug(DebugInfo,"Restart request on entire span %d",m_span); + return; + } + if (!validChan(chan)) { + Debug(DebugInfo,"Restart request on invalid channel %d on span %d",chan,m_span); + return; + } + if (force || !getChan(chan)->inUse()) { + Debug(DebugInfo,"Restarting B-channel %d on span %d",chan,m_span); + getChan(chan)->restart(); + } +} + +void PriSpan::ringChan(int chan, pri_event_ring &ev) +{ + if (chan == -1) + chan = findEmptyChan(); + if (!validChan(chan)) { + Debug(DebugInfo,"Ring on invalid channel %d on span %d",chan,m_span); + return; + } + Debug(DebugInfo,"Ring on channel %d on span %d",chan,m_span); + getChan(chan)->ring(ev.call); + Debug(DebugInfo,"caller='%s' callerno='%s' callingplan=%d", + ev.callingname,ev.callingnum,ev.callingplan); + Debug(DebugInfo,"callednum='%s' redirectnum='%s' calledplan=%d", + ev.callednum,ev.redirectingnum,ev.calledplan); + Debug(DebugInfo,"type=%d complete=%d format='%s'", + ev.ctype,ev.complete,lookup(ev.layer1,dict_str2law,"unknown")); + Message *m = new Message("ring"); + m->addParam("driver","zap"); + m->addParam("span",String(m_span)); + m->addParam("channel",String(chan)); + if (ev.callingnum[0]) + m->addParam("caller",ev.callingnum); + if (ev.callednum[0]) + m->addParam("called",ev.callednum); + Engine::dispatch(m); + *m = "preroute"; + Engine::dispatch(m); + *m = "route"; + if (Engine::dispatch(m)) { + *m = "call"; + m->addParam("callto",m->retValue()); + m->retValue() = 0; + int dataLaw = -1; + switch (ev.layer1) { + case PRI_LAYER_1_ALAW: + dataLaw = ZT_LAW_ALAW; + break; + case PRI_LAYER_1_ULAW: + dataLaw = ZT_LAW_MULAW; + break; + } + getChan(chan)->open(dataLaw); + m->userData(getChan(chan)); + if (Engine::dispatch(m)) + getChan(chan)->answer(); + else + getChan(chan)->hangup(PRI_CAUSE_REQUESTED_CHAN_UNAVAIL); + } + else + getChan(chan)->hangup(PRI_CAUSE_NO_ROUTE_DESTINATION); + delete m; +} + +void PriSpan::infoChan(int chan, pri_event_ring &ev) +{ + if (!validChan(chan)) { + Debug(DebugInfo,"Info on invalid channel %d on span %d",chan,m_span); + return; + } + Debug(DebugInfo,"info on channel %d on span %d",chan,m_span); + Debug(DebugInfo,"caller='%s' callerno='%s' callingplan=%d", + ev.callingname,ev.callingnum,ev.callingplan); + Debug(DebugInfo,"callednum='%s' redirectnum='%s' calledplan=%d", + ev.callednum,ev.redirectingnum,ev.calledplan); +} + +void PriSpan::hangupChan(int chan,pri_event_hangup &ev) +{ + if (!validChan(chan)) { + Debug(DebugInfo,"Hangup on invalid channel %d on span %d",chan,m_span); + return; + } + Debug(DebugInfo,"Hanging up channel %d on span %d",chan,m_span); + getChan(chan)->hangup(); +} + +void PriSpan::ackChan(int chan) +{ + if (!validChan(chan)) { + Debug(DebugInfo,"ACK on invalid channel %d on span %d",chan,m_span); + return; + } + Debug(DebugInfo,"ACKnowledging channel %d on span %d",chan,m_span); + getChan(chan)->setTimeout(0); +} + +void PriSpan::proceedingChan(int chan) +{ + if (!validChan(chan)) { + Debug(DebugInfo,"Proceeding on invalid channel %d on span %d",chan,m_span); + return; + } + Debug(DebugInfo,"Extending timeout on channel %d on span %d",chan,m_span); + getChan(chan)->setTimeout(60000000); +} + +void ZapSource::run() +{ + DataBlock buf(0,m_bufsize); + DataBlock data; + for (;;) { + Thread::yield(); + int fd = m_owner->fd(); + if (fd != -1) { + int rd = ::read(fd,buf.data(),buf.length()); +#ifdef DEBUG + Debug(DebugAll,"ZapSource read %d bytes",rd); +#endif + if (rd > 0) { + switch (m_owner->law()) { + case -1: + data.assign(buf.data(),rd); + Forward(data); + break; + case ZT_LAW_MULAW: + data.convert(buf,"mulaw","slin",rd); + Forward(data); + break; + case ZT_LAW_ALAW: + data.convert(buf,"alaw","slin",rd); + Forward(data); + break; + } + } + else if (rd < 0) { + if ((errno != EAGAIN) && (errno != EINTR)) + break; + } + else + break; + } + } +} + +void ZapConsumer::Consume(const DataBlock &data) +{ + int fd = m_owner->fd(); +#ifdef DEBUG + Debug(DebugAll,"ZapConsumer fd=%d datalen=%u",fd,data.length()); +#endif + if ((fd != -1) && !data.null()) { + DataBlock blk; + switch (m_owner->law()) { + case -1: + blk = data; + break; + case ZT_LAW_MULAW: + blk.convert(data,"slin","mulaw"); + break; + case ZT_LAW_ALAW: + blk.convert(data,"slin","alaw"); + break; + default: + return; + } + if (m_buffer.length()+blk.length() <= m_bufsize*4) + m_buffer += blk; + else + Debug("ZapConsumer",DebugAll,"Skipped %u bytes, buffer is full",blk.length()); + if (m_buffer.null()) + return; + if (m_buffer.length() >= m_bufsize) { + int wr = ::write(fd,m_buffer.data(),m_bufsize); + if (wr < 0) { + if ((errno != EAGAIN) && (errno != EINTR)) + Debug(DebugFail,"ZapConsumer write error %d: %s", + errno,::strerror(errno)); + } + else { + if ((unsigned)wr != m_bufsize) + Debug("ZapConsumer",DebugInfo,"Short write, %d of %u bytes",wr,m_bufsize); + m_buffer.cut(-wr); + } + } + } +} + +ZapChan::ZapChan(PriSpan *parent, int chan, unsigned int bufsize) + : DataEndpoint("zaptel"), m_span(parent), m_chan(chan), m_ring(false), + m_timeout(0), m_call(0), m_bufsize(bufsize), m_fd(-1), m_law(-1) +{ + Debug(DebugAll,"ZapChan::ZapChan(%p,%d) [%p]",parent,chan,this); + // I hate counting from one... + m_abschan = m_chan+m_span->chan1()-1; +} + +ZapChan::~ZapChan() +{ + Debug(DebugAll,"ZapChan::~ZapChan() [%p] %d",this,m_chan); + hangup(); +} + +void ZapChan::disconnected() +{ + Debugger debug("ZapChan::disconnected()"); + hangup(); +} + +bool ZapChan::nativeConnect(DataEndpoint *peer) +{ +#if 0 + ZapChan *zap = static_cast(peer); + if ((m_fd < 0) || !zap || (zap->fd() < 0)) + return false; + ZT_CONFINFO conf; + conf.confmode = ZT_CONF_DIGITALMON; + conf.confno = zap->absChan(); + if (ioctl(m_fd, ZT_SETCONF, &conf)) + return false; + conf.confno = zap->absChan(); + if (ioctl(m_fd, ZT_SETCONF, &conf)) + return false; +#endif + return false; +} + +const char *ZapChan::status() const +{ + if (m_ring) + return "ringing"; + if (m_call) + return m_timeout ? "calling" : "connected"; + return "idle"; +} + +void ZapChan::idle() +{ + if (m_timeout && (Time::now() > m_timeout)) { + Debug("ZapChan",DebugWarn,"Timeout %s channel %d on span %d", + status(),m_chan,m_span->span()); + m_timeout = 0; + hangup(); + } +} + +void ZapChan::restart() +{ + disconnect(); + setSource(); + setConsumer(); + ::pri_reset(m_span->pri(),m_chan); +} + +bool ZapChan::open(int defLaw) +{ + setSource(new ZapSource(this,m_bufsize)); + getSource()->deref(); + setConsumer(new ZapConsumer(this,m_bufsize)); + getConsumer()->deref(); + m_fd = zt_open(m_abschan,false,m_bufsize); + if (m_fd == -1) + return false; + if (zt_set_law(m_fd,defLaw)) { + m_law = defLaw; + Debug(DebugInfo,"Opened Zap channel %d, law is: %s (desired)",m_abschan,lookup(m_law,dict_str2ztlaw,"unknown")); + return true; + } + int laws[3]; + laws[0] = -1; + if (m_span->chans() > 24) { + laws[1] = ZT_LAW_ALAW; + laws[2] = ZT_LAW_MULAW; + } + else { + laws[1] = ZT_LAW_MULAW; + laws[2] = ZT_LAW_ALAW; + } + for (int l=0; l<3;l++) + if ((laws[l] != defLaw) && zt_set_law(m_fd,laws[l])) { + m_law = laws[l]; + Debug(DebugInfo,"Opened Zap channel %d, law is: %s (fallback)",m_abschan,lookup(m_law,dict_str2ztlaw,"unknown")); + return true; + } + zt_close(m_fd); + m_fd = -1; + Debug(DebugFail,"Unable to set zap to any known format"); + return false; +} + +bool ZapChan::answer() +{ + if (!m_ring) { + Debug("ZapChan",DebugWarn,"Answer request on %s channel %d on span %d", + status(),m_chan,m_span->span()); + return false; + } + m_ring = false; + m_timeout = 0; + Output("Answering on zap/%d (%d/%d)",m_abschan,m_span->span(),m_chan); + ::pri_answer(m_span->pri(),m_call,m_chan,0); + Message *m = new Message("answer"); + m->addParam("driver","zap"); + m->addParam("span",String(m_span->span())); + m->addParam("channel",String(m_chan)); + Engine::enqueue(m); + return true; +} + +void ZapChan::hangup(int cause) +{ + m_ring = false; + m_timeout = 0; + if (m_call) { + ::pri_hangup(m_span->pri(),m_call,cause); + ::pri_destroycall(m_span->pri(),m_call); + m_call = 0; + Message *m = new Message("hangup"); + m->addParam("driver","zap"); + m->addParam("span",String(m_span->span())); + m->addParam("channel",String(m_chan)); + Engine::enqueue(m); + } + disconnect(); + setSource(); + setConsumer(); + zt_close(m_fd); + m_fd = -1; +} + +void ZapChan::sendDigit(char digit) +{ + if (m_call) + ::pri_information(m_span->pri(),m_call,digit); +} + +void ZapChan::call(Message &msg) +{ + char *called = (char *)msg.getValue("called"); + Debug("ZapChan",DebugInfo,"Calling '%s' on channel %d span %d", + called, m_chan,m_span->span()); + int layer1 = lookup(msg.getValue("dataformat"),dict_str2law,0); + hangup(); + DataEndpoint *dd = static_cast(msg.userData()); + if (dd) { + int dataLaw = -1; + switch (layer1) { + case PRI_LAYER_1_ALAW: + dataLaw = ZT_LAW_ALAW; + break; + case PRI_LAYER_1_ULAW: + dataLaw = ZT_LAW_MULAW; + break; + } + open(dataLaw); + connect(dd); + } + else + msg.userData(this); + Output("Calling '%s' on zap/%d (%d/%d)",called,m_abschan,m_span->span(),m_chan); + m_call =::pri_new_call(span()->pri()); + ::pri_call(m_span->pri(),m_call,0,m_chan,1,0, + (char *)msg.getValue("caller"), + lookup(msg.getValue("callerplan"),dict_str2dplan,m_span->dplan()), + (char *)msg.getValue("callername"), + lookup(msg.getValue("callerpres"),dict_str2pres,m_span->pres()), + called, + lookup(msg.getValue("calledplan"),dict_str2dplan,m_span->dplan()), + layer1); + setTimeout(10000000); +} + +void ZapChan::ring(q931_call *call) +{ + m_call = call; + if (call) { + setTimeout(10000000); + m_ring = true; + ::pri_acknowledge(m_span->pri(),m_call,m_chan,0); + } + else + hangup(); +} + +bool ZapHandler::received(Message &msg) +{ + String dest(msg.getValue("callto")); + if (dest.null()) + return false; + Regexp r("^zap/\\([^/]*\\)/\\?\\(.*\\)$"); + if (!dest.matches(r)) + return false; + if (!msg.userData()) { + Debug(DebugFail,"Zaptel call found but no data channel!"); + return false; + } + String chan = dest.matchString(1); +#ifdef DEBUG + Debug(DebugInfo,"Found call to Zaptel chan='%s' name='%s'", + chan.c_str(),dest.matchString(2).c_str()); +#endif + ZapChan *c = 0; + + r = "^\\([0-9]\\+\\)-\\([0-9]*\\)$"; + if (chan.matches(r)) + c = zplugin.findChan(chan.matchString(1).toInteger(), + chan.matchString(2).toInteger(65535)); + else + c = zplugin.findChan(chan.toInteger(-1)); + + if (c) { + Debug(DebugInfo,"Will call chan zap/%d (%d/%d)", + c->absChan(),c->span()->span(),c->chan()); + c->call(msg); + return true; + } + else + Debug(DebugWarn,"Invalid Zaptel channel '%s'",chan.c_str()); + return false; +} + +bool ZapDropper::received(Message &msg) +{ + String id(msg.getValue("id")); + if (id.null()) { + Debug("ZapDropper",DebugInfo,"Dropping all calls"); + const ObjList *l = &zplugin.m_spans; + for (; l; l=l->next()) { + PriSpan *s = static_cast(l->get()); + if (s) { + for (int n=1; n<=s->chans(); n++) { + ZapChan *c = s->getChan(n); + if (c) { + c->hangup(); + } + } + } + } + return false; + } + if (!id.startsWith("zap/")) + return false; + ZapChan *c = 0; + id >> "zap/"; + int n = id.toInteger(); + if ((n > 0) && (c = zplugin.findChan(n))) { + Debug("ZapDropper",DebugInfo,"Dropping zap/%d (%d/%d)", + n,c->span()->span(),c->chan()); + c->hangup(); + return true; + } + Debug("ZapDropper",DebugInfo,"Could not find zap/%s",id.c_str()); + return false; +} + +bool StatusHandler::received(Message &msg) +{ + const char *sel = msg.getValue("module"); + if (sel && ::strcmp(sel,"zapchan") && ::strcmp(sel,"fixchans")) + return false; + String st("zapchan,type=fixchans,spans="); + st << zplugin.m_spans.count() << ",[LIST]"; + const ObjList *l = &zplugin.m_spans; + for (; l; l=l->next()) { + PriSpan *s = static_cast(l->get()); + if (s) { + for (int n=1; n<=s->chans(); n++) { + ZapChan *c = s->getChan(n); + if (c) { + st << ",zap/" << c->absChan() << "="; + st << s->span() << "/" << n << "/" << c->status(); + } + } + } + } + msg.retValue() << st << "\n"; + return false; +} + +ZaptelPlugin::ZaptelPlugin() +{ + Output("Loaded module Zaptel"); + ::pri_set_error(pri_err_cb); + ::pri_set_message(pri_msg_cb); +} + +ZaptelPlugin::~ZaptelPlugin() +{ + Output("Unloading module Zaptel"); +} + +PriSpan *ZaptelPlugin::findSpan(int chan) +{ + const ObjList *l = &m_spans; + for (; l; l=l->next()) { + PriSpan *s = static_cast(l->get()); + if (s && s->belongs(chan)) + return s; + } + return 0; +} + +ZapChan *ZaptelPlugin::findChan(int first, int last) +{ +#ifdef DEBUG + Debug(DebugAll,"ZaptelPlugin::findChan(%d,%d)",first,last); +#endif + // see first if we have an exact request + if (first > 0 && last < 0) { + PriSpan *s = findSpan(first); + return s ? s->getChan(first - s->chan1() + 1) : 0; + } + if (last < 0) + last = 65535; + const ObjList *l = &m_spans; + for (; l; l=l->next()) { + PriSpan *s = static_cast(l->get()); + if (s) { + Debug(DebugAll,"Searching for free chan in span %d [%p]", + s->span(),s); + int c = s->findEmptyChan(first,last); + if (c > 0) + return s->getChan(c); + if (s->belongs(last)) + break; + } + } + return 0; +} + +void ZaptelPlugin::initialize() +{ + Output("Initializing module Zaptel"); + Configuration cfg(Engine::configFile("zapchan")); + PriSpan::restartPeriod = cfg.getIntValue("general","restart") * 1000000ULL; + PriSpan::dumpEvents = cfg.getBoolValue("general","dumpevents"); + if (!m_spans.count()) { + s_buflen = cfg.getIntValue("general","buflen",480); + int chan1 = 1; + for (int span = 1;;span++) { + String sect("span "); + sect += String(span); + int num = cfg.getIntValue(sect,"chans",-1); + if (num < 0) + break; + if (num) { + chan1 = cfg.getIntValue(sect,"first",chan1); + PriSpan::create(span,chan1,num, + cfg.getIntValue(sect,"dchan", num > 24 ? 16 : -1), + cfg.getBoolValue(sect,"isnet",true), + cfg.getIntValue(sect,"swtype",dict_str2switch, + PRI_SWITCH_UNKNOWN), + cfg.getIntValue(sect,"dialplan",dict_str2dplan, + PRI_UNKNOWN), + cfg.getIntValue(sect,"presentation",dict_str2pres, + PRES_ALLOWED_USER_NUMBER_NOT_SCREENED) + ); + chan1 += num; + } + } + if (m_spans.count()) { + Output("Created %d spans",m_spans.count()); + Engine::install(new ZapHandler("call")); + Engine::install(new ZapDropper("drop")); + Engine::install(new StatusHandler); + } + else + Output("No spans created, module not activated"); + } +} + +/* vi: set ts=8 sw=4 sts=4 noet: */ diff --git a/run.in b/run.in new file mode 100644 index 00000000..b4cfd5ad --- /dev/null +++ b/run.in @@ -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 "$@" diff --git a/scripts/.cvsignore b/scripts/.cvsignore new file mode 100644 index 00000000..3359fbab --- /dev/null +++ b/scripts/.cvsignore @@ -0,0 +1,4 @@ +core* +*.orig +*~ +.*.swp diff --git a/tables/.cvsignore b/tables/.cvsignore new file mode 100644 index 00000000..60396cc7 --- /dev/null +++ b/tables/.cvsignore @@ -0,0 +1,8 @@ +core* +*.h +*.raw +?2? +gen +*.orig +*~ +.*.swp diff --git a/tables/Makefile.tables b/tables/Makefile.tables new file mode 100644 index 00000000..4c4daa00 --- /dev/null +++ b/tables/Makefile.tables @@ -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 diff --git a/tables/gen.awk b/tables/gen.awk new file mode 100644 index 00000000..f6465ef7 --- /dev/null +++ b/tables/gen.awk @@ -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; +} diff --git a/tables/gen.c b/tables/gen.c new file mode 100644 index 00000000..263fbc39 --- /dev/null +++ b/tables/gen.c @@ -0,0 +1,31 @@ +#include + +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; +} diff --git a/tables/gen.sh b/tables/gen.sh new file mode 100755 index 00000000..b95e6a54 --- /dev/null +++ b/tables/gen.sh @@ -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 diff --git a/tarballs/.cvsignore b/tarballs/.cvsignore new file mode 100644 index 00000000..8109dd3d --- /dev/null +++ b/tarballs/.cvsignore @@ -0,0 +1,5 @@ +core* +*.tar.gz +*.orig +*~ +.*.swp diff --git a/test/.cvsignore b/test/.cvsignore new file mode 100644 index 00000000..20aaabb7 --- /dev/null +++ b/test/.cvsignore @@ -0,0 +1,9 @@ +Makefile +core* +*.o +*.a +*.so +*.yate +*.orig +*~ +.*.swp diff --git a/test/Makefile.in b/test/Makefile.in new file mode 100644 index 00000000..eb5a6b2f --- /dev/null +++ b/test/Makefile.in @@ -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) diff --git a/test/randcall.cpp b/test/randcall.cpp new file mode 100644 index 00000000..b94a83c5 --- /dev/null +++ b/test/randcall.cpp @@ -0,0 +1,95 @@ +/* + test.c + This file holds the entry point of the Telephony Engine +*/ + +#include + +#include +#include + +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: */ diff --git a/test/test.cpp b/test/test.cpp new file mode 100644 index 00000000..c1babc6a --- /dev/null +++ b/test/test.cpp @@ -0,0 +1,46 @@ +/* + test.c + This file holds the entry point of the Telephony Engine +*/ + +#include + +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 + */ diff --git a/test/test1.cpp b/test/test1.cpp new file mode 100644 index 00000000..a1524646 --- /dev/null +++ b/test/test1.cpp @@ -0,0 +1,95 @@ +/* + test.c + This file holds the entry point of the Telephony Engine +*/ + +#include + +#include + +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&2 + echo "$ustr" >&2 + exit 1 + ;; + esac + shift +done diff --git a/yate.8 b/yate.8 new file mode 100644 index 00000000..a6ea86f1 --- /dev/null +++ b/yate.8 @@ -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 +.SH SEE ALSO +.\" .BR libyate (3), +.BR bayonne (8) diff --git a/yate.pc.in b/yate.pc.in new file mode 100644 index 00000000..625cc565 --- /dev/null +++ b/yate.pc.in @@ -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@ diff --git a/yate.spec.in b/yate.spec.in new file mode 100644 index 00000000..694f4986 --- /dev/null +++ b/yate.spec.in @@ -0,0 +1,82 @@ +Summary: Yet Another Telephony Engine +Name: yate +Version: @PACKAGE_VERSION@ +Release: 1 +Copyright: GPL +Packager: Paul Chitescu +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 +- 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 +- Added SIP channel and registration module + +* Sun Apr 04 2004 Paul Chitescu +- Added yate-config to the devel package + +* Mon Mar 29 2004 Paul Chitescu +- Created specfile diff --git a/yatengine.h b/yatengine.h new file mode 100644 index 00000000..52b08edf --- /dev/null +++ b/yatengine.h @@ -0,0 +1,1935 @@ +/** + * telengine.h + * This file is part of the YATE Project http://YATE.null.ro + */ +#ifndef __TELENGINE_H +#define __TELENGINE_H + +#ifndef __cplusplus +#error C++ is required +#endif + +struct timeval; + +/** + * Holds all Telephony Engine related classes. + */ +namespace TelEngine { + +/** + * Standard debugging levels. + */ +enum DebugLevel { + DebugFail = 0, + DebugGoOn = 2, + DebugWarn = 5, + DebugMild = 7, + DebugInfo = 9, + DebugAll = 10 +}; + +/** + * Retrive the current debug level + * @return The current debug level + */ +int debugLevel(); + +/** + * Set the current debug level. + * @param level The desired debug level + * @return The new debug level (may be different) + */ +int debugLevel(int level); + +/** + * Check if debugging output should be generated + * @param level The desired debug level + * @return True if messages should be output, false otherwise + */ +bool debugAt(int level); + +/** + * Outputs a debug string. + * @param level The level of the message + * @param format A printf() style format string + * @return True if message was output, false otherwise + */ +bool Debug(int level, const char *format, ...); + +/** + * Outputs a debug string for a specific facility. + * @param facility Facility that outputs the message + * @param level The level of the message + * @param format A printf() style format string + * @return True if message was output, false otherwise + */ +bool Debug(const char *facility, int level, const char *format, ...); + +/** + * Outputs a string to the debug console with formatting + * @param facility Facility that outputs the message + * @param format A printf() style format string + */ +void Output(const char *format, ...); + +/** + * An object that logs messages on creation and destruction + */ +class Debugger +{ +public: + /** + * The constructor prints the method entry message and indents. + * @param name Name of the function or block entered, must be static + * @param format printf() style format string + */ + Debugger(const char *name, const char *format = 0, ...); + + /** + * The constructor prints the method entry message and indents. + * @param level The level of the message + * @param name Name of the function or block entered, must be static + * @param format printf() style format string + */ + Debugger(int level, const char *name, const char *format = 0, ...); + + /** + * The destructor prints the method leave message and deindents. + */ + ~Debugger(); + + /** + * Set the output callback + * @param outFunc Pointer to the output function, NULL to use stderr + */ + static void setOutput(void (*outFunc)(const char *) = 0); + + /** + * Set the interactive output callback + * @param outFunc Pointer to the output function, NULL to disable + */ + static void setIntOut(void (*outFunc)(const char *) = 0); + + /** + * Enable or disable the debug output + */ + static void enableOutput(bool enable = true); + +private: + const char *m_name; +}; + +/** + * A structure to build (mainly static) Token-to-ID translation tables. + * A table of such structures must end with an entry with a null token + */ +typedef struct { + const char *token; + int value; +} TokenDict; + +/** + * An object with just a public virtual destructor + */ +class GenObject +{ +public: + /** + * Destructor. + */ + virtual ~GenObject() { } + + /** + * Destroys the object, disposes the memory. + */ + virtual void destruct() + { delete this; } +}; + +/** + * A reference counted object. + * Whenever using multiple inheritance you should inherit this class virtually. + */ +class RefObject : public GenObject +{ +public: + /** + * The constructor initializes the reference counter to 1! + * Use deref() to destruct the object when safe + */ + RefObject() + : m_refcount(1) { } + + /** + * Destructor. + */ + virtual ~RefObject(); + + /** + * Increments the reference counter + * @return The new reference count + */ + inline int ref() + { return ++m_refcount; } + + /** + * Decrements the reference counter, destroys the object if it reaches zero + *
+     * // Deref this object, return quickly if the object was deleted
+     * if (deref()) return;
+     * 
+ * @return True if the object was deleted, false if it still exists + */ + inline bool deref() + { int i = --m_refcount; if (i == 0) delete this; return (i <= 0); } + + /** + * Get the current value of the reference counter + * @return The value of the reference counter + */ + inline int refcount() const + { return m_refcount; } + + /** + * Refcounted objects should just have the counter decremented. + * That will destroy them only when the refcount reaches zero. + */ + virtual void destruct() + { deref(); } + +private: + int m_refcount; +}; + +/** + * A simple single-linked object list handling class + * @short An object list class + */ +class ObjList : public GenObject +{ +public: + /** + * Creates a new, empty list. + */ + ObjList(); + + /** + * Destroys the list and everything in it. + */ + virtual ~ObjList(); + + /** + * Get the number of elements in the list + * @return Count of items + */ + unsigned int length() const; + + /** + * Get the number of non-null objects in the list + * @return Count of items + */ + unsigned int count() const; + + /** + * Get the object associated to this list item + * @return Pointer to the object or NULL + */ + inline GenObject *get() const + { return m_obj; } + + /** + * Set the object associated to this list item + * @param obj Pointer to the new object to set + * @param delold True to delete the old object (default) + * @return Pointer to the old object if not destroyed + */ + GenObject *set(const GenObject *obj, bool delold = true); + + /** + * Get the next item in the list + * @return Pointer to the next item in list or NULL + */ + inline ObjList *next() const + { return m_next; } + + /** + * Get the last item in the list + * @return Pointer to the last item in list + */ + ObjList *last() const; + + /** + * Indexing operator + * @param index Index of the item to retrive + * @return Pointer to the item or NULL + */ + ObjList *operator[](int index) const; + + /** + * Get the item in the list that holds an object + * @param obj Pointer to the object to search for + * @return Pointer to the found item or NULL + */ + ObjList *find(const GenObject *obj) const; + + /** + * Insert an object at this point + * @param obj Pointer to the object to insert + * @return A pointer to the inserted list item + */ + ObjList *insert(const GenObject *obj); + + /** + * Append an object to the end of the list + * @param obj Pointer to the object to append + * @return A pointer to the inserted list item + */ + ObjList *append(const GenObject *obj); + + /** + * Delete this list item + * @param delold True to delete the object (default) + * @return Pointer to the object if not destroyed + */ + GenObject *remove(bool delobj = true); + + /** + * Delete the list item that holds a given object + * @param obj Object to search in the list + * @param delobj True to delete the object (default) + * @return Pointer to the object if not destroyed + */ + GenObject *remove(GenObject *obj, bool delobj = true); + + /** + * Clear the list and optionally delete all contained objects + */ + void clear(); + + /** + * Get the automatic delete flag + * @return True if will delete on destruct, false otherwise + */ + inline bool autoDelete() + { return m_delete; } + + /** + * Set the automatic delete flag + * @param autodelete True to delete on destruct, false otherwise + */ + inline void setDelete(bool autodelete) + { m_delete = autodelete; } + +private: + ObjList *m_next; + GenObject *m_obj; + bool m_delete; +}; + +class Regexp; +class StringMatchPrivate; + +/** + * A simple string handling class for C style (one byte) strings. + * For simplicity and read speed no copy-on-write is performed. + * Strings have hash capabilities and comparations are using the hash + * for fast inequality check. + * @short A C-style string handling class + */ +class String : public GenObject +{ +public: + /** + * Creates a new, empty string. + */ + String(); + + /** + * Creates a new initialized string. + * @param value Initial value of the string + * @param len Length of the data to copy, -1 for full string + */ + String(const char *value, int len = -1); + + /** + * Creates a new initialized string. + * @param value Character to fill the string + * @param repeat How many copies of the character to use + */ + String(char value, unsigned int repeat = 1); + + /** + * Creates a new initialized string from an integer. + * @param value Value to convert to string + */ + String(int value); + + /** + * Creates a new initialized string from an unsigned int. + * @param value Value to convert to string + */ + String(unsigned int value); + + /** + * Creates a new initialized string from a boolean. + * @param value Value to convert to string + */ + String(bool value); + + /** + * Copy constructor. + * @param value Initial value of the string + */ + String(const String &value); + + /** + * Destroys the string, disposes the memory. + */ + virtual ~String(); + + /** + * Get the value of the stored string. + * @return The stored C string which may be NULL. + */ + inline const char *c_str() const + { return m_string; } + + /** + * Get a valid non-NULL C string. + * @return The stored C string or "". + */ + inline const char *safe() const + { return m_string ? m_string : ""; } + + /** + * Get the length of the stored string. + * @return The length of the stored string, zero for NULL. + */ + inline unsigned int length() const + { return m_length; } + + /** + * Checks if the string holds a NULL pointer. + * @return True if the string holds NULL, false otherwise. + */ + inline bool null() const + { return !m_string; } + + /** + * Get the hash of the contained string. + * @return The hash of the string. + */ + unsigned int hash() const; + + /** + * Get the hash of an arbitrary string. + * @return The hash of the string. + */ + static unsigned int hash(const char *value); + + /** + * Clear the string and free the memory + */ + void clear(); + + /** + * Extract the caracter at a given index + * @param index Index of character in string + * @return Character at given index or 0 if out of range + */ + char at(int index) const; + + /** + * Substring extraction + * @param offs Offset of the substring, negative to count from end + * @param len Length of the substring, -1 for everything possible + * @return A copy of the requested substring + */ + String substr(int offs, int len = -1) const; + + /** + * Strip off leading and trailing blank characters + */ + String& trimBlanks(); + + /** + * Convert the string to an integer value. + * @param defvalue Default to return if the string is not a number + * @param base Numeration base, 0 to autodetect + * @return The integer interpretation or defvalue. + */ + int toInteger(int defvalue = 0, int base = 0) const; + + /** + * Convert the string to an integer value looking up first a token table. + * @param tokens Pointer to an array of tokens to lookup first + * @param defvalue Default to return if the string is not a token or number + * @param base Numeration base, 0 to autodetect + * @return The integer interpretation or defvalue. + */ + int toInteger(const TokenDict *tokens, int defvalue = 0, int base = 0) const; + + /** + * Convert the string to a boolean value. + * @param defvalue Default to return if the string is not a bool + * @return The boolean interpretation or defvalue. + */ + bool toBoolean(bool defvalue = false) const; + + /** + * Indexing operator + * @param index Index of character in string + * @return Character at given index or 0 if out of range + */ + inline char operator[](int index) const + { return at(index); } + + /** + * Conversion to "const char *" operator. + */ + inline operator const char*() const + { return m_string; }; + + /** + * Assigns a new value to the string from a character block. + * @param value New value of the string + * @param len Length of the data to copy, -1 for full string + */ + String& assign(const char *value, int len = -1); + + /** + * Assignment operator. + */ + inline String& operator=(const String &value) + { return operator=(value.c_str()); } + + /** + * Assignment from char* operator. + * @see TelEngine::strcpy + */ + String& operator=(const char *value); + + /** + * Assignment operator for single characters. + */ + String& operator=(char value); + + /** + * Assignment operator for integers. + */ + String& operator=(int value); + + /** + * Assignment operator for unsigned integers. + */ + String& operator=(unsigned int value); + + /** + * Assignment operator for booleans. + */ + inline String& operator=(bool value) + { return operator=(value ? "true" : "false"); } + + /** + * Appending operator for strings. + * @see TelEngine::strcat + */ + String& operator+=(const char *value); + + /** + * Appending operator for single characters. + */ + String& operator+=(char value); + + /** + * Appending operator for integers. + */ + String& operator+=(int value); + + /** + * Appending operator for unsigned integers. + */ + String& operator+=(unsigned int value); + + /** + * Appending operator for booleans. + */ + inline String& operator+=(bool value) + { return operator+=(value ? "true" : "false"); } + + /** + * Equality operator. + */ + bool operator==(const char *value) const; + + /** + * Inequality operator. + */ + bool operator!=(const char *value) const; + + /** + * Fast equality operator. + */ + bool operator==(const String &value) const; + + /** + * Fast inequality operator. + */ + bool operator!=(const String &value) const; + + /** + * Stream style appending operator for C strings + */ + inline String& operator<<(const char *value) + { return operator+=(value); } + + /** + * Stream style appending operator for single characters + */ + inline String& operator<<(char value) + { return operator+=(value); } + + /** + * Stream style appending operator for integers + */ + inline String& operator<<(int value) + { return operator+=(value); } + + /** + * Stream style appending operator for unsigned integers + */ + inline String& operator<<(unsigned int value) + { return operator+=(value); } + + /** + * Stream style appending operator for booleans + */ + inline String& operator<<(bool value) + { return operator+=(value); } + + /** + * Stream style substring skipping operator. + * It eats all characters up to and including the skip string + */ + String& operator>>(const char *skip); + + /** + * Stream style extraction operator for single characters + */ + String& operator>>(char &store); + + /** + * Stream style extraction operator for integers + */ + String& operator>>(int &store); + + /** + * Stream style extraction operator for unsigned integers + */ + String& operator>>(unsigned int &store); + + /** + * Stream style extraction operator for booleans + */ + String& operator>>(bool &store); + + /** + * Locate the first instance of a character in the string + * @param what Character to search for + * @param offs Offset in string to start searching from + * @return Offset of character or -1 if not found + */ + int find(char what, unsigned int offs = 0) const; + + /** + * Locate the first instance of a substring in the string + * @param what Substring to search for + * @param offs Offset in string to start searching from + * @return Offset of substring or -1 if not found + */ + int find(const char *what, unsigned int offs = 0) const; + + /** + * Locate the last instance of a character in the string + * @param what Character to search for + * @return Offset of character or -1 if not found + */ + int rfind(char what) const; + + /** + * Checks if the string starts with a substring + * @param what Substring to search for + * @param wordBreak Check if a word boundary follows the substring + * @return True if the substring occurs at the beginning of the string + */ + bool startsWith(const char *what, bool wordBreak = false) const; + + /** + * Checks if the string ends with a substring + * @param what Substring to search for + * @param wordBreak Check if a word boundary precedes the substring + * @return True if the substring occurs at the end of the string + */ + bool endsWith(const char *what, bool wordBreak = false) const; + + /** + * Checks if matches another string + * @param value String to check for match + * @return True if matches, false otherwise + */ + virtual bool matches(const String &value) + { return operator==(value); } + + /** + * Checks if matches a regular expression + * @param rexp Regular expression to check for match + * @return True if matches, false otherwise + */ + bool matches(Regexp &rexp); + + /** + * Get the offset of the last match + * @param index Index of the submatch to return, 0 for full match + * @return Offset of the last match, -1 if no match or not in range + */ + int matchOffset(int index = 0) const; + + /** + * Get the length of the last match + * @param index Index of the submatch to return, 0 for full match + * @return Length of the last match, 0 if no match or out of range + */ + int matchLength(int index = 0) const; + + /** + * Get a copy of a matched (sub)string + * @param index Index of the submatch to return, 0 for full match + * @return Copy of the matched substring + */ + inline String matchString(int index = 0) const + { return substr(matchOffset(index),matchLength(index)); } + + /** + * Create a string by replacing matched strings in a template + * @param templ Template of the string to generate + * @return Copy of template with "\0" - "\9" replaced with submatches + */ + String replaceMatches(const String &templ) const; + + /** + * Get the total number of submatches from the last match, 0 if no match + * @return Number of matching subexpressions + */ + int matchCount() const; + + /** + * Create an escaped string suitable for use in messages + * @param str String to convert to escaped format + * @param extraEsc Character to escape other than the default ones + * @return The string with special characters escaped + */ + static String msgEscape(const char *str, char extraEsc = 0); + + /** + * Create an escaped string suitable for use in messages + * @param extraEsc Character to escape other than the default ones + * @return The string with special characters escaped + */ + inline String msgEscape(char extraEsc = 0) const + { return msgEscape(c_str(),extraEsc); } + + /** + * Decode an escaped string back to its raw form + * @param str String to convert to unescaped format + * @param errptr Pointer to an integer to receive the place of 1st error + * @param extraEsc Character to unescape other than the default ones + * @return The string with special characters unescaped + */ + static String msgUnescape(const char *str, int *errptr = 0, char extraEsc = 0); + + /** + * Decode an escaped string back to its raw form + * @param errptr Pointer to an integer to receive the place of 1st error + * @param extraEsc Character to unescape other than the default ones + * @return The string with special characters unescaped + */ + inline String msgUnescape(int *errptr = 0, char extraEsc = 0) const + { return msgUnescape(c_str(),errptr,extraEsc); } + +protected: + /** + * Called whenever the value changed (except in constructors). + */ + virtual void changed(); + +private: + void clearMatches(); + char *m_string; + unsigned int m_length; + // i hope every C++ compiler now knows about mutable... + mutable unsigned int m_hash; + StringMatchPrivate *m_matches; +}; + +/** + * Utility function to replace NULL string pointers with an empty string + * @param str Pointer to a C string that may be NULL + * @return Original pointer or pointer to an empty string + */ +inline const char *c_safe(const char *str) + { return str ? str : ""; } + +/** + * Concatenation operator for strings. + */ +String operator+(const String &s1, const String &s2); + +/** + * Concatenation operator for strings. + */ +String operator+(const String &s1, const char *s2); + +/** + * Concatenation operator for strings. + */ +String operator+(const char *s1, const String &s2); + +/** + * Prevent careless programmers from overwriting the string + * @see TelEngine::String::operator= + */ +inline char *strcpy(String dest, const char *src) + { dest = src; return (char *)dest.c_str(); } + +/** + * Prevent careless programmers from overwriting the string + * @see TelEngine::String::operator+= + */ +inline char *strcat(String dest, const char *src) + { dest += src; return (char *)dest.c_str(); } + +/** + * Utility function to look up a string in a token table, + * interpret as number if it fails + * @param str String to look up + * @param tokens Pointer to the token table + * @param defvalue Value to return if lookup and conversion fail + * @param base Default base to use to convert to number + */ +int lookup(const char *str, const TokenDict *tokens, int defvalue = 0, int base = 0); + +/** + * Utility function to look up a number in a token table + * @param value Value to search for + * @param tokens Pointer to the token table + * @param defvalue Value to return if lookup fails + */ +const char *lookup(int value, const TokenDict *tokens, const char *defvalue = 0); + + +/** + * A regular expression matching class. + * @short A regexp matching class + */ +class Regexp : public String +{ + friend class String; +public: + /** + * Creates a new, empty regexp. + */ + Regexp(); + + /** + * Creates a new initialized regexp. + * @param value Initial value of the regexp. + */ + Regexp(const char *value); + + /** + * Copy constructor. + * @param value Initial value of the regexp. + */ + Regexp(const Regexp &value); + + /** + * Destroys the regexp, disposes the memory. + */ + virtual ~Regexp(); + + /** + * Assignment from char* operator. + */ + inline Regexp& operator=(const char *value) + { String::operator=(value); return *this; } + + /** + * Makes sure the regular expression is compiled + * @return True if successfully compiled, false on error + */ + bool compile(); + + /** + * Checks if the pattern matches a given value + * @param value String to check for match + * @return True if matches, false otherwise + */ + bool matches(const char *value); + + /** + * Checks if the pattern matches a string + * @param value String to check for match + * @return True if matches, false otherwise + */ + virtual bool matches(const String &value) + { return matches(value.safe()); } + +protected: + /** + * Called whenever the value changed (except in constructors) to recompile. + */ + virtual void changed(); + +private: + void cleanup(); + bool matches(const char *value, StringMatchPrivate *matches); + void *m_regexp; +}; + +/** + * A string class with a hashed string name + * @short A named string class. + */ +class NamedString : public String +{ +public: + /** + * Creates a new named string. + * @param name Name of this string + * @param value Initial value of the string. + */ + NamedString(const char *name, const char *value = 0); + + /** + * Retrive the name of this string. + * @return A hashed string with the name of the string + */ + inline const String& name() const + { return m_name; } + +private: + NamedString(); // no default constructor please + String m_name; +}; + +/** + * The Time class holds a time moment with microsecond accuracy + * @short A time holding class + */ +class Time : public GenObject +{ +public: + /** + * Constructs a Time object from the current time + */ + Time() + : m_time(now()) { } + + /** + * Constructs a Time object from a given time + * @param usec Time in microseconds + */ + Time(unsigned long long usec) + : m_time(usec) { } + + /** + * Constructs a Time object from a timeval structure + * @param tv Pointer to the timeval structure + */ + Time(struct timeval *tv) + : m_time(fromTimeval(tv)) { } + + /** + * Get time in seconds + * @return Time in seconds since the Epoch + */ + inline unsigned long sec() const + { return (m_time+500000) / 1000000; } + + /** + * Get time in milliseconds + * @return Time in milliseconds since the Epoch + */ + inline unsigned long long msec() const + { return (m_time+500) / 1000; } + + /** + * Get time in microseconds + * @return Time in microseconds since the Epoch + */ + inline unsigned long long usec() const + { return m_time; } + + /** + * Conversion to microseconds operator + */ + inline operator unsigned long long() const + { return m_time; } + + /** + * Assignment operator. + */ + inline Time& operator=(unsigned long long usec) + { m_time = usec; return *this; } + + /** + * Offsetting operator. + */ + inline Time& operator+=(long long delta) + { m_time += delta; return *this; } + + /** + * Offsetting operator. + */ + inline Time& operator-=(long long delta) + { m_time -= delta; return *this; } + + /** + * Fill in a timeval struct from a value in microseconds + * @param tv Pointer to the timeval structure + */ + inline void toTimeval(struct timeval *tv) const + { toTimeval(tv, m_time); } + + /** + * Fill in a timeval struct from a value in microseconds + * @param tv Pointer to the timeval structure + * @param usec Time to convert to timeval + */ + static void toTimeval(struct timeval *tv, unsigned long long usec); + + /** + * Convert time in a timeval struct to microseconds + * @param tv Pointer to the timeval structure + * @return Corresponding time in microseconds or zero if tv is NULL + */ + static unsigned long long fromTimeval(struct timeval *tv); + + /** + * Get the current system time in microseconds + * @return Time in microseconds since the Epoch + */ + static unsigned long long now(); + +private: + unsigned long long m_time; +}; + +/** + * This class holds a named list of named strings + * @short A named string container class + */ +class NamedList : public String +{ +public: + /** + * Creates a new named list. + * @param name Name of the list - must not be NULL or empty + */ + NamedList(const char *name); + + /** + * Get the number of parameters + * @return Count of named strings + */ + inline unsigned int length() const + { return m_params.length(); } + + /** + * Add a named string to the parameter list. + * @param param Parameter to add + */ + NamedList &addParam(NamedString *param); + + /** + * Add a named string to the parameter list. + * @param name Name of the new string + * @param value Value of the new string + */ + NamedList &addParam(const char *name, const char *value); + + /** + * Set a named string in the parameter list. + * @param param Parameter to set or add + */ + NamedList &setParam(NamedString *param); + + /** + * Set a named string in the parameter list. + * @param name Name of the string + * @param value Value of the string + */ + NamedList &setParam(const char *name, const char *value); + + /** + * Clars all instances of a named string in the parameter list. + * @param name Name of the string to remove + */ + NamedList &clearParam(const String &name); + + /** + * Locate a named string in the parameter list. + * @param name Name of parameter to locate + * @return A pointer to the named string or NULL. + */ + NamedString *getParam(const String &name) const; + + /** + * Locate a named string in the parameter list. + * @param index Index of the parameter to locate + * @return A pointer to the named string or NULL. + */ + NamedString *getParam(unsigned int index) const; + + /** + * Retrive the value of a named parameter. + * @param name Name of parameter to locate + * @param defvalue Default value to return if not found + * @return The string contained in the named parameter or the default + */ + const char *getValue(const String &name, const char *defvalue = 0) const; + +private: + NamedList(); // no default constructor please + NamedList(const NamedList &value); // no copy constructor + NamedList& operator=(const NamedList &value); // no assignment please + ObjList m_params; +}; + +/** + * A class for parsing and quickly accessing INI style configuration files + * @short Configuration file handling + */ +class Configuration : public String +{ +public: + /** + * Create an empty configuration + */ + Configuration(); + + /** + * Create a configuration from a file + * @param filename Name of file to initialize from + */ + Configuration(const char *filename); + + /** + * Assignment from string operator + */ + inline Configuration& operator=(const String &value) + { String::operator=(value); return *this; } + + /** + * Get the number of sections + * @return Count of sections + */ + inline unsigned int sections() const + { return m_sections.length(); } + + /** + * Retrive an entire section + * @param index Index of the section + * @return The section's content or NULL if no such section + */ + NamedList *getSection(unsigned int index) const; + + /** + * Retrive an entire section + * @param sect Name of the section + * @return The section's content or NULL if no such section + */ + NamedList *getSection(const String §) const; + + /** + * Locate a key/value pair in the section. + * @param sect Name of the section + * @param key Name of the key in section + * @return A pointer to the key/value pair or NULL. + */ + NamedString *getKey(const String §, const String &key) const; + + /** + * Retrive the value of a key in a section. + * @param sect Name of the section + * @param key Name of the key in section + * @param defvalue Default value to return if not found + * @return The string contained in the key or the default + */ + const char *getValue(const String §, const String &key, const char *defvalue = 0) const; + + /** + * Retrive the numeric value of a key in a section. + * @param sect Name of the section + * @param key Name of the key in section + * @param defvalue Default value to return if not found + * @return The number contained in the key or the default + */ + int getIntValue(const String §, const String &key, int defvalue = 0) const; + + /** + * Retrive the numeric value of a key in a section trying first a table lookup. + * @param sect Name of the section + * @param key Name of the key in section + * @param tokens A pointer to an array of tokens to try to lookup + * @param defvalue Default value to return if not found + * @return The number contained in the key or the default + */ + int getIntValue(const String §, const String &key, const TokenDict *tokens, int defvalue = 0) const; + + /** + * Retrive the boolean value of a key in a section. + * @param sect Name of the section + * @param key Name of the key in section + * @param defvalue Default value to return if not found + * @return The boolean value contained in the key or the default + */ + bool getBoolValue(const String §, const String &key, bool defvalue = false) const; + + /** + * Deletes an entire section + * @param sect Name of section to delete, NULL to delete all + */ + void clearSection(const char *sect = 0); + + /** + * Deletes a key/value pair + * @param sect Name of section + * @param key Name of the key to delete + */ + void clearKey(const String §, const String &key); + + /** + * Set the value of a key in a section. + * @param sect Name of the section, will be created if missing + * @param key Name of the key in section, will be created if missing + * @param value Value to set in the key + */ + void setValue(const String §, const char *key, const char *value = 0); + + /** + * Set the numeric value of a key in a section. + * @param sect Name of the section, will be created if missing + * @param key Name of the key in section, will be created if missing + * @param value Value to set in the key + */ + void setValue(const String §, const char *key, int value); + + /** + * Set the boolean value of a key in a section. + * @param sect Name of the section, will be created if missing + * @param key Name of the key in section, will be created if missing + * @param value Value to set in the key + */ + void setValue(const String §, const char *key, bool value); + + /** + * Load the configuration from file + * @return True if successfull, false for failure + */ + bool load(); + + /** + * Save the configuration to file + * @return True if successfull, false for failure + */ + bool save() const; + +private: + Configuration(const Configuration &value); // no copy constructor + Configuration& operator=(const Configuration &value); // no assignment please + ObjList *getSectHolder(const String §) const; + ObjList *makeSectHolder(const String §); + ObjList m_sections; +}; + +class MessageDispatcher; + +/** + * This class holds the messages that are moved around in the engine. + * @short A message container class + */ +class Message : public NamedList +{ + friend class MessageDispatcher; +public: + /** + * Creates a new message. + * + * @param name Name of the message - must not be NULL or empty + * @param retval Default return value + */ + Message(const char *name, const char *retval = 0); + + /** + * Retrive a reference to the value returned by the message. + * @return A reference to the value the message will return + */ + inline String &retValue() + { return m_return; } + + /** + * Retrive the obscure data associated with the message + * @return Pointer to arbitrary user data + */ + inline void *userData() const + { return m_data; } + + /** + * Set obscure data associated with the message + * @param _data Pointer to arbitrary user data + */ + inline void userData(void *_data) + { m_data = _data; } + + /** + * Retrive a reference to the creation time of the message. + * @return A reference to the Time when the message was created + */ + inline Time &msgTime() + { return m_time; } + + /** + * Name assignment operator + */ + inline Message& operator=(const char *value) + { String::operator=(value); return *this; } + + /** + * Encode the message into a string adequate for sending for processing + * to an external communication interface + * @param id Unique identifier to add to the string + */ + String encode(const char *id) const; + + /** + * Encode the message into a string adequate for sending as answer + * to an external communication interface + * @param received True if message was processed locally + * @param id Unique identifier to add to the string + */ + String encode(bool received, const char *id) const; + + /** + * Decode a string from an external communication interface for processing + * in the engine. The message is modified accordingly. + * @param str String to decode + * @param id A String object in which the identifier is stored + * @return -2 for success, -1 if the string was not a text form of a + * message, index of first erroneous character if failed + */ + int decode(const char *str, String &id); + + /** + * Decode a string from an external communication interface that is an + * answer to a specific external processing request. + * @param str String to decode + * @param received Pointer to variable to store the dispatch return value + * @param id The identifier expected + * @return -2 for success, -1 if the string was not the expected answer, + * index of first erroneous character if failed + */ + int decode(const char *str, bool &received, const char *id); + +protected: + /** + * Notify the message it has been dispatched + */ + virtual void dispatched(bool accepted) + { } + +private: + Message(); // no default constructor please + Message(const Message &value); // no copy constructor + Message& operator=(const Message &value); // no assignment please + String m_return; + Time m_time; + void *m_data; + void commonEncode(String &str) const; + int commonDecode(const char *str, int offs); +}; + +/** + * A message handler + */ +class MessageHandler : public String +{ + friend class MessageDispatcher; +public: + /** + * Creates a new message handler. + * @param name Name of the handled message - may be NULL + * @param priority Priority of the handler, 0 = top + */ + MessageHandler(const char *name, unsigned priority = 100); + + /** + * Handler destructor. + */ + virtual ~MessageHandler(); + + /** + * This method is called whenever the registered name matches the message. + * @param msg The received message + * @return True to stop processing, false to try other handlers + */ + virtual bool received(Message &msg) = 0; + + /** + * Find out the priority of the handler + * @return Stored priority of the handler, 0 = top + */ + inline unsigned priority() const + { return m_priority; } + +private: + unsigned m_priority; + MessageDispatcher *m_dispatcher; +}; + +/** + * A message receiver to be invoked by a message relay; + */ +class MessageReceiver : public GenObject +{ +public: + /** + * This method is called from the message relay. + * @param msg The received message + * @param id The identifier with which the relay was created + * @return True to stop processing, false to try other handlers + */ + virtual bool received(Message &msg, int id) = 0; +}; + +/** + * A message handler that allows to relay several messages to a single receiver + */ +class MessageRelay : public MessageHandler +{ +public: + /** + * Creates a new message relay. + * @param name Name of the handled message - may be NULL + * @param receiver Receiver of th relayed messages + * @param id Numeric identifier to pass to receiver + * @param priority Priority of the handler, 0 = top + */ + MessageRelay(const char *name, MessageReceiver *receiver, int id, int priority = 1) + : MessageHandler(name,priority), m_receiver(receiver), m_id(id) { } + + /** + * This method is called whenever the registered name matches the message. + * @param msg The received message + * @return True to stop processing, false to try other handlers + */ + virtual bool received(Message &msg) + { return m_receiver ? m_receiver->received(msg,m_id) : false; } + +private: + MessageReceiver *m_receiver; + int m_id; +}; + +class MutexPrivate; +class ThreadPrivate; + +/** + * Mutex support + */ +class Mutex +{ + friend class MutexPrivate; +public: + /** + * Construct a new unlocked mutex + */ + Mutex(); + + /** + * Copt constructor creates a shared mutex + * @param original Reference of the mutex to share + */ + Mutex(const Mutex &orginal); + + /** + * Destroy the mutex + */ + ~Mutex(); + + /** + * Assignment operator makes the mutex shared with the original + * @param original Reference of the mutex to share + */ + Mutex& operator=(const Mutex &original); + + /** + * Attempt to lock the mutex and eventually wait for it + * @param maxait Time in microseconds to wait for the mutex, -1 wait forever + * @return True if successfully locked, false on failure + */ + bool lock(long long int maxwait = -1); + + /** + * Unlock the mutex, does never wait + */ + void unlock(); + + /** + * Get the number of mutexes counting the shared ones only once + * @return Count of individual mutexes + */ + static int count(); + + /** + * Get the number of currently locked mutexes + * @return Count of locked mutexes, should be zero at program exit + */ + static int locks(); + +private: + MutexPrivate *privDataCopy() const; + MutexPrivate *m_private; +}; + +/** + * A lock is a stack allocated (automatic) object that locks a mutex on + * creation and unlocks it on destruction - typically when exiting a block + * @short Mutex locking object + */ +class Lock +{ +public: + /** + * Create the lock, try to lock the mutex + * @param mutex Reference to the mutex to lock + * @param maxait Time in microseconds to wait for the mutex, -1 wait forever + */ + inline Lock(Mutex &mutex, long long int maxwait = -1) + { m_mutex = mutex.lock(maxwait) ? &mutex : 0; } + + /** + * Destroy the lock, unlock the mutex if it was locked + */ + inline ~Lock() + { if (m_mutex) m_mutex->unlock(); } + + /** + * Return a pointer to the mutex this lock holds + * @return A mutex pointer or NULL if locking failed + */ + inline Mutex *mutex() const + { return m_mutex; } + +private: + Mutex *m_mutex; +}; + +/** + * Thread support class + */ +class Thread +{ + friend class ThreadPrivate; +public: + /** + * This method is called in the newly created thread. + * When it returns the thread terminates. + */ + virtual void run() = 0; + + /** + * This method is called when the current thread terminates. + */ + virtual void cleanup(); + + /** + * Check if the thread creation failed + * @return True if an error occured, false if created ok + */ + bool error() const; + + /** + * Check if the thread is running or not + * @return True if running, false if it has terminated + */ + bool running() const; + + /** + * Give up the currently running timeslice + */ + static void yield(); + + /** + * Get a pointer to the currently running thread + * @return A pointer to the current thread or NULL for main thread + */ + static Thread *current(); + + /** + * Get the number of threads + * @return Count of threads except the main one + */ + static int count(); + + /** + * Terminates the current thread. + */ + static void exit(); + + /** + * Terminates the specified thread. + */ + void cancel(); + + /** + * Kills all other running threads. Ouch! + * Must be called from the main thread or it does nothing. + */ + static void killall(); + + /** + * Create a child process - reexport the pthread altered version. + * You must not call ::fork() as this is not portable in pthread. + * See the manual page for fork (2) for details. + */ + static int fork(); + + /** + * On some platforms this method kills all other running threads. + * Must be called after Thread::fork() but before any exec*() call. + */ + static void preExec(); + +protected: + /** + * Creates and starts a new thread + * @param name Static name of the thread (for debugging purpose only) + */ + Thread(const char *name = 0); + + /** + * The destructor is called when the thread terminates + */ + virtual ~Thread(); + +private: + ThreadPrivate *m_private; +}; + +/** + * A message dispatcher + */ +class MessageDispatcher : public GenObject +{ +public: + /** + * Creates a new message dispatcher. + */ + MessageDispatcher(); + + /** + * Destroys the dispatcher and the installed handlers. + */ + ~MessageDispatcher(); + + /** + * Installs a handler in the dispatcher. + * @param handler A pointer to the handler to install + * @return True on success, false on failure + */ + bool install(MessageHandler *handler); + + /** + * Uninstalls a handler from the dispatcher. + * @param handler A pointer to the handler to uninstall + * @return True on success, false on failure + */ + bool uninstall(MessageHandler *handler); + + /** + * Dispatch a message to the installed handlers + * @param msg The message to dispatch + * @return True if one handler accepted it, false if all ignored + */ + bool dispatch(Message &msg); + + /** + * Get the number of messages waiting in the queue + * @return Count of messages in the queue + */ + inline unsigned int queueLength() const + { return m_messages.count(); } + + /** + * Put a message in the waiting queue + * @param msg The message to enqueue, will be destroyed after dispatching + * @return True if successfully queued, false otherwise + */ + bool enqueue(Message *msg); + + /** + * Dispatch all messages from the waiting queue + */ + void dequeue(); + + /** + * Dispatch one message from the waiting queue + * @return True if success, false if the queue is empty + */ + bool dequeueOne(); + + /** + * Clear all the message handlers + */ + inline void clear() + { m_handlers.clear(); } + + /** + * Install or remove a hook to catch messages after being dispatched + * @param hookFunc Pointer to a callback function + */ + inline void setHook(void (*hookFunc)(Message &, bool) = 0) + { m_hook = hookFunc; } + +private: + ObjList m_handlers; + ObjList m_messages; + Mutex m_mutex; + void (*m_hook)(Message &, bool); +}; + +/** + * Initialization and information about plugins. + * Plugins are located in @em shared libraries that are loaded at runtime. + * + *
+ * // Create static Plugin object by using the provided macro
+ * INIT_PLUGIN(Plugin);
+ *
+ * @short Plugin support + */ +class Plugin : public GenObject +{ +public: + /** + * Creates a new Plugin container. + * @param name the undecorated name of the library that contains the plugin + */ + Plugin(const char *name); + + /** + * Creates a new Plugin container. + * Alternate constructor which is also the default. + */ + Plugin(); + + /** + * Destroys the plugin. + * The destructor must never be called directly - the Loader will do it when @ref refCount() reaches zero. + */ + virtual ~Plugin(); + + /** + * Initialize the plugin after it was loaded and registered. + */ + virtual void initialize() = 0; +}; + +/** + * Macro to create static instance of the plugin + * @param pclass Class of the plugin to create + */ +#define INIT_PLUGIN(pclass) static pclass __plugin + +/** + * This class holds global information about the engine. + * Note: this is a singleton class. + * + * @short Engine globals + */ +class Engine +{ + friend class EnginePrivate; +public: + /** + * Main entry point to be called directly from a wrapper program + * @param argc Argument count + * @param argv Argument array + * @param environ Environment variables + * @return Program exit code + */ + static int main(int argc, const char **argv, const char **environ); + + /** + * Run the engine. + * @return Error code, 0 for success + */ + int run(); + + /** + * Get a pointer to the unique instance. + * @return A pointer to the singleton instance of the engine + */ + static Engine *self(); + + /** + * Register or unregister a plugin to the engine. + * @param plugin A pointer to the plugin to (un)register + * @param reg True to register (default), false to unregister + * @return True on success, false on failure + */ + static bool Register(const Plugin *plugin, bool reg = true); + + /** + * The configuration directory path + */ + inline static String configFile(const char *name) + { return s_cfgpath+"/"+name+s_cfgsuffix; } + + /** + * The configuration directory path + */ + inline static String &configPath() + { return s_cfgpath; } + + /** + * The module loading path + */ + inline static String &modulePath() + { return s_modpath; } + + /** + * The module suffix + */ + inline static String &moduleSuffix() + { return s_modsuffix; } + + /** + * Reinitialize the plugins + */ + static void init(); + + /** + * Stop the engine and the entire program + * @param code Return code of the program + */ + static void halt(unsigned int code); + + /** + * Check if the engine is currently exiting + * @return True if exiting, false in normal operation + */ + static bool exiting() + { return (s_haltcode != -1); } + + /** + * Installs a handler in the dispatcher. + * @param handler A pointer to the handler to install + * @return True on success, false on failure + */ + static bool install(MessageHandler *handler); + + /** + * Uninstalls a handler drom the dispatcher. + * @param handler A pointer to the handler to uninstall + * @return True on success, false on failure + */ + static bool uninstall(MessageHandler *handler); + + /** + * Enqueue a message in the message queue + * @param msg Pointer to the message to enqueue + * @return True if enqueued, false on error (already queued) + */ + static bool enqueue(Message *msg); + + /** + * Convenience function. + * Enqueue a new parameterless message in the message queue + * @param name Name of the empty message to put in queue + * @return True if enqueued, false on error (already queued) + */ + inline static bool enqueue(const char *name) + { return (name && *name) ? enqueue(new Message(name)) : false; } + + /** + * Dispatch a message to the registered handlers + * @param msg Pointer to the message to dispatch + * @return True if one handler accepted it, false if all ignored + */ + static bool dispatch(Message *msg); + + /** + * Dispatch a message to the registered handlers + * @param msg The message to dispatch + * @return True if one handler accepted it, false if all ignored + */ + static bool dispatch(Message &msg); + + /** + * Convenience function. + * Dispatch a parameterless message to the registered handlers + * @param name The name of the message to create and dispatch + * @return True if one handler accepted it, false if all ignored + */ + static bool dispatch(const char *name); + + /** + * Install or remove a hook to catch messages after being dispatched + * @param hookFunc Pointer to a callback function + */ + inline void setHook(void (*hookFunc)(Message &, bool) = 0) + { m_dispatcher.setHook(hookFunc); } + +protected: + /** + * Destroys the engine and everything. You must not call it directly, + * @ref run() will do it for you. + */ + ~Engine(); + + /** + * Loads one plugin from a shared object file + * @return True if success, false on failure + */ + bool loadPlugin(const char *file); + + /** + * Loads the plugins from the plugins directory + */ + void loadPlugins(); + + /** + * Initialize all registered plugins + */ + void initPlugins(); + +private: + Engine(); + ObjList m_libs; + MessageDispatcher m_dispatcher; + static Engine *s_self; + static String s_cfgpath; + static String s_cfgsuffix; + static String s_modpath; + static String s_modsuffix; + static int s_haltcode; + static int s_maxworkers; + static bool s_init; + static bool s_dynplugin; +}; + +}; // namespace TelEngine + +#endif /* __TELENGINE_H */ diff --git a/yatephone.h b/yatephone.h new file mode 100644 index 00000000..4ddf0ae6 --- /dev/null +++ b/yatephone.h @@ -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 + +/** + * 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 */